@authup/server-core 1.0.0-beta.61 → 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/client-scope/entity.d.ts.map +1 -1
- package/dist/adapters/database/domains/client-scope/entity.mjs +16 -8
- package/dist/adapters/database/domains/client-scope/entity.mjs.map +1 -1
- package/dist/adapters/database/domains/identity-provider-account/entity.d.ts.map +1 -1
- package/dist/adapters/database/domains/identity-provider-account/entity.mjs +1 -1
- package/dist/adapters/database/domains/identity-provider-account/entity.mjs.map +1 -1
- 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/database/migrations/mysql/1786631686318-SortIndexesAndAccountUniqueness.d.ts +50 -0
- package/dist/adapters/database/migrations/mysql/1786631686318-SortIndexesAndAccountUniqueness.d.ts.map +1 -0
- package/dist/adapters/database/migrations/mysql/1786631686318-SortIndexesAndAccountUniqueness.mjs +86 -0
- package/dist/adapters/database/migrations/mysql/1786631686318-SortIndexesAndAccountUniqueness.mjs.map +1 -0
- package/dist/adapters/database/migrations/postgres/1786631686318-SortIndexesAndAccountUniqueness.d.ts +42 -0
- package/dist/adapters/database/migrations/postgres/1786631686318-SortIndexesAndAccountUniqueness.d.ts.map +1 -0
- package/dist/adapters/database/migrations/postgres/1786631686318-SortIndexesAndAccountUniqueness.mjs +80 -0
- package/dist/adapters/database/migrations/postgres/1786631686318-SortIndexesAndAccountUniqueness.mjs.map +1 -0
- 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/adapters/oauth2/grant-types/utils/credentials.mjs +1 -1
- package/dist/adapters/http/adapters/oauth2/grant-types/utils/guess.mjs +1 -1
- package/dist/adapters/http/controllers/entities/client/module.mjs +2 -2
- package/dist/adapters/http/controllers/entities/identity-provider/module.d.ts +24 -4
- package/dist/adapters/http/controllers/entities/identity-provider/module.d.ts.map +1 -1
- package/dist/adapters/http/controllers/entities/identity-provider/module.mjs +117 -106
- package/dist/adapters/http/controllers/entities/identity-provider/module.mjs.map +1 -1
- package/dist/adapters/http/controllers/entities/identity-provider/types.d.ts +6 -4
- package/dist/adapters/http/controllers/entities/identity-provider/types.d.ts.map +1 -1
- package/dist/adapters/http/controllers/entities/realm/module.mjs +2 -2
- package/dist/adapters/http/controllers/entities/session/module.mjs +2 -2
- package/dist/adapters/http/controllers/entities/user/module.mjs +2 -2
- package/dist/adapters/http/controllers/entities/user-authenticator/module.mjs +2 -2
- 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 +14 -5
- 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/controllers/workflows/token/utils/extract.mjs +2 -1
- package/dist/adapters/http/controllers/workflows/token/utils/extract.mjs.map +1 -1
- package/dist/adapters/http/middleware/built-in/assets.mjs +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/request/helpers/param-id.d.ts.map +1 -1
- package/dist/adapters/http/request/helpers/param-id.mjs +2 -1
- package/dist/adapters/http/request/helpers/param-id.mjs.map +1 -1
- package/dist/adapters/http/ui/account-console/module.mjs +1 -1
- package/dist/adapters/http/ui/auth-console/http-client.mjs +1 -1
- package/dist/adapters/http/ui/auth-console/module.mjs +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/adapters/http/ui/shared/html.d.ts.map +1 -1
- package/dist/adapters/http/ui/shared/html.mjs +1 -0
- package/dist/adapters/http/ui/shared/html.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/read/env.mjs +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/event/repository.d.ts +2 -2
- package/dist/app/modules/database/repositories/event/repository.d.ts.map +1 -1
- package/dist/app/modules/database/repositories/event/repository.mjs +23 -6
- package/dist/app/modules/database/repositories/event/repository.mjs.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/database/repositories/identity-provider-account/repository.d.ts.map +1 -1
- package/dist/app/modules/database/repositories/identity-provider-account/repository.mjs +9 -1
- package/dist/app/modules/database/repositories/identity-provider-account/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 +20 -6
- package/dist/app/modules/http/modules/controller.mjs.map +1 -1
- package/dist/app/modules/identity/module.d.ts.map +1 -1
- package/dist/app/modules/identity/module.mjs +6 -3
- package/dist/app/modules/identity/module.mjs.map +1 -1
- package/dist/app/modules/identity/repositories/user.d.ts +1 -0
- package/dist/app/modules/identity/repositories/user.d.ts.map +1 -1
- package/dist/app/modules/identity/repositories/user.mjs +3 -0
- package/dist/app/modules/identity/repositories/user.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/authorize/state/repository.d.ts +1 -2
- package/dist/app/modules/oauth2/repositories/authorize/state/repository.d.ts.map +1 -1
- package/dist/app/modules/oauth2/repositories/authorize/state/repository.mjs +7 -16
- package/dist/app/modules/oauth2/repositories/authorize/state/repository.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/app/modules/runtime/module.mjs +2 -2
- package/dist/cli/commands/migration.mjs +1 -1
- package/dist/core/entities/client-scope/schema.d.ts.map +1 -1
- package/dist/core/entities/client-scope/schema.mjs +16 -1
- package/dist/core/entities/client-scope/schema.mjs.map +1 -1
- package/dist/core/entities/event/constants.d.ts +9 -0
- package/dist/core/entities/event/constants.d.ts.map +1 -1
- package/dist/core/entities/event/constants.mjs +9 -1
- package/dist/core/entities/event/constants.mjs.map +1 -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/event/index.mjs +2 -2
- package/dist/core/entities/event/types.d.ts +11 -2
- package/dist/core/entities/event/types.d.ts.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/entities/identity-provider-account/types.d.ts +6 -0
- package/dist/core/entities/identity-provider-account/types.d.ts.map +1 -1
- package/dist/core/entities/identity-provider-account/types.mjs.map +1 -1
- package/dist/core/entities/index.mjs +2 -2
- package/dist/core/identity/entities/user/types.d.ts +1 -0
- package/dist/core/identity/entities/user/types.d.ts.map +1 -1
- package/dist/core/identity/index.mjs +12 -11
- package/dist/core/identity/provider/account/module.d.ts +3 -0
- package/dist/core/identity/provider/account/module.d.ts.map +1 -1
- package/dist/core/identity/provider/account/module.mjs +54 -27
- package/dist/core/identity/provider/account/module.mjs.map +1 -1
- package/dist/core/identity/provider/account/types.d.ts +7 -0
- package/dist/core/identity/provider/account/types.d.ts.map +1 -1
- package/dist/core/identity/provider/authentication/index.mjs +3 -2
- 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 +12 -11
- package/dist/core/index.mjs +16 -12
- package/dist/core/oauth2/authorization/code-request/verifier/module.d.ts.map +1 -1
- package/dist/core/oauth2/authorization/code-request/verifier/module.mjs +7 -1
- package/dist/core/oauth2/authorization/code-request/verifier/module.mjs.map +1 -1
- 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/module.mjs +1 -2
- package/dist/core/oauth2/authorization/state/module.mjs.map +1 -1
- package/dist/core/oauth2/authorization/state/types.d.ts +13 -2
- package/dist/core/oauth2/authorization/state/types.d.ts.map +1 -1
- package/dist/core/oauth2/end-session/service.d.ts.map +1 -1
- package/dist/core/oauth2/end-session/service.mjs +1 -0
- package/dist/core/oauth2/end-session/service.mjs.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 +4 -0
- package/dist/core/oauth2/federated-login/index.d.ts.map +1 -0
- package/dist/core/oauth2/federated-login/index.mjs +7 -0
- package/dist/core/oauth2/federated-login/module.d.ts +29 -0
- package/dist/core/oauth2/federated-login/module.d.ts.map +1 -0
- package/dist/core/oauth2/federated-login/module.mjs +193 -0
- package/dist/core/oauth2/federated-login/module.mjs.map +1 -0
- package/dist/core/oauth2/federated-login/types.d.ts +196 -0
- package/dist/core/oauth2/federated-login/types.d.ts.map +1 -0
- package/dist/core/oauth2/federated-login/types.mjs +36 -0
- package/dist/core/oauth2/federated-login/types.mjs.map +1 -0
- 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.d.ts +1 -0
- package/dist/core/oauth2/index.d.ts.map +1 -1
- package/dist/core/oauth2/index.mjs +5 -1
- 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/core/provisioning/entities/policy/validator.d.ts.map +1 -1
- package/dist/core/provisioning/entities/policy/validator.mjs +2 -1
- package/dist/core/provisioning/entities/policy/validator.mjs.map +1 -1
- package/dist/core/provisioning/entities/realm/wildcard-validator.d.ts.map +1 -1
- package/dist/core/provisioning/entities/realm/wildcard-validator.mjs +2 -1
- package/dist/core/provisioning/entities/realm/wildcard-validator.mjs.map +1 -1
- package/dist/core/security/policy/evaluator.mjs.map +1 -1
- package/dist/node_modules/@ebec/core/dist/index.mjs +130 -0
- package/dist/node_modules/@ebec/core/dist/index.mjs.map +1 -0
- package/dist/swagger.json +119 -43
- package/dist/utils/error.d.ts.map +1 -1
- package/dist/utils/error.mjs +5 -6
- package/dist/utils/error.mjs.map +1 -1
- package/package.json +29 -29
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module.mjs","names":["BuiltInPolicyType","definePolicyData","ValidatorGroup","base64URLDecode","isObject","isUUID","DBody","DContext","DController","DDelete","DGet","DPath","DPost","DPut","DTags","getRequestHeader","getRequestIP","sendRedirect","EventName","EventRefType","EventScope","IdentityProviderAttributesValidator","IdentityProviderValidator","IdentityType","PermissionName","SessionAuthMethod","isOAuth2IdentityProvider","isOpenIDIdentityProvider","BadRequestError","EntityInactiveError","EntityNotFoundError","describeError","resolveURL","useRequestQuery","readRequestBody","OAuth2ErrorCode","OAuth2RequestError","URL","OAuth2AuthorizationCodeRequestValidator","RECORD_QUERY_PARAMETERS","createIdentityProviderOAuth2Authenticator","decodeQuery","describeQuerySchema","identityProviderSchema","toIdentityPolicyData","applyRouteRealmIDToBody","buildActorContext","getBodyRealmID","getRequestParamID","getRequestRealmID","useRequestEventContext","useRequestIdentityOrFail","useRequestParamID","useRequestPermissionEvaluator","ForceLoggedInMiddleware","IdentityProviderController","options","repository","realmRepository","accountManager","linkStore","codeRequestVerifier","codeRequestValidator","stateManager","codeIssuer","accessPolicyEvaluator","eventService","logger","ctx","getProviders","event","data","meta","findMany","schema","actor","permissionEvaluator","preEvaluate","name","IDENTITY_PROVIDER_READ","datum","evaluate","ATTRIBUTES","REALM_MATCH","realmId","getProvider","id","paramId","entity","findOneByIdOrName","editProvider","user","write","updateOnly","put","dropProvider","IDENTITY_PROVIDER_DELETE","findOneBy","entityId","remove","response","status","addProvider","authorizeOut","_id","resolve","authenticator","buildProviderAuthenticator","codeRequest","query","codeRequestDecoded","JSON","parse","malformed","codeRequestValidated","run","verify","client","state","saveAuthorizationState","buildRedirectURL","linkRequest","enabled","identity","type","USER","link","userId","providerId","url","authorizeIn","Error","protocol","verifyAuthorizationState","completeLink","realm_id","code","length","verified","undefined","clientId","client_id","authenticate","active","realm","accessPolicyId","allowed","subject","buildHostedAuthorizeURL","searchParams","set","ACCESS_DENIED","href","authorizationCode","issue","response_type","authMethod","EXTERNAL","record","scope","OAUTH2","AUTHORIZE","refType","CLIENT","refId","sessionId","actorType","actorId","actorName","requestIpAddress","requestUserAgent","reason","providerName","redirectUriVerified","redirect_uri","baseURL","group","body","where","IDENTITY_PROVIDER_UPDATE","UPDATE","IDENTITY_PROVIDER_CREATE","CREATE","validator","attributesValidator","attributes","validateJoinColumns","checkUniqueness","merge","saveWithEA","create","provider","resolveIdentity","handle","save","providerUserId","providerUserName","pickIdentityCandidate","providerUserEmail","e","error","confirmLink","_data","consume","account","attributeCandidates","email","requestContext","IDENTITY","IDENTITY_PROVIDER_LINKED","IDENTITY_PROVIDER_ACCOUNT","userRealmId","requestPath","requestMethod","key","candidates","candidate","codeRequestKeys","Object","keys","codeRequestKey_","codeRequestKey","codeRequestValue","String","findOneById","ip","userAgent","stateInvalid"],"sources":["../../../../../../src/adapters/http/controllers/entities/identity-provider/module.ts"],"sourcesContent":["/*\n * Copyright (c) 2022.\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 { BuiltInPolicyType, definePolicyData } from '@authup/access';\nimport { \n ValidatorGroup, \n base64URLDecode, \n isObject, \n isUUID, \n} from '@authup/kit';\nimport {\n DBody,\n DContext,\n DController,\n DDelete,\n DGet,\n DPath,\n DPost,\n DPut,\n DTags,\n} from '@routup/decorators';\nimport type { IAppEvent } from 'routup';\nimport {\n getRequestHeader,\n getRequestIP,\n sendRedirect,\n} from 'routup';\nimport type {\n IdentityProvider,\n IdentityProviderAccount,\n OAuth2AuthorizationCodeRequest,\n OAuth2IdentityProvider,\n OpenIDIdentityProvider,\n} from '@authup/core-kit';\nimport {\n EventName,\n EventRefType,\n EventScope,\n IdentityProviderAttributesValidator,\n IdentityProviderValidator,\n IdentityType,\n PermissionName,\n SessionAuthMethod,\n isOAuth2IdentityProvider,\n isOpenIDIdentityProvider,\n} from '@authup/core-kit';\nimport { BadRequestError, EntityInactiveError, EntityNotFoundError } from '@authup/errors';\nimport type { Logger } from '@authup/server-kit';\nimport { describeError, resolveURL } from '../../../../../utils/index.ts';\nimport { useRequestQuery } from '@routup/basic/query';\nimport { readRequestBody } from '@routup/basic/body';\nimport { OAuth2ErrorCode, OAuth2RequestError } from '@authup/specs';\nimport type {\n EntityCollectionResponse,\n EntityRecordResponse,\n IdentityProviderCreatePayload,\n IdentityProviderLinkConfirmPayload,\n IdentityProviderLinkRequestResponse,\n IdentityProviderSavePayload,\n IdentityProviderUpdatePayload,\n} from '@authup/core-http-kit';\nimport { URL } from 'node:url';\nimport type {\n IEventService,\n IIdentityProviderAccountLinkStore,\n IIdentityProviderAccountManager,\n IIdentityProviderRepository,\n IOAuth2AccessPolicyEvaluator,\n IOAuth2AuthorizationCodeIssuer,\n IOAuth2AuthorizationCodeRequestVerifier,\n IOAuth2AuthorizationStateManager,\n IRealmRepository,\n IdentityProviderIdentity,\n OAuth2AuthorizationState,\n OAuth2AuthorizationStateLink,\n} from '../../../../../core/index.ts';\nimport {\n OAuth2AuthorizationCodeRequestValidator,\n RECORD_QUERY_PARAMETERS,\n createIdentityProviderOAuth2Authenticator,\n decodeQuery,\n describeQuerySchema,\n identityProviderSchema,\n toIdentityPolicyData,\n} from '../../../../../core/index.ts';\nimport {\n applyRouteRealmIDToBody,\n buildActorContext,\n getBodyRealmID,\n getRequestParamID,\n getRequestRealmID,\n useRequestEventContext,\n useRequestIdentityOrFail,\n useRequestParamID,\n useRequestPermissionEvaluator,\n} from '../../../request/index.ts';\nimport { ForceLoggedInMiddleware } from '../../../middleware/index.ts';\nimport type { IdentityProviderControllerContext, IdentityProviderControllerOptions } from './types.ts';\n\n@DTags('identity')\n@DController(['/identity-providers', '/realms/:realmId/identity-providers'])\nexport class IdentityProviderController {\n protected options: IdentityProviderControllerOptions;\n\n protected repository: IIdentityProviderRepository;\n\n protected realmRepository: IRealmRepository;\n\n protected accountManager: IIdentityProviderAccountManager;\n\n protected linkStore: IIdentityProviderAccountLinkStore;\n\n protected codeRequestVerifier : IOAuth2AuthorizationCodeRequestVerifier;\n\n protected codeRequestValidator : OAuth2AuthorizationCodeRequestValidator;\n\n protected stateManager : IOAuth2AuthorizationStateManager;\n\n protected codeIssuer : IOAuth2AuthorizationCodeIssuer;\n\n protected accessPolicyEvaluator? : IOAuth2AccessPolicyEvaluator;\n\n protected eventService? : IEventService;\n\n protected logger? : Logger;\n\n // ---------------------------------------------------------\n\n constructor(ctx: IdentityProviderControllerContext) {\n this.options = ctx.options;\n this.repository = ctx.repository;\n this.realmRepository = ctx.realmRepository;\n this.accountManager = ctx.accountManager;\n this.linkStore = ctx.linkStore;\n this.codeIssuer = ctx.codeIssuer;\n this.codeRequestVerifier = ctx.codeRequestVerifier;\n this.codeRequestValidator = new OAuth2AuthorizationCodeRequestValidator();\n this.stateManager = ctx.stateManager;\n this.accessPolicyEvaluator = ctx.accessPolicyEvaluator;\n this.eventService = ctx.eventService;\n this.logger = ctx.logger;\n }\n\n // ---------------------------------------------------------\n\n @DGet('', [])\n async getProviders(\n @DContext() event: IAppEvent,\n ): Promise<EntityCollectionResponse<IdentityProvider>> {\n const {\n data,\n meta,\n } = await this.repository.findMany(\n await decodeQuery(useRequestQuery(event), {\n schema: identityProviderSchema,\n actor: buildActorContext(event),\n }),\n );\n\n try {\n const permissionEvaluator = useRequestPermissionEvaluator(event);\n await permissionEvaluator.preEvaluate({ name: PermissionName.IDENTITY_PROVIDER_READ });\n\n for (const datum of data) {\n try {\n await permissionEvaluator.evaluate({\n name: PermissionName.IDENTITY_PROVIDER_READ,\n data: definePolicyData({ [BuiltInPolicyType.ATTRIBUTES]: datum, [BuiltInPolicyType.REALM_MATCH]: datum.realmId ?? null }),\n });\n } catch {\n // do nothing\n }\n }\n } catch {\n // do nothing\n }\n\n return {\n data,\n meta: {\n ...meta,\n schema: describeQuerySchema(identityProviderSchema),\n },\n };\n }\n\n @DGet('/:id', [])\n async getProvider(\n @DPath('id') id: string,\n @DContext() event: IAppEvent,\n ): Promise<EntityRecordResponse<IdentityProvider>> {\n const paramId = useRequestParamID(event, { isUUID: false });\n\n const entity = await this.repository.findOneByIdOrName(\n paramId,\n getRequestRealmID(event),\n );\n\n if (!entity) {\n throw new EntityNotFoundError();\n }\n\n try {\n const permissionEvaluator = useRequestPermissionEvaluator(event);\n await permissionEvaluator.evaluate({\n name: PermissionName.IDENTITY_PROVIDER_READ,\n data: definePolicyData({ [BuiltInPolicyType.ATTRIBUTES]: entity, [BuiltInPolicyType.REALM_MATCH]: entity.realmId ?? null }),\n });\n } catch {\n // do nothing\n }\n\n return { data: entity, meta: { schema: describeQuerySchema(identityProviderSchema, RECORD_QUERY_PARAMETERS) } };\n }\n\n @DPost('/:id', [ForceLoggedInMiddleware])\n async editProvider(\n @DPath('id') id: string,\n @DBody() user: IdentityProviderUpdatePayload,\n @DContext() event: IAppEvent,\n ) : Promise<EntityRecordResponse<IdentityProvider>> {\n return this.write(event, { updateOnly: true });\n }\n\n @DPut('/:id', [ForceLoggedInMiddleware])\n async put(\n @DPath('id') id: string,\n @DBody() user: IdentityProviderSavePayload,\n @DContext() event: IAppEvent,\n ) : Promise<EntityRecordResponse<IdentityProvider>> {\n return this.write(event);\n }\n\n @DDelete('/:id', [ForceLoggedInMiddleware])\n async dropProvider(\n @DPath('id') id: string,\n @DContext() event: IAppEvent,\n ) : Promise<EntityRecordResponse<IdentityProvider>> {\n const paramId = useRequestParamID(event);\n\n const permissionEvaluator = useRequestPermissionEvaluator(event);\n await permissionEvaluator.preEvaluate({ name: PermissionName.IDENTITY_PROVIDER_DELETE });\n\n const entity = await this.repository.findOneBy({ id: paramId });\n\n if (!entity) {\n throw new EntityNotFoundError();\n }\n\n await permissionEvaluator.evaluate({\n name: PermissionName.IDENTITY_PROVIDER_DELETE,\n data: definePolicyData({ [BuiltInPolicyType.ATTRIBUTES]: entity, [BuiltInPolicyType.REALM_MATCH]: entity.realmId ?? null }),\n });\n\n const { id: entityId } = entity;\n\n await this.repository.remove(entity);\n\n entity.id = entityId;\n\n event.response.status = 202;\n\n return { data: entity, meta: {} };\n }\n\n @DPost('', [ForceLoggedInMiddleware])\n async addProvider(\n @DBody() user: IdentityProviderCreatePayload,\n @DContext() event: IAppEvent,\n ) : Promise<EntityRecordResponse<IdentityProvider>> {\n return this.write(event);\n }\n\n // ---------------------------------------------------------\n\n @DGet('/:id/authorize-out', [])\n async authorizeOut(\n @DPath('id') _id: string,\n @DContext() event: IAppEvent,\n ) {\n const id = useRequestParamID(event);\n const entity = await this.resolve(id);\n\n if (!isOAuth2IdentityProvider(entity) && !isOpenIDIdentityProvider(entity)) {\n throw new BadRequestError('Only an identity-provider based on the oauth protocol supports authorize redirect.');\n }\n\n const authenticator = this.buildProviderAuthenticator(entity);\n\n let codeRequest: OAuth2AuthorizationCodeRequest | undefined;\n const query = useRequestQuery(event);\n if (typeof query.codeRequest === 'string') {\n let codeRequestDecoded: OAuth2AuthorizationCodeRequest;\n\n try {\n codeRequestDecoded = JSON.parse(base64URLDecode(query.codeRequest));\n } catch {\n throw OAuth2RequestError.malformed('The code request is malformed and can not be parsed.');\n }\n\n const codeRequestValidated = await this.codeRequestValidator.run(codeRequestDecoded);\n const data = await this.codeRequestVerifier.verify(codeRequestValidated);\n\n if (\n data.client.realmId &&\n entity.realmId &&\n entity.realmId !== data.client.realmId\n ) {\n throw OAuth2RequestError.malformed('The provider and client realm do not match.');\n }\n\n codeRequest = data.data;\n }\n\n const state = await this.saveAuthorizationState(event, { codeRequest });\n\n return sendRedirect(event, authenticator.buildRedirectURL({ state }));\n }\n\n @DPost('/:id/link-request', [ForceLoggedInMiddleware])\n async linkRequest(\n @DPath('id') _id: string,\n @DContext() event: IAppEvent,\n ) : Promise<IdentityProviderLinkRequestResponse> {\n const id = useRequestParamID(event);\n const entity = await this.resolve(id);\n\n if (!isOAuth2IdentityProvider(entity) && !isOpenIDIdentityProvider(entity)) {\n throw new BadRequestError('Only an identity-provider based on the oauth protocol supports account linking.');\n }\n\n if (!entity.enabled) {\n throw new BadRequestError('The identity provider is not enabled.');\n }\n\n const identity = useRequestIdentityOrFail(event);\n if (identity.type !== IdentityType.USER) {\n throw new BadRequestError('Only a user can link an identity provider account.');\n }\n\n if (entity.realmId && entity.realmId !== identity.realmId) {\n throw new BadRequestError('The identity provider does not belong to the user realm.');\n }\n\n const authenticator = this.buildProviderAuthenticator(entity);\n\n const state = await this.saveAuthorizationState(event, { link: { userId: identity.id, providerId: entity.id } });\n\n return { url: authenticator.buildRedirectURL({ state }) };\n }\n\n @DGet('/:id/authorize-in', [])\n async authorizeIn(\n @DPath('id') _id: string,\n @DContext() event: IAppEvent,\n ) {\n const id = useRequestParamID(event);\n\n const entity = await this.resolve(id);\n\n if (!isOAuth2IdentityProvider(entity) && !isOpenIDIdentityProvider(entity)) {\n throw new Error(`The provider protocol ${entity.protocol} is not valid.`);\n }\n\n const data = await this.verifyAuthorizationState(event);\n\n const { link } = data;\n if (link) {\n return this.completeLink(event, entity, data, link);\n }\n\n if (\n entity.realmId &&\n data.codeRequest &&\n data.codeRequest.realm_id &&\n data.codeRequest.realm_id !== entity.realmId\n ) {\n throw OAuth2RequestError.malformed('The provider and client realm do not match.');\n }\n\n const { code } = useRequestQuery(event);\n\n if (typeof code !== 'string' || code.length === 0) {\n throw new BadRequestError('The authorization code is missing.');\n }\n\n // Re-verify the code request that authorize-out stored on the state,\n // BEFORE the provider's single-use code is spent. It re-resolves the\n // client (active, grant allowlist, scopes) and re-matches the\n // redirect_uri against the client's registered patterns, so a client\n // deactivated (or a pattern removed) while the user was away at the\n // provider cannot still receive a code, and a refused completion\n // provisions no user. Same fail-closed-at-completion rule the link path\n // applies to the provider.\n //\n // The redirect below rests on the MATCH, which the verifier enforces by\n // throwing `redirectUriMismatch`; `redirectUriVerified` is the flag\n // that survives to report it. Nothing else on this path knows whether\n // the uri was ever matched.\n const verified = data.codeRequest ?\n await this.codeRequestVerifier.verify(data.codeRequest) :\n undefined;\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.\n if (!entity.enabled) {\n throw new BadRequestError('The identity provider is not enabled.');\n }\n\n const authenticator = this.buildProviderAuthenticator(entity, { clientId: verified?.data.client_id });\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 reachable\n // for an already-linked user: a first login provisions an active one.\n if (!user.active) {\n throw new EntityInactiveError({ entity: 'user' });\n }\n\n const realm = await this.realmRepository.resolve(entity.realmId, true);\n\n // Application access policy (plan 052), federated leg. The denial\n // bounces back to the hosted authorize page, which renders it as a\n // denial card, rather than becoming an error redirect to the RP: the\n // person is standing in front of the browser and the card is the only\n // surface that can tell them why. A policy id with no wired evaluator\n // 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 const url = this.buildHostedAuthorizeURL(verified.data);\n url.searchParams.set('error', OAuth2ErrorCode.ACCESS_DENIED);\n\n return sendRedirect(event, url.href);\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 all\n // (`PKCE is required for public clients`), which is every console\n // client.\n const authorizationCode = await this.codeIssuer.issue(\n verified?.data ?? { response_type: 'code' },\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: getRequestIP(event) ?? null,\n requestUserAgent: getRequestHeader(event, 'user-agent') ?? null,\n data: {\n reason: 'federated',\n providerId: entity.id,\n providerName: entity.name,\n ...(verified?.data.scope ? { scope: verified.data.scope } : {}),\n },\n });\n\n // RFC 6749 §4.1.2: the code goes back to the client that asked for it.\n // It is bound to that client_id and that redirect_uri, so the RP is the\n // only party able to redeem it. Handing it to any other page strands\n // the login there (issue #3446).\n if (verified?.redirectUriVerified && verified.data.redirect_uri) {\n const url = new URL(verified.data.redirect_uri);\n url.searchParams.set('code', authorizationCode.id);\n if (verified.data.state) {\n url.searchParams.set('state', verified.data.state);\n }\n\n return sendRedirect(event, url.href);\n }\n\n // Only reachable when the state carried no code request at all: a\n // stored one always has a redirect_uri, because that mount is required\n // in OAuth2AuthorizationCodeRequestValidator (unlike `state`), so a\n // verified request is always a verified redirect target.\n const url = new URL(this.options.baseURL);\n url.searchParams.set('code', authorizationCode.id);\n\n return sendRedirect(event, url.href);\n }\n\n // ---------------------------------------------------------\n\n private async write(event: IAppEvent, options: {\n updateOnly?: boolean\n } = {}): Promise<EntityRecordResponse<IdentityProvider>> {\n let group: string;\n const id = getRequestParamID(event, { isUUID: false });\n const body = await readRequestBody(event);\n applyRouteRealmIDToBody(event, body);\n const realmId = getRequestRealmID(event) ?? getBodyRealmID(body);\n\n let entity: IdentityProvider | null | undefined;\n if (id) {\n const where: Record<string, any> = {};\n if (isUUID(id)) {\n where.id = id;\n } else {\n where.name = id;\n }\n\n if (realmId) {\n where.realmId = realmId;\n }\n\n entity = await this.repository.findOneBy(where);\n // Only a NAME key may upsert-create. A UUID addresses one specific\n // row, so a miss is a 404 (creating would write a different id).\n if (!entity && (options.updateOnly || where.id)) {\n throw new EntityNotFoundError();\n }\n } else if (options.updateOnly) {\n throw new EntityNotFoundError();\n }\n\n const permissionEvaluator = useRequestPermissionEvaluator(event);\n if (entity) {\n await permissionEvaluator.preEvaluate({ name: PermissionName.IDENTITY_PROVIDER_UPDATE });\n\n group = ValidatorGroup.UPDATE;\n } else {\n await permissionEvaluator.preEvaluate({ name: PermissionName.IDENTITY_PROVIDER_CREATE });\n\n group = ValidatorGroup.CREATE;\n }\n\n const validator = new IdentityProviderValidator();\n const data = await validator.run(body, { group });\n\n const attributesValidator = new IdentityProviderAttributesValidator();\n const attributes = await attributesValidator.run(body);\n\n await this.repository.validateJoinColumns(data);\n\n if (entity) {\n await permissionEvaluator.evaluate({\n name: PermissionName.IDENTITY_PROVIDER_UPDATE,\n data: definePolicyData({\n [BuiltInPolicyType.ATTRIBUTES]: {\n ...entity,\n ...data,\n },\n [BuiltInPolicyType.REALM_MATCH]: data.realmId ?? entity.realmId ?? null,\n }),\n });\n } else {\n if (!data.realmId) {\n const identity = useRequestIdentityOrFail(event);\n data.realmId = identity.realmId;\n }\n\n await permissionEvaluator.evaluate({\n name: PermissionName.IDENTITY_PROVIDER_CREATE,\n data: definePolicyData({ [BuiltInPolicyType.ATTRIBUTES]: data, [BuiltInPolicyType.REALM_MATCH]: data.realmId ?? null }),\n });\n }\n\n await this.repository.checkUniqueness(data, entity || undefined);\n\n if (entity) {\n entity = this.repository.merge(entity, data);\n await this.repository.saveWithEA(entity, attributes);\n\n event.response.status = 202;\n\n return { data: entity, meta: {} };\n }\n\n entity = this.repository.create(data);\n await this.repository.saveWithEA(entity, attributes);\n\n event.response.status = 201;\n\n return { data: entity, meta: {} };\n }\n\n // ---------------------------------------------------------\n\n /**\n * Callback half of the account-linking round-trip (plan 091): the\n * external identity is bound to the state-referenced user instead of\n * running the login path. No auth code and no session is minted; the\n * browser lands back on the account console with a marker param.\n */\n private async completeLink(\n event: IAppEvent,\n provider: OAuth2IdentityProvider | OpenIDIdentityProvider,\n state: OAuth2AuthorizationState,\n link: OAuth2AuthorizationStateLink,\n ) {\n // Fixed, server-derived return target (the account console page).\n // No client-supplied redirect exists on this path.\n const url = new URL(resolveURL(this.options.baseURL, 'account/connected-accounts'));\n\n const { code } = useRequestQuery(event);\n\n try {\n // The state is bound to the provider it was minted for, and the\n // provider must still be enabled at completion — a state cannot\n // be replayed against a different provider's callback, nor\n // complete a link after its provider was disabled.\n if (link.providerId !== provider.id || !provider.enabled) {\n throw new BadRequestError('The identity provider is not available for account linking.');\n }\n\n if (typeof code !== 'string' || code.length === 0) {\n throw new BadRequestError('The authorization code is missing.');\n }\n\n const authenticator = this.buildProviderAuthenticator(provider);\n\n const identity = await authenticator.resolveIdentity({ code });\n\n // A provider answering without a subject has nothing to bind. The\n // callback used to reach `link()`, which rejected it; the stash\n // would otherwise carry an empty providerUserId to the confirm.\n if (!identity.id) {\n throw new BadRequestError('The identity provider returned no subject.');\n }\n\n // Nothing is written here. This request is unauthenticated — the\n // browser round-trip cannot carry a bearer — so the only thing\n // distinguishing the browser that started the link from any other\n // would be the state's ip / user-agent binding, and both values\n // are chosen by whoever minted the state. An attacker minting a\n // state carrying their OWN userId and getting a victim to follow\n // it would otherwise bind the VICTIM's external identity to the\n // attacker's account (issue #3439).\n //\n // The resolved identity is stashed under a one-time handle\n // instead, and the account console confirms it with its bearer.\n // The stash keeps the requesting userId so the confirm can\n // require it to equal the AUTHENTICATED user: the handle is a\n // URL parameter, so it reaches browser history and proxy logs,\n // and on its own it must authorize nothing.\n const handle = await this.linkStore.save({\n providerId: provider.id,\n userId: link.userId,\n providerUserId: identity.id,\n providerUserName: this.pickIdentityCandidate(identity, 'name'),\n providerUserEmail: this.pickIdentityCandidate(identity, 'email'),\n });\n\n url.searchParams.set('linkHandle', handle);\n url.searchParams.set('provider', provider.id);\n } catch (e) {\n // The failure is swallowed into a redirect marker, so the log is\n // the only surface it can reach. Without this an unreachable or\n // rejecting provider is indistinguishable from a stale state.\n this.logger?.error(describeError(e, 'The identity-provider account link failed.'));\n\n url.searchParams.set('linkError', 'link_failed');\n }\n\n return sendRedirect(event, url.href);\n }\n\n /**\n * The bearer-authenticated half of the link (issue #3439): the account\n * row is written here, for the AUTHENTICATED user, never in the callback.\n */\n @DPost('/:id/link-confirm', [ForceLoggedInMiddleware])\n async confirmLink(\n @DPath('id') _id: string,\n @DBody() _data: IdentityProviderLinkConfirmPayload,\n @DContext() event: IAppEvent,\n ) : Promise<EntityRecordResponse<IdentityProviderAccount>> {\n const id = useRequestParamID(event);\n const provider = await this.resolve(id);\n\n if (!isOAuth2IdentityProvider(provider) && !isOpenIDIdentityProvider(provider)) {\n throw new BadRequestError('Only an identity-provider based on the oauth protocol supports account linking.');\n }\n\n if (!provider.enabled) {\n throw new BadRequestError('The identity provider is not enabled.');\n }\n\n const identity = useRequestIdentityOrFail(event);\n if (identity.type !== IdentityType.USER) {\n throw new BadRequestError('Only a user can link an identity provider account.');\n }\n\n const body = await readRequestBody(event);\n const handle = isObject(body) ? body.handle : undefined;\n if (typeof handle !== 'string' || handle.length === 0) {\n throw new BadRequestError('The account link handle is missing.');\n }\n\n const link = await this.linkStore.consume(handle);\n if (!link) {\n throw new BadRequestError('The account link request is unknown or expired.');\n }\n\n // The two checks that make the handle inert on its own: it may only\n // be redeemed by the user it was minted for, and only against the\n // provider it was minted for.\n if (link.userId !== identity.id || link.providerId !== provider.id) {\n throw new BadRequestError('The account link request does not belong to the authenticated user.');\n }\n\n const account = await this.accountManager.link(\n {\n id: link.providerUserId,\n // deliberately rebuilt from the stashed scalars: the resolved\n // identity carries the provider secret and the raw external\n // token payload, neither of which belongs in a cache\n attributeCandidates: {\n name: [link.providerUserName],\n email: [link.providerUserEmail],\n },\n data: {},\n provider,\n },\n identity.id,\n );\n\n // the confirm runs under a bearer, so unlike the callback it CAN be\n // attributed to a session and a route (the unlink emit's shape)\n const requestContext = useRequestEventContext();\n\n await this.eventService?.record({\n scope: EventScope.IDENTITY,\n name: EventName.IDENTITY_PROVIDER_LINKED,\n refType: EventRefType.IDENTITY_PROVIDER_ACCOUNT,\n refId: account.id,\n realmId: account.userRealmId ?? provider.realmId ?? null,\n actorType: IdentityType.USER,\n actorId: account.userId,\n actorName: identity.data.name ?? null,\n sessionId: requestContext?.sessionId ?? null,\n requestPath: requestContext?.requestPath ?? null,\n requestMethod: requestContext?.requestMethod ?? null,\n requestIpAddress: requestContext?.requestIpAddress ?? getRequestIP(event) ?? null,\n requestUserAgent: requestContext?.requestUserAgent ?? getRequestHeader(event, 'user-agent') ?? null,\n data: { providerId: provider.id, providerName: provider.name },\n });\n\n return { data: account, meta: {} };\n }\n\n private pickIdentityCandidate(\n identity: IdentityProviderIdentity,\n key: 'name' | 'email',\n ) : string | null {\n const candidates = identity.attributeCandidates?.[key] || [];\n for (const candidate of candidates) {\n if (typeof candidate === 'string' && candidate.length > 0) {\n return candidate;\n }\n }\n\n return null;\n }\n\n // ---------------------------------------------------------\n\n private buildHostedAuthorizeURL(codeRequest: OAuth2AuthorizationCodeRequest): URL {\n const url = new URL(resolveURL(this.options.baseURL, 'authorize'));\n\n const codeRequestKeys = Object.keys(codeRequest);\n for (const codeRequestKey_ of codeRequestKeys) {\n const codeRequestKey = codeRequestKey_ as keyof OAuth2AuthorizationCodeRequest;\n const codeRequestValue = codeRequest[codeRequestKey];\n // Preserve meaningful falsy values (e.g. max_age=0, which OIDC\n // treats as prompt=login) — only skip absent ones.\n if (typeof codeRequestValue !== 'undefined' && codeRequestValue !== null) {\n url.searchParams.set(codeRequestKey, String(codeRequestValue));\n }\n }\n\n return url;\n }\n\n // ---------------------------------------------------------\n\n private async resolve(id: string) {\n const entity = await this.repository.findOneById(id);\n\n if (!entity) {\n throw new EntityNotFoundError();\n }\n\n return entity;\n }\n\n // ---------------------------------------------------------\n\n private buildProviderAuthenticator(\n provider: OAuth2IdentityProvider | OpenIDIdentityProvider,\n options: { clientId?: string } = {},\n ) {\n return createIdentityProviderOAuth2Authenticator({\n accountManager: this.accountManager,\n provider,\n logger: this.logger,\n options: {\n baseURL: this.options.baseURL,\n clientId: options.clientId,\n },\n });\n }\n\n private async saveAuthorizationState(\n event: IAppEvent,\n data: Pick<OAuth2AuthorizationState, 'codeRequest' | 'link'> = {},\n ) : Promise<string> {\n return this.stateManager.save({\n ...data,\n ip: getRequestIP(event) ?? '',\n userAgent: getRequestHeader(event, 'user-agent') ?? undefined,\n });\n }\n\n private async verifyAuthorizationState(event: IAppEvent): Promise<OAuth2AuthorizationState> {\n const query = useRequestQuery(event);\n if (typeof query.state !== 'string') {\n throw OAuth2RequestError.stateInvalid();\n }\n\n return this.stateManager.verify(query.state, {\n ip: getRequestIP(event) ?? '',\n userAgent: getRequestHeader(event, 'user-agent') ?? undefined,\n });\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyGA,IAAauD,6BAAb,MAAaA;CACCC;CAEAC;CAEAC;CAEAC;CAEAC;CAEAC;CAEAC;CAEAC;CAEAC;CAEAC;CAEAC;CAEAC;CAIV,YAAYC,KAAwC;EAChD,KAAKZ,UAAUY,IAAIZ;EACnB,KAAKC,aAAaW,IAAIX;EACtB,KAAKC,kBAAkBU,IAAIV;EAC3B,KAAKC,iBAAiBS,IAAIT;EAC1B,KAAKC,YAAYQ,IAAIR;EACrB,KAAKI,aAAaI,IAAIJ;EACtB,KAAKH,sBAAsBO,IAAIP;EAC/B,KAAKC,uBAAuB,IAAIxB,wCAAAA;EAChC,KAAKyB,eAAeK,IAAIL;EACxB,KAAKE,wBAAwBG,IAAIH;EACjC,KAAKC,eAAeE,IAAIF;EACxB,KAAKC,SAASC,IAAID;CACtB;CAIA,MACME,aACF,OACmD;EACnD,MAAM,EACFE,MACAC,SACA,MAAM,KAAKf,WAAWgB,SACtB,MAAMhC,YAAYR,gBAAgBqC,KAAAA,GAAQ;GACtCI,QAAQ/B;GACRgC,OAAO7B,kBAAkBwB,KAAAA;EAC7B,CAAA,CAAA;EAGJ,IAAI;GACA,MAAMM,sBAAsBvB,8BAA8BiB,KAAAA;GAC1D,MAAMM,oBAAoBC,YAAY,EAAEC,MAAMtD,eAAeuD,uBAAuB,CAAA;GAEpF,KAAK,MAAMC,SAAST,MAChB,IAAI;IACA,MAAMK,oBAAoBK,SAAS;KAC/BH,MAAMtD,eAAeuD;KACrBR,MAAMtE,iBAAiB;OAAGD,kBAAkBkF,aAAaF;OAAQhF,kBAAkBmF,cAAcH,MAAMI,WAAW;KAAK,CAAA;IAC3H,CAAA;GACJ,QAAQ,CAER;EAER,QAAQ,CAER;EAEA,OAAO;GACHb;GACAC,MAAM;IACF,GAAGA;IACHE,QAAQhC,oBAAoBC,sBAAAA;GAChC;EACJ;CACJ;CAEA,MACM0C,YACF,IACA,OAC+C;EAC/C,MAAME,UAAUnC,kBAAkBkB,OAAO,EAAEjE,QAAQ,MAAM,CAAA;EAEzD,MAAMmF,SAAS,MAAM,KAAK/B,WAAWgC,kBACjCF,SACAtC,kBAAkBqB,KAAAA,CAAAA;EAGtB,IAAI,CAACkB,QACD,MAAM,IAAI1D,oBAAAA;EAGd,IAAI;GAEA,MAD4BuB,8BAA8BiB,KACpDM,CAAAA,CAAoBK,SAAS;IAC/BH,MAAMtD,eAAeuD;IACrBR,MAAMtE,iBAAiB;MAAGD,kBAAkBkF,aAAaM;MAASxF,kBAAkBmF,cAAcK,OAAOJ,WAAW;IAAK,CAAA;GAC7H,CAAA;EACJ,QAAQ,CAER;EAEA,OAAO;GAAEb,MAAMiB;GAAQhB,MAAM,EAAEE,QAAQhC,oBAAoBC,wBAAwBJ,uBAAAA,EAAyB;EAAE;CAClH;CAEA,MACMmD,aACF,IACA,MACA,OACgD;EAChD,OAAO,KAAKE,MAAMtB,OAAO,EAAEuB,YAAY,KAAK,CAAA;CAChD;CAEA,MACMC,IACF,IACA,MACA,OACgD;EAChD,OAAO,KAAKF,MAAMtB,KAAAA;CACtB;CAEA,MACMyB,aACF,IACA,OACgD;EAChD,MAAMR,UAAUnC,kBAAkBkB,KAAAA;EAElC,MAAMM,sBAAsBvB,8BAA8BiB,KAAAA;EAC1D,MAAMM,oBAAoBC,YAAY,EAAEC,MAAMtD,eAAewE,yBAAyB,CAAA;EAEtF,MAAMR,SAAS,MAAM,KAAK/B,WAAWwC,UAAU,EAAEX,IAAIC,QAAQ,CAAA;EAE7D,IAAI,CAACC,QACD,MAAM,IAAI1D,oBAAAA;EAGd,MAAM8C,oBAAoBK,SAAS;GAC/BH,MAAMtD,eAAewE;GACrBzB,MAAMtE,iBAAiB;KAAGD,kBAAkBkF,aAAaM;KAASxF,kBAAkBmF,cAAcK,OAAOJ,WAAW;GAAK,CAAA;EAC7H,CAAA;EAEA,MAAM,EAAEE,IAAIY,aAAaV;EAEzB,MAAM,KAAK/B,WAAW0C,OAAOX,MAAAA;EAE7BA,OAAOF,KAAKY;EAEZ5B,MAAM8B,SAASC,SAAS;EAExB,OAAO;GAAE9B,MAAMiB;GAAQhB,MAAM,CAAC;EAAE;CACpC;CAEA,MACM8B,YACF,MACA,OACgD;EAChD,OAAO,KAAKV,MAAMtB,KAAAA;CACtB;CAIA,MACMiC,aACF,KACA,OACF;EACE,MAAMjB,KAAKlC,kBAAkBkB,KAAAA;EAC7B,MAAMkB,SAAS,MAAM,KAAKiB,QAAQnB,EAAAA;EAElC,IAAI,CAAC5D,yBAAyB8D,MAAAA,KAAW,CAAC7D,yBAAyB6D,MAAAA,GAC/D,MAAM,IAAI5D,gBAAgB,oFAAA;EAG9B,MAAM8E,gBAAgB,KAAKC,2BAA2BnB,MAAAA;EAEtD,IAAIoB;EACJ,MAAMC,QAAQ5E,gBAAgBqC,KAAAA;EAC9B,IAAI,OAAOuC,MAAMD,gBAAgB,UAAU;GACvC,IAAIE;GAEJ,IAAI;IACAA,qBAAqBC,KAAKC,MAAM7G,gBAAgB0G,MAAMD,WAAW,CAAA;GACrE,QAAQ;IACJ,MAAMxE,mBAAmB6E,UAAU,sDAAA;GACvC;GAEA,MAAMC,uBAAuB,MAAM,KAAKpD,qBAAqBqD,IAAIL,kBAAAA;GACjE,MAAMvC,OAAO,MAAM,KAAKV,oBAAoBuD,OAAOF,oBAAAA;GAEnD,IACI3C,KAAK8C,OAAOjC,WACZI,OAAOJ,WACPI,OAAOJ,YAAYb,KAAK8C,OAAOjC,SAE/B,MAAMhD,mBAAmB6E,UAAU,6CAAA;GAGvCL,cAAcrC,KAAKA;EACvB;EAEA,MAAM+C,QAAQ,MAAM,KAAKC,uBAAuBjD,OAAO,EAAEsC,YAAY,CAAA;EAErE,OAAO3F,aAAaqD,OAAOoC,cAAcc,iBAAiB,EAAEF,MAAM,CAAA,CAAA;CACtE;CAEA,MACMG,YACF,KACA,OAC6C;EAC7C,MAAMnC,KAAKlC,kBAAkBkB,KAAAA;EAC7B,MAAMkB,SAAS,MAAM,KAAKiB,QAAQnB,EAAAA;EAElC,IAAI,CAAC5D,yBAAyB8D,MAAAA,KAAW,CAAC7D,yBAAyB6D,MAAAA,GAC/D,MAAM,IAAI5D,gBAAgB,iFAAA;EAG9B,IAAI,CAAC4D,OAAOkC,SACR,MAAM,IAAI9F,gBAAgB,uCAAA;EAG9B,MAAM+F,WAAWxE,yBAAyBmB,KAAAA;EAC1C,IAAIqD,SAASC,SAASrG,aAAasG,MAC/B,MAAM,IAAIjG,gBAAgB,oDAAA;EAG9B,IAAI4D,OAAOJ,WAAWI,OAAOJ,YAAYuC,SAASvC,SAC9C,MAAM,IAAIxD,gBAAgB,0DAAA;EAG9B,MAAM8E,gBAAgB,KAAKC,2BAA2BnB,MAAAA;EAEtD,MAAM8B,QAAQ,MAAM,KAAKC,uBAAuBjD,OAAO,EAAEwD,MAAM;GAAEC,QAAQJ,SAASrC;GAAI0C,YAAYxC,OAAOF;EAAG,EAAE,CAAA;EAE9G,OAAO,EAAE2C,KAAKvB,cAAcc,iBAAiB,EAAEF,MAAM,CAAA,EAAG;CAC5D;CAEA,MACMY,YACF,KACA,OACF;EACE,MAAM5C,KAAKlC,kBAAkBkB,KAAAA;EAE7B,MAAMkB,SAAS,MAAM,KAAKiB,QAAQnB,EAAAA;EAElC,IAAI,CAAC5D,yBAAyB8D,MAAAA,KAAW,CAAC7D,yBAAyB6D,MAAAA,GAC/D,MAAM,IAAI2C,MAAM,yBAAyB3C,OAAO4C,SAAS,eAAe;EAG5E,MAAM7D,OAAO,MAAM,KAAK8D,yBAAyB/D,KAAAA;EAEjD,MAAM,EAAEwD,SAASvD;EACjB,IAAIuD,MACA,OAAO,KAAKQ,aAAahE,OAAOkB,QAAQjB,MAAMuD,IAAAA;EAGlD,IACItC,OAAOJ,WACPb,KAAKqC,eACLrC,KAAKqC,YAAY2B,YACjBhE,KAAKqC,YAAY2B,aAAa/C,OAAOJ,SAErC,MAAMhD,mBAAmB6E,UAAU,6CAAA;EAGvC,MAAM,EAAEuB,SAASvG,gBAAgBqC,KAAAA;EAEjC,IAAI,OAAOkE,SAAS,YAAYA,KAAKC,WAAW,GAC5C,MAAM,IAAI7G,gBAAgB,oCAAA;EAgB9B,MAAM8G,WAAWnE,KAAKqC,cAClB,MAAM,KAAK/C,oBAAoBuD,OAAO7C,KAAKqC,WAAW,IACtD+B,KAAAA;EAKJ,IAAI,CAACnD,OAAOkC,SACR,MAAM,IAAI9F,gBAAgB,uCAAA;EAK9B,MAAM+D,OAAO,MAFS,KAAKgB,2BAA2BnB,QAAQ,EAAEoD,UAAUF,UAAUnE,KAAKsE,UAAU,CAEhFnC,CAAAA,CAAcoC,aAAa,EAAEN,KAAK,CAAA;EAKrD,IAAI,CAAC7C,KAAKoD,QACN,MAAM,IAAIlH,oBAAoB,EAAE2D,QAAQ,OAAO,CAAA;EAGnD,MAAMwD,QAAQ,MAAM,KAAKtF,gBAAgB+C,QAAQjB,OAAOJ,SAAS,IAAA;EAQjE,IAAIsD,UAAUrB,OAAO4B,gBAAgB;GACjC,IAAIC,UAAU;GAEd,MAAMC,UAAUvG,qBAAqB;IACjCgF,MAAMrG,aAAasG;IACnBtD,MAAM;KACF,GAAGoB;KACHqD;IACJ;GACJ,CAAA;GACA,IAAI,KAAK/E,yBAAyBkF,SAC9BD,UAAU,MAAM,KAAKjF,sBAAsBgB,SACvCyD,SAASrB,OAAO4B,gBAChBE,OAAAA;GAIR,IAAI,CAACD,SAAS;IACV,MAAMjB,MAAM,KAAKmB,wBAAwBV,SAASnE,IAAI;IACtD0D,IAAIoB,aAAaC,IAAI,SAASnH,gBAAgBoH,aAAa;IAE3D,OAAOtI,aAAaqD,OAAO2D,IAAIuB,IAAI;GACvC;EACJ;EAQA,MAAMC,oBAAoB,MAAM,KAAKzF,WAAW0F,MAC5ChB,UAAUnE,QAAQ,EAAEoF,eAAe,OAAO,GAC1C;GACI/B,MAAMrG,aAAasG;GACnBtD,MAAM;IACF,GAAGoB;IACHqD;GACJ;EACJ,GACA,EAAEY,YAAYnI,kBAAkBoI,SAAS,CAAA;EAU7C,MAAM,KAAK3F,cAAc4F,OAAO;GAC5BC,OAAO3I,WAAW4I;GAClBlF,MAAM5D,UAAU+I;GAChBC,SAAS/I,aAAagJ;GACtBC,OAAO1B,UAAUnE,KAAKsE,aAAa;GACnCD,UAAUF,UAAUnE,KAAKsE,aAAa;GACtCwB,WAAW;GACXC,WAAW/I,aAAasG;GACxB0C,SAAS5E,KAAKL;GACdkF,WAAW7E,KAAKb;GAChBM,SAASsD,UAAUnE,KAAKgE,YAAYS,MAAM1D;GAC1CmF,kBAAkBzJ,aAAasD,KAAAA,KAAU;GACzCoG,kBAAkB3J,iBAAiBuD,OAAO,YAAA,KAAiB;GAC3DC,MAAM;IACFoG,QAAQ;IACR3C,YAAYxC,OAAOF;IACnBsF,cAAcpF,OAAOV;IACrB,GAAI4D,UAAUnE,KAAKwF,QAAQ,EAAEA,OAAOrB,SAASnE,KAAKwF,MAAM,IAAI,CAAC;GACjE;EACJ,CAAA;EAMA,IAAIrB,UAAUmC,uBAAuBnC,SAASnE,KAAKuG,cAAc;GAC7D,MAAM7C,MAAM,IAAI5F,IAAIqG,SAASnE,KAAKuG,YAAY;GAC9C7C,IAAIoB,aAAaC,IAAI,QAAQG,kBAAkBnE,EAAE;GACjD,IAAIoD,SAASnE,KAAK+C,OACdW,IAAIoB,aAAaC,IAAI,SAASZ,SAASnE,KAAK+C,KAAK;GAGrD,OAAOrG,aAAaqD,OAAO2D,IAAIuB,IAAI;EACvC;EAMA,MAAMvB,MAAM,IAAI5F,IAAI,KAAKmB,QAAQuH,OAAO;EACxC9C,IAAIoB,aAAaC,IAAI,QAAQG,kBAAkBnE,EAAE;EAEjD,OAAOrE,aAAaqD,OAAO2D,IAAIuB,IAAI;CACvC;CAIA,MAAc5D,MAAMtB,OAAkBd,UAElC,CAAC,GAAoD;EACrD,IAAIwH;EACJ,MAAM1F,KAAKtC,kBAAkBsB,OAAO,EAAEjE,QAAQ,MAAM,CAAA;EACpD,MAAM4K,OAAO,MAAM/I,gBAAgBoC,KAAAA;EACnCzB,wBAAwByB,OAAO2G,IAAAA;EAC/B,MAAM7F,UAAUnC,kBAAkBqB,KAAAA,KAAUvB,eAAekI,IAAAA;EAE3D,IAAIzF;EACJ,IAAIF,IAAI;GACJ,MAAM4F,QAA6B,CAAC;GACpC,IAAI7K,OAAOiF,EAAAA,GACP4F,MAAM5F,KAAKA;QAEX4F,MAAMpG,OAAOQ;GAGjB,IAAIF,SACA8F,MAAM9F,UAAUA;GAGpBI,SAAS,MAAM,KAAK/B,WAAWwC,UAAUiF,KAAAA;GAGzC,IAAI,CAAC1F,WAAWhC,QAAQqC,cAAcqF,MAAM5F,KACxC,MAAM,IAAIxD,oBAAAA;EAElB,OAAO,IAAI0B,QAAQqC,YACf,MAAM,IAAI/D,oBAAAA;EAGd,MAAM8C,sBAAsBvB,8BAA8BiB,KAAAA;EAC1D,IAAIkB,QAAQ;GACR,MAAMZ,oBAAoBC,YAAY,EAAEC,MAAMtD,eAAe2J,yBAAyB,CAAA;GAEtFH,QAAQ9K,eAAekL;EAC3B,OAAO;GACH,MAAMxG,oBAAoBC,YAAY,EAAEC,MAAMtD,eAAe6J,yBAAyB,CAAA;GAEtFL,QAAQ9K,eAAeoL;EAC3B;EAGA,MAAM/G,OAAO,MAAMgH,IADGjK,0BACHiK,CAAAA,CAAUpE,IAAI8D,MAAM,EAAED,MAAM,CAAA;EAG/C,MAAMS,aAAa,MAAMD,IADOnK,oCACPmK,CAAAA,CAAoBrE,IAAI8D,IAAAA;EAEjD,MAAM,KAAKxH,WAAWiI,oBAAoBnH,IAAAA;EAE1C,IAAIiB,QACA,MAAMZ,oBAAoBK,SAAS;GAC/BH,MAAMtD,eAAe2J;GACrB5G,MAAMtE,iBAAiB;KAClBD,kBAAkBkF,aAAa;KAC5B,GAAGM;KACH,GAAGjB;IACP;KACCvE,kBAAkBmF,cAAcZ,KAAKa,WAAWI,OAAOJ,WAAW;GACvE,CAAA;EACJ,CAAA;OACG;GACH,IAAI,CAACb,KAAKa,SAENb,KAAKa,UADYjC,yBAAyBmB,KAC3BqD,CAAAA,CAASvC;GAG5B,MAAMR,oBAAoBK,SAAS;IAC/BH,MAAMtD,eAAe6J;IACrB9G,MAAMtE,iBAAiB;MAAGD,kBAAkBkF,aAAaX;MAAOvE,kBAAkBmF,cAAcZ,KAAKa,WAAW;IAAK,CAAA;GACzH,CAAA;EACJ;EAEA,MAAM,KAAK3B,WAAWkI,gBAAgBpH,MAAMiB,UAAUmD,KAAAA,CAAAA;EAEtD,IAAInD,QAAQ;GACRA,SAAS,KAAK/B,WAAWmI,MAAMpG,QAAQjB,IAAAA;GACvC,MAAM,KAAKd,WAAWoI,WAAWrG,QAAQiG,UAAAA;GAEzCnH,MAAM8B,SAASC,SAAS;GAExB,OAAO;IAAE9B,MAAMiB;IAAQhB,MAAM,CAAC;GAAE;EACpC;EAEAgB,SAAS,KAAK/B,WAAWqI,OAAOvH,IAAAA;EAChC,MAAM,KAAKd,WAAWoI,WAAWrG,QAAQiG,UAAAA;EAEzCnH,MAAM8B,SAASC,SAAS;EAExB,OAAO;GAAE9B,MAAMiB;GAAQhB,MAAM,CAAC;EAAE;CACpC;;;;;;IAUA,MAAc8D,aACVhE,OACAyH,UACAzE,OACAQ,MACF;EAGE,MAAMG,MAAM,IAAI5F,IAAIL,WAAW,KAAKwB,QAAQuH,SAAS,4BAAA,CAAA;EAErD,MAAM,EAAEvC,SAASvG,gBAAgBqC,KAAAA;EAEjC,IAAI;GAKA,IAAIwD,KAAKE,eAAe+D,SAASzG,MAAM,CAACyG,SAASrE,SAC7C,MAAM,IAAI9F,gBAAgB,6DAAA;GAG9B,IAAI,OAAO4G,SAAS,YAAYA,KAAKC,WAAW,GAC5C,MAAM,IAAI7G,gBAAgB,oCAAA;GAK9B,MAAM+F,WAAW,MAFK,KAAKhB,2BAA2BoF,QAE/BrF,CAAAA,CAAcsF,gBAAgB,EAAExD,KAAK,CAAA;GAK5D,IAAI,CAACb,SAASrC,IACV,MAAM,IAAI1D,gBAAgB,4CAAA;GAkB9B,MAAMqK,SAAS,MAAM,KAAKrI,UAAUsI,KAAK;IACrClE,YAAY+D,SAASzG;IACrByC,QAAQD,KAAKC;IACboE,gBAAgBxE,SAASrC;IACzB8G,kBAAkB,KAAKC,sBAAsB1E,UAAU,MAAA;IACvD2E,mBAAmB,KAAKD,sBAAsB1E,UAAU,OAAA;GAC5D,CAAA;GAEAM,IAAIoB,aAAaC,IAAI,cAAc2C,MAAAA;GACnChE,IAAIoB,aAAaC,IAAI,YAAYyC,SAASzG,EAAE;EAChD,SAASiH,GAAG;GAIR,KAAKpI,QAAQqI,MAAMzK,cAAcwK,GAAG,4CAAA,CAAA;GAEpCtE,IAAIoB,aAAaC,IAAI,aAAa,aAAA;EACtC;EAEA,OAAOrI,aAAaqD,OAAO2D,IAAIuB,IAAI;CACvC;;;;IAMA,MACMiD,YACF,KACA,OACA,OACuD;EACvD,MAAMnH,KAAKlC,kBAAkBkB,KAAAA;EAC7B,MAAMyH,WAAW,MAAM,KAAKtF,QAAQnB,EAAAA;EAEpC,IAAI,CAAC5D,yBAAyBqK,QAAAA,KAAa,CAACpK,yBAAyBoK,QAAAA,GACjE,MAAM,IAAInK,gBAAgB,iFAAA;EAG9B,IAAI,CAACmK,SAASrE,SACV,MAAM,IAAI9F,gBAAgB,uCAAA;EAG9B,MAAM+F,WAAWxE,yBAAyBmB,KAAAA;EAC1C,IAAIqD,SAASC,SAASrG,aAAasG,MAC/B,MAAM,IAAIjG,gBAAgB,oDAAA;EAG9B,MAAMqJ,OAAO,MAAM/I,gBAAgBoC,KAAAA;EACnC,MAAM2H,SAAS7L,SAAS6K,IAAAA,IAAQA,KAAKgB,SAAStD,KAAAA;EAC9C,IAAI,OAAOsD,WAAW,YAAYA,OAAOxD,WAAW,GAChD,MAAM,IAAI7G,gBAAgB,qCAAA;EAG9B,MAAMkG,OAAO,MAAM,KAAKlE,UAAU+I,QAAQV,MAAAA;EAC1C,IAAI,CAACnE,MACD,MAAM,IAAIlG,gBAAgB,iDAAA;EAM9B,IAAIkG,KAAKC,WAAWJ,SAASrC,MAAMwC,KAAKE,eAAe+D,SAASzG,IAC5D,MAAM,IAAI1D,gBAAgB,qEAAA;EAG9B,MAAMgL,UAAU,MAAM,KAAKjJ,eAAemE,KACtC;GACIxC,IAAIwC,KAAKqE;GAITU,qBAAqB;IACjB/H,MAAM,CAACgD,KAAKsE,gBAAiB;IAC7BU,OAAO,CAAChF,KAAKwE,iBAAkB;GACnC;GACA/H,MAAM,CAAC;GACPwH;EACJ,GACApE,SAASrC,EAAE;EAKf,MAAMyH,iBAAiB7J,uBAAAA;EAEvB,MAAM,KAAKgB,cAAc4F,OAAO;GAC5BC,OAAO3I,WAAW4L;GAClBlI,MAAM5D,UAAU+L;GAChB/C,SAAS/I,aAAa+L;GACtB9C,OAAOwC,QAAQtH;GACfF,SAASwH,QAAQO,eAAepB,SAAS3G,WAAW;GACpDkF,WAAW/I,aAAasG;GACxB0C,SAASqC,QAAQ7E;GACjByC,WAAW7C,SAASpD,KAAKO,QAAQ;GACjCuF,WAAW0C,gBAAgB1C,aAAa;GACxC+C,aAAaL,gBAAgBK,eAAe;GAC5CC,eAAeN,gBAAgBM,iBAAiB;GAChD5C,kBAAkBsC,gBAAgBtC,oBAAoBzJ,aAAasD,KAAAA,KAAU;GAC7EoG,kBAAkBqC,gBAAgBrC,oBAAoB3J,iBAAiBuD,OAAO,YAAA,KAAiB;GAC/FC,MAAM;IAAEyD,YAAY+D,SAASzG;IAAIsF,cAAcmB,SAASjH;GAAK;EACjE,CAAA;EAEA,OAAO;GAAEP,MAAMqI;GAASpI,MAAM,CAAC;EAAE;CACrC;CAEQ6H,sBACJ1E,UACA2F,KACc;EACd,MAAMC,aAAa5F,SAASkF,sBAAsBS,QAAQ,CAAA;EAC1D,KAAK,MAAME,aAAaD,YACpB,IAAI,OAAOC,cAAc,YAAYA,UAAU/E,SAAS,GACpD,OAAO+E;EAIf,OAAO;CACX;CAIQpE,wBAAwBxC,aAAkD;EAC9E,MAAMqB,MAAM,IAAI5F,IAAIL,WAAW,KAAKwB,QAAQuH,SAAS,WAAA,CAAA;EAErD,MAAM0C,kBAAkBC,OAAOC,KAAK/G,WAAAA;EACpC,KAAK,MAAMgH,mBAAmBH,iBAAiB;GAC3C,MAAMI,iBAAiBD;GACvB,MAAME,mBAAmBlH,YAAYiH;GAGrC,IAAI,OAAOC,qBAAqB,eAAeA,qBAAqB,MAChE7F,IAAIoB,aAAaC,IAAIuE,gBAAgBE,OAAOD,gBAAAA,CAAAA;EAEpD;EAEA,OAAO7F;CACX;CAIA,MAAcxB,QAAQnB,IAAY;EAC9B,MAAME,SAAS,MAAM,KAAK/B,WAAWuK,YAAY1I,EAAAA;EAEjD,IAAI,CAACE,QACD,MAAM,IAAI1D,oBAAAA;EAGd,OAAO0D;CACX;CAIQmB,2BACJoF,UACAvI,UAAiC,CAAC,GACpC;EACE,OAAOhB,0CAA0C;GAC7CmB,gBAAgB,KAAKA;GACrBoI;GACA5H,QAAQ,KAAKA;GACbX,SAAS;IACLuH,SAAS,KAAKvH,QAAQuH;IACtBnC,UAAUpF,QAAQoF;GACtB;EACJ,CAAA;CACJ;CAEA,MAAcrB,uBACVjD,OACAC,OAA+D,CAAC,GAChD;EAChB,OAAO,KAAKR,aAAamI,KAAK;GAC1B,GAAG3H;GACH0J,IAAIjN,aAAasD,KAAAA,KAAU;GAC3B4J,WAAWnN,iBAAiBuD,OAAO,YAAA,KAAiBqE,KAAAA;EACxD,CAAA;CACJ;CAEA,MAAcN,yBAAyB/D,OAAqD;EACxF,MAAMuC,QAAQ5E,gBAAgBqC,KAAAA;EAC9B,IAAI,OAAOuC,MAAMS,UAAU,UACvB,MAAMlF,mBAAmB+L,aAAY;EAGzC,OAAO,KAAKpK,aAAaqD,OAAOP,MAAMS,OAAO;GACzC2G,IAAIjN,aAAasD,KAAAA,KAAU;GAC3B4J,WAAWnN,iBAAiBuD,OAAO,YAAA,KAAiBqE,KAAAA;EACxD,CAAA;CACJ;AACJ;;;;;;;;;;;;;;;;;CA7oBoBrF,MAAAA,QAAAA,CAAAA,uBAAAA,CAAAA;;;;;;;;;;;;;CASDA,KAAAA,QAAAA,CAAAA,uBAAAA,CAAAA;;;;;;;;;;;;;CASGA,QAAAA,QAAAA,CAAAA,uBAAAA,CAAAA;;;;;;;;CAgCNA,MAAAA,IAAAA,CAAAA,uBAAAA,CAAAA;;;;;;;;;;;;;;;;CAsDiBA,MAAAA,qBAAAA,CAAAA,uBAAAA,CAAAA;;;;;;;;;;;;;;;;CAiYAA,MAAAA,qBAAAA,CAAAA,uBAAAA,CAAAA;;;;;;;;;;;;;;CA5lBnB,YAAA,CAAA,uBAAuB,qCAAA,CAAA"}
|
|
1
|
+
{"version":3,"file":"module.mjs","names":["BuiltInPolicyType","definePolicyData","ValidatorGroup","base64URLDecode","createNanoID","getURLBasePath","isObject","isUUID","DBody","DContext","DController","DDelete","DGet","DPath","DPost","DPut","DTags","getRequestHeader","getRequestIP","sendRedirect","EventName","EventRefType","EventScope","IdentityProviderAttributesValidator","IdentityProviderValidator","IdentityType","PermissionName","isOAuth2IdentityProvider","isOpenIDIdentityProvider","BadRequestError","EntityNotFoundError","describeError","resolveURL","useRequestQuery","setResponseCookie","unsetResponseCookie","useRequestCookie","readRequestBody","OAuth2RequestError","IDENTITY_PROVIDER_LOGIN_NOT_PENDING","URL","OAUTH2_FEDERATED_LOGIN_COOKIE","OAUTH2_FEDERATED_LOGIN_TTL","OAuth2AuthorizationCodeRequestValidator","RECORD_QUERY_PARAMETERS","createIdentityProviderOAuth2Authenticator","decodeQuery","describeQuerySchema","identityProviderSchema","applyRouteRealmIDToBody","buildActorContext","getBodyRealmID","getRequestParamID","getRequestRealmID","useRequestEventContext","useRequestIdentityOrFail","useRequestParamID","useRequestPermissionEvaluator","ForceLoggedInMiddleware","IdentityProviderController","options","repository","accountManager","linkStore","codeRequestVerifier","codeRequestValidator","stateManager","loginService","eventService","logger","ctx","getProviders","event","data","meta","findMany","schema","actor","getProvider","id","paramId","entity","findOneByIdOrName","evaluateOneOf","name","IDENTITY_PROVIDER_READ","IDENTITY_PROVIDER_UPDATE","IDENTITY_PROVIDER_DELETE","ATTRIBUTES","REALM_MATCH","realmId","editProvider","user","write","updateOnly","put","dropProvider","permissionEvaluator","preEvaluate","findOneBy","evaluate","entityId","remove","response","status","addProvider","authorizeOut","_id","resolve","enabled","query","codeRequest","malformed","codeRequestDecoded","JSON","parse","codeRequestValidated","run","verify","client","authenticator","buildProviderAuthenticator","browserNonce","setFederatedLoginCookie","state","saveAuthorizationState","buildRedirectURL","linkRequest","identity","type","USER","link","userId","providerId","url","authorizeIn","Error","protocol","verifyAuthorizationState","completeLink","buildHostedAuthorizeURL","href","code","error","length","info","stringify","slice","result","complete","provider","request","ipAddress","userAgent","kind","searchParams","set","pendingLoginId","completeLogin","assertSameOrigin","message","reason","path","buildFederatedLoginCookiePath","completeHandoff","group","body","where","UPDATE","IDENTITY_PROVIDER_CREATE","CREATE","validator","attributesValidator","attributes","validateJoinColumns","checkUniqueness","undefined","keepAll","merge","saveWithEA","create","baseURL","resolveIdentity","handle","save","providerUserId","providerUserName","pickIdentityCandidate","providerUserEmail","e","confirmLink","_data","consume","account","attributeCandidates","email","requestContext","record","scope","IDENTITY","IDENTITY_PROVIDER_LINKED","refType","IDENTITY_PROVIDER_ACCOUNT","refId","userRealmId","actorType","actorId","actorName","sessionId","requestPath","requestMethod","requestIpAddress","requestUserAgent","providerName","key","candidates","candidate","codeRequestKeys","Object","keys","codeRequestKey_","codeRequestKey","codeRequestValue","String","findOneById","clientId","value","httpOnly","sameSite","secure","maxAge","origin","ip","stateInvalid"],"sources":["../../../../../../src/adapters/http/controllers/entities/identity-provider/module.ts"],"sourcesContent":["/*\n * Copyright (c) 2022.\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 { BuiltInPolicyType, definePolicyData } from '@authup/access';\nimport {\n ValidatorGroup,\n base64URLDecode,\n createNanoID,\n getURLBasePath,\n isObject,\n isUUID,\n} from '@authup/kit';\nimport {\n DBody,\n DContext,\n DController,\n DDelete,\n DGet,\n DPath,\n DPost,\n DPut,\n DTags,\n} from '@routup/decorators';\nimport type { IAppEvent } from 'routup';\nimport {\n getRequestHeader,\n getRequestIP,\n sendRedirect,\n} from 'routup';\nimport type {\n IdentityProvider,\n IdentityProviderAccount,\n OAuth2AuthorizationCodeRequest,\n OAuth2IdentityProvider,\n OpenIDIdentityProvider,\n} from '@authup/core-kit';\nimport {\n EventName,\n EventRefType,\n EventScope,\n IdentityProviderAttributesValidator,\n IdentityProviderValidator,\n IdentityType,\n PermissionName,\n isOAuth2IdentityProvider,\n isOpenIDIdentityProvider,\n} from '@authup/core-kit';\nimport { BadRequestError, EntityNotFoundError } from '@authup/errors';\nimport type { Logger } from '@authup/server-kit';\nimport { describeError, resolveURL } from '../../../../../utils/index.ts';\nimport { useRequestQuery } from '@routup/basic/query';\nimport { setResponseCookie, unsetResponseCookie, useRequestCookie } from '@routup/basic/cookie';\nimport { readRequestBody } from '@routup/basic/body';\nimport type { OAuth2TokenGrantResponse } from '@authup/specs';\nimport { OAuth2RequestError } from '@authup/specs';\nimport type {\n EntityCollectionResponse,\n EntityRecordResponse,\n IdentityProviderCreatePayload,\n IdentityProviderLinkConfirmPayload,\n IdentityProviderLinkRequestResponse,\n IdentityProviderSavePayload,\n IdentityProviderUpdatePayload,\n} from '@authup/core-http-kit';\nimport { IDENTITY_PROVIDER_LOGIN_NOT_PENDING } from '@authup/core-http-kit';\nimport { URL } from 'node:url';\nimport type {\n IEventService,\n IIdentityProviderAccountLinkStore,\n IIdentityProviderAccountManager,\n IIdentityProviderRepository,\n IOAuth2AuthorizationCodeRequestVerifier,\n IOAuth2AuthorizationStateManager,\n IOAuth2FederatedLoginService,\n IdentityProviderIdentity,\n OAuth2AuthorizationState,\n OAuth2AuthorizationStateLink,\n} from '../../../../../core/index.ts';\nimport {\n OAUTH2_FEDERATED_LOGIN_COOKIE,\n OAUTH2_FEDERATED_LOGIN_TTL,\n OAuth2AuthorizationCodeRequestValidator,\n RECORD_QUERY_PARAMETERS,\n createIdentityProviderOAuth2Authenticator,\n decodeQuery,\n describeQuerySchema,\n identityProviderSchema,\n} from '../../../../../core/index.ts';\nimport {\n applyRouteRealmIDToBody,\n buildActorContext,\n getBodyRealmID,\n getRequestParamID,\n getRequestRealmID,\n useRequestEventContext,\n useRequestIdentityOrFail,\n useRequestParamID,\n useRequestPermissionEvaluator,\n} from '../../../request/index.ts';\nimport { ForceLoggedInMiddleware } from '../../../middleware/index.ts';\nimport type { IdentityProviderControllerContext, IdentityProviderControllerOptions } from './types.ts';\n\n@DTags('identity')\n@DController(['/identity-providers', '/realms/:realmId/identity-providers'])\nexport class IdentityProviderController {\n protected options: IdentityProviderControllerOptions;\n\n protected repository: IIdentityProviderRepository;\n\n protected accountManager: IIdentityProviderAccountManager;\n\n protected linkStore: IIdentityProviderAccountLinkStore;\n\n protected codeRequestVerifier : IOAuth2AuthorizationCodeRequestVerifier;\n\n protected codeRequestValidator : OAuth2AuthorizationCodeRequestValidator;\n\n protected stateManager : IOAuth2AuthorizationStateManager;\n\n protected loginService : IOAuth2FederatedLoginService;\n\n protected eventService? : IEventService;\n\n protected logger? : Logger;\n\n // ---------------------------------------------------------\n\n constructor(ctx: IdentityProviderControllerContext) {\n this.options = ctx.options;\n this.repository = ctx.repository;\n this.accountManager = ctx.accountManager;\n this.linkStore = ctx.linkStore;\n this.codeRequestVerifier = ctx.codeRequestVerifier;\n this.codeRequestValidator = new OAuth2AuthorizationCodeRequestValidator();\n this.stateManager = ctx.stateManager;\n this.loginService = ctx.loginService;\n this.eventService = ctx.eventService;\n this.logger = ctx.logger;\n }\n\n // ---------------------------------------------------------\n\n // Deliberately anonymous and ungated: the hosted login page lists a\n // realm's providers before anyone signs in. Safe only because findMany\n // does not extend with extra attributes, so the response is the schema's\n // fields.default projection. The record read below carries them and is\n // gated for exactly that reason.\n @DGet('', [])\n async getProviders(\n @DContext() event: IAppEvent,\n ): Promise<EntityCollectionResponse<IdentityProvider>> {\n const {\n data,\n meta,\n } = await this.repository.findMany(\n await decodeQuery(useRequestQuery(event), {\n schema: identityProviderSchema,\n actor: buildActorContext(event),\n }),\n );\n\n return {\n data,\n meta: {\n ...meta,\n schema: describeQuerySchema(identityProviderSchema),\n },\n };\n }\n\n @DGet('/:id', [ForceLoggedInMiddleware])\n async getProvider(\n @DPath('id') id: string,\n @DContext() event: IAppEvent,\n ): Promise<EntityRecordResponse<IdentityProvider>> {\n const paramId = useRequestParamID(event, { isUUID: false });\n\n const entity = await this.repository.findOneByIdOrName(\n paramId,\n getRequestRealmID(event),\n );\n\n if (!entity) {\n throw new EntityNotFoundError();\n }\n\n await useRequestPermissionEvaluator(event).evaluateOneOf({\n name: [\n PermissionName.IDENTITY_PROVIDER_READ,\n PermissionName.IDENTITY_PROVIDER_UPDATE,\n PermissionName.IDENTITY_PROVIDER_DELETE,\n ],\n data: definePolicyData({ [BuiltInPolicyType.ATTRIBUTES]: entity, [BuiltInPolicyType.REALM_MATCH]: entity.realmId ?? null }),\n });\n\n return { data: entity, meta: { schema: describeQuerySchema(identityProviderSchema, RECORD_QUERY_PARAMETERS) } };\n }\n\n @DPost('/:id', [ForceLoggedInMiddleware])\n async editProvider(\n @DPath('id') id: string,\n @DBody() user: IdentityProviderUpdatePayload,\n @DContext() event: IAppEvent,\n ) : Promise<EntityRecordResponse<IdentityProvider>> {\n return this.write(event, { updateOnly: true });\n }\n\n @DPut('/:id', [ForceLoggedInMiddleware])\n async put(\n @DPath('id') id: string,\n @DBody() user: IdentityProviderSavePayload,\n @DContext() event: IAppEvent,\n ) : Promise<EntityRecordResponse<IdentityProvider>> {\n return this.write(event);\n }\n\n @DDelete('/:id', [ForceLoggedInMiddleware])\n async dropProvider(\n @DPath('id') id: string,\n @DContext() event: IAppEvent,\n ) : Promise<EntityRecordResponse<IdentityProvider>> {\n const paramId = useRequestParamID(event);\n\n const permissionEvaluator = useRequestPermissionEvaluator(event);\n await permissionEvaluator.preEvaluate({ name: PermissionName.IDENTITY_PROVIDER_DELETE });\n\n const entity = await this.repository.findOneBy({ id: paramId });\n\n if (!entity) {\n throw new EntityNotFoundError();\n }\n\n await permissionEvaluator.evaluate({\n name: PermissionName.IDENTITY_PROVIDER_DELETE,\n data: definePolicyData({ [BuiltInPolicyType.ATTRIBUTES]: entity, [BuiltInPolicyType.REALM_MATCH]: entity.realmId ?? null }),\n });\n\n const { id: entityId } = entity;\n\n await this.repository.remove(entity);\n\n entity.id = entityId;\n\n event.response.status = 202;\n\n return { data: entity, meta: {} };\n }\n\n @DPost('', [ForceLoggedInMiddleware])\n async addProvider(\n @DBody() user: IdentityProviderCreatePayload,\n @DContext() event: IAppEvent,\n ) : Promise<EntityRecordResponse<IdentityProvider>> {\n return this.write(event);\n }\n\n // ---------------------------------------------------------\n\n @DGet('/:id/authorize-out', [])\n async authorizeOut(\n @DPath('id') _id: string,\n @DContext() event: IAppEvent,\n ) {\n const id = useRequestParamID(event);\n const entity = await this.resolve(id);\n\n if (!isOAuth2IdentityProvider(entity) && !isOpenIDIdentityProvider(entity)) {\n throw new BadRequestError('Only an identity-provider based on the oauth protocol supports authorize redirect.');\n }\n\n // Refused here as well as at the callback, so a disabled provider\n // costs no provider round trip.\n if (!entity.enabled) {\n throw new BadRequestError('The identity provider is not enabled.');\n }\n\n // A federated login completes an RP's authorization request: the\n // callback mints a code bound to that request and delivers it to the\n // request's redirect_uri. Without one there is nowhere to deliver a\n // code (the callback used to mint an unbound one and hand it to the\n // server root, issue #3457), so the login must not start.\n const query = useRequestQuery(event);\n if (typeof query.codeRequest !== 'string') {\n throw OAuth2RequestError.malformed('A federated login requires an authorization code request.');\n }\n\n let codeRequestDecoded: OAuth2AuthorizationCodeRequest;\n\n try {\n codeRequestDecoded = JSON.parse(base64URLDecode(query.codeRequest));\n } catch {\n throw OAuth2RequestError.malformed('The code request is malformed and can not be parsed.');\n }\n\n const codeRequestValidated = await this.codeRequestValidator.run(codeRequestDecoded);\n const data = await this.codeRequestVerifier.verify(codeRequestValidated);\n\n if (\n data.client.realmId &&\n entity.realmId &&\n entity.realmId !== data.client.realmId\n ) {\n throw OAuth2RequestError.malformed('The provider and client realm do not match.');\n }\n\n const authenticator = this.buildProviderAuthenticator(entity);\n\n // Ties this login to THIS browser. The callback requires the cookie\n // to match, so a crafted callback URL opened in someone else's\n // browser cannot make authup establish a session there (login CSRF).\n // The state's ip / user agent cannot carry that: both are chosen by\n // whoever mints the state (#3439). Keycloak and Authentik bind the\n // same hop to a cookie for the same reason.\n const browserNonce = createNanoID();\n this.setFederatedLoginCookie(event, browserNonce);\n\n const state = await this.saveAuthorizationState(event, {\n codeRequest: data.data,\n browserNonce,\n });\n\n return sendRedirect(event, authenticator.buildRedirectURL({ state }));\n }\n\n @DPost('/:id/link-request', [ForceLoggedInMiddleware])\n async linkRequest(\n @DPath('id') _id: string,\n @DContext() event: IAppEvent,\n ) : Promise<IdentityProviderLinkRequestResponse> {\n const id = useRequestParamID(event);\n const entity = await this.resolve(id);\n\n if (!isOAuth2IdentityProvider(entity) && !isOpenIDIdentityProvider(entity)) {\n throw new BadRequestError('Only an identity-provider based on the oauth protocol supports account linking.');\n }\n\n if (!entity.enabled) {\n throw new BadRequestError('The identity provider is not enabled.');\n }\n\n const identity = useRequestIdentityOrFail(event);\n if (identity.type !== IdentityType.USER) {\n throw new BadRequestError('Only a user can link an identity provider account.');\n }\n\n if (entity.realmId && entity.realmId !== identity.realmId) {\n throw new BadRequestError('The identity provider does not belong to the user realm.');\n }\n\n const authenticator = this.buildProviderAuthenticator(entity);\n\n const state = await this.saveAuthorizationState(event, { link: { userId: identity.id, providerId: entity.id } });\n\n return { url: authenticator.buildRedirectURL({ state }) };\n }\n\n @DGet('/:id/authorize-in', [])\n async authorizeIn(\n @DPath('id') _id: string,\n @DContext() event: IAppEvent,\n ) {\n const id = useRequestParamID(event);\n\n const entity = await this.resolve(id);\n\n if (!isOAuth2IdentityProvider(entity) && !isOpenIDIdentityProvider(entity)) {\n throw new Error(`The provider protocol ${entity.protocol} is not valid.`);\n }\n\n const data = await this.verifyAuthorizationState(event);\n\n const { link } = data;\n if (link) {\n return this.completeLink(event, entity, data, link);\n }\n\n // authorize-out no longer mints a login state without a code request;\n // this refuses the ones minted before that (issue #3457).\n if (!data.codeRequest) {\n throw OAuth2RequestError.malformed('The state carries no authorization code request.');\n }\n\n // The browser that started this login is the only one that may\n // finish it. Refused before the provider's single-use code is spent\n // and before any session exists. A state minted before this shipped\n // carries no nonce and is refused the same way; the hosted page\n // re-renders the request and the next attempt carries one.\n const browserNonce = useRequestCookie(event, OAUTH2_FEDERATED_LOGIN_COOKIE);\n if (\n !data.browserNonce ||\n typeof browserNonce !== 'string' ||\n browserNonce !== data.browserNonce\n ) {\n return sendRedirect(event, this.buildHostedAuthorizeURL(data.codeRequest).href);\n }\n\n const { code, error } = useRequestQuery(event);\n\n // RFC 6749 section 4.1.2.1: a provider answers a refused or failed\n // authorization (the person cancelled at the provider, the provider\n // is down) with `error` and no code. A top-level browser navigation\n // like every other refusal here, so it lands on the hosted login\n // again. Nothing of the provider's answer is echoed: whoever controls\n // the provider's redirect shapes those values.\n if (typeof error === 'string' && error.length > 0) {\n // JSON-quoted: a raw query value must not be able to forge a log line\n this.logger?.info(`The identity provider ${entity.id} answered the authorization with ${JSON.stringify(error.slice(0, 64))}.`);\n\n return sendRedirect(event, this.buildHostedAuthorizeURL(data.codeRequest).href);\n }\n\n if (typeof code !== 'string' || code.length === 0) {\n throw new BadRequestError('The authorization code is missing.');\n }\n\n const result = await this.loginService.complete({\n provider: entity,\n codeRequest: data.codeRequest,\n code,\n request: {\n ipAddress: getRequestIP(event),\n userAgent: getRequestHeader(event, 'user-agent'),\n },\n });\n\n // A refusal is a decision the person at the browser has to be told\n // about, and this is a top-level navigation, so it lands on the\n // hosted login rather than in a JSON body (issue #3458). A marker is\n // only attached when the refusal carries one; without it the page\n // re-runs the same verifier over the same request and states the\n // reason itself, so nothing is echoed.\n const url = this.buildHostedAuthorizeURL(result.codeRequest);\n\n if (result.kind === 'refused') {\n if (result.error) {\n url.searchParams.set('error', result.error);\n }\n\n return sendRedirect(event, url.href);\n }\n\n // The application's code is NOT minted here. The browser goes back to\n // the hosted authorize page, which completes the login and runs the\n // ladder an interactive login runs (prompt freshness, consent) before\n // any code exists (plan 094). So a custom-scheme redirect_uri needs no\n // interstitial on this leg either: it is navigated at the end of the\n // ladder, exactly as it is for an interactive login.\n //\n // The pending login rides a cookie rather than the URL, which is what\n // Keycloak and Authentik both do: only the browser that started the\n // login carries it, no other origin can set it, and nothing redeemable\n // reaches history, a log or a referrer. `SameSite=Lax` keeps it off\n // cross-site requests, so a page on another origin cannot drive the\n // completion. `provider` stays in the URL as a routing hint, and is no\n // secret.\n this.setFederatedLoginCookie(event, result.pendingLoginId);\n\n url.searchParams.set('provider', entity.id);\n\n return sendRedirect(event, url.href);\n }\n\n @DPost('/:id/login-complete', [])\n async completeLogin(\n @DPath('id') _id: string,\n @DContext() event: IAppEvent,\n ) : Promise<OAuth2TokenGrantResponse> {\n const id = useRequestParamID(event);\n\n this.assertSameOrigin(event);\n\n const pendingLoginId = useRequestCookie(event, OAUTH2_FEDERATED_LOGIN_COOKIE);\n if (typeof pendingLoginId !== 'string' || pendingLoginId.length === 0) {\n // Marked, because this refusal alone means the completion never\n // began: the caller was not in a federated login at all. The\n // hosted page reads it to skip the session teardown it performs\n // for a redemption that genuinely failed.\n throw new BadRequestError({\n message: 'The login request is unknown or expired.',\n data: { reason: IDENTITY_PROVIDER_LOGIN_NOT_PENDING },\n });\n }\n\n // Cleared whichever way the completion goes: it is single use, and a\n // cookie outliving its pending login only produces a confusing\n // refusal on some later page load.\n unsetResponseCookie(event, OAUTH2_FEDERATED_LOGIN_COOKIE, { path: this.buildFederatedLoginCookiePath() });\n\n return this.loginService.completeHandoff({\n pendingLoginId,\n providerId: id,\n });\n }\n\n // ---------------------------------------------------------\n\n private async write(event: IAppEvent, options: {\n updateOnly?: boolean\n } = {}): Promise<EntityRecordResponse<IdentityProvider>> {\n let group: string;\n const id = getRequestParamID(event, { isUUID: false });\n const body = await readRequestBody(event);\n applyRouteRealmIDToBody(event, body);\n const realmId = getRequestRealmID(event) ?? getBodyRealmID(body);\n\n let entity: IdentityProvider | null | undefined;\n if (id) {\n const where: Record<string, any> = {};\n if (isUUID(id)) {\n where.id = id;\n } else {\n where.name = id;\n }\n\n if (realmId) {\n where.realmId = realmId;\n }\n\n entity = await this.repository.findOneBy(where);\n // Only a NAME key may upsert-create. A UUID addresses one specific\n // row, so a miss is a 404 (creating would write a different id).\n if (!entity && (options.updateOnly || where.id)) {\n throw new EntityNotFoundError();\n }\n } else if (options.updateOnly) {\n throw new EntityNotFoundError();\n }\n\n const permissionEvaluator = useRequestPermissionEvaluator(event);\n if (entity) {\n await permissionEvaluator.preEvaluate({ name: PermissionName.IDENTITY_PROVIDER_UPDATE });\n\n group = ValidatorGroup.UPDATE;\n } else {\n await permissionEvaluator.preEvaluate({ name: PermissionName.IDENTITY_PROVIDER_CREATE });\n\n group = ValidatorGroup.CREATE;\n }\n\n const validator = new IdentityProviderValidator();\n const data = await validator.run(body, { group });\n\n const attributesValidator = new IdentityProviderAttributesValidator();\n const attributes = await attributesValidator.run(body);\n\n await this.repository.validateJoinColumns(data);\n\n if (entity) {\n await permissionEvaluator.evaluate({\n name: PermissionName.IDENTITY_PROVIDER_UPDATE,\n data: definePolicyData({\n [BuiltInPolicyType.ATTRIBUTES]: {\n ...entity,\n ...data,\n },\n [BuiltInPolicyType.REALM_MATCH]: data.realmId ?? entity.realmId ?? null,\n }),\n });\n } else {\n if (!data.realmId) {\n const identity = useRequestIdentityOrFail(event);\n data.realmId = identity.realmId;\n }\n\n await permissionEvaluator.evaluate({\n name: PermissionName.IDENTITY_PROVIDER_CREATE,\n data: definePolicyData({ [BuiltInPolicyType.ATTRIBUTES]: data, [BuiltInPolicyType.REALM_MATCH]: data.realmId ?? null }),\n });\n }\n\n await this.repository.checkUniqueness(data, entity || undefined);\n\n if (entity) {\n // A partial update must not delete attributes it never mentioned.\n // The payload is partial by contract, so automation written before\n // `requiredAmr` / `requiredAcr` existed would otherwise turn the\n // upstream assurance gate off by saying nothing about it. A caller\n // clears an attribute by sending it as `null`, which is what the\n // console submits for a blank field.\n //\n // A protocol switch is the exception and still replaces: the old\n // protocol's rows (an LDAP bind password) are dead configuration\n // no code reads any more, and keeping them leaves a secret behind.\n const keepAll = entity.protocol === data.protocol;\n\n entity = this.repository.merge(entity, data);\n await this.repository.saveWithEA(entity, attributes, { keepAll });\n\n event.response.status = 202;\n\n return { data: entity, meta: {} };\n }\n\n entity = this.repository.create(data);\n await this.repository.saveWithEA(entity, attributes);\n\n event.response.status = 201;\n\n return { data: entity, meta: {} };\n }\n\n // ---------------------------------------------------------\n\n /**\n * Callback half of the account-linking round-trip (plan 091): the\n * external identity is bound to the state-referenced user instead of\n * running the login path. No auth code and no session is minted; the\n * browser lands back on the account console with a marker param.\n */\n private async completeLink(\n event: IAppEvent,\n provider: OAuth2IdentityProvider | OpenIDIdentityProvider,\n state: OAuth2AuthorizationState,\n link: OAuth2AuthorizationStateLink,\n ) {\n // Fixed, server-derived return target (the account console page).\n // No client-supplied redirect exists on this path.\n const url = new URL(resolveURL(this.options.baseURL, 'account/connected-accounts'));\n\n const { code } = useRequestQuery(event);\n\n try {\n // The state is bound to the provider it was minted for, and the\n // provider must still be enabled at completion — a state cannot\n // be replayed against a different provider's callback, nor\n // complete a link after its provider was disabled.\n if (link.providerId !== provider.id || !provider.enabled) {\n throw new BadRequestError('The identity provider is not available for account linking.');\n }\n\n if (typeof code !== 'string' || code.length === 0) {\n throw new BadRequestError('The authorization code is missing.');\n }\n\n const authenticator = this.buildProviderAuthenticator(provider);\n\n const identity = await authenticator.resolveIdentity({ code });\n\n // A provider answering without a subject has nothing to bind. The\n // callback used to reach `link()`, which rejected it; the stash\n // would otherwise carry an empty providerUserId to the confirm.\n if (!identity.id) {\n throw new BadRequestError('The identity provider returned no subject.');\n }\n\n // Nothing is written here. This request is unauthenticated — the\n // browser round-trip cannot carry a bearer — so the only thing\n // distinguishing the browser that started the link from any other\n // would be the state's ip / user-agent binding, and both values\n // are chosen by whoever minted the state. An attacker minting a\n // state carrying their OWN userId and getting a victim to follow\n // it would otherwise bind the VICTIM's external identity to the\n // attacker's account (issue #3439).\n //\n // The resolved identity is stashed under a one-time handle\n // instead, and the account console confirms it with its bearer.\n // The stash keeps the requesting userId so the confirm can\n // require it to equal the AUTHENTICATED user: the handle is a\n // URL parameter, so it reaches browser history and proxy logs,\n // and on its own it must authorize nothing.\n const handle = await this.linkStore.save({\n providerId: provider.id,\n userId: link.userId,\n providerUserId: identity.id,\n providerUserName: this.pickIdentityCandidate(identity, 'name'),\n providerUserEmail: this.pickIdentityCandidate(identity, 'email'),\n });\n\n url.searchParams.set('linkHandle', handle);\n url.searchParams.set('provider', provider.id);\n } catch (e) {\n // The failure is swallowed into a redirect marker, so the log is\n // the only surface it can reach. Without this an unreachable or\n // rejecting provider is indistinguishable from a stale state.\n this.logger?.error(describeError(e, 'The identity-provider account link failed.'));\n\n url.searchParams.set('linkError', 'link_failed');\n }\n\n return sendRedirect(event, url.href);\n }\n\n /**\n * The bearer-authenticated half of the link (issue #3439): the account\n * row is written here, for the AUTHENTICATED user, never in the callback.\n */\n @DPost('/:id/link-confirm', [ForceLoggedInMiddleware])\n async confirmLink(\n @DPath('id') _id: string,\n @DBody() _data: IdentityProviderLinkConfirmPayload,\n @DContext() event: IAppEvent,\n ) : Promise<EntityRecordResponse<IdentityProviderAccount>> {\n const id = useRequestParamID(event);\n const provider = await this.resolve(id);\n\n if (!isOAuth2IdentityProvider(provider) && !isOpenIDIdentityProvider(provider)) {\n throw new BadRequestError('Only an identity-provider based on the oauth protocol supports account linking.');\n }\n\n if (!provider.enabled) {\n throw new BadRequestError('The identity provider is not enabled.');\n }\n\n const identity = useRequestIdentityOrFail(event);\n if (identity.type !== IdentityType.USER) {\n throw new BadRequestError('Only a user can link an identity provider account.');\n }\n\n const body = await readRequestBody(event);\n const handle = isObject(body) ? body.handle : undefined;\n if (typeof handle !== 'string' || handle.length === 0) {\n throw new BadRequestError('The account link handle is missing.');\n }\n\n const link = await this.linkStore.consume(handle);\n if (!link) {\n throw new BadRequestError('The account link request is unknown or expired.');\n }\n\n // The two checks that make the handle inert on its own: it may only\n // be redeemed by the user it was minted for, and only against the\n // provider it was minted for.\n if (link.userId !== identity.id || link.providerId !== provider.id) {\n throw new BadRequestError('The account link request does not belong to the authenticated user.');\n }\n\n const account = await this.accountManager.link(\n {\n id: link.providerUserId,\n // deliberately rebuilt from the stashed scalars: the resolved\n // identity carries the provider secret and the raw external\n // token payload, neither of which belongs in a cache\n attributeCandidates: {\n name: [link.providerUserName],\n email: [link.providerUserEmail],\n },\n data: {},\n provider,\n },\n identity.id,\n );\n\n // the confirm runs under a bearer, so unlike the callback it CAN be\n // attributed to a session and a route (the unlink emit's shape)\n const requestContext = useRequestEventContext();\n\n await this.eventService?.record({\n scope: EventScope.IDENTITY,\n name: EventName.IDENTITY_PROVIDER_LINKED,\n refType: EventRefType.IDENTITY_PROVIDER_ACCOUNT,\n refId: account.id,\n realmId: account.userRealmId ?? provider.realmId ?? null,\n actorType: IdentityType.USER,\n actorId: account.userId,\n actorName: identity.data.name ?? null,\n sessionId: requestContext?.sessionId ?? null,\n requestPath: requestContext?.requestPath ?? null,\n requestMethod: requestContext?.requestMethod ?? null,\n requestIpAddress: requestContext?.requestIpAddress ?? getRequestIP(event) ?? null,\n requestUserAgent: requestContext?.requestUserAgent ?? getRequestHeader(event, 'user-agent') ?? null,\n data: { providerId: provider.id, providerName: provider.name },\n });\n\n return { data: account, meta: {} };\n }\n\n private pickIdentityCandidate(\n identity: IdentityProviderIdentity,\n key: 'name' | 'email',\n ) : string | null {\n const candidates = identity.attributeCandidates?.[key] || [];\n for (const candidate of candidates) {\n if (typeof candidate === 'string' && candidate.length > 0) {\n return candidate;\n }\n }\n\n return null;\n }\n\n // ---------------------------------------------------------\n\n private buildHostedAuthorizeURL(codeRequest: OAuth2AuthorizationCodeRequest): URL {\n const url = new URL(resolveURL(this.options.baseURL, 'authorize'));\n\n const codeRequestKeys = Object.keys(codeRequest);\n for (const codeRequestKey_ of codeRequestKeys) {\n const codeRequestKey = codeRequestKey_ as keyof OAuth2AuthorizationCodeRequest;\n const codeRequestValue = codeRequest[codeRequestKey];\n // Preserve meaningful falsy values (e.g. max_age=0, which OIDC\n // treats as prompt=login) — only skip absent ones.\n if (typeof codeRequestValue !== 'undefined' && codeRequestValue !== null) {\n url.searchParams.set(codeRequestKey, String(codeRequestValue));\n }\n }\n\n return url;\n }\n\n // ---------------------------------------------------------\n\n private async resolve(id: string) {\n const entity = await this.repository.findOneById(id);\n\n if (!entity) {\n throw new EntityNotFoundError();\n }\n\n return entity;\n }\n\n // ---------------------------------------------------------\n\n private buildProviderAuthenticator(\n provider: OAuth2IdentityProvider | OpenIDIdentityProvider,\n options: { clientId?: string } = {},\n ) {\n return createIdentityProviderOAuth2Authenticator({\n accountManager: this.accountManager,\n provider,\n logger: this.logger,\n options: {\n baseURL: this.options.baseURL,\n clientId: options.clientId,\n },\n });\n }\n\n /**\n * Scoped to the routes that read it, so the cookie never rides an\n * ordinary API request, and to publicUrl's base path, since a sub-path\n * deployment mounts every route under it.\n */\n private buildFederatedLoginCookiePath() : string {\n return `${getURLBasePath(this.options.baseURL)}/identity-providers`;\n }\n\n private setFederatedLoginCookie(event: IAppEvent, value: string) : void {\n setResponseCookie(event, OAUTH2_FEDERATED_LOGIN_COOKIE, value, {\n httpOnly: true,\n sameSite: 'lax',\n // The scheme is read from the parsed URL rather than the raw\n // string: `publicUrl` is validated but never canonicalized, so a\n // configured `HTTPS://...` would leave a prefix test false and\n // drop the flag from the one cookie the login is bound to.\n secure: new URL(this.options.baseURL).protocol === 'https:',\n path: this.buildFederatedLoginCookiePath(),\n maxAge: OAUTH2_FEDERATED_LOGIN_TTL / 1000,\n });\n }\n\n /**\n * The completion answers with a token pair and authenticates on a cookie\n * alone, so it must be reachable only from the hosted page's own origin.\n *\n * CORS cannot carry that here: the default reflects every origin WITH\n * credentials, and `SameSite` is scoped to the registrable domain rather\n * than the origin, so a sibling subdomain's script is same-site and would\n * both send the cookie and be allowed to read the response. A browser\n * sends `Origin` on every POST, so requiring it to be publicUrl's own is\n * what closes that. A request without the header is not a browser and\n * carries no cookie of ours to begin with.\n */\n private assertSameOrigin(event: IAppEvent) : void {\n const origin = getRequestHeader(event, 'origin');\n if (typeof origin !== 'string' || origin.length === 0) {\n return;\n }\n\n if (origin !== new URL(this.options.baseURL).origin) {\n throw new BadRequestError('The login request is unknown or expired.');\n }\n }\n\n private async saveAuthorizationState(\n event: IAppEvent,\n data: Pick<OAuth2AuthorizationState, 'codeRequest' | 'link' | 'browserNonce'> = {},\n ) : Promise<string> {\n return this.stateManager.save({\n ...data,\n ip: getRequestIP(event) ?? '',\n userAgent: getRequestHeader(event, 'user-agent') ?? undefined,\n });\n }\n\n private async verifyAuthorizationState(event: IAppEvent): Promise<OAuth2AuthorizationState> {\n const query = useRequestQuery(event);\n if (typeof query.state !== 'string') {\n throw OAuth2RequestError.stateInvalid();\n }\n\n return this.stateManager.verify(query.state, {\n ip: getRequestIP(event) ?? '',\n userAgent: getRequestHeader(event, 'user-agent') ?? undefined,\n });\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
import type { Logger } from '@authup/server-kit';
|
|
2
|
-
import type { IEventService, IIdentityProviderAccountLinkStore, IIdentityProviderAccountManager, IIdentityProviderRepository,
|
|
2
|
+
import type { IEventService, IIdentityProviderAccountLinkStore, IIdentityProviderAccountManager, IIdentityProviderRepository, IOAuth2AuthorizationCodeRequestVerifier, IOAuth2AuthorizationStateManager, IOAuth2FederatedLoginService } from '../../../../../core/index.ts';
|
|
3
3
|
export type IdentityProviderControllerOptions = {
|
|
4
4
|
baseURL: string;
|
|
5
5
|
};
|
|
6
6
|
export type IdentityProviderControllerContext = {
|
|
7
7
|
options: IdentityProviderControllerOptions;
|
|
8
8
|
repository: IIdentityProviderRepository;
|
|
9
|
-
realmRepository: IRealmRepository;
|
|
10
9
|
accountManager: IIdentityProviderAccountManager;
|
|
11
10
|
linkStore: IIdentityProviderAccountLinkStore;
|
|
12
|
-
codeIssuer: IOAuth2AuthorizationCodeIssuer;
|
|
13
11
|
codeRequestVerifier: IOAuth2AuthorizationCodeRequestVerifier;
|
|
14
12
|
stateManager: IOAuth2AuthorizationStateManager;
|
|
15
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Owns the federated-login completion ladder. The controller only maps
|
|
15
|
+
* its result onto a transport.
|
|
16
|
+
*/
|
|
17
|
+
loginService: IOAuth2FederatedLoginService;
|
|
16
18
|
eventService?: IEventService;
|
|
17
19
|
logger?: Logger;
|
|
18
20
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../../../src/adapters/http/controllers/entities/identity-provider/types.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,KAAK,EACR,aAAa,EACb,iCAAiC,EACjC,+BAA+B,EAC/B,2BAA2B,EAC3B,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../../../src/adapters/http/controllers/entities/identity-provider/types.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,KAAK,EACR,aAAa,EACb,iCAAiC,EACjC,+BAA+B,EAC/B,2BAA2B,EAC3B,uCAAuC,EACvC,gCAAgC,EAChC,4BAA4B,EAC/B,MAAM,8BAA8B,CAAC;AAEtC,MAAM,MAAM,iCAAiC,GAAG;IAC5C,OAAO,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,iCAAiC,GAAG;IAC5C,OAAO,EAAE,iCAAiC,CAAC;IAE3C,UAAU,EAAE,2BAA2B,CAAC;IAExC,cAAc,EAAE,+BAA+B,CAAC;IAChD,SAAS,EAAE,iCAAiC,CAAC;IAC7C,mBAAmB,EAAE,uCAAuC,CAAC;IAC7D,YAAY,EAAE,gCAAgC,CAAC;IAC/C;;;OAGG;IACH,YAAY,EAAE,4BAA4B,CAAC;IAE3C,YAAY,CAAC,EAAE,aAAa,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC"}
|
|
@@ -3,12 +3,12 @@ import "node:url";
|
|
|
3
3
|
import.meta.url;
|
|
4
4
|
import { RECORD_QUERY_PARAMETERS, describeQuerySchema } from "../../../../../core/query/describe.mjs";
|
|
5
5
|
import { realmSchema } from "../../../../../core/entities/realm/schema.mjs";
|
|
6
|
+
import { resolveURL } from "../../../../../utils/url.mjs";
|
|
7
|
+
import "../../../../../utils/index.mjs";
|
|
6
8
|
import "../../../../../core/index.mjs";
|
|
7
9
|
import { buildActorContext } from "../../../request/helpers/actor.mjs";
|
|
8
10
|
import "../../../request/index.mjs";
|
|
9
11
|
import { ForceLoggedInMiddleware } from "../../../middleware/built-in/loggedin/module.mjs";
|
|
10
|
-
import { resolveURL } from "../../../../../utils/url.mjs";
|
|
11
|
-
import "../../../../../utils/index.mjs";
|
|
12
12
|
import "../../../middleware/index.mjs";
|
|
13
13
|
import { getJwkRouteHandler, getJwksRouteHandler } from "../../workflows/jwks/handlers/read.mjs";
|
|
14
14
|
import "../../workflows/index.mjs";
|
|
@@ -3,13 +3,13 @@ import "node:url";
|
|
|
3
3
|
import.meta.url;
|
|
4
4
|
import { RECORD_QUERY_PARAMETERS, describeQuerySchema } from "../../../../../core/query/describe.mjs";
|
|
5
5
|
import { sessionSchema } from "../../../../../core/entities/session/schema.mjs";
|
|
6
|
+
import { isSelfToken } from "../../../../../utils/self-id.mjs";
|
|
7
|
+
import "../../../../../utils/index.mjs";
|
|
6
8
|
import "../../../../../core/index.mjs";
|
|
7
9
|
import { buildActorContext } from "../../../request/helpers/actor.mjs";
|
|
8
10
|
import { useRequestSessionId } from "../../../request/helpers/session-id.mjs";
|
|
9
11
|
import "../../../request/index.mjs";
|
|
10
12
|
import { ForceLoggedInMiddleware } from "../../../middleware/built-in/loggedin/module.mjs";
|
|
11
|
-
import { isSelfToken } from "../../../../../utils/self-id.mjs";
|
|
12
|
-
import "../../../../../utils/index.mjs";
|
|
13
13
|
import "../../../middleware/index.mjs";
|
|
14
14
|
import { useRequestQuery } from "@routup/basic/query";
|
|
15
15
|
import { DContext, DController, DDelete, DGet, DPath, DTags } from "@routup/decorators";
|
|
@@ -3,13 +3,13 @@ import "node:url";
|
|
|
3
3
|
import.meta.url;
|
|
4
4
|
import { RECORD_QUERY_PARAMETERS, describeQuerySchema } from "../../../../../core/query/describe.mjs";
|
|
5
5
|
import { userSchema } from "../../../../../core/entities/user/schema.mjs";
|
|
6
|
+
import { isSelfToken } from "../../../../../utils/self-id.mjs";
|
|
7
|
+
import "../../../../../utils/index.mjs";
|
|
6
8
|
import "../../../../../core/index.mjs";
|
|
7
9
|
import { buildActorContext } from "../../../request/helpers/actor.mjs";
|
|
8
10
|
import { applyRouteRealmIDToBody, getRequestRealmID } from "../../../request/helpers/realm-id.mjs";
|
|
9
11
|
import "../../../request/index.mjs";
|
|
10
12
|
import { ForceLoggedInMiddleware } from "../../../middleware/built-in/loggedin/module.mjs";
|
|
11
|
-
import { isSelfToken } from "../../../../../utils/self-id.mjs";
|
|
12
|
-
import "../../../../../utils/index.mjs";
|
|
13
13
|
import "../../../middleware/index.mjs";
|
|
14
14
|
import { useRequestQuery } from "@routup/basic/query";
|
|
15
15
|
import { DBody, DContext, DController, DDelete, DGet, DPath, DPost, DPut, DTags } from "@routup/decorators";
|
|
@@ -3,13 +3,13 @@ import "node:url";
|
|
|
3
3
|
import.meta.url;
|
|
4
4
|
import { RECORD_QUERY_PARAMETERS, describeQuerySchema } from "../../../../../core/query/describe.mjs";
|
|
5
5
|
import { userAuthenticatorSchema } from "../../../../../core/entities/user-authenticator/schema.mjs";
|
|
6
|
+
import { isSelfToken } from "../../../../../utils/self-id.mjs";
|
|
7
|
+
import "../../../../../utils/index.mjs";
|
|
6
8
|
import "../../../../../core/index.mjs";
|
|
7
9
|
import { useRequestIdentity } from "../../../request/helpers/identity.mjs";
|
|
8
10
|
import { buildActorContext } from "../../../request/helpers/actor.mjs";
|
|
9
11
|
import "../../../request/index.mjs";
|
|
10
12
|
import { ForceLoggedInMiddleware } from "../../../middleware/built-in/loggedin/module.mjs";
|
|
11
|
-
import { isSelfToken } from "../../../../../utils/self-id.mjs";
|
|
12
|
-
import "../../../../../utils/index.mjs";
|
|
13
13
|
import "../../../middleware/index.mjs";
|
|
14
14
|
import { IdentityType } from "@authup/core-kit";
|
|
15
15
|
import { BadRequestError } from "@authup/errors";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Session } from '@authup/core-kit';
|
|
1
2
|
import type { IAppEvent } from 'routup';
|
|
2
3
|
import type { UserAuthenticatorChallengeResponse, UserAuthenticatorChallengeSendPayload, UserAuthenticatorChallengeVerifyPayload, UserAuthenticatorChallengeVerifyResponse } from '@authup/core-http-kit';
|
|
3
4
|
import type { IOAuth2MfaLoginService, ISessionManager, IUserAuthenticatorService } from '../../../../../core/index.ts';
|
|
@@ -37,6 +38,7 @@ export declare class AuthenticatorChallengeController {
|
|
|
37
38
|
*/
|
|
38
39
|
protected resolveActor(event: IAppEvent): ChallengeActor;
|
|
39
40
|
status(event: IAppEvent): Promise<UserAuthenticatorChallengeResponse>;
|
|
41
|
+
protected resolveSession(event: IAppEvent): Promise<Session | null>;
|
|
40
42
|
send(data: UserAuthenticatorChallengeSendPayload, event: IAppEvent): Promise<{
|
|
41
43
|
success: true;
|
|
42
44
|
}>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../../../../../../src/adapters/http/controllers/workflows/authenticator-challenge/module.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../../../../../../src/adapters/http/controllers/workflows/authenticator-challenge/module.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAKhD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AAExC,OAAO,KAAK,EACR,kCAAkC,EAClC,qCAAqC,EACrC,uCAAuC,EACvC,wCAAwC,EAC3C,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,sBAAsB,EAAE,eAAe,EAAE,yBAAyB,EAAE,MAAM,8BAA8B,CAAC;AACvH,OAAO,KAAK,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAQxF,MAAM,MAAM,uCAAuC,GAAG;IAClD,OAAO,EAAE,yBAAyB,CAAC;IACnC,cAAc,EAAE,eAAe,CAAC;IAChC,eAAe,CAAC,EAAE,sBAAsB,CAAC;CAC5C,CAAC;AAEF,KAAK,cAAc,GAAG;IAClB,QAAQ,EAAE,eAAe,CAAC;IAC1B,MAAM,EAAE,qBAAqB,GAAG,IAAI,CAAC;CACxC,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,qBAEa,gCAAgC;IACzC,SAAS,CAAC,OAAO,EAAE,yBAAyB,CAAC;IAE7C,SAAS,CAAC,cAAc,EAAE,eAAe,CAAC;IAE1C,SAAS,CAAC,eAAe,CAAC,EAAE,sBAAsB,CAAC;gBAEvC,GAAG,EAAE,uCAAuC;IAMxD;;;;;OAKG;IACH,SAAS,CAAC,YAAY,CAAC,KAAK,EAAE,SAAS,GAAI,cAAc;IAenD,MAAM,CACI,KAAK,EAAE,SAAS,GAC7B,OAAO,CAAC,kCAAkC,CAAC;cA6C9B,cAAc,CAAC,KAAK,EAAE,SAAS,GAAI,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;IAUpE,IAAI,CACG,IAAI,EAAE,qCAAqC,EACxC,KAAK,EAAE,SAAS,GAC7B,OAAO,CAAC;QAAE,OAAO,EAAE,IAAI,CAAA;KAAE,CAAC;IAqBvB,MAAM,CACC,IAAI,EAAE,uCAAuC,EAC1C,KAAK,EAAE,SAAS,GAC7B,OAAO,CAAC,wCAAwC,CAAC;CAuEvD"}
|
|
@@ -6,9 +6,11 @@ import { useRequestLocale } from "../../../request/helpers/locale.mjs";
|
|
|
6
6
|
import { useRequestMfaLoginTicket } from "../../../request/helpers/mfa-login-ticket.mjs";
|
|
7
7
|
import { useRequestSessionId } from "../../../request/helpers/session-id.mjs";
|
|
8
8
|
import "../../../request/index.mjs";
|
|
9
|
-
import { IdentityType, UserAuthenticatorKind } from "@authup/core-kit";
|
|
9
|
+
import { IdentityType, SessionAuthMethod, UserAuthenticatorKind } from "@authup/core-kit";
|
|
10
10
|
import { BadRequestError, EntityCredentialsInvalidError, UnauthorizedError } from "@authup/errors";
|
|
11
|
+
import { OAuth2AuthenticationContextClass } from "@authup/specs";
|
|
11
12
|
import { getRequestHeader, getRequestIP } from "routup";
|
|
13
|
+
import { useRequestQuery } from "@routup/basic/query";
|
|
12
14
|
import { DBody, DContext, DController, DGet, DPost, DTags } from "@routup/decorators";
|
|
13
15
|
//#region src/adapters/http/controllers/workflows/authenticator-challenge/module.ts
|
|
14
16
|
function _ts_decorate(decorators, target, key, desc) {
|
|
@@ -53,13 +55,29 @@ var AuthenticatorChallengeController = class {
|
|
|
53
55
|
throw new UnauthorizedError();
|
|
54
56
|
}
|
|
55
57
|
async status(event) {
|
|
56
|
-
const { identity } = this.resolveActor(event);
|
|
58
|
+
const { identity, ticket } = this.resolveActor(event);
|
|
57
59
|
if (identity.type !== IdentityType.USER) return {
|
|
58
60
|
required: false,
|
|
59
61
|
enrollmentRequired: false,
|
|
60
62
|
kinds: []
|
|
61
63
|
};
|
|
62
|
-
|
|
64
|
+
const status = await this.service.challenge(identity.id);
|
|
65
|
+
const session = ticket ? null : await this.resolveSession(event);
|
|
66
|
+
if (session && session.authMethod === SessionAuthMethod.EXTERNAL) {
|
|
67
|
+
const { acrValues } = useRequestQuery(event);
|
|
68
|
+
const stepUpRequested = typeof acrValues === "string" && acrValues.split(" ").includes(OAuth2AuthenticationContextClass.MFA);
|
|
69
|
+
return {
|
|
70
|
+
...status,
|
|
71
|
+
required: stepUpRequested && status.required,
|
|
72
|
+
enrollmentRequired: false
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
return status;
|
|
76
|
+
}
|
|
77
|
+
async resolveSession(event) {
|
|
78
|
+
const sessionId = useRequestSessionId(event);
|
|
79
|
+
if (!sessionId) return null;
|
|
80
|
+
return this.sessionManager.findOneById(sessionId);
|
|
63
81
|
}
|
|
64
82
|
async send(data, event) {
|
|
65
83
|
const { identity } = this.resolveActor(event);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module.mjs","names":["DBody","DContext","DController","DGet","DPost","DTags","IdentityType","UserAuthenticatorKind","BadRequestError","EntityCredentialsInvalidError","UnauthorizedError","getRequestHeader","getRequestIP","useRequestIdentity","useRequestLocale","useRequestMfaLoginTicket","useRequestSessionId","AuthenticatorChallengeController","service","sessionManager","mfaLoginService","ctx","resolveActor","event","identity","ticket","status","type","USER","required","enrollmentRequired","kinds","challenge","id","send","data","Object","values","includes","kind","sendChallenge","locale","success","verify","response","sessionId","payload","session_id","session","findOneById","verified","ipAddress","userAgent","clientId","onVerified","markMfaVerified","token","complete","userName","name"],"sources":["../../../../../../src/adapters/http/controllers/workflows/authenticator-challenge/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 {\n DBody,\n DContext,\n DController,\n DGet,\n DPost,\n DTags,\n} from '@routup/decorators';\nimport { IdentityType, UserAuthenticatorKind } from '@authup/core-kit';\nimport { BadRequestError, EntityCredentialsInvalidError, UnauthorizedError } from '@authup/errors';\nimport type { IAppEvent } from 'routup';\nimport { getRequestHeader, getRequestIP } from 'routup';\nimport type {\n UserAuthenticatorChallengeResponse,\n UserAuthenticatorChallengeSendPayload,\n UserAuthenticatorChallengeVerifyPayload,\n UserAuthenticatorChallengeVerifyResponse,\n} from '@authup/core-http-kit';\nimport type { IOAuth2MfaLoginService, ISessionManager, IUserAuthenticatorService } from '../../../../../core/index.ts';\nimport type { RequestIdentity, RequestMfaLoginTicket } from '../../../request/index.ts';\nimport {\n useRequestIdentity,\n useRequestLocale,\n useRequestMfaLoginTicket,\n useRequestSessionId,\n} from '../../../request/index.ts';\n\nexport type AuthenticatorChallengeControllerContext = {\n service: IUserAuthenticatorService,\n sessionManager: ISessionManager,\n mfaLoginService?: IOAuth2MfaLoginService,\n};\n\ntype ChallengeActor = {\n identity: RequestIdentity,\n ticket: RequestMfaLoginTicket | null,\n};\n\n/**\n * Login-time challenge surface, scoped to the CURRENT bearer identity.\n * GET reports whether (and how) the caller must present a second factor;\n * POST verifies a factor and stamps mfa_at onto the backing session so\n * the /authorize backstop passes.\n *\n * Besides a regular access bearer, these routes accept the \"MFA-pending\"\n * login ticket (kind: mfa_token, issue #3242) the password grant issues\n * for factor kinds that cannot ride the single grant POST (email /\n * WebAuthn). A ticket-authenticated verify additionally completes the\n * login: the response carries the full token grant for the now-MFA'd\n * pending session.\n */\n@DTags('userAuthenticator')\n@DController('/authenticators/challenge')\nexport class AuthenticatorChallengeController {\n protected service: IUserAuthenticatorService;\n\n protected sessionManager: ISessionManager;\n\n protected mfaLoginService?: IOAuth2MfaLoginService;\n\n constructor(ctx: AuthenticatorChallengeControllerContext) {\n this.service = ctx.service;\n this.sessionManager = ctx.sessionManager;\n this.mfaLoginService = ctx.mfaLoginService;\n }\n\n /**\n * Resolve the acting subject: the regular request identity (access\n * bearer — the middleware never sets it for a ticket) or the stashed\n * MFA-pending ticket. Anything else is unauthorized — the previous\n * ForceLoggedInMiddleware contract, widened by exactly one bearer kind.\n */\n protected resolveActor(event: IAppEvent) : ChallengeActor {\n const identity = useRequestIdentity(event);\n if (identity) {\n return { identity, ticket: null };\n }\n\n const ticket = useRequestMfaLoginTicket(event);\n if (ticket) {\n return { identity: ticket.identity, ticket };\n }\n\n throw new UnauthorizedError();\n }\n\n @DGet('')\n async status(\n @DContext() event: IAppEvent,\n ): Promise<UserAuthenticatorChallengeResponse> {\n const { identity } = this.resolveActor(event);\n if (identity.type !== IdentityType.USER) {\n return {\n required: false,\n enrollmentRequired: false,\n kinds: [],\n };\n }\n\n return this.service.challenge(identity.id);\n }\n\n @DPost('/send')\n async send(\n @DBody() data: UserAuthenticatorChallengeSendPayload,\n @DContext() event: IAppEvent,\n ): Promise<{ success: true }> {\n const { identity } = this.resolveActor(event);\n if (identity.type !== IdentityType.USER) {\n throw new BadRequestError('Only user identities can request a challenge.');\n }\n\n if (\n !data ||\n !Object.values(UserAuthenticatorKind).includes(data.kind as UserAuthenticatorKind)\n ) {\n throw new BadRequestError('A kind must be provided.');\n }\n\n await this.service.sendChallenge(identity.id, data.kind, { locale: useRequestLocale(event) });\n\n // uniform 200 regardless of whether a code was actually mailed —\n // no enrollment-status oracle for the caller.\n return { success: true };\n }\n\n @DPost('')\n async verify(\n @DBody() data: UserAuthenticatorChallengeVerifyPayload,\n @DContext() event: IAppEvent,\n ): Promise<UserAuthenticatorChallengeVerifyResponse> {\n const { identity, ticket } = this.resolveActor(event);\n if (identity.type !== IdentityType.USER) {\n throw new BadRequestError('Only user identities can complete a challenge.');\n }\n\n if (\n !data ||\n typeof data.response !== 'string' ||\n !data.response ||\n !Object.values(UserAuthenticatorKind).includes(data.kind as UserAuthenticatorKind)\n ) {\n throw new BadRequestError('A kind and response must be provided.');\n }\n\n // Bind the proof to the backing session — the /authorize backstop\n // reads session.mfa_at. The session id is server-derived (stashed\n // by the authorization middleware / carried by the verified ticket),\n // never client input. The stamp runs INSIDE the verify unit of work\n // (before the code consumption persists), so a stamp failure never\n // burns a single-use code.\n const sessionId = ticket ?\n ticket.payload.session_id :\n useRequestSessionId(event);\n const session = sessionId ?\n await this.sessionManager.findOneById(sessionId) :\n null;\n\n // a ticket references exactly one pending session — without it\n // (swept / expired mid-flight) the login can not complete.\n if (ticket && !session) {\n throw new UnauthorizedError();\n }\n\n const verified = await this.service.verify(\n identity.id,\n {\n kind: data.kind,\n response: data.response,\n },\n {\n ipAddress: getRequestIP(event) ?? null,\n userAgent: getRequestHeader(event, 'user-agent') ?? null,\n clientId: identity.clientId,\n sessionId: sessionId ?? null,\n ...(session ? {\n onVerified: async () => {\n await this.sessionManager.markMfaVerified(session);\n },\n } : {}),\n },\n );\n if (!verified) {\n throw new EntityCredentialsInvalidError();\n }\n\n // Ticket flow: the factor is verified and mfa_at stamped — complete\n // the login by minting the full grant for the pending session and\n // consuming the ticket (single use).\n if (ticket && session && this.mfaLoginService) {\n const token = await this.mfaLoginService.complete({\n session,\n ticket: ticket.payload,\n userName: identity.data.name,\n });\n\n return { verified: true, token };\n }\n\n return { verified: true };\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AA4DA,IAAaiB,mCAAb,MAAaA;CACCC;CAEAC;CAEAC;CAEV,YAAYC,KAA8C;EACtD,KAAKH,UAAUG,IAAIH;EACnB,KAAKC,iBAAiBE,IAAIF;EAC1B,KAAKC,kBAAkBC,IAAID;CAC/B;;;;;;IAQA,aAAuBG,OAAmC;EACtD,MAAMC,WAAWX,mBAAmBU,KAAAA;EACpC,IAAIC,UACA,OAAO;GAAEA;GAAUC,QAAQ;EAAK;EAGpC,MAAMA,SAASV,yBAAyBQ,KAAAA;EACxC,IAAIE,QACA,OAAO;GAAED,UAAUC,OAAOD;GAAUC;EAAO;EAG/C,MAAM,IAAIf,kBAAAA;CACd;CAEA,MACMgB,OACF,OAC2C;EAC3C,MAAM,EAAEF,aAAa,KAAKF,aAAaC,KAAAA;EACvC,IAAIC,SAASG,SAASrB,aAAasB,MAC/B,OAAO;GACHC,UAAU;GACVC,oBAAoB;GACpBC,OAAO,CAAA;EACX;EAGJ,OAAO,KAAKb,QAAQc,UAAUR,SAASS,EAAE;CAC7C;CAEA,MACMC,KACF,MACA,OAC0B;EAC1B,MAAM,EAAEV,aAAa,KAAKF,aAAaC,KAAAA;EACvC,IAAIC,SAASG,SAASrB,aAAasB,MAC/B,MAAM,IAAIpB,gBAAgB,+CAAA;EAG9B,IACI,CAAC2B,QACD,CAACC,OAAOC,OAAO9B,qBAAAA,CAAAA,CAAuB+B,SAASH,KAAKI,IAAI,GAExD,MAAM,IAAI/B,gBAAgB,0BAAA;EAG9B,MAAM,KAAKU,QAAQsB,cAAchB,SAASS,IAAIE,KAAKI,MAAM,EAAEE,QAAQ3B,iBAAiBS,KAAAA,EAAO,CAAA;EAI3F,OAAO,EAAEmB,SAAS,KAAK;CAC3B;CAEA,MACMC,OACF,MACA,OACiD;EACjD,MAAM,EAAEnB,UAAUC,WAAW,KAAKH,aAAaC,KAAAA;EAC/C,IAAIC,SAASG,SAASrB,aAAasB,MAC/B,MAAM,IAAIpB,gBAAgB,gDAAA;EAG9B,IACI,CAAC2B,QACD,OAAOA,KAAKS,aAAa,YACzB,CAACT,KAAKS,YACN,CAACR,OAAOC,OAAO9B,qBAAAA,CAAAA,CAAuB+B,SAASH,KAAKI,IAAI,GAExD,MAAM,IAAI/B,gBAAgB,uCAAA;EAS9B,MAAMqC,YAAYpB,SACdA,OAAOqB,QAAQC,aACf/B,oBAAoBO,KAAAA;EACxB,MAAMyB,UAAUH,YACZ,MAAM,KAAK1B,eAAe8B,YAAYJ,SAAAA,IACtC;EAIJ,IAAIpB,UAAU,CAACuB,SACX,MAAM,IAAItC,kBAAAA;EAqBd,IAAI,CAACwC,MAlBkB,KAAKhC,QAAQyB,OAChCnB,SAASS,IACT;GACIM,MAAMJ,KAAKI;GACXK,UAAUT,KAAKS;EACnB,GACA;GACIO,WAAWvC,aAAaW,KAAAA,KAAU;GAClC6B,WAAWzC,iBAAiBY,OAAO,YAAA,KAAiB;GACpD8B,UAAU7B,SAAS6B;GACnBR,WAAWA,aAAa;GACxB,GAAIG,UAAU,EACVM,YAAY,YAAA;IACR,MAAM,KAAKnC,eAAeoC,gBAAgBP,OAAAA;GAC9C,EACJ,IAAI,CAAC;EACT,CAAA,GAGA,MAAM,IAAIvC,8BAAAA;EAMd,IAAIgB,UAAUuB,WAAW,KAAK5B,iBAO1B,OAAO;GAAE8B,UAAU;GAAMM,OAAAA,MANL,KAAKpC,gBAAgBqC,SAAS;IAC9CT;IACAvB,QAAQA,OAAOqB;IACfY,UAAUlC,SAASW,KAAKwB;GAC5B,CAAA;EAE+B;EAGnC,OAAO,EAAET,UAAU,KAAK;CAC5B;AACJ"}
|
|
1
|
+
{"version":3,"file":"module.mjs","names":["DBody","DContext","DController","DGet","DPost","DTags","IdentityType","SessionAuthMethod","UserAuthenticatorKind","OAuth2AuthenticationContextClass","useRequestQuery","BadRequestError","EntityCredentialsInvalidError","UnauthorizedError","getRequestHeader","getRequestIP","useRequestIdentity","useRequestLocale","useRequestMfaLoginTicket","useRequestSessionId","AuthenticatorChallengeController","service","sessionManager","mfaLoginService","ctx","resolveActor","event","identity","ticket","status","type","USER","required","enrollmentRequired","kinds","challenge","id","session","resolveSession","authMethod","EXTERNAL","acrValues","stepUpRequested","split","includes","MFA","sessionId","findOneById","send","data","Object","values","kind","sendChallenge","locale","success","verify","response","payload","session_id","verified","ipAddress","userAgent","clientId","onVerified","markMfaVerified","token","complete","userName","name"],"sources":["../../../../../../src/adapters/http/controllers/workflows/authenticator-challenge/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 {\n DBody,\n DContext,\n DController,\n DGet,\n DPost,\n DTags,\n} from '@routup/decorators';\nimport type { Session } from '@authup/core-kit';\nimport { IdentityType, SessionAuthMethod, UserAuthenticatorKind } from '@authup/core-kit';\nimport { OAuth2AuthenticationContextClass } from '@authup/specs';\nimport { useRequestQuery } from '@routup/basic/query';\nimport { BadRequestError, EntityCredentialsInvalidError, UnauthorizedError } from '@authup/errors';\nimport type { IAppEvent } from 'routup';\nimport { getRequestHeader, getRequestIP } from 'routup';\nimport type {\n UserAuthenticatorChallengeResponse,\n UserAuthenticatorChallengeSendPayload,\n UserAuthenticatorChallengeVerifyPayload,\n UserAuthenticatorChallengeVerifyResponse,\n} from '@authup/core-http-kit';\nimport type { IOAuth2MfaLoginService, ISessionManager, IUserAuthenticatorService } from '../../../../../core/index.ts';\nimport type { RequestIdentity, RequestMfaLoginTicket } from '../../../request/index.ts';\nimport {\n useRequestIdentity,\n useRequestLocale,\n useRequestMfaLoginTicket,\n useRequestSessionId,\n} from '../../../request/index.ts';\n\nexport type AuthenticatorChallengeControllerContext = {\n service: IUserAuthenticatorService,\n sessionManager: ISessionManager,\n mfaLoginService?: IOAuth2MfaLoginService,\n};\n\ntype ChallengeActor = {\n identity: RequestIdentity,\n ticket: RequestMfaLoginTicket | null,\n};\n\n/**\n * Login-time challenge surface, scoped to the CURRENT bearer identity.\n * GET reports whether (and how) the caller must present a second factor;\n * POST verifies a factor and stamps mfa_at onto the backing session so\n * the /authorize backstop passes.\n *\n * Besides a regular access bearer, these routes accept the \"MFA-pending\"\n * login ticket (kind: mfa_token, issue #3242) the password grant issues\n * for factor kinds that cannot ride the single grant POST (email /\n * WebAuthn). A ticket-authenticated verify additionally completes the\n * login: the response carries the full token grant for the now-MFA'd\n * pending session.\n */\n@DTags('userAuthenticator')\n@DController('/authenticators/challenge')\nexport class AuthenticatorChallengeController {\n protected service: IUserAuthenticatorService;\n\n protected sessionManager: ISessionManager;\n\n protected mfaLoginService?: IOAuth2MfaLoginService;\n\n constructor(ctx: AuthenticatorChallengeControllerContext) {\n this.service = ctx.service;\n this.sessionManager = ctx.sessionManager;\n this.mfaLoginService = ctx.mfaLoginService;\n }\n\n /**\n * Resolve the acting subject: the regular request identity (access\n * bearer — the middleware never sets it for a ticket) or the stashed\n * MFA-pending ticket. Anything else is unauthorized — the previous\n * ForceLoggedInMiddleware contract, widened by exactly one bearer kind.\n */\n protected resolveActor(event: IAppEvent) : ChallengeActor {\n const identity = useRequestIdentity(event);\n if (identity) {\n return { identity, ticket: null };\n }\n\n const ticket = useRequestMfaLoginTicket(event);\n if (ticket) {\n return { identity: ticket.identity, ticket };\n }\n\n throw new UnauthorizedError();\n }\n\n @DGet('')\n async status(\n @DContext() event: IAppEvent,\n ): Promise<UserAuthenticatorChallengeResponse> {\n const { identity, ticket } = this.resolveActor(event);\n if (identity.type !== IdentityType.USER) {\n return {\n required: false,\n enrollmentRequired: false,\n kinds: [],\n };\n }\n\n const status = await this.service.challenge(identity.id);\n\n // What this SESSION still owes, not merely what the user holds. A\n // session an external identity provider established was authenticated\n // there, which is where MFA is enforced for it, so authup asks for no\n // local factor on top and `mfaRequired` forces no local enrollment.\n //\n // The rule lives here rather than in the page, so the hosted ladder\n // and any other consumer read one answer; `authorizeInner` keeps its\n // own copy as the backstop, which is the point of a backstop.\n //\n // The exception is an application that requested MFA explicitly. The\n // caller passes the `acr_values` it is being asked to satisfy, since\n // only it knows the authorization request.\n const session = ticket ? null : await this.resolveSession(event);\n if (session && session.authMethod === SessionAuthMethod.EXTERNAL) {\n const { acrValues } = useRequestQuery(event);\n const stepUpRequested = typeof acrValues === 'string' &&\n acrValues.split(' ').includes(OAuth2AuthenticationContextClass.MFA);\n\n // Enrollment stays suppressed either way: `authorizeInner` never\n // forces it on an external session, so offering it here would\n // route the person into a step the server does not ask for. A\n // step-up can only ADD the challenge, and only for a factor the\n // person already holds.\n return {\n ...status,\n required: stepUpRequested && status.required,\n enrollmentRequired: false,\n };\n }\n\n return status;\n }\n\n protected async resolveSession(event: IAppEvent) : Promise<Session | null> {\n const sessionId = useRequestSessionId(event);\n if (!sessionId) {\n return null;\n }\n\n return this.sessionManager.findOneById(sessionId);\n }\n\n @DPost('/send')\n async send(\n @DBody() data: UserAuthenticatorChallengeSendPayload,\n @DContext() event: IAppEvent,\n ): Promise<{ success: true }> {\n const { identity } = this.resolveActor(event);\n if (identity.type !== IdentityType.USER) {\n throw new BadRequestError('Only user identities can request a challenge.');\n }\n\n if (\n !data ||\n !Object.values(UserAuthenticatorKind).includes(data.kind as UserAuthenticatorKind)\n ) {\n throw new BadRequestError('A kind must be provided.');\n }\n\n await this.service.sendChallenge(identity.id, data.kind, { locale: useRequestLocale(event) });\n\n // uniform 200 regardless of whether a code was actually mailed —\n // no enrollment-status oracle for the caller.\n return { success: true };\n }\n\n @DPost('')\n async verify(\n @DBody() data: UserAuthenticatorChallengeVerifyPayload,\n @DContext() event: IAppEvent,\n ): Promise<UserAuthenticatorChallengeVerifyResponse> {\n const { identity, ticket } = this.resolveActor(event);\n if (identity.type !== IdentityType.USER) {\n throw new BadRequestError('Only user identities can complete a challenge.');\n }\n\n if (\n !data ||\n typeof data.response !== 'string' ||\n !data.response ||\n !Object.values(UserAuthenticatorKind).includes(data.kind as UserAuthenticatorKind)\n ) {\n throw new BadRequestError('A kind and response must be provided.');\n }\n\n // Bind the proof to the backing session — the /authorize backstop\n // reads session.mfa_at. The session id is server-derived (stashed\n // by the authorization middleware / carried by the verified ticket),\n // never client input. The stamp runs INSIDE the verify unit of work\n // (before the code consumption persists), so a stamp failure never\n // burns a single-use code.\n const sessionId = ticket ?\n ticket.payload.session_id :\n useRequestSessionId(event);\n const session = sessionId ?\n await this.sessionManager.findOneById(sessionId) :\n null;\n\n // a ticket references exactly one pending session — without it\n // (swept / expired mid-flight) the login can not complete.\n if (ticket && !session) {\n throw new UnauthorizedError();\n }\n\n const verified = await this.service.verify(\n identity.id,\n {\n kind: data.kind,\n response: data.response,\n },\n {\n ipAddress: getRequestIP(event) ?? null,\n userAgent: getRequestHeader(event, 'user-agent') ?? null,\n clientId: identity.clientId,\n sessionId: sessionId ?? null,\n ...(session ? {\n onVerified: async () => {\n await this.sessionManager.markMfaVerified(session);\n },\n } : {}),\n },\n );\n if (!verified) {\n throw new EntityCredentialsInvalidError();\n }\n\n // Ticket flow: the factor is verified and mfa_at stamped — complete\n // the login by minting the full grant for the pending session and\n // consuming the ticket (single use).\n if (ticket && session && this.mfaLoginService) {\n const token = await this.mfaLoginService.complete({\n session,\n ticket: ticket.payload,\n userName: identity.data.name,\n });\n\n return { verified: true, token };\n }\n\n return { verified: true };\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+DA,IAAaoB,mCAAb,MAAaA;CACCC;CAEAC;CAEAC;CAEV,YAAYC,KAA8C;EACtD,KAAKH,UAAUG,IAAIH;EACnB,KAAKC,iBAAiBE,IAAIF;EAC1B,KAAKC,kBAAkBC,IAAID;CAC/B;;;;;;IAQA,aAAuBG,OAAmC;EACtD,MAAMC,WAAWX,mBAAmBU,KAAAA;EACpC,IAAIC,UACA,OAAO;GAAEA;GAAUC,QAAQ;EAAK;EAGpC,MAAMA,SAASV,yBAAyBQ,KAAAA;EACxC,IAAIE,QACA,OAAO;GAAED,UAAUC,OAAOD;GAAUC;EAAO;EAG/C,MAAM,IAAIf,kBAAAA;CACd;CAEA,MACMgB,OACF,OAC2C;EAC3C,MAAM,EAAEF,UAAUC,WAAW,KAAKH,aAAaC,KAAAA;EAC/C,IAAIC,SAASG,SAASxB,aAAayB,MAC/B,OAAO;GACHC,UAAU;GACVC,oBAAoB;GACpBC,OAAO,CAAA;EACX;EAGJ,MAAML,SAAS,MAAM,KAAKR,QAAQc,UAAUR,SAASS,EAAE;EAcvD,MAAMC,UAAUT,SAAS,OAAO,MAAM,KAAKU,eAAeZ,KAAAA;EAC1D,IAAIW,WAAWA,QAAQE,eAAehC,kBAAkBiC,UAAU;GAC9D,MAAM,EAAEC,cAAc/B,gBAAgBgB,KAAAA;GACtC,MAAMgB,kBAAkB,OAAOD,cAAc,YACzCA,UAAUE,MAAM,GAAA,CAAA,CAAKC,SAASnC,iCAAiCoC,GAAG;GAOtE,OAAO;IACH,GAAGhB;IACHG,UAAUU,mBAAmBb,OAAOG;IACpCC,oBAAoB;GACxB;EACJ;EAEA,OAAOJ;CACX;CAEA,MAAgBS,eAAeZ,OAA4C;EACvE,MAAMoB,YAAY3B,oBAAoBO,KAAAA;EACtC,IAAI,CAACoB,WACD,OAAO;EAGX,OAAO,KAAKxB,eAAeyB,YAAYD,SAAAA;CAC3C;CAEA,MACME,KACF,MACA,OAC0B;EAC1B,MAAM,EAAErB,aAAa,KAAKF,aAAaC,KAAAA;EACvC,IAAIC,SAASG,SAASxB,aAAayB,MAC/B,MAAM,IAAIpB,gBAAgB,+CAAA;EAG9B,IACI,CAACsC,QACD,CAACC,OAAOC,OAAO3C,qBAAAA,CAAAA,CAAuBoC,SAASK,KAAKG,IAAI,GAExD,MAAM,IAAIzC,gBAAgB,0BAAA;EAG9B,MAAM,KAAKU,QAAQgC,cAAc1B,SAASS,IAAIa,KAAKG,MAAM,EAAEE,QAAQrC,iBAAiBS,KAAAA,EAAO,CAAA;EAI3F,OAAO,EAAE6B,SAAS,KAAK;CAC3B;CAEA,MACMC,OACF,MACA,OACiD;EACjD,MAAM,EAAE7B,UAAUC,WAAW,KAAKH,aAAaC,KAAAA;EAC/C,IAAIC,SAASG,SAASxB,aAAayB,MAC/B,MAAM,IAAIpB,gBAAgB,gDAAA;EAG9B,IACI,CAACsC,QACD,OAAOA,KAAKQ,aAAa,YACzB,CAACR,KAAKQ,YACN,CAACP,OAAOC,OAAO3C,qBAAAA,CAAAA,CAAuBoC,SAASK,KAAKG,IAAI,GAExD,MAAM,IAAIzC,gBAAgB,uCAAA;EAS9B,MAAMmC,YAAYlB,SACdA,OAAO8B,QAAQC,aACfxC,oBAAoBO,KAAAA;EACxB,MAAMW,UAAUS,YACZ,MAAM,KAAKxB,eAAeyB,YAAYD,SAAAA,IACtC;EAIJ,IAAIlB,UAAU,CAACS,SACX,MAAM,IAAIxB,kBAAAA;EAqBd,IAAI,CAAC+C,MAlBkB,KAAKvC,QAAQmC,OAChC7B,SAASS,IACT;GACIgB,MAAMH,KAAKG;GACXK,UAAUR,KAAKQ;EACnB,GACA;GACII,WAAW9C,aAAaW,KAAAA,KAAU;GAClCoC,WAAWhD,iBAAiBY,OAAO,YAAA,KAAiB;GACpDqC,UAAUpC,SAASoC;GACnBjB,WAAWA,aAAa;GACxB,GAAIT,UAAU,EACV2B,YAAY,YAAA;IACR,MAAM,KAAK1C,eAAe2C,gBAAgB5B,OAAAA;GAC9C,EACJ,IAAI,CAAC;EACT,CAAA,GAGA,MAAM,IAAIzB,8BAAAA;EAMd,IAAIgB,UAAUS,WAAW,KAAKd,iBAO1B,OAAO;GAAEqC,UAAU;GAAMM,OAAAA,MANL,KAAK3C,gBAAgB4C,SAAS;IAC9C9B;IACAT,QAAQA,OAAO8B;IACfU,UAAUzC,SAASsB,KAAKoB;GAC5B,CAAA;EAE+B;EAGnC,OAAO,EAAET,UAAU,KAAK;CAC5B;AACJ"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../../../../../../src/adapters/http/controllers/workflows/authorize/module.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../../../../../../src/adapters/http/controllers/workflows/authorize/module.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AAgBxC,OAAO,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAGlE,OAAO,KAAK,EAAE,uCAAuC,EAAE,MAAM,8BAA8B,CAAC;AAC5F,OAAO,EAAE,uCAAuC,EAAE,MAAM,8BAA8B,CAAC;AACvF,OAAO,KAAK,EAAE,0BAA0B,EAAE,0BAA0B,EAAE,MAAM,YAAY,CAAC;AAMzF,qBACa,mBAAmB;IAI5B,SAAS,CAAC,OAAO,EAAE,0BAA0B,CAAC;IAE9C,SAAS,CAAC,mBAAmB,EAAG,uCAAuC,CAAC;IAExE,SAAS,CAAC,oBAAoB,EAAG,uCAAuC,CAAC;IAEzE,SAAS,CAAC,UAAU,EAAE,oBAAoB,CAAC;gBAI/B,GAAG,EAAE,0BAA0B;IAyBrC,OAAO,CAAa,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IAmC/D,KAAK,CAAa,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC;CAmH7D"}
|
|
@@ -2,18 +2,19 @@ import "node:path";
|
|
|
2
2
|
import "node:url";
|
|
3
3
|
import.meta.url;
|
|
4
4
|
import { OAuth2AuthorizationCodeRequestValidator } from "../../../../../core/oauth2/authorization/code-request/validator.mjs";
|
|
5
|
+
import { sanitizeError } from "../../../../../utils/error.mjs";
|
|
6
|
+
import "../../../../../utils/index.mjs";
|
|
5
7
|
import "../../../../../core/index.mjs";
|
|
6
8
|
import { readFromLocations } from "../../../request/helpers/locations.mjs";
|
|
7
9
|
import "../../../request/index.mjs";
|
|
8
10
|
import { ForceUserLoggedInMiddleware } from "../../../middleware/built-in/loggedin/user.mjs";
|
|
9
11
|
import { renderAuthConsolePage } from "../../../ui/auth-console/module.mjs";
|
|
10
|
-
import { sanitizeError } from "../../../../../utils/error.mjs";
|
|
11
|
-
import "../../../../../utils/index.mjs";
|
|
12
12
|
import "../../../middleware/index.mjs";
|
|
13
13
|
import "../../../ui/index.mjs";
|
|
14
14
|
import { HTTPOAuth2Authorizer } from "../../../adapters/oauth2/authorization/module.mjs";
|
|
15
15
|
import "../../../adapters/index.mjs";
|
|
16
|
-
import {
|
|
16
|
+
import { isUUID } from "@authup/kit";
|
|
17
|
+
import { OAuth2AccessDeniedError, OAuth2ErrorCode, OAuth2LoginRequiredError, isOAuth2AccessDeniedError } from "@authup/specs";
|
|
17
18
|
import { URL } from "node:url";
|
|
18
19
|
import { useRequestQuery } from "@routup/basic/query";
|
|
19
20
|
import { DContext, DController, DGet, DPost } from "@routup/decorators";
|
|
@@ -106,15 +107,22 @@ var AuthorizeController = class {
|
|
|
106
107
|
};
|
|
107
108
|
}
|
|
108
109
|
if (!error) {
|
|
109
|
-
|
|
110
|
-
|
|
110
|
+
const query = useRequestQuery(event);
|
|
111
|
+
let mapped;
|
|
112
|
+
if (query.error === OAuth2ErrorCode.ACCESS_DENIED) mapped = OAuth2AccessDeniedError.forClient();
|
|
113
|
+
else if (query.error === OAuth2ErrorCode.LOGIN_REQUIRED) mapped = OAuth2LoginRequiredError.providerUnavailable();
|
|
114
|
+
if (mapped) {
|
|
115
|
+
const normalized = sanitizeError(mapped);
|
|
111
116
|
error = {
|
|
112
117
|
...normalized,
|
|
113
118
|
message: normalized.message
|
|
114
119
|
};
|
|
115
120
|
}
|
|
116
121
|
}
|
|
122
|
+
const query = useRequestQuery(event);
|
|
123
|
+
const federatedLogin = typeof query.provider === "string" && isUUID(query.provider) ? { providerId: query.provider } : void 0;
|
|
117
124
|
const requestURL = new URL(event.request.url);
|
|
125
|
+
requestURL.searchParams.delete("provider");
|
|
118
126
|
return renderAuthConsolePage(event, {
|
|
119
127
|
url: "/authorize",
|
|
120
128
|
payload: {
|
|
@@ -126,6 +134,7 @@ var AuthorizeController = class {
|
|
|
126
134
|
scopes,
|
|
127
135
|
realm,
|
|
128
136
|
redirectUriVerified,
|
|
137
|
+
federatedLogin,
|
|
129
138
|
features: this.options.features,
|
|
130
139
|
requestPath: `${requestURL.pathname}${requestURL.search}`
|
|
131
140
|
}
|