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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,11 +5,98 @@ var OidcService = require('./OidcService.cjs.js');
5
5
  var errors = require('@backstage/errors');
6
6
  var express = require('express');
7
7
  var readTokenExpiration = require('./readTokenExpiration.cjs.js');
8
+ var zod = require('zod');
9
+ var zodValidationError = require('zod-validation-error');
10
+ var OidcError = require('./OidcError.cjs.js');
8
11
 
9
12
  function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
10
13
 
11
14
  var Router__default = /*#__PURE__*/_interopDefaultCompat(Router);
12
15
 
16
+ const authorizeQuerySchema = zod.z.object({
17
+ client_id: zod.z.string().min(1),
18
+ redirect_uri: zod.z.string().url(),
19
+ response_type: zod.z.string().min(1),
20
+ scope: zod.z.string().optional(),
21
+ state: zod.z.string().optional(),
22
+ nonce: zod.z.string().optional(),
23
+ code_challenge: zod.z.string().optional(),
24
+ code_challenge_method: zod.z.string().optional()
25
+ });
26
+ const sessionIdParamSchema = zod.z.object({
27
+ sessionId: zod.z.string().min(1)
28
+ });
29
+ const tokenRequestBodySchema = zod.z.object({
30
+ grant_type: zod.z.string().min(1),
31
+ code: zod.z.string().optional(),
32
+ redirect_uri: zod.z.string().url().optional(),
33
+ code_verifier: zod.z.string().optional(),
34
+ refresh_token: zod.z.string().optional(),
35
+ client_id: zod.z.string().optional(),
36
+ client_secret: zod.z.string().optional()
37
+ });
38
+ const registerRequestBodySchema = zod.z.object({
39
+ client_name: zod.z.string().optional(),
40
+ redirect_uris: zod.z.array(zod.z.string().url()).min(1),
41
+ response_types: zod.z.array(zod.z.string()).optional(),
42
+ grant_types: zod.z.array(zod.z.string()).optional(),
43
+ scope: zod.z.string().optional()
44
+ });
45
+ const revokeRequestBodySchema = zod.z.object({
46
+ token: zod.z.string().min(1),
47
+ token_type_hint: zod.z.string().optional(),
48
+ client_id: zod.z.string().optional(),
49
+ client_secret: zod.z.string().optional()
50
+ });
51
+ function validateRequest(schema, data) {
52
+ const parseResult = schema.safeParse(data);
53
+ if (!parseResult.success) {
54
+ const errorMessage = zodValidationError.fromZodError(parseResult.error).message;
55
+ throw new OidcError.OidcError("invalid_request", errorMessage, 400);
56
+ }
57
+ return parseResult.data;
58
+ }
59
+ async function authenticateClient(req, oidc, bodyClientId, bodyClientSecret) {
60
+ let clientId;
61
+ let clientSecret;
62
+ const basicAuth = req.headers.authorization?.match(/^Basic[ ]+([^\s]+)$/i);
63
+ if (basicAuth) {
64
+ try {
65
+ const decoded = Buffer.from(basicAuth[1], "base64").toString("utf8");
66
+ const idx = decoded.indexOf(":");
67
+ if (idx >= 0) {
68
+ clientId = decoded.slice(0, idx);
69
+ clientSecret = decoded.slice(idx + 1);
70
+ }
71
+ } catch {
72
+ }
73
+ }
74
+ if (!clientId || !clientSecret) {
75
+ if (bodyClientId && bodyClientSecret) {
76
+ clientId = bodyClientId;
77
+ clientSecret = bodyClientSecret;
78
+ }
79
+ }
80
+ if (!clientId || !clientSecret) {
81
+ throw new OidcError.OidcError(
82
+ "invalid_client",
83
+ "Client authentication required",
84
+ 401
85
+ );
86
+ }
87
+ try {
88
+ const ok = await oidc.verifyClientCredentials({
89
+ clientId,
90
+ clientSecret
91
+ });
92
+ if (!ok) {
93
+ throw new OidcError.OidcError("invalid_client", "Invalid client credentials", 401);
94
+ }
95
+ } catch (e) {
96
+ throw OidcError.OidcError.fromError(e);
97
+ }
98
+ return { clientId, clientSecret };
99
+ }
13
100
  class OidcRouter {
14
101
  oidc;
15
102
  logger;
@@ -58,9 +145,10 @@ class OidcRouter {
58
145
  }
59
146
  res.json(userInfo);
60
147
  });
61
- if (this.config.getOptionalBoolean(
148
+ const dcrEnabled = this.config.getOptionalBoolean(
62
149
  "auth.experimentalDynamicClientRegistration.enabled"
63
- )) {
150
+ );
151
+ if (dcrEnabled) {
64
152
  router.get("/v1/authorize", async (req, res) => {
65
153
  const {
66
154
  client_id: clientId,
@@ -71,14 +159,7 @@ class OidcRouter {
71
159
  nonce,
72
160
  code_challenge: codeChallenge,
73
161
  code_challenge_method: codeChallengeMethod
74
- } = req.query;
75
- if (!clientId || !redirectUri || !responseType) {
76
- this.logger.error(`Failed to authorize: Missing required parameters`);
77
- return res.status(400).json({
78
- error: "invalid_request",
79
- error_description: "Missing required parameters: client_id, redirect_uri, response_type"
80
- });
81
- }
162
+ } = validateRequest(authorizeQuerySchema, req.query);
82
163
  try {
83
164
  const result = await this.oidc.createAuthorizationSession({
84
165
  clientId,
@@ -96,31 +177,25 @@ class OidcRouter {
96
177
  );
97
178
  return res.redirect(authSessionRedirectUrl.toString());
98
179
  } catch (error) {
99
- const errorParams = new URLSearchParams();
100
- errorParams.append(
101
- "error",
102
- errors.isError(error) ? error.name : "server_error"
103
- );
104
- errorParams.append(
105
- "error_description",
106
- errors.isError(error) ? error.message : "Unknown error"
107
- );
108
- if (state) {
109
- errorParams.append("state", state);
180
+ if (OidcError.OidcError.isOidcError(error)) {
181
+ const errorParams = new URLSearchParams();
182
+ errorParams.append("error", error.body.error);
183
+ errorParams.append(
184
+ "error_description",
185
+ error.body.error_description
186
+ );
187
+ if (state) {
188
+ errorParams.append("state", state);
189
+ }
190
+ const redirectUrl = new URL(redirectUri);
191
+ redirectUrl.search = errorParams.toString();
192
+ return res.redirect(redirectUrl.toString());
110
193
  }
111
- const redirectUrl = new URL(redirectUri);
112
- redirectUrl.search = errorParams.toString();
113
- return res.redirect(redirectUrl.toString());
194
+ throw error;
114
195
  }
115
196
  });
116
197
  router.get("/v1/sessions/:sessionId", async (req, res) => {
117
- const { sessionId } = req.params;
118
- if (!sessionId) {
119
- return res.status(400).json({
120
- error: "invalid_request",
121
- error_description: "Missing Authorization Session ID"
122
- });
123
- }
198
+ const { sessionId } = validateRequest(sessionIdParamSchema, req.params);
124
199
  try {
125
200
  const session = await this.oidc.getAuthorizationSession({
126
201
  sessionId
@@ -132,32 +207,19 @@ class OidcRouter {
132
207
  redirectUri: session.redirectUri
133
208
  });
134
209
  } catch (error) {
135
- const description = errors.isError(error) ? error.message : "Unknown error";
136
- this.logger.error(
137
- `Failed to get authorization session: ${description}`,
138
- error
139
- );
140
- return res.status(404).json({
141
- error: "not_found",
142
- error_description: description
143
- });
210
+ throw OidcError.OidcError.fromError(error);
144
211
  }
145
212
  });
146
213
  router.post("/v1/sessions/:sessionId/approve", async (req, res) => {
147
- const { sessionId } = req.params;
148
- if (!sessionId) {
149
- return res.status(400).json({
150
- error: "invalid_request",
151
- error_description: "Missing authorization session ID"
152
- });
153
- }
214
+ const { sessionId } = validateRequest(sessionIdParamSchema, req.params);
154
215
  try {
155
216
  const httpCredentials = await this.httpAuth.credentials(req);
156
217
  if (!this.auth.isPrincipal(httpCredentials, "user")) {
157
- return res.status(401).json({
158
- error: "unauthorized",
159
- error_description: "Authentication required"
160
- });
218
+ throw new OidcError.OidcError(
219
+ "access_denied",
220
+ "Authentication required",
221
+ 403
222
+ );
161
223
  }
162
224
  const { userEntityRef } = httpCredentials.principal;
163
225
  const result = await this.oidc.approveAuthorizationSession({
@@ -168,31 +230,14 @@ class OidcRouter {
168
230
  redirectUrl: result.redirectUrl
169
231
  });
170
232
  } catch (error) {
171
- const description = errors.isError(error) ? error.message : "Unknown error";
172
- this.logger.error(
173
- `Failed to approve authorization session: ${description}`,
174
- error
175
- );
176
- return res.status(400).json({
177
- error: "invalid_request",
178
- error_description: description
179
- });
233
+ throw OidcError.OidcError.fromError(error);
180
234
  }
181
235
  });
182
236
  router.post("/v1/sessions/:sessionId/reject", async (req, res) => {
183
- const { sessionId } = req.params;
184
- if (!sessionId) {
185
- return res.status(400).json({
186
- error: "invalid_request",
187
- error_description: "Missing authorization session ID"
188
- });
189
- }
237
+ const { sessionId } = validateRequest(sessionIdParamSchema, req.params);
190
238
  const httpCredentials = await this.httpAuth.credentials(req);
191
239
  if (!this.auth.isPrincipal(httpCredentials, "user")) {
192
- return res.status(401).json({
193
- error: "unauthorized",
194
- error_description: "Authentication required"
195
- });
240
+ throw new OidcError.OidcError("access_denied", "Authentication required", 403);
196
241
  }
197
242
  const { userEntityRef } = httpCredentials.principal;
198
243
  try {
@@ -215,15 +260,7 @@ class OidcRouter {
215
260
  redirectUrl: redirectUrl.toString()
216
261
  });
217
262
  } catch (error) {
218
- const description = errors.isError(error) ? error.message : "Unknown error";
219
- this.logger.error(
220
- `Failed to reject authorization session: ${description}`,
221
- error
222
- );
223
- return res.status(400).json({
224
- error: "invalid_request",
225
- error_description: description
226
- });
263
+ throw OidcError.OidcError.fromError(error);
227
264
  }
228
265
  });
229
266
  router.post("/v1/token", async (req, res) => {
@@ -231,59 +268,83 @@ class OidcRouter {
231
268
  grant_type: grantType,
232
269
  code,
233
270
  redirect_uri: redirectUri,
234
- code_verifier: codeVerifier
235
- } = req.body;
236
- if (!grantType || !code || !redirectUri) {
237
- this.logger.error(
238
- `Failed to exchange code for token: Missing required parameters`
239
- );
240
- return res.status(400).json({
241
- error: "invalid_request",
242
- error_description: "Missing required parameters"
243
- });
244
- }
271
+ code_verifier: codeVerifier,
272
+ refresh_token: refreshToken,
273
+ client_id: bodyClientId,
274
+ client_secret: bodyClientSecret
275
+ } = validateRequest(tokenRequestBodySchema, req.body);
245
276
  const expiresIn = readTokenExpiration.readDcrTokenExpiration(this.config);
246
277
  try {
247
- const result = await this.oidc.exchangeCodeForToken({
248
- code,
249
- redirectUri,
250
- codeVerifier,
251
- grantType,
252
- expiresIn
253
- });
254
- return res.json({
255
- access_token: result.accessToken,
256
- token_type: result.tokenType,
257
- expires_in: result.expiresIn,
258
- id_token: result.idToken,
259
- scope: result.scope
260
- });
261
- } catch (error) {
262
- const description = errors.isError(error) ? error.message : "Unknown error";
263
- this.logger.error(
264
- `Failed to exchange code for token: ${description}`,
265
- error
266
- );
267
- if (errors.isError(error)) {
268
- if (error.name === "AuthenticationError") {
269
- return res.status(401).json({
270
- error: "invalid_client",
271
- error_description: error.message
272
- });
278
+ if (grantType === "authorization_code") {
279
+ if (!code || !redirectUri) {
280
+ throw new OidcError.OidcError(
281
+ "invalid_request",
282
+ "Missing code or redirect_uri parameters for authorization_code grant",
283
+ 400
284
+ );
285
+ }
286
+ const result = await this.oidc.exchangeCodeForToken({
287
+ code,
288
+ redirectUri,
289
+ codeVerifier,
290
+ grantType,
291
+ expiresIn
292
+ });
293
+ return res.json({
294
+ access_token: result.accessToken,
295
+ token_type: result.tokenType,
296
+ expires_in: result.expiresIn,
297
+ id_token: result.idToken,
298
+ scope: result.scope,
299
+ ...result.refreshToken && {
300
+ refresh_token: result.refreshToken
301
+ }
302
+ });
303
+ }
304
+ if (grantType === "refresh_token") {
305
+ if (!refreshToken) {
306
+ throw new OidcError.OidcError(
307
+ "invalid_request",
308
+ "Missing refresh_token parameter for refresh_token grant",
309
+ 400
310
+ );
273
311
  }
274
- if (error.name === "InputError") {
275
- return res.status(400).json({
276
- error: "invalid_request",
277
- error_description: error.message
278
- });
312
+ const hasCredentials = req.headers.authorization?.match(/^Basic[ ]+([^\s]+)$/i) || bodyClientId && bodyClientSecret;
313
+ let authenticatedClientId;
314
+ if (hasCredentials) {
315
+ const { clientId: authedId } = await authenticateClient(
316
+ req,
317
+ this.oidc,
318
+ bodyClientId,
319
+ bodyClientSecret
320
+ );
321
+ authenticatedClientId = authedId;
279
322
  }
323
+ const result = await this.oidc.refreshAccessToken({
324
+ refreshToken,
325
+ clientId: authenticatedClientId
326
+ });
327
+ return res.json({
328
+ access_token: result.accessToken,
329
+ token_type: result.tokenType,
330
+ expires_in: result.expiresIn,
331
+ refresh_token: result.refreshToken
332
+ });
280
333
  }
281
- return res.status(500).json({
282
- error: "server_error",
283
- error_description: description
284
- });
334
+ throw new OidcError.OidcError(
335
+ "unsupported_grant_type",
336
+ `Grant type ${grantType} is not supported`,
337
+ 400
338
+ );
339
+ } catch (error) {
340
+ if (errors.isError(error) && error.name === "AuthenticationError") {
341
+ throw new OidcError.OidcError("invalid_grant", error.message, 400, error);
342
+ }
343
+ throw OidcError.OidcError.fromError(error);
285
344
  }
286
345
  });
346
+ }
347
+ if (dcrEnabled) {
287
348
  router.post("/v1/register", async (req, res) => {
288
349
  const {
289
350
  client_name: clientName,
@@ -291,37 +352,49 @@ class OidcRouter {
291
352
  response_types: responseTypes,
292
353
  grant_types: grantTypes,
293
354
  scope
294
- } = req.body;
295
- if (!redirectUris?.length) {
296
- res.status(400).json({
297
- error: "invalid_request",
298
- error_description: "redirect_uris is required"
299
- });
300
- return;
301
- }
355
+ } = validateRequest(registerRequestBodySchema, req.body);
302
356
  try {
303
357
  const client = await this.oidc.registerClient({
304
- clientName,
358
+ clientName: clientName ?? "Backstage CLI",
305
359
  redirectUris,
306
360
  responseTypes,
307
361
  grantTypes,
308
362
  scope
309
363
  });
310
- res.status(201).json({
364
+ return res.status(201).json({
311
365
  client_id: client.clientId,
312
366
  redirect_uris: client.redirectUris,
313
367
  client_secret: client.clientSecret
314
368
  });
315
369
  } catch (e) {
316
- const description = errors.isError(e) ? e.message : "Unknown error";
317
- this.logger.error(`Failed to register client: ${description}`, e);
318
- res.status(500).json({
319
- error: "server_error",
320
- error_description: `Failed to register client: ${description}`
321
- });
370
+ throw OidcError.OidcError.fromError(e);
371
+ }
372
+ });
373
+ router.post("/v1/revoke", async (req, res) => {
374
+ try {
375
+ const {
376
+ token,
377
+ client_id: bodyClientId,
378
+ client_secret: bodyClientSecret
379
+ } = validateRequest(revokeRequestBodySchema, req.body ?? {});
380
+ await authenticateClient(
381
+ req,
382
+ this.oidc,
383
+ bodyClientId,
384
+ bodyClientSecret
385
+ );
386
+ try {
387
+ await this.oidc.revokeRefreshToken(token);
388
+ } catch (e) {
389
+ this.logger.debug("Failed to revoke token", e);
390
+ }
391
+ return res.status(200).send("");
392
+ } catch (e) {
393
+ throw OidcError.OidcError.fromError(e);
322
394
  }
323
395
  });
324
396
  }
397
+ router.use(OidcError.OidcError.middleware(this.logger));
325
398
  return router;
326
399
  }
327
400
  }
@@ -1 +1 @@
1
- {"version":3,"file":"OidcRouter.cjs.js","sources":["../../src/service/OidcRouter.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport Router from 'express-promise-router';\nimport { OidcService } from './OidcService';\nimport { AuthenticationError, isError } from '@backstage/errors';\nimport {\n AuthService,\n HttpAuthService,\n LoggerService,\n RootConfigService,\n} from '@backstage/backend-plugin-api';\nimport { TokenIssuer } from '../identity/types';\nimport { UserInfoDatabase } from '../database/UserInfoDatabase';\nimport { OidcDatabase } from '../database/OidcDatabase';\nimport { json } from 'express';\nimport { readDcrTokenExpiration } from './readTokenExpiration.ts';\n\nexport class OidcRouter {\n private readonly oidc: OidcService;\n private readonly logger: LoggerService;\n private readonly auth: AuthService;\n private readonly appUrl: string;\n private readonly httpAuth: HttpAuthService;\n private readonly config: RootConfigService;\n\n private constructor(\n oidc: OidcService,\n logger: LoggerService,\n auth: AuthService,\n appUrl: string,\n httpAuth: HttpAuthService,\n config: RootConfigService,\n ) {\n this.oidc = oidc;\n this.logger = logger;\n this.auth = auth;\n this.appUrl = appUrl;\n this.httpAuth = httpAuth;\n this.config = config;\n }\n\n static create(options: {\n auth: AuthService;\n tokenIssuer: TokenIssuer;\n baseUrl: string;\n appUrl: string;\n logger: LoggerService;\n userInfo: UserInfoDatabase;\n oidc: OidcDatabase;\n httpAuth: HttpAuthService;\n config: RootConfigService;\n }) {\n return new OidcRouter(\n OidcService.create(options),\n options.logger,\n options.auth,\n options.appUrl,\n options.httpAuth,\n options.config,\n );\n }\n\n public getRouter() {\n const router = Router();\n\n router.use(json());\n\n // OpenID Provider Configuration endpoint\n // https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig\n // Returns the OpenID Provider Configuration document containing metadata about the provider\n router.get('/.well-known/openid-configuration', (_req, res) => {\n res.json(this.oidc.getConfiguration());\n });\n\n // JSON Web Key Set endpoint\n // https://openid.net/specs/openid-connect-core-1_0.html#rfc.section.10.1.1\n // Returns the public keys used to verify JWTs issued by this provider\n router.get('/.well-known/jwks.json', async (_req, res) => {\n const { keys } = await this.oidc.listPublicKeys();\n res.json({ keys });\n });\n\n // UserInfo endpoint\n // https://openid.net/specs/openid-connect-core-1_0.html#UserInfo\n // Returns claims about the authenticated user using an access token\n router.get('/v1/userinfo', async (req, res) => {\n const matches = req.headers.authorization?.match(/^Bearer[ ]+(\\S+)$/i);\n const token = matches?.[1];\n if (!token) {\n throw new AuthenticationError('No token provided');\n }\n\n const userInfo = await this.oidc.getUserInfo({ token });\n\n if (!userInfo) {\n res.status(404).send('User info not found');\n return;\n }\n\n res.json(userInfo);\n });\n\n if (\n this.config.getOptionalBoolean(\n 'auth.experimentalDynamicClientRegistration.enabled',\n )\n ) {\n // Authorization endpoint\n // https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest\n // Handles the initial authorization request from the client, validates parameters,\n // and redirects to the Authorization Session page for user approval\n router.get('/v1/authorize', async (req, res) => {\n // todo(blam): maybe add zod types for validating input\n const {\n client_id: clientId,\n redirect_uri: redirectUri,\n response_type: responseType,\n scope,\n state,\n nonce,\n code_challenge: codeChallenge,\n code_challenge_method: codeChallengeMethod,\n } = req.query;\n\n if (!clientId || !redirectUri || !responseType) {\n this.logger.error(`Failed to authorize: Missing required parameters`);\n return res.status(400).json({\n error: 'invalid_request',\n error_description:\n 'Missing required parameters: client_id, redirect_uri, response_type',\n });\n }\n\n try {\n const result = await this.oidc.createAuthorizationSession({\n clientId: clientId as string,\n redirectUri: redirectUri as string,\n responseType: responseType as string,\n scope: scope as string | undefined,\n state: state as string | undefined,\n nonce: nonce as string | undefined,\n codeChallenge: codeChallenge as string | undefined,\n codeChallengeMethod: codeChallengeMethod as string | undefined,\n });\n\n // todo(blam): maybe this URL could be overridable by config if\n // the plugin is mounted somewhere else?\n // support slashes in baseUrl?\n const authSessionRedirectUrl = new URL(\n `./oauth2/authorize/${result.id}`,\n ensureTrailingSlash(this.appUrl),\n );\n\n return res.redirect(authSessionRedirectUrl.toString());\n } catch (error) {\n const errorParams = new URLSearchParams();\n errorParams.append(\n 'error',\n isError(error) ? error.name : 'server_error',\n );\n errorParams.append(\n 'error_description',\n isError(error) ? error.message : 'Unknown error',\n );\n if (state) {\n errorParams.append('state', state as string);\n }\n\n const redirectUrl = new URL(redirectUri as string);\n redirectUrl.search = errorParams.toString();\n return res.redirect(redirectUrl.toString());\n }\n });\n\n // Authorization Session request details endpoint\n // Returns Authorization Session request details for the frontend\n router.get('/v1/sessions/:sessionId', async (req, res) => {\n const { sessionId } = req.params;\n\n if (!sessionId) {\n return res.status(400).json({\n error: 'invalid_request',\n error_description: 'Missing Authorization Session ID',\n });\n }\n\n try {\n const session = await this.oidc.getAuthorizationSession({\n sessionId,\n });\n\n return res.json({\n id: session.id,\n clientName: session.clientName,\n scope: session.scope,\n redirectUri: session.redirectUri,\n });\n } catch (error) {\n const description = isError(error) ? error.message : 'Unknown error';\n this.logger.error(\n `Failed to get authorization session: ${description}`,\n error,\n );\n return res.status(404).json({\n error: 'not_found',\n error_description: description,\n });\n }\n });\n\n // Authorization Session approval endpoint\n // Handles user approval of Authorization Session requests and generates authorization codes\n router.post('/v1/sessions/:sessionId/approve', async (req, res) => {\n const { sessionId } = req.params;\n\n if (!sessionId) {\n return res.status(400).json({\n error: 'invalid_request',\n error_description: 'Missing authorization session ID',\n });\n }\n\n try {\n const httpCredentials = await this.httpAuth.credentials(req);\n\n if (!this.auth.isPrincipal(httpCredentials, 'user')) {\n return res.status(401).json({\n error: 'unauthorized',\n error_description: 'Authentication required',\n });\n }\n\n const { userEntityRef } = httpCredentials.principal;\n\n const result = await this.oidc.approveAuthorizationSession({\n sessionId,\n userEntityRef,\n });\n\n return res.json({\n redirectUrl: result.redirectUrl,\n });\n } catch (error) {\n const description = isError(error) ? error.message : 'Unknown error';\n this.logger.error(\n `Failed to approve authorization session: ${description}`,\n error,\n );\n return res.status(400).json({\n error: 'invalid_request',\n error_description: description,\n });\n }\n });\n\n // Authorization Session rejection endpoint\n // Handles user rejection of Authorization Session requests and redirects with error\n router.post('/v1/sessions/:sessionId/reject', async (req, res) => {\n const { sessionId } = req.params;\n\n if (!sessionId) {\n return res.status(400).json({\n error: 'invalid_request',\n error_description: 'Missing authorization session ID',\n });\n }\n\n const httpCredentials = await this.httpAuth.credentials(req);\n\n if (!this.auth.isPrincipal(httpCredentials, 'user')) {\n return res.status(401).json({\n error: 'unauthorized',\n error_description: 'Authentication required',\n });\n }\n\n const { userEntityRef } = httpCredentials.principal;\n try {\n const session = await this.oidc.getAuthorizationSession({\n sessionId,\n });\n\n await this.oidc.rejectAuthorizationSession({\n sessionId,\n userEntityRef,\n });\n\n const errorParams = new URLSearchParams();\n errorParams.append('error', 'access_denied');\n errorParams.append('error_description', 'User denied the request');\n if (session.state) {\n errorParams.append('state', session.state);\n }\n\n const redirectUrl = new URL(session.redirectUri);\n redirectUrl.search = errorParams.toString();\n\n return res.json({\n redirectUrl: redirectUrl.toString(),\n });\n } catch (error) {\n const description = isError(error) ? error.message : 'Unknown error';\n this.logger.error(\n `Failed to reject authorization session: ${description}`,\n error,\n );\n\n return res.status(400).json({\n error: 'invalid_request',\n error_description: description,\n });\n }\n });\n\n // Token endpoint\n // https://openid.net/specs/openid-connect-core-1_0.html#TokenRequest\n // Exchanges authorization codes for access tokens and ID tokens\n router.post('/v1/token', async (req, res) => {\n // todo(blam): maybe add zod types for validating input\n const {\n grant_type: grantType,\n code,\n redirect_uri: redirectUri,\n code_verifier: codeVerifier,\n } = req.body;\n\n if (!grantType || !code || !redirectUri) {\n this.logger.error(\n `Failed to exchange code for token: Missing required parameters`,\n );\n return res.status(400).json({\n error: 'invalid_request',\n error_description: 'Missing required parameters',\n });\n }\n\n const expiresIn = readDcrTokenExpiration(this.config);\n\n try {\n const result = await this.oidc.exchangeCodeForToken({\n code,\n redirectUri,\n codeVerifier,\n grantType,\n expiresIn,\n });\n\n return res.json({\n access_token: result.accessToken,\n token_type: result.tokenType,\n expires_in: result.expiresIn,\n id_token: result.idToken,\n scope: result.scope,\n });\n } catch (error) {\n const description = isError(error) ? error.message : 'Unknown error';\n this.logger.error(\n `Failed to exchange code for token: ${description}`,\n error,\n );\n\n if (isError(error)) {\n if (error.name === 'AuthenticationError') {\n return res.status(401).json({\n error: 'invalid_client',\n error_description: error.message,\n });\n }\n if (error.name === 'InputError') {\n return res.status(400).json({\n error: 'invalid_request',\n error_description: error.message,\n });\n }\n }\n\n return res.status(500).json({\n error: 'server_error',\n error_description: description,\n });\n }\n });\n\n // Dynamic Client Registration endpoint\n // https://openid.net/specs/openid-connect-registration-1_0.html#ClientRegistration\n // Allows clients to register themselves dynamically with the provider\n router.post('/v1/register', async (req, res) => {\n // todo(blam): maybe add zod types for validating input\n const {\n client_name: clientName,\n redirect_uris: redirectUris,\n response_types: responseTypes,\n grant_types: grantTypes,\n scope,\n } = req.body;\n\n if (!redirectUris?.length) {\n res.status(400).json({\n error: 'invalid_request',\n error_description: 'redirect_uris is required',\n });\n return;\n }\n\n try {\n const client = await this.oidc.registerClient({\n clientName,\n redirectUris,\n responseTypes,\n grantTypes,\n scope,\n });\n\n res.status(201).json({\n client_id: client.clientId,\n redirect_uris: client.redirectUris,\n client_secret: client.clientSecret,\n });\n } catch (e) {\n const description = isError(e) ? e.message : 'Unknown error';\n this.logger.error(`Failed to register client: ${description}`, e);\n\n res.status(500).json({\n error: 'server_error',\n error_description: `Failed to register client: ${description}`,\n });\n }\n });\n }\n\n return router;\n }\n}\nfunction ensureTrailingSlash(appUrl: string): string | URL | undefined {\n if (appUrl.endsWith('/')) {\n return appUrl;\n }\n return `${appUrl}/`;\n}\n"],"names":["OidcService","Router","json","AuthenticationError","isError","readDcrTokenExpiration"],"mappings":"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"}
1
+ {"version":3,"file":"OidcRouter.cjs.js","sources":["../../src/service/OidcRouter.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport Router from 'express-promise-router';\nimport { OidcService } from './OidcService';\nimport { AuthenticationError, isError } from '@backstage/errors';\nimport {\n AuthService,\n HttpAuthService,\n LoggerService,\n RootConfigService,\n} from '@backstage/backend-plugin-api';\nimport { TokenIssuer } from '../identity/types';\nimport { UserInfoDatabase } from '../database/UserInfoDatabase';\nimport { OidcDatabase } from '../database/OidcDatabase';\nimport { OfflineAccessService } from './OfflineAccessService';\nimport { json } from 'express';\nimport { readDcrTokenExpiration } from './readTokenExpiration';\nimport { z } from 'zod';\nimport { fromZodError } from 'zod-validation-error';\nimport { OidcError } from './OidcError';\n\nconst authorizeQuerySchema = z.object({\n client_id: z.string().min(1),\n redirect_uri: z.string().url(),\n response_type: z.string().min(1),\n scope: z.string().optional(),\n state: z.string().optional(),\n nonce: z.string().optional(),\n code_challenge: z.string().optional(),\n code_challenge_method: z.string().optional(),\n});\n\nconst sessionIdParamSchema = z.object({\n sessionId: z.string().min(1),\n});\n\nconst tokenRequestBodySchema = z.object({\n grant_type: z.string().min(1),\n code: z.string().optional(),\n redirect_uri: z.string().url().optional(),\n code_verifier: z.string().optional(),\n refresh_token: z.string().optional(),\n client_id: z.string().optional(),\n client_secret: z.string().optional(),\n});\n\nconst registerRequestBodySchema = z.object({\n client_name: z.string().optional(),\n redirect_uris: z.array(z.string().url()).min(1),\n response_types: z.array(z.string()).optional(),\n grant_types: z.array(z.string()).optional(),\n scope: z.string().optional(),\n});\n\nconst revokeRequestBodySchema = z.object({\n token: z.string().min(1),\n token_type_hint: z.string().optional(),\n client_id: z.string().optional(),\n client_secret: z.string().optional(),\n});\n\nfunction validateRequest<T>(schema: z.ZodSchema<T>, data: unknown): T {\n const parseResult = schema.safeParse(data);\n if (!parseResult.success) {\n const errorMessage = fromZodError(parseResult.error).message;\n throw new OidcError('invalid_request', errorMessage, 400);\n }\n return parseResult.data;\n}\n\nasync function authenticateClient(\n req: { headers: { authorization?: string } },\n oidc: OidcService,\n bodyClientId?: string,\n bodyClientSecret?: string,\n): Promise<{ clientId: string; clientSecret: string }> {\n let clientId: string | undefined;\n let clientSecret: string | undefined;\n\n const basicAuth = req.headers.authorization?.match(/^Basic[ ]+([^\\s]+)$/i);\n if (basicAuth) {\n try {\n const decoded = Buffer.from(basicAuth[1], 'base64').toString('utf8');\n const idx = decoded.indexOf(':');\n if (idx >= 0) {\n clientId = decoded.slice(0, idx);\n clientSecret = decoded.slice(idx + 1);\n }\n } catch {\n /* ignore */\n }\n }\n\n if (!clientId || !clientSecret) {\n if (bodyClientId && bodyClientSecret) {\n clientId = bodyClientId;\n clientSecret = bodyClientSecret;\n }\n }\n\n if (!clientId || !clientSecret) {\n throw new OidcError(\n 'invalid_client',\n 'Client authentication required',\n 401,\n );\n }\n\n try {\n const ok = await oidc.verifyClientCredentials({\n clientId,\n clientSecret,\n });\n if (!ok) {\n throw new OidcError('invalid_client', 'Invalid client credentials', 401);\n }\n } catch (e) {\n throw OidcError.fromError(e);\n }\n\n return { clientId, clientSecret };\n}\n\nexport class OidcRouter {\n private readonly oidc: OidcService;\n private readonly logger: LoggerService;\n private readonly auth: AuthService;\n private readonly appUrl: string;\n private readonly httpAuth: HttpAuthService;\n private readonly config: RootConfigService;\n\n private constructor(\n oidc: OidcService,\n logger: LoggerService,\n auth: AuthService,\n appUrl: string,\n httpAuth: HttpAuthService,\n config: RootConfigService,\n ) {\n this.oidc = oidc;\n this.logger = logger;\n this.auth = auth;\n this.appUrl = appUrl;\n this.httpAuth = httpAuth;\n this.config = config;\n }\n\n static create(options: {\n auth: AuthService;\n tokenIssuer: TokenIssuer;\n baseUrl: string;\n appUrl: string;\n logger: LoggerService;\n userInfo: UserInfoDatabase;\n oidc: OidcDatabase;\n httpAuth: HttpAuthService;\n config: RootConfigService;\n offlineAccess?: OfflineAccessService;\n }) {\n return new OidcRouter(\n OidcService.create(options),\n options.logger,\n options.auth,\n options.appUrl,\n options.httpAuth,\n options.config,\n );\n }\n\n public getRouter() {\n const router = Router();\n\n router.use(json());\n\n // OpenID Provider Configuration endpoint\n // https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig\n // Returns the OpenID Provider Configuration document containing metadata about the provider\n router.get('/.well-known/openid-configuration', (_req, res) => {\n res.json(this.oidc.getConfiguration());\n });\n\n // JSON Web Key Set endpoint\n // https://openid.net/specs/openid-connect-core-1_0.html#rfc.section.10.1.1\n // Returns the public keys used to verify JWTs issued by this provider\n router.get('/.well-known/jwks.json', async (_req, res) => {\n const { keys } = await this.oidc.listPublicKeys();\n res.json({ keys });\n });\n\n // UserInfo endpoint\n // https://openid.net/specs/openid-connect-core-1_0.html#UserInfo\n // Returns claims about the authenticated user using an access token\n router.get('/v1/userinfo', async (req, res) => {\n const matches = req.headers.authorization?.match(/^Bearer[ ]+(\\S+)$/i);\n const token = matches?.[1];\n if (!token) {\n throw new AuthenticationError('No token provided');\n }\n\n const userInfo = await this.oidc.getUserInfo({ token });\n\n if (!userInfo) {\n res.status(404).send('User info not found');\n return;\n }\n\n res.json(userInfo);\n });\n\n const dcrEnabled = this.config.getOptionalBoolean(\n 'auth.experimentalDynamicClientRegistration.enabled',\n );\n\n if (dcrEnabled) {\n // Authorization endpoint\n // https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest\n // Handles the initial authorization request from the client, validates parameters,\n // and redirects to the Authorization Session page for user approval\n router.get('/v1/authorize', async (req, res) => {\n const {\n client_id: clientId,\n redirect_uri: redirectUri,\n response_type: responseType,\n scope,\n state,\n nonce,\n code_challenge: codeChallenge,\n code_challenge_method: codeChallengeMethod,\n } = validateRequest(authorizeQuerySchema, req.query);\n\n try {\n const result = await this.oidc.createAuthorizationSession({\n clientId,\n redirectUri,\n responseType,\n scope,\n state,\n nonce,\n codeChallenge,\n codeChallengeMethod,\n });\n\n // todo(blam): maybe this URL could be overridable by config if\n // the plugin is mounted somewhere else?\n // support slashes in baseUrl?\n const authSessionRedirectUrl = new URL(\n `./oauth2/authorize/${result.id}`,\n ensureTrailingSlash(this.appUrl),\n );\n\n return res.redirect(authSessionRedirectUrl.toString());\n } catch (error) {\n if (OidcError.isOidcError(error)) {\n const errorParams = new URLSearchParams();\n errorParams.append('error', error.body.error);\n errorParams.append(\n 'error_description',\n error.body.error_description,\n );\n if (state) {\n errorParams.append('state', state);\n }\n\n const redirectUrl = new URL(redirectUri);\n redirectUrl.search = errorParams.toString();\n return res.redirect(redirectUrl.toString());\n }\n throw error;\n }\n });\n\n // Authorization Session request details endpoint\n // Returns Authorization Session request details for the frontend\n router.get('/v1/sessions/:sessionId', async (req, res) => {\n const { sessionId } = validateRequest(sessionIdParamSchema, req.params);\n\n try {\n const session = await this.oidc.getAuthorizationSession({\n sessionId,\n });\n\n return res.json({\n id: session.id,\n clientName: session.clientName,\n scope: session.scope,\n redirectUri: session.redirectUri,\n });\n } catch (error) {\n throw OidcError.fromError(error);\n }\n });\n\n // Authorization Session approval endpoint\n // Handles user approval of Authorization Session requests and generates authorization codes\n router.post('/v1/sessions/:sessionId/approve', async (req, res) => {\n const { sessionId } = validateRequest(sessionIdParamSchema, req.params);\n\n try {\n const httpCredentials = await this.httpAuth.credentials(req);\n\n if (!this.auth.isPrincipal(httpCredentials, 'user')) {\n throw new OidcError(\n 'access_denied',\n 'Authentication required',\n 403,\n );\n }\n\n const { userEntityRef } = httpCredentials.principal;\n\n const result = await this.oidc.approveAuthorizationSession({\n sessionId,\n userEntityRef,\n });\n\n return res.json({\n redirectUrl: result.redirectUrl,\n });\n } catch (error) {\n throw OidcError.fromError(error);\n }\n });\n\n // Authorization Session rejection endpoint\n // Handles user rejection of Authorization Session requests and redirects with error\n router.post('/v1/sessions/:sessionId/reject', async (req, res) => {\n const { sessionId } = validateRequest(sessionIdParamSchema, req.params);\n\n const httpCredentials = await this.httpAuth.credentials(req);\n\n if (!this.auth.isPrincipal(httpCredentials, 'user')) {\n throw new OidcError('access_denied', 'Authentication required', 403);\n }\n\n const { userEntityRef } = httpCredentials.principal;\n try {\n const session = await this.oidc.getAuthorizationSession({\n sessionId,\n });\n\n await this.oidc.rejectAuthorizationSession({\n sessionId,\n userEntityRef,\n });\n\n const errorParams = new URLSearchParams();\n errorParams.append('error', 'access_denied');\n errorParams.append('error_description', 'User denied the request');\n if (session.state) {\n errorParams.append('state', session.state);\n }\n\n const redirectUrl = new URL(session.redirectUri);\n redirectUrl.search = errorParams.toString();\n\n return res.json({\n redirectUrl: redirectUrl.toString(),\n });\n } catch (error) {\n throw OidcError.fromError(error);\n }\n });\n\n // Token endpoint\n // https://openid.net/specs/openid-connect-core-1_0.html#TokenRequest\n // Exchanges authorization codes for access tokens and ID tokens\n // Also handles refresh token grant type\n router.post('/v1/token', async (req, res) => {\n const {\n grant_type: grantType,\n code,\n redirect_uri: redirectUri,\n code_verifier: codeVerifier,\n refresh_token: refreshToken,\n client_id: bodyClientId,\n client_secret: bodyClientSecret,\n } = validateRequest(tokenRequestBodySchema, req.body);\n\n const expiresIn = readDcrTokenExpiration(this.config);\n\n try {\n // Handle authorization_code grant type\n if (grantType === 'authorization_code') {\n if (!code || !redirectUri) {\n throw new OidcError(\n 'invalid_request',\n 'Missing code or redirect_uri parameters for authorization_code grant',\n 400,\n );\n }\n\n const result = await this.oidc.exchangeCodeForToken({\n code,\n redirectUri,\n codeVerifier,\n grantType,\n expiresIn,\n });\n\n return res.json({\n access_token: result.accessToken,\n token_type: result.tokenType,\n expires_in: result.expiresIn,\n id_token: result.idToken,\n scope: result.scope,\n ...(result.refreshToken && {\n refresh_token: result.refreshToken,\n }),\n });\n }\n\n // Handle refresh_token grant type\n if (grantType === 'refresh_token') {\n if (!refreshToken) {\n throw new OidcError(\n 'invalid_request',\n 'Missing refresh_token parameter for refresh_token grant',\n 400,\n );\n }\n\n // Authenticate if credentials are provided via Basic auth or body\n const hasCredentials =\n req.headers.authorization?.match(/^Basic[ ]+([^\\s]+)$/i) ||\n (bodyClientId && bodyClientSecret);\n\n let authenticatedClientId: string | undefined;\n if (hasCredentials) {\n const { clientId: authedId } = await authenticateClient(\n req,\n this.oidc,\n bodyClientId,\n bodyClientSecret,\n );\n authenticatedClientId = authedId;\n }\n\n const result = await this.oidc.refreshAccessToken({\n refreshToken,\n clientId: authenticatedClientId,\n });\n\n return res.json({\n access_token: result.accessToken,\n token_type: result.tokenType,\n expires_in: result.expiresIn,\n refresh_token: result.refreshToken,\n });\n }\n\n // Unsupported grant type\n throw new OidcError(\n 'unsupported_grant_type',\n `Grant type ${grantType} is not supported`,\n 400,\n );\n } catch (error) {\n // Invalid auth codes and refresh tokens should be invalid_grant, not invalid_client.\n // Client auth failures are already thrown as OidcError by authenticateClient.\n if (isError(error) && error.name === 'AuthenticationError') {\n throw new OidcError('invalid_grant', error.message, 400, error);\n }\n throw OidcError.fromError(error);\n }\n });\n }\n\n // Dynamic Client Registration endpoint - only available when DCR is enabled\n if (dcrEnabled) {\n // https://openid.net/specs/openid-connect-registration-1_0.html#ClientRegistration\n // Allows clients to register themselves dynamically with the provider\n router.post('/v1/register', async (req, res) => {\n const {\n client_name: clientName,\n redirect_uris: redirectUris,\n response_types: responseTypes,\n grant_types: grantTypes,\n scope,\n } = validateRequest(registerRequestBodySchema, req.body);\n\n try {\n const client = await this.oidc.registerClient({\n clientName: clientName ?? 'Backstage CLI',\n redirectUris,\n responseTypes,\n grantTypes,\n scope,\n });\n\n return res.status(201).json({\n client_id: client.clientId,\n redirect_uris: client.redirectUris,\n client_secret: client.clientSecret,\n });\n } catch (e) {\n throw OidcError.fromError(e);\n }\n });\n\n // Token Revocation endpoint (RFC 7009-like)\n // Allows clients to revoke refresh tokens\n router.post('/v1/revoke', async (req, res) => {\n try {\n const {\n token,\n client_id: bodyClientId,\n client_secret: bodyClientSecret,\n } = validateRequest(revokeRequestBodySchema, req.body ?? {});\n\n await authenticateClient(\n req,\n this.oidc,\n bodyClientId,\n bodyClientSecret,\n );\n\n try {\n await this.oidc.revokeRefreshToken(token);\n } catch (e) {\n // RFC 7009 says always respond 200 even for invalid tokens\n this.logger.debug('Failed to revoke token', e);\n }\n\n return res.status(200).send('');\n } catch (e) {\n throw OidcError.fromError(e);\n }\n });\n }\n\n router.use(OidcError.middleware(this.logger));\n\n return router;\n }\n}\nfunction ensureTrailingSlash(appUrl: string): string {\n if (appUrl.endsWith('/')) {\n return appUrl;\n }\n return `${appUrl}/`;\n}\n"],"names":["z","fromZodError","OidcError","OidcService","Router","json","AuthenticationError","readDcrTokenExpiration","isError"],"mappings":"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"}