diff --git a/package-lock.json b/package-lock.json index f1bf35e4..0b34dcc8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,6 @@ "version": "0.26.0", "license": "MIT", "dependencies": { - "@azure/msal-node": "^2.10.0", "@opentelemetry/tracing": "^0.24.0", "adal-node": "^0.2.4", "applicationinsights": "^1.0.5", @@ -61,38 +60,6 @@ "vscode": "^1.81.0" } }, - "node_modules/@azure/msal-common": { - "version": "14.13.0", - "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-14.13.0.tgz", - "integrity": "sha512-b4M/tqRzJ4jGU91BiwCsLTqChveUEyFK3qY2wGfZ0zBswIBZjAxopx5CYt5wzZFKuN15HqRDYXQbztttuIC3nA==", - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@azure/msal-node": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-2.10.0.tgz", - "integrity": "sha512-JxsSE0464a8IA/+q5EHKmchwNyUFJHtCH00tSXsLaOddwLjG6yVvTH6lGgPcWMhO7YWUXj/XVgVgeE9kZtsPUQ==", - "license": "MIT", - "dependencies": { - "@azure/msal-common": "14.13.0", - "jsonwebtoken": "^9.0.0", - "uuid": "^8.3.0" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/@azure/msal-node/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/@babel/code-frame": { "version": "7.24.2", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", @@ -2263,46 +2230,6 @@ "node": ">=6.0" } }, - "node_modules/jsonwebtoken": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", - "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", - "license": "MIT", - "dependencies": { - "jws": "^3.2.2", - "lodash.includes": "^4.3.0", - "lodash.isboolean": "^3.0.3", - "lodash.isinteger": "^4.0.4", - "lodash.isnumber": "^3.0.3", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.once": "^4.0.0", - "ms": "^2.1.1", - "semver": "^7.5.4" - }, - "engines": { - "node": ">=12", - "npm": ">=6" - } - }, - "node_modules/jsonwebtoken/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/jsonwebtoken/node_modules/semver": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", - "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/jwa": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", @@ -2379,54 +2306,12 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, - "node_modules/lodash.includes": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", - "license": "MIT" - }, - "node_modules/lodash.isboolean": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", - "license": "MIT" - }, - "node_modules/lodash.isinteger": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", - "license": "MIT" - }, - "node_modules/lodash.isnumber": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", - "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", - "license": "MIT" - }, - "node_modules/lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", - "license": "MIT" - }, - "node_modules/lodash.isstring": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", - "license": "MIT" - }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "license": "MIT" }, - "node_modules/lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", - "license": "MIT" - }, "node_modules/log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", diff --git a/package.json b/package.json index 49ff2ffe..49d4eafe 100644 --- a/package.json +++ b/package.json @@ -673,8 +673,6 @@ "webpack-cli": "^5.0.1" }, "dependencies": { - "@azure/msal-node": "^2.10.0", - "@opentelemetry/tracing": "^0.24.0", "adal-node": "^0.2.4", "applicationinsights": "^1.0.5", "aws4": "^1.9.1", diff --git a/src/utils/auth/oidcClient.ts b/src/utils/auth/oidcClient.ts index 39a94239..cce4a1b5 100644 --- a/src/utils/auth/oidcClient.ts +++ b/src/utils/auth/oidcClient.ts @@ -1,4 +1,3 @@ -import { ILoopbackClient, ServerAuthorizationCodeResponse } from "@azure/msal-node"; import * as crypto from 'crypto'; import * as http from "http"; import * as jws from 'jws'; @@ -7,7 +6,29 @@ import { v4 as uuid } from 'uuid'; import { env, Uri, window } from "vscode"; import { MemoryCache } from '../memoryCache'; -export class CodeLoopbackClient implements ILoopbackClient { +type ServerAuthorizationCodeResponse = { + // Success case + code?: string; + client_info?: string; + state?: string; + cloud_instance_name?: string; + cloud_instance_host_name?: string; + cloud_graph_host_name?: string; + msgraph_host?: string; + // Error case + error?: string; + error_uri?: string; + error_description?: string; + suberror?: string; + timestamp?: string; + trace_id?: string; + correlation_id?: string; + claims?: string; + // Native Account ID + accountId?: string; +}; + +export class CodeLoopbackClient { port: number = 0; // default port, which will be set to a random available port private server!: http.Server; @@ -64,6 +85,9 @@ export class CodeLoopbackClient implements ILoopbackClient { const redirectUri = await this.getRedirectUri(); res.writeHead(302, { location: redirectUri }); // Prevent auth code from being saved in the browser history res.end(); + } else { + res.end(`Authorization Server Error:${JSON.stringify(authCodeResponse)}`); + reject(new Error(`Authorization Server Error:${JSON.stringify(authCodeResponse)}`)); } resolve({ url, ...authCodeResponse }); }); @@ -174,7 +198,6 @@ export class CodeLoopbackClient implements ILoopbackClient { } return ""; } - /** * Parses string into an object. * @@ -198,7 +221,7 @@ export class CodeLoopbackClient implements ILoopbackClient { export const CALLBACK_PORT = 7777; -export const remoteOutput = window.createOutputChannel("oidc"); +export const remoteOutput = window.createOutputChannel('REST-OIDC'); interface TokenInformation { access_token: string; @@ -224,7 +247,7 @@ export class OidcClient { authorizeEndpoint: string, tokenEndpoint: string, scopes: string, - audience: string,): Promise { + audience: string): Promise { const key = `${clientId}-${callbackPort}-${authorizeEndpoint}-${tokenEndpoint}-${scopes}-${audience}`; const cache = MemoryCache.createOrGet('oidc'); @@ -246,10 +269,17 @@ export class OidcClient { } public async getAccessToken(): Promise { + const tryDecode = (token: string): any => { + try { + const { payload } = jws.decode(token) ?? {}; + return JSON.parse(payload); + } catch (ex) { + return null; + } + }; if (this._tokenInformation?.access_token) { - const { payload } = jws.decode(this._tokenInformation.access_token) ?? {}; - const payloadJson = JSON.parse(payload); - if (payloadJson.exp && payloadJson.exp > Date.now() / 1000) { + const payloadJson = tryDecode(this._tokenInformation.access_token); + if (payloadJson === null || payloadJson.exp && payloadJson.exp > Date.now() / 1000) { return this._tokenInformation.access_token; } else { return this.getAccessTokenByRefreshToken(this._tokenInformation.refresh_token, this.clientId).then((resp) => { @@ -262,7 +292,7 @@ export class OidcClient { const nonceId = uuid(); // Retrieve all required scopes - const scopes = this.getScopes((this.scopes ?? "").split(' ')); + const scopes = this.getScopes((this.scopes ?? "").split(',')); const codeVerifier = toBase64UrlEncoding(crypto.randomBytes(32)); const codeChallenge = toBase64UrlEncoding(sha256(codeVerifier)); @@ -310,13 +340,13 @@ export class OidcClient { const loopbackClient = await CodeLoopbackClient.initialize(this.callbackPort); - + try { await env.openExternal(uri); const callBackResp = await loopbackClient.listenForAuthCode(); const codeExchangePromise = this._handleCallback(Uri.parse(callBackResp.url)); - const resp = await Promise.race([ + const resp = await Promise.race([ codeExchangePromise, new Promise((_, reject) => setTimeout(() => reject('Cancelled'), 60000)) ]); @@ -358,7 +388,7 @@ export class OidcClient { } - private async _handleCallback(uri: Uri): Promise { + private async _handleCallback(uri: Uri): Promise { const query = new URLSearchParams(uri.query); const code = query.get('code'); const stateId = query.get('state'); @@ -389,18 +419,26 @@ export class OidcClient { code_verifier: codeVerifier, redirect_uri: this.redirectUri, }).toString(); + try { + const response = await fetch(`${this.tokenEndpoint}`, { + method: 'POST', + headers: { + "Content-Type": "application/x-www-form-urlencoded", + 'Content-Length': postData.length.toString() + }, + body: postData + }); + const json = await response.json(); + const { access_token, refresh_token } = json; + if (!access_token) { + remoteOutput.appendLine(`Failed to retrieve access token: ${response.status} ${JSON.stringify(json)}`); + } - const response = await fetch(`${this.tokenEndpoint}`, { - method: 'POST', - headers: { - "Content-Type": "application/x-www-form-urlencoded", - 'Content-Length': postData.length.toString() - }, - body: postData - }); - - const { access_token, refresh_token } = await response.json(); - return { access_token, refresh_token }; + return { access_token, refresh_token }; + } catch (ex) { + remoteOutput.appendLine(`Failed to retrieve access token: ${ex}`); + return undefined; + } } /** @@ -410,9 +448,10 @@ export class OidcClient { private getScopes(scopes: string[] = []): string[] { const modifiedScopes = [...scopes]; - if (!modifiedScopes.includes('offline_access')) { - modifiedScopes.push('offline_access'); - } + // if (!modifiedScopes.includes('offline_access')) { + // modifiedScopes.push('offline_access'); + // } + if (!modifiedScopes.includes('openid')) { modifiedScopes.push('openid'); } @@ -436,4 +475,4 @@ export function toBase64UrlEncoding(buffer: Buffer) { export function sha256(buffer: string | Uint8Array): Buffer { return crypto.createHash('sha256').update(buffer).digest(); -} \ No newline at end of file +} diff --git a/src/utils/httpVariableProviders/systemVariableProvider.ts b/src/utils/httpVariableProviders/systemVariableProvider.ts index 247c05fd..80420d44 100644 --- a/src/utils/httpVariableProviders/systemVariableProvider.ts +++ b/src/utils/httpVariableProviders/systemVariableProvider.ts @@ -38,7 +38,7 @@ export class SystemVariableProvider implements HttpVariableProvider { private readonly requestUrlRegex: RegExp = /^(?:[^\s]+\s+)([^:]*:\/\/\/?[^/\s]*\/?)/; private readonly aadRegex: RegExp = new RegExp(`\\s*\\${Constants.AzureActiveDirectoryVariableName}(\\s+(${Constants.AzureActiveDirectoryForceNewOption}))?(\\s+(ppe|public|cn|de|us))?(\\s+([^\\.]+\\.[^\\}\\s]+|[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}))?(\\s+aud:([^\\.]+\\.[^\\}\\s]+|[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}))?\\s*`); - private readonly oidcRegex: RegExp = new RegExp(`\\s*(\\${Constants.OidcVariableName})(?:\\s+(${Constants.OIdcForceNewOption}))?(?:\\s*clientId:([\\w|-]+))?(?:\\s*issuer:([\\w|\.|:|/]+))?(?:\\s*callbackPort:([\\w|_]+))?(?:\\s*authorizeEndpoint:([\\w|\.|:|/|_|-]+))?(?:\\s*tokenEndpoint:([\\w|\.|:|/|_|-]+))?(?:\\s*scopes:([\\w|,]+))?(?:\\s*audience:(\\w+))?`); + private readonly oidcRegex: RegExp = new RegExp(`\\s*(\\${Constants.OidcVariableName})(?:\\s+(${Constants.OIdcForceNewOption}))?(?:\\s*clientId:([\\w|\.|:|/|_|-]+))?(?:\\s*issuer:([\\w|\.|:|/]+))?(?:\\s*callbackPort:([\\w|_]+))?(?:\\s*authorizeEndpoint:([\\w|\.|:|/|_|-]+))?(?:\\s*tokenEndpoint:([\\w|\.|:|/|_|-]+))?(?:\\s*scopes:([\\w|\.|:|/|_|-]+))?(?:\\s*audience:([\\w|\.|:|/|_|-]+))?`); private readonly innerSettingsEnvironmentVariableProvider: EnvironmentVariableProvider = EnvironmentVariableProvider.Instance; private static _instance: SystemVariableProvider;