@backstage/plugin-auth-node 0.5.3-next.0 → 0.5.3
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 +28 -0
- package/dist/extensions/AuthOwnershipResolutionExtensionPoint.cjs.js +10 -0
- package/dist/extensions/AuthOwnershipResolutionExtensionPoint.cjs.js.map +1 -0
- package/dist/extensions/AuthProvidersExtensionPoint.cjs.js +10 -0
- package/dist/extensions/AuthProvidersExtensionPoint.cjs.js.map +1 -0
- package/dist/flow/sendWebMessageResponse.cjs.js +41 -0
- package/dist/flow/sendWebMessageResponse.cjs.js.map +1 -0
- package/dist/identity/DefaultIdentityClient.cjs.js +103 -0
- package/dist/identity/DefaultIdentityClient.cjs.js.map +1 -0
- package/dist/identity/IdentityClient.cjs.js +27 -0
- package/dist/identity/IdentityClient.cjs.js.map +1 -0
- package/dist/identity/getBearerTokenFromAuthorizationHeader.cjs.js +12 -0
- package/dist/identity/getBearerTokenFromAuthorizationHeader.cjs.js.map +1 -0
- package/dist/identity/prepareBackstageIdentityResponse.cjs.js +36 -0
- package/dist/identity/prepareBackstageIdentityResponse.cjs.js.map +1 -0
- package/dist/index.cjs.js +48 -1063
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +6 -4
- package/dist/oauth/CookieScopeManager.cjs.js +90 -0
- package/dist/oauth/CookieScopeManager.cjs.js.map +1 -0
- package/dist/oauth/OAuthCookieManager.cjs.js +87 -0
- package/dist/oauth/OAuthCookieManager.cjs.js.map +1 -0
- package/dist/oauth/OAuthEnvironmentHandler.cjs.js +64 -0
- package/dist/oauth/OAuthEnvironmentHandler.cjs.js.map +1 -0
- package/dist/oauth/PassportOAuthAuthenticatorHelper.cjs.js +69 -0
- package/dist/oauth/PassportOAuthAuthenticatorHelper.cjs.js.map +1 -0
- package/dist/oauth/createOAuthProviderFactory.cjs.js +37 -0
- package/dist/oauth/createOAuthProviderFactory.cjs.js.map +1 -0
- package/dist/oauth/createOAuthRouteHandlers.cjs.js +215 -0
- package/dist/oauth/createOAuthRouteHandlers.cjs.js.map +1 -0
- package/dist/oauth/state.cjs.js +31 -0
- package/dist/oauth/state.cjs.js.map +1 -0
- package/dist/oauth/types.cjs.js +8 -0
- package/dist/oauth/types.cjs.js.map +1 -0
- package/dist/passport/PassportHelpers.cjs.js +144 -0
- package/dist/passport/PassportHelpers.cjs.js.map +1 -0
- package/dist/proxy/createProxyAuthProviderFactory.cjs.js +32 -0
- package/dist/proxy/createProxyAuthProviderFactory.cjs.js.map +1 -0
- package/dist/proxy/createProxyRouteHandlers.cjs.js +39 -0
- package/dist/proxy/createProxyRouteHandlers.cjs.js.map +1 -0
- package/dist/proxy/types.cjs.js +8 -0
- package/dist/proxy/types.cjs.js.map +1 -0
- package/dist/sign-in/commonSignInResolvers.cjs.js +70 -0
- package/dist/sign-in/commonSignInResolvers.cjs.js.map +1 -0
- package/dist/sign-in/createSignInResolverFactory.cjs.js +37 -0
- package/dist/sign-in/createSignInResolverFactory.cjs.js.map +1 -0
- package/dist/sign-in/readDeclarativeSignInResolver.cjs.js +38 -0
- package/dist/sign-in/readDeclarativeSignInResolver.cjs.js.map +1 -0
- package/dist/types.cjs.js +17 -0
- package/dist/types.cjs.js.map +1 -0
- package/package.json +7 -6
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var PassportHelpers = require('../passport/PassportHelpers.cjs.js');
|
|
4
|
+
|
|
5
|
+
class PassportOAuthAuthenticatorHelper {
|
|
6
|
+
static defaultProfileTransform = async (input) => ({
|
|
7
|
+
profile: PassportHelpers.PassportHelpers.transformProfile(
|
|
8
|
+
input.fullProfile ?? {},
|
|
9
|
+
input.session.idToken
|
|
10
|
+
)
|
|
11
|
+
});
|
|
12
|
+
static from(strategy) {
|
|
13
|
+
return new PassportOAuthAuthenticatorHelper(strategy);
|
|
14
|
+
}
|
|
15
|
+
#strategy;
|
|
16
|
+
constructor(strategy) {
|
|
17
|
+
this.#strategy = strategy;
|
|
18
|
+
}
|
|
19
|
+
async start(input, options) {
|
|
20
|
+
return PassportHelpers.PassportHelpers.executeRedirectStrategy(input.req, this.#strategy, {
|
|
21
|
+
scope: input.scope,
|
|
22
|
+
state: input.state,
|
|
23
|
+
...options
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
async authenticate(input, options) {
|
|
27
|
+
const { result, privateInfo } = await PassportHelpers.PassportHelpers.executeFrameHandlerStrategy(input.req, this.#strategy, options);
|
|
28
|
+
return {
|
|
29
|
+
fullProfile: result.fullProfile,
|
|
30
|
+
session: {
|
|
31
|
+
accessToken: result.accessToken,
|
|
32
|
+
tokenType: result.params.token_type ?? "bearer",
|
|
33
|
+
scope: result.params.scope,
|
|
34
|
+
expiresInSeconds: result.params.expires_in,
|
|
35
|
+
idToken: result.params.id_token,
|
|
36
|
+
refreshToken: privateInfo.refreshToken
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
async refresh(input) {
|
|
41
|
+
const result = await PassportHelpers.PassportHelpers.executeRefreshTokenStrategy(
|
|
42
|
+
this.#strategy,
|
|
43
|
+
input.refreshToken,
|
|
44
|
+
input.scope
|
|
45
|
+
);
|
|
46
|
+
const fullProfile = await this.fetchProfile(result.accessToken);
|
|
47
|
+
return {
|
|
48
|
+
fullProfile,
|
|
49
|
+
session: {
|
|
50
|
+
accessToken: result.accessToken,
|
|
51
|
+
tokenType: result.params.token_type ?? "bearer",
|
|
52
|
+
scope: result.params.scope,
|
|
53
|
+
expiresInSeconds: result.params.expires_in,
|
|
54
|
+
idToken: result.params.id_token,
|
|
55
|
+
refreshToken: result.refreshToken
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
async fetchProfile(accessToken) {
|
|
60
|
+
const profile = await PassportHelpers.PassportHelpers.executeFetchUserProfileStrategy(
|
|
61
|
+
this.#strategy,
|
|
62
|
+
accessToken
|
|
63
|
+
);
|
|
64
|
+
return profile;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
exports.PassportOAuthAuthenticatorHelper = PassportOAuthAuthenticatorHelper;
|
|
69
|
+
//# sourceMappingURL=PassportOAuthAuthenticatorHelper.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PassportOAuthAuthenticatorHelper.cjs.js","sources":["../../src/oauth/PassportOAuthAuthenticatorHelper.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 { Strategy } from 'passport';\nimport {\n PassportDoneCallback,\n PassportHelpers,\n PassportProfile,\n} from '../passport';\nimport { ProfileTransform } from '../types';\nimport {\n OAuthAuthenticatorAuthenticateInput,\n OAuthAuthenticatorRefreshInput,\n OAuthAuthenticatorResult,\n OAuthAuthenticatorStartInput,\n} from './types';\n\n/** @public */\nexport type PassportOAuthResult = {\n fullProfile: PassportProfile;\n params: {\n id_token?: string;\n scope: string;\n token_type?: string;\n expires_in: number;\n };\n accessToken: string;\n};\n\n/** @public */\nexport type PassportOAuthPrivateInfo = {\n refreshToken?: string;\n};\n\n/** @public */\nexport type PassportOAuthDoneCallback = PassportDoneCallback<\n PassportOAuthResult,\n PassportOAuthPrivateInfo\n>;\n\n/** @public */\nexport class PassportOAuthAuthenticatorHelper {\n static defaultProfileTransform: ProfileTransform<\n OAuthAuthenticatorResult<PassportProfile>\n > = async input => ({\n profile: PassportHelpers.transformProfile(\n input.fullProfile ?? {},\n input.session.idToken,\n ),\n });\n\n static from(strategy: Strategy) {\n return new PassportOAuthAuthenticatorHelper(strategy);\n }\n\n readonly #strategy: Strategy;\n\n private constructor(strategy: Strategy) {\n this.#strategy = strategy;\n }\n\n async start(\n input: OAuthAuthenticatorStartInput,\n options: Record<string, string>,\n ): Promise<{ url: string; status?: number }> {\n return PassportHelpers.executeRedirectStrategy(input.req, this.#strategy, {\n scope: input.scope,\n state: input.state,\n ...options,\n });\n }\n\n async authenticate(\n input: OAuthAuthenticatorAuthenticateInput,\n options?: Record<string, string>,\n ): Promise<OAuthAuthenticatorResult<PassportProfile>> {\n const { result, privateInfo } =\n await PassportHelpers.executeFrameHandlerStrategy<\n PassportOAuthResult,\n PassportOAuthPrivateInfo\n >(input.req, this.#strategy, options);\n\n return {\n fullProfile: result.fullProfile as PassportProfile,\n session: {\n accessToken: result.accessToken,\n tokenType: result.params.token_type ?? 'bearer',\n scope: result.params.scope,\n expiresInSeconds: result.params.expires_in,\n idToken: result.params.id_token,\n refreshToken: privateInfo.refreshToken,\n },\n };\n }\n\n async refresh(\n input: OAuthAuthenticatorRefreshInput,\n ): Promise<OAuthAuthenticatorResult<PassportProfile>> {\n const result = await PassportHelpers.executeRefreshTokenStrategy(\n this.#strategy,\n input.refreshToken,\n input.scope,\n );\n const fullProfile = await this.fetchProfile(result.accessToken);\n return {\n fullProfile,\n session: {\n accessToken: result.accessToken,\n tokenType: result.params.token_type ?? 'bearer',\n scope: result.params.scope,\n expiresInSeconds: result.params.expires_in,\n idToken: result.params.id_token,\n refreshToken: result.refreshToken,\n },\n };\n }\n\n async fetchProfile(accessToken: string): Promise<PassportProfile> {\n const profile = await PassportHelpers.executeFetchUserProfileStrategy(\n this.#strategy,\n accessToken,\n );\n return profile;\n }\n}\n"],"names":["PassportHelpers"],"mappings":";;;;AAsDO,MAAM,gCAAiC,CAAA;AAAA,EAC5C,OAAO,uBAEH,GAAA,OAAM,KAAU,MAAA;AAAA,IAClB,SAASA,+BAAgB,CAAA,gBAAA;AAAA,MACvB,KAAA,CAAM,eAAe,EAAC;AAAA,MACtB,MAAM,OAAQ,CAAA,OAAA;AAAA,KAChB;AAAA,GACF,CAAA,CAAA;AAAA,EAEA,OAAO,KAAK,QAAoB,EAAA;AAC9B,IAAO,OAAA,IAAI,iCAAiC,QAAQ,CAAA,CAAA;AAAA,GACtD;AAAA,EAES,SAAA,CAAA;AAAA,EAED,YAAY,QAAoB,EAAA;AACtC,IAAA,IAAA,CAAK,SAAY,GAAA,QAAA,CAAA;AAAA,GACnB;AAAA,EAEA,MAAM,KACJ,CAAA,KAAA,EACA,OAC2C,EAAA;AAC3C,IAAA,OAAOA,+BAAgB,CAAA,uBAAA,CAAwB,KAAM,CAAA,GAAA,EAAK,KAAK,SAAW,EAAA;AAAA,MACxE,OAAO,KAAM,CAAA,KAAA;AAAA,MACb,OAAO,KAAM,CAAA,KAAA;AAAA,MACb,GAAG,OAAA;AAAA,KACJ,CAAA,CAAA;AAAA,GACH;AAAA,EAEA,MAAM,YACJ,CAAA,KAAA,EACA,OACoD,EAAA;AACpD,IAAM,MAAA,EAAE,MAAQ,EAAA,WAAA,EACd,GAAA,MAAMA,+BAAgB,CAAA,2BAAA,CAGpB,KAAM,CAAA,GAAA,EAAK,IAAK,CAAA,SAAA,EAAW,OAAO,CAAA,CAAA;AAEtC,IAAO,OAAA;AAAA,MACL,aAAa,MAAO,CAAA,WAAA;AAAA,MACpB,OAAS,EAAA;AAAA,QACP,aAAa,MAAO,CAAA,WAAA;AAAA,QACpB,SAAA,EAAW,MAAO,CAAA,MAAA,CAAO,UAAc,IAAA,QAAA;AAAA,QACvC,KAAA,EAAO,OAAO,MAAO,CAAA,KAAA;AAAA,QACrB,gBAAA,EAAkB,OAAO,MAAO,CAAA,UAAA;AAAA,QAChC,OAAA,EAAS,OAAO,MAAO,CAAA,QAAA;AAAA,QACvB,cAAc,WAAY,CAAA,YAAA;AAAA,OAC5B;AAAA,KACF,CAAA;AAAA,GACF;AAAA,EAEA,MAAM,QACJ,KACoD,EAAA;AACpD,IAAM,MAAA,MAAA,GAAS,MAAMA,+BAAgB,CAAA,2BAAA;AAAA,MACnC,IAAK,CAAA,SAAA;AAAA,MACL,KAAM,CAAA,YAAA;AAAA,MACN,KAAM,CAAA,KAAA;AAAA,KACR,CAAA;AACA,IAAA,MAAM,WAAc,GAAA,MAAM,IAAK,CAAA,YAAA,CAAa,OAAO,WAAW,CAAA,CAAA;AAC9D,IAAO,OAAA;AAAA,MACL,WAAA;AAAA,MACA,OAAS,EAAA;AAAA,QACP,aAAa,MAAO,CAAA,WAAA;AAAA,QACpB,SAAA,EAAW,MAAO,CAAA,MAAA,CAAO,UAAc,IAAA,QAAA;AAAA,QACvC,KAAA,EAAO,OAAO,MAAO,CAAA,KAAA;AAAA,QACrB,gBAAA,EAAkB,OAAO,MAAO,CAAA,UAAA;AAAA,QAChC,OAAA,EAAS,OAAO,MAAO,CAAA,QAAA;AAAA,QACvB,cAAc,MAAO,CAAA,YAAA;AAAA,OACvB;AAAA,KACF,CAAA;AAAA,GACF;AAAA,EAEA,MAAM,aAAa,WAA+C,EAAA;AAChE,IAAM,MAAA,OAAA,GAAU,MAAMA,+BAAgB,CAAA,+BAAA;AAAA,MACpC,IAAK,CAAA,SAAA;AAAA,MACL,WAAA;AAAA,KACF,CAAA;AACA,IAAO,OAAA,OAAA,CAAA;AAAA,GACT;AACF;;;;"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
require('zod-to-json-schema');
|
|
4
|
+
require('zod-validation-error');
|
|
5
|
+
require('@backstage/errors');
|
|
6
|
+
var readDeclarativeSignInResolver = require('../sign-in/readDeclarativeSignInResolver.cjs.js');
|
|
7
|
+
require('../sign-in/commonSignInResolvers.cjs.js');
|
|
8
|
+
var OAuthEnvironmentHandler = require('./OAuthEnvironmentHandler.cjs.js');
|
|
9
|
+
var createOAuthRouteHandlers = require('./createOAuthRouteHandlers.cjs.js');
|
|
10
|
+
|
|
11
|
+
function createOAuthProviderFactory(options) {
|
|
12
|
+
return (ctx) => {
|
|
13
|
+
return OAuthEnvironmentHandler.OAuthEnvironmentHandler.mapConfig(ctx.config, (envConfig) => {
|
|
14
|
+
const signInResolver = readDeclarativeSignInResolver.readDeclarativeSignInResolver({
|
|
15
|
+
config: envConfig,
|
|
16
|
+
signInResolverFactories: options.signInResolverFactories ?? {}
|
|
17
|
+
}) ?? options.signInResolver;
|
|
18
|
+
return createOAuthRouteHandlers.createOAuthRouteHandlers({
|
|
19
|
+
authenticator: options.authenticator,
|
|
20
|
+
appUrl: ctx.appUrl,
|
|
21
|
+
baseUrl: ctx.baseUrl,
|
|
22
|
+
config: envConfig,
|
|
23
|
+
isOriginAllowed: ctx.isOriginAllowed,
|
|
24
|
+
cookieConfigurer: ctx.cookieConfigurer,
|
|
25
|
+
providerId: ctx.providerId,
|
|
26
|
+
resolverContext: ctx.resolverContext,
|
|
27
|
+
additionalScopes: options.additionalScopes,
|
|
28
|
+
stateTransform: options.stateTransform,
|
|
29
|
+
profileTransform: options.profileTransform,
|
|
30
|
+
signInResolver
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
exports.createOAuthProviderFactory = createOAuthProviderFactory;
|
|
37
|
+
//# sourceMappingURL=createOAuthProviderFactory.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createOAuthProviderFactory.cjs.js","sources":["../../src/oauth/createOAuthProviderFactory.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 { readDeclarativeSignInResolver } from '../sign-in';\nimport {\n AuthProviderFactory,\n ProfileTransform,\n SignInResolver,\n} from '../types';\nimport { OAuthEnvironmentHandler } from './OAuthEnvironmentHandler';\nimport { createOAuthRouteHandlers } from './createOAuthRouteHandlers';\nimport { OAuthStateTransform } from './state';\nimport { OAuthAuthenticator, OAuthAuthenticatorResult } from './types';\nimport { SignInResolverFactory } from '../sign-in/createSignInResolverFactory';\n\n/** @public */\nexport function createOAuthProviderFactory<TProfile>(options: {\n authenticator: OAuthAuthenticator<unknown, TProfile>;\n additionalScopes?: string[];\n stateTransform?: OAuthStateTransform;\n profileTransform?: ProfileTransform<OAuthAuthenticatorResult<TProfile>>;\n signInResolver?: SignInResolver<OAuthAuthenticatorResult<TProfile>>;\n signInResolverFactories?: {\n [name in string]: SignInResolverFactory;\n };\n}): AuthProviderFactory {\n return ctx => {\n return OAuthEnvironmentHandler.mapConfig(ctx.config, envConfig => {\n const signInResolver =\n readDeclarativeSignInResolver({\n config: envConfig,\n signInResolverFactories: options.signInResolverFactories ?? {},\n }) ?? options.signInResolver;\n\n return createOAuthRouteHandlers<TProfile>({\n authenticator: options.authenticator,\n appUrl: ctx.appUrl,\n baseUrl: ctx.baseUrl,\n config: envConfig,\n isOriginAllowed: ctx.isOriginAllowed,\n cookieConfigurer: ctx.cookieConfigurer,\n providerId: ctx.providerId,\n resolverContext: ctx.resolverContext,\n additionalScopes: options.additionalScopes,\n stateTransform: options.stateTransform,\n profileTransform: options.profileTransform,\n signInResolver,\n });\n });\n };\n}\n"],"names":["OAuthEnvironmentHandler","readDeclarativeSignInResolver","createOAuthRouteHandlers"],"mappings":";;;;;;;;;;AA6BO,SAAS,2BAAqC,OAS7B,EAAA;AACtB,EAAA,OAAO,CAAO,GAAA,KAAA;AACZ,IAAA,OAAOA,+CAAwB,CAAA,SAAA,CAAU,GAAI,CAAA,MAAA,EAAQ,CAAa,SAAA,KAAA;AAChE,MAAA,MAAM,iBACJC,2DAA8B,CAAA;AAAA,QAC5B,MAAQ,EAAA,SAAA;AAAA,QACR,uBAAA,EAAyB,OAAQ,CAAA,uBAAA,IAA2B,EAAC;AAAA,OAC9D,KAAK,OAAQ,CAAA,cAAA,CAAA;AAEhB,MAAA,OAAOC,iDAAmC,CAAA;AAAA,QACxC,eAAe,OAAQ,CAAA,aAAA;AAAA,QACvB,QAAQ,GAAI,CAAA,MAAA;AAAA,QACZ,SAAS,GAAI,CAAA,OAAA;AAAA,QACb,MAAQ,EAAA,SAAA;AAAA,QACR,iBAAiB,GAAI,CAAA,eAAA;AAAA,QACrB,kBAAkB,GAAI,CAAA,gBAAA;AAAA,QACtB,YAAY,GAAI,CAAA,UAAA;AAAA,QAChB,iBAAiB,GAAI,CAAA,eAAA;AAAA,QACrB,kBAAkB,OAAQ,CAAA,gBAAA;AAAA,QAC1B,gBAAgB,OAAQ,CAAA,cAAA;AAAA,QACxB,kBAAkB,OAAQ,CAAA,gBAAA;AAAA,QAC1B,cAAA;AAAA,OACD,CAAA,CAAA;AAAA,KACF,CAAA,CAAA;AAAA,GACH,CAAA;AACF;;;;"}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var crypto = require('crypto');
|
|
4
|
+
var url = require('url');
|
|
5
|
+
var errors = require('@backstage/errors');
|
|
6
|
+
var state = require('./state.cjs.js');
|
|
7
|
+
var sendWebMessageResponse = require('../flow/sendWebMessageResponse.cjs.js');
|
|
8
|
+
var prepareBackstageIdentityResponse = require('../identity/prepareBackstageIdentityResponse.cjs.js');
|
|
9
|
+
require('jose');
|
|
10
|
+
var OAuthCookieManager = require('./OAuthCookieManager.cjs.js');
|
|
11
|
+
var CookieScopeManager = require('./CookieScopeManager.cjs.js');
|
|
12
|
+
|
|
13
|
+
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
|
|
14
|
+
|
|
15
|
+
var crypto__default = /*#__PURE__*/_interopDefaultCompat(crypto);
|
|
16
|
+
|
|
17
|
+
function createOAuthRouteHandlers(options) {
|
|
18
|
+
const {
|
|
19
|
+
authenticator,
|
|
20
|
+
config,
|
|
21
|
+
baseUrl,
|
|
22
|
+
appUrl,
|
|
23
|
+
providerId,
|
|
24
|
+
isOriginAllowed,
|
|
25
|
+
cookieConfigurer,
|
|
26
|
+
resolverContext,
|
|
27
|
+
signInResolver
|
|
28
|
+
} = options;
|
|
29
|
+
const defaultAppOrigin = new url.URL(appUrl).origin;
|
|
30
|
+
const callbackUrl = config.getOptionalString("callbackUrl") ?? `${baseUrl}/${providerId}/handler/frame`;
|
|
31
|
+
const stateTransform = options.stateTransform ?? ((state) => ({ state }));
|
|
32
|
+
const profileTransform = options.profileTransform ?? authenticator.defaultProfileTransform;
|
|
33
|
+
const authenticatorCtx = authenticator.initialize({ config, callbackUrl });
|
|
34
|
+
const cookieManager = new OAuthCookieManager.OAuthCookieManager({
|
|
35
|
+
baseUrl,
|
|
36
|
+
callbackUrl,
|
|
37
|
+
defaultAppOrigin,
|
|
38
|
+
providerId,
|
|
39
|
+
cookieConfigurer
|
|
40
|
+
});
|
|
41
|
+
const scopeManager = CookieScopeManager.CookieScopeManager.create({
|
|
42
|
+
config,
|
|
43
|
+
authenticator,
|
|
44
|
+
cookieManager,
|
|
45
|
+
additionalScopes: options.additionalScopes
|
|
46
|
+
});
|
|
47
|
+
return {
|
|
48
|
+
async start(req, res) {
|
|
49
|
+
const env = req.query.env?.toString();
|
|
50
|
+
const origin = req.query.origin?.toString();
|
|
51
|
+
const redirectUrl = req.query.redirectUrl?.toString();
|
|
52
|
+
const flow = req.query.flow?.toString();
|
|
53
|
+
if (!env) {
|
|
54
|
+
throw new errors.InputError("No env provided in request query parameters");
|
|
55
|
+
}
|
|
56
|
+
const nonce = crypto__default.default.randomBytes(16).toString("base64");
|
|
57
|
+
cookieManager.setNonce(res, nonce, origin);
|
|
58
|
+
const { scope, scopeState } = await scopeManager.start(req);
|
|
59
|
+
const state$1 = { nonce, env, origin, redirectUrl, flow, ...scopeState };
|
|
60
|
+
const { state: transformedState } = await stateTransform(state$1, { req });
|
|
61
|
+
const { url, status } = await options.authenticator.start(
|
|
62
|
+
{
|
|
63
|
+
req,
|
|
64
|
+
scope,
|
|
65
|
+
state: state.encodeOAuthState(transformedState)
|
|
66
|
+
},
|
|
67
|
+
authenticatorCtx
|
|
68
|
+
);
|
|
69
|
+
res.statusCode = status || 302;
|
|
70
|
+
res.setHeader("Location", url);
|
|
71
|
+
res.setHeader("Content-Length", "0");
|
|
72
|
+
res.end();
|
|
73
|
+
},
|
|
74
|
+
async frameHandler(req, res) {
|
|
75
|
+
let origin = defaultAppOrigin;
|
|
76
|
+
let state$1;
|
|
77
|
+
try {
|
|
78
|
+
state$1 = state.decodeOAuthState(req.query.state?.toString() ?? "");
|
|
79
|
+
if (state$1.origin) {
|
|
80
|
+
try {
|
|
81
|
+
origin = new url.URL(state$1.origin).origin;
|
|
82
|
+
} catch {
|
|
83
|
+
throw new errors.NotAllowedError("App origin is invalid, failed to parse");
|
|
84
|
+
}
|
|
85
|
+
if (!isOriginAllowed(origin)) {
|
|
86
|
+
throw new errors.NotAllowedError(`Origin '${origin}' is not allowed`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const cookieNonce = cookieManager.getNonce(req);
|
|
90
|
+
const stateNonce = state$1.nonce;
|
|
91
|
+
if (!cookieNonce) {
|
|
92
|
+
throw new errors.NotAllowedError("Auth response is missing cookie nonce");
|
|
93
|
+
}
|
|
94
|
+
if (cookieNonce !== stateNonce) {
|
|
95
|
+
throw new errors.NotAllowedError("Invalid nonce");
|
|
96
|
+
}
|
|
97
|
+
const result = await authenticator.authenticate(
|
|
98
|
+
{ req },
|
|
99
|
+
authenticatorCtx
|
|
100
|
+
);
|
|
101
|
+
const { profile } = await profileTransform(result, resolverContext);
|
|
102
|
+
const signInResult = signInResolver && await signInResolver({ profile, result }, resolverContext);
|
|
103
|
+
const grantedScopes = await scopeManager.handleCallback(req, {
|
|
104
|
+
result,
|
|
105
|
+
state: state$1,
|
|
106
|
+
origin
|
|
107
|
+
});
|
|
108
|
+
const response = {
|
|
109
|
+
profile,
|
|
110
|
+
providerInfo: {
|
|
111
|
+
idToken: result.session.idToken,
|
|
112
|
+
accessToken: result.session.accessToken,
|
|
113
|
+
scope: grantedScopes,
|
|
114
|
+
expiresInSeconds: result.session.expiresInSeconds
|
|
115
|
+
},
|
|
116
|
+
...signInResult && {
|
|
117
|
+
backstageIdentity: prepareBackstageIdentityResponse.prepareBackstageIdentityResponse(signInResult)
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
if (result.session.refreshToken) {
|
|
121
|
+
cookieManager.setRefreshToken(
|
|
122
|
+
res,
|
|
123
|
+
result.session.refreshToken,
|
|
124
|
+
origin
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
if (state$1.flow === "redirect") {
|
|
128
|
+
if (!state$1.redirectUrl) {
|
|
129
|
+
throw new errors.InputError(
|
|
130
|
+
"No redirectUrl provided in request query parameters"
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
res.redirect(state$1.redirectUrl);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
sendWebMessageResponse.sendWebMessageResponse(res, origin, {
|
|
137
|
+
type: "authorization_response",
|
|
138
|
+
response
|
|
139
|
+
});
|
|
140
|
+
} catch (error) {
|
|
141
|
+
const { name, message } = errors.isError(error) ? error : new Error("Encountered invalid error");
|
|
142
|
+
if (state$1?.flow === "redirect" && state$1?.redirectUrl) {
|
|
143
|
+
const redirectUrl = new url.URL(state$1.redirectUrl);
|
|
144
|
+
redirectUrl.searchParams.set("error", message);
|
|
145
|
+
res.redirect(redirectUrl.toString());
|
|
146
|
+
} else {
|
|
147
|
+
sendWebMessageResponse.sendWebMessageResponse(res, origin, {
|
|
148
|
+
type: "authorization_response",
|
|
149
|
+
error: { name, message }
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
async logout(req, res) {
|
|
155
|
+
if (req.header("X-Requested-With") !== "XMLHttpRequest") {
|
|
156
|
+
throw new errors.AuthenticationError("Invalid X-Requested-With header");
|
|
157
|
+
}
|
|
158
|
+
if (authenticator.logout) {
|
|
159
|
+
const refreshToken = cookieManager.getRefreshToken(req);
|
|
160
|
+
await authenticator.logout({ req, refreshToken }, authenticatorCtx);
|
|
161
|
+
}
|
|
162
|
+
cookieManager.removeRefreshToken(res, req.get("origin"));
|
|
163
|
+
await scopeManager.clear(req);
|
|
164
|
+
res.status(200).end();
|
|
165
|
+
},
|
|
166
|
+
async refresh(req, res) {
|
|
167
|
+
if (req.header("X-Requested-With") !== "XMLHttpRequest") {
|
|
168
|
+
throw new errors.AuthenticationError("Invalid X-Requested-With header");
|
|
169
|
+
}
|
|
170
|
+
try {
|
|
171
|
+
const refreshToken = cookieManager.getRefreshToken(req);
|
|
172
|
+
if (!refreshToken) {
|
|
173
|
+
throw new errors.InputError("Missing session cookie");
|
|
174
|
+
}
|
|
175
|
+
const scopeRefresh = await scopeManager.refresh(req);
|
|
176
|
+
const result = await authenticator.refresh(
|
|
177
|
+
{ req, scope: scopeRefresh.scope, refreshToken },
|
|
178
|
+
authenticatorCtx
|
|
179
|
+
);
|
|
180
|
+
const grantedScope = await scopeRefresh.commit(result);
|
|
181
|
+
const { profile } = await profileTransform(result, resolverContext);
|
|
182
|
+
const newRefreshToken = result.session.refreshToken;
|
|
183
|
+
if (newRefreshToken && newRefreshToken !== refreshToken) {
|
|
184
|
+
cookieManager.setRefreshToken(
|
|
185
|
+
res,
|
|
186
|
+
newRefreshToken,
|
|
187
|
+
req.get("origin")
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
const response = {
|
|
191
|
+
profile,
|
|
192
|
+
providerInfo: {
|
|
193
|
+
idToken: result.session.idToken,
|
|
194
|
+
accessToken: result.session.accessToken,
|
|
195
|
+
scope: grantedScope,
|
|
196
|
+
expiresInSeconds: result.session.expiresInSeconds
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
if (signInResolver) {
|
|
200
|
+
const identity = await signInResolver(
|
|
201
|
+
{ profile, result },
|
|
202
|
+
resolverContext
|
|
203
|
+
);
|
|
204
|
+
response.backstageIdentity = prepareBackstageIdentityResponse.prepareBackstageIdentityResponse(identity);
|
|
205
|
+
}
|
|
206
|
+
res.status(200).json(response);
|
|
207
|
+
} catch (error) {
|
|
208
|
+
throw new errors.AuthenticationError("Refresh failed", error);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
exports.createOAuthRouteHandlers = createOAuthRouteHandlers;
|
|
215
|
+
//# sourceMappingURL=createOAuthRouteHandlers.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createOAuthRouteHandlers.cjs.js","sources":["../../src/oauth/createOAuthRouteHandlers.ts"],"sourcesContent":["/*\n * Copyright 2020 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 express from 'express';\nimport crypto from 'crypto';\nimport { URL } from 'url';\nimport {\n AuthenticationError,\n InputError,\n isError,\n NotAllowedError,\n} from '@backstage/errors';\nimport {\n encodeOAuthState,\n decodeOAuthState,\n OAuthStateTransform,\n} from './state';\nimport { sendWebMessageResponse } from '../flow';\nimport { prepareBackstageIdentityResponse } from '../identity';\nimport { OAuthCookieManager } from './OAuthCookieManager';\nimport {\n AuthProviderRouteHandlers,\n AuthResolverContext,\n ClientAuthResponse,\n CookieConfigurer,\n ProfileTransform,\n SignInResolver,\n} from '../types';\nimport { OAuthAuthenticator, OAuthAuthenticatorResult } from './types';\nimport { Config } from '@backstage/config';\nimport { CookieScopeManager } from './CookieScopeManager';\n\n/** @public */\nexport interface OAuthRouteHandlersOptions<TProfile> {\n authenticator: OAuthAuthenticator<any, TProfile>;\n appUrl: string;\n baseUrl: string;\n isOriginAllowed: (origin: string) => boolean;\n providerId: string;\n config: Config;\n resolverContext: AuthResolverContext;\n additionalScopes?: string[];\n stateTransform?: OAuthStateTransform;\n profileTransform?: ProfileTransform<OAuthAuthenticatorResult<TProfile>>;\n cookieConfigurer?: CookieConfigurer;\n signInResolver?: SignInResolver<OAuthAuthenticatorResult<TProfile>>;\n}\n\n/** @internal */\ntype ClientOAuthResponse = ClientAuthResponse<{\n /**\n * An access token issued for the signed in user.\n */\n accessToken: string;\n /**\n * (Optional) Id token issued for the signed in user.\n */\n idToken?: string;\n /**\n * Expiry of the access token in seconds.\n */\n expiresInSeconds?: number;\n /**\n * Scopes granted for the access token.\n */\n scope: string;\n}>;\n\n/** @public */\nexport function createOAuthRouteHandlers<TProfile>(\n options: OAuthRouteHandlersOptions<TProfile>,\n): AuthProviderRouteHandlers {\n const {\n authenticator,\n config,\n baseUrl,\n appUrl,\n providerId,\n isOriginAllowed,\n cookieConfigurer,\n resolverContext,\n signInResolver,\n } = options;\n\n const defaultAppOrigin = new URL(appUrl).origin;\n const callbackUrl =\n config.getOptionalString('callbackUrl') ??\n `${baseUrl}/${providerId}/handler/frame`;\n\n const stateTransform = options.stateTransform ?? (state => ({ state }));\n const profileTransform =\n options.profileTransform ?? authenticator.defaultProfileTransform;\n const authenticatorCtx = authenticator.initialize({ config, callbackUrl });\n const cookieManager = new OAuthCookieManager({\n baseUrl,\n callbackUrl,\n defaultAppOrigin,\n providerId,\n cookieConfigurer,\n });\n\n const scopeManager = CookieScopeManager.create({\n config,\n authenticator,\n cookieManager,\n additionalScopes: options.additionalScopes,\n });\n\n return {\n async start(\n this: never,\n req: express.Request,\n res: express.Response,\n ): Promise<void> {\n const env = req.query.env?.toString();\n const origin = req.query.origin?.toString();\n const redirectUrl = req.query.redirectUrl?.toString();\n const flow = req.query.flow?.toString();\n\n if (!env) {\n throw new InputError('No env provided in request query parameters');\n }\n\n const nonce = crypto.randomBytes(16).toString('base64');\n // set a nonce cookie before redirecting to oauth provider\n cookieManager.setNonce(res, nonce, origin);\n\n const { scope, scopeState } = await scopeManager.start(req);\n\n const state = { nonce, env, origin, redirectUrl, flow, ...scopeState };\n const { state: transformedState } = await stateTransform(state, { req });\n\n const { url, status } = await options.authenticator.start(\n {\n req,\n scope,\n state: encodeOAuthState(transformedState),\n },\n authenticatorCtx,\n );\n\n res.statusCode = status || 302;\n res.setHeader('Location', url);\n res.setHeader('Content-Length', '0');\n res.end();\n },\n\n async frameHandler(\n this: never,\n req: express.Request,\n res: express.Response,\n ): Promise<void> {\n let origin = defaultAppOrigin;\n let state;\n\n try {\n state = decodeOAuthState(req.query.state?.toString() ?? '');\n\n if (state.origin) {\n try {\n origin = new URL(state.origin).origin;\n } catch {\n throw new NotAllowedError('App origin is invalid, failed to parse');\n }\n if (!isOriginAllowed(origin)) {\n throw new NotAllowedError(`Origin '${origin}' is not allowed`);\n }\n }\n\n // The same nonce is passed through cookie and state, and they must match\n const cookieNonce = cookieManager.getNonce(req);\n const stateNonce = state.nonce;\n if (!cookieNonce) {\n throw new NotAllowedError('Auth response is missing cookie nonce');\n }\n if (cookieNonce !== stateNonce) {\n throw new NotAllowedError('Invalid nonce');\n }\n\n const result = await authenticator.authenticate(\n { req },\n authenticatorCtx,\n );\n const { profile } = await profileTransform(result, resolverContext);\n\n const signInResult =\n signInResolver &&\n (await signInResolver({ profile, result }, resolverContext));\n\n const grantedScopes = await scopeManager.handleCallback(req, {\n result,\n state,\n origin,\n });\n\n const response: ClientOAuthResponse = {\n profile,\n providerInfo: {\n idToken: result.session.idToken,\n accessToken: result.session.accessToken,\n scope: grantedScopes,\n expiresInSeconds: result.session.expiresInSeconds,\n },\n ...(signInResult && {\n backstageIdentity: prepareBackstageIdentityResponse(signInResult),\n }),\n };\n\n if (result.session.refreshToken) {\n // set new refresh token\n cookieManager.setRefreshToken(\n res,\n result.session.refreshToken,\n origin,\n );\n }\n\n // When using the redirect flow we rely on refresh token we just\n // acquired to get a new session once we're back in the app.\n if (state.flow === 'redirect') {\n if (!state.redirectUrl) {\n throw new InputError(\n 'No redirectUrl provided in request query parameters',\n );\n }\n res.redirect(state.redirectUrl);\n return;\n }\n\n // post message back to popup if successful\n sendWebMessageResponse(res, origin, {\n type: 'authorization_response',\n response,\n });\n } catch (error) {\n const { name, message } = isError(error)\n ? error\n : new Error('Encountered invalid error'); // Being a bit safe and not forwarding the bad value\n\n if (state?.flow === 'redirect' && state?.redirectUrl) {\n const redirectUrl = new URL(state.redirectUrl);\n redirectUrl.searchParams.set('error', message);\n\n // set the error in a cookie and redirect user back to sign in where the error can be rendered\n res.redirect(redirectUrl.toString());\n } else {\n // post error message back to popup if failure\n sendWebMessageResponse(res, origin, {\n type: 'authorization_response',\n error: { name, message },\n });\n }\n }\n },\n\n async logout(\n this: never,\n req: express.Request,\n res: express.Response,\n ): Promise<void> {\n // We use this as a lightweight CSRF protection\n if (req.header('X-Requested-With') !== 'XMLHttpRequest') {\n throw new AuthenticationError('Invalid X-Requested-With header');\n }\n\n if (authenticator.logout) {\n const refreshToken = cookieManager.getRefreshToken(req);\n await authenticator.logout({ req, refreshToken }, authenticatorCtx);\n }\n\n // remove refresh token cookie if it is set\n cookieManager.removeRefreshToken(res, req.get('origin'));\n\n // remove persisted scopes\n await scopeManager.clear(req);\n\n res.status(200).end();\n },\n\n async refresh(\n this: never,\n req: express.Request,\n res: express.Response,\n ): Promise<void> {\n // We use this as a lightweight CSRF protection\n if (req.header('X-Requested-With') !== 'XMLHttpRequest') {\n throw new AuthenticationError('Invalid X-Requested-With header');\n }\n\n try {\n const refreshToken = cookieManager.getRefreshToken(req);\n\n // throw error if refresh token is missing in the request\n if (!refreshToken) {\n throw new InputError('Missing session cookie');\n }\n\n const scopeRefresh = await scopeManager.refresh(req);\n\n const result = await authenticator.refresh(\n { req, scope: scopeRefresh.scope, refreshToken },\n authenticatorCtx,\n );\n\n const grantedScope = await scopeRefresh.commit(result);\n\n const { profile } = await profileTransform(result, resolverContext);\n\n const newRefreshToken = result.session.refreshToken;\n if (newRefreshToken && newRefreshToken !== refreshToken) {\n cookieManager.setRefreshToken(\n res,\n newRefreshToken,\n req.get('origin'),\n );\n }\n\n const response: ClientOAuthResponse = {\n profile,\n providerInfo: {\n idToken: result.session.idToken,\n accessToken: result.session.accessToken,\n scope: grantedScope,\n expiresInSeconds: result.session.expiresInSeconds,\n },\n };\n\n if (signInResolver) {\n const identity = await signInResolver(\n { profile, result },\n resolverContext,\n );\n response.backstageIdentity =\n prepareBackstageIdentityResponse(identity);\n }\n\n res.status(200).json(response);\n } catch (error) {\n throw new AuthenticationError('Refresh failed', error);\n }\n },\n };\n}\n"],"names":["URL","OAuthCookieManager","CookieScopeManager","InputError","crypto","state","encodeOAuthState","decodeOAuthState","NotAllowedError","prepareBackstageIdentityResponse","sendWebMessageResponse","isError","AuthenticationError"],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var pickBy = require('lodash/pickBy');
|
|
4
|
+
var errors = require('@backstage/errors');
|
|
5
|
+
|
|
6
|
+
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
|
|
7
|
+
|
|
8
|
+
var pickBy__default = /*#__PURE__*/_interopDefaultCompat(pickBy);
|
|
9
|
+
|
|
10
|
+
function encodeOAuthState(state) {
|
|
11
|
+
const stateString = new URLSearchParams(
|
|
12
|
+
pickBy__default.default(state, (value) => value !== void 0)
|
|
13
|
+
).toString();
|
|
14
|
+
return Buffer.from(stateString, "utf-8").toString("hex");
|
|
15
|
+
}
|
|
16
|
+
function decodeOAuthState(encodedState) {
|
|
17
|
+
const state = Object.fromEntries(
|
|
18
|
+
new URLSearchParams(Buffer.from(encodedState, "hex").toString("utf-8"))
|
|
19
|
+
);
|
|
20
|
+
if (!state.env || state.env?.length === 0) {
|
|
21
|
+
throw new errors.NotAllowedError("OAuth state is invalid, missing env");
|
|
22
|
+
}
|
|
23
|
+
if (!state.nonce || state.nonce?.length === 0) {
|
|
24
|
+
throw new errors.NotAllowedError("OAuth state is invalid, missing nonce");
|
|
25
|
+
}
|
|
26
|
+
return state;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
exports.decodeOAuthState = decodeOAuthState;
|
|
30
|
+
exports.encodeOAuthState = encodeOAuthState;
|
|
31
|
+
//# sourceMappingURL=state.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state.cjs.js","sources":["../../src/oauth/state.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 pickBy from 'lodash/pickBy';\nimport { Request } from 'express';\nimport { NotAllowedError } from '@backstage/errors';\n\n/**\n * A type for the serialized value in the `state` parameter of the OAuth authorization flow\n * @public\n */\nexport type OAuthState = {\n nonce: string;\n env: string;\n origin?: string;\n scope?: string;\n redirectUrl?: string;\n flow?: string;\n audience?: string;\n};\n\n/** @public */\nexport type OAuthStateTransform = (\n state: OAuthState,\n context: { req: Request },\n) => Promise<{ state: OAuthState }>;\n\n/** @public */\nexport function encodeOAuthState(state: OAuthState): string {\n const stateString = new URLSearchParams(\n pickBy<string>(state, value => value !== undefined),\n ).toString();\n\n return Buffer.from(stateString, 'utf-8').toString('hex');\n}\n\n/** @public */\nexport function decodeOAuthState(encodedState: string): OAuthState {\n const state = Object.fromEntries(\n new URLSearchParams(Buffer.from(encodedState, 'hex').toString('utf-8')),\n );\n if (!state.env || state.env?.length === 0) {\n throw new NotAllowedError('OAuth state is invalid, missing env');\n }\n if (!state.nonce || state.nonce?.length === 0) {\n throw new NotAllowedError('OAuth state is invalid, missing nonce');\n }\n\n return state as OAuthState;\n}\n"],"names":["pickBy","NotAllowedError"],"mappings":";;;;;;;;;AAyCO,SAAS,iBAAiB,KAA2B,EAAA;AAC1D,EAAA,MAAM,cAAc,IAAI,eAAA;AAAA,IACtBA,uBAAe,CAAA,KAAA,EAAO,CAAS,KAAA,KAAA,KAAA,KAAU,KAAS,CAAA,CAAA;AAAA,IAClD,QAAS,EAAA,CAAA;AAEX,EAAA,OAAO,OAAO,IAAK,CAAA,WAAA,EAAa,OAAO,CAAA,CAAE,SAAS,KAAK,CAAA,CAAA;AACzD,CAAA;AAGO,SAAS,iBAAiB,YAAkC,EAAA;AACjE,EAAA,MAAM,QAAQ,MAAO,CAAA,WAAA;AAAA,IACnB,IAAI,gBAAgB,MAAO,CAAA,IAAA,CAAK,cAAc,KAAK,CAAA,CAAE,QAAS,CAAA,OAAO,CAAC,CAAA;AAAA,GACxE,CAAA;AACA,EAAA,IAAI,CAAC,KAAM,CAAA,GAAA,IAAO,KAAM,CAAA,GAAA,EAAK,WAAW,CAAG,EAAA;AACzC,IAAM,MAAA,IAAIC,uBAAgB,qCAAqC,CAAA,CAAA;AAAA,GACjE;AACA,EAAA,IAAI,CAAC,KAAM,CAAA,KAAA,IAAS,KAAM,CAAA,KAAA,EAAO,WAAW,CAAG,EAAA;AAC7C,IAAM,MAAA,IAAIA,uBAAgB,uCAAuC,CAAA,CAAA;AAAA,GACnE;AAEA,EAAO,OAAA,KAAA,CAAA;AACT;;;;;"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.cjs.js","sources":["../../src/oauth/types.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 { Config } from '@backstage/config';\nimport { Request } from 'express';\nimport { ProfileTransform } from '../types';\n\n/** @public */\nexport interface OAuthSession {\n accessToken: string;\n tokenType: string;\n idToken?: string;\n scope: string;\n expiresInSeconds?: number;\n refreshToken?: string;\n refreshTokenExpiresInSeconds?: number;\n}\n\n/** @public */\nexport interface OAuthAuthenticatorScopeOptions {\n persist?: boolean;\n required?: string[];\n transform?: (options: {\n /** Scopes requested by the client */\n requested: Iterable<string>;\n /** Scopes which have already been granted */\n granted: Iterable<string>;\n /** Scopes that are required for the authenticator to function */\n required: Iterable<string>;\n /** Additional scopes added through configuration */\n additional: Iterable<string>;\n }) => Iterable<string>;\n}\n\n/** @public */\nexport interface OAuthAuthenticatorStartInput {\n scope: string;\n state: string;\n req: Request;\n}\n\n/** @public */\nexport interface OAuthAuthenticatorAuthenticateInput {\n req: Request;\n}\n\n/** @public */\nexport interface OAuthAuthenticatorRefreshInput {\n scope: string;\n refreshToken: string;\n req: Request;\n}\n\n/** @public */\nexport interface OAuthAuthenticatorLogoutInput {\n accessToken?: string;\n refreshToken?: string;\n req: Request;\n}\n\n/** @public */\nexport interface OAuthAuthenticatorResult<TProfile> {\n fullProfile: TProfile;\n session: OAuthSession;\n}\n\n/** @public */\nexport interface OAuthAuthenticator<TContext, TProfile> {\n defaultProfileTransform: ProfileTransform<OAuthAuthenticatorResult<TProfile>>;\n /** @deprecated use `scopes.persist` instead */\n shouldPersistScopes?: boolean;\n scopes?: OAuthAuthenticatorScopeOptions;\n initialize(ctx: { callbackUrl: string; config: Config }): TContext;\n start(\n input: OAuthAuthenticatorStartInput,\n ctx: TContext,\n ): Promise<{ url: string; status?: number }>;\n authenticate(\n input: OAuthAuthenticatorAuthenticateInput,\n ctx: TContext,\n ): Promise<OAuthAuthenticatorResult<TProfile>>;\n refresh(\n input: OAuthAuthenticatorRefreshInput,\n ctx: TContext,\n ): Promise<OAuthAuthenticatorResult<TProfile>>;\n logout?(input: OAuthAuthenticatorLogoutInput, ctx: TContext): Promise<void>;\n}\n\n/** @public */\nexport function createOAuthAuthenticator<TContext, TProfile>(\n authenticator: OAuthAuthenticator<TContext, TProfile>,\n): OAuthAuthenticator<TContext, TProfile> {\n return authenticator;\n}\n"],"names":[],"mappings":";;AAsGO,SAAS,yBACd,aACwC,EAAA;AACxC,EAAO,OAAA,aAAA,CAAA;AACT;;;;"}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var jose = require('jose');
|
|
4
|
+
|
|
5
|
+
class PassportHelpers {
|
|
6
|
+
constructor() {
|
|
7
|
+
}
|
|
8
|
+
static transformProfile = (profile, idToken) => {
|
|
9
|
+
let email = void 0;
|
|
10
|
+
if (profile.emails && profile.emails.length > 0) {
|
|
11
|
+
const [firstEmail] = profile.emails;
|
|
12
|
+
email = firstEmail.value;
|
|
13
|
+
} else if (profile.email) {
|
|
14
|
+
email = profile.email;
|
|
15
|
+
}
|
|
16
|
+
let picture = void 0;
|
|
17
|
+
if (profile.avatarUrl) {
|
|
18
|
+
picture = profile.avatarUrl;
|
|
19
|
+
} else if (profile.photos && profile.photos.length > 0) {
|
|
20
|
+
const [firstPhoto] = profile.photos;
|
|
21
|
+
picture = firstPhoto.value;
|
|
22
|
+
} else if (profile.photo) {
|
|
23
|
+
picture = profile.photo;
|
|
24
|
+
}
|
|
25
|
+
let displayName = profile.displayName ?? profile.username ?? profile.id;
|
|
26
|
+
if ((!email || !picture || !displayName) && idToken) {
|
|
27
|
+
try {
|
|
28
|
+
const decoded = jose.decodeJwt(idToken);
|
|
29
|
+
if (!email && decoded.email) {
|
|
30
|
+
email = decoded.email;
|
|
31
|
+
}
|
|
32
|
+
if (!picture && decoded.picture) {
|
|
33
|
+
picture = decoded.picture;
|
|
34
|
+
}
|
|
35
|
+
if (!displayName && decoded.name) {
|
|
36
|
+
displayName = decoded.name;
|
|
37
|
+
}
|
|
38
|
+
} catch (e) {
|
|
39
|
+
throw new Error(`Failed to parse id token and get profile info, ${e}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
email,
|
|
44
|
+
picture,
|
|
45
|
+
displayName
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
static async executeRedirectStrategy(req, providerStrategy, options) {
|
|
49
|
+
return new Promise((resolve) => {
|
|
50
|
+
const strategy = Object.create(providerStrategy);
|
|
51
|
+
strategy.redirect = (url, status) => {
|
|
52
|
+
resolve({ url, status: status ?? void 0 });
|
|
53
|
+
};
|
|
54
|
+
strategy.authenticate(req, { ...options });
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
static async executeFrameHandlerStrategy(req, providerStrategy, options) {
|
|
58
|
+
return new Promise((resolve, reject) => {
|
|
59
|
+
const strategy = Object.create(providerStrategy);
|
|
60
|
+
strategy.success = (result, privateInfo) => {
|
|
61
|
+
resolve({ result, privateInfo });
|
|
62
|
+
};
|
|
63
|
+
strategy.fail = (info) => {
|
|
64
|
+
reject(new Error(`Authentication rejected, ${info.message ?? ""}`));
|
|
65
|
+
};
|
|
66
|
+
strategy.error = (error) => {
|
|
67
|
+
let message = `Authentication failed, ${error.message}`;
|
|
68
|
+
if (error.oauthError?.data) {
|
|
69
|
+
try {
|
|
70
|
+
const errorData = JSON.parse(error.oauthError.data);
|
|
71
|
+
if (errorData.message) {
|
|
72
|
+
message += ` - ${errorData.message}`;
|
|
73
|
+
}
|
|
74
|
+
} catch (parseError) {
|
|
75
|
+
message += ` - ${error.oauthError}`;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
reject(new Error(message));
|
|
79
|
+
};
|
|
80
|
+
strategy.redirect = () => {
|
|
81
|
+
reject(new Error("Unexpected redirect"));
|
|
82
|
+
};
|
|
83
|
+
strategy.authenticate(req, { ...options ?? {} });
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
static async executeRefreshTokenStrategy(providerStrategy, refreshToken, scope) {
|
|
87
|
+
return new Promise((resolve, reject) => {
|
|
88
|
+
const anyStrategy = providerStrategy;
|
|
89
|
+
const OAuth2 = anyStrategy._oauth2.constructor;
|
|
90
|
+
const oauth2 = new OAuth2(
|
|
91
|
+
anyStrategy._oauth2._clientId,
|
|
92
|
+
anyStrategy._oauth2._clientSecret,
|
|
93
|
+
anyStrategy._oauth2._baseSite,
|
|
94
|
+
anyStrategy._oauth2._authorizeUrl,
|
|
95
|
+
anyStrategy._refreshURL || anyStrategy._oauth2._accessTokenUrl,
|
|
96
|
+
anyStrategy._oauth2._customHeaders
|
|
97
|
+
);
|
|
98
|
+
oauth2.getOAuthAccessToken(
|
|
99
|
+
refreshToken,
|
|
100
|
+
{
|
|
101
|
+
scope,
|
|
102
|
+
grant_type: "refresh_token"
|
|
103
|
+
},
|
|
104
|
+
(err, accessToken, newRefreshToken, params) => {
|
|
105
|
+
if (err) {
|
|
106
|
+
reject(
|
|
107
|
+
new Error(`Failed to refresh access token ${err.toString()}`)
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
if (!accessToken) {
|
|
111
|
+
reject(
|
|
112
|
+
new Error(
|
|
113
|
+
`Failed to refresh access token, no access token received`
|
|
114
|
+
)
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
resolve({
|
|
118
|
+
accessToken,
|
|
119
|
+
refreshToken: newRefreshToken,
|
|
120
|
+
params
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
);
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
static async executeFetchUserProfileStrategy(providerStrategy, accessToken) {
|
|
127
|
+
return new Promise((resolve, reject) => {
|
|
128
|
+
const anyStrategy = providerStrategy;
|
|
129
|
+
anyStrategy.userProfile(
|
|
130
|
+
accessToken,
|
|
131
|
+
(error, rawProfile) => {
|
|
132
|
+
if (error) {
|
|
133
|
+
reject(error);
|
|
134
|
+
} else {
|
|
135
|
+
resolve(rawProfile);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
);
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
exports.PassportHelpers = PassportHelpers;
|
|
144
|
+
//# sourceMappingURL=PassportHelpers.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PassportHelpers.cjs.js","sources":["../../src/passport/PassportHelpers.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 { Request } from 'express';\nimport { decodeJwt } from 'jose';\nimport { Strategy } from 'passport';\nimport { PassportProfile } from './types';\nimport { ProfileInfo } from '../types';\n\n// Re-declared here to avoid direct dependency on passport-oauth2\n/** @internal */\ninterface InternalOAuthError extends Error {\n oauthError?: {\n data?: string;\n };\n}\n\n/** @public */\nexport class PassportHelpers {\n private constructor() {}\n\n static transformProfile = (\n profile: PassportProfile,\n idToken?: string,\n ): ProfileInfo => {\n let email: string | undefined = undefined;\n if (profile.emails && profile.emails.length > 0) {\n const [firstEmail] = profile.emails;\n email = firstEmail.value;\n } else if (profile.email) {\n // This is the case for Atlassian\n email = profile.email;\n }\n\n let picture: string | undefined = undefined;\n if (profile.avatarUrl) {\n picture = profile.avatarUrl;\n } else if (profile.photos && profile.photos.length > 0) {\n const [firstPhoto] = profile.photos;\n picture = firstPhoto.value;\n } else if (profile.photo) {\n // This is the case for Atlassian\n picture = profile.photo;\n }\n\n let displayName: string | undefined =\n profile.displayName ?? profile.username ?? profile.id;\n\n if ((!email || !picture || !displayName) && idToken) {\n try {\n const decoded = decodeJwt(idToken) as {\n email?: string;\n name?: string;\n picture?: string;\n };\n if (!email && decoded.email) {\n email = decoded.email;\n }\n if (!picture && decoded.picture) {\n picture = decoded.picture;\n }\n if (!displayName && decoded.name) {\n displayName = decoded.name;\n }\n } catch (e) {\n throw new Error(`Failed to parse id token and get profile info, ${e}`);\n }\n }\n\n return {\n email,\n picture,\n displayName,\n };\n };\n\n static async executeRedirectStrategy(\n req: Request,\n providerStrategy: Strategy,\n options: Record<string, string>,\n ): Promise<{\n /**\n * URL to redirect to\n */\n url: string;\n /**\n * Status code to use for the redirect\n */\n status?: number;\n }> {\n return new Promise(resolve => {\n const strategy = Object.create(providerStrategy);\n strategy.redirect = (url: string, status?: number) => {\n resolve({ url, status: status ?? undefined });\n };\n\n strategy.authenticate(req, { ...options });\n });\n }\n\n static async executeFrameHandlerStrategy<TResult, TPrivateInfo = never>(\n req: Request,\n providerStrategy: Strategy,\n options?: Record<string, string>,\n ): Promise<{ result: TResult; privateInfo: TPrivateInfo }> {\n return new Promise((resolve, reject) => {\n const strategy = Object.create(providerStrategy);\n strategy.success = (result: any, privateInfo: any) => {\n resolve({ result, privateInfo });\n };\n strategy.fail = (\n info: { type: 'success' | 'error'; message?: string },\n // _status: number,\n ) => {\n reject(new Error(`Authentication rejected, ${info.message ?? ''}`));\n };\n strategy.error = (error: InternalOAuthError) => {\n let message = `Authentication failed, ${error.message}`;\n\n if (error.oauthError?.data) {\n try {\n const errorData = JSON.parse(error.oauthError.data);\n\n if (errorData.message) {\n message += ` - ${errorData.message}`;\n }\n } catch (parseError) {\n message += ` - ${error.oauthError}`;\n }\n }\n\n reject(new Error(message));\n };\n strategy.redirect = () => {\n reject(new Error('Unexpected redirect'));\n };\n strategy.authenticate(req, { ...(options ?? {}) });\n });\n }\n\n static async executeRefreshTokenStrategy(\n providerStrategy: Strategy,\n refreshToken: string,\n scope: string,\n ): Promise<{\n /**\n * An access token issued for the signed in user.\n */\n accessToken: string;\n /**\n * Optionally, the server can issue a new Refresh Token for the user\n */\n refreshToken?: string;\n params: any;\n }> {\n return new Promise((resolve, reject) => {\n const anyStrategy = providerStrategy as any;\n const OAuth2 = anyStrategy._oauth2.constructor;\n const oauth2 = new OAuth2(\n anyStrategy._oauth2._clientId,\n anyStrategy._oauth2._clientSecret,\n anyStrategy._oauth2._baseSite,\n anyStrategy._oauth2._authorizeUrl,\n anyStrategy._refreshURL || anyStrategy._oauth2._accessTokenUrl,\n anyStrategy._oauth2._customHeaders,\n );\n\n oauth2.getOAuthAccessToken(\n refreshToken,\n {\n scope,\n grant_type: 'refresh_token',\n },\n (\n err: Error | null,\n accessToken: string,\n newRefreshToken: string,\n params: any,\n ) => {\n if (err) {\n reject(\n new Error(`Failed to refresh access token ${err.toString()}`),\n );\n }\n if (!accessToken) {\n reject(\n new Error(\n `Failed to refresh access token, no access token received`,\n ),\n );\n }\n\n resolve({\n accessToken,\n refreshToken: newRefreshToken,\n params,\n });\n },\n );\n });\n }\n\n static async executeFetchUserProfileStrategy(\n providerStrategy: Strategy,\n accessToken: string,\n ): Promise<PassportProfile> {\n return new Promise((resolve, reject) => {\n const anyStrategy = providerStrategy as unknown as {\n userProfile(accessToken: string, callback: Function): void;\n };\n anyStrategy.userProfile(\n accessToken,\n (error: Error, rawProfile: PassportProfile) => {\n if (error) {\n reject(error);\n } else {\n resolve(rawProfile);\n }\n },\n );\n });\n }\n}\n"],"names":["decodeJwt"],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
require('zod-to-json-schema');
|
|
4
|
+
require('zod-validation-error');
|
|
5
|
+
require('@backstage/errors');
|
|
6
|
+
var readDeclarativeSignInResolver = require('../sign-in/readDeclarativeSignInResolver.cjs.js');
|
|
7
|
+
require('../sign-in/commonSignInResolvers.cjs.js');
|
|
8
|
+
var createProxyRouteHandlers = require('./createProxyRouteHandlers.cjs.js');
|
|
9
|
+
|
|
10
|
+
function createProxyAuthProviderFactory(options) {
|
|
11
|
+
return (ctx) => {
|
|
12
|
+
const signInResolver = options.signInResolver ?? readDeclarativeSignInResolver.readDeclarativeSignInResolver({
|
|
13
|
+
config: ctx.config,
|
|
14
|
+
signInResolverFactories: options.signInResolverFactories ?? {}
|
|
15
|
+
});
|
|
16
|
+
if (!signInResolver) {
|
|
17
|
+
throw new Error(
|
|
18
|
+
`No sign-in resolver configured for proxy auth provider '${ctx.providerId}'`
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
return createProxyRouteHandlers.createProxyAuthRouteHandlers({
|
|
22
|
+
signInResolver,
|
|
23
|
+
config: ctx.config,
|
|
24
|
+
authenticator: options.authenticator,
|
|
25
|
+
resolverContext: ctx.resolverContext,
|
|
26
|
+
profileTransform: options.profileTransform
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
exports.createProxyAuthProviderFactory = createProxyAuthProviderFactory;
|
|
32
|
+
//# sourceMappingURL=createProxyAuthProviderFactory.cjs.js.map
|