// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { AzureCliCredential, ChainedTokenCredential, DefaultAzureCredential, TokenCredential } from "@azure/identity";
import { AccountInfo, AuthenticationResult, PublicClientApplication } from "@azure/msal-node";
import open from "open";
const scopes = ["499b84ac-1321-427f-aa17-267ca6975798/.default"];
class OAuthAuthenticator {
static clientId = "0d50963b-7bb9-4fe7-94c7-a99af00b5136";
static defaultAuthority = "https://login.microsoftonline.com/common";
static zeroTenantId = "00000000-0000-0000-0000-000000000000";
private accountId: AccountInfo | null;
private publicClientApp: PublicClientApplication;
constructor(tenantId?: string) {
this.accountId = null;
let authority = OAuthAuthenticator.defaultAuthority;
if (tenantId && tenantId !== OAuthAuthenticator.zeroTenantId) {
authority = `https://login.microsoftonline.com/${tenantId}`;
}
this.publicClientApp = new PublicClientApplication({
auth: {
clientId: OAuthAuthenticator.clientId,
authority,
},
});
}
public async getToken(): Promise<string> {
let authResult: AuthenticationResult | null = null;
if (this.accountId) {
try {
authResult = await this.publicClientApp.acquireTokenSilent({
scopes,
account: this.accountId,
});
} catch (error) {
authResult = null;
}
}
if (!authResult) {
authResult = await this.publicClientApp.acquireTokenInteractive({
scopes,
openBrowser: async (url) => {
open(url);
},
});
this.accountId = authResult.account;
}
if (!authResult.accessToken) {
throw new Error("Failed to obtain Azure DevOps OAuth token.");
}
return authResult.accessToken;
}
}
function createAuthenticator(type: string, tenantId?: string): () => Promise<string> {
switch (type) {
case "pat":
// Use Personal Access Token from environment variable
return async () => {
const token = process.env.AZURE_DEVOPS_PAT || process.env.ADO_PAT;
if (!token) {
throw new Error(
"Personal Access Token not found. Please set AZURE_DEVOPS_PAT or ADO_PAT environment variable."
);
}
return token;
};
case "azcli":
case "env":
if (type !== "env") {
process.env.AZURE_TOKEN_CREDENTIALS = "dev";
}
let credential: TokenCredential = new DefaultAzureCredential(); // CodeQL [SM05138] resolved by explicitly setting AZURE_TOKEN_CREDENTIALS
if (tenantId) {
// Use Azure CLI credential if tenantId is provided for multi-tenant scenarios
const azureCliCredential = new AzureCliCredential({ tenantId });
credential = new ChainedTokenCredential(azureCliCredential, credential);
}
return async () => {
const result = await credential.getToken(scopes);
if (!result) {
throw new Error("Failed to obtain Azure DevOps token. Ensure you have Azure CLI logged or use interactive type of authentication.");
}
return result.token;
};
default:
const authenticator = new OAuthAuthenticator(tenantId);
return () => {
return authenticator.getToken();
};
}
}
export { createAuthenticator };