239 lines
5.3 KiB
JavaScript
Executable File
239 lines
5.3 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
|
|
import fs from "node:fs";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import readline from "node:readline";
|
|
import { spawnSync } from "node:child_process";
|
|
|
|
function runAz(args, options = {}) {
|
|
const result = spawnSync("az", args, {
|
|
encoding: "utf8",
|
|
stdio: options.quiet
|
|
? ["ignore", "ignore", "ignore"]
|
|
: ["ignore", "pipe", "pipe"],
|
|
});
|
|
|
|
if (result.error) {
|
|
throw result.error;
|
|
}
|
|
|
|
if (result.status !== 0 && options.allowFailure !== true) {
|
|
throw new Error(
|
|
(result.stderr || "").trim() || `az ${args.join(" ")} failed`,
|
|
);
|
|
}
|
|
|
|
return {
|
|
status: result.status ?? 1,
|
|
stdout: (result.stdout || "").trim(),
|
|
stderr: (result.stderr || "").trim(),
|
|
};
|
|
}
|
|
|
|
async function main() {
|
|
const argv = process.argv.slice(2);
|
|
const usageText = `Usage: ${path.basename(process.argv[1])} [options]
|
|
Options:
|
|
-n, --app-name <name> Application display name (required)
|
|
-h, --help Show this help message and exit`;
|
|
let appName = "";
|
|
|
|
for (let i = 0; i < argv.length; i += 1) {
|
|
const arg = argv[i];
|
|
if (arg === "-h" || arg === "--help") {
|
|
console.log(usageText);
|
|
process.exit(0);
|
|
}
|
|
|
|
if (arg === "-n" || arg === "--app-name") {
|
|
appName = argv[i + 1] || "";
|
|
i += 1;
|
|
continue;
|
|
}
|
|
|
|
if (arg.startsWith("-")) {
|
|
console.error(`Unknown option: ${arg}`);
|
|
console.error("Use -h or --help for usage information.");
|
|
process.exit(1);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
if (!appName) {
|
|
console.error("Error: Application name is required.");
|
|
console.log(usageText);
|
|
process.exit(1);
|
|
}
|
|
|
|
let appId = runAz([
|
|
"ad",
|
|
"app",
|
|
"list",
|
|
"--display-name",
|
|
appName,
|
|
"--query",
|
|
"[0].appId",
|
|
"-o",
|
|
"tsv",
|
|
]).stdout;
|
|
|
|
let userConfirmation = "";
|
|
if (appId) {
|
|
const rl = readline.createInterface({
|
|
input: process.stdin,
|
|
output: process.stdout,
|
|
});
|
|
userConfirmation = await new Promise((resolve) => {
|
|
rl.question(
|
|
`Application '${appName}' already exists. Update it? [y/N]: `,
|
|
(answer) => {
|
|
rl.close();
|
|
resolve(answer.trim());
|
|
},
|
|
);
|
|
});
|
|
|
|
if (!/^(yes|y)$/i.test(userConfirmation)) {
|
|
console.log("Canceled.");
|
|
process.exit(0);
|
|
}
|
|
}
|
|
|
|
if (!appId) {
|
|
appId = runAz([
|
|
"ad",
|
|
"app",
|
|
"create",
|
|
"--display-name",
|
|
appName,
|
|
"--query",
|
|
"appId",
|
|
"-o",
|
|
"tsv",
|
|
]).stdout;
|
|
|
|
if (!appId) {
|
|
console.error(`Error: Failed to create application '${appName}'.`);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
const requiredResourceAccess = [
|
|
{
|
|
resourceAppId: "00000003-0000-0000-c000-000000000000",
|
|
resourceAccess: [
|
|
{ id: "0e263e50-5827-48a4-b97c-d940288653c7", type: "Scope" },
|
|
],
|
|
},
|
|
{
|
|
resourceAppId: "499b84ac-1321-427f-aa17-267ca6975798",
|
|
resourceAccess: [
|
|
{ id: "ee69721e-6c3a-468f-a9ec-302d16a4c599", type: "Scope" },
|
|
],
|
|
},
|
|
{
|
|
resourceAppId: "797f4846-ba00-4fd7-ba43-dac1f8f63013",
|
|
resourceAccess: [
|
|
{ id: "41094075-9dad-400e-a0bd-54e686782033", type: "Scope" },
|
|
],
|
|
},
|
|
];
|
|
|
|
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "create-pca-"));
|
|
const requiredResourceAccessFile = path.join(
|
|
tempDir,
|
|
"required-resource-accesses.json",
|
|
);
|
|
|
|
try {
|
|
fs.writeFileSync(
|
|
requiredResourceAccessFile,
|
|
JSON.stringify(requiredResourceAccess, null, 2),
|
|
"utf8",
|
|
);
|
|
|
|
try {
|
|
runAz(
|
|
[
|
|
"ad",
|
|
"app",
|
|
"update",
|
|
"--id",
|
|
appId,
|
|
"--sign-in-audience",
|
|
"AzureADMyOrg",
|
|
"--is-fallback-public-client",
|
|
"true",
|
|
"--required-resource-accesses",
|
|
`@${requiredResourceAccessFile}`,
|
|
"--public-client-redirect-uris",
|
|
"http://localhost",
|
|
`msal${appId}://auth`,
|
|
"--enable-access-token-issuance",
|
|
"true",
|
|
"--enable-id-token-issuance",
|
|
"true",
|
|
],
|
|
{ quiet: true },
|
|
);
|
|
} catch {
|
|
console.error(
|
|
`Error: Failed to configure application '${appName}' (${appId}).`,
|
|
);
|
|
process.exit(1);
|
|
}
|
|
} finally {
|
|
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
}
|
|
|
|
runAz(["ad", "sp", "create", "--id", appId], {
|
|
quiet: true,
|
|
allowFailure: true,
|
|
});
|
|
|
|
const adminConsentResult = runAz(
|
|
["ad", "app", "permission", "admin-consent", "--id", appId],
|
|
{ quiet: true, allowFailure: true },
|
|
);
|
|
if (adminConsentResult.status !== 0) {
|
|
console.error(
|
|
`Error: Failed to grant admin consent for '${appName}' (${appId}).`,
|
|
);
|
|
process.exit(1);
|
|
}
|
|
|
|
const tenantId = runAz([
|
|
"account",
|
|
"show",
|
|
"--query",
|
|
"tenantId",
|
|
"-o",
|
|
"tsv",
|
|
]).stdout;
|
|
if (!tenantId) {
|
|
console.error(
|
|
"Error: Failed to resolve tenantId from current Azure CLI context.",
|
|
);
|
|
process.exit(1);
|
|
}
|
|
|
|
if (userConfirmation) {
|
|
console.log(`Updated application '${appName}'`);
|
|
} else {
|
|
console.log(`Created application '${appName}'`);
|
|
}
|
|
console.log(`appId: ${appId}`);
|
|
console.log(`export const config = {
|
|
"appName": "${appName}",
|
|
"tenantId": "${tenantId}",
|
|
"clientId": "${appId}"
|
|
};`);
|
|
}
|
|
|
|
main().catch((err) => {
|
|
console.error(`Error: ${err.message}`);
|
|
process.exit(1);
|
|
});
|