@microsoft/vscode-azext-azureauth 6.0.0-alpha.4 → 6.0.0-alpha.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -26,6 +26,7 @@ __exportStar(require("./contracts/AzureTenant"), exports);
26
26
  // The `AzureDevOpsSubscriptionProvider` is intentionally not exported, it must be imported from `'@microsoft/vscode-azext-azureauth/azdo'`
27
27
  __exportStar(require("./providers/AzureSubscriptionProviderBase"), exports);
28
28
  __exportStar(require("./providers/VSCodeAzureSubscriptionProvider"), exports);
29
+ __exportStar(require("./utils/BearerChallengePolicy"), exports);
29
30
  __exportStar(require("./utils/configuredAzureEnv"), exports);
30
31
  __exportStar(require("./utils/dedupeSubscriptions"), exports);
31
32
  __exportStar(require("./utils/getMetricsForTelemetry"), exports);
@@ -38,6 +38,7 @@ var __importStar = (this && this.__importStar) || (function () {
38
38
  })();
39
39
  Object.defineProperty(exports, "__esModule", { value: true });
40
40
  exports.AzureSubscriptionProviderBase = void 0;
41
+ const BearerChallengePolicy_1 = require("../utils/BearerChallengePolicy");
41
42
  const util_1 = require("util");
42
43
  const vscode = __importStar(require("vscode"));
43
44
  const AzureSubscriptionProviderRequestOptions_1 = require("../contracts/AzureSubscriptionProviderRequestOptions");
@@ -115,12 +116,18 @@ class AzureSubscriptionProviderBase {
115
116
  // If silent, suppress with normal timeout
116
117
  this.silenceRefreshEvents();
117
118
  }
118
- const session = await (0, getSessionFromVSCode_1.getSessionFromVSCode)(undefined, tenant?.tenantId, {
119
- account: tenant?.account,
120
- clearSessionPreference: options.clearSessionPreference ?? AzureSubscriptionProviderRequestOptions_1.DefaultSignInOptions.clearSessionPreference,
121
- createIfNone: prompt,
122
- silent: !prompt,
123
- });
119
+ let session;
120
+ try {
121
+ session = await (0, getSessionFromVSCode_1.getSessionFromVSCode)(undefined, tenant?.tenantId, {
122
+ account: tenant?.account,
123
+ clearSessionPreference: options.clearSessionPreference ?? AzureSubscriptionProviderRequestOptions_1.DefaultSignInOptions.clearSessionPreference,
124
+ createIfNone: prompt,
125
+ silent: !prompt,
126
+ });
127
+ }
128
+ catch (err) {
129
+ throw maybeImproveSignInError(err, tenant?.tenantId);
130
+ }
124
131
  if (prompt) {
125
132
  // Interactive sign in can take a while, so silence events for a bit longer
126
133
  this.silenceRefreshEvents();
@@ -341,8 +348,15 @@ class AzureSubscriptionProviderBase {
341
348
  }
342
349
  };
343
350
  armSubs ??= await import('@azure/arm-resources-subscriptions');
351
+ const endpoint = (0, configuredAzureEnv_1.getConfiguredAzureEnv)().resourceManagerEndpointUrl;
352
+ const client = new armSubs.SubscriptionClient(credential, { endpoint });
353
+ client.pipeline.addPolicy(new BearerChallengePolicy_1.BearerChallengePolicy(async (challenge) => {
354
+ this.silenceRefreshEvents();
355
+ const session = await (0, getSessionFromVSCode_1.getSessionFromVSCode)(challenge, tenant.tenantId, { createIfNone: true, account: tenant.account });
356
+ return session?.accessToken;
357
+ }, endpoint), { phase: 'Sign', afterPolicies: ['bearerTokenAuthenticationPolicy'] });
344
358
  return {
345
- client: new armSubs.SubscriptionClient(credential, { endpoint: (0, configuredAzureEnv_1.getConfiguredAzureEnv)().resourceManagerEndpointUrl }),
359
+ client,
346
360
  credential: credential,
347
361
  authentication: {
348
362
  getSession: async () => {
@@ -368,13 +382,13 @@ class AzureSubscriptionProviderBase {
368
382
  };
369
383
  }
370
384
  log(message) {
371
- this.logger?.debug(`[auth] ${message}`);
385
+ this.logger?.info(`[auth] ${message}`);
372
386
  }
373
387
  logForAccount(account, message) {
374
- this.logger?.debug(`[auth] [account: ${(0, screen_1.screen)(account)}] ${message}`);
388
+ this.logger?.info(`[auth] [account: ${(0, screen_1.screen)(account)}] ${message}`);
375
389
  }
376
390
  logForTenant(tenant, message) {
377
- this.logger?.debug(`[auth] [account: ${(0, screen_1.screen)(tenant.account)}] [tenant: ${(0, screen_1.screen)(tenant)}] ${message}`);
391
+ this.logger?.info(`[auth] [account: ${(0, screen_1.screen)(tenant.account)}] [tenant: ${(0, screen_1.screen)(tenant)}] ${message}`);
378
392
  }
379
393
  warnForAccount(account, message) {
380
394
  this.logger?.warn(`[auth] [account: ${(0, screen_1.screen)(account)}] ${message}`);
@@ -426,4 +440,28 @@ class AzureSubscriptionProviderBase {
426
440
  }
427
441
  }
428
442
  exports.AzureSubscriptionProviderBase = AzureSubscriptionProviderBase;
443
+ /**
444
+ * Inspects an error thrown during sign-in and returns a more user-friendly
445
+ * error when possible (e.g. native broker errors), otherwise returns the
446
+ * original error unchanged.
447
+ */
448
+ function maybeImproveSignInError(err, tenantId) {
449
+ if (!(err instanceof Error)) {
450
+ return err;
451
+ }
452
+ const message = err.message;
453
+ // The native MSAL broker surfaces opaque "platform_broker_error" messages
454
+ // that don't tell the user what went wrong. Re-wrap with actionable text.
455
+ if (message.includes('platform_broker_error')) {
456
+ const tenantHint = tenantId
457
+ ? vscode.l10n.t(' for tenant "{0}"', tenantId)
458
+ : '';
459
+ const improved = new Error(vscode.l10n.t('Sign-in failed{0}. The tenant may have expired or is no longer valid. Please verify the tenant is still active and try again.', tenantHint), { cause: err });
460
+ if (err.stack && improved.stack) {
461
+ improved.stack += `\nCaused by: ${err.stack}`;
462
+ }
463
+ return improved;
464
+ }
465
+ return err;
466
+ }
429
467
  //# sourceMappingURL=AzureSubscriptionProviderBase.js.map
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ /*---------------------------------------------------------------------------------------------
3
+ * Copyright (c) Microsoft Corporation. All rights reserved.
4
+ * Licensed under the MIT License. See License.txt in the project root for license information.
5
+ *--------------------------------------------------------------------------------------------*/
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.BearerChallengePolicy = void 0;
8
+ exports.getDefaultScopeFromEndpoint = getDefaultScopeFromEndpoint;
9
+ function getDefaultScopeFromEndpoint(endpoint) {
10
+ let base = endpoint ?? 'https://management.azure.com/';
11
+ base = base.replace(/\/+$/, '');
12
+ return `${base}/.default`;
13
+ }
14
+ const challengeRetriedRequests = new WeakSet();
15
+ class BearerChallengePolicy {
16
+ getTokenForChallenge;
17
+ endpoint;
18
+ name = 'BearerChallengePolicy';
19
+ constructor(getTokenForChallenge, endpoint) {
20
+ this.getTokenForChallenge = getTokenForChallenge;
21
+ this.endpoint = endpoint;
22
+ }
23
+ async sendRequest(request, next) {
24
+ const initial = await next(request);
25
+ if (initial.status === 401 && !challengeRetriedRequests.has(request)) {
26
+ const header = initial.headers.get('WWW-Authenticate') || initial.headers.get('www-authenticate');
27
+ if (header) {
28
+ const scopes = [getDefaultScopeFromEndpoint(this.endpoint)];
29
+ challengeRetriedRequests.add(request);
30
+ const token = await this.getTokenForChallenge({ wwwAuthenticate: header, fallbackScopes: scopes });
31
+ if (token) {
32
+ request.headers.set('Authorization', `Bearer ${token}`);
33
+ return await next(request);
34
+ }
35
+ }
36
+ }
37
+ return initial;
38
+ }
39
+ }
40
+ exports.BearerChallengePolicy = BearerChallengePolicy;
41
+ //# sourceMappingURL=BearerChallengePolicy.js.map
@@ -6,6 +6,7 @@ export type * from './contracts/AzureSubscriptionProviderRequestOptions';
6
6
  export * from './contracts/AzureTenant';
7
7
  export * from './providers/AzureSubscriptionProviderBase';
8
8
  export * from './providers/VSCodeAzureSubscriptionProvider';
9
+ export * from './utils/BearerChallengePolicy';
9
10
  export * from './utils/configuredAzureEnv';
10
11
  export * from './utils/dedupeSubscriptions';
11
12
  export * from './utils/getMetricsForTelemetry';
@@ -10,6 +10,7 @@ export * from './contracts/AzureTenant';
10
10
  // The `AzureDevOpsSubscriptionProvider` is intentionally not exported, it must be imported from `'@microsoft/vscode-azext-azureauth/azdo'`
11
11
  export * from './providers/AzureSubscriptionProviderBase';
12
12
  export * from './providers/VSCodeAzureSubscriptionProvider';
13
+ export * from './utils/BearerChallengePolicy';
13
14
  export * from './utils/configuredAzureEnv';
14
15
  export * from './utils/dedupeSubscriptions';
15
16
  export * from './utils/getMetricsForTelemetry';
@@ -2,6 +2,7 @@
2
2
  * Copyright (c) Microsoft Corporation. All rights reserved.
3
3
  * Licensed under the MIT License. See License.txt in the project root for license information.
4
4
  *--------------------------------------------------------------------------------------------*/
5
+ import { BearerChallengePolicy } from '../utils/BearerChallengePolicy';
5
6
  import { inspect } from 'util';
6
7
  import * as vscode from 'vscode';
7
8
  import { DefaultOptions, DefaultSignInOptions } from '../contracts/AzureSubscriptionProviderRequestOptions';
@@ -79,12 +80,18 @@ export class AzureSubscriptionProviderBase {
79
80
  // If silent, suppress with normal timeout
80
81
  this.silenceRefreshEvents();
81
82
  }
82
- const session = await getSessionFromVSCode(undefined, tenant?.tenantId, {
83
- account: tenant?.account,
84
- clearSessionPreference: options.clearSessionPreference ?? DefaultSignInOptions.clearSessionPreference,
85
- createIfNone: prompt,
86
- silent: !prompt,
87
- });
83
+ let session;
84
+ try {
85
+ session = await getSessionFromVSCode(undefined, tenant?.tenantId, {
86
+ account: tenant?.account,
87
+ clearSessionPreference: options.clearSessionPreference ?? DefaultSignInOptions.clearSessionPreference,
88
+ createIfNone: prompt,
89
+ silent: !prompt,
90
+ });
91
+ }
92
+ catch (err) {
93
+ throw maybeImproveSignInError(err, tenant?.tenantId);
94
+ }
88
95
  if (prompt) {
89
96
  // Interactive sign in can take a while, so silence events for a bit longer
90
97
  this.silenceRefreshEvents();
@@ -305,8 +312,15 @@ export class AzureSubscriptionProviderBase {
305
312
  }
306
313
  };
307
314
  armSubs ??= await import('@azure/arm-resources-subscriptions');
315
+ const endpoint = getConfiguredAzureEnv().resourceManagerEndpointUrl;
316
+ const client = new armSubs.SubscriptionClient(credential, { endpoint });
317
+ client.pipeline.addPolicy(new BearerChallengePolicy(async (challenge) => {
318
+ this.silenceRefreshEvents();
319
+ const session = await getSessionFromVSCode(challenge, tenant.tenantId, { createIfNone: true, account: tenant.account });
320
+ return session?.accessToken;
321
+ }, endpoint), { phase: 'Sign', afterPolicies: ['bearerTokenAuthenticationPolicy'] });
308
322
  return {
309
- client: new armSubs.SubscriptionClient(credential, { endpoint: getConfiguredAzureEnv().resourceManagerEndpointUrl }),
323
+ client,
310
324
  credential: credential,
311
325
  authentication: {
312
326
  getSession: async () => {
@@ -332,13 +346,13 @@ export class AzureSubscriptionProviderBase {
332
346
  };
333
347
  }
334
348
  log(message) {
335
- this.logger?.debug(`[auth] ${message}`);
349
+ this.logger?.info(`[auth] ${message}`);
336
350
  }
337
351
  logForAccount(account, message) {
338
- this.logger?.debug(`[auth] [account: ${screen(account)}] ${message}`);
352
+ this.logger?.info(`[auth] [account: ${screen(account)}] ${message}`);
339
353
  }
340
354
  logForTenant(tenant, message) {
341
- this.logger?.debug(`[auth] [account: ${screen(tenant.account)}] [tenant: ${screen(tenant)}] ${message}`);
355
+ this.logger?.info(`[auth] [account: ${screen(tenant.account)}] [tenant: ${screen(tenant)}] ${message}`);
342
356
  }
343
357
  warnForAccount(account, message) {
344
358
  this.logger?.warn(`[auth] [account: ${screen(account)}] ${message}`);
@@ -389,4 +403,28 @@ export class AzureSubscriptionProviderBase {
389
403
  throw err;
390
404
  }
391
405
  }
406
+ /**
407
+ * Inspects an error thrown during sign-in and returns a more user-friendly
408
+ * error when possible (e.g. native broker errors), otherwise returns the
409
+ * original error unchanged.
410
+ */
411
+ function maybeImproveSignInError(err, tenantId) {
412
+ if (!(err instanceof Error)) {
413
+ return err;
414
+ }
415
+ const message = err.message;
416
+ // The native MSAL broker surfaces opaque "platform_broker_error" messages
417
+ // that don't tell the user what went wrong. Re-wrap with actionable text.
418
+ if (message.includes('platform_broker_error')) {
419
+ const tenantHint = tenantId
420
+ ? vscode.l10n.t(' for tenant "{0}"', tenantId)
421
+ : '';
422
+ const improved = new Error(vscode.l10n.t('Sign-in failed{0}. The tenant may have expired or is no longer valid. Please verify the tenant is still active and try again.', tenantHint), { cause: err });
423
+ if (err.stack && improved.stack) {
424
+ improved.stack += `\nCaused by: ${err.stack}`;
425
+ }
426
+ return improved;
427
+ }
428
+ return err;
429
+ }
392
430
  //# sourceMappingURL=AzureSubscriptionProviderBase.js.map
@@ -0,0 +1,10 @@
1
+ import type { PipelinePolicy, PipelineRequest, PipelineResponse, SendRequest } from '@azure/core-rest-pipeline';
2
+ import type * as vscode from 'vscode';
3
+ export declare function getDefaultScopeFromEndpoint(endpoint?: string): string;
4
+ export declare class BearerChallengePolicy implements PipelinePolicy {
5
+ private readonly getTokenForChallenge;
6
+ private readonly endpoint?;
7
+ readonly name = "BearerChallengePolicy";
8
+ constructor(getTokenForChallenge: (request: vscode.AuthenticationWwwAuthenticateRequest) => Promise<string | undefined>, endpoint?: string | undefined);
9
+ sendRequest(request: PipelineRequest, next: SendRequest): Promise<PipelineResponse>;
10
+ }
@@ -0,0 +1,36 @@
1
+ /*---------------------------------------------------------------------------------------------
2
+ * Copyright (c) Microsoft Corporation. All rights reserved.
3
+ * Licensed under the MIT License. See License.txt in the project root for license information.
4
+ *--------------------------------------------------------------------------------------------*/
5
+ export function getDefaultScopeFromEndpoint(endpoint) {
6
+ let base = endpoint ?? 'https://management.azure.com/';
7
+ base = base.replace(/\/+$/, '');
8
+ return `${base}/.default`;
9
+ }
10
+ const challengeRetriedRequests = new WeakSet();
11
+ export class BearerChallengePolicy {
12
+ getTokenForChallenge;
13
+ endpoint;
14
+ name = 'BearerChallengePolicy';
15
+ constructor(getTokenForChallenge, endpoint) {
16
+ this.getTokenForChallenge = getTokenForChallenge;
17
+ this.endpoint = endpoint;
18
+ }
19
+ async sendRequest(request, next) {
20
+ const initial = await next(request);
21
+ if (initial.status === 401 && !challengeRetriedRequests.has(request)) {
22
+ const header = initial.headers.get('WWW-Authenticate') || initial.headers.get('www-authenticate');
23
+ if (header) {
24
+ const scopes = [getDefaultScopeFromEndpoint(this.endpoint)];
25
+ challengeRetriedRequests.add(request);
26
+ const token = await this.getTokenForChallenge({ wwwAuthenticate: header, fallbackScopes: scopes });
27
+ if (token) {
28
+ request.headers.set('Authorization', `Bearer ${token}`);
29
+ return await next(request);
30
+ }
31
+ }
32
+ }
33
+ return initial;
34
+ }
35
+ }
36
+ //# sourceMappingURL=BearerChallengePolicy.js.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@microsoft/vscode-azext-azureauth",
3
3
  "author": "Microsoft Corporation",
4
- "version": "6.0.0-alpha.4",
4
+ "version": "6.0.0-alpha.6",
5
5
  "description": "Azure authentication helpers for Visual Studio Code",
6
6
  "tags": [
7
7
  "azure",
@@ -55,6 +55,7 @@
55
55
  },
56
56
  "dependencies": {
57
57
  "@azure/arm-resources-subscriptions": "^2.1.0",
58
+ "@azure/core-rest-pipeline": "^1.22.2",
58
59
  "@azure/identity": "^4.13.0",
59
60
  "@azure/ms-rest-azure-env": "^2.0.0"
60
61
  },