Authentication refactoring.
This commit is contained in:
@@ -8,7 +8,10 @@
|
||||
|
||||
import { getTokenUsingMsal } from "./pca-auth.ts";
|
||||
import { getTokenUsingAzureIdentity } from "./client-auth.ts";
|
||||
import { loadConfig } from "../index.ts";
|
||||
import { loadAuthConfig, loadConfig } from "../index.ts";
|
||||
import { SkAzureCredential } from "./sk-credential.ts";
|
||||
import { DefaultAzureCredential } from "@azure/identity";
|
||||
import type { TokenCredential } from "@azure/core-auth";
|
||||
|
||||
// Reexporting functions and types from submodules
|
||||
export {
|
||||
@@ -24,21 +27,42 @@ export { getCredential } from "./client-auth.ts";
|
||||
export const RESOURCE_SCOPE_BY_NAME = {
|
||||
graph: "https://graph.microsoft.com/.default",
|
||||
devops: "499b84ac-1321-427f-aa17-267ca6975798/.default",
|
||||
arm: "https://management.azure.com/.default",
|
||||
azurerm: "https://management.azure.com/.default",
|
||||
openai: "https://cognitiveservices.azure.com/.default",
|
||||
} as const;
|
||||
|
||||
export type ResourceName = keyof typeof RESOURCE_SCOPE_BY_NAME;
|
||||
export const DEFAULT_RESOURCES: ResourceName[] = ["graph", "devops", "arm"];
|
||||
export const DEFAULT_RESOURCES: ResourceName[] = ["graph", "devops", "azurerm"];
|
||||
|
||||
// A helper function to translate short resource names to their corresponding scopes
|
||||
export function translateResourceNamesToScopes(resourceNames: string[]): string[] {
|
||||
return resourceNames.map((name) => RESOURCE_SCOPE_BY_NAME[name as ResourceName]);
|
||||
}
|
||||
|
||||
export function supportedResourceNames(): ResourceName[] {
|
||||
return Object.keys(RESOURCE_SCOPE_BY_NAME) as ResourceName[];
|
||||
}
|
||||
|
||||
// Generic utility functions
|
||||
export type AuthMode = "azure-identity" | "msal";
|
||||
|
||||
export async function getTokenCredential(
|
||||
tenantId?: string,
|
||||
clientId?: string,
|
||||
): Promise<TokenCredential> {
|
||||
const config = await loadConfig();
|
||||
|
||||
if (config.authMode === "azure-identity") {
|
||||
return new DefaultAzureCredential();
|
||||
}
|
||||
|
||||
const authConfig = await loadAuthConfig("public-config");
|
||||
return new SkAzureCredential(
|
||||
tenantId || authConfig.tenantId,
|
||||
clientId || authConfig.clientId,
|
||||
);
|
||||
}
|
||||
|
||||
export async function getAccessToken(
|
||||
tenantId: string,
|
||||
clientId: string,
|
||||
@@ -55,24 +79,3 @@ export async function getAccessToken(
|
||||
return getTokenUsingAzureIdentity(tenantId, clientId, resources);
|
||||
}
|
||||
}
|
||||
|
||||
// export function getAzureIdentityGraphAuthProvider(
|
||||
// tenantId: string,
|
||||
// clientId: string,
|
||||
// ) {
|
||||
// const credential = new DefaultAzureCredential({
|
||||
// tenantId,
|
||||
// managedIdentityClientId: clientId,
|
||||
// });
|
||||
|
||||
// const getBearerToken = getBearerTokenProvider(
|
||||
// credential,
|
||||
// "https://graph.microsoft.com/.default",
|
||||
// );
|
||||
|
||||
// return (done: (error: Error | null, accessToken: string | null) => void) => {
|
||||
// void getBearerToken()
|
||||
// .then((token) => done(null, token))
|
||||
// .catch((err) => done(err as Error, null));
|
||||
// };
|
||||
// }
|
||||
@@ -22,14 +22,14 @@ const LOGIN_REQUIRED_MESSAGE = "Login required. Run: sk-az-tools login";
|
||||
const BROWSER_KEYWORDS = Object.keys(apps).sort();
|
||||
const OPEN_APPS = apps as Record<string, string | readonly string[]>;
|
||||
const CHROMIUM_BROWSERS = new Set(["edge", "chrome", "brave"]);
|
||||
const CONFIG_FILE_NAME = "config";
|
||||
const SESSION_STATE_NAME = "session-state";
|
||||
|
||||
type SessionState = {
|
||||
activeAccountUpn: string | null;
|
||||
};
|
||||
|
||||
async function readSessionState(): Promise<SessionState> {
|
||||
const parsed = (await getConfig("sk-az-tools", CONFIG_FILE_NAME)) as { activeAccountUpn?: unknown };
|
||||
const parsed = (await getConfig("sk-az-tools", SESSION_STATE_NAME)) as { activeAccountUpn?: unknown };
|
||||
return {
|
||||
activeAccountUpn:
|
||||
typeof parsed?.activeAccountUpn === "string"
|
||||
@@ -39,14 +39,14 @@ async function readSessionState(): Promise<SessionState> {
|
||||
}
|
||||
|
||||
async function writeSessionState(state: SessionState): Promise<void> {
|
||||
const sessionPath = path.join(getConfigDir("sk-az-tools"), `${CONFIG_FILE_NAME}.json`);
|
||||
const sessionPath = path.join(getConfigDir("sk-az-tools"), `${SESSION_STATE_NAME}.json`);
|
||||
await mkdir(path.dirname(sessionPath), { recursive: true });
|
||||
await writeFile(sessionPath, JSON.stringify(state, null, 2), "utf8");
|
||||
}
|
||||
|
||||
async function clearSessionState(): Promise<void> {
|
||||
try {
|
||||
const sessionPath = path.join(getConfigDir("sk-az-tools"), `${CONFIG_FILE_NAME}.json`);
|
||||
const sessionPath = path.join(getConfigDir("sk-az-tools"), `${SESSION_STATE_NAME}.json`);
|
||||
await unlink(sessionPath);
|
||||
} catch (err) {
|
||||
if ((err as { code?: string } | null)?.code !== "ENOENT") {
|
||||
@@ -104,19 +104,19 @@ function getBrowserOpenOptions(browser?: string, browserProfile?: string): Param
|
||||
const browserKeyword = getBrowserKeyword(browser);
|
||||
if (!CHROMIUM_BROWSERS.has(browserKeyword)) {
|
||||
throw new Error(
|
||||
"--browser-profile is supported only with --browser edge|chrome|brave",
|
||||
"--browser-profile is supported only with --browser-name edge|chrome|brave",
|
||||
);
|
||||
}
|
||||
|
||||
if (!browserName) {
|
||||
throw new Error("--browser-profile requires --browser");
|
||||
throw new Error("--browser-profile requires --browser-name");
|
||||
}
|
||||
|
||||
return {
|
||||
wait: false,
|
||||
app: {
|
||||
name: browserName,
|
||||
arguments: [`--profile-directory=${browserProfile.trim()}`],
|
||||
arguments: [`--profile-directory=${browserProfile.trim()}`],
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -130,19 +130,18 @@ function validateBrowserOptions(browser?: string, browserProfile?: string): void
|
||||
const browserKeyword = getBrowserKeyword(browser);
|
||||
if (!CHROMIUM_BROWSERS.has(browserKeyword)) {
|
||||
throw new Error(
|
||||
"--browser-profile is supported only with --browser edge|chrome|brave",
|
||||
"--browser-profile is supported only with --browser-name edge|chrome|brave",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function parseResources(resourcesCsv?: string): ResourceName[] {
|
||||
if (!resourcesCsv || resourcesCsv.trim() === "") {
|
||||
export function parseResources(resourcesInput?: string[]): ResourceName[] {
|
||||
if (!resourcesInput || resourcesInput.length === 0) {
|
||||
return [...DEFAULT_RESOURCES];
|
||||
}
|
||||
|
||||
const resources = resourcesCsv
|
||||
.split(",")
|
||||
const resources = resourcesInput
|
||||
.map((item) => item.trim().toLowerCase())
|
||||
.filter(Boolean);
|
||||
|
||||
@@ -317,7 +316,7 @@ export async function loginDeviceCode(
|
||||
export async function login(
|
||||
tenantId: string,
|
||||
clientId: string,
|
||||
resourcesCsv?: string,
|
||||
resourcesInput?: string[],
|
||||
useDeviceCode = false,
|
||||
noBrowser = false,
|
||||
browser?: string,
|
||||
@@ -332,8 +331,8 @@ export async function login(
|
||||
if (!clientId) throw new Error("clientId is required");
|
||||
validateBrowserOptions(browser, browserProfile);
|
||||
|
||||
const resources = parseResources(resourcesCsv);
|
||||
const scopes = translateResourceNamesToScopes(resources);
|
||||
const resources = parseResources(resourcesInput);
|
||||
const scopes = translateResourceNamesToScopes(resources) as string[];
|
||||
const pca = await createPca(tenantId, clientId);
|
||||
const session = await readSessionState();
|
||||
const preferredAccount = session.activeAccountUpn
|
||||
@@ -344,6 +343,10 @@ export async function login(
|
||||
let selectedAccount: AccountInfo | null = preferredAccount;
|
||||
let token = await acquireTokenWithCache(pca, scopes, selectedAccount);
|
||||
|
||||
if (token?.account) {
|
||||
selectedAccount = token.account;
|
||||
}
|
||||
|
||||
if (!token) {
|
||||
if (useDeviceCode) {
|
||||
token = await pca.acquireTokenByDeviceCode({
|
||||
@@ -378,6 +381,11 @@ export async function login(
|
||||
});
|
||||
}
|
||||
|
||||
if (!selectedAccount) {
|
||||
const accounts = await pca.getTokenCache().getAllAccounts();
|
||||
selectedAccount = accounts[0] ?? null;
|
||||
}
|
||||
|
||||
const activeAccountUpn = selectedAccount?.username ?? null;
|
||||
if (activeAccountUpn) {
|
||||
await writeSessionState({ activeAccountUpn });
|
||||
|
||||
Reference in New Issue
Block a user