@atlasauth/emulator-core 0.2.0 → 0.3.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/dist/custom-html.d.ts +57 -0
- package/dist/custom-html.js +491 -0
- package/dist/custom-html.js.map +1 -0
- package/dist/engine.d.ts +1 -1
- package/dist/engine.js +4 -0
- package/dist/engine.js.map +1 -1
- package/dist/error-catalog.js +8 -0
- package/dist/error-catalog.js.map +1 -1
- package/dist/event-gate.js +60 -2
- package/dist/event-gate.js.map +1 -1
- package/dist/generic.js +5 -1
- package/dist/generic.js.map +1 -1
- package/dist/handlers/access-signals.d.ts +48 -0
- package/dist/handlers/access-signals.js +76 -0
- package/dist/handlers/access-signals.js.map +1 -0
- package/dist/handlers/approvals.js +54 -1
- package/dist/handlers/approvals.js.map +1 -1
- package/dist/handlers/authz-data.d.ts +2 -0
- package/dist/handlers/authz-data.js +627 -0
- package/dist/handlers/authz-data.js.map +1 -0
- package/dist/handlers/authz-layers.d.ts +229 -0
- package/dist/handlers/authz-layers.js +786 -0
- package/dist/handlers/authz-layers.js.map +1 -0
- package/dist/handlers/authz-state.d.ts +80 -0
- package/dist/handlers/authz-state.js +175 -0
- package/dist/handlers/authz-state.js.map +1 -0
- package/dist/handlers/authz.js +240 -177
- package/dist/handlers/authz.js.map +1 -1
- package/dist/handlers/billing.d.ts +99 -1
- package/dist/handlers/billing.js +556 -77
- package/dist/handlers/billing.js.map +1 -1
- package/dist/handlers/cedar-ext.d.ts +35 -0
- package/dist/handlers/cedar-ext.js +174 -0
- package/dist/handlers/cedar-ext.js.map +1 -0
- package/dist/handlers/cedar.d.ts +146 -0
- package/dist/handlers/cedar.js +689 -0
- package/dist/handlers/cedar.js.map +1 -0
- package/dist/handlers/connections.d.ts +4 -1
- package/dist/handlers/connections.js +267 -38
- package/dist/handlers/connections.js.map +1 -1
- package/dist/handlers/control.js +6 -1
- package/dist/handlers/control.js.map +1 -1
- package/dist/handlers/device.js +18 -1
- package/dist/handlers/device.js.map +1 -1
- package/dist/handlers/emu3-misc.d.ts +38 -0
- package/dist/handlers/emu3-misc.js +1056 -0
- package/dist/handlers/emu3-misc.js.map +1 -0
- package/dist/handlers/emu4-actions-access.d.ts +46 -0
- package/dist/handlers/emu4-actions-access.js +107 -0
- package/dist/handlers/emu4-actions-access.js.map +1 -0
- package/dist/handlers/emu4-actions-catalog-extra.d.ts +3 -0
- package/dist/handlers/emu4-actions-catalog-extra.js +204 -0
- package/dist/handlers/emu4-actions-catalog-extra.js.map +1 -0
- package/dist/handlers/emu4-actions-catalog.d.ts +3 -0
- package/dist/handlers/emu4-actions-catalog.js +143 -0
- package/dist/handlers/emu4-actions-catalog.js.map +1 -0
- package/dist/handlers/emu4-actions-def.d.ts +50 -0
- package/dist/handlers/emu4-actions-def.js +250 -0
- package/dist/handlers/emu4-actions-def.js.map +1 -0
- package/dist/handlers/emu4-actions-sandbox.d.ts +36 -0
- package/dist/handlers/emu4-actions-sandbox.js +318 -0
- package/dist/handlers/emu4-actions-sandbox.js.map +1 -0
- package/dist/handlers/emu4-actions-typegen.d.ts +5 -0
- package/dist/handlers/emu4-actions-typegen.js +73 -0
- package/dist/handlers/emu4-actions-typegen.js.map +1 -0
- package/dist/handlers/emu4-actions.d.ts +2 -0
- package/dist/handlers/emu4-actions.js +506 -0
- package/dist/handlers/emu4-actions.js.map +1 -0
- package/dist/handlers/emu4-authz-trust.d.ts +40 -0
- package/dist/handlers/emu4-authz-trust.js +488 -0
- package/dist/handlers/emu4-authz-trust.js.map +1 -0
- package/dist/handlers/emu4-authz.d.ts +2 -0
- package/dist/handlers/emu4-authz.js +752 -0
- package/dist/handlers/emu4-authz.js.map +1 -0
- package/dist/handlers/emu4-billing-core.d.ts +23 -0
- package/dist/handlers/emu4-billing-core.js +22 -0
- package/dist/handlers/emu4-billing-core.js.map +1 -0
- package/dist/handlers/emu4-billing-x.d.ts +2 -0
- package/dist/handlers/emu4-billing-x.js +489 -0
- package/dist/handlers/emu4-billing-x.js.map +1 -0
- package/dist/handlers/emu4-billing.d.ts +14 -0
- package/dist/handlers/emu4-billing.js +846 -0
- package/dist/handlers/emu4-billing.js.map +1 -0
- package/dist/handlers/emu4-clones.d.ts +2 -0
- package/dist/handlers/emu4-clones.js +322 -0
- package/dist/handlers/emu4-clones.js.map +1 -0
- package/dist/handlers/emu4-compliance.d.ts +8 -0
- package/dist/handlers/emu4-compliance.js +39 -0
- package/dist/handlers/emu4-compliance.js.map +1 -0
- package/dist/handlers/emu4-config.d.ts +14 -0
- package/dist/handlers/emu4-config.js +148 -0
- package/dist/handlers/emu4-config.js.map +1 -0
- package/dist/handlers/emu4-cookies.d.ts +8 -0
- package/dist/handlers/emu4-cookies.js +248 -0
- package/dist/handlers/emu4-cookies.js.map +1 -0
- package/dist/handlers/emu4-erasure.d.ts +2 -0
- package/dist/handlers/emu4-erasure.js +587 -0
- package/dist/handlers/emu4-erasure.js.map +1 -0
- package/dist/handlers/emu4-federation.d.ts +8 -0
- package/dist/handlers/emu4-federation.js +18 -0
- package/dist/handlers/emu4-federation.js.map +1 -0
- package/dist/handlers/emu4-journey-math.d.ts +42 -0
- package/dist/handlers/emu4-journey-math.js +99 -0
- package/dist/handlers/emu4-journey-math.js.map +1 -0
- package/dist/handlers/emu4-messaging.d.ts +21 -0
- package/dist/handlers/emu4-messaging.js +595 -0
- package/dist/handlers/emu4-messaging.js.map +1 -0
- package/dist/handlers/emu4-minors.d.ts +7 -0
- package/dist/handlers/emu4-minors.js +408 -0
- package/dist/handlers/emu4-minors.js.map +1 -0
- package/dist/handlers/emu4-oauth-audit.d.ts +5 -0
- package/dist/handlers/emu4-oauth-audit.js +17 -0
- package/dist/handlers/emu4-oauth-audit.js.map +1 -0
- package/dist/handlers/emu4-oauth-auth.d.ts +83 -0
- package/dist/handlers/emu4-oauth-auth.js +363 -0
- package/dist/handlers/emu4-oauth-auth.js.map +1 -0
- package/dist/handlers/emu4-oauth-delivery.d.ts +68 -0
- package/dist/handlers/emu4-oauth-delivery.js +207 -0
- package/dist/handlers/emu4-oauth-delivery.js.map +1 -0
- package/dist/handlers/emu4-oauth-keys.d.ts +11 -0
- package/dist/handlers/emu4-oauth-keys.js +268 -0
- package/dist/handlers/emu4-oauth-keys.js.map +1 -0
- package/dist/handlers/emu4-oauth-pub.d.ts +36 -0
- package/dist/handlers/emu4-oauth-pub.js +420 -0
- package/dist/handlers/emu4-oauth-pub.js.map +1 -0
- package/dist/handlers/emu4-oauth.d.ts +10 -0
- package/dist/handlers/emu4-oauth.js +23 -0
- package/dist/handlers/emu4-oauth.js.map +1 -0
- package/dist/handlers/emu4-policies.d.ts +2 -0
- package/dist/handlers/emu4-policies.js +408 -0
- package/dist/handlers/emu4-policies.js.map +1 -0
- package/dist/handlers/emu4-region.d.ts +2 -0
- package/dist/handlers/emu4-region.js +189 -0
- package/dist/handlers/emu4-region.js.map +1 -0
- package/dist/handlers/emu4-saml.d.ts +12 -0
- package/dist/handlers/emu4-saml.js +532 -0
- package/dist/handlers/emu4-saml.js.map +1 -0
- package/dist/handlers/emu4-shared.d.ts +44 -0
- package/dist/handlers/emu4-shared.js +90 -0
- package/dist/handlers/emu4-shared.js.map +1 -0
- package/dist/handlers/emu4-snapshots.d.ts +38 -0
- package/dist/handlers/emu4-snapshots.js +231 -0
- package/dist/handlers/emu4-snapshots.js.map +1 -0
- package/dist/handlers/emu4-sns.d.ts +9 -0
- package/dist/handlers/emu4-sns.js +21 -0
- package/dist/handlers/emu4-sns.js.map +1 -0
- package/dist/handlers/emu4-trust-binding.d.ts +27 -0
- package/dist/handlers/emu4-trust-binding.js +344 -0
- package/dist/handlers/emu4-trust-binding.js.map +1 -0
- package/dist/handlers/emu4-trust.d.ts +35 -0
- package/dist/handlers/emu4-trust.js +543 -0
- package/dist/handlers/emu4-trust.js.map +1 -0
- package/dist/handlers/fga-engine.d.ts +78 -0
- package/dist/handlers/fga-engine.js +663 -0
- package/dist/handlers/fga-engine.js.map +1 -0
- package/dist/handlers/hooks.js +13 -0
- package/dist/handlers/hooks.js.map +1 -1
- package/dist/handlers/index.js +14 -1
- package/dist/handlers/index.js.map +1 -1
- package/dist/handlers/machines.js +6 -0
- package/dist/handlers/machines.js.map +1 -1
- package/dist/handlers/messaging.d.ts +3 -1
- package/dist/handlers/messaging.js +455 -48
- package/dist/handlers/messaging.js.map +1 -1
- package/dist/handlers/oauth-adv.d.ts +191 -0
- package/dist/handlers/oauth-adv.js +1305 -0
- package/dist/handlers/oauth-adv.js.map +1 -0
- package/dist/handlers/oauth-flows.d.ts +25 -1
- package/dist/handlers/oauth-flows.js +111 -50
- package/dist/handlers/oauth-flows.js.map +1 -1
- package/dist/handlers/oauth.d.ts +24 -3
- package/dist/handlers/oauth.js +245 -60
- package/dist/handlers/oauth.js.map +1 -1
- package/dist/handlers/orgs.js +6 -0
- package/dist/handlers/orgs.js.map +1 -1
- package/dist/handlers/platform.d.ts +5 -0
- package/dist/handlers/platform.js +33 -15
- package/dist/handlers/platform.js.map +1 -1
- package/dist/handlers/scim.d.ts +2 -0
- package/dist/handlers/scim.js +198 -11
- package/dist/handlers/scim.js.map +1 -1
- package/dist/handlers/sessions.d.ts +2 -0
- package/dist/handlers/sessions.js +24 -16
- package/dist/handlers/sessions.js.map +1 -1
- package/dist/handlers/users.js +3 -0
- package/dist/handlers/users.js.map +1 -1
- package/dist/jwe.d.ts +18 -0
- package/dist/jwe.js +101 -0
- package/dist/jwe.js.map +1 -0
- package/dist/jws.js +2 -0
- package/dist/jws.js.map +1 -1
- package/dist/messaging-callbacks.d.ts +100 -0
- package/dist/messaging-callbacks.js +767 -0
- package/dist/messaging-callbacks.js.map +1 -0
- package/dist/openapi.js +41 -1
- package/dist/openapi.js.map +1 -1
- package/dist/parity-emu3-authz.d.ts +3 -0
- package/dist/parity-emu3-authz.js +61 -0
- package/dist/parity-emu3-authz.js.map +1 -0
- package/dist/parity-emu3-billing.d.ts +3 -0
- package/dist/parity-emu3-billing.js +25 -0
- package/dist/parity-emu3-billing.js.map +1 -0
- package/dist/parity-emu3-messaging.d.ts +3 -0
- package/dist/parity-emu3-messaging.js +27 -0
- package/dist/parity-emu3-messaging.js.map +1 -0
- package/dist/parity-emu3-misc.d.ts +2 -0
- package/dist/parity-emu3-misc.js +63 -0
- package/dist/parity-emu3-misc.js.map +1 -0
- package/dist/parity-emu3-oauth.d.ts +2 -0
- package/dist/parity-emu3-oauth.js +39 -0
- package/dist/parity-emu3-oauth.js.map +1 -0
- package/dist/parity-emu4-actions.d.ts +2 -0
- package/dist/parity-emu4-actions.js +38 -0
- package/dist/parity-emu4-actions.js.map +1 -0
- package/dist/parity-emu4-authz.d.ts +3 -0
- package/dist/parity-emu4-authz.js +68 -0
- package/dist/parity-emu4-authz.js.map +1 -0
- package/dist/parity-emu4-billing.d.ts +6 -0
- package/dist/parity-emu4-billing.js +44 -0
- package/dist/parity-emu4-billing.js.map +1 -0
- package/dist/parity-emu4-compliance.d.ts +2 -0
- package/dist/parity-emu4-compliance.js +92 -0
- package/dist/parity-emu4-compliance.js.map +1 -0
- package/dist/parity-emu4-federation.d.ts +2 -0
- package/dist/parity-emu4-federation.js +33 -0
- package/dist/parity-emu4-federation.js.map +1 -0
- package/dist/parity-emu4-messaging.d.ts +3 -0
- package/dist/parity-emu4-messaging.js +42 -0
- package/dist/parity-emu4-messaging.js.map +1 -0
- package/dist/parity-emu4-oauth.d.ts +2 -0
- package/dist/parity-emu4-oauth.js +55 -0
- package/dist/parity-emu4-oauth.js.map +1 -0
- package/dist/parity-emu4-trust.d.ts +2 -0
- package/dist/parity-emu4-trust.js +41 -0
- package/dist/parity-emu4-trust.js.map +1 -0
- package/dist/parity-routes.js +19 -1
- package/dist/parity-routes.js.map +1 -1
- package/dist/sns-cert.d.ts +9 -0
- package/dist/sns-cert.js +101 -0
- package/dist/sns-cert.js.map +1 -0
- package/dist/tokens.d.ts +69 -4
- package/dist/tokens.js +164 -38
- package/dist/tokens.js.map +1 -1
- package/package.json +2 -2
package/dist/handlers/oauth.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.splitScope = exports.fail = exports.resetOAuth = exports.st = void 0;
|
|
3
|
+
exports.userClaims = exports.splitScope = exports.fail = exports.resetOAuth = exports.st = void 0;
|
|
4
4
|
exports.discoveryDocument = discoveryDocument;
|
|
5
5
|
exports.authenticateClient = authenticateClient;
|
|
6
6
|
exports.mintAccess = mintAccess;
|
|
7
7
|
exports.issueTokens = issueTokens;
|
|
8
|
+
exports.idTokenFor = idTokenFor;
|
|
9
|
+
exports.revokeGrant = revokeGrant;
|
|
10
|
+
exports.checkResourceToken = checkResourceToken;
|
|
8
11
|
exports.issueCode = issueCode;
|
|
9
12
|
exports.oauthHandlers = oauthHandlers;
|
|
10
13
|
const crypto_util_1 = require("../crypto-util");
|
|
@@ -13,7 +16,10 @@ const openapi_1 = require("../openapi");
|
|
|
13
16
|
const store_1 = require("../store");
|
|
14
17
|
const jws_1 = require("../jws");
|
|
15
18
|
const oauth_flows_1 = require("./oauth-flows");
|
|
19
|
+
const oauth_adv_1 = require("./oauth-adv");
|
|
16
20
|
const util_1 = require("./util");
|
|
21
|
+
const emu4_oauth_auth_1 = require("./emu4-oauth-auth");
|
|
22
|
+
const emu4_oauth_pub_1 = require("./emu4-oauth-pub");
|
|
17
23
|
/**
|
|
18
24
|
* The OAuth 2.0 / OpenID Connect provider: client registry (`/v1/oauth_clients`), discovery, authorize (code + PKCE
|
|
19
25
|
* S256), token (authorization_code, refresh_token with rotation and reuse detection, client_credentials, token
|
|
@@ -51,20 +57,39 @@ function discoveryDocument(e) {
|
|
|
51
57
|
userinfo_endpoint: `${iss}/oauth2/userinfo`,
|
|
52
58
|
revocation_endpoint: `${iss}/oauth2/revoke`,
|
|
53
59
|
introspection_endpoint: `${iss}/oauth2/introspect`,
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
60
|
+
end_session_endpoint: `${iss}/oauth2/logout`,
|
|
61
|
+
backchannel_logout_supported: true,
|
|
62
|
+
backchannel_logout_session_supported: true,
|
|
63
|
+
frontchannel_logout_supported: true,
|
|
64
|
+
frontchannel_logout_session_supported: true,
|
|
65
|
+
grant_management_endpoint: `${iss}/oauth2/grants`,
|
|
66
|
+
grant_management_actions_supported: ['create', 'merge', 'replace'],
|
|
67
|
+
grant_management_action_required: false,
|
|
68
|
+
...((0, oauth_adv_1.adv)(e).cfg.dynamic_registration ? { registration_endpoint: `${iss}/oauth2/register` } : {}),
|
|
69
|
+
response_types_supported: [...oauth_adv_1.RESPONSE_TYPES_SUPPORTED],
|
|
70
|
+
response_modes_supported: ['query', 'fragment', ...emu4_oauth_auth_1.EXT_RESPONSE_MODES],
|
|
71
|
+
authorization_signing_alg_values_supported: [...emu4_oauth_auth_1.OP_SIGNING_ALGS],
|
|
72
|
+
request_object_encryption_alg_values_supported: ['RSA-OAEP-256', 'ECDH-ES'],
|
|
73
|
+
request_object_encryption_enc_values_supported: ['A128GCM', 'A256GCM', 'A128CBC-HS256', 'A256CBC-HS512'],
|
|
74
|
+
request_parameter_supported: true,
|
|
75
|
+
request_uri_parameter_supported: true,
|
|
76
|
+
require_request_uri_registration: true,
|
|
77
|
+
request_object_signing_alg_values_supported: [...oauth_adv_1.REQUEST_OBJECT_ALGS],
|
|
78
|
+
authorization_details_types_supported: (0, oauth_adv_1.adv)(e).cfg.authorization_details_types,
|
|
79
|
+
grant_types_supported: ['authorization_code', 'implicit', 'refresh_token', 'client_credentials', EXCHANGE, 'urn:ietf:params:oauth:grant-type:device_code', 'urn:openid:params:grant-type:ciba', ...((0, oauth_adv_1.umaEnabled)(e) ? [oauth_adv_1.UMA_GRANT_TYPE] : [])],
|
|
57
80
|
subject_types_supported: ['public'],
|
|
58
|
-
id_token_signing_alg_values_supported: [
|
|
59
|
-
token_endpoint_auth_methods_supported: ['client_secret_basic', 'client_secret_post', 'none'],
|
|
81
|
+
id_token_signing_alg_values_supported: [...emu4_oauth_auth_1.OP_SIGNING_ALGS],
|
|
82
|
+
token_endpoint_auth_methods_supported: ['client_secret_basic', 'client_secret_post', 'private_key_jwt', 'none'],
|
|
83
|
+
token_endpoint_auth_signing_alg_values_supported: [...oauth_adv_1.REQUEST_OBJECT_ALGS],
|
|
60
84
|
code_challenge_methods_supported: ['S256'],
|
|
61
85
|
dpop_signing_alg_values_supported: ['ES256', 'ES384', 'RS256', 'PS256', 'EdDSA'],
|
|
62
86
|
tls_client_certificate_bound_access_tokens: true,
|
|
63
87
|
pushed_authorization_request_endpoint: `${iss}/oauth2/par`,
|
|
64
88
|
device_authorization_endpoint: `${iss}/oauth2/device_authorization`,
|
|
65
89
|
backchannel_authentication_endpoint: `${iss}/oauth2/backchannel_authentication`,
|
|
66
|
-
backchannel_token_delivery_modes_supported: ['poll', 'ping'],
|
|
67
|
-
|
|
90
|
+
backchannel_token_delivery_modes_supported: ['poll', 'ping', 'push'],
|
|
91
|
+
backchannel_user_code_parameter_supported: false,
|
|
92
|
+
scopes_supported: [...SCOPES, ...oauth_adv_1.GRANT_MANAGEMENT_SCOPES],
|
|
68
93
|
claims_supported: ['sub', 'iss', 'aud', 'exp', 'iat', 'auth_time', 'nonce', 'email', 'email_verified', 'name', 'picture'],
|
|
69
94
|
authorization_response_iss_parameter_supported: true,
|
|
70
95
|
atlas_emulator: true,
|
|
@@ -72,7 +97,7 @@ function discoveryDocument(e) {
|
|
|
72
97
|
}
|
|
73
98
|
const FLOW_FLAGS = ['require_consent', 'dpop_bound_access_tokens', 'tls_client_certificate_bound_access_tokens', 'require_pushed_authorization_requests'];
|
|
74
99
|
/** The client settings the consent, binding, PAR and CIBA flows read; validated here so a bad value never reaches a flow. */
|
|
75
|
-
function pickFlowFields(body) {
|
|
100
|
+
function pickFlowFields(body, existing) {
|
|
76
101
|
const out = {};
|
|
77
102
|
for (const k of FLOW_FLAGS) {
|
|
78
103
|
if (body[k] === undefined)
|
|
@@ -82,12 +107,25 @@ function pickFlowFields(body) {
|
|
|
82
107
|
out[k] = body[k];
|
|
83
108
|
}
|
|
84
109
|
if (body.backchannel_token_delivery_mode !== undefined) {
|
|
85
|
-
if (!['poll', 'ping'].includes(body.backchannel_token_delivery_mode))
|
|
86
|
-
throw new errors_1.EmulatorError('VALIDATION_FAILED', 'backchannel_token_delivery_mode must be poll or
|
|
110
|
+
if (!['poll', 'ping', 'push'].includes(body.backchannel_token_delivery_mode))
|
|
111
|
+
throw new errors_1.EmulatorError('VALIDATION_FAILED', 'backchannel_token_delivery_mode must be poll, ping or push.', 'backchannel_token_delivery_mode');
|
|
87
112
|
out.backchannel_token_delivery_mode = body.backchannel_token_delivery_mode;
|
|
88
113
|
}
|
|
89
114
|
if (body.backchannel_client_notification_endpoint !== undefined)
|
|
90
115
|
out.backchannel_client_notification_endpoint = body.backchannel_client_notification_endpoint;
|
|
116
|
+
if ((out.backchannel_token_delivery_mode ?? existing?.backchannel_token_delivery_mode) !== undefined && ['ping', 'push'].includes(out.backchannel_token_delivery_mode ?? existing?.backchannel_token_delivery_mode) && !(out.backchannel_client_notification_endpoint ?? existing?.backchannel_client_notification_endpoint)) {
|
|
117
|
+
throw new errors_1.EmulatorError('VALIDATION_FAILED', 'ping and push delivery need backchannel_client_notification_endpoint.', 'backchannel_client_notification_endpoint');
|
|
118
|
+
}
|
|
119
|
+
// Key, request-object and response-type metadata, validated like the real registry.
|
|
120
|
+
const meta = (0, oauth_adv_1.parseClientMeta)(body);
|
|
121
|
+
if (!meta.ok)
|
|
122
|
+
throw new errors_1.EmulatorError('VALIDATION_FAILED', meta.message);
|
|
123
|
+
Object.assign(out, meta.value);
|
|
124
|
+
for (const k of ['backchannel_logout_uri', 'frontchannel_logout_uri'])
|
|
125
|
+
if (body[k] !== undefined)
|
|
126
|
+
out[k] = body[k];
|
|
127
|
+
if (body.claims_redirect_uris !== undefined)
|
|
128
|
+
out.claims_redirect_uris = body.claims_redirect_uris;
|
|
91
129
|
return out;
|
|
92
130
|
}
|
|
93
131
|
const clientShape = (e, c, secret) => {
|
|
@@ -111,6 +149,16 @@ const clientShape = (e, c, secret) => {
|
|
|
111
149
|
require_pushed_authorization_requests: c.require_pushed_authorization_requests === true,
|
|
112
150
|
backchannel_token_delivery_mode: c.backchannel_token_delivery_mode ?? 'poll',
|
|
113
151
|
backchannel_client_notification_endpoint: c.backchannel_client_notification_endpoint ?? null,
|
|
152
|
+
response_types: (0, oauth_adv_1.clientResponseTypes)(c),
|
|
153
|
+
require_signed_request_object: c.require_signed_request_object === true,
|
|
154
|
+
...(0, emu4_oauth_auth_1.clientExtras)(c),
|
|
155
|
+
request_object_signing_alg: c.request_object_signing_alg ?? null,
|
|
156
|
+
request_uris: c.request_uris ?? [],
|
|
157
|
+
jwks: c.jwks ?? null,
|
|
158
|
+
jwks_uri: c.jwks_uri ?? null,
|
|
159
|
+
backchannel_logout_uri: c.backchannel_logout_uri ?? null,
|
|
160
|
+
frontchannel_logout_uri: c.frontchannel_logout_uri ?? null,
|
|
161
|
+
claims_redirect_uris: c.claims_redirect_uris ?? [],
|
|
114
162
|
created_at: c.created_at,
|
|
115
163
|
updated_at: c.updated_at,
|
|
116
164
|
...(secret ? { client_secret: secret } : {}),
|
|
@@ -122,8 +170,8 @@ const liveClient = (e, id) => {
|
|
|
122
170
|
throw (0, errors_1.notFound)('OAuth client');
|
|
123
171
|
return c;
|
|
124
172
|
};
|
|
125
|
-
/** Client authentication: Basic,
|
|
126
|
-
function authenticateClient(e, c) {
|
|
173
|
+
/** Client authentication with the method the client is REGISTERED for: Basic, `client_id` (+ `client_secret`) in the body, a signed `client_assertion` (private_key_jwt), or nothing for a public client. */
|
|
174
|
+
async function authenticateClient(e, c) {
|
|
127
175
|
let id = typeof c.body.client_id === 'string' ? c.body.client_id : undefined;
|
|
128
176
|
let secret = typeof c.body.client_secret === 'string' ? c.body.client_secret : undefined;
|
|
129
177
|
const basic = /^Basic\s+(.+)$/i.exec(c.request.headers.get('authorization') ?? '')?.[1];
|
|
@@ -132,10 +180,18 @@ function authenticateClient(e, c) {
|
|
|
132
180
|
id = decodeURIComponent(i ?? '');
|
|
133
181
|
secret = decodeURIComponent(rest.join(':'));
|
|
134
182
|
}
|
|
183
|
+
// A client assertion names the client itself when no client_id was sent.
|
|
184
|
+
id ??= (0, emu4_oauth_auth_1.assertionClientId)(c.body);
|
|
135
185
|
const client = e.db.oauthClients.get(id);
|
|
136
186
|
if (!client || client.deleted)
|
|
137
187
|
return { error: (0, exports.fail)(401, 'invalid_client', 'Unknown client.') };
|
|
188
|
+
if (client.auth_method === 'private_key_jwt') {
|
|
189
|
+
const problem = secret !== undefined ? 'This client must authenticate with a private_key_jwt client assertion.' : await (0, emu4_oauth_auth_1.verifyClientAssertion)(e, c.body, client);
|
|
190
|
+
return problem ? { error: (0, exports.fail)(401, 'invalid_client', problem) } : { client };
|
|
191
|
+
}
|
|
138
192
|
const previousOk = client.previous_secret !== undefined && client.previous_secret === secret && e.clock.now() < client.previous_until;
|
|
193
|
+
if (client.auth_method !== 'none' && client.secret_expires_at && e.clock.now() >= client.secret_expires_at)
|
|
194
|
+
return { error: (0, exports.fail)(401, 'invalid_client', 'Client authentication failed.') };
|
|
139
195
|
if (client.auth_method !== 'none' && client.secret !== secret && !previousOk)
|
|
140
196
|
return { error: (0, exports.fail)(401, 'invalid_client', 'Client authentication failed.') };
|
|
141
197
|
return { client };
|
|
@@ -143,9 +199,10 @@ function authenticateClient(e, c) {
|
|
|
143
199
|
const splitScope = (s) => (typeof s === 'string' ? s.split(/\s+/).filter(Boolean) : []);
|
|
144
200
|
exports.splitScope = splitScope;
|
|
145
201
|
async function mintAccess(e, input) {
|
|
202
|
+
const ttl = input.ttl ?? ACCESS_TTL_S;
|
|
146
203
|
const jti = e.db.newId('oat');
|
|
147
204
|
const token = await e.tokens.signClaims({
|
|
148
|
-
sub: input.userId
|
|
205
|
+
sub: input.userId ? await (0, emu4_oauth_auth_1.subjectFor)(e, input.client, input.userId) : input.client.id,
|
|
149
206
|
aud: input.audience ?? input.client.id,
|
|
150
207
|
client_id: input.client.id,
|
|
151
208
|
scope: input.scope.join(' '),
|
|
@@ -153,9 +210,9 @@ async function mintAccess(e, input) {
|
|
|
153
210
|
token_use: 'access_token',
|
|
154
211
|
...(input.cnf ? { cnf: input.cnf } : {}),
|
|
155
212
|
...(input.extra ?? {}),
|
|
156
|
-
},
|
|
213
|
+
}, ttl);
|
|
157
214
|
const s = (0, exports.st)(e);
|
|
158
|
-
s.access.set(jti, { clientId: input.client.id, userId: input.userId, scope: input.scope, exp: e.clock.now() +
|
|
215
|
+
s.access.set(jti, { clientId: input.client.id, userId: input.userId, scope: input.scope, exp: e.clock.now() + ttl * 1000, revoked: false, grantId: input.grant?.id ?? null, cnf: input.cnf });
|
|
159
216
|
input.grant?.jtis.add(jti);
|
|
160
217
|
return { token, jti };
|
|
161
218
|
}
|
|
@@ -169,9 +226,14 @@ const userClaims = (e, userId, scope) => {
|
|
|
169
226
|
...(scope.includes('profile') ? { name: [u.first_name, u.last_name].filter(Boolean).join(' ') || undefined, picture: u.image_url ?? undefined, given_name: u.first_name ?? undefined, family_name: u.last_name ?? undefined } : {}),
|
|
170
227
|
};
|
|
171
228
|
};
|
|
229
|
+
exports.userClaims = userClaims;
|
|
172
230
|
async function issueTokens(e, client, grant, nonce, authTime, refreshRec, cnf) {
|
|
173
|
-
const access = await mintAccess(e, { client, userId: grant.userId, scope: grant.scope, grant, cnf });
|
|
231
|
+
const access = await mintAccess(e, { client, userId: grant.userId, scope: grant.scope, grant, cnf, extra: grant.managedId ? { grant_id: grant.managedId } : undefined });
|
|
174
232
|
const out = { access_token: access.token, token_type: cnf?.jkt ? 'DPoP' : 'Bearer', expires_in: ACCESS_TTL_S, scope: grant.scope.join(' ') };
|
|
233
|
+
if (grant.managedId)
|
|
234
|
+
out.grant_id = grant.managedId;
|
|
235
|
+
if (grant.details?.length)
|
|
236
|
+
out.authorization_details = grant.details;
|
|
175
237
|
if (grant.scope.includes('offline_access') || client.grant_types.includes('refresh_token')) {
|
|
176
238
|
const rt = refreshRec ?? `ort_${(0, store_1.randomToken)(32)}`;
|
|
177
239
|
if (!(0, exports.st)(e).refresh.has(rt))
|
|
@@ -179,17 +241,55 @@ async function issueTokens(e, client, grant, nonce, authTime, refreshRec, cnf) {
|
|
|
179
241
|
out.refresh_token = rt;
|
|
180
242
|
}
|
|
181
243
|
if (grant.scope.includes('openid') && grant.userId) {
|
|
182
|
-
out.id_token = await e
|
|
244
|
+
out.id_token = await idTokenFor(e, client, grant.userId, grant.scope, nonce, authTime, grant.sid);
|
|
245
|
+
(0, oauth_adv_1.recordRp)(e, grant.sid, client.id, grant.userId);
|
|
183
246
|
}
|
|
184
247
|
return out;
|
|
185
248
|
}
|
|
249
|
+
async function idTokenFor(e, client, userId, scope, nonce, authTime, sid, extra = {}) {
|
|
250
|
+
return e.tokens.signClaimsAs(client.id_token_signed_response_alg ?? null, { sub: await (0, emu4_oauth_auth_1.subjectFor)(e, client, userId), aud: client.id, azp: client.id, auth_time: Math.floor(authTime / 1000), ...(nonce ? { nonce } : {}), ...(sid ? { sid } : {}), token_use: 'id_token', ...(0, exports.userClaims)(e, userId, scope), ...extra }, ACCESS_TTL_S);
|
|
251
|
+
}
|
|
252
|
+
/** End a grant and every access token minted from it. */
|
|
253
|
+
function revokeGrant(e, g) {
|
|
254
|
+
g.revoked = true;
|
|
255
|
+
for (const j of g.jtis) {
|
|
256
|
+
const a = (0, exports.st)(e).access.get(j);
|
|
257
|
+
if (a)
|
|
258
|
+
a.revoked = true;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
/** A presented access token for a resource of this provider: signature, revocation and its sender constraint (DPoP proof or client certificate). */
|
|
262
|
+
async function checkResourceToken(c, bearer, scheme) {
|
|
263
|
+
const e = c.engine;
|
|
264
|
+
const v = await e.tokens.verifyAny(bearer);
|
|
265
|
+
const rec = v.ok ? (0, exports.st)(e).access.get(String(v.claims.jti)) : undefined;
|
|
266
|
+
if (!v.ok || v.claims.token_use !== 'access_token' || !rec || rec.revoked || (rec.grantId && (0, exports.st)(e).grants.get(rec.grantId)?.revoked))
|
|
267
|
+
return { error: true, message: 'The access token is invalid or expired.' };
|
|
268
|
+
const bad = { error: true, message: 'A valid DPoP proof for this token is required.' };
|
|
269
|
+
if (rec.cnf?.jkt) {
|
|
270
|
+
const proof = c.request.headers.get('dpop');
|
|
271
|
+
if (scheme.toLowerCase() !== 'dpop' || !proof)
|
|
272
|
+
return bad;
|
|
273
|
+
try {
|
|
274
|
+
const r = await (0, jws_1.verifyDpopProof)({ proof, htm: c.request.method, htu: c.request.url, nowMs: e.clock.now(), accessToken: bearer });
|
|
275
|
+
if (r.jkt !== rec.cnf.jkt || !(0, oauth_flows_1.claimDpopJti)(e, r.jkt, r.jti, (jws_1.DPOP_IAT_WINDOW_S + 5) * 1000))
|
|
276
|
+
return bad;
|
|
277
|
+
}
|
|
278
|
+
catch {
|
|
279
|
+
return bad;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
else if (rec.cnf?.['x5t#S256'] && (await (0, jws_1.forwardedCertThumbprint)(c.request.headers)) !== rec.cnf['x5t#S256'])
|
|
283
|
+
return { error: true, message: 'The token is bound to a client certificate that was not presented.' };
|
|
284
|
+
return { rec };
|
|
285
|
+
}
|
|
186
286
|
/** Create the grant and the single-use authorization code for an approved request (after login and, when needed, consent). */
|
|
187
287
|
function issueCode(e, a) {
|
|
188
|
-
const grant = { id: e.db.newId('oag'), clientId: a.client.id, userId: a.userId, scope: a.scope, revoked: false, jtis: new Set() };
|
|
288
|
+
const grant = { id: e.db.newId('oag'), clientId: a.client.id, userId: a.userId, scope: a.scope, revoked: false, jtis: new Set(), sid: (0, oauth_adv_1.sidFor)(e, a.userId), details: a.ext?.details ?? null };
|
|
189
289
|
(0, exports.st)(e).grants.set(grant.id, grant);
|
|
190
290
|
const code = `oc_${(0, store_1.randomToken)(24)}`;
|
|
191
291
|
const now = e.clock.now();
|
|
192
|
-
(0, exports.st)(e).codes.set(code, { grantId: grant.id, redirectUri: a.redirectUri, challenge: a.challenge, nonce: a.nonce, authTime: now, expiresAt: now + CODE_TTL_MS, used: false });
|
|
292
|
+
(0, exports.st)(e).codes.set(code, { grantId: grant.id, redirectUri: a.redirectUri, challenge: a.challenge, nonce: a.nonce, authTime: now, expiresAt: now + CODE_TTL_MS, used: false, ext: a.ext });
|
|
193
293
|
return code;
|
|
194
294
|
}
|
|
195
295
|
/** What one token request proved: a DPoP key (single-use proof) and, for clients that ask for it, a client certificate. */
|
|
@@ -221,27 +321,32 @@ async function provedOf(c, client) {
|
|
|
221
321
|
}
|
|
222
322
|
const cnfFor = (p) => (p.jkt || p.x5t ? { ...(p.jkt ? { jkt: p.jkt } : {}), ...(p.x5t ? { 'x5t#S256': p.x5t } : {}) } : undefined);
|
|
223
323
|
function oauthHandlers() {
|
|
224
|
-
const redirect = (uri, params) => {
|
|
225
|
-
const u = new URL(uri);
|
|
226
|
-
for (const [k, v] of Object.entries(params))
|
|
227
|
-
if (v !== undefined)
|
|
228
|
-
u.searchParams.set(k, v);
|
|
229
|
-
return { status: 302, headers: { location: u.toString(), 'cache-control': 'no-store' } };
|
|
230
|
-
};
|
|
231
324
|
return [
|
|
232
325
|
// ── client registry ──
|
|
233
326
|
{
|
|
234
327
|
method: 'POST',
|
|
235
328
|
pattern: '/v1/oauth_clients',
|
|
236
|
-
handler: (c) => {
|
|
329
|
+
handler: async (c) => {
|
|
237
330
|
const e = c.engine;
|
|
238
331
|
const name = String((0, util_1.need)(c.body, 'name'));
|
|
239
332
|
const uris = c.body.redirect_uris;
|
|
240
333
|
if (!Array.isArray(uris) || !uris.length || !uris.every((u) => typeof u === 'string'))
|
|
241
334
|
throw new errors_1.EmulatorError('VALIDATION_FAILED', 'redirect_uris needs at least one URI.', 'redirect_uris');
|
|
242
335
|
const method = c.body.token_endpoint_auth_method ?? 'client_secret_basic';
|
|
336
|
+
if (!emu4_oauth_auth_1.AUTH_METHODS.includes(method))
|
|
337
|
+
throw new errors_1.EmulatorError('VALIDATION_FAILED', `token_endpoint_auth_method must be one of: ${emu4_oauth_auth_1.AUTH_METHODS.join(', ')}.`, 'token_endpoint_auth_method');
|
|
338
|
+
const flowFields = pickFlowFields(c.body);
|
|
339
|
+
const keyedProblem = (0, emu4_oauth_auth_1.keyedClientProblem)(method, flowFields.jwks, flowFields.jwks_uri);
|
|
340
|
+
if (keyedProblem)
|
|
341
|
+
throw new errors_1.EmulatorError('VALIDATION_FAILED', keyedProblem, 'jwks');
|
|
342
|
+
const subject = await (0, emu4_oauth_auth_1.resolveSubject)(e, { subject_type: c.body.subject_type, sector_identifier_uri: c.body.sector_identifier_uri, redirect_uris: uris, redirectUrisChanged: true });
|
|
343
|
+
if (!subject.ok)
|
|
344
|
+
throw new errors_1.EmulatorError('VALIDATION_FAILED', subject.message, subject.param);
|
|
345
|
+
const rtProblem = (0, oauth_adv_1.responseTypeProblem)((0, oauth_adv_1.clientResponseTypes)(c.body), c.body.grant_types ?? ['authorization_code', 'refresh_token']);
|
|
346
|
+
if (rtProblem)
|
|
347
|
+
throw new errors_1.EmulatorError('VALIDATION_FAILED', rtProblem, 'response_types');
|
|
243
348
|
const now = e.clock.now();
|
|
244
|
-
const secret = method === 'none' ? undefined : `oacs_${(0, store_1.randomToken)(32)}`;
|
|
349
|
+
const secret = method === 'none' || method === 'private_key_jwt' ? undefined : `oacs_${(0, store_1.randomToken)(32)}`;
|
|
245
350
|
const row = e.db.oauthClients.insert({
|
|
246
351
|
name,
|
|
247
352
|
redirect_uris: uris,
|
|
@@ -250,7 +355,8 @@ function oauthHandlers() {
|
|
|
250
355
|
auth_method: method,
|
|
251
356
|
first_party: c.body.first_party === true,
|
|
252
357
|
post_logout_redirect_uris: c.body.post_logout_redirect_uris ?? [],
|
|
253
|
-
...
|
|
358
|
+
...flowFields,
|
|
359
|
+
...subject.value,
|
|
254
360
|
secret,
|
|
255
361
|
created_at: now,
|
|
256
362
|
updated_at: now,
|
|
@@ -264,13 +370,27 @@ function oauthHandlers() {
|
|
|
264
370
|
{
|
|
265
371
|
method: 'PATCH',
|
|
266
372
|
pattern: '/v1/oauth_clients/:id',
|
|
267
|
-
handler: (c) => {
|
|
373
|
+
handler: async (c) => {
|
|
268
374
|
const e = c.engine;
|
|
269
375
|
const row = liveClient(e, c.params.id);
|
|
270
|
-
|
|
376
|
+
const next = { ...row };
|
|
377
|
+
for (const k of ['name', 'redirect_uris', 'allowed_scopes', 'grant_types', 'first_party', 'post_logout_redirect_uris'])
|
|
271
378
|
if (k in c.body)
|
|
272
|
-
|
|
273
|
-
Object.assign(
|
|
379
|
+
next[k] = c.body[k];
|
|
380
|
+
Object.assign(next, pickFlowFields(c.body, row));
|
|
381
|
+
const keyedProblem = (0, emu4_oauth_auth_1.keyedClientProblem)(next.auth_method, next.jwks, next.jwks_uri);
|
|
382
|
+
if (keyedProblem)
|
|
383
|
+
throw new errors_1.EmulatorError('VALIDATION_FAILED', keyedProblem, 'jwks');
|
|
384
|
+
if (c.body.subject_type !== undefined || c.body.sector_identifier_uri !== undefined || c.body.redirect_uris !== undefined) {
|
|
385
|
+
const subject = await (0, emu4_oauth_auth_1.resolveSubject)(e, { subject_type: c.body.subject_type, sector_identifier_uri: c.body.sector_identifier_uri, redirect_uris: next.redirect_uris, redirectUrisChanged: c.body.redirect_uris !== undefined, existing: row, publisherSector: (0, emu4_oauth_pub_1.publisherSectorOf)(e, row) });
|
|
386
|
+
if (!subject.ok)
|
|
387
|
+
throw new errors_1.EmulatorError('VALIDATION_FAILED', subject.message, subject.param);
|
|
388
|
+
Object.assign(next, subject.value);
|
|
389
|
+
}
|
|
390
|
+
const rtProblem = (0, oauth_adv_1.responseTypeProblem)((0, oauth_adv_1.clientResponseTypes)(next), next.grant_types);
|
|
391
|
+
if (rtProblem)
|
|
392
|
+
throw new errors_1.EmulatorError('VALIDATION_FAILED', rtProblem, 'response_types');
|
|
393
|
+
Object.assign(row, next); // validated first: a refused PATCH changes nothing
|
|
274
394
|
row.updated_at = e.clock.now();
|
|
275
395
|
e.changed({ type: 'oauth_client.updated', data: clientShape(e, row) });
|
|
276
396
|
return { body: clientShape(e, row) };
|
|
@@ -322,33 +442,76 @@ function oauthHandlers() {
|
|
|
322
442
|
method: 'GET',
|
|
323
443
|
pattern: '/oauth2/authorize',
|
|
324
444
|
public: true,
|
|
325
|
-
handler: (c) => {
|
|
445
|
+
handler: async (c) => {
|
|
326
446
|
const e = c.engine;
|
|
327
|
-
const
|
|
447
|
+
const rawUri = c.query.get('request_uri');
|
|
448
|
+
// An https request_uri names a request object to fetch (RFC 9101); anything else is a pushed request (RFC 9126).
|
|
449
|
+
const jarUri = rawUri && /^https:\/\//i.test(rawUri) ? rawUri : null;
|
|
450
|
+
const resolved = jarUri ? { query: c.query } : (0, oauth_flows_1.resolveRequestUri)(e, c.query);
|
|
328
451
|
if ('error' in resolved)
|
|
329
452
|
return (0, exports.fail)(400, resolved.error.code, resolved.error.description);
|
|
453
|
+
const parUsed = !!rawUri && !jarUri;
|
|
330
454
|
const q = resolved.query;
|
|
331
455
|
const client = e.db.oauthClients.get(q.get('client_id') ?? undefined);
|
|
332
456
|
if (!client || client.deleted)
|
|
333
457
|
return (0, exports.fail)(400, 'invalid_request', 'Unknown client_id.');
|
|
334
|
-
|
|
458
|
+
if (client.require_pushed_authorization_requests && !parUsed)
|
|
459
|
+
return (0, exports.fail)(400, 'invalid_request', 'This client must push its authorization request (POST /oauth2/par) and send the request_uri.');
|
|
460
|
+
const p = Object.fromEntries(q.entries());
|
|
461
|
+
let rarRaw = p.authorization_details;
|
|
462
|
+
if (client.require_signed_request_object === true && !parUsed && !p.request && !jarUri)
|
|
463
|
+
return (0, exports.fail)(400, 'invalid_request', 'This client must send a signed request object.');
|
|
464
|
+
// A signed request object supersedes the query parameters; it is verified BEFORE the redirect_uri is trusted.
|
|
465
|
+
if (p.request || jarUri) {
|
|
466
|
+
const loaded = await (0, oauth_adv_1.loadRequestObject)(e, client, { request: p.request, requestUri: jarUri ?? undefined });
|
|
467
|
+
if (!loaded.ok)
|
|
468
|
+
return (0, exports.fail)(400, loaded.error, loaded.description);
|
|
469
|
+
for (const k of oauth_adv_1.REQUEST_OBJECT_PARAMS)
|
|
470
|
+
if (typeof loaded.claims[k] === 'string')
|
|
471
|
+
p[k] = loaded.claims[k];
|
|
472
|
+
if (loaded.claims.authorization_details !== undefined)
|
|
473
|
+
rarRaw = loaded.claims.authorization_details;
|
|
474
|
+
delete p.request;
|
|
475
|
+
}
|
|
476
|
+
const redirectUri = p.redirect_uri ?? (client.redirect_uris.length === 1 ? client.redirect_uris[0] : null);
|
|
335
477
|
if (!redirectUri || !client.redirect_uris.includes(redirectUri))
|
|
336
478
|
return (0, exports.fail)(400, 'invalid_request', 'redirect_uri is not registered for this client.');
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
const
|
|
340
|
-
const back = (params) =>
|
|
341
|
-
if (
|
|
342
|
-
return back({ error: 'unsupported_response_type', error_description: '
|
|
343
|
-
|
|
479
|
+
const state = p.state;
|
|
480
|
+
const rt = (0, oauth_adv_1.parseResponseType)(p.response_type);
|
|
481
|
+
const mode = p.response_mode ?? null;
|
|
482
|
+
const back = (params) => (0, emu4_oauth_auth_1.authReply)(e, client, redirectUri, (0, oauth_adv_1.authUrl)(e, redirectUri, { ...params, state, iss: e.issuer }, !!rt?.frontChannelTokens, mode), mode);
|
|
483
|
+
if (!rt)
|
|
484
|
+
return back({ error: 'unsupported_response_type', error_description: 'The response_type is not supported.' });
|
|
485
|
+
if (!(0, oauth_adv_1.clientResponseTypes)(client).includes(rt.canonical))
|
|
486
|
+
return back({ error: 'unauthorized_client', error_description: 'This client is not registered for this response_type.' });
|
|
487
|
+
if (mode && !['query', 'fragment', ...emu4_oauth_auth_1.EXT_RESPONSE_MODES].includes(mode))
|
|
488
|
+
return back({ error: 'invalid_request', error_description: 'The response_mode is not supported.' });
|
|
489
|
+
if (mode && !(0, oauth_adv_1.responseModeAllowed)(rt, mode))
|
|
490
|
+
return back({ error: 'invalid_request', error_description: 'A response_type that returns tokens cannot use the query response mode.' });
|
|
491
|
+
// OIDC Core 3.2.2.1 / 3.3.2.1: the nonce is required whenever an ID token comes back from the authorization endpoint.
|
|
492
|
+
if (rt.idToken && !p.nonce)
|
|
493
|
+
return back({ error: 'invalid_request', error_description: 'The nonce parameter is required for this response_type.' });
|
|
494
|
+
const gmParsed = (0, oauth_adv_1.parseGrantManagement)(p.grant_management_action, p.grant_id);
|
|
495
|
+
if (!gmParsed.ok)
|
|
496
|
+
return back({ error: 'invalid_request', error_description: gmParsed.message });
|
|
497
|
+
const gm = gmParsed.value;
|
|
498
|
+
if (rt.code && !client.grant_types.includes('authorization_code'))
|
|
499
|
+
return back({ error: 'unauthorized_client', error_description: 'This client is not permitted the authorization_code grant.' });
|
|
500
|
+
if (rt.frontChannelTokens && !client.grant_types.includes('implicit'))
|
|
501
|
+
return back({ error: 'unauthorized_client', error_description: 'This client is not permitted the implicit grant.' });
|
|
502
|
+
const scope = (0, exports.splitScope)(p.scope ?? 'openid');
|
|
344
503
|
const outside = scope.filter((s) => !client.allowed_scopes.includes(s) && !SCOPES.includes(s));
|
|
345
504
|
if (outside.length)
|
|
346
505
|
return back({ error: 'invalid_scope', error_description: `Scope not allowed: ${outside.join(' ')}.` });
|
|
347
|
-
const challenge =
|
|
348
|
-
if (challenge && (
|
|
506
|
+
const challenge = p.code_challenge ?? null;
|
|
507
|
+
if (challenge && (p.code_challenge_method ?? 'plain') !== 'S256')
|
|
349
508
|
return back({ error: 'invalid_request', error_description: 'code_challenge_method must be S256.' });
|
|
350
|
-
if (!challenge && client.auth_method === 'none')
|
|
509
|
+
if (!challenge && rt.code && client.auth_method === 'none')
|
|
351
510
|
return back({ error: 'invalid_request', error_description: 'A public client must use PKCE (code_challenge).' });
|
|
511
|
+
// RFC 9396: authorization_details against the instance's type allowlist (an empty allowlist refuses any type).
|
|
512
|
+
const rar = (0, oauth_adv_1.validateAuthorizationDetails)(rarRaw, (0, oauth_adv_1.adv)(e).cfg.authorization_details_types);
|
|
513
|
+
if (!rar.ok)
|
|
514
|
+
return back({ error: 'invalid_authorization_details', error_description: rar.message });
|
|
352
515
|
if (q.get('emulator_decision') === 'deny')
|
|
353
516
|
return back({ error: 'access_denied', error_description: 'The user denied the request.' });
|
|
354
517
|
const hint = q.get('login_hint');
|
|
@@ -361,10 +524,17 @@ function oauthHandlers() {
|
|
|
361
524
|
return back({ error: 'login_required', error_description: 'No user to sign in. Pass login_hint or POST /__emulator/oauth/login.' });
|
|
362
525
|
if (user.banned)
|
|
363
526
|
return back({ error: 'access_denied', error_description: 'The user is banned.' });
|
|
364
|
-
const
|
|
527
|
+
const details = rar.value ?? null;
|
|
528
|
+
// Grant management: a merge or replace names this client's and this user's active grant; adding anything to it asks the user again.
|
|
529
|
+
const gmProblem = (0, oauth_adv_1.gmValid)(e, gm, client.id, user.id);
|
|
530
|
+
if (gmProblem)
|
|
531
|
+
return back({ error: 'invalid_grant', error_description: gmProblem });
|
|
532
|
+
const widens = !!gm && gm.action !== 'create' && (0, oauth_adv_1.gmWidens)(gm, (0, oauth_adv_1.managedFor)(e, gm.grantId, client.id, user.id), scope, details);
|
|
533
|
+
const ext = { rt: rt.canonical, mode, details, gm };
|
|
534
|
+
const gate = await (0, oauth_flows_1.consentGate)(e, { client, user, scope, redirectUri, challenge, nonce: p.nonce ?? null, state, prompt: q.get('prompt'), ext, force: widens });
|
|
365
535
|
if (gate)
|
|
366
536
|
return gate;
|
|
367
|
-
return
|
|
537
|
+
return (0, emu4_oauth_auth_1.authReply)(e, client, redirectUri, await (0, oauth_adv_1.completeAuth)(e, { client, userId: user.id, scope, redirectUri, challenge, nonce: p.nonce ?? null, state, ext }), mode);
|
|
368
538
|
},
|
|
369
539
|
},
|
|
370
540
|
{ method: 'POST', pattern: '/__emulator/oauth/login', public: true, handler: (c) => (((0, exports.st)(c.engine).autoUser = typeof c.body.user_id === 'string' ? c.body.user_id : null), { body: { ok: true } }) },
|
|
@@ -375,14 +545,15 @@ function oauthHandlers() {
|
|
|
375
545
|
public: true,
|
|
376
546
|
handler: async (c) => {
|
|
377
547
|
const e = c.engine;
|
|
378
|
-
const auth = authenticateClient(e, c);
|
|
548
|
+
const auth = await authenticateClient(e, c);
|
|
379
549
|
if ('error' in auth)
|
|
380
550
|
return auth.error;
|
|
381
551
|
const client = auth.client;
|
|
382
552
|
const type = String(c.body.grant_type ?? '');
|
|
383
553
|
if (!type)
|
|
384
554
|
return (0, exports.fail)(400, 'invalid_request', 'grant_type is required.');
|
|
385
|
-
|
|
555
|
+
const umaPat = type === 'client_credentials' && !c.body.audience && (0, exports.splitScope)(c.body.scope).includes('uma_protection') && (0, oauth_adv_1.umaEnabled)(e);
|
|
556
|
+
if (type !== EXCHANGE && type !== oauth_adv_1.UMA_GRANT_TYPE && !umaPat && !client.grant_types.includes(type) && !(type === 'refresh_token'))
|
|
386
557
|
return (0, exports.fail)(400, 'unauthorized_client', `This client may not use ${type}.`);
|
|
387
558
|
const s = (0, exports.st)(e);
|
|
388
559
|
const now = e.clock.now();
|
|
@@ -393,6 +564,10 @@ function oauthHandlers() {
|
|
|
393
564
|
const ext = await (0, oauth_flows_1.extensionGrant)(c, client, type, cnf);
|
|
394
565
|
if (ext)
|
|
395
566
|
return ext;
|
|
567
|
+
if (umaPat)
|
|
568
|
+
return client.auth_method === 'none' ? (0, exports.fail)(401, 'invalid_client', 'A protection API token requires a confidential client.') : (0, oauth_adv_1.umaProtectionToken)(c, client);
|
|
569
|
+
if (type === oauth_adv_1.UMA_GRANT_TYPE && (0, oauth_adv_1.umaEnabled)(e))
|
|
570
|
+
return (0, oauth_adv_1.umaTicketGrant)(c, client);
|
|
396
571
|
if (type === 'authorization_code') {
|
|
397
572
|
const rec = s.codes.get(String(c.body.code ?? ''));
|
|
398
573
|
if (!rec)
|
|
@@ -422,6 +597,12 @@ function oauthHandlers() {
|
|
|
422
597
|
rec.used = true;
|
|
423
598
|
if (pr.proved.jkt)
|
|
424
599
|
grant.jkt = pr.proved.jkt;
|
|
600
|
+
// OpenID grant management: apply the create / merge / replace the code carried; the tokens are for the grant as it stands afterwards.
|
|
601
|
+
if (rec.ext?.gm) {
|
|
602
|
+
const applied = (0, oauth_adv_1.applyGrantManagement)(e, grant, rec.ext.gm);
|
|
603
|
+
if (applied)
|
|
604
|
+
return (0, exports.fail)(400, applied.error, applied.message);
|
|
605
|
+
}
|
|
425
606
|
return { body: await issueTokens(e, client, grant, rec.nonce, rec.authTime, undefined, cnf), headers: { 'cache-control': 'no-store', pragma: 'no-cache' } };
|
|
426
607
|
}
|
|
427
608
|
if (type === 'refresh_token') {
|
|
@@ -430,7 +611,7 @@ function oauthHandlers() {
|
|
|
430
611
|
const grant = rec ? s.grants.get(rec.grantId) : undefined;
|
|
431
612
|
if (!rec || !grant || grant.clientId !== client.id)
|
|
432
613
|
return (0, exports.fail)(400, 'invalid_grant', 'The refresh token is invalid.');
|
|
433
|
-
if (grant.revoked)
|
|
614
|
+
if (grant.revoked || (grant.managedId && (0, oauth_adv_1.adv)(e).managed.get(grant.managedId)?.status !== 'active'))
|
|
434
615
|
return (0, exports.fail)(400, 'invalid_grant', 'The grant was revoked.');
|
|
435
616
|
// A DPoP-bound refresh token is usable only with the key it was bound to (RFC 9449 section 8).
|
|
436
617
|
if (grant.jkt && pr.proved.jkt !== grant.jkt)
|
|
@@ -466,6 +647,8 @@ function oauthHandlers() {
|
|
|
466
647
|
const a = await mintAccess(e, { client, userId: null, scope, grant: null, audience: typeof c.body.audience === 'string' ? c.body.audience : undefined, cnf });
|
|
467
648
|
return { body: { access_token: a.token, token_type: cnf?.jkt ? 'DPoP' : 'Bearer', expires_in: ACCESS_TTL_S, scope: scope.join(' ') }, headers: { 'cache-control': 'no-store' } };
|
|
468
649
|
}
|
|
650
|
+
if (type === EXCHANGE && c.body.subject_token_type === emu4_oauth_pub_1.GOOGLE_ACCESS_TOKEN_TYPE)
|
|
651
|
+
return (0, emu4_oauth_pub_1.googleTokenExchange)(c, client);
|
|
469
652
|
if (type === EXCHANGE) {
|
|
470
653
|
const subject = String(c.body.subject_token ?? '');
|
|
471
654
|
if (!subject)
|
|
@@ -501,9 +684,9 @@ function oauthHandlers() {
|
|
|
501
684
|
method: 'POST',
|
|
502
685
|
pattern: '/oauth2/revoke',
|
|
503
686
|
public: true,
|
|
504
|
-
handler: (c) => {
|
|
687
|
+
handler: async (c) => {
|
|
505
688
|
const e = c.engine;
|
|
506
|
-
const auth = authenticateClient(e, c);
|
|
689
|
+
const auth = await authenticateClient(e, c);
|
|
507
690
|
if ('error' in auth)
|
|
508
691
|
return auth.error;
|
|
509
692
|
const token = String(c.body.token ?? '');
|
|
@@ -541,7 +724,7 @@ function oauthHandlers() {
|
|
|
541
724
|
public: true,
|
|
542
725
|
handler: async (c) => {
|
|
543
726
|
const e = c.engine;
|
|
544
|
-
const auth = authenticateClient(e, c);
|
|
727
|
+
const auth = await authenticateClient(e, c);
|
|
545
728
|
if ('error' in auth)
|
|
546
729
|
return auth.error;
|
|
547
730
|
const token = String(c.body.token ?? '');
|
|
@@ -552,7 +735,7 @@ function oauthHandlers() {
|
|
|
552
735
|
const g = s.grants.get(rt.grantId);
|
|
553
736
|
if (!g || g.revoked || rt.rotatedAt !== undefined || g.clientId !== auth.client.id)
|
|
554
737
|
return inactive;
|
|
555
|
-
return { body: { active: true, token_use: 'refresh_token', client_id: g.clientId, sub: g.userId, scope: g.scope.join(' ') }, headers: { 'cache-control': 'no-store' } };
|
|
738
|
+
return { body: { active: true, token_use: 'refresh_token', client_id: g.clientId, ...(g.userId ? { sub: await (0, emu4_oauth_auth_1.subjectFor)(e, auth.client, g.userId) } : {}), scope: g.scope.join(' '), ...(g.details?.length ? { authorization_details: g.details } : {}), ...(g.managedId ? { grant_id: g.managedId } : {}) }, headers: { 'cache-control': 'no-store' } };
|
|
556
739
|
}
|
|
557
740
|
const v = await e.tokens.verifyAny(token);
|
|
558
741
|
if (!v.ok || v.claims.token_use !== 'access_token')
|
|
@@ -560,9 +743,11 @@ function oauthHandlers() {
|
|
|
560
743
|
const rec = s.access.get(String(v.claims.jti));
|
|
561
744
|
if (!rec || rec.revoked || (rec.grantId && s.grants.get(rec.grantId)?.revoked))
|
|
562
745
|
return inactive;
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
746
|
+
// another client's token: do not leak (a resource server may introspect the tokens issued for it)
|
|
747
|
+
if (rec.clientId !== auth.client.id && v.claims.uma_rs !== auth.client.id)
|
|
748
|
+
return inactive;
|
|
749
|
+
const granted = rec.grantId ? s.grants.get(rec.grantId) : undefined;
|
|
750
|
+
return { body: { active: true, token_type: rec.cnf?.jkt ? 'DPoP' : 'Bearer', ...(granted?.details?.length ? { authorization_details: granted.details } : {}), ...(v.claims.grant_id ? { grant_id: v.claims.grant_id } : {}), ...(v.claims.permissions ? { permissions: v.claims.permissions, uma_rs: v.claims.uma_rs } : {}), ...(rec.cnf ? { cnf: rec.cnf } : {}), token_use: 'access_token', client_id: rec.clientId, sub: v.claims.sub, aud: v.claims.aud, iss: v.claims.iss, scope: rec.scope.join(' '), exp: v.claims.exp, iat: v.claims.iat, jti: v.claims.jti, ...(v.claims.act ? { act: v.claims.act } : {}) }, headers: { 'cache-control': 'no-store' } };
|
|
566
751
|
},
|
|
567
752
|
},
|
|
568
753
|
...['GET', 'POST'].map((method) => ({
|
|
@@ -600,7 +785,7 @@ function oauthHandlers() {
|
|
|
600
785
|
}
|
|
601
786
|
if (!rec.scope.includes('openid'))
|
|
602
787
|
return { status: 403, headers: { 'www-authenticate': 'Bearer error="insufficient_scope", scope="openid"' }, body: { error: 'insufficient_scope', error_description: 'The openid scope is required.' } };
|
|
603
|
-
return { body: { sub: rec.userId, ...userClaims(e, rec.userId, rec.scope) } };
|
|
788
|
+
return { body: { sub: await (0, emu4_oauth_auth_1.subjectFor)(e, e.db.oauthClients.get(rec.clientId), rec.userId), ...(0, exports.userClaims)(e, rec.userId, rec.scope) } };
|
|
604
789
|
},
|
|
605
790
|
})),
|
|
606
791
|
];
|