@backstage/plugin-auth-node 0.7.0-next.1 → 0.7.0-next.2
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
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @backstage/plugin-auth-node
|
|
2
2
|
|
|
3
|
+
## 0.7.0-next.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 9244b70: Added `OAuthAuthenticatorLogoutResult` type. The `logout` method on `OAuthAuthenticator` can now optionally return `{ logoutUrl }` to trigger a browser redirect after sign-out. This allows providers like Auth0 to clear their session cookies by redirecting to their logout endpoint.
|
|
8
|
+
- Updated dependencies
|
|
9
|
+
- @backstage/errors@1.3.0-next.0
|
|
10
|
+
- @backstage/backend-plugin-api@1.9.0-next.2
|
|
11
|
+
- @backstage/catalog-client@1.14.1-next.0
|
|
12
|
+
- @backstage/catalog-model@1.7.8-next.0
|
|
13
|
+
- @backstage/config@1.3.7-next.0
|
|
14
|
+
|
|
3
15
|
## 0.7.0-next.1
|
|
4
16
|
|
|
5
17
|
### Minor Changes
|
package/dist/index.d.ts
CHANGED
|
@@ -604,6 +604,15 @@ interface OAuthAuthenticatorLogoutInput {
|
|
|
604
604
|
req: Request;
|
|
605
605
|
}
|
|
606
606
|
/** @public */
|
|
607
|
+
interface OAuthAuthenticatorLogoutResult {
|
|
608
|
+
/**
|
|
609
|
+
* If set, the frontend will redirect the browser to this URL after clearing
|
|
610
|
+
* the Backstage session. Use this to terminate provider-side sessions (e.g.
|
|
611
|
+
* Auth0's `/v2/logout` endpoint).
|
|
612
|
+
*/
|
|
613
|
+
logoutUrl?: string;
|
|
614
|
+
}
|
|
615
|
+
/** @public */
|
|
607
616
|
interface OAuthAuthenticatorResult<TProfile> {
|
|
608
617
|
fullProfile: TProfile;
|
|
609
618
|
session: OAuthSession;
|
|
@@ -624,7 +633,7 @@ interface OAuthAuthenticator<TContext, TProfile> {
|
|
|
624
633
|
}>;
|
|
625
634
|
authenticate(input: OAuthAuthenticatorAuthenticateInput, ctx: TContext): Promise<OAuthAuthenticatorResult<TProfile>>;
|
|
626
635
|
refresh(input: OAuthAuthenticatorRefreshInput, ctx: TContext): Promise<OAuthAuthenticatorResult<TProfile>>;
|
|
627
|
-
logout?(input: OAuthAuthenticatorLogoutInput, ctx: TContext): Promise<void>;
|
|
636
|
+
logout?(input: OAuthAuthenticatorLogoutInput, ctx: TContext): Promise<void | OAuthAuthenticatorLogoutResult>;
|
|
628
637
|
}
|
|
629
638
|
/** @public */
|
|
630
639
|
declare function createOAuthAuthenticator<TContext, TProfile>(authenticator: OAuthAuthenticator<TContext, TProfile>): OAuthAuthenticator<TContext, TProfile>;
|
|
@@ -828,4 +837,4 @@ interface ProxyAuthRouteHandlersOptions<TResult> {
|
|
|
828
837
|
declare function createProxyAuthRouteHandlers<TResult>(options: ProxyAuthRouteHandlersOptions<TResult>): AuthProviderRouteHandlers;
|
|
829
838
|
|
|
830
839
|
export { DefaultIdentityClient, IdentityClient, OAuthEnvironmentHandler, PassportHelpers, PassportOAuthAuthenticatorHelper, authOwnershipResolutionExtensionPoint, authProvidersExtensionPoint, commonSignInResolvers, createOAuthAuthenticator, createOAuthProviderFactory, createOAuthRouteHandlers, createProxyAuthProviderFactory, createProxyAuthRouteHandlers, createProxyAuthenticator, createSignInResolverFactory, decodeOAuthState, encodeOAuthState, getBearerTokenFromAuthorizationHeader, prepareBackstageIdentityResponse, readDeclarativeSignInResolver, sendWebMessageResponse, tokenTypes };
|
|
831
|
-
export type { AuthOwnershipResolutionExtensionPoint, AuthOwnershipResolver, AuthProviderConfig, AuthProviderFactory, AuthProviderRegistrationOptions, AuthProviderRouteHandlers, AuthProvidersExtensionPoint, AuthResolverCatalogUserQuery, AuthResolverContext, BackstageIdentityResponse, BackstageSignInResult, BackstageUserIdentity, ClientAuthResponse, CookieConfigurer, IdentityApi, IdentityApiGetIdentityRequest, IdentityClientOptions, OAuthAuthenticator, OAuthAuthenticatorAuthenticateInput, OAuthAuthenticatorLogoutInput, OAuthAuthenticatorRefreshInput, OAuthAuthenticatorResult, OAuthAuthenticatorScopeOptions, OAuthAuthenticatorStartInput, OAuthRouteHandlersOptions, OAuthSession, OAuthState, OAuthStateTransform, PassportDoneCallback, PassportOAuthDoneCallback, PassportOAuthPrivateInfo, PassportOAuthResult, PassportProfile, ProfileInfo, ProfileTransform, ProxyAuthRouteHandlersOptions, ProxyAuthenticator, ReadDeclarativeSignInResolverOptions, SignInInfo, SignInResolver, SignInResolverFactory, SignInResolverFactoryOptions, TokenParams, WebMessageResponse };
|
|
840
|
+
export type { AuthOwnershipResolutionExtensionPoint, AuthOwnershipResolver, AuthProviderConfig, AuthProviderFactory, AuthProviderRegistrationOptions, AuthProviderRouteHandlers, AuthProvidersExtensionPoint, AuthResolverCatalogUserQuery, AuthResolverContext, BackstageIdentityResponse, BackstageSignInResult, BackstageUserIdentity, ClientAuthResponse, CookieConfigurer, IdentityApi, IdentityApiGetIdentityRequest, IdentityClientOptions, OAuthAuthenticator, OAuthAuthenticatorAuthenticateInput, OAuthAuthenticatorLogoutInput, OAuthAuthenticatorLogoutResult, OAuthAuthenticatorRefreshInput, OAuthAuthenticatorResult, OAuthAuthenticatorScopeOptions, OAuthAuthenticatorStartInput, OAuthRouteHandlersOptions, OAuthSession, OAuthState, OAuthStateTransform, PassportDoneCallback, PassportOAuthDoneCallback, PassportOAuthPrivateInfo, PassportOAuthResult, PassportProfile, ProfileInfo, ProfileTransform, ProxyAuthRouteHandlersOptions, ProxyAuthenticator, ReadDeclarativeSignInResolverOptions, SignInInfo, SignInResolver, SignInResolverFactory, SignInResolverFactoryOptions, TokenParams, WebMessageResponse };
|
|
@@ -158,12 +158,30 @@ function createOAuthRouteHandlers(options) {
|
|
|
158
158
|
if (req.header("X-Requested-With") !== "XMLHttpRequest") {
|
|
159
159
|
throw new errors.AuthenticationError("Invalid X-Requested-With header");
|
|
160
160
|
}
|
|
161
|
+
const origin = req.get("origin");
|
|
162
|
+
if (origin && !isOriginAllowed(origin)) {
|
|
163
|
+
throw new errors.NotAllowedError(`Origin '${origin}' is not allowed`);
|
|
164
|
+
}
|
|
165
|
+
let logoutResult = void 0;
|
|
161
166
|
if (authenticator.logout) {
|
|
162
167
|
const refreshToken = cookieManager.getRefreshToken(req);
|
|
163
|
-
await authenticator.logout(
|
|
168
|
+
logoutResult = await authenticator.logout(
|
|
169
|
+
{ req, refreshToken },
|
|
170
|
+
authenticatorCtx
|
|
171
|
+
);
|
|
164
172
|
}
|
|
165
|
-
cookieManager.removeRefreshToken(res,
|
|
173
|
+
cookieManager.removeRefreshToken(res, origin);
|
|
166
174
|
await scopeManager.clear(req);
|
|
175
|
+
if (logoutResult?.logoutUrl) {
|
|
176
|
+
try {
|
|
177
|
+
const logoutUrl = new node_url.URL(logoutResult.logoutUrl);
|
|
178
|
+
if (logoutUrl.protocol === "https:" || logoutUrl.hostname === "localhost") {
|
|
179
|
+
res.status(200).json({ logoutUrl: logoutResult.logoutUrl });
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
} catch {
|
|
183
|
+
}
|
|
184
|
+
}
|
|
167
185
|
res.status(200).end();
|
|
168
186
|
},
|
|
169
187
|
async refresh(req, res) {
|
|
@@ -1 +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 'node:crypto';\nimport { URL } from 'node: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, readDurationFromConfig } 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 const sessionDuration = config.has('sessionDuration')\n ? readDurationFromConfig(config, { key: 'sessionDuration' })\n : undefined;\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 sessionDuration,\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 {\n req,\n scope: scopeRefresh.scope,\n scopeAlreadyGranted: scopeRefresh.scopeAlreadyGranted,\n refreshToken,\n },\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":["config","URL","readDurationFromConfig","OAuthCookieManager","CookieScopeManager","InputError","crypto","state","encodeOAuthState","decodeOAuthState","NotAllowedError","prepareBackstageIdentityResponse","sendWebMessageResponse","isError","AuthenticationError"],"mappings":"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"}
|
|
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 'node:crypto';\nimport { URL } from 'node: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 {\n OAuthAuthenticator,\n OAuthAuthenticatorLogoutResult,\n OAuthAuthenticatorResult,\n} from './types';\nimport { Config, readDurationFromConfig } 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 const sessionDuration = config.has('sessionDuration')\n ? readDurationFromConfig(config, { key: 'sessionDuration' })\n : undefined;\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 sessionDuration,\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 const origin = req.get('origin');\n if (origin && !isOriginAllowed(origin)) {\n throw new NotAllowedError(`Origin '${origin}' is not allowed`);\n }\n\n let logoutResult: void | OAuthAuthenticatorLogoutResult = undefined;\n if (authenticator.logout) {\n const refreshToken = cookieManager.getRefreshToken(req);\n logoutResult = await authenticator.logout(\n { req, refreshToken },\n authenticatorCtx,\n );\n }\n\n // remove refresh token cookie if it is set\n cookieManager.removeRefreshToken(res, origin);\n\n // remove persisted scopes\n await scopeManager.clear(req);\n\n if (logoutResult?.logoutUrl) {\n try {\n const logoutUrl = new URL(logoutResult.logoutUrl);\n if (\n logoutUrl.protocol === 'https:' ||\n logoutUrl.hostname === 'localhost'\n ) {\n res.status(200).json({ logoutUrl: logoutResult.logoutUrl });\n return;\n }\n } catch {\n // Malformed URL — fall through to empty response\n }\n }\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 {\n req,\n scope: scopeRefresh.scope,\n scopeAlreadyGranted: scopeRefresh.scopeAlreadyGranted,\n refreshToken,\n },\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":["config","URL","readDurationFromConfig","OAuthCookieManager","CookieScopeManager","InputError","crypto","state","encodeOAuthState","decodeOAuthState","NotAllowedError","prepareBackstageIdentityResponse","sendWebMessageResponse","isError","AuthenticationError"],"mappings":"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"}
|
|
@@ -1 +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 /**\n * Signals whether the requested scope has already been granted for the session. Will only be set if the `scopes.persist` option is enabled.\n */\n scopeAlreadyGranted?: boolean;\n\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
|
|
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 /**\n * Signals whether the requested scope has already been granted for the session. Will only be set if the `scopes.persist` option is enabled.\n */\n scopeAlreadyGranted?: boolean;\n\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 OAuthAuthenticatorLogoutResult {\n /**\n * If set, the frontend will redirect the browser to this URL after clearing\n * the Backstage session. Use this to terminate provider-side sessions (e.g.\n * Auth0's `/v2/logout` endpoint).\n */\n logoutUrl?: string;\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?(\n input: OAuthAuthenticatorLogoutInput,\n ctx: TContext,\n ): Promise<void | OAuthAuthenticatorLogoutResult>;\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":";;AAwHO,SAAS,yBACd,aAAA,EACwC;AACxC,EAAA,OAAO,aAAA;AACT;;;;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@backstage/plugin-auth-node",
|
|
3
|
-
"version": "0.7.0-next.
|
|
3
|
+
"version": "0.7.0-next.2",
|
|
4
4
|
"backstage": {
|
|
5
5
|
"role": "node-library",
|
|
6
6
|
"pluginId": "auth",
|
|
@@ -38,11 +38,11 @@
|
|
|
38
38
|
"test": "backstage-cli package test"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@backstage/backend-plugin-api": "1.9.0-next.
|
|
42
|
-
"@backstage/catalog-client": "1.14.0",
|
|
43
|
-
"@backstage/catalog-model": "1.7.
|
|
44
|
-
"@backstage/config": "1.3.
|
|
45
|
-
"@backstage/errors": "1.
|
|
41
|
+
"@backstage/backend-plugin-api": "1.9.0-next.2",
|
|
42
|
+
"@backstage/catalog-client": "1.14.1-next.0",
|
|
43
|
+
"@backstage/catalog-model": "1.7.8-next.0",
|
|
44
|
+
"@backstage/config": "1.3.7-next.0",
|
|
45
|
+
"@backstage/errors": "1.3.0-next.0",
|
|
46
46
|
"@backstage/types": "1.2.2",
|
|
47
47
|
"@types/express": "^4.17.6",
|
|
48
48
|
"@types/passport": "^1.0.3",
|
|
@@ -55,9 +55,9 @@
|
|
|
55
55
|
"zod-validation-error": "^4.0.2"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
|
-
"@backstage/backend-defaults": "0.16.1-next.
|
|
59
|
-
"@backstage/backend-test-utils": "1.11.2-next.
|
|
60
|
-
"@backstage/cli": "0.36.1-next.
|
|
58
|
+
"@backstage/backend-defaults": "0.16.1-next.2",
|
|
59
|
+
"@backstage/backend-test-utils": "1.11.2-next.2",
|
|
60
|
+
"@backstage/cli": "0.36.1-next.2",
|
|
61
61
|
"cookie-parser": "^1.4.6",
|
|
62
62
|
"express-promise-router": "^4.1.1",
|
|
63
63
|
"lodash": "^4.17.21",
|