@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 +45 -0
- package/config.d.ts +28 -4
- package/dist/service/CimdClient.cjs.js +133 -0
- package/dist/service/CimdClient.cjs.js.map +1 -0
- package/dist/service/OidcRouter.cjs.js +16 -18
- package/dist/service/OidcRouter.cjs.js.map +1 -1
- package/dist/service/OidcService.cjs.js +93 -57
- package/dist/service/OidcService.cjs.js.map +1 -1
- package/dist/service/readTokenExpiration.cjs.js +9 -26
- package/dist/service/readTokenExpiration.cjs.js.map +1 -1
- package/dist/service/router.cjs.js +17 -26
- package/dist/service/router.cjs.js.map +1 -1
- package/migrations/20251217120000_drop_oidc_clients_fk.js +44 -0
- package/package.json +15 -13
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
|
-
*
|
|
156
|
-
* Defaults to
|
|
161
|
+
* Whether to enable Client ID Metadata Documents support
|
|
162
|
+
* Defaults to false
|
|
157
163
|
*/
|
|
158
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
190
|
-
const { sessionId
|
|
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.
|
|
226
|
-
|
|
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.
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
11
|
-
|
|
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:
|
|
15
|
+
key: processingIntervalKey
|
|
31
16
|
});
|
|
32
17
|
const durationS = Math.round(types.durationToMilliseconds(duration) / 1e3);
|
|
33
|
-
if (durationS <
|
|
34
|
-
return
|
|
35
|
-
} else if (durationS >
|
|
36
|
-
return
|
|
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
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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:
|
|
71
|
-
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
|
|
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 {
|
|
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
|
|
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
|
|
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",
|
|
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
|
|
76
|
-
"@backstage/backend-test-utils": "1.11.0
|
|
77
|
-
"@backstage/cli": "0.35.4
|
|
78
|
-
"@backstage/plugin-auth-backend-module-google-provider": "0.3.12
|
|
79
|
-
"@backstage/plugin-auth-backend-module-guest-provider": "0.2.16
|
|
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",
|