@backstage/plugin-auth-backend 0.26.0 → 0.27.0-next.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -0
- package/config.d.ts +31 -0
- package/dist/authPlugin.cjs.js +14 -1
- package/dist/authPlugin.cjs.js.map +1 -1
- package/dist/database/OfflineSessionDatabase.cjs.js +136 -0
- package/dist/database/OfflineSessionDatabase.cjs.js.map +1 -0
- package/dist/identity/StaticKeyStore.cjs.js +2 -2
- package/dist/identity/StaticKeyStore.cjs.js.map +1 -1
- package/dist/lib/refreshToken.cjs.js +60 -0
- package/dist/lib/refreshToken.cjs.js.map +1 -0
- package/dist/service/OfflineAccessService.cjs.js +177 -0
- package/dist/service/OfflineAccessService.cjs.js.map +1 -0
- package/dist/service/OidcError.cjs.js +57 -0
- package/dist/service/OidcError.cjs.js.map +1 -0
- package/dist/service/OidcRouter.cjs.js +215 -142
- package/dist/service/OidcRouter.cjs.js.map +1 -1
- package/dist/service/OidcService.cjs.js +98 -20
- package/dist/service/OidcService.cjs.js.map +1 -1
- package/dist/service/router.cjs.js +2 -1
- package/dist/service/router.cjs.js.map +1 -1
- package/migrations/20251020000000_offline_sessions.js +78 -0
- package/package.json +16 -14
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OidcRouter.cjs.js","sources":["../../src/service/OidcRouter.ts"],"sourcesContent":["/*\n * Copyright 2025 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 */\nimport Router from 'express-promise-router';\nimport { OidcService } from './OidcService';\nimport { AuthenticationError, isError } from '@backstage/errors';\nimport {\n AuthService,\n HttpAuthService,\n LoggerService,\n RootConfigService,\n} from '@backstage/backend-plugin-api';\nimport { TokenIssuer } from '../identity/types';\nimport { UserInfoDatabase } from '../database/UserInfoDatabase';\nimport { OidcDatabase } from '../database/OidcDatabase';\nimport { json } from 'express';\nimport { readDcrTokenExpiration } from './readTokenExpiration.ts';\n\nexport class OidcRouter {\n private readonly oidc: OidcService;\n private readonly logger: LoggerService;\n private readonly auth: AuthService;\n private readonly appUrl: string;\n private readonly httpAuth: HttpAuthService;\n private readonly config: RootConfigService;\n\n private constructor(\n oidc: OidcService,\n logger: LoggerService,\n auth: AuthService,\n appUrl: string,\n httpAuth: HttpAuthService,\n config: RootConfigService,\n ) {\n this.oidc = oidc;\n this.logger = logger;\n this.auth = auth;\n this.appUrl = appUrl;\n this.httpAuth = httpAuth;\n this.config = config;\n }\n\n static create(options: {\n auth: AuthService;\n tokenIssuer: TokenIssuer;\n baseUrl: string;\n appUrl: string;\n logger: LoggerService;\n userInfo: UserInfoDatabase;\n oidc: OidcDatabase;\n httpAuth: HttpAuthService;\n config: RootConfigService;\n }) {\n return new OidcRouter(\n OidcService.create(options),\n options.logger,\n options.auth,\n options.appUrl,\n options.httpAuth,\n options.config,\n );\n }\n\n public getRouter() {\n const router = Router();\n\n router.use(json());\n\n // OpenID Provider Configuration endpoint\n // https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig\n // Returns the OpenID Provider Configuration document containing metadata about the provider\n router.get('/.well-known/openid-configuration', (_req, res) => {\n res.json(this.oidc.getConfiguration());\n });\n\n // JSON Web Key Set endpoint\n // https://openid.net/specs/openid-connect-core-1_0.html#rfc.section.10.1.1\n // Returns the public keys used to verify JWTs issued by this provider\n router.get('/.well-known/jwks.json', async (_req, res) => {\n const { keys } = await this.oidc.listPublicKeys();\n res.json({ keys });\n });\n\n // UserInfo endpoint\n // https://openid.net/specs/openid-connect-core-1_0.html#UserInfo\n // Returns claims about the authenticated user using an access token\n router.get('/v1/userinfo', async (req, res) => {\n const matches = req.headers.authorization?.match(/^Bearer[ ]+(\\S+)$/i);\n const token = matches?.[1];\n if (!token) {\n throw new AuthenticationError('No token provided');\n }\n\n const userInfo = await this.oidc.getUserInfo({ token });\n\n if (!userInfo) {\n res.status(404).send('User info not found');\n return;\n }\n\n res.json(userInfo);\n });\n\n if (\n this.config.getOptionalBoolean(\n 'auth.experimentalDynamicClientRegistration.enabled',\n )\n ) {\n // Authorization endpoint\n // https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest\n // Handles the initial authorization request from the client, validates parameters,\n // and redirects to the Authorization Session page for user approval\n router.get('/v1/authorize', async (req, res) => {\n // todo(blam): maybe add zod types for validating input\n const {\n client_id: clientId,\n redirect_uri: redirectUri,\n response_type: responseType,\n scope,\n state,\n nonce,\n code_challenge: codeChallenge,\n code_challenge_method: codeChallengeMethod,\n } = req.query;\n\n if (!clientId || !redirectUri || !responseType) {\n this.logger.error(`Failed to authorize: Missing required parameters`);\n return res.status(400).json({\n error: 'invalid_request',\n error_description:\n 'Missing required parameters: client_id, redirect_uri, response_type',\n });\n }\n\n try {\n const result = await this.oidc.createAuthorizationSession({\n clientId: clientId as string,\n redirectUri: redirectUri as string,\n responseType: responseType as string,\n scope: scope as string | undefined,\n state: state as string | undefined,\n nonce: nonce as string | undefined,\n codeChallenge: codeChallenge as string | undefined,\n codeChallengeMethod: codeChallengeMethod as string | undefined,\n });\n\n // todo(blam): maybe this URL could be overridable by config if\n // the plugin is mounted somewhere else?\n // support slashes in baseUrl?\n const authSessionRedirectUrl = new URL(\n `./oauth2/authorize/${result.id}`,\n ensureTrailingSlash(this.appUrl),\n );\n\n return res.redirect(authSessionRedirectUrl.toString());\n } catch (error) {\n const errorParams = new URLSearchParams();\n errorParams.append(\n 'error',\n isError(error) ? error.name : 'server_error',\n );\n errorParams.append(\n 'error_description',\n isError(error) ? error.message : 'Unknown error',\n );\n if (state) {\n errorParams.append('state', state as string);\n }\n\n const redirectUrl = new URL(redirectUri as string);\n redirectUrl.search = errorParams.toString();\n return res.redirect(redirectUrl.toString());\n }\n });\n\n // Authorization Session request details endpoint\n // Returns Authorization Session request details for the frontend\n router.get('/v1/sessions/:sessionId', async (req, res) => {\n const { sessionId } = req.params;\n\n if (!sessionId) {\n return res.status(400).json({\n error: 'invalid_request',\n error_description: 'Missing Authorization Session ID',\n });\n }\n\n try {\n const session = await this.oidc.getAuthorizationSession({\n sessionId,\n });\n\n return res.json({\n id: session.id,\n clientName: session.clientName,\n scope: session.scope,\n redirectUri: session.redirectUri,\n });\n } catch (error) {\n const description = isError(error) ? error.message : 'Unknown error';\n this.logger.error(\n `Failed to get authorization session: ${description}`,\n error,\n );\n return res.status(404).json({\n error: 'not_found',\n error_description: description,\n });\n }\n });\n\n // Authorization Session approval endpoint\n // Handles user approval of Authorization Session requests and generates authorization codes\n router.post('/v1/sessions/:sessionId/approve', async (req, res) => {\n const { sessionId } = req.params;\n\n if (!sessionId) {\n return res.status(400).json({\n error: 'invalid_request',\n error_description: 'Missing authorization session ID',\n });\n }\n\n try {\n const httpCredentials = await this.httpAuth.credentials(req);\n\n if (!this.auth.isPrincipal(httpCredentials, 'user')) {\n return res.status(401).json({\n error: 'unauthorized',\n error_description: 'Authentication required',\n });\n }\n\n const { userEntityRef } = httpCredentials.principal;\n\n const result = await this.oidc.approveAuthorizationSession({\n sessionId,\n userEntityRef,\n });\n\n return res.json({\n redirectUrl: result.redirectUrl,\n });\n } catch (error) {\n const description = isError(error) ? error.message : 'Unknown error';\n this.logger.error(\n `Failed to approve authorization session: ${description}`,\n error,\n );\n return res.status(400).json({\n error: 'invalid_request',\n error_description: description,\n });\n }\n });\n\n // Authorization Session rejection endpoint\n // Handles user rejection of Authorization Session requests and redirects with error\n router.post('/v1/sessions/:sessionId/reject', async (req, res) => {\n const { sessionId } = req.params;\n\n if (!sessionId) {\n return res.status(400).json({\n error: 'invalid_request',\n error_description: 'Missing authorization session ID',\n });\n }\n\n const httpCredentials = await this.httpAuth.credentials(req);\n\n if (!this.auth.isPrincipal(httpCredentials, 'user')) {\n return res.status(401).json({\n error: 'unauthorized',\n error_description: 'Authentication required',\n });\n }\n\n const { userEntityRef } = httpCredentials.principal;\n try {\n const session = await this.oidc.getAuthorizationSession({\n sessionId,\n });\n\n await this.oidc.rejectAuthorizationSession({\n sessionId,\n userEntityRef,\n });\n\n const errorParams = new URLSearchParams();\n errorParams.append('error', 'access_denied');\n errorParams.append('error_description', 'User denied the request');\n if (session.state) {\n errorParams.append('state', session.state);\n }\n\n const redirectUrl = new URL(session.redirectUri);\n redirectUrl.search = errorParams.toString();\n\n return res.json({\n redirectUrl: redirectUrl.toString(),\n });\n } catch (error) {\n const description = isError(error) ? error.message : 'Unknown error';\n this.logger.error(\n `Failed to reject authorization session: ${description}`,\n error,\n );\n\n return res.status(400).json({\n error: 'invalid_request',\n error_description: description,\n });\n }\n });\n\n // Token endpoint\n // https://openid.net/specs/openid-connect-core-1_0.html#TokenRequest\n // Exchanges authorization codes for access tokens and ID tokens\n router.post('/v1/token', async (req, res) => {\n // todo(blam): maybe add zod types for validating input\n const {\n grant_type: grantType,\n code,\n redirect_uri: redirectUri,\n code_verifier: codeVerifier,\n } = req.body;\n\n if (!grantType || !code || !redirectUri) {\n this.logger.error(\n `Failed to exchange code for token: Missing required parameters`,\n );\n return res.status(400).json({\n error: 'invalid_request',\n error_description: 'Missing required parameters',\n });\n }\n\n const expiresIn = readDcrTokenExpiration(this.config);\n\n try {\n const result = await this.oidc.exchangeCodeForToken({\n code,\n redirectUri,\n codeVerifier,\n grantType,\n expiresIn,\n });\n\n return res.json({\n access_token: result.accessToken,\n token_type: result.tokenType,\n expires_in: result.expiresIn,\n id_token: result.idToken,\n scope: result.scope,\n });\n } catch (error) {\n const description = isError(error) ? error.message : 'Unknown error';\n this.logger.error(\n `Failed to exchange code for token: ${description}`,\n error,\n );\n\n if (isError(error)) {\n if (error.name === 'AuthenticationError') {\n return res.status(401).json({\n error: 'invalid_client',\n error_description: error.message,\n });\n }\n if (error.name === 'InputError') {\n return res.status(400).json({\n error: 'invalid_request',\n error_description: error.message,\n });\n }\n }\n\n return res.status(500).json({\n error: 'server_error',\n error_description: description,\n });\n }\n });\n\n // Dynamic Client Registration endpoint\n // https://openid.net/specs/openid-connect-registration-1_0.html#ClientRegistration\n // Allows clients to register themselves dynamically with the provider\n router.post('/v1/register', async (req, res) => {\n // todo(blam): maybe add zod types for validating input\n const {\n client_name: clientName,\n redirect_uris: redirectUris,\n response_types: responseTypes,\n grant_types: grantTypes,\n scope,\n } = req.body;\n\n if (!redirectUris?.length) {\n res.status(400).json({\n error: 'invalid_request',\n error_description: 'redirect_uris is required',\n });\n return;\n }\n\n try {\n const client = await this.oidc.registerClient({\n clientName,\n redirectUris,\n responseTypes,\n grantTypes,\n scope,\n });\n\n res.status(201).json({\n client_id: client.clientId,\n redirect_uris: client.redirectUris,\n client_secret: client.clientSecret,\n });\n } catch (e) {\n const description = isError(e) ? e.message : 'Unknown error';\n this.logger.error(`Failed to register client: ${description}`, e);\n\n res.status(500).json({\n error: 'server_error',\n error_description: `Failed to register client: ${description}`,\n });\n }\n });\n }\n\n return router;\n }\n}\nfunction ensureTrailingSlash(appUrl: string): string | URL | undefined {\n if (appUrl.endsWith('/')) {\n return appUrl;\n }\n return `${appUrl}/`;\n}\n"],"names":["OidcService","Router","json","AuthenticationError","isError","readDcrTokenExpiration"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"OidcRouter.cjs.js","sources":["../../src/service/OidcRouter.ts"],"sourcesContent":["/*\n * Copyright 2025 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 */\nimport Router from 'express-promise-router';\nimport { OidcService } from './OidcService';\nimport { AuthenticationError, isError } from '@backstage/errors';\nimport {\n AuthService,\n HttpAuthService,\n LoggerService,\n RootConfigService,\n} from '@backstage/backend-plugin-api';\nimport { TokenIssuer } from '../identity/types';\nimport { UserInfoDatabase } from '../database/UserInfoDatabase';\nimport { OidcDatabase } from '../database/OidcDatabase';\nimport { OfflineAccessService } from './OfflineAccessService';\nimport { json } from 'express';\nimport { readDcrTokenExpiration } from './readTokenExpiration';\nimport { z } from 'zod';\nimport { fromZodError } from 'zod-validation-error';\nimport { OidcError } from './OidcError';\n\nconst authorizeQuerySchema = z.object({\n client_id: z.string().min(1),\n redirect_uri: z.string().url(),\n response_type: z.string().min(1),\n scope: z.string().optional(),\n state: z.string().optional(),\n nonce: z.string().optional(),\n code_challenge: z.string().optional(),\n code_challenge_method: z.string().optional(),\n});\n\nconst sessionIdParamSchema = z.object({\n sessionId: z.string().min(1),\n});\n\nconst tokenRequestBodySchema = z.object({\n grant_type: z.string().min(1),\n code: z.string().optional(),\n redirect_uri: z.string().url().optional(),\n code_verifier: z.string().optional(),\n refresh_token: z.string().optional(),\n client_id: z.string().optional(),\n client_secret: z.string().optional(),\n});\n\nconst registerRequestBodySchema = z.object({\n client_name: z.string().optional(),\n redirect_uris: z.array(z.string().url()).min(1),\n response_types: z.array(z.string()).optional(),\n grant_types: z.array(z.string()).optional(),\n scope: z.string().optional(),\n});\n\nconst revokeRequestBodySchema = z.object({\n token: z.string().min(1),\n token_type_hint: z.string().optional(),\n client_id: z.string().optional(),\n client_secret: z.string().optional(),\n});\n\nfunction validateRequest<T>(schema: z.ZodSchema<T>, data: unknown): T {\n const parseResult = schema.safeParse(data);\n if (!parseResult.success) {\n const errorMessage = fromZodError(parseResult.error).message;\n throw new OidcError('invalid_request', errorMessage, 400);\n }\n return parseResult.data;\n}\n\nasync function authenticateClient(\n req: { headers: { authorization?: string } },\n oidc: OidcService,\n bodyClientId?: string,\n bodyClientSecret?: string,\n): Promise<{ clientId: string; clientSecret: string }> {\n let clientId: string | undefined;\n let clientSecret: string | undefined;\n\n const basicAuth = req.headers.authorization?.match(/^Basic[ ]+([^\\s]+)$/i);\n if (basicAuth) {\n try {\n const decoded = Buffer.from(basicAuth[1], 'base64').toString('utf8');\n const idx = decoded.indexOf(':');\n if (idx >= 0) {\n clientId = decoded.slice(0, idx);\n clientSecret = decoded.slice(idx + 1);\n }\n } catch {\n /* ignore */\n }\n }\n\n if (!clientId || !clientSecret) {\n if (bodyClientId && bodyClientSecret) {\n clientId = bodyClientId;\n clientSecret = bodyClientSecret;\n }\n }\n\n if (!clientId || !clientSecret) {\n throw new OidcError(\n 'invalid_client',\n 'Client authentication required',\n 401,\n );\n }\n\n try {\n const ok = await oidc.verifyClientCredentials({\n clientId,\n clientSecret,\n });\n if (!ok) {\n throw new OidcError('invalid_client', 'Invalid client credentials', 401);\n }\n } catch (e) {\n throw OidcError.fromError(e);\n }\n\n return { clientId, clientSecret };\n}\n\nexport class OidcRouter {\n private readonly oidc: OidcService;\n private readonly logger: LoggerService;\n private readonly auth: AuthService;\n private readonly appUrl: string;\n private readonly httpAuth: HttpAuthService;\n private readonly config: RootConfigService;\n\n private constructor(\n oidc: OidcService,\n logger: LoggerService,\n auth: AuthService,\n appUrl: string,\n httpAuth: HttpAuthService,\n config: RootConfigService,\n ) {\n this.oidc = oidc;\n this.logger = logger;\n this.auth = auth;\n this.appUrl = appUrl;\n this.httpAuth = httpAuth;\n this.config = config;\n }\n\n static create(options: {\n auth: AuthService;\n tokenIssuer: TokenIssuer;\n baseUrl: string;\n appUrl: string;\n logger: LoggerService;\n userInfo: UserInfoDatabase;\n oidc: OidcDatabase;\n httpAuth: HttpAuthService;\n config: RootConfigService;\n offlineAccess?: OfflineAccessService;\n }) {\n return new OidcRouter(\n OidcService.create(options),\n options.logger,\n options.auth,\n options.appUrl,\n options.httpAuth,\n options.config,\n );\n }\n\n public getRouter() {\n const router = Router();\n\n router.use(json());\n\n // OpenID Provider Configuration endpoint\n // https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig\n // Returns the OpenID Provider Configuration document containing metadata about the provider\n router.get('/.well-known/openid-configuration', (_req, res) => {\n res.json(this.oidc.getConfiguration());\n });\n\n // JSON Web Key Set endpoint\n // https://openid.net/specs/openid-connect-core-1_0.html#rfc.section.10.1.1\n // Returns the public keys used to verify JWTs issued by this provider\n router.get('/.well-known/jwks.json', async (_req, res) => {\n const { keys } = await this.oidc.listPublicKeys();\n res.json({ keys });\n });\n\n // UserInfo endpoint\n // https://openid.net/specs/openid-connect-core-1_0.html#UserInfo\n // Returns claims about the authenticated user using an access token\n router.get('/v1/userinfo', async (req, res) => {\n const matches = req.headers.authorization?.match(/^Bearer[ ]+(\\S+)$/i);\n const token = matches?.[1];\n if (!token) {\n throw new AuthenticationError('No token provided');\n }\n\n const userInfo = await this.oidc.getUserInfo({ token });\n\n if (!userInfo) {\n res.status(404).send('User info not found');\n return;\n }\n\n res.json(userInfo);\n });\n\n const dcrEnabled = this.config.getOptionalBoolean(\n 'auth.experimentalDynamicClientRegistration.enabled',\n );\n\n if (dcrEnabled) {\n // Authorization endpoint\n // https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest\n // Handles the initial authorization request from the client, validates parameters,\n // and redirects to the Authorization Session page for user approval\n router.get('/v1/authorize', async (req, res) => {\n const {\n client_id: clientId,\n redirect_uri: redirectUri,\n response_type: responseType,\n scope,\n state,\n nonce,\n code_challenge: codeChallenge,\n code_challenge_method: codeChallengeMethod,\n } = validateRequest(authorizeQuerySchema, req.query);\n\n try {\n const result = await this.oidc.createAuthorizationSession({\n clientId,\n redirectUri,\n responseType,\n scope,\n state,\n nonce,\n codeChallenge,\n codeChallengeMethod,\n });\n\n // todo(blam): maybe this URL could be overridable by config if\n // the plugin is mounted somewhere else?\n // support slashes in baseUrl?\n const authSessionRedirectUrl = new URL(\n `./oauth2/authorize/${result.id}`,\n ensureTrailingSlash(this.appUrl),\n );\n\n return res.redirect(authSessionRedirectUrl.toString());\n } catch (error) {\n if (OidcError.isOidcError(error)) {\n const errorParams = new URLSearchParams();\n errorParams.append('error', error.body.error);\n errorParams.append(\n 'error_description',\n error.body.error_description,\n );\n if (state) {\n errorParams.append('state', state);\n }\n\n const redirectUrl = new URL(redirectUri);\n redirectUrl.search = errorParams.toString();\n return res.redirect(redirectUrl.toString());\n }\n throw error;\n }\n });\n\n // Authorization Session request details endpoint\n // Returns Authorization Session request details for the frontend\n router.get('/v1/sessions/:sessionId', async (req, res) => {\n const { sessionId } = validateRequest(sessionIdParamSchema, req.params);\n\n try {\n const session = await this.oidc.getAuthorizationSession({\n sessionId,\n });\n\n return res.json({\n id: session.id,\n clientName: session.clientName,\n scope: session.scope,\n redirectUri: session.redirectUri,\n });\n } catch (error) {\n throw OidcError.fromError(error);\n }\n });\n\n // Authorization Session approval endpoint\n // Handles user approval of Authorization Session requests and generates authorization codes\n router.post('/v1/sessions/:sessionId/approve', async (req, res) => {\n const { sessionId } = validateRequest(sessionIdParamSchema, req.params);\n\n try {\n const httpCredentials = await this.httpAuth.credentials(req);\n\n if (!this.auth.isPrincipal(httpCredentials, 'user')) {\n throw new OidcError(\n 'access_denied',\n 'Authentication required',\n 403,\n );\n }\n\n const { userEntityRef } = httpCredentials.principal;\n\n const result = await this.oidc.approveAuthorizationSession({\n sessionId,\n userEntityRef,\n });\n\n return res.json({\n redirectUrl: result.redirectUrl,\n });\n } catch (error) {\n throw OidcError.fromError(error);\n }\n });\n\n // Authorization Session rejection endpoint\n // Handles user rejection of Authorization Session requests and redirects with error\n router.post('/v1/sessions/:sessionId/reject', async (req, res) => {\n const { sessionId } = validateRequest(sessionIdParamSchema, req.params);\n\n const httpCredentials = await this.httpAuth.credentials(req);\n\n if (!this.auth.isPrincipal(httpCredentials, 'user')) {\n throw new OidcError('access_denied', 'Authentication required', 403);\n }\n\n const { userEntityRef } = httpCredentials.principal;\n try {\n const session = await this.oidc.getAuthorizationSession({\n sessionId,\n });\n\n await this.oidc.rejectAuthorizationSession({\n sessionId,\n userEntityRef,\n });\n\n const errorParams = new URLSearchParams();\n errorParams.append('error', 'access_denied');\n errorParams.append('error_description', 'User denied the request');\n if (session.state) {\n errorParams.append('state', session.state);\n }\n\n const redirectUrl = new URL(session.redirectUri);\n redirectUrl.search = errorParams.toString();\n\n return res.json({\n redirectUrl: redirectUrl.toString(),\n });\n } catch (error) {\n throw OidcError.fromError(error);\n }\n });\n\n // Token endpoint\n // https://openid.net/specs/openid-connect-core-1_0.html#TokenRequest\n // Exchanges authorization codes for access tokens and ID tokens\n // Also handles refresh token grant type\n router.post('/v1/token', async (req, res) => {\n const {\n grant_type: grantType,\n code,\n redirect_uri: redirectUri,\n code_verifier: codeVerifier,\n refresh_token: refreshToken,\n client_id: bodyClientId,\n client_secret: bodyClientSecret,\n } = validateRequest(tokenRequestBodySchema, req.body);\n\n const expiresIn = readDcrTokenExpiration(this.config);\n\n try {\n // Handle authorization_code grant type\n if (grantType === 'authorization_code') {\n if (!code || !redirectUri) {\n throw new OidcError(\n 'invalid_request',\n 'Missing code or redirect_uri parameters for authorization_code grant',\n 400,\n );\n }\n\n const result = await this.oidc.exchangeCodeForToken({\n code,\n redirectUri,\n codeVerifier,\n grantType,\n expiresIn,\n });\n\n return res.json({\n access_token: result.accessToken,\n token_type: result.tokenType,\n expires_in: result.expiresIn,\n id_token: result.idToken,\n scope: result.scope,\n ...(result.refreshToken && {\n refresh_token: result.refreshToken,\n }),\n });\n }\n\n // Handle refresh_token grant type\n if (grantType === 'refresh_token') {\n if (!refreshToken) {\n throw new OidcError(\n 'invalid_request',\n 'Missing refresh_token parameter for refresh_token grant',\n 400,\n );\n }\n\n // Authenticate if credentials are provided via Basic auth or body\n const hasCredentials =\n req.headers.authorization?.match(/^Basic[ ]+([^\\s]+)$/i) ||\n (bodyClientId && bodyClientSecret);\n\n let authenticatedClientId: string | undefined;\n if (hasCredentials) {\n const { clientId: authedId } = await authenticateClient(\n req,\n this.oidc,\n bodyClientId,\n bodyClientSecret,\n );\n authenticatedClientId = authedId;\n }\n\n const result = await this.oidc.refreshAccessToken({\n refreshToken,\n clientId: authenticatedClientId,\n });\n\n return res.json({\n access_token: result.accessToken,\n token_type: result.tokenType,\n expires_in: result.expiresIn,\n refresh_token: result.refreshToken,\n });\n }\n\n // Unsupported grant type\n throw new OidcError(\n 'unsupported_grant_type',\n `Grant type ${grantType} is not supported`,\n 400,\n );\n } catch (error) {\n // Invalid auth codes and refresh tokens should be invalid_grant, not invalid_client.\n // Client auth failures are already thrown as OidcError by authenticateClient.\n if (isError(error) && error.name === 'AuthenticationError') {\n throw new OidcError('invalid_grant', error.message, 400, error);\n }\n throw OidcError.fromError(error);\n }\n });\n }\n\n // Dynamic Client Registration endpoint - only available when DCR is enabled\n if (dcrEnabled) {\n // https://openid.net/specs/openid-connect-registration-1_0.html#ClientRegistration\n // Allows clients to register themselves dynamically with the provider\n router.post('/v1/register', async (req, res) => {\n const {\n client_name: clientName,\n redirect_uris: redirectUris,\n response_types: responseTypes,\n grant_types: grantTypes,\n scope,\n } = validateRequest(registerRequestBodySchema, req.body);\n\n try {\n const client = await this.oidc.registerClient({\n clientName: clientName ?? 'Backstage CLI',\n redirectUris,\n responseTypes,\n grantTypes,\n scope,\n });\n\n return res.status(201).json({\n client_id: client.clientId,\n redirect_uris: client.redirectUris,\n client_secret: client.clientSecret,\n });\n } catch (e) {\n throw OidcError.fromError(e);\n }\n });\n\n // Token Revocation endpoint (RFC 7009-like)\n // Allows clients to revoke refresh tokens\n router.post('/v1/revoke', async (req, res) => {\n try {\n const {\n token,\n client_id: bodyClientId,\n client_secret: bodyClientSecret,\n } = validateRequest(revokeRequestBodySchema, req.body ?? {});\n\n await authenticateClient(\n req,\n this.oidc,\n bodyClientId,\n bodyClientSecret,\n );\n\n try {\n await this.oidc.revokeRefreshToken(token);\n } catch (e) {\n // RFC 7009 says always respond 200 even for invalid tokens\n this.logger.debug('Failed to revoke token', e);\n }\n\n return res.status(200).send('');\n } catch (e) {\n throw OidcError.fromError(e);\n }\n });\n }\n\n router.use(OidcError.middleware(this.logger));\n\n return router;\n }\n}\nfunction ensureTrailingSlash(appUrl: string): string {\n if (appUrl.endsWith('/')) {\n return appUrl;\n }\n return `${appUrl}/`;\n}\n"],"names":["z","fromZodError","OidcError","OidcService","Router","json","AuthenticationError","readDcrTokenExpiration","isError"],"mappings":"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"}
|
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
var errors = require('@backstage/errors');
|
|
4
4
|
var jose = require('jose');
|
|
5
|
-
var crypto = require('crypto');
|
|
5
|
+
var crypto = require('node:crypto');
|
|
6
6
|
var luxon = require('luxon');
|
|
7
7
|
var matcher = require('matcher');
|
|
8
|
+
var readTokenExpiration = require('./readTokenExpiration.cjs.js');
|
|
8
9
|
|
|
9
10
|
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
|
|
10
11
|
|
|
@@ -18,13 +19,15 @@ class OidcService {
|
|
|
18
19
|
userInfo;
|
|
19
20
|
oidc;
|
|
20
21
|
config;
|
|
21
|
-
|
|
22
|
+
offlineAccess;
|
|
23
|
+
constructor(auth, tokenIssuer, baseUrl, userInfo, oidc, config, offlineAccess) {
|
|
22
24
|
this.auth = auth;
|
|
23
25
|
this.tokenIssuer = tokenIssuer;
|
|
24
26
|
this.baseUrl = baseUrl;
|
|
25
27
|
this.userInfo = userInfo;
|
|
26
28
|
this.oidc = oidc;
|
|
27
29
|
this.config = config;
|
|
30
|
+
this.offlineAccess = offlineAccess;
|
|
28
31
|
}
|
|
29
32
|
static create(options) {
|
|
30
33
|
return new OidcService(
|
|
@@ -33,10 +36,14 @@ class OidcService {
|
|
|
33
36
|
options.baseUrl,
|
|
34
37
|
options.userInfo,
|
|
35
38
|
options.oidc,
|
|
36
|
-
options.config
|
|
39
|
+
options.config,
|
|
40
|
+
options.offlineAccess
|
|
37
41
|
);
|
|
38
42
|
}
|
|
39
43
|
getConfiguration() {
|
|
44
|
+
const dcrEnabled = this.config.getOptionalBoolean(
|
|
45
|
+
"auth.experimentalDynamicClientRegistration.enabled"
|
|
46
|
+
);
|
|
40
47
|
return {
|
|
41
48
|
issuer: this.baseUrl,
|
|
42
49
|
token_endpoint: `${this.baseUrl}/v1/token`,
|
|
@@ -56,16 +63,25 @@ class OidcService {
|
|
|
56
63
|
"PS512",
|
|
57
64
|
"EdDSA"
|
|
58
65
|
],
|
|
59
|
-
scopes_supported: [
|
|
66
|
+
scopes_supported: [
|
|
67
|
+
"openid",
|
|
68
|
+
...this.offlineAccess ? ["offline_access"] : []
|
|
69
|
+
],
|
|
60
70
|
token_endpoint_auth_methods_supported: [
|
|
61
71
|
"client_secret_basic",
|
|
62
72
|
"client_secret_post"
|
|
63
73
|
],
|
|
64
74
|
claims_supported: ["sub", "ent"],
|
|
65
|
-
grant_types_supported: [
|
|
75
|
+
grant_types_supported: [
|
|
76
|
+
"authorization_code",
|
|
77
|
+
...this.offlineAccess ? ["refresh_token"] : []
|
|
78
|
+
],
|
|
66
79
|
authorization_endpoint: `${this.baseUrl}/v1/authorize`,
|
|
67
|
-
|
|
68
|
-
|
|
80
|
+
code_challenge_methods_supported: ["S256", "plain"],
|
|
81
|
+
...dcrEnabled && {
|
|
82
|
+
registration_endpoint: `${this.baseUrl}/v1/register`,
|
|
83
|
+
revocation_endpoint: `${this.baseUrl}/v1/revoke`
|
|
84
|
+
}
|
|
69
85
|
};
|
|
70
86
|
}
|
|
71
87
|
async listPublicKeys() {
|
|
@@ -123,13 +139,7 @@ class OidcService {
|
|
|
123
139
|
if (responseType !== "code") {
|
|
124
140
|
throw new errors.InputError("Only authorization code flow is supported");
|
|
125
141
|
}
|
|
126
|
-
const client = await this.
|
|
127
|
-
if (!client) {
|
|
128
|
-
throw new errors.InputError("Invalid client_id");
|
|
129
|
-
}
|
|
130
|
-
if (!client.redirectUris.includes(redirectUri)) {
|
|
131
|
-
throw new errors.InputError("Invalid redirect_uri");
|
|
132
|
-
}
|
|
142
|
+
const client = await this.resolveClient(clientId, redirectUri);
|
|
133
143
|
if (codeChallenge) {
|
|
134
144
|
if (!codeChallengeMethod || !["S256", "plain"].includes(codeChallengeMethod)) {
|
|
135
145
|
throw new errors.InputError("Invalid code_challenge_method");
|
|
@@ -156,6 +166,26 @@ class OidcService {
|
|
|
156
166
|
redirectUri
|
|
157
167
|
};
|
|
158
168
|
}
|
|
169
|
+
async getClientName(clientId) {
|
|
170
|
+
const client = await this.oidc.getClient({ clientId });
|
|
171
|
+
if (!client) {
|
|
172
|
+
throw new errors.InputError("Invalid client_id");
|
|
173
|
+
}
|
|
174
|
+
return client.clientName;
|
|
175
|
+
}
|
|
176
|
+
async resolveClient(clientId, redirectUri) {
|
|
177
|
+
const client = await this.oidc.getClient({ clientId });
|
|
178
|
+
if (!client) {
|
|
179
|
+
throw new errors.InputError("Invalid client_id");
|
|
180
|
+
}
|
|
181
|
+
if (!client.redirectUris.includes(redirectUri)) {
|
|
182
|
+
throw new errors.InputError("Invalid redirect_uri");
|
|
183
|
+
}
|
|
184
|
+
return {
|
|
185
|
+
clientName: client.clientName,
|
|
186
|
+
redirectUris: client.redirectUris
|
|
187
|
+
};
|
|
188
|
+
}
|
|
159
189
|
async approveAuthorizationSession(opts) {
|
|
160
190
|
const { sessionId, userEntityRef } = opts;
|
|
161
191
|
const session = await this.oidc.getAuthorizationSession({
|
|
@@ -204,14 +234,11 @@ class OidcService {
|
|
|
204
234
|
if (session.status !== "pending") {
|
|
205
235
|
throw new errors.NotFoundError("Authorization session not found or expired");
|
|
206
236
|
}
|
|
207
|
-
const
|
|
208
|
-
if (!client) {
|
|
209
|
-
throw new errors.InputError("Invalid client_id");
|
|
210
|
-
}
|
|
237
|
+
const clientName = await this.getClientName(session.clientId);
|
|
211
238
|
return {
|
|
212
239
|
id: session.id,
|
|
213
240
|
clientId: session.clientId,
|
|
214
|
-
clientName
|
|
241
|
+
clientName,
|
|
215
242
|
redirectUri: session.redirectUri,
|
|
216
243
|
scope: session.scope,
|
|
217
244
|
state: session.state,
|
|
@@ -294,14 +321,65 @@ class OidcService {
|
|
|
294
321
|
sub: session.userEntityRef
|
|
295
322
|
}
|
|
296
323
|
});
|
|
324
|
+
let refreshToken;
|
|
325
|
+
const scopes = session.scope?.split(" ") ?? [];
|
|
326
|
+
if (scopes.includes("offline_access") && this.offlineAccess) {
|
|
327
|
+
refreshToken = await this.offlineAccess.issueRefreshToken({
|
|
328
|
+
userEntityRef: session.userEntityRef,
|
|
329
|
+
oidcClientId: session.clientId
|
|
330
|
+
});
|
|
331
|
+
}
|
|
297
332
|
return {
|
|
298
333
|
accessToken: token,
|
|
299
334
|
tokenType: "Bearer",
|
|
300
335
|
expiresIn,
|
|
301
336
|
idToken: token,
|
|
302
|
-
scope: session.scope || "openid"
|
|
337
|
+
scope: session.scope || "openid",
|
|
338
|
+
refreshToken
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
async refreshAccessToken(params) {
|
|
342
|
+
if (!this.offlineAccess) {
|
|
343
|
+
throw new errors.InputError("Refresh tokens are not enabled");
|
|
344
|
+
}
|
|
345
|
+
const { accessToken, refreshToken } = await this.offlineAccess.refreshAccessToken({
|
|
346
|
+
refreshToken: params.refreshToken,
|
|
347
|
+
tokenIssuer: this.tokenIssuer,
|
|
348
|
+
clientId: params.clientId
|
|
349
|
+
});
|
|
350
|
+
const expiresIn = readTokenExpiration.readDcrTokenExpiration(this.config);
|
|
351
|
+
return {
|
|
352
|
+
accessToken,
|
|
353
|
+
tokenType: "Bearer",
|
|
354
|
+
expiresIn,
|
|
355
|
+
refreshToken
|
|
303
356
|
};
|
|
304
357
|
}
|
|
358
|
+
/**
|
|
359
|
+
* Verifies client credentials against the registered OIDC clients
|
|
360
|
+
*/
|
|
361
|
+
async verifyClientCredentials(options) {
|
|
362
|
+
const { clientId, clientSecret } = options;
|
|
363
|
+
const client = await this.oidc.getClient({ clientId });
|
|
364
|
+
if (!client?.clientSecret) {
|
|
365
|
+
return false;
|
|
366
|
+
}
|
|
367
|
+
const expected = Buffer.from(client.clientSecret, "utf8");
|
|
368
|
+
const provided = Buffer.from(clientSecret, "utf8");
|
|
369
|
+
if (expected.length !== provided.length) {
|
|
370
|
+
return false;
|
|
371
|
+
}
|
|
372
|
+
return crypto__default.default.timingSafeEqual(expected, provided);
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Revoke a refresh token if offline access is enabled
|
|
376
|
+
*/
|
|
377
|
+
async revokeRefreshToken(token) {
|
|
378
|
+
if (!this.offlineAccess) {
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
await this.offlineAccess.revokeRefreshToken(token);
|
|
382
|
+
}
|
|
305
383
|
verifyPkce(codeChallenge, codeVerifier, method) {
|
|
306
384
|
if (!method || method === "plain") {
|
|
307
385
|
return codeChallenge === codeVerifier;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OidcService.cjs.js","sources":["../../src/service/OidcService.ts"],"sourcesContent":["/*\n * Copyright 2025 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 */\nimport { AuthService, RootConfigService } from '@backstage/backend-plugin-api';\nimport { TokenIssuer } from '../identity/types';\nimport { UserInfoDatabase } from '../database/UserInfoDatabase';\nimport {\n AuthenticationError,\n InputError,\n NotFoundError,\n} from '@backstage/errors';\nimport { decodeJwt } from 'jose';\nimport crypto from 'crypto';\nimport { OidcDatabase } from '../database/OidcDatabase';\nimport { DateTime } from 'luxon';\nimport matcher from 'matcher';\n\nexport class OidcService {\n private readonly auth: AuthService;\n private readonly tokenIssuer: TokenIssuer;\n private readonly baseUrl: string;\n private readonly userInfo: UserInfoDatabase;\n private readonly oidc: OidcDatabase;\n private readonly config: RootConfigService;\n\n private constructor(\n auth: AuthService,\n tokenIssuer: TokenIssuer,\n baseUrl: string,\n userInfo: UserInfoDatabase,\n oidc: OidcDatabase,\n config: RootConfigService,\n ) {\n this.auth = auth;\n this.tokenIssuer = tokenIssuer;\n this.baseUrl = baseUrl;\n this.userInfo = userInfo;\n this.oidc = oidc;\n this.config = config;\n }\n\n static create(options: {\n auth: AuthService;\n tokenIssuer: TokenIssuer;\n baseUrl: string;\n userInfo: UserInfoDatabase;\n oidc: OidcDatabase;\n config: RootConfigService;\n }) {\n return new OidcService(\n options.auth,\n options.tokenIssuer,\n options.baseUrl,\n options.userInfo,\n options.oidc,\n options.config,\n );\n }\n\n public getConfiguration() {\n return {\n issuer: this.baseUrl,\n token_endpoint: `${this.baseUrl}/v1/token`,\n userinfo_endpoint: `${this.baseUrl}/v1/userinfo`,\n jwks_uri: `${this.baseUrl}/.well-known/jwks.json`,\n response_types_supported: ['code', 'id_token'],\n subject_types_supported: ['public'],\n id_token_signing_alg_values_supported: [\n 'RS256',\n 'RS384',\n 'RS512',\n 'ES256',\n 'ES384',\n 'ES512',\n 'PS256',\n 'PS384',\n 'PS512',\n 'EdDSA',\n ],\n scopes_supported: ['openid'],\n token_endpoint_auth_methods_supported: [\n 'client_secret_basic',\n 'client_secret_post',\n ],\n claims_supported: ['sub', 'ent'],\n grant_types_supported: ['authorization_code'],\n authorization_endpoint: `${this.baseUrl}/v1/authorize`,\n registration_endpoint: `${this.baseUrl}/v1/register`,\n code_challenge_methods_supported: ['S256', 'plain'],\n };\n }\n\n public async listPublicKeys() {\n return await this.tokenIssuer.listPublicKeys();\n }\n\n public async getUserInfo({ token }: { token: string }) {\n const credentials = await this.auth.authenticate(token, {\n allowLimitedAccess: true,\n });\n if (!this.auth.isPrincipal(credentials, 'user')) {\n throw new InputError(\n 'Userinfo endpoint must be called with a token that represents a user principal',\n );\n }\n\n const { sub: userEntityRef } = decodeJwt(token);\n\n if (typeof userEntityRef !== 'string') {\n throw new Error('Invalid user token, user entity ref must be a string');\n }\n return await this.userInfo.getUserInfo(userEntityRef);\n }\n\n public async registerClient(opts: {\n responseTypes?: string[];\n grantTypes?: string[];\n clientName: string;\n redirectUris?: string[];\n scope?: string;\n }) {\n const generatedClientId = crypto.randomUUID();\n const generatedClientSecret = crypto.randomUUID();\n\n const allowedRedirectUriPatterns = this.config.getOptionalStringArray(\n 'auth.experimentalDynamicClientRegistration.allowedRedirectUriPatterns',\n ) ?? ['*'];\n\n for (const redirectUri of opts.redirectUris ?? []) {\n if (\n !allowedRedirectUriPatterns.some(pattern =>\n matcher.isMatch(redirectUri, pattern),\n )\n ) {\n throw new InputError('Invalid redirect_uri');\n }\n }\n\n return await this.oidc.createClient({\n clientId: generatedClientId,\n clientName: opts.clientName,\n clientSecret: generatedClientSecret,\n redirectUris: opts.redirectUris ?? [],\n responseTypes: opts.responseTypes ?? ['code'],\n grantTypes: opts.grantTypes ?? ['authorization_code'],\n scope: opts.scope,\n });\n }\n\n public async createAuthorizationSession(opts: {\n clientId: string;\n redirectUri: string;\n responseType: string;\n scope?: string;\n state?: string;\n nonce?: string;\n codeChallenge?: string;\n codeChallengeMethod?: string;\n }) {\n const {\n clientId,\n redirectUri,\n responseType,\n scope,\n state,\n nonce,\n codeChallenge,\n codeChallengeMethod,\n } = opts;\n\n if (responseType !== 'code') {\n throw new InputError('Only authorization code flow is supported');\n }\n\n const client = await this.oidc.getClient({ clientId });\n if (!client) {\n throw new InputError('Invalid client_id');\n }\n\n if (!client.redirectUris.includes(redirectUri)) {\n throw new InputError('Invalid redirect_uri');\n }\n\n if (codeChallenge) {\n if (\n !codeChallengeMethod ||\n !['S256', 'plain'].includes(codeChallengeMethod)\n ) {\n throw new InputError('Invalid code_challenge_method');\n }\n }\n\n const sessionId = crypto.randomUUID();\n const sessionExpiresAt = DateTime.now().plus({ hours: 1 }).toJSDate();\n\n await this.oidc.createAuthorizationSession({\n id: sessionId,\n clientId,\n redirectUri,\n responseType,\n scope,\n state,\n codeChallenge,\n codeChallengeMethod,\n nonce,\n expiresAt: sessionExpiresAt,\n });\n\n return {\n id: sessionId,\n clientName: client.clientName,\n scope,\n redirectUri,\n };\n }\n\n public async approveAuthorizationSession(opts: {\n sessionId: string;\n userEntityRef: string;\n }) {\n const { sessionId, userEntityRef } = opts;\n\n const session = await this.oidc.getAuthorizationSession({\n id: sessionId,\n });\n\n if (!session) {\n throw new NotFoundError('Invalid authorization session');\n }\n\n if (DateTime.fromJSDate(session.expiresAt) < DateTime.now()) {\n throw new InputError('Authorization session expired');\n }\n\n if (session.status !== 'pending') {\n throw new NotFoundError('Authorization session not found or expired');\n }\n\n await this.oidc.updateAuthorizationSession({\n id: session.id,\n userEntityRef,\n status: 'approved',\n });\n\n const authorizationCode = crypto.randomBytes(32).toString('base64url');\n const codeExpiresAt = DateTime.now().plus({ minutes: 10 }).toJSDate();\n\n await this.oidc.createAuthorizationCode({\n code: authorizationCode,\n sessionId: session.id,\n expiresAt: codeExpiresAt,\n });\n\n const redirectUrl = new URL(session.redirectUri);\n\n redirectUrl.searchParams.append('code', authorizationCode);\n if (session.state) {\n redirectUrl.searchParams.append('state', session.state);\n }\n\n return {\n redirectUrl: redirectUrl.toString(),\n };\n }\n\n public async getAuthorizationSession(opts: { sessionId: string }) {\n const session = await this.oidc.getAuthorizationSession({\n id: opts.sessionId,\n });\n\n if (!session) {\n throw new NotFoundError('Invalid authorization session');\n }\n\n if (DateTime.fromJSDate(session.expiresAt) < DateTime.now()) {\n throw new InputError('Authorization session expired');\n }\n\n if (session.status !== 'pending') {\n throw new NotFoundError('Authorization session not found or expired');\n }\n\n const client = await this.oidc.getClient({ clientId: session.clientId });\n if (!client) {\n throw new InputError('Invalid client_id');\n }\n\n return {\n id: session.id,\n clientId: session.clientId,\n clientName: client.clientName,\n redirectUri: session.redirectUri,\n scope: session.scope,\n state: session.state,\n responseType: session.responseType,\n codeChallenge: session.codeChallenge,\n codeChallengeMethod: session.codeChallengeMethod,\n nonce: session.nonce,\n expiresAt: session.expiresAt,\n status: session.status,\n };\n }\n\n public async rejectAuthorizationSession(opts: {\n sessionId: string;\n userEntityRef: string;\n }) {\n const { sessionId, userEntityRef } = opts;\n\n const session = await this.oidc.getAuthorizationSession({\n id: sessionId,\n });\n\n if (!session) {\n throw new NotFoundError('Invalid authorization session');\n }\n\n if (DateTime.fromJSDate(session.expiresAt) < DateTime.now()) {\n throw new InputError('Authorization session expired');\n }\n\n if (session.status !== 'pending') {\n throw new NotFoundError('Authorization session not found or expired');\n }\n\n await this.oidc.updateAuthorizationSession({\n id: session.id,\n status: 'rejected',\n userEntityRef,\n });\n }\n\n public async exchangeCodeForToken(params: {\n code: string;\n redirectUri: string;\n codeVerifier?: string;\n grantType: string;\n expiresIn: number;\n }) {\n const { code, redirectUri, codeVerifier, grantType, expiresIn } = params;\n\n if (grantType !== 'authorization_code') {\n throw new InputError('Unsupported grant type');\n }\n\n const authCode = await this.oidc.getAuthorizationCode({ code });\n if (!authCode) {\n throw new AuthenticationError('Invalid authorization code');\n }\n\n if (DateTime.fromJSDate(authCode.expiresAt) < DateTime.now()) {\n throw new AuthenticationError('Authorization code expired');\n }\n\n if (authCode.used) {\n throw new AuthenticationError('Authorization code already used');\n }\n\n const session = await this.oidc.getAuthorizationSession({\n id: authCode.sessionId,\n });\n\n if (!session) {\n throw new NotFoundError('Invalid authorization session');\n }\n\n if (session.redirectUri !== redirectUri) {\n throw new AuthenticationError('Redirect URI mismatch');\n }\n\n if (session.status !== 'approved') {\n throw new AuthenticationError('Authorization not approved');\n }\n\n if (!session.userEntityRef) {\n throw new AuthenticationError('No user associated with authorization');\n }\n\n if (session.codeChallenge) {\n if (!codeVerifier) {\n throw new AuthenticationError('Code verifier required for PKCE');\n }\n\n if (\n !this.verifyPkce(\n session.codeChallenge,\n codeVerifier,\n session.codeChallengeMethod,\n )\n ) {\n throw new AuthenticationError('Invalid code verifier');\n }\n }\n\n await this.oidc.updateAuthorizationCode({\n code,\n used: true,\n });\n\n const { token } = await this.tokenIssuer.issueToken({\n claims: {\n sub: session.userEntityRef,\n },\n });\n\n return {\n accessToken: token,\n tokenType: 'Bearer',\n expiresIn: expiresIn,\n idToken: token,\n scope: session.scope || 'openid',\n };\n }\n\n private verifyPkce(\n codeChallenge: string,\n codeVerifier: string,\n method?: string,\n ): boolean {\n if (!method || method === 'plain') {\n return codeChallenge === codeVerifier;\n }\n\n if (method === 'S256') {\n const hash = crypto.createHash('sha256').update(codeVerifier).digest();\n const base64urlHash = hash.toString('base64url');\n return codeChallenge === base64urlHash;\n }\n\n return false;\n }\n}\n"],"names":["InputError","decodeJwt","crypto","matcher","DateTime","NotFoundError","AuthenticationError"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"OidcService.cjs.js","sources":["../../src/service/OidcService.ts"],"sourcesContent":["/*\n * Copyright 2025 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 */\nimport { AuthService, RootConfigService } from '@backstage/backend-plugin-api';\nimport { TokenIssuer } from '../identity/types';\nimport { UserInfoDatabase } from '../database/UserInfoDatabase';\nimport {\n AuthenticationError,\n InputError,\n NotFoundError,\n} from '@backstage/errors';\nimport { decodeJwt } from 'jose';\nimport crypto from 'node:crypto';\nimport { OidcDatabase } from '../database/OidcDatabase';\nimport { DateTime } from 'luxon';\nimport matcher from 'matcher';\nimport { OfflineAccessService } from './OfflineAccessService';\nimport { readDcrTokenExpiration } from './readTokenExpiration';\n\nexport class OidcService {\n private readonly auth: AuthService;\n private readonly tokenIssuer: TokenIssuer;\n private readonly baseUrl: string;\n private readonly userInfo: UserInfoDatabase;\n private readonly oidc: OidcDatabase;\n private readonly config: RootConfigService;\n private readonly offlineAccess?: OfflineAccessService;\n\n private constructor(\n auth: AuthService,\n tokenIssuer: TokenIssuer,\n baseUrl: string,\n userInfo: UserInfoDatabase,\n oidc: OidcDatabase,\n config: RootConfigService,\n offlineAccess?: OfflineAccessService,\n ) {\n this.auth = auth;\n this.tokenIssuer = tokenIssuer;\n this.baseUrl = baseUrl;\n this.userInfo = userInfo;\n this.oidc = oidc;\n this.config = config;\n this.offlineAccess = offlineAccess;\n }\n\n static create(options: {\n auth: AuthService;\n tokenIssuer: TokenIssuer;\n baseUrl: string;\n userInfo: UserInfoDatabase;\n oidc: OidcDatabase;\n config: RootConfigService;\n offlineAccess?: OfflineAccessService;\n }) {\n return new OidcService(\n options.auth,\n options.tokenIssuer,\n options.baseUrl,\n options.userInfo,\n options.oidc,\n options.config,\n options.offlineAccess,\n );\n }\n\n public getConfiguration() {\n const dcrEnabled = this.config.getOptionalBoolean(\n 'auth.experimentalDynamicClientRegistration.enabled',\n );\n\n return {\n issuer: this.baseUrl,\n token_endpoint: `${this.baseUrl}/v1/token`,\n userinfo_endpoint: `${this.baseUrl}/v1/userinfo`,\n jwks_uri: `${this.baseUrl}/.well-known/jwks.json`,\n response_types_supported: ['code', 'id_token'],\n subject_types_supported: ['public'],\n id_token_signing_alg_values_supported: [\n 'RS256',\n 'RS384',\n 'RS512',\n 'ES256',\n 'ES384',\n 'ES512',\n 'PS256',\n 'PS384',\n 'PS512',\n 'EdDSA',\n ],\n scopes_supported: [\n 'openid',\n ...(this.offlineAccess ? ['offline_access'] : []),\n ],\n token_endpoint_auth_methods_supported: [\n 'client_secret_basic',\n 'client_secret_post',\n ],\n claims_supported: ['sub', 'ent'],\n grant_types_supported: [\n 'authorization_code',\n ...(this.offlineAccess ? ['refresh_token'] : []),\n ],\n authorization_endpoint: `${this.baseUrl}/v1/authorize`,\n code_challenge_methods_supported: ['S256', 'plain'],\n ...(dcrEnabled && {\n registration_endpoint: `${this.baseUrl}/v1/register`,\n revocation_endpoint: `${this.baseUrl}/v1/revoke`,\n }),\n };\n }\n\n public async listPublicKeys() {\n return await this.tokenIssuer.listPublicKeys();\n }\n\n public async getUserInfo({ token }: { token: string }) {\n const credentials = await this.auth.authenticate(token, {\n allowLimitedAccess: true,\n });\n if (!this.auth.isPrincipal(credentials, 'user')) {\n throw new InputError(\n 'Userinfo endpoint must be called with a token that represents a user principal',\n );\n }\n\n const { sub: userEntityRef } = decodeJwt(token);\n\n if (typeof userEntityRef !== 'string') {\n throw new Error('Invalid user token, user entity ref must be a string');\n }\n return await this.userInfo.getUserInfo(userEntityRef);\n }\n\n public async registerClient(opts: {\n responseTypes?: string[];\n grantTypes?: string[];\n clientName: string;\n redirectUris?: string[];\n scope?: string;\n }) {\n const generatedClientId = crypto.randomUUID();\n const generatedClientSecret = crypto.randomUUID();\n\n const allowedRedirectUriPatterns = this.config.getOptionalStringArray(\n 'auth.experimentalDynamicClientRegistration.allowedRedirectUriPatterns',\n ) ?? ['*'];\n\n for (const redirectUri of opts.redirectUris ?? []) {\n if (\n !allowedRedirectUriPatterns.some(pattern =>\n matcher.isMatch(redirectUri, pattern),\n )\n ) {\n throw new InputError('Invalid redirect_uri');\n }\n }\n\n return await this.oidc.createClient({\n clientId: generatedClientId,\n clientName: opts.clientName,\n clientSecret: generatedClientSecret,\n redirectUris: opts.redirectUris ?? [],\n responseTypes: opts.responseTypes ?? ['code'],\n grantTypes: opts.grantTypes ?? ['authorization_code'],\n scope: opts.scope,\n });\n }\n\n public async createAuthorizationSession(opts: {\n clientId: string;\n redirectUri: string;\n responseType: string;\n scope?: string;\n state?: string;\n nonce?: string;\n codeChallenge?: string;\n codeChallengeMethod?: string;\n }) {\n const {\n clientId,\n redirectUri,\n responseType,\n scope,\n state,\n nonce,\n codeChallenge,\n codeChallengeMethod,\n } = opts;\n\n if (responseType !== 'code') {\n throw new InputError('Only authorization code flow is supported');\n }\n\n const client = await this.resolveClient(clientId, redirectUri);\n\n if (codeChallenge) {\n if (\n !codeChallengeMethod ||\n !['S256', 'plain'].includes(codeChallengeMethod)\n ) {\n throw new InputError('Invalid code_challenge_method');\n }\n }\n\n const sessionId = crypto.randomUUID();\n const sessionExpiresAt = DateTime.now().plus({ hours: 1 }).toJSDate();\n\n await this.oidc.createAuthorizationSession({\n id: sessionId,\n clientId,\n redirectUri,\n responseType,\n scope,\n state,\n codeChallenge,\n codeChallengeMethod,\n nonce,\n expiresAt: sessionExpiresAt,\n });\n\n return {\n id: sessionId,\n clientName: client.clientName,\n scope,\n redirectUri,\n };\n }\n\n private async getClientName(clientId: string): Promise<string> {\n const client = await this.oidc.getClient({ clientId });\n if (!client) {\n throw new InputError('Invalid client_id');\n }\n return client.clientName;\n }\n\n private async resolveClient(\n clientId: string,\n redirectUri: string,\n ): Promise<{ clientName: string; redirectUris: string[] }> {\n const client = await this.oidc.getClient({ clientId });\n if (!client) {\n throw new InputError('Invalid client_id');\n }\n\n if (!client.redirectUris.includes(redirectUri)) {\n throw new InputError('Invalid redirect_uri');\n }\n\n return {\n clientName: client.clientName,\n redirectUris: client.redirectUris,\n };\n }\n\n public async approveAuthorizationSession(opts: {\n sessionId: string;\n userEntityRef: string;\n }) {\n const { sessionId, userEntityRef } = opts;\n\n const session = await this.oidc.getAuthorizationSession({\n id: sessionId,\n });\n\n if (!session) {\n throw new NotFoundError('Invalid authorization session');\n }\n\n if (DateTime.fromJSDate(session.expiresAt) < DateTime.now()) {\n throw new InputError('Authorization session expired');\n }\n\n if (session.status !== 'pending') {\n throw new NotFoundError('Authorization session not found or expired');\n }\n\n await this.oidc.updateAuthorizationSession({\n id: session.id,\n userEntityRef,\n status: 'approved',\n });\n\n const authorizationCode = crypto.randomBytes(32).toString('base64url');\n const codeExpiresAt = DateTime.now().plus({ minutes: 10 }).toJSDate();\n\n await this.oidc.createAuthorizationCode({\n code: authorizationCode,\n sessionId: session.id,\n expiresAt: codeExpiresAt,\n });\n\n const redirectUrl = new URL(session.redirectUri);\n\n redirectUrl.searchParams.append('code', authorizationCode);\n if (session.state) {\n redirectUrl.searchParams.append('state', session.state);\n }\n\n return {\n redirectUrl: redirectUrl.toString(),\n };\n }\n\n public async getAuthorizationSession(opts: { sessionId: string }) {\n const session = await this.oidc.getAuthorizationSession({\n id: opts.sessionId,\n });\n\n if (!session) {\n throw new NotFoundError('Invalid authorization session');\n }\n\n if (DateTime.fromJSDate(session.expiresAt) < DateTime.now()) {\n throw new InputError('Authorization session expired');\n }\n\n if (session.status !== 'pending') {\n throw new NotFoundError('Authorization session not found or expired');\n }\n\n const clientName = await this.getClientName(session.clientId);\n\n return {\n id: session.id,\n clientId: session.clientId,\n clientName,\n redirectUri: session.redirectUri,\n scope: session.scope,\n state: session.state,\n responseType: session.responseType,\n codeChallenge: session.codeChallenge,\n codeChallengeMethod: session.codeChallengeMethod,\n nonce: session.nonce,\n expiresAt: session.expiresAt,\n status: session.status,\n };\n }\n\n public async rejectAuthorizationSession(opts: {\n sessionId: string;\n userEntityRef: string;\n }) {\n const { sessionId, userEntityRef } = opts;\n\n const session = await this.oidc.getAuthorizationSession({\n id: sessionId,\n });\n\n if (!session) {\n throw new NotFoundError('Invalid authorization session');\n }\n\n if (DateTime.fromJSDate(session.expiresAt) < DateTime.now()) {\n throw new InputError('Authorization session expired');\n }\n\n if (session.status !== 'pending') {\n throw new NotFoundError('Authorization session not found or expired');\n }\n\n await this.oidc.updateAuthorizationSession({\n id: session.id,\n status: 'rejected',\n userEntityRef,\n });\n }\n\n public async exchangeCodeForToken(params: {\n code: string;\n redirectUri: string;\n codeVerifier?: string;\n grantType: string;\n expiresIn: number;\n }) {\n const { code, redirectUri, codeVerifier, grantType, expiresIn } = params;\n\n if (grantType !== 'authorization_code') {\n throw new InputError('Unsupported grant type');\n }\n\n const authCode = await this.oidc.getAuthorizationCode({ code });\n if (!authCode) {\n throw new AuthenticationError('Invalid authorization code');\n }\n\n if (DateTime.fromJSDate(authCode.expiresAt) < DateTime.now()) {\n throw new AuthenticationError('Authorization code expired');\n }\n\n if (authCode.used) {\n throw new AuthenticationError('Authorization code already used');\n }\n\n const session = await this.oidc.getAuthorizationSession({\n id: authCode.sessionId,\n });\n\n if (!session) {\n throw new NotFoundError('Invalid authorization session');\n }\n\n if (session.redirectUri !== redirectUri) {\n throw new AuthenticationError('Redirect URI mismatch');\n }\n\n if (session.status !== 'approved') {\n throw new AuthenticationError('Authorization not approved');\n }\n\n if (!session.userEntityRef) {\n throw new AuthenticationError('No user associated with authorization');\n }\n\n if (session.codeChallenge) {\n if (!codeVerifier) {\n throw new AuthenticationError('Code verifier required for PKCE');\n }\n\n if (\n !this.verifyPkce(\n session.codeChallenge,\n codeVerifier,\n session.codeChallengeMethod,\n )\n ) {\n throw new AuthenticationError('Invalid code verifier');\n }\n }\n\n await this.oidc.updateAuthorizationCode({\n code,\n used: true,\n });\n\n const { token } = await this.tokenIssuer.issueToken({\n claims: {\n sub: session.userEntityRef,\n },\n });\n\n // Check if offline_access scope is requested\n let refreshToken: string | undefined;\n const scopes = session.scope?.split(' ') ?? [];\n if (scopes.includes('offline_access') && this.offlineAccess) {\n refreshToken = await this.offlineAccess.issueRefreshToken({\n userEntityRef: session.userEntityRef,\n oidcClientId: session.clientId,\n });\n }\n\n return {\n accessToken: token,\n tokenType: 'Bearer',\n expiresIn: expiresIn,\n idToken: token,\n scope: session.scope || 'openid',\n refreshToken,\n };\n }\n\n public async refreshAccessToken(params: {\n refreshToken: string;\n clientId?: string;\n }): Promise<{\n accessToken: string;\n tokenType: string;\n expiresIn: number;\n refreshToken: string;\n }> {\n if (!this.offlineAccess) {\n throw new InputError('Refresh tokens are not enabled');\n }\n\n const { accessToken, refreshToken } =\n await this.offlineAccess.refreshAccessToken({\n refreshToken: params.refreshToken,\n tokenIssuer: this.tokenIssuer,\n clientId: params.clientId,\n });\n\n const expiresIn = readDcrTokenExpiration(this.config);\n\n return {\n accessToken,\n tokenType: 'Bearer',\n expiresIn,\n refreshToken,\n };\n }\n\n /**\n * Verifies client credentials against the registered OIDC clients\n */\n public async verifyClientCredentials(options: {\n clientId: string;\n clientSecret: string;\n }): Promise<boolean> {\n const { clientId, clientSecret } = options;\n const client = await this.oidc.getClient({ clientId });\n if (!client?.clientSecret) {\n return false;\n }\n const expected = Buffer.from(client.clientSecret, 'utf8');\n const provided = Buffer.from(clientSecret, 'utf8');\n if (expected.length !== provided.length) {\n return false;\n }\n return crypto.timingSafeEqual(expected, provided);\n }\n\n /**\n * Revoke a refresh token if offline access is enabled\n */\n public async revokeRefreshToken(token: string): Promise<void> {\n if (!this.offlineAccess) {\n return;\n }\n await this.offlineAccess.revokeRefreshToken(token);\n }\n\n private verifyPkce(\n codeChallenge: string,\n codeVerifier: string,\n method?: string,\n ): boolean {\n if (!method || method === 'plain') {\n return codeChallenge === codeVerifier;\n }\n\n if (method === 'S256') {\n const hash = crypto.createHash('sha256').update(codeVerifier).digest();\n const base64urlHash = hash.toString('base64url');\n return codeChallenge === base64urlHash;\n }\n\n return false;\n }\n}\n"],"names":["InputError","decodeJwt","crypto","matcher","DateTime","NotFoundError","AuthenticationError","readDcrTokenExpiration"],"mappings":";;;;;;;;;;;;;;AA+BO,MAAM,WAAA,CAAY;AAAA,EACN,IAAA;AAAA,EACA,WAAA;AAAA,EACA,OAAA;AAAA,EACA,QAAA;AAAA,EACA,IAAA;AAAA,EACA,MAAA;AAAA,EACA,aAAA;AAAA,EAET,YACN,IAAA,EACA,WAAA,EACA,SACA,QAAA,EACA,IAAA,EACA,QACA,aAAA,EACA;AACA,IAAA,IAAA,CAAK,IAAA,GAAO,IAAA;AACZ,IAAA,IAAA,CAAK,WAAA,GAAc,WAAA;AACnB,IAAA,IAAA,CAAK,OAAA,GAAU,OAAA;AACf,IAAA,IAAA,CAAK,QAAA,GAAW,QAAA;AAChB,IAAA,IAAA,CAAK,IAAA,GAAO,IAAA;AACZ,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AACd,IAAA,IAAA,CAAK,aAAA,GAAgB,aAAA;AAAA,EACvB;AAAA,EAEA,OAAO,OAAO,OAAA,EAQX;AACD,IAAA,OAAO,IAAI,WAAA;AAAA,MACT,OAAA,CAAQ,IAAA;AAAA,MACR,OAAA,CAAQ,WAAA;AAAA,MACR,OAAA,CAAQ,OAAA;AAAA,MACR,OAAA,CAAQ,QAAA;AAAA,MACR,OAAA,CAAQ,IAAA;AAAA,MACR,OAAA,CAAQ,MAAA;AAAA,MACR,OAAA,CAAQ;AAAA,KACV;AAAA,EACF;AAAA,EAEO,gBAAA,GAAmB;AACxB,IAAA,MAAM,UAAA,GAAa,KAAK,MAAA,CAAO,kBAAA;AAAA,MAC7B;AAAA,KACF;AAEA,IAAA,OAAO;AAAA,MACL,QAAQ,IAAA,CAAK,OAAA;AAAA,MACb,cAAA,EAAgB,CAAA,EAAG,IAAA,CAAK,OAAO,CAAA,SAAA,CAAA;AAAA,MAC/B,iBAAA,EAAmB,CAAA,EAAG,IAAA,CAAK,OAAO,CAAA,YAAA,CAAA;AAAA,MAClC,QAAA,EAAU,CAAA,EAAG,IAAA,CAAK,OAAO,CAAA,sBAAA,CAAA;AAAA,MACzB,wBAAA,EAA0B,CAAC,MAAA,EAAQ,UAAU,CAAA;AAAA,MAC7C,uBAAA,EAAyB,CAAC,QAAQ,CAAA;AAAA,MAClC,qCAAA,EAAuC;AAAA,QACrC,OAAA;AAAA,QACA,OAAA;AAAA,QACA,OAAA;AAAA,QACA,OAAA;AAAA,QACA,OAAA;AAAA,QACA,OAAA;AAAA,QACA,OAAA;AAAA,QACA,OAAA;AAAA,QACA,OAAA;AAAA,QACA;AAAA,OACF;AAAA,MACA,gBAAA,EAAkB;AAAA,QAChB,QAAA;AAAA,QACA,GAAI,IAAA,CAAK,aAAA,GAAgB,CAAC,gBAAgB,IAAI;AAAC,OACjD;AAAA,MACA,qCAAA,EAAuC;AAAA,QACrC,qBAAA;AAAA,QACA;AAAA,OACF;AAAA,MACA,gBAAA,EAAkB,CAAC,KAAA,EAAO,KAAK,CAAA;AAAA,MAC/B,qBAAA,EAAuB;AAAA,QACrB,oBAAA;AAAA,QACA,GAAI,IAAA,CAAK,aAAA,GAAgB,CAAC,eAAe,IAAI;AAAC,OAChD;AAAA,MACA,sBAAA,EAAwB,CAAA,EAAG,IAAA,CAAK,OAAO,CAAA,aAAA,CAAA;AAAA,MACvC,gCAAA,EAAkC,CAAC,MAAA,EAAQ,OAAO,CAAA;AAAA,MAClD,GAAI,UAAA,IAAc;AAAA,QAChB,qBAAA,EAAuB,CAAA,EAAG,IAAA,CAAK,OAAO,CAAA,YAAA,CAAA;AAAA,QACtC,mBAAA,EAAqB,CAAA,EAAG,IAAA,CAAK,OAAO,CAAA,UAAA;AAAA;AACtC,KACF;AAAA,EACF;AAAA,EAEA,MAAa,cAAA,GAAiB;AAC5B,IAAA,OAAO,MAAM,IAAA,CAAK,WAAA,CAAY,cAAA,EAAe;AAAA,EAC/C;AAAA,EAEA,MAAa,WAAA,CAAY,EAAE,KAAA,EAAM,EAAsB;AACrD,IAAA,MAAM,WAAA,GAAc,MAAM,IAAA,CAAK,IAAA,CAAK,aAAa,KAAA,EAAO;AAAA,MACtD,kBAAA,EAAoB;AAAA,KACrB,CAAA;AACD,IAAA,IAAI,CAAC,IAAA,CAAK,IAAA,CAAK,WAAA,CAAY,WAAA,EAAa,MAAM,CAAA,EAAG;AAC/C,MAAA,MAAM,IAAIA,iBAAA;AAAA,QACR;AAAA,OACF;AAAA,IACF;AAEA,IAAA,MAAM,EAAE,GAAA,EAAK,aAAA,EAAc,GAAIC,eAAU,KAAK,CAAA;AAE9C,IAAA,IAAI,OAAO,kBAAkB,QAAA,EAAU;AACrC,MAAA,MAAM,IAAI,MAAM,sDAAsD,CAAA;AAAA,IACxE;AACA,IAAA,OAAO,MAAM,IAAA,CAAK,QAAA,CAAS,WAAA,CAAY,aAAa,CAAA;AAAA,EACtD;AAAA,EAEA,MAAa,eAAe,IAAA,EAMzB;AACD,IAAA,MAAM,iBAAA,GAAoBC,wBAAO,UAAA,EAAW;AAC5C,IAAA,MAAM,qBAAA,GAAwBA,wBAAO,UAAA,EAAW;AAEhD,IAAA,MAAM,0BAAA,GAA6B,KAAK,MAAA,CAAO,sBAAA;AAAA,MAC7C;AAAA,KACF,IAAK,CAAC,GAAG,CAAA;AAET,IAAA,KAAA,MAAW,WAAA,IAAe,IAAA,CAAK,YAAA,IAAgB,EAAC,EAAG;AACjD,MAAA,IACE,CAAC,0BAAA,CAA2B,IAAA;AAAA,QAAK,CAAA,OAAA,KAC/BC,wBAAA,CAAQ,OAAA,CAAQ,WAAA,EAAa,OAAO;AAAA,OACtC,EACA;AACA,QAAA,MAAM,IAAIH,kBAAW,sBAAsB,CAAA;AAAA,MAC7C;AAAA,IACF;AAEA,IAAA,OAAO,MAAM,IAAA,CAAK,IAAA,CAAK,YAAA,CAAa;AAAA,MAClC,QAAA,EAAU,iBAAA;AAAA,MACV,YAAY,IAAA,CAAK,UAAA;AAAA,MACjB,YAAA,EAAc,qBAAA;AAAA,MACd,YAAA,EAAc,IAAA,CAAK,YAAA,IAAgB,EAAC;AAAA,MACpC,aAAA,EAAe,IAAA,CAAK,aAAA,IAAiB,CAAC,MAAM,CAAA;AAAA,MAC5C,UAAA,EAAY,IAAA,CAAK,UAAA,IAAc,CAAC,oBAAoB,CAAA;AAAA,MACpD,OAAO,IAAA,CAAK;AAAA,KACb,CAAA;AAAA,EACH;AAAA,EAEA,MAAa,2BAA2B,IAAA,EASrC;AACD,IAAA,MAAM;AAAA,MACJ,QAAA;AAAA,MACA,WAAA;AAAA,MACA,YAAA;AAAA,MACA,KAAA;AAAA,MACA,KAAA;AAAA,MACA,KAAA;AAAA,MACA,aAAA;AAAA,MACA;AAAA,KACF,GAAI,IAAA;AAEJ,IAAA,IAAI,iBAAiB,MAAA,EAAQ;AAC3B,MAAA,MAAM,IAAIA,kBAAW,2CAA2C,CAAA;AAAA,IAClE;AAEA,IAAA,MAAM,MAAA,GAAS,MAAM,IAAA,CAAK,aAAA,CAAc,UAAU,WAAW,CAAA;AAE7D,IAAA,IAAI,aAAA,EAAe;AACjB,MAAA,IACE,CAAC,uBACD,CAAC,CAAC,QAAQ,OAAO,CAAA,CAAE,QAAA,CAAS,mBAAmB,CAAA,EAC/C;AACA,QAAA,MAAM,IAAIA,kBAAW,+BAA+B,CAAA;AAAA,MACtD;AAAA,IACF;AAEA,IAAA,MAAM,SAAA,GAAYE,wBAAO,UAAA,EAAW;AACpC,IAAA,MAAM,gBAAA,GAAmBE,cAAA,CAAS,GAAA,EAAI,CAAE,IAAA,CAAK,EAAE,KAAA,EAAO,CAAA,EAAG,CAAA,CAAE,QAAA,EAAS;AAEpE,IAAA,MAAM,IAAA,CAAK,KAAK,0BAAA,CAA2B;AAAA,MACzC,EAAA,EAAI,SAAA;AAAA,MACJ,QAAA;AAAA,MACA,WAAA;AAAA,MACA,YAAA;AAAA,MACA,KAAA;AAAA,MACA,KAAA;AAAA,MACA,aAAA;AAAA,MACA,mBAAA;AAAA,MACA,KAAA;AAAA,MACA,SAAA,EAAW;AAAA,KACZ,CAAA;AAED,IAAA,OAAO;AAAA,MACL,EAAA,EAAI,SAAA;AAAA,MACJ,YAAY,MAAA,CAAO,UAAA;AAAA,MACnB,KAAA;AAAA,MACA;AAAA,KACF;AAAA,EACF;AAAA,EAEA,MAAc,cAAc,QAAA,EAAmC;AAC7D,IAAA,MAAM,SAAS,MAAM,IAAA,CAAK,KAAK,SAAA,CAAU,EAAE,UAAU,CAAA;AACrD,IAAA,IAAI,CAAC,MAAA,EAAQ;AACX,MAAA,MAAM,IAAIJ,kBAAW,mBAAmB,CAAA;AAAA,IAC1C;AACA,IAAA,OAAO,MAAA,CAAO,UAAA;AAAA,EAChB;AAAA,EAEA,MAAc,aAAA,CACZ,QAAA,EACA,WAAA,EACyD;AACzD,IAAA,MAAM,SAAS,MAAM,IAAA,CAAK,KAAK,SAAA,CAAU,EAAE,UAAU,CAAA;AACrD,IAAA,IAAI,CAAC,MAAA,EAAQ;AACX,MAAA,MAAM,IAAIA,kBAAW,mBAAmB,CAAA;AAAA,IAC1C;AAEA,IAAA,IAAI,CAAC,MAAA,CAAO,YAAA,CAAa,QAAA,CAAS,WAAW,CAAA,EAAG;AAC9C,MAAA,MAAM,IAAIA,kBAAW,sBAAsB,CAAA;AAAA,IAC7C;AAEA,IAAA,OAAO;AAAA,MACL,YAAY,MAAA,CAAO,UAAA;AAAA,MACnB,cAAc,MAAA,CAAO;AAAA,KACvB;AAAA,EACF;AAAA,EAEA,MAAa,4BAA4B,IAAA,EAGtC;AACD,IAAA,MAAM,EAAE,SAAA,EAAW,aAAA,EAAc,GAAI,IAAA;AAErC,IAAA,MAAM,OAAA,GAAU,MAAM,IAAA,CAAK,IAAA,CAAK,uBAAA,CAAwB;AAAA,MACtD,EAAA,EAAI;AAAA,KACL,CAAA;AAED,IAAA,IAAI,CAAC,OAAA,EAAS;AACZ,MAAA,MAAM,IAAIK,qBAAc,+BAA+B,CAAA;AAAA,IACzD;AAEA,IAAA,IAAID,eAAS,UAAA,CAAW,OAAA,CAAQ,SAAS,CAAA,GAAIA,cAAA,CAAS,KAAI,EAAG;AAC3D,MAAA,MAAM,IAAIJ,kBAAW,+BAA+B,CAAA;AAAA,IACtD;AAEA,IAAA,IAAI,OAAA,CAAQ,WAAW,SAAA,EAAW;AAChC,MAAA,MAAM,IAAIK,qBAAc,4CAA4C,CAAA;AAAA,IACtE;AAEA,IAAA,MAAM,IAAA,CAAK,KAAK,0BAAA,CAA2B;AAAA,MACzC,IAAI,OAAA,CAAQ,EAAA;AAAA,MACZ,aAAA;AAAA,MACA,MAAA,EAAQ;AAAA,KACT,CAAA;AAED,IAAA,MAAM,oBAAoBH,uBAAA,CAAO,WAAA,CAAY,EAAE,CAAA,CAAE,SAAS,WAAW,CAAA;AACrE,IAAA,MAAM,aAAA,GAAgBE,cAAA,CAAS,GAAA,EAAI,CAAE,IAAA,CAAK,EAAE,OAAA,EAAS,EAAA,EAAI,CAAA,CAAE,QAAA,EAAS;AAEpE,IAAA,MAAM,IAAA,CAAK,KAAK,uBAAA,CAAwB;AAAA,MACtC,IAAA,EAAM,iBAAA;AAAA,MACN,WAAW,OAAA,CAAQ,EAAA;AAAA,MACnB,SAAA,EAAW;AAAA,KACZ,CAAA;AAED,IAAA,MAAM,WAAA,GAAc,IAAI,GAAA,CAAI,OAAA,CAAQ,WAAW,CAAA;AAE/C,IAAA,WAAA,CAAY,YAAA,CAAa,MAAA,CAAO,MAAA,EAAQ,iBAAiB,CAAA;AACzD,IAAA,IAAI,QAAQ,KAAA,EAAO;AACjB,MAAA,WAAA,CAAY,YAAA,CAAa,MAAA,CAAO,OAAA,EAAS,OAAA,CAAQ,KAAK,CAAA;AAAA,IACxD;AAEA,IAAA,OAAO;AAAA,MACL,WAAA,EAAa,YAAY,QAAA;AAAS,KACpC;AAAA,EACF;AAAA,EAEA,MAAa,wBAAwB,IAAA,EAA6B;AAChE,IAAA,MAAM,OAAA,GAAU,MAAM,IAAA,CAAK,IAAA,CAAK,uBAAA,CAAwB;AAAA,MACtD,IAAI,IAAA,CAAK;AAAA,KACV,CAAA;AAED,IAAA,IAAI,CAAC,OAAA,EAAS;AACZ,MAAA,MAAM,IAAIC,qBAAc,+BAA+B,CAAA;AAAA,IACzD;AAEA,IAAA,IAAID,eAAS,UAAA,CAAW,OAAA,CAAQ,SAAS,CAAA,GAAIA,cAAA,CAAS,KAAI,EAAG;AAC3D,MAAA,MAAM,IAAIJ,kBAAW,+BAA+B,CAAA;AAAA,IACtD;AAEA,IAAA,IAAI,OAAA,CAAQ,WAAW,SAAA,EAAW;AAChC,MAAA,MAAM,IAAIK,qBAAc,4CAA4C,CAAA;AAAA,IACtE;AAEA,IAAA,MAAM,UAAA,GAAa,MAAM,IAAA,CAAK,aAAA,CAAc,QAAQ,QAAQ,CAAA;AAE5D,IAAA,OAAO;AAAA,MACL,IAAI,OAAA,CAAQ,EAAA;AAAA,MACZ,UAAU,OAAA,CAAQ,QAAA;AAAA,MAClB,UAAA;AAAA,MACA,aAAa,OAAA,CAAQ,WAAA;AAAA,MACrB,OAAO,OAAA,CAAQ,KAAA;AAAA,MACf,OAAO,OAAA,CAAQ,KAAA;AAAA,MACf,cAAc,OAAA,CAAQ,YAAA;AAAA,MACtB,eAAe,OAAA,CAAQ,aAAA;AAAA,MACvB,qBAAqB,OAAA,CAAQ,mBAAA;AAAA,MAC7B,OAAO,OAAA,CAAQ,KAAA;AAAA,MACf,WAAW,OAAA,CAAQ,SAAA;AAAA,MACnB,QAAQ,OAAA,CAAQ;AAAA,KAClB;AAAA,EACF;AAAA,EAEA,MAAa,2BAA2B,IAAA,EAGrC;AACD,IAAA,MAAM,EAAE,SAAA,EAAW,aAAA,EAAc,GAAI,IAAA;AAErC,IAAA,MAAM,OAAA,GAAU,MAAM,IAAA,CAAK,IAAA,CAAK,uBAAA,CAAwB;AAAA,MACtD,EAAA,EAAI;AAAA,KACL,CAAA;AAED,IAAA,IAAI,CAAC,OAAA,EAAS;AACZ,MAAA,MAAM,IAAIA,qBAAc,+BAA+B,CAAA;AAAA,IACzD;AAEA,IAAA,IAAID,eAAS,UAAA,CAAW,OAAA,CAAQ,SAAS,CAAA,GAAIA,cAAA,CAAS,KAAI,EAAG;AAC3D,MAAA,MAAM,IAAIJ,kBAAW,+BAA+B,CAAA;AAAA,IACtD;AAEA,IAAA,IAAI,OAAA,CAAQ,WAAW,SAAA,EAAW;AAChC,MAAA,MAAM,IAAIK,qBAAc,4CAA4C,CAAA;AAAA,IACtE;AAEA,IAAA,MAAM,IAAA,CAAK,KAAK,0BAAA,CAA2B;AAAA,MACzC,IAAI,OAAA,CAAQ,EAAA;AAAA,MACZ,MAAA,EAAQ,UAAA;AAAA,MACR;AAAA,KACD,CAAA;AAAA,EACH;AAAA,EAEA,MAAa,qBAAqB,MAAA,EAM/B;AACD,IAAA,MAAM,EAAE,IAAA,EAAM,WAAA,EAAa,YAAA,EAAc,SAAA,EAAW,WAAU,GAAI,MAAA;AAElE,IAAA,IAAI,cAAc,oBAAA,EAAsB;AACtC,MAAA,MAAM,IAAIL,kBAAW,wBAAwB,CAAA;AAAA,IAC/C;AAEA,IAAA,MAAM,WAAW,MAAM,IAAA,CAAK,KAAK,oBAAA,CAAqB,EAAE,MAAM,CAAA;AAC9D,IAAA,IAAI,CAAC,QAAA,EAAU;AACb,MAAA,MAAM,IAAIM,2BAAoB,4BAA4B,CAAA;AAAA,IAC5D;AAEA,IAAA,IAAIF,eAAS,UAAA,CAAW,QAAA,CAAS,SAAS,CAAA,GAAIA,cAAA,CAAS,KAAI,EAAG;AAC5D,MAAA,MAAM,IAAIE,2BAAoB,4BAA4B,CAAA;AAAA,IAC5D;AAEA,IAAA,IAAI,SAAS,IAAA,EAAM;AACjB,MAAA,MAAM,IAAIA,2BAAoB,iCAAiC,CAAA;AAAA,IACjE;AAEA,IAAA,MAAM,OAAA,GAAU,MAAM,IAAA,CAAK,IAAA,CAAK,uBAAA,CAAwB;AAAA,MACtD,IAAI,QAAA,CAAS;AAAA,KACd,CAAA;AAED,IAAA,IAAI,CAAC,OAAA,EAAS;AACZ,MAAA,MAAM,IAAID,qBAAc,+BAA+B,CAAA;AAAA,IACzD;AAEA,IAAA,IAAI,OAAA,CAAQ,gBAAgB,WAAA,EAAa;AACvC,MAAA,MAAM,IAAIC,2BAAoB,uBAAuB,CAAA;AAAA,IACvD;AAEA,IAAA,IAAI,OAAA,CAAQ,WAAW,UAAA,EAAY;AACjC,MAAA,MAAM,IAAIA,2BAAoB,4BAA4B,CAAA;AAAA,IAC5D;AAEA,IAAA,IAAI,CAAC,QAAQ,aAAA,EAAe;AAC1B,MAAA,MAAM,IAAIA,2BAAoB,uCAAuC,CAAA;AAAA,IACvE;AAEA,IAAA,IAAI,QAAQ,aAAA,EAAe;AACzB,MAAA,IAAI,CAAC,YAAA,EAAc;AACjB,QAAA,MAAM,IAAIA,2BAAoB,iCAAiC,CAAA;AAAA,MACjE;AAEA,MAAA,IACE,CAAC,IAAA,CAAK,UAAA;AAAA,QACJ,OAAA,CAAQ,aAAA;AAAA,QACR,YAAA;AAAA,QACA,OAAA,CAAQ;AAAA,OACV,EACA;AACA,QAAA,MAAM,IAAIA,2BAAoB,uBAAuB,CAAA;AAAA,MACvD;AAAA,IACF;AAEA,IAAA,MAAM,IAAA,CAAK,KAAK,uBAAA,CAAwB;AAAA,MACtC,IAAA;AAAA,MACA,IAAA,EAAM;AAAA,KACP,CAAA;AAED,IAAA,MAAM,EAAE,KAAA,EAAM,GAAI,MAAM,IAAA,CAAK,YAAY,UAAA,CAAW;AAAA,MAClD,MAAA,EAAQ;AAAA,QACN,KAAK,OAAA,CAAQ;AAAA;AACf,KACD,CAAA;AAGD,IAAA,IAAI,YAAA;AACJ,IAAA,MAAM,SAAS,OAAA,CAAQ,KAAA,EAAO,KAAA,CAAM,GAAG,KAAK,EAAC;AAC7C,IAAA,IAAI,MAAA,CAAO,QAAA,CAAS,gBAAgB,CAAA,IAAK,KAAK,aAAA,EAAe;AAC3D,MAAA,YAAA,GAAe,MAAM,IAAA,CAAK,aAAA,CAAc,iBAAA,CAAkB;AAAA,QACxD,eAAe,OAAA,CAAQ,aAAA;AAAA,QACvB,cAAc,OAAA,CAAQ;AAAA,OACvB,CAAA;AAAA,IACH;AAEA,IAAA,OAAO;AAAA,MACL,WAAA,EAAa,KAAA;AAAA,MACb,SAAA,EAAW,QAAA;AAAA,MACX,SAAA;AAAA,MACA,OAAA,EAAS,KAAA;AAAA,MACT,KAAA,EAAO,QAAQ,KAAA,IAAS,QAAA;AAAA,MACxB;AAAA,KACF;AAAA,EACF;AAAA,EAEA,MAAa,mBAAmB,MAAA,EAQ7B;AACD,IAAA,IAAI,CAAC,KAAK,aAAA,EAAe;AACvB,MAAA,MAAM,IAAIN,kBAAW,gCAAgC,CAAA;AAAA,IACvD;AAEA,IAAA,MAAM,EAAE,WAAA,EAAa,YAAA,KACnB,MAAM,IAAA,CAAK,cAAc,kBAAA,CAAmB;AAAA,MAC1C,cAAc,MAAA,CAAO,YAAA;AAAA,MACrB,aAAa,IAAA,CAAK,WAAA;AAAA,MAClB,UAAU,MAAA,CAAO;AAAA,KAClB,CAAA;AAEH,IAAA,MAAM,SAAA,GAAYO,0CAAA,CAAuB,IAAA,CAAK,MAAM,CAAA;AAEpD,IAAA,OAAO;AAAA,MACL,WAAA;AAAA,MACA,SAAA,EAAW,QAAA;AAAA,MACX,SAAA;AAAA,MACA;AAAA,KACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAa,wBAAwB,OAAA,EAGhB;AACnB,IAAA,MAAM,EAAE,QAAA,EAAU,YAAA,EAAa,GAAI,OAAA;AACnC,IAAA,MAAM,SAAS,MAAM,IAAA,CAAK,KAAK,SAAA,CAAU,EAAE,UAAU,CAAA;AACrD,IAAA,IAAI,CAAC,QAAQ,YAAA,EAAc;AACzB,MAAA,OAAO,KAAA;AAAA,IACT;AACA,IAAA,MAAM,QAAA,GAAW,MAAA,CAAO,IAAA,CAAK,MAAA,CAAO,cAAc,MAAM,CAAA;AACxD,IAAA,MAAM,QAAA,GAAW,MAAA,CAAO,IAAA,CAAK,YAAA,EAAc,MAAM,CAAA;AACjD,IAAA,IAAI,QAAA,CAAS,MAAA,KAAW,QAAA,CAAS,MAAA,EAAQ;AACvC,MAAA,OAAO,KAAA;AAAA,IACT;AACA,IAAA,OAAOL,uBAAA,CAAO,eAAA,CAAgB,QAAA,EAAU,QAAQ,CAAA;AAAA,EAClD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAa,mBAAmB,KAAA,EAA8B;AAC5D,IAAA,IAAI,CAAC,KAAK,aAAA,EAAe;AACvB,MAAA;AAAA,IACF;AACA,IAAA,MAAM,IAAA,CAAK,aAAA,CAAc,kBAAA,CAAmB,KAAK,CAAA;AAAA,EACnD;AAAA,EAEQ,UAAA,CACN,aAAA,EACA,YAAA,EACA,MAAA,EACS;AACT,IAAA,IAAI,CAAC,MAAA,IAAU,MAAA,KAAW,OAAA,EAAS;AACjC,MAAA,OAAO,aAAA,KAAkB,YAAA;AAAA,IAC3B;AAEA,IAAA,IAAI,WAAW,MAAA,EAAQ;AACrB,MAAA,MAAM,IAAA,GAAOA,wBAAO,UAAA,CAAW,QAAQ,EAAE,MAAA,CAAO,YAAY,EAAE,MAAA,EAAO;AACrE,MAAA,MAAM,aAAA,GAAgB,IAAA,CAAK,QAAA,CAAS,WAAW,CAAA;AAC/C,MAAA,OAAO,aAAA,KAAkB,aAAA;AAAA,IAC3B;AAEA,IAAA,OAAO,KAAA;AAAA,EACT;AACF;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"router.cjs.js","sources":["../../src/service/router.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 Router from 'express-promise-router';\nimport cookieParser from 'cookie-parser';\nimport {\n AuthService,\n DatabaseService,\n DiscoveryService,\n HttpAuthService,\n LoggerService,\n RootConfigService,\n} from '@backstage/backend-plugin-api';\nimport { AuthOwnershipResolver } from '@backstage/plugin-auth-node';\nimport { CatalogService } from '@backstage/plugin-catalog-node';\nimport { NotFoundError } from '@backstage/errors';\nimport { KeyStores } from '../identity/KeyStores';\nimport { TokenFactory } from '../identity/TokenFactory';\nimport { UserInfoDatabase } from '../database/UserInfoDatabase';\nimport session from 'express-session';\nimport connectSessionKnex from 'connect-session-knex';\nimport passport from 'passport';\nimport { AuthDatabase } from '../database/AuthDatabase';\nimport {\n readBackstageTokenExpiration,\n readDcrTokenExpiration,\n} from './readTokenExpiration.ts';\nimport { StaticTokenIssuer } from '../identity/StaticTokenIssuer';\nimport { StaticKeyStore } from '../identity/StaticKeyStore';\nimport { bindProviderRouters, ProviderFactories } from '../providers/router';\nimport { OidcRouter } from './OidcRouter';\nimport { OidcDatabase } from '../database/OidcDatabase';\n\ninterface RouterOptions {\n logger: LoggerService;\n database: DatabaseService;\n config: RootConfigService;\n discovery: DiscoveryService;\n auth: AuthService;\n tokenFactoryAlgorithm?: string;\n providerFactories?: ProviderFactories;\n catalog: CatalogService;\n ownershipResolver?: AuthOwnershipResolver;\n httpAuth: HttpAuthService;\n}\n\nexport async function createRouter(\n options: RouterOptions,\n): Promise<express.Router> {\n const {\n logger,\n config,\n discovery,\n database: db,\n tokenFactoryAlgorithm,\n providerFactories = {},\n httpAuth,\n } = options;\n\n const router = Router();\n\n const appUrl = config.getString('app.baseUrl');\n const authUrl = await discovery.getExternalBaseUrl('auth');\n const backstageTokenExpiration = readBackstageTokenExpiration(config);\n const database = AuthDatabase.create(db);\n\n const keyStore = await KeyStores.fromConfig(config, {\n logger,\n database,\n });\n\n const userInfo = await UserInfoDatabase.create({\n database,\n });\n\n const omitClaimsFromToken = config.getOptionalBoolean(\n 'auth.omitIdentityTokenOwnershipClaim',\n )\n ? ['ent']\n : [];\n\n const createTokenIssuer = (opts: {\n logger: LoggerService;\n expirationSeconds: number;\n }) => {\n if (keyStore instanceof StaticKeyStore) {\n return new StaticTokenIssuer(\n {\n logger: opts.logger,\n issuer: authUrl,\n sessionExpirationSeconds: opts.expirationSeconds,\n omitClaimsFromToken,\n },\n keyStore as StaticKeyStore,\n );\n }\n return new TokenFactory({\n issuer: authUrl,\n keyStore,\n keyDurationSeconds: opts.expirationSeconds,\n logger: opts.logger,\n algorithm:\n tokenFactoryAlgorithm ??\n config.getOptionalString('auth.identityTokenAlgorithm'),\n omitClaimsFromToken,\n });\n };\n\n const tokenIssuer = createTokenIssuer({\n logger: logger.child({ component: 'token-factory' }),\n expirationSeconds: backstageTokenExpiration,\n });\n\n const secret = config.getOptionalString('auth.session.secret');\n if (secret) {\n router.use(cookieParser(secret));\n const enforceCookieSSL = authUrl.startsWith('https');\n const KnexSessionStore = connectSessionKnex(session);\n router.use(\n session({\n secret,\n saveUninitialized: false,\n resave: false,\n cookie: { secure: enforceCookieSSL ? 'auto' : false },\n store: new KnexSessionStore({\n createtable: false,\n knex: await database.get(),\n }),\n }),\n );\n router.use(passport.initialize());\n router.use(passport.session());\n } else {\n router.use(cookieParser());\n }\n\n router.use(express.urlencoded({ extended: false }));\n router.use(express.json());\n\n bindProviderRouters(router, {\n providers: providerFactories,\n appUrl,\n baseUrl: authUrl,\n tokenIssuer,\n ...options,\n auth: options.auth,\n userInfo,\n });\n\n const dcrTokenExpiration = readDcrTokenExpiration(config);\n\n const oidcTokenIssuer = createTokenIssuer({\n logger: logger.child({ component: 'oidc-token-factory' }),\n expirationSeconds: dcrTokenExpiration,\n });\n\n const oidc = await OidcDatabase.create({ database });\n\n const oidcRouter = OidcRouter.create({\n auth: options.auth,\n tokenIssuer: oidcTokenIssuer,\n baseUrl: authUrl,\n appUrl,\n userInfo,\n oidc,\n logger,\n httpAuth,\n config,\n });\n\n router.use(oidcRouter.getRouter());\n\n // Gives a more helpful error message than a plain 404\n router.use('/:provider/', req => {\n const { provider } = req.params;\n throw new NotFoundError(`Unknown auth provider '${provider}'`);\n });\n\n return router;\n}\n"],"names":["router","Router","readBackstageTokenExpiration","AuthDatabase","KeyStores","UserInfoDatabase","StaticKeyStore","StaticTokenIssuer","TokenFactory","cookieParser","connectSessionKnex","session","passport","express","bindProviderRouters","readDcrTokenExpiration","OidcDatabase","OidcRouter","NotFoundError"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"router.cjs.js","sources":["../../src/service/router.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 Router from 'express-promise-router';\nimport cookieParser from 'cookie-parser';\nimport {\n AuthService,\n DatabaseService,\n DiscoveryService,\n HttpAuthService,\n LoggerService,\n RootConfigService,\n} from '@backstage/backend-plugin-api';\nimport { AuthOwnershipResolver } from '@backstage/plugin-auth-node';\nimport { CatalogService } from '@backstage/plugin-catalog-node';\nimport { NotFoundError } from '@backstage/errors';\nimport { KeyStores } from '../identity/KeyStores';\nimport { TokenFactory } from '../identity/TokenFactory';\nimport { UserInfoDatabase } from '../database/UserInfoDatabase';\nimport session from 'express-session';\nimport connectSessionKnex from 'connect-session-knex';\nimport passport from 'passport';\nimport { AuthDatabase } from '../database/AuthDatabase';\nimport {\n readBackstageTokenExpiration,\n readDcrTokenExpiration,\n} from './readTokenExpiration.ts';\nimport { StaticTokenIssuer } from '../identity/StaticTokenIssuer';\nimport { StaticKeyStore } from '../identity/StaticKeyStore';\nimport { bindProviderRouters, ProviderFactories } from '../providers/router';\nimport { OidcRouter } from './OidcRouter';\nimport { OidcDatabase } from '../database/OidcDatabase';\nimport { OfflineAccessService } from './OfflineAccessService';\n\ninterface RouterOptions {\n logger: LoggerService;\n database: DatabaseService;\n config: RootConfigService;\n discovery: DiscoveryService;\n auth: AuthService;\n tokenFactoryAlgorithm?: string;\n providerFactories?: ProviderFactories;\n catalog: CatalogService;\n ownershipResolver?: AuthOwnershipResolver;\n httpAuth: HttpAuthService;\n offlineAccess?: OfflineAccessService;\n}\n\nexport async function createRouter(\n options: RouterOptions,\n): Promise<express.Router> {\n const {\n logger,\n config,\n discovery,\n database: db,\n tokenFactoryAlgorithm,\n providerFactories = {},\n httpAuth,\n } = options;\n\n const router = Router();\n\n const appUrl = config.getString('app.baseUrl');\n const authUrl = await discovery.getExternalBaseUrl('auth');\n const backstageTokenExpiration = readBackstageTokenExpiration(config);\n const database = AuthDatabase.create(db);\n\n const keyStore = await KeyStores.fromConfig(config, {\n logger,\n database,\n });\n\n const userInfo = await UserInfoDatabase.create({\n database,\n });\n\n const omitClaimsFromToken = config.getOptionalBoolean(\n 'auth.omitIdentityTokenOwnershipClaim',\n )\n ? ['ent']\n : [];\n\n const createTokenIssuer = (opts: {\n logger: LoggerService;\n expirationSeconds: number;\n }) => {\n if (keyStore instanceof StaticKeyStore) {\n return new StaticTokenIssuer(\n {\n logger: opts.logger,\n issuer: authUrl,\n sessionExpirationSeconds: opts.expirationSeconds,\n omitClaimsFromToken,\n },\n keyStore as StaticKeyStore,\n );\n }\n return new TokenFactory({\n issuer: authUrl,\n keyStore,\n keyDurationSeconds: opts.expirationSeconds,\n logger: opts.logger,\n algorithm:\n tokenFactoryAlgorithm ??\n config.getOptionalString('auth.identityTokenAlgorithm'),\n omitClaimsFromToken,\n });\n };\n\n const tokenIssuer = createTokenIssuer({\n logger: logger.child({ component: 'token-factory' }),\n expirationSeconds: backstageTokenExpiration,\n });\n\n const secret = config.getOptionalString('auth.session.secret');\n if (secret) {\n router.use(cookieParser(secret));\n const enforceCookieSSL = authUrl.startsWith('https');\n const KnexSessionStore = connectSessionKnex(session);\n router.use(\n session({\n secret,\n saveUninitialized: false,\n resave: false,\n cookie: { secure: enforceCookieSSL ? 'auto' : false },\n store: new KnexSessionStore({\n createtable: false,\n knex: await database.get(),\n }),\n }),\n );\n router.use(passport.initialize());\n router.use(passport.session());\n } else {\n router.use(cookieParser());\n }\n\n router.use(express.urlencoded({ extended: false }));\n router.use(express.json());\n\n bindProviderRouters(router, {\n providers: providerFactories,\n appUrl,\n baseUrl: authUrl,\n tokenIssuer,\n ...options,\n auth: options.auth,\n userInfo,\n });\n\n const dcrTokenExpiration = readDcrTokenExpiration(config);\n\n const oidcTokenIssuer = createTokenIssuer({\n logger: logger.child({ component: 'oidc-token-factory' }),\n expirationSeconds: dcrTokenExpiration,\n });\n\n const oidc = await OidcDatabase.create({ database });\n\n const oidcRouter = OidcRouter.create({\n auth: options.auth,\n tokenIssuer: oidcTokenIssuer,\n baseUrl: authUrl,\n appUrl,\n userInfo,\n oidc,\n logger,\n httpAuth,\n config,\n offlineAccess: options.offlineAccess,\n });\n\n router.use(oidcRouter.getRouter());\n\n // Gives a more helpful error message than a plain 404\n router.use('/:provider/', req => {\n const { provider } = req.params;\n throw new NotFoundError(`Unknown auth provider '${provider}'`);\n });\n\n return router;\n}\n"],"names":["router","Router","readBackstageTokenExpiration","AuthDatabase","KeyStores","UserInfoDatabase","StaticKeyStore","StaticTokenIssuer","TokenFactory","cookieParser","connectSessionKnex","session","passport","express","bindProviderRouters","readDcrTokenExpiration","OidcDatabase","OidcRouter","NotFoundError"],"mappings":"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"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 The Backstage Authors
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
// @ts-check
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @param {import('knex').Knex} knex
|
|
21
|
+
*/
|
|
22
|
+
exports.up = async function up(knex) {
|
|
23
|
+
await knex.schema.createTable('offline_sessions', table => {
|
|
24
|
+
table.comment(
|
|
25
|
+
'Offline sessions for refresh tokens in dynamic client registration and device auth flows',
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
table
|
|
29
|
+
.string('id')
|
|
30
|
+
.primary()
|
|
31
|
+
.notNullable()
|
|
32
|
+
.comment('Persistent session ID that remains across token rotations');
|
|
33
|
+
|
|
34
|
+
table
|
|
35
|
+
.string('user_entity_ref')
|
|
36
|
+
.notNullable()
|
|
37
|
+
.comment('Backstage user entity reference');
|
|
38
|
+
|
|
39
|
+
table
|
|
40
|
+
.string('oidc_client_id')
|
|
41
|
+
.nullable()
|
|
42
|
+
.comment('OIDC client identifier (optional, for OIDC flows)');
|
|
43
|
+
|
|
44
|
+
table
|
|
45
|
+
.text('token_hash')
|
|
46
|
+
.notNullable()
|
|
47
|
+
.comment('Current refresh token hash (scrypt)');
|
|
48
|
+
|
|
49
|
+
table
|
|
50
|
+
.timestamp('created_at', { useTz: true, precision: 0 })
|
|
51
|
+
.notNullable()
|
|
52
|
+
.defaultTo(knex.fn.now())
|
|
53
|
+
.comment('Session creation timestamp');
|
|
54
|
+
|
|
55
|
+
table
|
|
56
|
+
.timestamp('last_used_at', { useTz: true, precision: 0 })
|
|
57
|
+
.notNullable()
|
|
58
|
+
.defaultTo(knex.fn.now())
|
|
59
|
+
.comment('Last token refresh timestamp');
|
|
60
|
+
|
|
61
|
+
table
|
|
62
|
+
.foreign('oidc_client_id')
|
|
63
|
+
.references('client_id')
|
|
64
|
+
.inTable('oidc_clients')
|
|
65
|
+
.onDelete('CASCADE');
|
|
66
|
+
table.index('user_entity_ref', 'offline_sessions_user_idx');
|
|
67
|
+
table.index('oidc_client_id', 'offline_sessions_oidc_client_idx');
|
|
68
|
+
table.index('created_at', 'offline_sessions_created_idx');
|
|
69
|
+
table.index('last_used_at', 'offline_sessions_last_used_idx');
|
|
70
|
+
});
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @param {import('knex').Knex} knex
|
|
75
|
+
*/
|
|
76
|
+
exports.down = async function down(knex) {
|
|
77
|
+
await knex.schema.dropTable('offline_sessions');
|
|
78
|
+
};
|