@backstage/plugin-auth-backend 0.27.0-next.1 → 0.27.0

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,50 @@
1
1
  # @backstage/plugin-auth-backend
2
2
 
3
+ ## 0.27.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 31de2c9: Added experimental support for Client ID Metadata Documents (CIMD).
8
+
9
+ This allows Backstage to act as an OAuth 2.0 authorization server that supports the [IETF Client ID Metadata Document draft](https://datatracker.ietf.org/doc/draft-ietf-oauth-client-id-metadata-document/). External OAuth clients can use HTTPS URLs as their `client_id`, and Backstage will fetch metadata from those URLs to validate the client.
10
+
11
+ **Configuration example:**
12
+
13
+ ```yaml
14
+ auth:
15
+ experimentalClientIdMetadataDocuments:
16
+ enabled: true
17
+ # Optional: restrict which `client_id` URLs are allowed (defaults to ['*'])
18
+ allowedClientIdPatterns:
19
+ - 'https://example.com/*'
20
+ - 'https://*.trusted-domain.com/*'
21
+ # Optional: restrict which redirect URIs are allowed (defaults to ['*'])
22
+ allowedRedirectUriPatterns:
23
+ - 'http://localhost:*'
24
+ - 'https://*.example.com/*'
25
+ ```
26
+
27
+ Clients using CIMD must host a JSON metadata document at their `client_id` URL containing at minimum:
28
+
29
+ ```json
30
+ {
31
+ "client_id": "https://example.com/.well-known/oauth-client/my-app",
32
+ "client_name": "My Application",
33
+ "redirect_uris": ["http://localhost:8080/callback"],
34
+ "token_endpoint_auth_method": "none"
35
+ }
36
+ ```
37
+
38
+ - d0786b9: Added experimental support for refresh tokens via the `auth.experimentalRefreshToken.enabled` configuration option. When enabled, clients can request the `offline_access` scope to receive refresh tokens that can be used to obtain new access tokens without re-authentication.
39
+
40
+ ### Patch Changes
41
+
42
+ - 7455dae: Use node prefix on native imports
43
+ - Updated dependencies
44
+ - @backstage/plugin-catalog-node@2.0.0
45
+ - @backstage/backend-plugin-api@1.7.0
46
+ - @backstage/plugin-auth-node@0.6.13
47
+
3
48
  ## 0.27.0-next.1
4
49
 
5
50
  ### Minor Changes
package/config.d.ts CHANGED
@@ -95,7 +95,6 @@ export interface Config {
95
95
 
96
96
  /**
97
97
  * The backstage token expiration.
98
- * Defaults to 1 hour (3600s). Maximum allowed is 24 hours.
99
98
  */
100
99
  backstageTokenExpiration?: HumanDuration | string;
101
100
 
@@ -150,12 +149,37 @@ export interface Config {
150
149
  * dynamic client registration. Defaults to '[*]' which allows any redirect URI.
151
150
  */
152
151
  allowedRedirectUriPatterns?: string[];
152
+ };
153
153
 
154
+ /**
155
+ * Configuration for Client ID Metadata Documents (CIMD)
156
+ *
157
+ * @see https://datatracker.ietf.org/doc/draft-ietf-oauth-client-id-metadata-document/
158
+ */
159
+ experimentalClientIdMetadataDocuments?: {
154
160
  /**
155
- * The expiration time for the client registration access tokens.
156
- * Defaults to 1 hour (3600s). Maximum allowed is 24 hours.
161
+ * Whether to enable Client ID Metadata Documents support
162
+ * Defaults to false
157
163
  */
158
- tokenExpiration?: HumanDuration | string;
164
+ enabled?: boolean;
165
+
166
+ /**
167
+ * A list of allowed URI patterns for client_id URLs.
168
+ * Uses glob-style pattern matching where `*` matches any characters.
169
+ * Defaults to ['*'] which allows any client_id URL.
170
+ *
171
+ * @example ['https://example.com/*', 'https://*.trusted-domain.com/*']
172
+ */
173
+ allowedClientIdPatterns?: string[];
174
+
175
+ /**
176
+ * A list of allowed URI patterns for redirect URIs.
177
+ * Uses glob-style pattern matching where `*` matches any characters.
178
+ * Defaults to ['*'] which allows any redirect URI.
179
+ *
180
+ * @example ['http://localhost:*', 'http://127.0.0.1:*\/callback']
181
+ */
182
+ allowedRedirectUriPatterns?: string[];
159
183
  };
160
184
  };
161
185
  }
@@ -0,0 +1,133 @@
1
+ 'use strict';
2
+
3
+ var errors = require('@backstage/errors');
4
+ var promises = require('node:dns/promises');
5
+ var ipaddr = require('ipaddr.js');
6
+
7
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
8
+
9
+ var ipaddr__default = /*#__PURE__*/_interopDefaultCompat(ipaddr);
10
+
11
+ const FETCH_TIMEOUT_MS = 1e4;
12
+ const MAX_RESPONSE_BYTES = 64 * 1024;
13
+ const FORBIDDEN_AUTH_METHODS = [
14
+ "client_secret_basic",
15
+ "client_secret_post",
16
+ "client_secret_jwt"
17
+ ];
18
+ function validateCimdUrl(clientId) {
19
+ if (/\/\.\.?(\/|$)/.test(clientId)) {
20
+ throw new errors.InputError(
21
+ "Invalid client_id: path must not contain dot segments"
22
+ );
23
+ }
24
+ let url;
25
+ try {
26
+ url = new URL(clientId);
27
+ } catch {
28
+ throw new errors.InputError("Invalid client_id: not a valid URL");
29
+ }
30
+ const isHttps = url.protocol === "https:";
31
+ const isLocalHttp = url.protocol === "http:" && (url.hostname === "localhost" || url.hostname === "127.0.0.1") && process.env.NODE_ENV === "development";
32
+ if (!isHttps && !isLocalHttp) {
33
+ throw new errors.InputError(
34
+ "Invalid client_id: must use HTTPS (or HTTP for localhost in development)"
35
+ );
36
+ }
37
+ if (url.pathname === "" || url.pathname === "/") {
38
+ throw new errors.InputError("Invalid client_id: must have a path component");
39
+ }
40
+ if (url.hash) {
41
+ throw new errors.InputError("Invalid client_id: must not contain a fragment");
42
+ }
43
+ if (url.username || url.password) {
44
+ throw new errors.InputError("Invalid client_id: must not contain credentials");
45
+ }
46
+ if (url.search) {
47
+ throw new errors.InputError("Invalid client_id: must not contain a query string");
48
+ }
49
+ return url;
50
+ }
51
+ function isNonPublicIp(ip) {
52
+ try {
53
+ const addr = ipaddr__default.default.parse(ip);
54
+ const range = addr.range();
55
+ return range !== "unicast";
56
+ } catch {
57
+ return true;
58
+ }
59
+ }
60
+ async function validateHostNotPrivate(hostname) {
61
+ try {
62
+ const addresses = await promises.lookup(hostname, { all: true });
63
+ const nonPublicAddr = addresses.find((addr) => isNonPublicIp(addr.address));
64
+ if (nonPublicAddr) {
65
+ throw new errors.InputError("Invalid client_id URL");
66
+ }
67
+ } catch (error) {
68
+ if (errors.isError(error) && error.name === "InputError") throw error;
69
+ throw new errors.InputError("Failed to fetch client metadata");
70
+ }
71
+ }
72
+ function validateMetadata(metadata, expectedClientId) {
73
+ if (metadata.client_id !== expectedClientId) {
74
+ throw new errors.InputError("Client ID mismatch in metadata document");
75
+ }
76
+ if (!Array.isArray(metadata.redirect_uris) || metadata.redirect_uris.length === 0) {
77
+ throw new errors.InputError("Metadata must include at least one redirect_uri");
78
+ }
79
+ for (const uri of metadata.redirect_uris) {
80
+ if (!URL.canParse(uri)) {
81
+ throw new errors.InputError(`Invalid redirect_uri in metadata: ${uri}`);
82
+ }
83
+ }
84
+ if (metadata.client_secret !== void 0 || metadata.client_secret_expires_at !== void 0) {
85
+ throw new errors.InputError("Client metadata must not contain client_secret");
86
+ }
87
+ if (metadata.token_endpoint_auth_method && FORBIDDEN_AUTH_METHODS.includes(metadata.token_endpoint_auth_method)) {
88
+ throw new errors.InputError("Client metadata uses forbidden auth method");
89
+ }
90
+ }
91
+ async function fetchCimdMetadata(opts) {
92
+ const url = opts.validatedUrl ?? validateCimdUrl(opts.clientId);
93
+ const isLocalhostDev = (url.hostname === "localhost" || url.hostname === "127.0.0.1") && process.env.NODE_ENV === "development";
94
+ if (!isLocalhostDev) {
95
+ await validateHostNotPrivate(url.hostname);
96
+ }
97
+ let response;
98
+ try {
99
+ response = await fetch(url.toString(), {
100
+ method: "GET",
101
+ headers: { Accept: "application/json" },
102
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
103
+ });
104
+ } catch {
105
+ throw new errors.InputError("Failed to fetch client metadata");
106
+ }
107
+ if (!response.ok) {
108
+ throw new errors.InputError("Failed to fetch client metadata");
109
+ }
110
+ const contentLength = Number(response.headers.get("content-length"));
111
+ if (contentLength > MAX_RESPONSE_BYTES) {
112
+ throw new errors.InputError("Client metadata document too large");
113
+ }
114
+ let metadata;
115
+ try {
116
+ metadata = await response.json();
117
+ } catch {
118
+ throw new errors.InputError("Invalid client metadata document");
119
+ }
120
+ validateMetadata(metadata, opts.clientId);
121
+ return {
122
+ clientId: metadata.client_id,
123
+ clientName: metadata.client_name || metadata.client_id,
124
+ redirectUris: metadata.redirect_uris,
125
+ responseTypes: metadata.response_types || ["code"],
126
+ grantTypes: metadata.grant_types || ["authorization_code"],
127
+ scope: metadata.scope
128
+ };
129
+ }
130
+
131
+ exports.fetchCimdMetadata = fetchCimdMetadata;
132
+ exports.validateCimdUrl = validateCimdUrl;
133
+ //# sourceMappingURL=CimdClient.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CimdClient.cjs.js","sources":["../../src/service/CimdClient.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 */\n\nimport { InputError, isError } from '@backstage/errors';\nimport { lookup } from 'node:dns/promises';\nimport ipaddr from 'ipaddr.js';\n\nconst FETCH_TIMEOUT_MS = 10000;\nconst MAX_RESPONSE_BYTES = 64 * 1024;\n\n/** Auth methods that require a client secret - forbidden for CIMD clients */\nconst FORBIDDEN_AUTH_METHODS = [\n 'client_secret_basic',\n 'client_secret_post',\n 'client_secret_jwt',\n];\n\n/**\n * Raw metadata document from a CIMD URL.\n * Note: client_secret fields are included for validation (must NOT be present).\n */\ninterface CimdMetadata {\n client_id: string;\n client_name?: string;\n redirect_uris: string[];\n response_types?: string[];\n grant_types?: string[];\n scope?: string;\n token_endpoint_auth_method?: string;\n client_secret?: string;\n client_secret_expires_at?: number;\n}\n\n/** Validated CIMD client info */\nexport interface CimdClientInfo {\n clientId: string;\n clientName: string;\n redirectUris: string[];\n responseTypes: string[];\n grantTypes: string[];\n scope?: string;\n}\n\n/**\n * Validates and parses a CIMD URL per the IETF draft specification.\n * Requires HTTPS for production, but allows HTTP for localhost (development).\n *\n * @see https://datatracker.ietf.org/doc/draft-ietf-oauth-client-id-metadata-document/\n * @throws InputError if the URL is invalid per the CIMD spec\n */\nexport function validateCimdUrl(clientId: string): URL {\n // Per IETF draft: MUST NOT contain single-dot or double-dot path segments\n // Check before URL parsing since the URL constructor normalizes these away\n if (/\\/\\.\\.?(\\/|$)/.test(clientId)) {\n throw new InputError(\n 'Invalid client_id: path must not contain dot segments',\n );\n }\n\n let url: URL;\n try {\n url = new URL(clientId);\n } catch {\n throw new InputError('Invalid client_id: not a valid URL');\n }\n\n const isHttps = url.protocol === 'https:';\n const isLocalHttp =\n url.protocol === 'http:' &&\n (url.hostname === 'localhost' || url.hostname === '127.0.0.1') &&\n process.env.NODE_ENV === 'development';\n\n if (!isHttps && !isLocalHttp) {\n throw new InputError(\n 'Invalid client_id: must use HTTPS (or HTTP for localhost in development)',\n );\n }\n\n if (url.pathname === '' || url.pathname === '/') {\n throw new InputError('Invalid client_id: must have a path component');\n }\n\n if (url.hash) {\n throw new InputError('Invalid client_id: must not contain a fragment');\n }\n\n if (url.username || url.password) {\n throw new InputError('Invalid client_id: must not contain credentials');\n }\n\n // Per IETF draft: SHOULD NOT include a query string\n // We reject this for stricter compliance and security\n if (url.search) {\n throw new InputError('Invalid client_id: must not contain a query string');\n }\n\n return url;\n}\n\n/**\n * Checks if a client_id is a valid CIMD URL.\n * Requires HTTPS for production, but allows HTTP for localhost (development).\n */\nexport function isCimdUrl(clientId: string): boolean {\n try {\n validateCimdUrl(clientId);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * SSRF (Server-Side Request Forgery) Protection\n *\n * When fetching CIMD metadata from client-provided URLs, we must prevent\n * attackers from tricking Backstage into accessing internal resources.\n * For example, an attacker could provide a URL that resolves to:\n * - 127.0.0.1 (localhost services)\n * - 10.x.x.x, 172.16-31.x.x, 192.168.x.x (internal network)\n * - Cloud metadata endpoints (169.254.169.254)\n *\n * We use ipaddr.js to check if resolved IPs are in non-public ranges.\n * Only 'unicast' (public internet) addresses are allowed.\n *\n * @see https://datatracker.ietf.org/doc/draft-ietf-oauth-client-id-metadata-document/\n * Section 5.1 - Security Considerations\n */\nfunction isNonPublicIp(ip: string): boolean {\n try {\n const addr = ipaddr.parse(ip);\n const range = addr.range();\n // Only allow public unicast addresses\n return range !== 'unicast';\n } catch {\n // If we can't parse the IP, treat it as non-public and block it\n return true;\n }\n}\n\nasync function validateHostNotPrivate(hostname: string): Promise<void> {\n try {\n const addresses = await lookup(hostname, { all: true });\n const nonPublicAddr = addresses.find(addr => isNonPublicIp(addr.address));\n if (nonPublicAddr) {\n throw new InputError('Invalid client_id URL');\n }\n } catch (error) {\n if (isError(error) && error.name === 'InputError') throw error;\n throw new InputError('Failed to fetch client metadata');\n }\n}\n\nfunction validateMetadata(\n metadata: CimdMetadata,\n expectedClientId: string,\n): void {\n if (metadata.client_id !== expectedClientId) {\n throw new InputError('Client ID mismatch in metadata document');\n }\n\n if (\n !Array.isArray(metadata.redirect_uris) ||\n metadata.redirect_uris.length === 0\n ) {\n throw new InputError('Metadata must include at least one redirect_uri');\n }\n\n for (const uri of metadata.redirect_uris) {\n if (!URL.canParse(uri)) {\n throw new InputError(`Invalid redirect_uri in metadata: ${uri}`);\n }\n }\n\n if (\n metadata.client_secret !== undefined ||\n metadata.client_secret_expires_at !== undefined\n ) {\n throw new InputError('Client metadata must not contain client_secret');\n }\n\n if (\n metadata.token_endpoint_auth_method &&\n FORBIDDEN_AUTH_METHODS.includes(metadata.token_endpoint_auth_method)\n ) {\n throw new InputError('Client metadata uses forbidden auth method');\n }\n}\n\n/**\n * Fetches and validates a CIMD metadata document.\n * @throws InputError if fetching or validation fails\n */\nexport async function fetchCimdMetadata(opts: {\n clientId: string;\n validatedUrl?: URL;\n}): Promise<CimdClientInfo> {\n const url = opts.validatedUrl ?? validateCimdUrl(opts.clientId);\n\n // Skip SSRF validation for localhost in development only\n const isLocalhostDev =\n (url.hostname === 'localhost' || url.hostname === '127.0.0.1') &&\n process.env.NODE_ENV === 'development';\n if (!isLocalhostDev) {\n await validateHostNotPrivate(url.hostname);\n }\n\n let response: Response;\n try {\n response = await fetch(url.toString(), {\n method: 'GET',\n headers: { Accept: 'application/json' },\n signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),\n });\n } catch {\n throw new InputError('Failed to fetch client metadata');\n }\n\n if (!response.ok) {\n throw new InputError('Failed to fetch client metadata');\n }\n\n const contentLength = Number(response.headers.get('content-length'));\n if (contentLength > MAX_RESPONSE_BYTES) {\n throw new InputError('Client metadata document too large');\n }\n\n let metadata: CimdMetadata;\n try {\n metadata = await response.json();\n } catch {\n throw new InputError('Invalid client metadata document');\n }\n\n validateMetadata(metadata, opts.clientId);\n\n return {\n clientId: metadata.client_id,\n clientName: metadata.client_name || metadata.client_id,\n redirectUris: metadata.redirect_uris,\n responseTypes: metadata.response_types || ['code'],\n grantTypes: metadata.grant_types || ['authorization_code'],\n scope: metadata.scope,\n };\n}\n"],"names":["InputError","ipaddr","lookup","isError"],"mappings":"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"}
@@ -4,7 +4,6 @@ var Router = require('express-promise-router');
4
4
  var OidcService = require('./OidcService.cjs.js');
5
5
  var errors = require('@backstage/errors');
6
6
  var express = require('express');
7
- var readTokenExpiration = require('./readTokenExpiration.cjs.js');
8
7
  var zod = require('zod');
9
8
  var zodValidationError = require('zod-validation-error');
10
9
  var OidcError = require('./OidcError.cjs.js');
@@ -13,6 +12,9 @@ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'defau
13
12
 
14
13
  var Router__default = /*#__PURE__*/_interopDefaultCompat(Router);
15
14
 
15
+ function ensureTrailingSlash(url) {
16
+ return url.endsWith("/") ? url : `${url}/`;
17
+ }
16
18
  const authorizeQuerySchema = zod.z.object({
17
19
  client_id: zod.z.string().min(1),
18
20
  redirect_uri: zod.z.string().url(),
@@ -56,7 +58,8 @@ function validateRequest(schema, data) {
56
58
  }
57
59
  return parseResult.data;
58
60
  }
59
- async function authenticateClient(req, oidc, bodyClientId, bodyClientSecret) {
61
+ async function authenticateClient(opts) {
62
+ const { req, oidc, bodyClientId, bodyClientSecret } = opts;
60
63
  let clientId;
61
64
  let clientSecret;
62
65
  const basicAuth = req.headers.authorization?.match(/^Basic[ ]+([^\s]+)$/i);
@@ -148,7 +151,10 @@ class OidcRouter {
148
151
  const dcrEnabled = this.config.getOptionalBoolean(
149
152
  "auth.experimentalDynamicClientRegistration.enabled"
150
153
  );
151
- if (dcrEnabled) {
154
+ const cimdEnabled = this.config.getOptionalBoolean(
155
+ "auth.experimentalClientIdMetadataDocuments.enabled"
156
+ );
157
+ if (dcrEnabled || cimdEnabled) {
152
158
  router.get("/v1/authorize", async (req, res) => {
153
159
  const {
154
160
  client_id: clientId,
@@ -273,7 +279,6 @@ class OidcRouter {
273
279
  client_id: bodyClientId,
274
280
  client_secret: bodyClientSecret
275
281
  } = validateRequest(tokenRequestBodySchema, req.body);
276
- const expiresIn = readTokenExpiration.readDcrTokenExpiration(this.config);
277
282
  try {
278
283
  if (grantType === "authorization_code") {
279
284
  if (!code || !redirectUri) {
@@ -287,8 +292,7 @@ class OidcRouter {
287
292
  code,
288
293
  redirectUri,
289
294
  codeVerifier,
290
- grantType,
291
- expiresIn
295
+ grantType
292
296
  });
293
297
  return res.json({
294
298
  access_token: result.accessToken,
@@ -312,12 +316,12 @@ class OidcRouter {
312
316
  const hasCredentials = req.headers.authorization?.match(/^Basic[ ]+([^\s]+)$/i) || bodyClientId && bodyClientSecret;
313
317
  let authenticatedClientId;
314
318
  if (hasCredentials) {
315
- const { clientId: authedId } = await authenticateClient(
319
+ const { clientId: authedId } = await authenticateClient({
316
320
  req,
317
- this.oidc,
321
+ oidc: this.oidc,
318
322
  bodyClientId,
319
323
  bodyClientSecret
320
- );
324
+ });
321
325
  authenticatedClientId = authedId;
322
326
  }
323
327
  const result = await this.oidc.refreshAccessToken({
@@ -377,12 +381,12 @@ class OidcRouter {
377
381
  client_id: bodyClientId,
378
382
  client_secret: bodyClientSecret
379
383
  } = validateRequest(revokeRequestBodySchema, req.body ?? {});
380
- await authenticateClient(
384
+ await authenticateClient({
381
385
  req,
382
- this.oidc,
386
+ oidc: this.oidc,
383
387
  bodyClientId,
384
388
  bodyClientSecret
385
- );
389
+ });
386
390
  try {
387
391
  await this.oidc.revokeRefreshToken(token);
388
392
  } catch (e) {
@@ -398,12 +402,6 @@ class OidcRouter {
398
402
  return router;
399
403
  }
400
404
  }
401
- function ensureTrailingSlash(appUrl) {
402
- if (appUrl.endsWith("/")) {
403
- return appUrl;
404
- }
405
- return `${appUrl}/`;
406
- }
407
405
 
408
406
  exports.OidcRouter = OidcRouter;
409
407
  //# sourceMappingURL=OidcRouter.cjs.js.map
@@ -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 { 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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"}
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 { z } from 'zod';\nimport { fromZodError } from 'zod-validation-error';\nimport { OidcError } from './OidcError';\n\nfunction ensureTrailingSlash(url: string): string {\n return url.endsWith('/') ? url : `${url}/`;\n}\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(opts: {\n req: { headers: { authorization?: string } };\n oidc: OidcService;\n bodyClientId?: string;\n bodyClientSecret?: string;\n}): Promise<{ clientId: string; clientSecret: string }> {\n const { req, oidc, bodyClientId, bodyClientSecret } = opts;\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 const cimdEnabled = this.config.getOptionalBoolean(\n 'auth.experimentalClientIdMetadataDocuments.enabled',\n );\n\n if (dcrEnabled || cimdEnabled) {\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 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 });\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 oidc: 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 oidc: 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}\n"],"names":["z","fromZodError","OidcError","OidcService","Router","json","AuthenticationError","isError"],"mappings":"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"}
@@ -5,7 +5,7 @@ var jose = require('jose');
5
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
+ var CimdClient = require('./CimdClient.cjs.js');
9
9
 
10
10
  function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
11
11
 
@@ -44,6 +44,7 @@ class OidcService {
44
44
  const dcrEnabled = this.config.getOptionalBoolean(
45
45
  "auth.experimentalDynamicClientRegistration.enabled"
46
46
  );
47
+ const { enabled: cimdEnabled } = this.getCimdConfig();
47
48
  return {
48
49
  issuer: this.baseUrl,
49
50
  token_endpoint: `${this.baseUrl}/v1/token`,
@@ -69,7 +70,8 @@ class OidcService {
69
70
  ],
70
71
  token_endpoint_auth_methods_supported: [
71
72
  "client_secret_basic",
72
- "client_secret_post"
73
+ "client_secret_post",
74
+ ...cimdEnabled ? ["none"] : []
73
75
  ],
74
76
  claims_supported: ["sub", "ent"],
75
77
  grant_types_supported: [
@@ -81,7 +83,8 @@ class OidcService {
81
83
  ...dcrEnabled && {
82
84
  registration_endpoint: `${this.baseUrl}/v1/register`,
83
85
  revocation_endpoint: `${this.baseUrl}/v1/revoke`
84
- }
86
+ },
87
+ ...cimdEnabled && { client_id_metadata_document_supported: true }
85
88
  };
86
89
  }
87
90
  async listPublicKeys() {
@@ -139,7 +142,12 @@ class OidcService {
139
142
  if (responseType !== "code") {
140
143
  throw new errors.InputError("Only authorization code flow is supported");
141
144
  }
142
- const client = await this.resolveClient(clientId, redirectUri);
145
+ const client = await this.resolveClient({ clientId, redirectUri });
146
+ if (client.requiresPkce && !codeChallenge) {
147
+ throw new errors.InputError(
148
+ "PKCE is required for public clients. Provide a code_challenge parameter."
149
+ );
150
+ }
143
151
  if (codeChallenge) {
144
152
  if (!codeChallengeMethod || !["S256", "plain"].includes(codeChallengeMethod)) {
145
153
  throw new errors.InputError("Invalid code_challenge_method");
@@ -166,31 +174,76 @@ class OidcService {
166
174
  redirectUri
167
175
  };
168
176
  }
169
- async getClientName(clientId) {
170
- const client = await this.oidc.getClient({ clientId });
171
- if (!client) {
177
+ getCimdConfig() {
178
+ return {
179
+ enabled: this.config.getOptionalBoolean(
180
+ "auth.experimentalClientIdMetadataDocuments.enabled"
181
+ ) ?? false,
182
+ allowedClientIdPatterns: this.config.getOptionalStringArray(
183
+ "auth.experimentalClientIdMetadataDocuments.allowedClientIdPatterns"
184
+ ) ?? ["*"],
185
+ allowedRedirectUriPatterns: this.config.getOptionalStringArray(
186
+ "auth.experimentalClientIdMetadataDocuments.allowedRedirectUriPatterns"
187
+ ) ?? ["*"]
188
+ };
189
+ }
190
+ async resolveClient(opts) {
191
+ let cimdUrl;
192
+ try {
193
+ cimdUrl = CimdClient.validateCimdUrl(opts.clientId);
194
+ } catch {
195
+ }
196
+ if (cimdUrl) {
197
+ return this.resolveCimdClient({ ...opts, cimdUrl });
198
+ }
199
+ return this.resolveDcrClient(opts);
200
+ }
201
+ async resolveCimdClient(opts) {
202
+ const cimd = this.getCimdConfig();
203
+ if (!cimd.enabled) {
204
+ throw new errors.InputError("Client ID metadata documents not enabled");
205
+ }
206
+ if (!cimd.allowedClientIdPatterns.some(
207
+ (pattern) => matcher__default.default.isMatch(opts.clientId, pattern)
208
+ )) {
172
209
  throw new errors.InputError("Invalid client_id");
173
210
  }
174
- return client.clientName;
211
+ const cimdClient = await CimdClient.fetchCimdMetadata({
212
+ clientId: opts.clientId,
213
+ validatedUrl: opts.cimdUrl
214
+ });
215
+ if (opts.redirectUri) {
216
+ if (!cimd.allowedRedirectUriPatterns.some(
217
+ (pattern) => matcher__default.default.isMatch(opts.redirectUri, pattern)
218
+ )) {
219
+ throw new errors.InputError("Invalid redirect_uri");
220
+ }
221
+ if (!cimdClient.redirectUris.includes(opts.redirectUri)) {
222
+ throw new errors.InputError("Redirect URI not registered");
223
+ }
224
+ }
225
+ return {
226
+ clientName: cimdClient.clientName,
227
+ redirectUris: cimdClient.redirectUris,
228
+ requiresPkce: true
229
+ };
175
230
  }
176
- async resolveClient(clientId, redirectUri) {
177
- const client = await this.oidc.getClient({ clientId });
231
+ async resolveDcrClient(opts) {
232
+ const client = await this.oidc.getClient({ clientId: opts.clientId });
178
233
  if (!client) {
179
234
  throw new errors.InputError("Invalid client_id");
180
235
  }
181
- if (!client.redirectUris.includes(redirectUri)) {
236
+ if (opts.redirectUri && !client.redirectUris.includes(opts.redirectUri)) {
182
237
  throw new errors.InputError("Invalid redirect_uri");
183
238
  }
184
239
  return {
185
240
  clientName: client.clientName,
186
- redirectUris: client.redirectUris
241
+ redirectUris: client.redirectUris,
242
+ requiresPkce: false
187
243
  };
188
244
  }
189
- async approveAuthorizationSession(opts) {
190
- const { sessionId, userEntityRef } = opts;
191
- const session = await this.oidc.getAuthorizationSession({
192
- id: sessionId
193
- });
245
+ async getValidPendingSession(sessionId) {
246
+ const session = await this.oidc.getAuthorizationSession({ id: sessionId });
194
247
  if (!session) {
195
248
  throw new errors.NotFoundError("Invalid authorization session");
196
249
  }
@@ -200,6 +253,11 @@ class OidcService {
200
253
  if (session.status !== "pending") {
201
254
  throw new errors.NotFoundError("Authorization session not found or expired");
202
255
  }
256
+ return session;
257
+ }
258
+ async approveAuthorizationSession(opts) {
259
+ const { sessionId, userEntityRef } = opts;
260
+ const session = await this.getValidPendingSession(sessionId);
203
261
  await this.oidc.updateAuthorizationSession({
204
262
  id: session.id,
205
263
  userEntityRef,
@@ -222,19 +280,10 @@ class OidcService {
222
280
  };
223
281
  }
224
282
  async getAuthorizationSession(opts) {
225
- const session = await this.oidc.getAuthorizationSession({
226
- id: opts.sessionId
283
+ const session = await this.getValidPendingSession(opts.sessionId);
284
+ const { clientName } = await this.resolveClient({
285
+ clientId: session.clientId
227
286
  });
228
- if (!session) {
229
- throw new errors.NotFoundError("Invalid authorization session");
230
- }
231
- if (luxon.DateTime.fromJSDate(session.expiresAt) < luxon.DateTime.now()) {
232
- throw new errors.InputError("Authorization session expired");
233
- }
234
- if (session.status !== "pending") {
235
- throw new errors.NotFoundError("Authorization session not found or expired");
236
- }
237
- const clientName = await this.getClientName(session.clientId);
238
287
  return {
239
288
  id: session.id,
240
289
  clientId: session.clientId,
@@ -252,18 +301,7 @@ class OidcService {
252
301
  }
253
302
  async rejectAuthorizationSession(opts) {
254
303
  const { sessionId, userEntityRef } = opts;
255
- const session = await this.oidc.getAuthorizationSession({
256
- id: sessionId
257
- });
258
- if (!session) {
259
- throw new errors.NotFoundError("Invalid authorization session");
260
- }
261
- if (luxon.DateTime.fromJSDate(session.expiresAt) < luxon.DateTime.now()) {
262
- throw new errors.InputError("Authorization session expired");
263
- }
264
- if (session.status !== "pending") {
265
- throw new errors.NotFoundError("Authorization session not found or expired");
266
- }
304
+ const session = await this.getValidPendingSession(sessionId);
267
305
  await this.oidc.updateAuthorizationSession({
268
306
  id: session.id,
269
307
  status: "rejected",
@@ -271,7 +309,7 @@ class OidcService {
271
309
  });
272
310
  }
273
311
  async exchangeCodeForToken(params) {
274
- const { code, redirectUri, codeVerifier, grantType, expiresIn } = params;
312
+ const { code, redirectUri, codeVerifier, grantType } = params;
275
313
  if (grantType !== "authorization_code") {
276
314
  throw new errors.InputError("Unsupported grant type");
277
315
  }
@@ -304,11 +342,11 @@ class OidcService {
304
342
  if (!codeVerifier) {
305
343
  throw new errors.AuthenticationError("Code verifier required for PKCE");
306
344
  }
307
- if (!this.verifyPkce(
308
- session.codeChallenge,
345
+ if (!this.verifyPkce({
346
+ codeChallenge: session.codeChallenge,
309
347
  codeVerifier,
310
- session.codeChallengeMethod
311
- )) {
348
+ method: session.codeChallengeMethod
349
+ })) {
312
350
  throw new errors.AuthenticationError("Invalid code verifier");
313
351
  }
314
352
  }
@@ -332,7 +370,7 @@ class OidcService {
332
370
  return {
333
371
  accessToken: token,
334
372
  tokenType: "Bearer",
335
- expiresIn,
373
+ expiresIn: 3600,
336
374
  idToken: token,
337
375
  scope: session.scope || "openid",
338
376
  refreshToken
@@ -347,11 +385,10 @@ class OidcService {
347
385
  tokenIssuer: this.tokenIssuer,
348
386
  clientId: params.clientId
349
387
  });
350
- const expiresIn = readTokenExpiration.readDcrTokenExpiration(this.config);
351
388
  return {
352
389
  accessToken,
353
390
  tokenType: "Bearer",
354
- expiresIn,
391
+ expiresIn: 3600,
355
392
  refreshToken
356
393
  };
357
394
  }
@@ -380,14 +417,13 @@ class OidcService {
380
417
  }
381
418
  await this.offlineAccess.revokeRefreshToken(token);
382
419
  }
383
- verifyPkce(codeChallenge, codeVerifier, method) {
384
- if (!method || method === "plain") {
385
- return codeChallenge === codeVerifier;
386
- }
387
- if (method === "S256") {
388
- const hash = crypto__default.default.createHash("sha256").update(codeVerifier).digest();
389
- const base64urlHash = hash.toString("base64url");
390
- return codeChallenge === base64urlHash;
420
+ verifyPkce(opts) {
421
+ if (!opts.method || opts.method === "plain") {
422
+ return opts.codeChallenge === opts.codeVerifier;
423
+ }
424
+ if (opts.method === "S256") {
425
+ const hash = crypto__default.default.createHash("sha256").update(opts.codeVerifier).digest("base64url");
426
+ return opts.codeChallenge === hash;
391
427
  }
392
428
  return false;
393
429
  }
@@ -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 '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":"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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 { validateCimdUrl, fetchCimdMetadata } from './CimdClient';\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 const { enabled: cimdEnabled } = this.getCimdConfig();\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 ...(cimdEnabled ? ['none'] : []),\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 ...(cimdEnabled && { client_id_metadata_document_supported: true }),\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 (client.requiresPkce && !codeChallenge) {\n throw new InputError(\n 'PKCE is required for public clients. Provide a code_challenge parameter.',\n );\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 private getCimdConfig() {\n return {\n enabled:\n this.config.getOptionalBoolean(\n 'auth.experimentalClientIdMetadataDocuments.enabled',\n ) ?? false,\n allowedClientIdPatterns: this.config.getOptionalStringArray(\n 'auth.experimentalClientIdMetadataDocuments.allowedClientIdPatterns',\n ) ?? ['*'],\n allowedRedirectUriPatterns: this.config.getOptionalStringArray(\n 'auth.experimentalClientIdMetadataDocuments.allowedRedirectUriPatterns',\n ) ?? ['*'],\n };\n }\n\n private async resolveClient(opts: {\n clientId: string;\n redirectUri?: string;\n }) {\n let cimdUrl: URL | undefined;\n try {\n cimdUrl = validateCimdUrl(opts.clientId);\n } catch {\n // Not a valid CIMD URL, fall through to DCR\n }\n\n if (cimdUrl) {\n return this.resolveCimdClient({ ...opts, cimdUrl });\n }\n return this.resolveDcrClient(opts);\n }\n\n private async resolveCimdClient(opts: {\n clientId: string;\n cimdUrl: URL;\n redirectUri?: string;\n }) {\n const cimd = this.getCimdConfig();\n\n if (!cimd.enabled) {\n throw new InputError('Client ID metadata documents not enabled');\n }\n\n if (\n !cimd.allowedClientIdPatterns.some(pattern =>\n matcher.isMatch(opts.clientId, pattern),\n )\n ) {\n throw new InputError('Invalid client_id');\n }\n\n const cimdClient = await fetchCimdMetadata({\n clientId: opts.clientId,\n validatedUrl: opts.cimdUrl,\n });\n\n if (opts.redirectUri) {\n if (\n !cimd.allowedRedirectUriPatterns.some(pattern =>\n matcher.isMatch(opts.redirectUri!, pattern),\n )\n ) {\n throw new InputError('Invalid redirect_uri');\n }\n\n if (!cimdClient.redirectUris.includes(opts.redirectUri)) {\n throw new InputError('Redirect URI not registered');\n }\n }\n\n return {\n clientName: cimdClient.clientName,\n redirectUris: cimdClient.redirectUris,\n requiresPkce: true,\n };\n }\n\n private async resolveDcrClient(opts: {\n clientId: string;\n redirectUri?: string;\n }) {\n const client = await this.oidc.getClient({ clientId: opts.clientId });\n if (!client) {\n throw new InputError('Invalid client_id');\n }\n\n if (opts.redirectUri && !client.redirectUris.includes(opts.redirectUri)) {\n throw new InputError('Invalid redirect_uri');\n }\n\n return {\n clientName: client.clientName,\n redirectUris: client.redirectUris,\n requiresPkce: false,\n };\n }\n\n private async getValidPendingSession(sessionId: string) {\n const session = await this.oidc.getAuthorizationSession({ id: sessionId });\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 return session;\n }\n\n public async approveAuthorizationSession(opts: {\n sessionId: string;\n userEntityRef: string;\n }) {\n const { sessionId, userEntityRef } = opts;\n const session = await this.getValidPendingSession(sessionId);\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.getValidPendingSession(opts.sessionId);\n const { clientName } = await this.resolveClient({\n clientId: session.clientId,\n });\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 const session = await this.getValidPendingSession(sessionId);\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 }) {\n const { code, redirectUri, codeVerifier, grantType } = 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 codeChallenge: session.codeChallenge,\n codeVerifier,\n method: 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: 3600,\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 return {\n accessToken,\n tokenType: 'Bearer',\n expiresIn: 3600,\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(opts: {\n codeChallenge: string;\n codeVerifier: string;\n method?: string;\n }): boolean {\n if (!opts.method || opts.method === 'plain') {\n return opts.codeChallenge === opts.codeVerifier;\n }\n\n if (opts.method === 'S256') {\n const hash = crypto\n .createHash('sha256')\n .update(opts.codeVerifier)\n .digest('base64url');\n return opts.codeChallenge === hash;\n }\n\n return false;\n }\n}\n"],"names":["InputError","decodeJwt","crypto","matcher","DateTime","validateCimdUrl","fetchCimdMetadata","NotFoundError","AuthenticationError"],"mappings":"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"}
@@ -6,39 +6,22 @@ var types = require('@backstage/types');
6
6
  const TOKEN_EXP_DEFAULT_S = 3600;
7
7
  const TOKEN_EXP_MIN_S = 600;
8
8
  const TOKEN_EXP_MAX_S = 86400;
9
- function readBackstageTokenExpiration(config) {
10
- return readTokenExpiration(config, {
11
- configKey: "auth.backstageTokenExpiration"
12
- });
13
- }
14
- function readDcrTokenExpiration(config) {
15
- return readTokenExpiration(config, {
16
- configKey: "auth.experimentalDynamicClientRegistration.tokenExpiration"
17
- });
18
- }
19
- function readTokenExpiration(config$1, options) {
20
- const {
21
- configKey,
22
- maxExpiration = TOKEN_EXP_MAX_S,
23
- minExpiration = TOKEN_EXP_MIN_S,
24
- defaultExpiration = TOKEN_EXP_DEFAULT_S
25
- } = options ?? {};
26
- if (!config$1.has(configKey)) {
27
- return defaultExpiration;
9
+ function readBackstageTokenExpiration(config$1) {
10
+ const processingIntervalKey = "auth.backstageTokenExpiration";
11
+ if (!config$1.has(processingIntervalKey)) {
12
+ return TOKEN_EXP_DEFAULT_S;
28
13
  }
29
14
  const duration = config.readDurationFromConfig(config$1, {
30
- key: configKey
15
+ key: processingIntervalKey
31
16
  });
32
17
  const durationS = Math.round(types.durationToMilliseconds(duration) / 1e3);
33
- if (durationS < minExpiration) {
34
- return minExpiration;
35
- } else if (durationS > maxExpiration) {
36
- return maxExpiration;
18
+ if (durationS < TOKEN_EXP_MIN_S) {
19
+ return TOKEN_EXP_MIN_S;
20
+ } else if (durationS > TOKEN_EXP_MAX_S) {
21
+ return TOKEN_EXP_MAX_S;
37
22
  }
38
23
  return durationS;
39
24
  }
40
25
 
41
26
  exports.readBackstageTokenExpiration = readBackstageTokenExpiration;
42
- exports.readDcrTokenExpiration = readDcrTokenExpiration;
43
- exports.readTokenExpiration = readTokenExpiration;
44
27
  //# sourceMappingURL=readTokenExpiration.cjs.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"readTokenExpiration.cjs.js","sources":["../../src/service/readTokenExpiration.ts"],"sourcesContent":["/*\n * Copyright 2024 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 { RootConfigService } from '@backstage/backend-plugin-api';\nimport { readDurationFromConfig } from '@backstage/config';\nimport { durationToMilliseconds } from '@backstage/types';\n\nconst TOKEN_EXP_DEFAULT_S = 3600;\nconst TOKEN_EXP_MIN_S = 600;\nconst TOKEN_EXP_MAX_S = 86400;\n\nexport function readBackstageTokenExpiration(config: RootConfigService) {\n return readTokenExpiration(config, {\n configKey: 'auth.backstageTokenExpiration',\n });\n}\n\nexport function readDcrTokenExpiration(config: RootConfigService) {\n return readTokenExpiration(config, {\n configKey: 'auth.experimentalDynamicClientRegistration.tokenExpiration',\n });\n}\n\nexport function readTokenExpiration(\n config: RootConfigService,\n options: {\n configKey: string;\n maxExpiration?: number;\n minExpiration?: number;\n defaultExpiration?: number;\n },\n): number {\n const {\n configKey,\n maxExpiration = TOKEN_EXP_MAX_S,\n minExpiration = TOKEN_EXP_MIN_S,\n defaultExpiration = TOKEN_EXP_DEFAULT_S,\n } = options ?? {};\n if (!config.has(configKey)) {\n return defaultExpiration;\n }\n\n const duration = readDurationFromConfig(config, {\n key: configKey,\n });\n\n const durationS = Math.round(durationToMilliseconds(duration) / 1000);\n\n if (durationS < minExpiration) {\n return minExpiration;\n } else if (durationS > maxExpiration) {\n return maxExpiration;\n }\n return durationS;\n}\n"],"names":["config","readDurationFromConfig","durationToMilliseconds"],"mappings":";;;;;AAoBA,MAAM,mBAAA,GAAsB,IAAA;AAC5B,MAAM,eAAA,GAAkB,GAAA;AACxB,MAAM,eAAA,GAAkB,KAAA;AAEjB,SAAS,6BAA6B,MAAA,EAA2B;AACtE,EAAA,OAAO,oBAAoB,MAAA,EAAQ;AAAA,IACjC,SAAA,EAAW;AAAA,GACZ,CAAA;AACH;AAEO,SAAS,uBAAuB,MAAA,EAA2B;AAChE,EAAA,OAAO,oBAAoB,MAAA,EAAQ;AAAA,IACjC,SAAA,EAAW;AAAA,GACZ,CAAA;AACH;AAEO,SAAS,mBAAA,CACdA,UACA,OAAA,EAMQ;AACR,EAAA,MAAM;AAAA,IACJ,SAAA;AAAA,IACA,aAAA,GAAgB,eAAA;AAAA,IAChB,aAAA,GAAgB,eAAA;AAAA,IAChB,iBAAA,GAAoB;AAAA,GACtB,GAAI,WAAW,EAAC;AAChB,EAAA,IAAI,CAACA,QAAA,CAAO,GAAA,CAAI,SAAS,CAAA,EAAG;AAC1B,IAAA,OAAO,iBAAA;AAAA,EACT;AAEA,EAAA,MAAM,QAAA,GAAWC,8BAAuBD,QAAA,EAAQ;AAAA,IAC9C,GAAA,EAAK;AAAA,GACN,CAAA;AAED,EAAA,MAAM,YAAY,IAAA,CAAK,KAAA,CAAME,4BAAA,CAAuB,QAAQ,IAAI,GAAI,CAAA;AAEpE,EAAA,IAAI,YAAY,aAAA,EAAe;AAC7B,IAAA,OAAO,aAAA;AAAA,EACT,CAAA,MAAA,IAAW,YAAY,aAAA,EAAe;AACpC,IAAA,OAAO,aAAA;AAAA,EACT;AACA,EAAA,OAAO,SAAA;AACT;;;;;;"}
1
+ {"version":3,"file":"readTokenExpiration.cjs.js","sources":["../../src/service/readTokenExpiration.ts"],"sourcesContent":["/*\n * Copyright 2024 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 { RootConfigService } from '@backstage/backend-plugin-api';\nimport { readDurationFromConfig } from '@backstage/config';\nimport { durationToMilliseconds } from '@backstage/types';\n\nconst TOKEN_EXP_DEFAULT_S = 3600;\nconst TOKEN_EXP_MIN_S = 600;\nconst TOKEN_EXP_MAX_S = 86400;\n\nexport function readBackstageTokenExpiration(config: RootConfigService) {\n const processingIntervalKey = 'auth.backstageTokenExpiration';\n\n if (!config.has(processingIntervalKey)) {\n return TOKEN_EXP_DEFAULT_S;\n }\n\n const duration = readDurationFromConfig(config, {\n key: processingIntervalKey,\n });\n\n const durationS = Math.round(durationToMilliseconds(duration) / 1000);\n\n if (durationS < TOKEN_EXP_MIN_S) {\n return TOKEN_EXP_MIN_S;\n } else if (durationS > TOKEN_EXP_MAX_S) {\n return TOKEN_EXP_MAX_S;\n }\n return durationS;\n}\n"],"names":["config","readDurationFromConfig","durationToMilliseconds"],"mappings":";;;;;AAoBA,MAAM,mBAAA,GAAsB,IAAA;AAC5B,MAAM,eAAA,GAAkB,GAAA;AACxB,MAAM,eAAA,GAAkB,KAAA;AAEjB,SAAS,6BAA6BA,QAAA,EAA2B;AACtE,EAAA,MAAM,qBAAA,GAAwB,+BAAA;AAE9B,EAAA,IAAI,CAACA,QAAA,CAAO,GAAA,CAAI,qBAAqB,CAAA,EAAG;AACtC,IAAA,OAAO,mBAAA;AAAA,EACT;AAEA,EAAA,MAAM,QAAA,GAAWC,8BAAuBD,QAAA,EAAQ;AAAA,IAC9C,GAAA,EAAK;AAAA,GACN,CAAA;AAED,EAAA,MAAM,YAAY,IAAA,CAAK,KAAA,CAAME,4BAAA,CAAuB,QAAQ,IAAI,GAAI,CAAA;AAEpE,EAAA,IAAI,YAAY,eAAA,EAAiB;AAC/B,IAAA,OAAO,eAAA;AAAA,EACT,CAAA,MAAA,IAAW,YAAY,eAAA,EAAiB;AACtC,IAAA,OAAO,eAAA;AAAA,EACT;AACA,EAAA,OAAO,SAAA;AACT;;;;"}
@@ -52,31 +52,27 @@ async function createRouter(options) {
52
52
  const omitClaimsFromToken = config.getOptionalBoolean(
53
53
  "auth.omitIdentityTokenOwnershipClaim"
54
54
  ) ? ["ent"] : [];
55
- const createTokenIssuer = (opts) => {
56
- if (keyStore instanceof StaticKeyStore.StaticKeyStore) {
57
- return new StaticTokenIssuer.StaticTokenIssuer(
58
- {
59
- logger: opts.logger,
60
- issuer: authUrl,
61
- sessionExpirationSeconds: opts.expirationSeconds,
62
- omitClaimsFromToken
63
- },
64
- keyStore
65
- );
66
- }
67
- return new TokenFactory.TokenFactory({
55
+ let tokenIssuer;
56
+ if (keyStore instanceof StaticKeyStore.StaticKeyStore) {
57
+ tokenIssuer = new StaticTokenIssuer.StaticTokenIssuer(
58
+ {
59
+ logger: logger.child({ component: "token-factory" }),
60
+ issuer: authUrl,
61
+ sessionExpirationSeconds: backstageTokenExpiration,
62
+ omitClaimsFromToken
63
+ },
64
+ keyStore
65
+ );
66
+ } else {
67
+ tokenIssuer = new TokenFactory.TokenFactory({
68
68
  issuer: authUrl,
69
69
  keyStore,
70
- keyDurationSeconds: opts.expirationSeconds,
71
- logger: opts.logger,
70
+ keyDurationSeconds: backstageTokenExpiration,
71
+ logger: logger.child({ component: "token-factory" }),
72
72
  algorithm: tokenFactoryAlgorithm ?? config.getOptionalString("auth.identityTokenAlgorithm"),
73
73
  omitClaimsFromToken
74
74
  });
75
- };
76
- const tokenIssuer = createTokenIssuer({
77
- logger: logger.child({ component: "token-factory" }),
78
- expirationSeconds: backstageTokenExpiration
79
- });
75
+ }
80
76
  const secret = config.getOptionalString("auth.session.secret");
81
77
  if (secret) {
82
78
  router$1.use(cookieParser__default.default(secret));
@@ -110,15 +106,10 @@ async function createRouter(options) {
110
106
  auth: options.auth,
111
107
  userInfo
112
108
  });
113
- const dcrTokenExpiration = readTokenExpiration.readDcrTokenExpiration(config);
114
- const oidcTokenIssuer = createTokenIssuer({
115
- logger: logger.child({ component: "oidc-token-factory" }),
116
- expirationSeconds: dcrTokenExpiration
117
- });
118
109
  const oidc = await OidcDatabase.OidcDatabase.create({ database });
119
110
  const oidcRouter = OidcRouter.OidcRouter.create({
120
111
  auth: options.auth,
121
- tokenIssuer: oidcTokenIssuer,
112
+ tokenIssuer,
122
113
  baseUrl: authUrl,
123
114
  appUrl,
124
115
  userInfo,
@@ -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';\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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8DA,eAAsB,aACpB,OAAA,EACyB;AACzB,EAAA,MAAM;AAAA,IACJ,MAAA;AAAA,IACA,MAAA;AAAA,IACA,SAAA;AAAA,IACA,QAAA,EAAU,EAAA;AAAA,IACV,qBAAA;AAAA,IACA,oBAAoB,EAAC;AAAA,IACrB;AAAA,GACF,GAAI,OAAA;AAEJ,EAAA,MAAMA,WAASC,uBAAA,EAAO;AAEtB,EAAA,MAAM,MAAA,GAAS,MAAA,CAAO,SAAA,CAAU,aAAa,CAAA;AAC7C,EAAA,MAAM,OAAA,GAAU,MAAM,SAAA,CAAU,kBAAA,CAAmB,MAAM,CAAA;AACzD,EAAA,MAAM,wBAAA,GAA2BC,iDAA6B,MAAM,CAAA;AACpE,EAAA,MAAM,QAAA,GAAWC,yBAAA,CAAa,MAAA,CAAO,EAAE,CAAA;AAEvC,EAAA,MAAM,QAAA,GAAW,MAAMC,mBAAA,CAAU,UAAA,CAAW,MAAA,EAAQ;AAAA,IAClD,MAAA;AAAA,IACA;AAAA,GACD,CAAA;AAED,EAAA,MAAM,QAAA,GAAW,MAAMC,iCAAA,CAAiB,MAAA,CAAO;AAAA,IAC7C;AAAA,GACD,CAAA;AAED,EAAA,MAAM,sBAAsB,MAAA,CAAO,kBAAA;AAAA,IACjC;AAAA,GACF,GACI,CAAC,KAAK,CAAA,GACN,EAAC;AAEL,EAAA,MAAM,iBAAA,GAAoB,CAAC,IAAA,KAGrB;AACJ,IAAA,IAAI,oBAAoBC,6BAAA,EAAgB;AACtC,MAAA,OAAO,IAAIC,mCAAA;AAAA,QACT;AAAA,UACE,QAAQ,IAAA,CAAK,MAAA;AAAA,UACb,MAAA,EAAQ,OAAA;AAAA,UACR,0BAA0B,IAAA,CAAK,iBAAA;AAAA,UAC/B;AAAA,SACF;AAAA,QACA;AAAA,OACF;AAAA,IACF;AACA,IAAA,OAAO,IAAIC,yBAAA,CAAa;AAAA,MACtB,MAAA,EAAQ,OAAA;AAAA,MACR,QAAA;AAAA,MACA,oBAAoB,IAAA,CAAK,iBAAA;AAAA,MACzB,QAAQ,IAAA,CAAK,MAAA;AAAA,MACb,SAAA,EACE,qBAAA,IACA,MAAA,CAAO,iBAAA,CAAkB,6BAA6B,CAAA;AAAA,MACxD;AAAA,KACD,CAAA;AAAA,EACH,CAAA;AAEA,EAAA,MAAM,cAAc,iBAAA,CAAkB;AAAA,IACpC,QAAQ,MAAA,CAAO,KAAA,CAAM,EAAE,SAAA,EAAW,iBAAiB,CAAA;AAAA,IACnD,iBAAA,EAAmB;AAAA,GACpB,CAAA;AAED,EAAA,MAAM,MAAA,GAAS,MAAA,CAAO,iBAAA,CAAkB,qBAAqB,CAAA;AAC7D,EAAA,IAAI,MAAA,EAAQ;AACV,IAAAR,QAAA,CAAO,GAAA,CAAIS,6BAAA,CAAa,MAAM,CAAC,CAAA;AAC/B,IAAA,MAAM,gBAAA,GAAmB,OAAA,CAAQ,UAAA,CAAW,OAAO,CAAA;AACnD,IAAA,MAAM,gBAAA,GAAmBC,oCAAmBC,wBAAO,CAAA;AACnD,IAAAX,QAAA,CAAO,GAAA;AAAA,MACLW,wBAAA,CAAQ;AAAA,QACN,MAAA;AAAA,QACA,iBAAA,EAAmB,KAAA;AAAA,QACnB,MAAA,EAAQ,KAAA;AAAA,QACR,MAAA,EAAQ,EAAE,MAAA,EAAQ,gBAAA,GAAmB,SAAS,KAAA,EAAM;AAAA,QACpD,KAAA,EAAO,IAAI,gBAAA,CAAiB;AAAA,UAC1B,WAAA,EAAa,KAAA;AAAA,UACb,IAAA,EAAM,MAAM,QAAA,CAAS,GAAA;AAAI,SAC1B;AAAA,OACF;AAAA,KACH;AACA,IAAAX,QAAA,CAAO,GAAA,CAAIY,yBAAA,CAAS,UAAA,EAAY,CAAA;AAChC,IAAAZ,QAAA,CAAO,GAAA,CAAIY,yBAAA,CAAS,OAAA,EAAS,CAAA;AAAA,EAC/B,CAAA,MAAO;AACL,IAAAZ,QAAA,CAAO,GAAA,CAAIS,+BAAc,CAAA;AAAA,EAC3B;AAEA,EAAAT,QAAA,CAAO,IAAIa,wBAAA,CAAQ,UAAA,CAAW,EAAE,QAAA,EAAU,KAAA,EAAO,CAAC,CAAA;AAClD,EAAAb,QAAA,CAAO,GAAA,CAAIa,wBAAA,CAAQ,IAAA,EAAM,CAAA;AAEzB,EAAAC,0BAAA,CAAoBd,QAAA,EAAQ;AAAA,IAC1B,SAAA,EAAW,iBAAA;AAAA,IACX,MAAA;AAAA,IACA,OAAA,EAAS,OAAA;AAAA,IACT,WAAA;AAAA,IACA,GAAG,OAAA;AAAA,IACH,MAAM,OAAA,CAAQ,IAAA;AAAA,IACd;AAAA,GACD,CAAA;AAED,EAAA,MAAM,kBAAA,GAAqBe,2CAAuB,MAAM,CAAA;AAExD,EAAA,MAAM,kBAAkB,iBAAA,CAAkB;AAAA,IACxC,QAAQ,MAAA,CAAO,KAAA,CAAM,EAAE,SAAA,EAAW,sBAAsB,CAAA;AAAA,IACxD,iBAAA,EAAmB;AAAA,GACpB,CAAA;AAED,EAAA,MAAM,OAAO,MAAMC,yBAAA,CAAa,MAAA,CAAO,EAAE,UAAU,CAAA;AAEnD,EAAA,MAAM,UAAA,GAAaC,sBAAW,MAAA,CAAO;AAAA,IACnC,MAAM,OAAA,CAAQ,IAAA;AAAA,IACd,WAAA,EAAa,eAAA;AAAA,IACb,OAAA,EAAS,OAAA;AAAA,IACT,MAAA;AAAA,IACA,QAAA;AAAA,IACA,IAAA;AAAA,IACA,MAAA;AAAA,IACA,QAAA;AAAA,IACA,MAAA;AAAA,IACA,eAAe,OAAA,CAAQ;AAAA,GACxB,CAAA;AAED,EAAAjB,QAAA,CAAO,GAAA,CAAI,UAAA,CAAW,SAAA,EAAW,CAAA;AAGjC,EAAAA,QAAA,CAAO,GAAA,CAAI,eAAe,CAAA,GAAA,KAAO;AAC/B,IAAA,MAAM,EAAE,QAAA,EAAS,GAAI,GAAA,CAAI,MAAA;AACzB,IAAA,MAAM,IAAIkB,oBAAA,CAAc,CAAA,uBAAA,EAA0B,QAAQ,CAAA,CAAA,CAAG,CAAA;AAAA,EAC/D,CAAC,CAAA;AAED,EAAA,OAAOlB,QAAA;AACT;;;;"}
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 { readBackstageTokenExpiration } from './readTokenExpiration';\nimport { TokenIssuer } from '../identity/types';\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 let tokenIssuer: TokenIssuer;\n if (keyStore instanceof StaticKeyStore) {\n tokenIssuer = new StaticTokenIssuer(\n {\n logger: logger.child({ component: 'token-factory' }),\n issuer: authUrl,\n sessionExpirationSeconds: backstageTokenExpiration,\n omitClaimsFromToken,\n },\n keyStore as StaticKeyStore,\n );\n } else {\n tokenIssuer = new TokenFactory({\n issuer: authUrl,\n keyStore,\n keyDurationSeconds: backstageTokenExpiration,\n logger: logger.child({ component: 'token-factory' }),\n algorithm:\n tokenFactoryAlgorithm ??\n config.getOptionalString('auth.identityTokenAlgorithm'),\n omitClaimsFromToken,\n });\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 oidc = await OidcDatabase.create({ database });\n\n const oidcRouter = OidcRouter.create({\n auth: options.auth,\n tokenIssuer,\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","OidcDatabase","OidcRouter","NotFoundError"],"mappings":"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"}
@@ -0,0 +1,44 @@
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
+ * Drop the foreign key constraint on oauth_authorization_sessions.client_id
21
+ * to allow CIMD (Client ID Metadata Document) clients which are not stored
22
+ * in the oidc_clients table.
23
+ *
24
+ * @param {import('knex').Knex} knex
25
+ */
26
+ exports.up = async function up(knex) {
27
+ await knex.schema.alterTable('oauth_authorization_sessions', table => {
28
+ table.dropForeign(['client_id']);
29
+ });
30
+ };
31
+
32
+ /**
33
+ * @param {import('knex').Knex} knex
34
+ */
35
+ exports.down = async function down(knex) {
36
+ // Delete sessions with CIMD client_ids (not in oidc_clients) before re-adding FK
37
+ await knex('oauth_authorization_sessions')
38
+ .whereNotIn('client_id', knex('oidc_clients').select('client_id'))
39
+ .delete();
40
+
41
+ await knex.schema.alterTable('oauth_authorization_sessions', table => {
42
+ table.foreign('client_id').references('client_id').inTable('oidc_clients');
43
+ });
44
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage/plugin-auth-backend",
3
- "version": "0.27.0-next.1",
3
+ "version": "0.27.0",
4
4
  "description": "A Backstage backend plugin that handles authentication",
5
5
  "backstage": {
6
6
  "role": "backend-plugin",
@@ -47,19 +47,20 @@
47
47
  "test": "backstage-cli package test"
48
48
  },
49
49
  "dependencies": {
50
- "@backstage/backend-plugin-api": "1.7.0-next.1",
51
- "@backstage/catalog-model": "1.7.6",
52
- "@backstage/config": "1.3.6",
53
- "@backstage/errors": "1.2.7",
54
- "@backstage/plugin-auth-node": "0.6.13-next.1",
55
- "@backstage/plugin-catalog-node": "2.0.0-next.1",
56
- "@backstage/types": "1.2.2",
50
+ "@backstage/backend-plugin-api": "^1.7.0",
51
+ "@backstage/catalog-model": "^1.7.6",
52
+ "@backstage/config": "^1.3.6",
53
+ "@backstage/errors": "^1.2.7",
54
+ "@backstage/plugin-auth-node": "^0.6.13",
55
+ "@backstage/plugin-catalog-node": "^2.0.0",
56
+ "@backstage/types": "^1.2.2",
57
57
  "@google-cloud/firestore": "^7.0.0",
58
58
  "connect-session-knex": "^4.0.0",
59
59
  "cookie-parser": "^1.4.5",
60
60
  "express": "^4.22.0",
61
61
  "express-promise-router": "^4.1.0",
62
62
  "express-session": "^1.17.1",
63
+ "ipaddr.js": "^2.3.0",
63
64
  "jose": "^5.0.0",
64
65
  "knex": "^3.0.0",
65
66
  "lodash": "^4.17.21",
@@ -72,15 +73,16 @@
72
73
  "zod-validation-error": "^5.0.0"
73
74
  },
74
75
  "devDependencies": {
75
- "@backstage/backend-defaults": "0.15.2-next.1",
76
- "@backstage/backend-test-utils": "1.11.0-next.1",
77
- "@backstage/cli": "0.35.4-next.2",
78
- "@backstage/plugin-auth-backend-module-google-provider": "0.3.12-next.0",
79
- "@backstage/plugin-auth-backend-module-guest-provider": "0.2.16-next.0",
76
+ "@backstage/backend-defaults": "^0.15.2",
77
+ "@backstage/backend-test-utils": "^1.11.0",
78
+ "@backstage/cli": "^0.35.4",
79
+ "@backstage/plugin-auth-backend-module-google-provider": "^0.3.12",
80
+ "@backstage/plugin-auth-backend-module-guest-provider": "^0.2.16",
80
81
  "@types/cookie-parser": "^1.4.2",
81
82
  "@types/express": "^4.17.6",
82
83
  "@types/express-session": "^1.17.2",
83
84
  "@types/passport": "^1.0.3",
85
+ "msw": "^1.0.0",
84
86
  "supertest": "^7.0.0"
85
87
  },
86
88
  "configSchema": "config.d.ts",