Authentication refactoring.

This commit is contained in:
2026-03-11 10:41:42 +01:00
parent d69402a33d
commit b678dd5ace
12 changed files with 214 additions and 152 deletions

View File

@@ -1 +0,0 @@
// SPDX-License-Identifier: MIT

View File

@@ -1,48 +1,21 @@
// SPDX-License-Identifier: MIT
import { getAccessToken } from "../../azure/index.ts";
import { getDevOpsApiToken } from "../../devops/index.ts";
import { loadAuthConfig } from "../../index.ts";
type GetTokenOptions = {
type?: string;
};
import { RESOURCE_SCOPE_BY_NAME, ResourceName, supportedResourceNames, getTokenCredential } from "../../azure/index.ts";
export async function runGetTokenCommand(
options: GetTokenOptions,
): Promise<unknown> {
const tokenType = (options.type ?? "").toString().trim().toLowerCase();
if (!tokenType) {
throw new Error(
"--type is required for get-token (allowed: azurerm, devops)",
);
type: ResourceName,
): Promise<void> {
if (!type || !supportedResourceNames().includes(type)) {
throw new Error(`Token type is required for get-token (allowed: ${supportedResourceNames().join(", ")})`);
}
const config = await loadAuthConfig("public-config");
const credential = await getTokenCredential();
if (tokenType === "azurerm") {
const accessToken = await getAccessToken(config.tenantId, config.clientId, ["arm"]);
if (!accessToken) {
throw new Error("Failed to obtain AzureRM token");
}
return {
tokenType,
accessToken,
};
const accessToken = await credential.getToken(RESOURCE_SCOPE_BY_NAME[type]);
if (!accessToken) {
throw new Error("Failed to obtain access token.");
}
if (tokenType === "devops") {
const accessToken = await getDevOpsApiToken(
config.tenantId,
config.clientId,
);
return {
tokenType,
accessToken,
};
}
throw new Error(`Invalid --type '${options.type}'. Allowed: azurerm, devops`);
// Output only the token string for easy consumption in scripts
console.log(accessToken.token);
}

View File

@@ -1,25 +1,35 @@
// SPDX-License-Identifier: MIT
import { login } from "../../azure/index.ts";
import type { ResourceName } from "../../azure/index.ts";
import { loadAuthConfig } from "../../index.ts";
type LoginOptions = {
resources?: string;
useDeviceCode?: boolean;
noBrowser?: boolean;
browser?: string;
browserName?: string;
browserProfile?: string;
};
export async function runLoginCommand(options: LoginOptions): Promise<unknown> {
type LoginResult = {
accountUpn: string | null;
resources: Array<{ resource: string; expiresOn: string | null }>;
flow: "device-code" | "interactive";
browserLaunchAttempted: boolean;
};
export async function runLoginCommand(resources: ResourceName[], options: LoginOptions): Promise<void> {
const config = await loadAuthConfig("public-config");
return login(
const result = await login(
config.tenantId,
config.clientId,
options.resources,
resources,
Boolean(options.useDeviceCode),
Boolean(options.noBrowser),
options.browser,
options.browserName,
options.browserProfile,
);
) as LoginResult;
console.log(`Logged in as ${result.accountUpn ?? "<unknown>"} using ${result.flow} flow for resources: ${resources.join(",")}`);
}

View File

@@ -7,7 +7,28 @@ type LogoutOptions = {
all?: boolean;
};
export async function runLogoutCommand(options: LogoutOptions): Promise<unknown> {
type LogoutResult = {
clearedAll: boolean;
signedOut: string[];
};
export async function runLogoutCommand(options: LogoutOptions): Promise<void> {
const config = await loadAuthConfig("public-config");
return logout(config.tenantId, config.clientId, Boolean(options.all));
const result = await logout(config.tenantId, config.clientId, Boolean(options.all)) as LogoutResult;
if (result.signedOut.length === 0) {
console.log(
result.clearedAll
? "Cleared all cached accounts."
: "No active account to sign out.",
);
return;
}
if (result.clearedAll) {
console.log(`Cleared all cached accounts: ${result.signedOut.join(", ")}`);
return;
}
console.log(`Signed out: ${result.signedOut.join(", ")}`);
}

View File

@@ -1,8 +1,6 @@
// SPDX-License-Identifier: MIT
import { getAccessToken } from "../../azure/index.ts";
import { getDevOpsApiToken } from "../../devops/index.ts";
import { loadAuthConfig } from "../../index.ts";
import { RESOURCE_SCOPE_BY_NAME, ResourceName, getTokenCredential } from "../../azure/index.ts";
function parseHeaderLine(
header?: string,
@@ -35,24 +33,39 @@ function hasAuthorizationHeader(headers: Headers): boolean {
return false;
}
function resolveResourceNameForHost(host: string): ResourceName | null {
switch (host) {
case "management.azure.com":
return "azurerm";
case "dev.azure.com":
return "devops";
case "graph.microsoft.com":
return "graph";
case "cognitiveservices.azure.com":
return "openai";
default:
if (host.endsWith(".openai.azure.com")) {
return "openai";
}
return null;
}
}
async function getAutoAuthorizationHeader(url: URL): Promise<string | null> {
const host = url.hostname.toLowerCase();
if (host !== "management.azure.com" && host !== "dev.azure.com") {
const resourceName = resolveResourceNameForHost(host);
if (!resourceName) {
return null;
}
const config = await loadAuthConfig("public-config");
const credential = await getTokenCredential();
if (host === "management.azure.com") {
const accessToken = await getAccessToken(config.tenantId, config.clientId, ["arm"]);
if (!accessToken) {
throw new Error("Failed to obtain AzureRM token");
}
return `Bearer ${accessToken}`;
const accessToken = await credential.getToken(RESOURCE_SCOPE_BY_NAME[resourceName]);
if (!accessToken?.token) {
throw new Error(`Failed to obtain ${resourceName} token`);
}
const accessToken = await getDevOpsApiToken(config.tenantId, config.clientId);
return `Bearer ${accessToken}`;
return `Bearer ${accessToken.token}`;
}
type httpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";

View File

@@ -0,0 +1,6 @@
// SPDX-License-Identifier: MIT
// Hidden test command for development purposes
export async function runTestCommand(): Promise<void> {
console.log("Test command executed.");
}