@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.
- package/CHANGELOG.md +13 -0
- package/config.d.ts +31 -0
- package/dist/authPlugin.cjs.js +14 -1
- package/dist/authPlugin.cjs.js.map +1 -1
- package/dist/database/OfflineSessionDatabase.cjs.js +136 -0
- package/dist/database/OfflineSessionDatabase.cjs.js.map +1 -0
- package/dist/lib/refreshToken.cjs.js +60 -0
- package/dist/lib/refreshToken.cjs.js.map +1 -0
- package/dist/service/OfflineAccessService.cjs.js +177 -0
- package/dist/service/OfflineAccessService.cjs.js.map +1 -0
- package/dist/service/OidcError.cjs.js +57 -0
- package/dist/service/OidcError.cjs.js.map +1 -0
- package/dist/service/OidcRouter.cjs.js +215 -142
- package/dist/service/OidcRouter.cjs.js.map +1 -1
- package/dist/service/OidcService.cjs.js +97 -19
- package/dist/service/OidcService.cjs.js.map +1 -1
- package/dist/service/router.cjs.js +2 -1
- package/dist/service/router.cjs.js.map +1 -1
- package/migrations/20251020000000_offline_sessions.js +78 -0
- package/package.json +11 -9
|
@@ -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
|
-
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
"error",
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
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
|
-
|
|
248
|
-
code
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
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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"}
|