@authup/server-core 1.0.0-beta.62 → 1.0.0-beta.63
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/adapters/database/domains/identity-provider-attribute/subscriber.d.ts +8 -1
- package/dist/adapters/database/domains/identity-provider-attribute/subscriber.d.ts.map +1 -1
- package/dist/adapters/database/domains/identity-provider-attribute/subscriber.mjs +14 -0
- package/dist/adapters/database/domains/identity-provider-attribute/subscriber.mjs.map +1 -1
- package/dist/adapters/http/adapters/oauth2/grant-types/refresh_token.d.ts.map +1 -1
- package/dist/adapters/http/adapters/oauth2/grant-types/refresh_token.mjs +8 -2
- package/dist/adapters/http/adapters/oauth2/grant-types/refresh_token.mjs.map +1 -1
- package/dist/adapters/http/controllers/entities/client/module.mjs +1 -1
- package/dist/adapters/http/controllers/entities/identity-provider/module.d.ts +23 -1
- package/dist/adapters/http/controllers/entities/identity-provider/module.d.ts.map +1 -1
- package/dist/adapters/http/controllers/entities/identity-provider/module.mjs +85 -46
- package/dist/adapters/http/controllers/entities/identity-provider/module.mjs.map +1 -1
- package/dist/adapters/http/controllers/workflows/authenticator-challenge/module.d.ts +2 -0
- package/dist/adapters/http/controllers/workflows/authenticator-challenge/module.d.ts.map +1 -1
- package/dist/adapters/http/controllers/workflows/authenticator-challenge/module.mjs +21 -3
- package/dist/adapters/http/controllers/workflows/authenticator-challenge/module.mjs.map +1 -1
- package/dist/adapters/http/controllers/workflows/authorize/module.d.ts.map +1 -1
- package/dist/adapters/http/controllers/workflows/authorize/module.mjs +5 -0
- package/dist/adapters/http/controllers/workflows/authorize/module.mjs.map +1 -1
- package/dist/adapters/http/controllers/workflows/token/module.d.ts.map +1 -1
- package/dist/adapters/http/controllers/workflows/token/module.mjs +22 -9
- package/dist/adapters/http/controllers/workflows/token/module.mjs.map +1 -1
- package/dist/adapters/http/middleware/built-in/cors.d.ts.map +1 -1
- package/dist/adapters/http/middleware/built-in/cors.mjs.map +1 -1
- package/dist/adapters/http/middleware/built-in/error.d.ts.map +1 -1
- package/dist/adapters/http/middleware/built-in/error.mjs +10 -1
- package/dist/adapters/http/middleware/built-in/error.mjs.map +1 -1
- package/dist/adapters/http/ui/console-packages/module.d.ts +1 -1
- package/dist/adapters/http/ui/console-packages/module.mjs +2 -2
- package/dist/adapters/http/ui/console-packages/module.mjs.map +1 -1
- package/dist/app/modules/config/normalize.d.ts.map +1 -1
- package/dist/app/modules/config/normalize.mjs +2 -2
- package/dist/app/modules/config/normalize.mjs.map +1 -1
- package/dist/app/modules/config/types.d.ts +8 -0
- package/dist/app/modules/config/types.d.ts.map +1 -1
- package/dist/app/modules/database/repositories/identity-provider/repository.d.ts +3 -1
- package/dist/app/modules/database/repositories/identity-provider/repository.d.ts.map +1 -1
- package/dist/app/modules/database/repositories/identity-provider/repository.mjs +2 -2
- package/dist/app/modules/database/repositories/identity-provider/repository.mjs.map +1 -1
- package/dist/app/modules/http/modules/controller.d.ts.map +1 -1
- package/dist/app/modules/http/modules/controller.mjs +5 -2
- package/dist/app/modules/http/modules/controller.mjs.map +1 -1
- package/dist/app/modules/oauth2/constants.d.ts +2 -1
- package/dist/app/modules/oauth2/constants.d.ts.map +1 -1
- package/dist/app/modules/oauth2/constants.mjs +1 -0
- package/dist/app/modules/oauth2/constants.mjs.map +1 -1
- package/dist/app/modules/oauth2/module.d.ts.map +1 -1
- package/dist/app/modules/oauth2/module.mjs +5 -0
- package/dist/app/modules/oauth2/module.mjs.map +1 -1
- package/dist/app/modules/oauth2/repositories/constants.d.ts +1 -0
- package/dist/app/modules/oauth2/repositories/constants.d.ts.map +1 -1
- package/dist/app/modules/oauth2/repositories/constants.mjs +1 -0
- package/dist/app/modules/oauth2/repositories/constants.mjs.map +1 -1
- package/dist/app/modules/oauth2/repositories/federated-login/index.d.ts +2 -0
- package/dist/app/modules/oauth2/repositories/federated-login/index.d.ts.map +1 -0
- package/dist/app/modules/oauth2/repositories/federated-login/index.mjs +5 -0
- package/dist/app/modules/oauth2/repositories/federated-login/store.d.ts +9 -0
- package/dist/app/modules/oauth2/repositories/federated-login/store.d.ts.map +1 -0
- package/dist/app/modules/oauth2/repositories/federated-login/store.mjs +33 -0
- package/dist/app/modules/oauth2/repositories/federated-login/store.mjs.map +1 -0
- package/dist/app/modules/oauth2/repositories/index.d.ts +1 -0
- package/dist/app/modules/oauth2/repositories/index.d.ts.map +1 -1
- package/dist/app/modules/oauth2/repositories/index.mjs +3 -1
- package/dist/core/entities/event/entity-event-handler.d.ts.map +1 -1
- package/dist/core/entities/event/entity-event-handler.mjs +14 -1
- package/dist/core/entities/event/entity-event-handler.mjs.map +1 -1
- package/dist/core/entities/identity-provider/types.d.ts +9 -1
- package/dist/core/entities/identity-provider/types.d.ts.map +1 -1
- package/dist/core/identity/index.mjs +2 -1
- package/dist/core/identity/provider/account/module.mjs +1 -1
- package/dist/core/identity/provider/authentication/index.mjs +2 -1
- package/dist/core/identity/provider/authentication/protocols/index.mjs +2 -1
- package/dist/core/identity/provider/authentication/protocols/oauth2/assurance.d.ts +46 -0
- package/dist/core/identity/provider/authentication/protocols/oauth2/assurance.d.ts.map +1 -0
- package/dist/core/identity/provider/authentication/protocols/oauth2/assurance.mjs +89 -0
- package/dist/core/identity/provider/authentication/protocols/oauth2/assurance.mjs.map +1 -0
- package/dist/core/identity/provider/authentication/protocols/oauth2/index.d.ts +1 -0
- package/dist/core/identity/provider/authentication/protocols/oauth2/index.d.ts.map +1 -1
- package/dist/core/identity/provider/authentication/protocols/oauth2/index.mjs +2 -1
- package/dist/core/identity/provider/authentication/protocols/oauth2/module.d.ts.map +1 -1
- package/dist/core/identity/provider/authentication/protocols/oauth2/module.mjs +9 -2
- package/dist/core/identity/provider/authentication/protocols/oauth2/module.mjs.map +1 -1
- package/dist/core/identity/provider/index.mjs +2 -1
- package/dist/core/index.mjs +4 -2
- package/dist/core/oauth2/authorization/helpers.d.ts.map +1 -1
- package/dist/core/oauth2/authorization/helpers.mjs +6 -1
- package/dist/core/oauth2/authorization/helpers.mjs.map +1 -1
- package/dist/core/oauth2/authorization/module.d.ts.map +1 -1
- package/dist/core/oauth2/authorization/module.mjs +5 -3
- package/dist/core/oauth2/authorization/module.mjs.map +1 -1
- package/dist/core/oauth2/authorization/state/types.d.ts +8 -0
- package/dist/core/oauth2/authorization/state/types.d.ts.map +1 -1
- package/dist/core/oauth2/federated-login/constants.d.ts +22 -0
- package/dist/core/oauth2/federated-login/constants.d.ts.map +1 -0
- package/dist/core/oauth2/federated-login/constants.mjs +27 -0
- package/dist/core/oauth2/federated-login/constants.mjs.map +1 -0
- package/dist/core/oauth2/federated-login/index.d.ts +1 -0
- package/dist/core/oauth2/federated-login/index.d.ts.map +1 -1
- package/dist/core/oauth2/federated-login/index.mjs +2 -1
- package/dist/core/oauth2/federated-login/module.d.ts +12 -3
- package/dist/core/oauth2/federated-login/module.d.ts.map +1 -1
- package/dist/core/oauth2/federated-login/module.mjs +96 -40
- package/dist/core/oauth2/federated-login/module.mjs.map +1 -1
- package/dist/core/oauth2/federated-login/types.d.ts +82 -24
- package/dist/core/oauth2/federated-login/types.d.ts.map +1 -1
- package/dist/core/oauth2/federated-login/types.mjs +5 -0
- package/dist/core/oauth2/federated-login/types.mjs.map +1 -1
- package/dist/core/oauth2/grant-types/authorize.mjs +1 -1
- package/dist/core/oauth2/grant-types/client-credentials.mjs +1 -1
- package/dist/core/oauth2/grant-types/identity.mjs +1 -1
- package/dist/core/oauth2/grant-types/password.mjs +1 -1
- package/dist/core/oauth2/grant-types/refresh-token.mjs +1 -1
- package/dist/core/oauth2/index.mjs +3 -2
- package/dist/core/oauth2/token/verifier/module.d.ts.map +1 -1
- package/dist/core/oauth2/token/verifier/module.mjs +3 -3
- package/dist/core/oauth2/token/verifier/module.mjs.map +1 -1
- package/dist/swagger.json +107 -59
- package/package.json +13 -13
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module.mjs","names":["isSafeRedirectURLScheme","EventName","EventRefType","EventScope","IdentityType","SessionAuthMethod","InternalError","OAuth2ErrorCode","OAuth2RequestError","isOAuth2Error","createIdentityProviderOAuth2Authenticator","toIdentityPolicyData","OAuth2FederatedLoginRefusal","OAuth2FederatedLoginService","options","accountManager","realmRepository","codeRequestVerifier","codeIssuer","accessPolicyEvaluator","eventService","logger","authenticatorFactory","ctx","provider","baseURL","clientId","complete","input","code","codeRequest","verified","verify","e","kind","refusal","CODE_REQUEST","realmId","client","malformed","redirectUri","data","redirect_uri","redirectUriVerified","enabled","PROVIDER_DISABLED","error","LOGIN_REQUIRED","authenticator","client_id","user","authenticate","active","USER_INACTIVE","ACCESS_DENIED","realm","resolve","accessPolicyId","allowed","subject","type","USER","evaluate","authorizationCode","issue","authMethod","EXTERNAL","record","scope","OAUTH2","name","AUTHORIZE","refType","CLIENT","refId","sessionId","actorType","actorId","id","actorName","realm_id","requestIpAddress","request","ipAddress","requestUserAgent","userAgent","reason","providerId","providerName","state","displayName"],"sources":["../../../../src/core/oauth2/federated-login/module.ts"],"sourcesContent":["/*\n * Copyright (c) 2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport { isSafeRedirectURLScheme } from '@authup/kit';\nimport {\n EventName,\n EventRefType,\n EventScope,\n IdentityType,\n SessionAuthMethod,\n} from '@authup/core-kit';\nimport { InternalError } from '@authup/errors';\nimport type { Logger } from '@authup/server-kit';\nimport { OAuth2ErrorCode, OAuth2RequestError, isOAuth2Error } from '@authup/specs';\nimport type { IEventService, IRealmRepository } from '../../entities/index.ts';\n// Deep imports, never the `core/identity` barrel: it reaches back into\n// this module through the core barrel, and the cycle would TDZ-crash.\nimport type { IIdentityProviderAccountManager } from '../../identity/provider/account/types.ts';\nimport { createIdentityProviderOAuth2Authenticator } from '../../identity/provider/authentication/factory.ts';\nimport { toIdentityPolicyData } from '../../identity/permission/identity-policy-data.ts';\nimport type { IOAuth2AccessPolicyEvaluator } from '../access-policy/index.ts';\nimport type {\n IOAuth2AuthorizationCodeIssuer,\n IOAuth2AuthorizationCodeRequestVerifier,\n OAuth2AuthorizationCodeRequestVerificationResult,\n} from '../authorization/index.ts';\nimport type {\n IOAuth2FederatedLoginService,\n OAuth2FederatedLoginAuthenticatorFactory,\n OAuth2FederatedLoginCompleteInput,\n OAuth2FederatedLoginCompleteResult,\n OAuth2FederatedLoginServiceContext,\n OAuth2FederatedLoginServiceOptions,\n} from './types.ts';\nimport { OAuth2FederatedLoginRefusal } from './types.ts';\n\nexport class OAuth2FederatedLoginService implements IOAuth2FederatedLoginService {\n protected options : OAuth2FederatedLoginServiceOptions;\n\n protected accountManager : IIdentityProviderAccountManager;\n\n protected realmRepository : IRealmRepository;\n\n protected codeRequestVerifier : IOAuth2AuthorizationCodeRequestVerifier;\n\n protected codeIssuer : IOAuth2AuthorizationCodeIssuer;\n\n protected accessPolicyEvaluator? : IOAuth2AccessPolicyEvaluator;\n\n protected eventService? : IEventService;\n\n protected logger? : Logger;\n\n protected authenticatorFactory : OAuth2FederatedLoginAuthenticatorFactory;\n\n constructor(ctx: OAuth2FederatedLoginServiceContext) {\n this.options = ctx.options;\n this.accountManager = ctx.accountManager;\n this.realmRepository = ctx.realmRepository;\n this.codeRequestVerifier = ctx.codeRequestVerifier;\n this.codeIssuer = ctx.codeIssuer;\n this.accessPolicyEvaluator = ctx.accessPolicyEvaluator;\n this.eventService = ctx.eventService;\n this.logger = ctx.logger;\n this.authenticatorFactory = ctx.authenticatorFactory ??\n ((provider, options) => createIdentityProviderOAuth2Authenticator({\n accountManager: this.accountManager,\n provider,\n logger: options.logger,\n options: {\n baseURL: options.baseURL,\n clientId: options.clientId,\n },\n }));\n }\n\n async complete(input: OAuth2FederatedLoginCompleteInput): Promise<OAuth2FederatedLoginCompleteResult> {\n const {\n code, \n codeRequest, \n provider, \n } = input;\n\n // Re-verify the request BEFORE the provider's single-use code is\n // spent. It re-resolves the client (active, grant allowlist, scopes)\n // and re-matches the redirect_uri against the client's registered\n // patterns, so a client deactivated (or a pattern removed) while the\n // person was away at the provider cannot still receive a code, and a\n // refused completion provisions no user.\n //\n // Only a refusal is reported as one; a server failure keeps throwing.\n let verified : OAuth2AuthorizationCodeRequestVerificationResult;\n try {\n verified = await this.codeRequestVerifier.verify(codeRequest);\n } catch (e) {\n if (!isOAuth2Error(e)) {\n throw e;\n }\n\n return {\n kind: 'refused',\n refusal: OAuth2FederatedLoginRefusal.CODE_REQUEST,\n codeRequest,\n };\n }\n\n // The provider and the client must share a realm, the completion-side\n // half of the plan-041 binding. It rests on the client the\n // verification just resolved rather than the `realm_id` carried on\n // the state blob: that value is only there because `authorize-out`\n // stores the VERIFIED request, so a guard reading it would disappear\n // silently for any state that reached here without the stamp. A\n // realm-less (global) provider matches every client by design.\n if (provider.realmId && verified.client.realmId !== provider.realmId) {\n throw OAuth2RequestError.malformed('The provider and client realm do not match.');\n }\n\n // A stored code request always carries a redirect_uri (that mount is\n // required in OAuth2AuthorizationCodeRequestValidator, unlike\n // `state`), so a verified request is a verified redirect target. The\n // guard keeps the redirect decision resting on the match itself,\n // which is the only thing here that knows the uri was ever matched.\n const redirectUri = verified.data.redirect_uri;\n if (!verified.redirectUriVerified || !redirectUri) {\n throw OAuth2RequestError.malformed('The redirect_uri was not verified.');\n }\n\n // A non-http(s) target is navigated from the interstitial page, which\n // `location.assign`s it and renders it as an href, so a\n // script-capable scheme would execute on the IdP origin. The client\n // validator and the code-request verifier both refuse such a scheme;\n // this fails closed should either gap, and it runs before the\n // provider's single-use code is spent, a user provisioned or a code\n // minted.\n if (!isSafeRedirectURLScheme(redirectUri)) {\n throw new InternalError('The redirect_uri scheme is not allowed.');\n }\n\n // The provider must still be enabled, the rule the link path already\n // applies. Disabling a provider has to stop logins in flight too,\n // otherwise it only stops new ones. Checked before the exchange, so\n // the refusal contacts the provider not at all.\n if (!provider.enabled) {\n return {\n kind: 'refused',\n refusal: OAuth2FederatedLoginRefusal.PROVIDER_DISABLED,\n error: OAuth2ErrorCode.LOGIN_REQUIRED,\n codeRequest: verified.data,\n };\n }\n\n const authenticator = this.authenticatorFactory(provider, {\n baseURL: this.options.baseURL,\n clientId: verified.data.client_id,\n logger: this.logger,\n });\n\n const user = await authenticator.authenticate({ code });\n\n // The local login path refuses an inactive user (EntityInactiveError),\n // and a federated login must not be the way around that. Only\n // reachable for an already-linked user: a first login provisions an\n // active one.\n if (!user.active) {\n return {\n kind: 'refused',\n refusal: OAuth2FederatedLoginRefusal.USER_INACTIVE,\n error: OAuth2ErrorCode.ACCESS_DENIED,\n codeRequest: verified.data,\n };\n }\n\n const realm = await this.realmRepository.resolve(provider.realmId, true);\n\n // Application access policy (plan 052), federated leg. A policy id\n // with no wired evaluator denies (fail closed).\n if (verified.client.accessPolicyId) {\n let allowed = false;\n\n const subject = toIdentityPolicyData({\n type: IdentityType.USER,\n data: {\n ...user,\n realm,\n },\n });\n if (this.accessPolicyEvaluator && subject) {\n allowed = await this.accessPolicyEvaluator.evaluate(\n verified.client.accessPolicyId,\n subject,\n );\n }\n\n if (!allowed) {\n return {\n kind: 'refused',\n refusal: OAuth2FederatedLoginRefusal.ACCESS_DENIED,\n error: OAuth2ErrorCode.ACCESS_DENIED,\n codeRequest: verified.data,\n };\n }\n }\n\n // The WHOLE verified request reaches the issuer, never a hand-picked\n // subset: it carries code_challenge / code_challenge_method and nonce\n // (plus acr_values, which no redemption path reads yet). A code that\n // lost its PKCE challenge cannot be redeemed by a public client at\n // all (`PKCE is required for public clients`), which is every console\n // client.\n const authorizationCode = await this.codeIssuer.issue(\n verified.data,\n {\n type: IdentityType.USER,\n data: {\n ...user,\n realm,\n },\n },\n { authMethod: SessionAuthMethod.EXTERNAL },\n );\n\n // The interactive path records this in OAuth2Authorization.authorize();\n // this leg issues its code directly, so without an emit here a\n // federated authorization leaves no trace in auth_events while every\n // other one does. `reason: federated` is what tells the two apart:\n // there was no consent step to report. No session exists yet (the\n // /token exchange creates it), hence a null sessionId. Metrics stay\n // uninstrumented on this leg, as they already are.\n await this.eventService?.record({\n scope: EventScope.OAUTH2,\n name: EventName.AUTHORIZE,\n refType: EventRefType.CLIENT,\n refId: verified.data.client_id ?? null,\n clientId: verified.data.client_id ?? null,\n sessionId: null,\n actorType: IdentityType.USER,\n actorId: user.id,\n actorName: user.name,\n realmId: verified.data.realm_id ?? realm.id,\n requestIpAddress: input.request?.ipAddress ?? null,\n requestUserAgent: input.request?.userAgent ?? null,\n data: {\n reason: 'federated',\n providerId: provider.id,\n providerName: provider.name,\n ...(verified.data.scope ? { scope: verified.data.scope } : {}),\n },\n });\n\n return {\n kind: 'issued',\n redirectUri,\n code: authorizationCode.id,\n state: verified.data.state,\n codeRequest: verified.data,\n client: {\n id: verified.client.id,\n name: verified.client.name,\n displayName: verified.client.displayName ?? null,\n },\n };\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"module.mjs","names":["isSafeRedirectURLScheme","EventName","EventRefType","EventScope","IdentityType","ScopeName","SessionAuthMethod","BadRequestError","InternalError","OAuth2ErrorCode","OAuth2RequestError","OAuth2SubKind","isOAuth2Error","describeError","createIdentityProviderOAuth2Authenticator","isIdentityProviderAssuranceError","toIdentityPolicyData","deriveAmrAcr","buildOAuth2BearerTokenResponse","OAUTH2_FEDERATED_LOGIN_TTL","OAuth2FederatedLoginRefusal","OAuth2FederatedLoginService","options","accountManager","realmRepository","codeRequestVerifier","sessionManager","pendingLoginStore","accessTokenIssuer","refreshTokenIssuer","accessPolicyEvaluator","eventService","metrics","logger","authenticatorFactory","ctx","provider","baseURL","clientId","complete","input","code","codeRequest","verified","verify","e","kind","refusal","CODE_REQUEST","realmId","client","malformed","redirectUri","data","redirect_uri","redirectUriVerified","enabled","PROVIDER_DISABLED","error","LOGIN_REQUIRED","authenticator","client_id","user","authenticate","warn","id","ASSURANCE_INSUFFICIENT","ACCESS_DENIED","active","USER_INACTIVE","accessPolicyId","realm","resolve","allowed","subject","type","USER","evaluate","session","create","userAgent","request","undefined","ipAddress","sub","subKind","mfaAt","authMethod","EXTERNAL","expiresAt","Date","now","toISOString","pendingLoginId","save","sessionId","providerId","userName","name","completeHandoff","refuse","pending","consume","existing","findOneById","parse","refresh","payload","session_id","user_agent","remote_address","scope","GLOBAL","sub_kind","realm_id","realm_name","accessToken","accessTokenPayload","issue","refreshToken","refreshTokenPayload","record","OAUTH2","LOGIN","refType","SESSION","refId","actorType","actorId","actorName","requestIpAddress","requestUserAgent","reason","recordLogin"],"sources":["../../../../src/core/oauth2/federated-login/module.ts"],"sourcesContent":["/*\n * Copyright (c) 2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport { isSafeRedirectURLScheme } from '@authup/kit';\nimport type { User } from '@authup/core-kit';\nimport {\n EventName,\n EventRefType,\n EventScope,\n IdentityType,\n ScopeName,\n SessionAuthMethod,\n} from '@authup/core-kit';\nimport { BadRequestError, InternalError } from '@authup/errors';\nimport type { Logger } from '@authup/server-kit';\nimport type { OAuth2TokenGrantResponse, OAuth2TokenPayload } from '@authup/specs';\nimport {\n OAuth2ErrorCode,\n OAuth2RequestError,\n OAuth2SubKind,\n isOAuth2Error,\n} from '@authup/specs';\nimport { describeError } from '../../../utils/index.ts';\nimport type { IEventService, IRealmRepository } from '../../entities/index.ts';\nimport type { IAuthFlowMetrics } from '../../metrics/index.ts';\nimport type { ISessionManager } from '../../authentication/index.ts';\n// Deep imports, never the `core/identity` barrel: it reaches back into\n// this module through the core barrel, and the cycle would TDZ-crash.\nimport type { IIdentityProviderAccountManager } from '../../identity/provider/account/types.ts';\nimport { createIdentityProviderOAuth2Authenticator } from '../../identity/provider/authentication/factory.ts';\nimport { isIdentityProviderAssuranceError } from '../../identity/provider/authentication/protocols/oauth2/assurance.ts';\nimport { toIdentityPolicyData } from '../../identity/permission/identity-policy-data.ts';\nimport type { IOAuth2AccessPolicyEvaluator } from '../access-policy/index.ts';\nimport type {\n IOAuth2AuthorizationCodeRequestVerifier,\n OAuth2AuthorizationCodeRequestVerificationResult,\n} from '../authorization/index.ts';\nimport { deriveAmrAcr } from '../authorization/helpers.ts';\nimport { buildOAuth2BearerTokenResponse } from '../response/index.ts';\nimport type { IOAuth2TokenIssuer } from '../token/index.ts';\nimport { OAUTH2_FEDERATED_LOGIN_TTL } from './constants.ts';\nimport type {\n IOAuth2FederatedLoginService,\n IOAuth2FederatedLoginStore,\n OAuth2FederatedLoginAuthenticatorFactory,\n OAuth2FederatedLoginCompleteHandoffInput,\n OAuth2FederatedLoginCompleteInput,\n OAuth2FederatedLoginCompleteResult,\n OAuth2FederatedLoginServiceContext,\n OAuth2FederatedLoginServiceOptions,\n} from './types.ts';\nimport { OAuth2FederatedLoginRefusal } from './types.ts';\n\nexport class OAuth2FederatedLoginService implements IOAuth2FederatedLoginService {\n protected options : OAuth2FederatedLoginServiceOptions;\n\n protected accountManager : IIdentityProviderAccountManager;\n\n protected realmRepository : IRealmRepository;\n\n protected codeRequestVerifier : IOAuth2AuthorizationCodeRequestVerifier;\n\n protected sessionManager : ISessionManager;\n\n protected pendingLoginStore : IOAuth2FederatedLoginStore;\n\n protected accessTokenIssuer : IOAuth2TokenIssuer;\n\n protected refreshTokenIssuer : IOAuth2TokenIssuer;\n\n protected accessPolicyEvaluator? : IOAuth2AccessPolicyEvaluator;\n\n protected eventService? : IEventService;\n\n protected metrics? : IAuthFlowMetrics;\n\n protected logger? : Logger;\n\n protected authenticatorFactory : OAuth2FederatedLoginAuthenticatorFactory;\n\n constructor(ctx: OAuth2FederatedLoginServiceContext) {\n this.options = ctx.options;\n this.accountManager = ctx.accountManager;\n this.realmRepository = ctx.realmRepository;\n this.codeRequestVerifier = ctx.codeRequestVerifier;\n this.sessionManager = ctx.sessionManager;\n this.pendingLoginStore = ctx.pendingLoginStore;\n this.accessTokenIssuer = ctx.accessTokenIssuer;\n this.refreshTokenIssuer = ctx.refreshTokenIssuer;\n this.accessPolicyEvaluator = ctx.accessPolicyEvaluator;\n this.eventService = ctx.eventService;\n this.metrics = ctx.metrics;\n this.logger = ctx.logger;\n this.authenticatorFactory = ctx.authenticatorFactory ??\n ((provider, options) => createIdentityProviderOAuth2Authenticator({\n accountManager: this.accountManager,\n provider,\n logger: options.logger,\n options: {\n baseURL: options.baseURL,\n clientId: options.clientId,\n },\n }));\n }\n\n async complete(input: OAuth2FederatedLoginCompleteInput): Promise<OAuth2FederatedLoginCompleteResult> {\n const {\n code, \n codeRequest, \n provider, \n } = input;\n\n // Re-verify the request BEFORE the provider's single-use code is\n // spent. It re-resolves the client (active, grant allowlist, scopes)\n // and re-matches the redirect_uri against the client's registered\n // patterns, so a client deactivated (or a pattern removed) while the\n // person was away at the provider cannot still receive a code, and a\n // refused completion provisions no user.\n //\n // Only a refusal is reported as one; a server failure keeps throwing.\n let verified : OAuth2AuthorizationCodeRequestVerificationResult;\n try {\n verified = await this.codeRequestVerifier.verify(codeRequest);\n } catch (e) {\n if (!isOAuth2Error(e)) {\n throw e;\n }\n\n return {\n kind: 'refused',\n refusal: OAuth2FederatedLoginRefusal.CODE_REQUEST,\n codeRequest,\n };\n }\n\n // The provider and the client must share a realm, the completion-side\n // half of the plan-041 binding. It rests on the client the\n // verification just resolved rather than the `realm_id` carried on\n // the state blob: that value is only there because `authorize-out`\n // stores the VERIFIED request, so a guard reading it would disappear\n // silently for any state that reached here without the stamp. A\n // realm-less (global) provider matches every client by design.\n if (provider.realmId && verified.client.realmId !== provider.realmId) {\n throw OAuth2RequestError.malformed('The provider and client realm do not match.');\n }\n\n // A stored code request always carries a redirect_uri (that mount is\n // required in OAuth2AuthorizationCodeRequestValidator, unlike\n // `state`), so a verified request is a verified redirect target. The\n // guard keeps the redirect decision resting on the match itself,\n // which is the only thing here that knows the uri was ever matched.\n const redirectUri = verified.data.redirect_uri;\n if (!verified.redirectUriVerified || !redirectUri) {\n throw OAuth2RequestError.malformed('The redirect_uri was not verified.');\n }\n\n // A non-http(s) target is navigated from the interstitial page, which\n // `location.assign`s it and renders it as an href, so a\n // script-capable scheme would execute on the IdP origin. The client\n // validator and the code-request verifier both refuse such a scheme;\n // this fails closed should either gap, and it runs before the\n // provider's single-use code is spent, a user provisioned or a code\n // minted.\n if (!isSafeRedirectURLScheme(redirectUri)) {\n throw new InternalError('The redirect_uri scheme is not allowed.');\n }\n\n // The provider must still be enabled, the rule the link path already\n // applies. Disabling a provider has to stop logins in flight too,\n // otherwise it only stops new ones. Checked before the exchange, so\n // the refusal contacts the provider not at all.\n if (!provider.enabled) {\n return {\n kind: 'refused',\n refusal: OAuth2FederatedLoginRefusal.PROVIDER_DISABLED,\n error: OAuth2ErrorCode.LOGIN_REQUIRED,\n codeRequest: verified.data,\n };\n }\n\n const authenticator = this.authenticatorFactory(provider, {\n baseURL: this.options.baseURL,\n clientId: verified.data.client_id,\n logger: this.logger,\n });\n\n let user : User;\n try {\n user = await authenticator.authenticate({ code });\n } catch (e) {\n // The upstream did not meet the provider's assurance allow-list\n // (issue #3477). No user is provisioned and no account linked\n // because the gate throws inside `resolveIdentity`, before the\n // account manager runs - not because of where this catch sits.\n // The reason only goes to the log: the marker\n // set the hosted page maps is deliberately closed, so `access_denied`\n // is what the browser sees.\n if (!isIdentityProviderAssuranceError(e)) {\n throw e;\n }\n\n this.logger?.warn(describeError(\n e,\n `The identity provider (${provider.id}) login was refused.`,\n ));\n\n return {\n kind: 'refused',\n refusal: OAuth2FederatedLoginRefusal.ASSURANCE_INSUFFICIENT,\n error: OAuth2ErrorCode.ACCESS_DENIED,\n codeRequest: verified.data,\n };\n }\n\n // The local login path refuses an inactive user (EntityInactiveError),\n // and a federated login must not be the way around that. Only\n // reachable for an already-linked user: a first login provisions an\n // active one.\n if (!user.active) {\n return {\n kind: 'refused',\n refusal: OAuth2FederatedLoginRefusal.USER_INACTIVE,\n error: OAuth2ErrorCode.ACCESS_DENIED,\n codeRequest: verified.data,\n };\n }\n\n // Application access policy (plan 052), federated leg. A policy id\n // with no wired evaluator denies (fail closed).\n if (verified.client.accessPolicyId) {\n const realm = await this.realmRepository.resolve(provider.realmId, true);\n let allowed = false;\n\n const subject = toIdentityPolicyData({\n type: IdentityType.USER,\n data: {\n ...user,\n realm,\n },\n });\n if (this.accessPolicyEvaluator && subject) {\n allowed = await this.accessPolicyEvaluator.evaluate(\n verified.client.accessPolicyId,\n subject,\n );\n }\n\n if (!allowed) {\n return {\n kind: 'refused',\n refusal: OAuth2FederatedLoginRefusal.ACCESS_DENIED,\n error: OAuth2ErrorCode.ACCESS_DENIED,\n codeRequest: verified.data,\n };\n }\n }\n\n // The callback establishes the session and stops there. The RP's\n // code is issued at the end of the hosted authorize ladder, so a\n // federated login passes the same gates a password login does: the\n // second factor, inline enrollment, prompt/max_age freshness,\n // acr_values step-up and consent (plan 094).\n //\n // Its lifetime is the pending login's: an abandoned one self-expires and\n // is swept with the regular session sweep, and redemption extends it\n // to the regular one.\n const session = await this.sessionManager.create({\n userAgent: input.request?.userAgent ?? undefined,\n ipAddress: input.request?.ipAddress ?? undefined,\n realmId: user.realmId,\n // no `clientId`: a USER-subject session, and the column is the\n // client-SUBJECT foreign key. The application lands on the\n // token rows at the /token exchange.\n sub: user.id,\n subKind: IdentityType.USER,\n mfaAt: null,\n authMethod: SessionAuthMethod.EXTERNAL,\n expiresAt: new Date(Date.now() + OAUTH2_FEDERATED_LOGIN_TTL).toISOString(),\n });\n\n const pendingLoginId = await this.pendingLoginStore.save({\n sessionId: session.id,\n providerId: provider.id,\n userName: user.name,\n });\n\n return {\n kind: 'issued',\n pendingLoginId,\n codeRequest: verified.data,\n };\n }\n\n async completeHandoff(input: OAuth2FederatedLoginCompleteHandoffInput): Promise<OAuth2TokenGrantResponse> {\n // One message for every refusal: the caller is anonymous, so an\n // unknown pending login, an expired one and a foreign one must not\n // be distinguishable.\n const refuse = () => new BadRequestError('The login request is unknown or expired.');\n\n const pending = await this.pendingLoginStore.consume(input.pendingLoginId);\n if (!pending) {\n throw refuse();\n }\n\n // The browser binding is the cookie the callback set, which only the\n // browser that started this login carries and no other origin can\n // write. So there is nothing here to prove beyond the provider the\n // completion was addressed at.\n //\n // The callback request's address and agent are deliberately not\n // compared: both are chosen by whoever makes that request (under the\n // shipped `trustProxy: true` the address is the client-supplied\n // left-most `X-Forwarded-For` entry), and comparing them across a\n // top-level navigation and the page's own request is the shape issue\n // #3439 rejected.\n if (pending.providerId !== input.providerId) {\n throw refuse();\n }\n\n const existing = await this.sessionManager.findOneById(pending.sessionId);\n if (!existing) {\n throw refuse();\n }\n\n // The pending session carries the pending login's own deadline, and\n // the lookup is by id alone. Today the cache entry above expires with\n // it, so this only fires on clock skew between the two stores, but the\n // expiry must be enforced where it is written: `refresh` would extend\n // an abandoned login into a regular session.\n if (Date.parse(existing.expiresAt) <= Date.now()) {\n throw refuse();\n }\n\n const realm = await this.realmRepository.resolve(existing.realmId, true);\n\n // The login is complete now, so the pending session becomes a\n // regular one.\n const session = await this.sessionManager.refresh(existing);\n\n const payload : Partial<OAuth2TokenPayload> = {\n session_id: session.id,\n user_agent: session.userAgent,\n remote_address: session.ipAddress,\n scope: ScopeName.GLOBAL,\n sub: session.sub,\n sub_kind: OAuth2SubKind.USER,\n realm_id: session.realmId,\n realm_name: realm.name,\n ...deriveAmrAcr(session),\n };\n\n const [accessToken, accessTokenPayload] = await this.accessTokenIssuer.issue(payload);\n const [refreshToken, refreshTokenPayload] = await this.refreshTokenIssuer.issue(payload);\n\n await this.eventService?.record({\n scope: EventScope.OAUTH2,\n name: EventName.LOGIN,\n refType: EventRefType.SESSION,\n refId: session.id,\n clientId: null,\n sessionId: session.id,\n actorType: IdentityType.USER,\n actorId: session.sub,\n actorName: pending.userName ?? null,\n realmId: session.realmId,\n requestIpAddress: session.ipAddress ?? null,\n requestUserAgent: session.userAgent ?? null,\n data: { reason: 'federated', providerId: pending.providerId },\n });\n this.metrics?.recordLogin('success');\n\n return buildOAuth2BearerTokenResponse({\n accessToken,\n accessTokenPayload,\n refreshToken,\n refreshTokenPayload,\n });\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import type { OAuth2AuthorizationCodeRequest, OAuth2IdentityProvider, OpenIDIdentityProvider, User } from '@authup/core-kit';
|
|
2
|
-
import type { OAuth2ErrorCode } from '@authup/specs';
|
|
2
|
+
import type { OAuth2ErrorCode, OAuth2TokenGrantResponse } from '@authup/specs';
|
|
3
3
|
import type { Logger } from '@authup/server-kit';
|
|
4
4
|
import type { IIdentityProviderAccountManager } from '../../identity/provider/account/types.ts';
|
|
5
5
|
import type { IOAuth2Authenticator } from '../../identity/provider/authentication/protocols/index.ts';
|
|
6
6
|
import type { IEventService, IRealmRepository } from '../../entities/index.ts';
|
|
7
|
+
import type { ISessionManager } from '../../authentication/index.ts';
|
|
8
|
+
import type { IAuthFlowMetrics } from '../../metrics/index.ts';
|
|
9
|
+
import type { IOAuth2TokenIssuer } from '../token/index.ts';
|
|
7
10
|
import type { IOAuth2AccessPolicyEvaluator } from '../access-policy/index.ts';
|
|
8
|
-
import type {
|
|
11
|
+
import type { IOAuth2AuthorizationCodeRequestVerifier } from '../authorization/index.ts';
|
|
9
12
|
export type OAuth2FederatedLoginCompleteInput = {
|
|
10
13
|
/**
|
|
11
14
|
* The provider the callback belongs to, already resolved and
|
|
@@ -51,7 +54,13 @@ export declare enum OAuth2FederatedLoginRefusal {
|
|
|
51
54
|
/**
|
|
52
55
|
* The client's access policy denied the identity (plan 052).
|
|
53
56
|
*/
|
|
54
|
-
ACCESS_DENIED = "accessDenied"
|
|
57
|
+
ACCESS_DENIED = "accessDenied",
|
|
58
|
+
/**
|
|
59
|
+
* The upstream did not meet the provider's `requiredAmr` / `requiredAcr`
|
|
60
|
+
* allow-list (issue #3477). Opt-in: a provider declaring neither cannot
|
|
61
|
+
* reach this.
|
|
62
|
+
*/
|
|
63
|
+
ASSURANCE_INSUFFICIENT = "assuranceInsufficient"
|
|
55
64
|
}
|
|
56
65
|
export type OAuth2FederatedLoginRefusedResult = {
|
|
57
66
|
kind: 'refused';
|
|
@@ -71,40 +80,85 @@ export type OAuth2FederatedLoginRefusedResult = {
|
|
|
71
80
|
export type OAuth2FederatedLoginIssuedResult = {
|
|
72
81
|
kind: 'issued';
|
|
73
82
|
/**
|
|
74
|
-
*
|
|
75
|
-
*
|
|
83
|
+
* Identifies the pending login the hosted authorize page completes. The
|
|
84
|
+
* caller puts it in a cookie; it is never a URL parameter. No token and no
|
|
85
|
+
* authorization code: the application's code is issued at the end of the
|
|
86
|
+
* hosted ladder, once the prompt gates and consent have run.
|
|
76
87
|
*/
|
|
77
|
-
|
|
88
|
+
pendingLoginId: string;
|
|
78
89
|
/**
|
|
79
|
-
* The
|
|
90
|
+
* The verified request, which the caller re-renders on the hosted
|
|
91
|
+
* page alongside it.
|
|
80
92
|
*/
|
|
81
|
-
|
|
82
|
-
|
|
93
|
+
codeRequest: OAuth2AuthorizationCodeRequest;
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
96
|
+
* What a pending login holds. Three scalars, never the identity object (which
|
|
97
|
+
* carries the provider entity incl. its EA-loaded clientSecret and the raw
|
|
98
|
+
* external token payload) and never a token.
|
|
99
|
+
*/
|
|
100
|
+
export type OAuth2FederatedLoginPending = {
|
|
101
|
+
sessionId: string;
|
|
83
102
|
/**
|
|
84
|
-
* The
|
|
85
|
-
*
|
|
103
|
+
* The provider the login was started at. The completion endpoint is
|
|
104
|
+
* provider-scoped, so a pending login cannot be completed at another.
|
|
86
105
|
*/
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
106
|
+
providerId: string;
|
|
107
|
+
/**
|
|
108
|
+
* Actor name for the LOGIN security event.
|
|
109
|
+
*/
|
|
110
|
+
userName?: string | null;
|
|
111
|
+
};
|
|
112
|
+
export interface IOAuth2FederatedLoginStore {
|
|
113
|
+
/**
|
|
114
|
+
* @returns the id of the pending login
|
|
115
|
+
*/
|
|
116
|
+
save(data: OAuth2FederatedLoginPending): Promise<string>;
|
|
117
|
+
/**
|
|
118
|
+
* Reads and DROPS it. Single use: one pending login completes once.
|
|
119
|
+
*/
|
|
120
|
+
consume(id: string): Promise<OAuth2FederatedLoginPending | null>;
|
|
121
|
+
}
|
|
122
|
+
export type OAuth2FederatedLoginCompleteHandoffInput = {
|
|
123
|
+
/**
|
|
124
|
+
* From the cookie the callback set on this browser.
|
|
125
|
+
*/
|
|
126
|
+
pendingLoginId: string;
|
|
127
|
+
/**
|
|
128
|
+
* The provider the completion endpoint was addressed at.
|
|
129
|
+
*/
|
|
130
|
+
providerId: string;
|
|
93
131
|
};
|
|
94
132
|
export type OAuth2FederatedLoginCompleteResult = OAuth2FederatedLoginRefusedResult | OAuth2FederatedLoginIssuedResult;
|
|
95
133
|
/**
|
|
96
|
-
* Completes the
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
134
|
+
* Completes the external leg of a federated login after a provider sent
|
|
135
|
+
* the browser back (issue #3446). It owns the refusal ladder: realm match,
|
|
136
|
+
* code-request re-verification, redirect-scheme gate, provider and user
|
|
137
|
+
* state, access policy.
|
|
138
|
+
*
|
|
139
|
+
* It does NOT mint the RP's authorization code. It establishes the authup
|
|
140
|
+
* session and hands the browser a cookie naming it; the hosted authorize
|
|
141
|
+
* page completes it and runs the same ladder a password login runs before
|
|
142
|
+
* any code is issued (plan 094).
|
|
100
143
|
*
|
|
101
144
|
* Deliberately transport-free: it decides WHAT the answer is, and returns
|
|
102
145
|
* it as a discriminated result. The adapter decides how that answer
|
|
103
|
-
* reaches the browser
|
|
104
|
-
* scheme needs), because only the adapter knows the difference.
|
|
146
|
+
* reaches the browser.
|
|
105
147
|
*/
|
|
106
148
|
export interface IOAuth2FederatedLoginService {
|
|
107
149
|
complete(input: OAuth2FederatedLoginCompleteInput): Promise<OAuth2FederatedLoginCompleteResult>;
|
|
150
|
+
/**
|
|
151
|
+
* Exchange the pending login for the grant of the session the callback
|
|
152
|
+
* established. Refuses without detail: the caller is anonymous.
|
|
153
|
+
*
|
|
154
|
+
* The local second factor is deliberately NOT gated here: an external
|
|
155
|
+
* provider authenticated this login and is where MFA is enforced for it.
|
|
156
|
+
* An application that asks for one explicitly (`acr_values`) still steps
|
|
157
|
+
* up at `POST /authorize`.
|
|
158
|
+
*
|
|
159
|
+
* @throws BadRequestError
|
|
160
|
+
*/
|
|
161
|
+
completeHandoff(input: OAuth2FederatedLoginCompleteHandoffInput): Promise<OAuth2TokenGrantResponse>;
|
|
108
162
|
}
|
|
109
163
|
export type OAuth2FederatedLoginServiceOptions = {
|
|
110
164
|
/**
|
|
@@ -129,9 +183,13 @@ export type OAuth2FederatedLoginServiceContext = {
|
|
|
129
183
|
accountManager: IIdentityProviderAccountManager;
|
|
130
184
|
realmRepository: IRealmRepository;
|
|
131
185
|
codeRequestVerifier: IOAuth2AuthorizationCodeRequestVerifier;
|
|
132
|
-
|
|
186
|
+
sessionManager: ISessionManager;
|
|
187
|
+
pendingLoginStore: IOAuth2FederatedLoginStore;
|
|
188
|
+
accessTokenIssuer: IOAuth2TokenIssuer;
|
|
189
|
+
refreshTokenIssuer: IOAuth2TokenIssuer;
|
|
133
190
|
accessPolicyEvaluator?: IOAuth2AccessPolicyEvaluator;
|
|
134
191
|
eventService?: IEventService;
|
|
192
|
+
metrics?: IAuthFlowMetrics;
|
|
135
193
|
logger?: Logger;
|
|
136
194
|
authenticatorFactory?: OAuth2FederatedLoginAuthenticatorFactory;
|
|
137
195
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/core/oauth2/federated-login/types.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EACR,8BAA8B,EAC9B,sBAAsB,EACtB,sBAAsB,EACtB,IAAI,EACP,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/core/oauth2/federated-login/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -23,6 +23,11 @@ import.meta.url;
|
|
|
23
23
|
/**
|
|
24
24
|
* The client's access policy denied the identity (plan 052).
|
|
25
25
|
*/ OAuth2FederatedLoginRefusal["ACCESS_DENIED"] = "accessDenied";
|
|
26
|
+
/**
|
|
27
|
+
* The upstream did not meet the provider's `requiredAmr` / `requiredAcr`
|
|
28
|
+
* allow-list (issue #3477). Opt-in: a provider declaring neither cannot
|
|
29
|
+
* reach this.
|
|
30
|
+
*/ OAuth2FederatedLoginRefusal["ASSURANCE_INSUFFICIENT"] = "assuranceInsufficient";
|
|
26
31
|
return OAuth2FederatedLoginRefusal;
|
|
27
32
|
}({});
|
|
28
33
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.mjs","names":["OAuth2FederatedLoginRefusal"],"sources":["../../../../src/core/oauth2/federated-login/types.ts"],"sourcesContent":["/*\n * Copyright (c) 2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport type {\n OAuth2AuthorizationCodeRequest,\n OAuth2IdentityProvider,\n OpenIDIdentityProvider, \n User, \n} from '@authup/core-kit';\nimport type { OAuth2ErrorCode } from '@authup/specs';\nimport type { Logger } from '@authup/server-kit';\nimport type { IIdentityProviderAccountManager } from '../../identity/provider/account/types.ts';\nimport type { IOAuth2Authenticator } from '../../identity/provider/authentication/protocols/index.ts';\nimport type { IEventService, IRealmRepository } from '../../entities/index.ts';\nimport type { IOAuth2AccessPolicyEvaluator } from '../access-policy/index.ts';\nimport type {
|
|
1
|
+
{"version":3,"file":"types.mjs","names":["OAuth2FederatedLoginRefusal"],"sources":["../../../../src/core/oauth2/federated-login/types.ts"],"sourcesContent":["/*\n * Copyright (c) 2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport type {\n OAuth2AuthorizationCodeRequest,\n OAuth2IdentityProvider,\n OpenIDIdentityProvider, \n User, \n} from '@authup/core-kit';\nimport type { OAuth2ErrorCode, OAuth2TokenGrantResponse } from '@authup/specs';\nimport type { Logger } from '@authup/server-kit';\nimport type { IIdentityProviderAccountManager } from '../../identity/provider/account/types.ts';\nimport type { IOAuth2Authenticator } from '../../identity/provider/authentication/protocols/index.ts';\nimport type { IEventService, IRealmRepository } from '../../entities/index.ts';\nimport type { ISessionManager } from '../../authentication/index.ts';\nimport type { IAuthFlowMetrics } from '../../metrics/index.ts';\nimport type { IOAuth2TokenIssuer } from '../token/index.ts';\nimport type { IOAuth2AccessPolicyEvaluator } from '../access-policy/index.ts';\nimport type { IOAuth2AuthorizationCodeRequestVerifier } from '../authorization/index.ts';\n\nexport type OAuth2FederatedLoginCompleteInput = {\n /**\n * The provider the callback belongs to, already resolved and\n * protocol-checked by the caller.\n */\n provider: OAuth2IdentityProvider | OpenIDIdentityProvider,\n /**\n * The RP's original authorization request, as `authorize-out` stored\n * it on the state blob. It is re-verified here rather than trusted.\n */\n codeRequest: OAuth2AuthorizationCodeRequest,\n /**\n * The provider's single-use authorization code.\n */\n code: string,\n request?: {\n ipAddress?: string | null,\n userAgent?: string | null,\n },\n};\n\n/**\n * Why a completion was refused. Every value is a REFUSAL — a decision the\n * person at the browser has to be told about on the hosted login page —\n * never a server failure, which keeps throwing.\n */\nexport enum OAuth2FederatedLoginRefusal {\n /**\n * The stored code request no longer verifies (client deactivated or\n * unknown, grant allowlist, scopes, redirect_uri mismatch). The hosted\n * page re-runs the same verifier and renders the same refusal, so\n * nothing is echoed and the bounce carries no marker.\n */\n CODE_REQUEST = 'codeRequest',\n /**\n * The provider was disabled while the person was away at it.\n */\n PROVIDER_DISABLED = 'providerDisabled',\n /**\n * The linked local user is inactive. The local login path refuses one\n * too, and a federated login must not be the way around that.\n */\n USER_INACTIVE = 'userInactive',\n /**\n * The client's access policy denied the identity (plan 052).\n */\n ACCESS_DENIED = 'accessDenied',\n /**\n * The upstream did not meet the provider's `requiredAmr` / `requiredAcr`\n * allow-list (issue #3477). Opt-in: a provider declaring neither cannot\n * reach this.\n */\n ASSURANCE_INSUFFICIENT = 'assuranceInsufficient',\n}\n\nexport type OAuth2FederatedLoginRefusedResult = {\n kind: 'refused',\n refusal: OAuth2FederatedLoginRefusal,\n /**\n * The marker the hosted authorize page maps onto its own copy, when\n * the refusal has one. Absent means \"render the request again\", which\n * re-runs the verifier and states the reason itself.\n */\n error?: `${OAuth2ErrorCode}`,\n /**\n * The request to re-render. The verified copy when re-verification got\n * that far, else the stored one.\n */\n codeRequest: OAuth2AuthorizationCodeRequest,\n};\n\nexport type OAuth2FederatedLoginIssuedResult = {\n kind: 'issued',\n /**\n * Identifies the pending login the hosted authorize page completes. The\n * caller puts it in a cookie; it is never a URL parameter. No token and no\n * authorization code: the application's code is issued at the end of the\n * hosted ladder, once the prompt gates and consent have run.\n */\n pendingLoginId: string,\n /**\n * The verified request, which the caller re-renders on the hosted\n * page alongside it.\n */\n codeRequest: OAuth2AuthorizationCodeRequest,\n};\n\n/**\n * What a pending login holds. Three scalars, never the identity object (which\n * carries the provider entity incl. its EA-loaded clientSecret and the raw\n * external token payload) and never a token.\n */\nexport type OAuth2FederatedLoginPending = {\n sessionId: string,\n /**\n * The provider the login was started at. The completion endpoint is\n * provider-scoped, so a pending login cannot be completed at another.\n */\n providerId: string,\n /**\n * Actor name for the LOGIN security event.\n */\n userName?: string | null,\n};\n\nexport interface IOAuth2FederatedLoginStore {\n /**\n * @returns the id of the pending login\n */\n save(data: OAuth2FederatedLoginPending) : Promise<string>;\n\n /**\n * Reads and DROPS it. Single use: one pending login completes once.\n */\n consume(id: string) : Promise<OAuth2FederatedLoginPending | null>;\n}\n\nexport type OAuth2FederatedLoginCompleteHandoffInput = {\n /**\n * From the cookie the callback set on this browser.\n */\n pendingLoginId: string,\n /**\n * The provider the completion endpoint was addressed at.\n */\n providerId: string,\n};\n\nexport type OAuth2FederatedLoginCompleteResult = OAuth2FederatedLoginRefusedResult |\n OAuth2FederatedLoginIssuedResult;\n\n/**\n * Completes the external leg of a federated login after a provider sent\n * the browser back (issue #3446). It owns the refusal ladder: realm match,\n * code-request re-verification, redirect-scheme gate, provider and user\n * state, access policy.\n *\n * It does NOT mint the RP's authorization code. It establishes the authup\n * session and hands the browser a cookie naming it; the hosted authorize\n * page completes it and runs the same ladder a password login runs before\n * any code is issued (plan 094).\n *\n * Deliberately transport-free: it decides WHAT the answer is, and returns\n * it as a discriminated result. The adapter decides how that answer\n * reaches the browser.\n */\nexport interface IOAuth2FederatedLoginService {\n complete(input: OAuth2FederatedLoginCompleteInput) : Promise<OAuth2FederatedLoginCompleteResult>;\n\n /**\n * Exchange the pending login for the grant of the session the callback\n * established. Refuses without detail: the caller is anonymous.\n *\n * The local second factor is deliberately NOT gated here: an external\n * provider authenticated this login and is where MFA is enforced for it.\n * An application that asks for one explicitly (`acr_values`) still steps\n * up at `POST /authorize`.\n *\n * @throws BadRequestError\n */\n completeHandoff(input: OAuth2FederatedLoginCompleteHandoffInput) : Promise<OAuth2TokenGrantResponse>;\n}\n\nexport type OAuth2FederatedLoginServiceOptions = {\n /**\n * The public base URL. Reaches the provider authenticator, which\n * builds the callback URL the external token exchange must repeat.\n */\n baseURL: string,\n};\n\n/**\n * Builds the authenticator that exchanges the provider's code. Injected\n * rather than constructed inline so the refusal ladder can be exercised\n * without an external provider; it defaults to the real preset/protocol\n * factory.\n */\nexport type OAuth2FederatedLoginAuthenticatorFactory = (\n provider: OAuth2IdentityProvider | OpenIDIdentityProvider,\n options: {\n baseURL: string, \n clientId?: string, \n logger?: Logger \n },\n) => IOAuth2Authenticator<User>;\n\nexport type OAuth2FederatedLoginServiceContext = {\n options: OAuth2FederatedLoginServiceOptions,\n\n accountManager: IIdentityProviderAccountManager,\n realmRepository: IRealmRepository,\n codeRequestVerifier: IOAuth2AuthorizationCodeRequestVerifier,\n\n sessionManager: ISessionManager,\n pendingLoginStore: IOAuth2FederatedLoginStore,\n accessTokenIssuer: IOAuth2TokenIssuer,\n refreshTokenIssuer: IOAuth2TokenIssuer,\n\n accessPolicyEvaluator?: IOAuth2AccessPolicyEvaluator,\n eventService?: IEventService,\n metrics?: IAuthFlowMetrics,\n logger?: Logger,\n authenticatorFactory?: OAuth2FederatedLoginAuthenticatorFactory,\n};\n"],"mappings":";;;;;;;;GAkDA,IAAO,8BAAKA,uBAAAA,6BAAAA;;;;;;IAMP,4BAAA,kBAAA;;;IAIA,4BAAA,uBAAA;;;;IAKA,4BAAA,mBAAA;;;IAIA,4BAAA,mBAAA;;;;;IAMA,4BAAA,4BAAA;CAzBOA,OAAAA;AA2BX,EAAA,CAAA,CAAA"}
|
|
@@ -2,8 +2,8 @@ import "node:path";
|
|
|
2
2
|
import "node:url";
|
|
3
3
|
import.meta.url;
|
|
4
4
|
import { buildOAuth2TokenHash, deriveAmrAcr } from "../authorization/helpers.mjs";
|
|
5
|
-
import { OAuth2BaseGrant } from "./base.mjs";
|
|
6
5
|
import { buildOAuth2BearerTokenResponse } from "../response/bearer.mjs";
|
|
6
|
+
import { OAuth2BaseGrant } from "./base.mjs";
|
|
7
7
|
import { ScopeName } from "@authup/core-kit";
|
|
8
8
|
import { JWKUse, hasOAuth2Scopes } from "@authup/specs";
|
|
9
9
|
//#region src/core/oauth2/grant-types/authorize.ts
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import "node:path";
|
|
2
2
|
import "node:url";
|
|
3
3
|
import.meta.url;
|
|
4
|
-
import { OAuth2BaseGrant } from "./base.mjs";
|
|
5
4
|
import { buildOAuth2BearerTokenResponse } from "../response/bearer.mjs";
|
|
5
|
+
import { OAuth2BaseGrant } from "./base.mjs";
|
|
6
6
|
import { IdentityType, ScopeName, SessionAuthMethod } from "@authup/core-kit";
|
|
7
7
|
import { OAuth2SubKind } from "@authup/specs";
|
|
8
8
|
//#region src/core/oauth2/grant-types/client-credentials.ts
|
|
@@ -2,8 +2,8 @@ import "node:path";
|
|
|
2
2
|
import "node:url";
|
|
3
3
|
import.meta.url;
|
|
4
4
|
import { deriveAmrAcr } from "../authorization/helpers.mjs";
|
|
5
|
-
import { OAuth2BaseGrant } from "./base.mjs";
|
|
6
5
|
import { buildOAuth2BearerTokenResponse } from "../response/bearer.mjs";
|
|
6
|
+
import { OAuth2BaseGrant } from "./base.mjs";
|
|
7
7
|
import { ScopeName, SessionAuthMethod } from "@authup/core-kit";
|
|
8
8
|
//#region src/core/oauth2/grant-types/identity.ts
|
|
9
9
|
var IdentityGrantType = class extends OAuth2BaseGrant {
|
|
@@ -2,8 +2,8 @@ import "node:path";
|
|
|
2
2
|
import "node:url";
|
|
3
3
|
import.meta.url;
|
|
4
4
|
import { deriveAmrAcr } from "../authorization/helpers.mjs";
|
|
5
|
-
import { OAuth2BaseGrant } from "./base.mjs";
|
|
6
5
|
import { buildOAuth2BearerTokenResponse } from "../response/bearer.mjs";
|
|
6
|
+
import { OAuth2BaseGrant } from "./base.mjs";
|
|
7
7
|
import { EventName, EventRefType, EventScope, IdentityType, ScopeName, SessionAuthMethod } from "@authup/core-kit";
|
|
8
8
|
import { OAuth2SubKind, OAuth2TokenGrant } from "@authup/specs";
|
|
9
9
|
//#region src/core/oauth2/grant-types/password.ts
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import "node:path";
|
|
2
2
|
import "node:url";
|
|
3
3
|
import.meta.url;
|
|
4
|
-
import { OAuth2BaseGrant } from "./base.mjs";
|
|
5
4
|
import { buildOAuth2BearerTokenResponse } from "../response/bearer.mjs";
|
|
5
|
+
import { OAuth2BaseGrant } from "./base.mjs";
|
|
6
6
|
import { isSessionTokenRelationMissingError } from "../session-token/error.mjs";
|
|
7
7
|
import "../session-token/index.mjs";
|
|
8
8
|
import { EventName, EventRefType, EventScope } from "@authup/core-kit";
|
|
@@ -17,10 +17,11 @@ import "./authorization/index.mjs";
|
|
|
17
17
|
import { OAuth2EndSessionService } from "./end-session/service.mjs";
|
|
18
18
|
import { OAuth2EndSessionRequestValidator } from "./end-session/validator.mjs";
|
|
19
19
|
import "./end-session/index.mjs";
|
|
20
|
+
import { OAUTH2_FEDERATED_LOGIN_COOKIE, OAUTH2_FEDERATED_LOGIN_TTL } from "./federated-login/constants.mjs";
|
|
21
|
+
import { buildOAuth2BearerTokenResponse } from "./response/bearer.mjs";
|
|
20
22
|
import { OAuth2FederatedLoginRefusal } from "./federated-login/types.mjs";
|
|
21
23
|
import { OAuth2FederatedLoginService } from "./federated-login/module.mjs";
|
|
22
24
|
import "./federated-login/index.mjs";
|
|
23
|
-
import { buildOAuth2BearerTokenResponse } from "./response/bearer.mjs";
|
|
24
25
|
import { OAuth2AuthorizeGrant } from "./grant-types/authorize.mjs";
|
|
25
26
|
import { ClientCredentialsGrant } from "./grant-types/client-credentials.mjs";
|
|
26
27
|
import { IdentityGrantType } from "./grant-types/identity.mjs";
|
|
@@ -43,4 +44,4 @@ import { OAuth2TokenRevoker } from "./token/revoker/module.mjs";
|
|
|
43
44
|
import { OAuth2TokenSigner } from "./token/signer/module.mjs";
|
|
44
45
|
import { OAuth2TokenVerifier } from "./token/verifier/module.mjs";
|
|
45
46
|
import "./token/index.mjs";
|
|
46
|
-
export { ClientCredentialsGrant, IdentityGrantType, OAuth2AccessPolicyEvaluator, OAuth2AccessTokenIssuer, OAuth2Authorization, OAuth2AuthorizationCodeIssuer, OAuth2AuthorizationCodeRequestValidator, OAuth2AuthorizationCodeRequestVerifier, OAuth2AuthorizationCodeVerifier, OAuth2AuthorizationStateManager, OAuth2AuthorizeGrant, OAuth2ClientAuthenticator, OAuth2EndSessionRequestValidator, OAuth2EndSessionService, OAuth2FederatedLoginRefusal, OAuth2FederatedLoginService, OAuth2MfaLoginService, OAuth2MfaTokenIssuer, OAuth2OpenIDClaimsBuilder, OAuth2OpenIDTokenIssuer, OAuth2RefreshTokenGrant, OAuth2RefreshTokenIssuer, OAuth2ScopeAttributesResolver, OAuth2TokenRevoker, OAuth2TokenSigner, OAuth2TokenVerifier, PasswordGrantType, SESSION_TOKEN_RELATION_MISSING_ERROR_INSTANCE, SessionTokenRelationMissingError, assertClientGrantAllowed, base64URLEncode, buildOAuth2BearerTokenResponse, buildOAuth2CodeChallenge, buildOAuth2TokenHash, deriveAmrAcr, generateOAuth2CodeVerifier, isSessionTokenRelationMissingError };
|
|
47
|
+
export { ClientCredentialsGrant, IdentityGrantType, OAUTH2_FEDERATED_LOGIN_COOKIE, OAUTH2_FEDERATED_LOGIN_TTL, OAuth2AccessPolicyEvaluator, OAuth2AccessTokenIssuer, OAuth2Authorization, OAuth2AuthorizationCodeIssuer, OAuth2AuthorizationCodeRequestValidator, OAuth2AuthorizationCodeRequestVerifier, OAuth2AuthorizationCodeVerifier, OAuth2AuthorizationStateManager, OAuth2AuthorizeGrant, OAuth2ClientAuthenticator, OAuth2EndSessionRequestValidator, OAuth2EndSessionService, OAuth2FederatedLoginRefusal, OAuth2FederatedLoginService, OAuth2MfaLoginService, OAuth2MfaTokenIssuer, OAuth2OpenIDClaimsBuilder, OAuth2OpenIDTokenIssuer, OAuth2RefreshTokenGrant, OAuth2RefreshTokenIssuer, OAuth2ScopeAttributesResolver, OAuth2TokenRevoker, OAuth2TokenSigner, OAuth2TokenVerifier, PasswordGrantType, SESSION_TOKEN_RELATION_MISSING_ERROR_INSTANCE, SessionTokenRelationMissingError, assertClientGrantAllowed, base64URLEncode, buildOAuth2BearerTokenResponse, buildOAuth2CodeChallenge, buildOAuth2TokenHash, deriveAmrAcr, generateOAuth2CodeVerifier, isSessionTokenRelationMissingError };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../../../../../src/core/oauth2/token/verifier/module.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAOxD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AACrE,OAAO,KAAK,EAAE,oBAAoB,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAEjF,qBAAa,mBAAoB,YAAW,oBAAoB;IAC5D,SAAS,CAAC,QAAQ,EAAG,SAAS,CAAC;IAE/B,SAAS,CAAC,eAAe,EAAG,sBAAsB,CAAC;gBAG/C,QAAQ,EAAG,SAAS,EACpB,eAAe,EAAG,sBAAsB;IAMtC,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAIxC,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,wBAA6B,GAAI,OAAO,CAAC,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../../../../../src/core/oauth2/token/verifier/module.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAOxD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AACrE,OAAO,KAAK,EAAE,oBAAoB,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAEjF,qBAAa,mBAAoB,YAAW,oBAAoB;IAC5D,SAAS,CAAC,QAAQ,EAAG,SAAS,CAAC;IAE/B,SAAS,CAAC,eAAe,EAAG,sBAAsB,CAAC;gBAG/C,QAAQ,EAAG,SAAS,EACpB,eAAe,EAAG,sBAAsB;IAMtC,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAIxC,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,wBAA6B,GAAI,OAAO,CAAC,kBAAkB,CAAC;CA+HpG"}
|
|
@@ -27,9 +27,9 @@ var OAuth2TokenVerifier = class {
|
|
|
27
27
|
const header = extractTokenHeader(token);
|
|
28
28
|
if (!header.kid) throw JWTError.headerPropertyInvalid("kid");
|
|
29
29
|
const key = await this.keyStore.resolveById(header.kid);
|
|
30
|
-
if (!key) throw
|
|
31
|
-
if (key.use !== JWKUse.SIGNATURE) throw
|
|
32
|
-
if (key.status === KeyStatus.DISABLED) throw
|
|
30
|
+
if (!key) throw JWTError.headerPropertyInvalid("kid");
|
|
31
|
+
if (key.use !== JWKUse.SIGNATURE) throw JWTError.headerPropertyInvalid("kid");
|
|
32
|
+
if (key.status === KeyStatus.DISABLED) throw JWTError.headerPropertyInvalid("kid");
|
|
33
33
|
switch (key.type) {
|
|
34
34
|
case JWKType.OCT:
|
|
35
35
|
if (!key.decryptionKey) throw JWKError.decryptionKeyMissing();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module.mjs","names":["extractTokenHeader","verifyToken","KeyStatus","JWKError","JWKType","JWKUse","JWTError","OAuth2TokenVerifier","keyStore","tokenRepository","isInactive","id","verify","token","options","payload","findOneBySignature","jti","payloadPropertyInvalid","skipActiveCheck","notActive","header","kid","headerPropertyInvalid","key","resolveById","
|
|
1
|
+
{"version":3,"file":"module.mjs","names":["extractTokenHeader","verifyToken","KeyStatus","JWKError","JWKType","JWKUse","JWTError","OAuth2TokenVerifier","keyStore","tokenRepository","isInactive","id","verify","token","options","payload","findOneBySignature","jti","payloadPropertyInvalid","skipActiveCheck","notActive","header","kid","headerPropertyInvalid","key","resolveById","use","SIGNATURE","status","DISABLED","type","OCT","decryptionKey","decryptionKeyMissing","ignoreExpiry","EC","encryptionKey","signatureAlgorithm","algorithms","saveWithSignature"],"sources":["../../../../../src/core/oauth2/token/verifier/module.ts"],"sourcesContent":["/*\n * Copyright (c) 2025.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport {\n type TokenECAlgorithm,\n type TokenRSAAlgorithm,\n extractTokenHeader,\n verifyToken,\n} from '@authup/server-kit';\nimport { KeyStatus } from '@authup/core-kit';\nimport type { OAuth2TokenPayload } from '@authup/specs';\nimport {\n JWKError,\n JWKType,\n JWKUse,\n JWTError,\n} from '@authup/specs';\nimport type { IKeyStore } from '../../../key/index.ts';\nimport type { IOAuth2TokenRepository } from '../repository/types.ts';\nimport type { IOAuth2TokenVerifier, OAuth2TokenVerifyOptions } from './types.ts';\n\nexport class OAuth2TokenVerifier implements IOAuth2TokenVerifier {\n protected keyStore : IKeyStore;\n\n protected tokenRepository : IOAuth2TokenRepository;\n\n constructor(\n keyStore : IKeyStore,\n tokenRepository : IOAuth2TokenRepository,\n ) {\n this.keyStore = keyStore;\n this.tokenRepository = tokenRepository;\n }\n\n async isInactive(id: string): Promise<boolean> {\n return this.tokenRepository.isInactive(id);\n }\n\n async verify(token: string, options: OAuth2TokenVerifyOptions = {}) : Promise<OAuth2TokenPayload> {\n let payload = await this.tokenRepository.findOneBySignature(token);\n if (payload) {\n if (!payload.jti) {\n throw JWTError.payloadPropertyInvalid('jti');\n }\n\n if (!options.skipActiveCheck) {\n const isInactive = await this.isInactive(payload.jti);\n if (isInactive) {\n throw JWTError.notActive();\n }\n }\n\n return payload;\n }\n\n const header = extractTokenHeader(token);\n if (!header.kid) {\n throw JWTError.headerPropertyInvalid('kid');\n }\n\n // A `kid` that resolves to no usable verification key is a property of\n // the TOKEN, not a missing JWK resource: `JWKError.notFound` is the\n // right answer at `GET /jwks/:id`, and here it made a dead bearer 404\n // on every resource route, escape the refresh grant's `isJWTError`\n // catch as a 404 from `/token`, and drive the kit's http hook into its\n // terminal branch instead of a refresh - so a key rotation ended every\n // live browser session. It also stopped echoing the supplied `kid`.\n const key = await this.keyStore.resolveById(header.kid);\n if (!key) {\n throw JWTError.headerPropertyInvalid('kid');\n }\n\n // the key store also holds at-rest encryption keys (use: enc) —\n // those must never verify a token.\n if (key.use !== JWKUse.SIGNATURE) {\n throw JWTError.headerPropertyInvalid('kid');\n }\n\n // disabled = neither signs nor verifies (passive still verifies).\n if (key.status === KeyStatus.DISABLED) {\n throw JWTError.headerPropertyInvalid('kid');\n }\n\n switch (key.type) {\n case JWKType.OCT: {\n if (!key.decryptionKey) {\n throw JWKError.decryptionKeyMissing();\n }\n\n payload = await verifyToken(\n token,\n {\n type: JWKType.OCT,\n key: key.decryptionKey,\n },\n { ignoreExpiry: options.ignoreExpiry },\n );\n break;\n }\n case JWKType.EC: {\n if (!key.encryptionKey) {\n throw JWKError.decryptionKeyMissing();\n }\n\n payload = await verifyToken(\n token,\n {\n type: key.type,\n key: key.encryptionKey,\n ...(\n key.signatureAlgorithm ?\n { algorithms: [key.signatureAlgorithm] } :\n []\n ) as TokenECAlgorithm[],\n },\n { ignoreExpiry: options.ignoreExpiry },\n );\n break;\n }\n default: {\n if (!key.encryptionKey) {\n throw JWKError.decryptionKeyMissing();\n }\n\n payload = await verifyToken(\n token,\n {\n type: key.type,\n key: key.encryptionKey,\n ...(\n key.signatureAlgorithm ?\n { algorithms: [key.signatureAlgorithm] } :\n []\n ) as TokenRSAAlgorithm[],\n },\n { ignoreExpiry: options.ignoreExpiry },\n );\n }\n }\n\n if (!payload.jti) {\n throw JWTError.payloadPropertyInvalid('jti');\n }\n\n // Never populate the shared signature-keyed claims cache on the\n // exp-ignoring path. An expired token verified with `ignoreExpiry`\n // (the RP-initiated-logout id_token_hint) would otherwise be re-cached\n // with buildTTL's 1h fallback (a past exp yields a non-positive ttl),\n // and the cache-first branch above returns it with no exp re-check on\n // every subsequent verify — so `/token/introspect` would report an\n // expired token as active for up to an hour (RFC 7662). Keep the\n // exp-bypass scoped to this single call.\n if (!options.ignoreExpiry) {\n await this.tokenRepository.saveWithSignature(payload, token);\n }\n\n if (!options.skipActiveCheck) {\n const isInactive = await this.isInactive(payload.jti);\n if (isInactive) {\n throw JWTError.notActive();\n }\n }\n\n return payload;\n }\n}\n"],"mappings":";;;;;;;AAyBA,IAAaO,sBAAb,MAAaA;CACCC;CAEAC;CAEV,YACID,UACAC,iBACF;EACE,KAAKD,WAAWA;EAChB,KAAKC,kBAAkBA;CAC3B;CAEA,MAAMC,WAAWC,IAA8B;EAC3C,OAAO,KAAKF,gBAAgBC,WAAWC,EAAAA;CAC3C;CAEA,MAAMC,OAAOC,OAAeC,UAAoC,CAAC,GAAiC;EAC9F,IAAIC,UAAU,MAAM,KAAKN,gBAAgBO,mBAAmBH,KAAAA;EAC5D,IAAIE,SAAS;GACT,IAAI,CAACA,QAAQE,KACT,MAAMX,SAASY,uBAAuB,KAAA;GAG1C,IAAI,CAACJ,QAAQK,iBAELT;QAAAA,MADqB,KAAKA,WAAWK,QAAQE,GAAG,GAEhD,MAAMX,SAASc,UAAS;GAAA;GAIhC,OAAOL;EACX;EAEA,MAAMM,SAASrB,mBAAmBa,KAAAA;EAClC,IAAI,CAACQ,OAAOC,KACR,MAAMhB,SAASiB,sBAAsB,KAAA;EAUzC,MAAMC,MAAM,MAAM,KAAKhB,SAASiB,YAAYJ,OAAOC,GAAG;EACtD,IAAI,CAACE,KACD,MAAMlB,SAASiB,sBAAsB,KAAA;EAKzC,IAAIC,IAAIE,QAAQrB,OAAOsB,WACnB,MAAMrB,SAASiB,sBAAsB,KAAA;EAIzC,IAAIC,IAAII,WAAW1B,UAAU2B,UACzB,MAAMvB,SAASiB,sBAAsB,KAAA;EAGzC,QAAQC,IAAIM,MAAZ;GACI,KAAK1B,QAAQ2B;IACT,IAAI,CAACP,IAAIQ,eACL,MAAM7B,SAAS8B,qBAAoB;IAGvClB,UAAU,MAAMd,YACZY,OACA;KACIiB,MAAM1B,QAAQ2B;KACdP,KAAKA,IAAIQ;IACb,GACA,EAAEE,cAAcpB,QAAQoB,aAAa,CAAA;IAEzC;GAEJ,KAAK9B,QAAQ+B;IACT,IAAI,CAACX,IAAIY,eACL,MAAMjC,SAAS8B,qBAAoB;IAGvClB,UAAU,MAAMd,YACZY,OACA;KACIiB,MAAMN,IAAIM;KACVN,KAAKA,IAAIY;KACT,GACIZ,IAAIa,qBACA,EAAEC,YAAY,CAACd,IAAIa,kBAAmB,EAAC,IACvC,CAAA;IAEZ,GACA,EAAEH,cAAcpB,QAAQoB,aAAa,CAAA;IAEzC;GAEJ;IACI,IAAI,CAACV,IAAIY,eACL,MAAMjC,SAAS8B,qBAAoB;IAGvClB,UAAU,MAAMd,YACZY,OACA;KACIiB,MAAMN,IAAIM;KACVN,KAAKA,IAAIY;KACT,GACIZ,IAAIa,qBACA,EAAEC,YAAY,CAACd,IAAIa,kBAAmB,EAAC,IACvC,CAAA;IAEZ,GACA,EAAEH,cAAcpB,QAAQoB,aAAa,CAAA;EAGjD;EAEA,IAAI,CAACnB,QAAQE,KACT,MAAMX,SAASY,uBAAuB,KAAA;EAW1C,IAAI,CAACJ,QAAQoB,cACT,MAAM,KAAKzB,gBAAgB8B,kBAAkBxB,SAASF,KAAAA;EAG1D,IAAI,CAACC,QAAQK,iBAELT;OAAAA,MADqB,KAAKA,WAAWK,QAAQE,GAAG,GAEhD,MAAMX,SAASc,UAAS;EAAA;EAIhC,OAAOL;CACX;AACJ"}
|