@backstage/plugin-auth-backend-module-microsoft-provider 0.3.18-next.0 → 0.3.18-next.1
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.
- package/CHANGELOG.md +6 -0
- package/dist/authenticator.cjs.js +29 -3
- package/dist/authenticator.cjs.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/package.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @backstage/plugin-auth-backend-module-microsoft-provider
|
|
2
2
|
|
|
3
|
+
## 0.3.18-next.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 7ccaf9d: Fixed an issue where acquiring tokens with non-Graph scopes (such as Azure Management API) would crash the sign-in resolver because the user profile was unavailable. This affected both the initial sign-in and later token refreshes. The Microsoft authenticator now makes a separate Graph API call to fetch the profile when the primary token targets a different resource. Setting the `skipUserProfile` configuration option to true disables this extra call.
|
|
8
|
+
|
|
3
9
|
## 0.3.18-next.0
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
|
@@ -3,6 +3,29 @@
|
|
|
3
3
|
var pluginAuthNode = require('@backstage/plugin-auth-node');
|
|
4
4
|
var strategy = require('./strategy.cjs.js');
|
|
5
5
|
|
|
6
|
+
const GRAPH_PROFILE_SCOPE = "openid email User.Read offline_access";
|
|
7
|
+
async function withProfileFetchedViaGraph(result, input, ctx) {
|
|
8
|
+
if (result.fullProfile || ctx.skipUserProfile) {
|
|
9
|
+
return result;
|
|
10
|
+
}
|
|
11
|
+
const refreshToken = result.session.refreshToken ?? input.refreshToken;
|
|
12
|
+
if (!refreshToken) {
|
|
13
|
+
return result;
|
|
14
|
+
}
|
|
15
|
+
const graphResult = await ctx.helper.refresh({
|
|
16
|
+
req: input.req,
|
|
17
|
+
scope: GRAPH_PROFILE_SCOPE,
|
|
18
|
+
refreshToken
|
|
19
|
+
});
|
|
20
|
+
return {
|
|
21
|
+
...result,
|
|
22
|
+
fullProfile: graphResult.fullProfile,
|
|
23
|
+
session: {
|
|
24
|
+
...result.session,
|
|
25
|
+
refreshToken: graphResult.session.refreshToken ?? refreshToken
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
}
|
|
6
29
|
const microsoftAuthenticator = pluginAuthNode.createOAuthAuthenticator({
|
|
7
30
|
defaultProfileTransform: pluginAuthNode.PassportOAuthAuthenticatorHelper.defaultProfileTransform,
|
|
8
31
|
scopes: {
|
|
@@ -36,7 +59,8 @@ const microsoftAuthenticator = pluginAuthNode.createOAuthAuthenticator({
|
|
|
36
59
|
const helper = pluginAuthNode.PassportOAuthAuthenticatorHelper.from(strategy$1);
|
|
37
60
|
return {
|
|
38
61
|
helper,
|
|
39
|
-
domainHint
|
|
62
|
+
domainHint,
|
|
63
|
+
skipUserProfile
|
|
40
64
|
};
|
|
41
65
|
},
|
|
42
66
|
async start(input, ctx) {
|
|
@@ -49,10 +73,12 @@ const microsoftAuthenticator = pluginAuthNode.createOAuthAuthenticator({
|
|
|
49
73
|
return ctx.helper.start(input, options);
|
|
50
74
|
},
|
|
51
75
|
async authenticate(input, ctx) {
|
|
52
|
-
|
|
76
|
+
const result = await ctx.helper.authenticate(input);
|
|
77
|
+
return withProfileFetchedViaGraph(result, input, ctx);
|
|
53
78
|
},
|
|
54
79
|
async refresh(input, ctx) {
|
|
55
|
-
|
|
80
|
+
const result = await ctx.helper.refresh(input);
|
|
81
|
+
return withProfileFetchedViaGraph(result, input, ctx);
|
|
56
82
|
}
|
|
57
83
|
});
|
|
58
84
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"authenticator.cjs.js","sources":["../src/authenticator.ts"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n createOAuthAuthenticator,\n PassportOAuthAuthenticatorHelper,\n PassportOAuthDoneCallback,\n PassportProfile,\n} from '@backstage/plugin-auth-node';\nimport { ExtendedMicrosoftStrategy } from './strategy';\n\n/** @public */\nexport const microsoftAuthenticator = createOAuthAuthenticator({\n defaultProfileTransform:\n PassportOAuthAuthenticatorHelper.defaultProfileTransform,\n scopes: {\n required: ['email', 'openid', 'offline_access', 'user.read'],\n transform({ requested, granted, required, additional }) {\n // Resources scopes are of the form `<resource>/<scope>`, and are handled\n // separately from the normal scopes in the client. When request a\n // resource scope we should only include forward the request scope along\n // with offline_access.\n const hasResourceScope = Array.from(requested).some(s => s.includes('/'));\n if (hasResourceScope) {\n return [...requested, 'offline_access'];\n }\n return [...requested, ...granted, ...required, ...additional];\n },\n },\n initialize({ callbackUrl, config }) {\n const clientId = config.getString('clientId');\n const clientSecret = config.getString('clientSecret');\n const tenantId = config.getString('tenantId');\n const domainHint = config.getOptionalString('domainHint');\n const skipUserProfile =\n config.getOptionalBoolean('skipUserProfile') ?? false;\n\n const strategy = new ExtendedMicrosoftStrategy(\n {\n clientID: clientId,\n clientSecret: clientSecret,\n callbackURL: callbackUrl,\n tenant: tenantId,\n },\n (\n accessToken: string,\n refreshToken: string,\n params: any,\n fullProfile: PassportProfile,\n done: PassportOAuthDoneCallback,\n ) => {\n done(undefined, { fullProfile, params, accessToken }, { refreshToken });\n },\n );\n\n strategy.setSkipUserProfile(skipUserProfile);\n const helper = PassportOAuthAuthenticatorHelper.from(strategy);\n\n return {\n helper,\n domainHint,\n };\n },\n\n async start(input, ctx) {\n const options: Record<string, string> = {\n accessType: 'offline',\n };\n\n if (ctx.domainHint !== undefined) {\n options.domain_hint = ctx.domainHint;\n }\n\n return ctx.helper.start(input, options);\n },\n\n async authenticate(input, ctx) {\n
|
|
1
|
+
{"version":3,"file":"authenticator.cjs.js","sources":["../src/authenticator.ts"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n createOAuthAuthenticator,\n OAuthAuthenticatorRefreshInput,\n OAuthAuthenticatorResult,\n PassportOAuthAuthenticatorHelper,\n PassportOAuthDoneCallback,\n PassportProfile,\n} from '@backstage/plugin-auth-node';\nimport { ExtendedMicrosoftStrategy } from './strategy';\n\nconst GRAPH_PROFILE_SCOPE = 'openid email User.Read offline_access';\n\n/**\n * When the acquired access token targets a non-Graph resource, it can't be\n * used to fetch the user profile from the Microsoft Graph API. In that case,\n * make a separate refresh call with Graph scopes to get a token for profile\n * fetching, so that the sign-in resolver always receives a valid profile.\n * This is possible because Microsoft refresh tokens are resource-agnostic:\n * a single refresh token can produce access tokens for different resources.\n * The access token from the original result is preserved - only the profile\n * is taken from the Graph-scoped call.\n */\nasync function withProfileFetchedViaGraph(\n result: OAuthAuthenticatorResult<PassportProfile>,\n input: { req: OAuthAuthenticatorRefreshInput['req']; refreshToken?: string },\n ctx: { helper: PassportOAuthAuthenticatorHelper; skipUserProfile: boolean },\n): Promise<OAuthAuthenticatorResult<PassportProfile>> {\n if (result.fullProfile || ctx.skipUserProfile) {\n return result;\n }\n const refreshToken = result.session.refreshToken ?? input.refreshToken;\n if (!refreshToken) {\n return result;\n }\n const graphResult = await ctx.helper.refresh({\n req: input.req,\n scope: GRAPH_PROFILE_SCOPE,\n refreshToken,\n });\n return {\n ...result,\n fullProfile: graphResult.fullProfile,\n session: {\n ...result.session,\n refreshToken: graphResult.session.refreshToken ?? refreshToken,\n },\n };\n}\n\n/** @public */\nexport const microsoftAuthenticator = createOAuthAuthenticator({\n defaultProfileTransform:\n PassportOAuthAuthenticatorHelper.defaultProfileTransform,\n scopes: {\n required: ['email', 'openid', 'offline_access', 'user.read'],\n transform({ requested, granted, required, additional }) {\n // Resources scopes are of the form `<resource>/<scope>`, and are handled\n // separately from the normal scopes in the client. When request a\n // resource scope we should only include forward the request scope along\n // with offline_access.\n const hasResourceScope = Array.from(requested).some(s => s.includes('/'));\n if (hasResourceScope) {\n return [...requested, 'offline_access'];\n }\n return [...requested, ...granted, ...required, ...additional];\n },\n },\n initialize({ callbackUrl, config }) {\n const clientId = config.getString('clientId');\n const clientSecret = config.getString('clientSecret');\n const tenantId = config.getString('tenantId');\n const domainHint = config.getOptionalString('domainHint');\n const skipUserProfile =\n config.getOptionalBoolean('skipUserProfile') ?? false;\n\n const strategy = new ExtendedMicrosoftStrategy(\n {\n clientID: clientId,\n clientSecret: clientSecret,\n callbackURL: callbackUrl,\n tenant: tenantId,\n },\n (\n accessToken: string,\n refreshToken: string,\n params: any,\n fullProfile: PassportProfile,\n done: PassportOAuthDoneCallback,\n ) => {\n done(undefined, { fullProfile, params, accessToken }, { refreshToken });\n },\n );\n\n strategy.setSkipUserProfile(skipUserProfile);\n const helper = PassportOAuthAuthenticatorHelper.from(strategy);\n\n return {\n helper,\n domainHint,\n skipUserProfile,\n };\n },\n\n async start(input, ctx) {\n const options: Record<string, string> = {\n accessType: 'offline',\n };\n\n if (ctx.domainHint !== undefined) {\n options.domain_hint = ctx.domainHint;\n }\n\n return ctx.helper.start(input, options);\n },\n\n async authenticate(input, ctx) {\n const result = await ctx.helper.authenticate(input);\n return withProfileFetchedViaGraph(result, input, ctx);\n },\n\n async refresh(input, ctx) {\n const result = await ctx.helper.refresh(input);\n return withProfileFetchedViaGraph(result, input, ctx);\n },\n});\n"],"names":["createOAuthAuthenticator","PassportOAuthAuthenticatorHelper","strategy","ExtendedMicrosoftStrategy"],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ import * as _backstage_backend_plugin_api from '@backstage/backend-plugin-api';
|
|
|
6
6
|
declare const microsoftAuthenticator: _backstage_plugin_auth_node.OAuthAuthenticator<{
|
|
7
7
|
helper: PassportOAuthAuthenticatorHelper;
|
|
8
8
|
domainHint: string | undefined;
|
|
9
|
+
skipUserProfile: boolean;
|
|
9
10
|
}, PassportProfile>;
|
|
10
11
|
|
|
11
12
|
/** @public */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@backstage/plugin-auth-backend-module-microsoft-provider",
|
|
3
|
-
"version": "0.3.18-next.
|
|
3
|
+
"version": "0.3.18-next.1",
|
|
4
4
|
"description": "The microsoft-provider backend module for the auth plugin.",
|
|
5
5
|
"backstage": {
|
|
6
6
|
"role": "backend-plugin-module",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"test": "backstage-cli package test"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@backstage/backend-plugin-api": "1.10.0-next.
|
|
40
|
+
"@backstage/backend-plugin-api": "1.10.0-next.1",
|
|
41
41
|
"@backstage/plugin-auth-node": "0.7.4-next.0",
|
|
42
42
|
"express": "^4.22.0",
|
|
43
43
|
"jose": "^5.0.0",
|
|
@@ -45,11 +45,11 @@
|
|
|
45
45
|
"zod": "^3.25.76 || ^4.0.0"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
|
-
"@backstage/backend-defaults": "0.17.
|
|
49
|
-
"@backstage/backend-test-utils": "1.11.6-next.
|
|
50
|
-
"@backstage/cli": "0.36.
|
|
48
|
+
"@backstage/backend-defaults": "0.17.7-next.2",
|
|
49
|
+
"@backstage/backend-test-utils": "1.11.6-next.1",
|
|
50
|
+
"@backstage/cli": "0.36.5-next.1",
|
|
51
51
|
"@backstage/config": "1.3.8",
|
|
52
|
-
"@backstage/plugin-auth-backend": "0.30.0-next.
|
|
52
|
+
"@backstage/plugin-auth-backend": "0.30.0-next.1",
|
|
53
53
|
"@backstage/types": "1.2.2",
|
|
54
54
|
"@types/passport-microsoft": "^1.0.0",
|
|
55
55
|
"msw": "^2.0.0",
|