@authup/server-core 1.0.0-beta.62 → 1.0.0-beta.63

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. package/dist/adapters/database/domains/identity-provider-attribute/subscriber.d.ts +8 -1
  2. package/dist/adapters/database/domains/identity-provider-attribute/subscriber.d.ts.map +1 -1
  3. package/dist/adapters/database/domains/identity-provider-attribute/subscriber.mjs +14 -0
  4. package/dist/adapters/database/domains/identity-provider-attribute/subscriber.mjs.map +1 -1
  5. package/dist/adapters/http/adapters/oauth2/grant-types/refresh_token.d.ts.map +1 -1
  6. package/dist/adapters/http/adapters/oauth2/grant-types/refresh_token.mjs +8 -2
  7. package/dist/adapters/http/adapters/oauth2/grant-types/refresh_token.mjs.map +1 -1
  8. package/dist/adapters/http/controllers/entities/client/module.mjs +1 -1
  9. package/dist/adapters/http/controllers/entities/identity-provider/module.d.ts +23 -1
  10. package/dist/adapters/http/controllers/entities/identity-provider/module.d.ts.map +1 -1
  11. package/dist/adapters/http/controllers/entities/identity-provider/module.mjs +85 -46
  12. package/dist/adapters/http/controllers/entities/identity-provider/module.mjs.map +1 -1
  13. package/dist/adapters/http/controllers/workflows/authenticator-challenge/module.d.ts +2 -0
  14. package/dist/adapters/http/controllers/workflows/authenticator-challenge/module.d.ts.map +1 -1
  15. package/dist/adapters/http/controllers/workflows/authenticator-challenge/module.mjs +21 -3
  16. package/dist/adapters/http/controllers/workflows/authenticator-challenge/module.mjs.map +1 -1
  17. package/dist/adapters/http/controllers/workflows/authorize/module.d.ts.map +1 -1
  18. package/dist/adapters/http/controllers/workflows/authorize/module.mjs +5 -0
  19. package/dist/adapters/http/controllers/workflows/authorize/module.mjs.map +1 -1
  20. package/dist/adapters/http/controllers/workflows/token/module.d.ts.map +1 -1
  21. package/dist/adapters/http/controllers/workflows/token/module.mjs +22 -9
  22. package/dist/adapters/http/controllers/workflows/token/module.mjs.map +1 -1
  23. package/dist/adapters/http/middleware/built-in/cors.d.ts.map +1 -1
  24. package/dist/adapters/http/middleware/built-in/cors.mjs.map +1 -1
  25. package/dist/adapters/http/middleware/built-in/error.d.ts.map +1 -1
  26. package/dist/adapters/http/middleware/built-in/error.mjs +10 -1
  27. package/dist/adapters/http/middleware/built-in/error.mjs.map +1 -1
  28. package/dist/adapters/http/ui/console-packages/module.d.ts +1 -1
  29. package/dist/adapters/http/ui/console-packages/module.mjs +2 -2
  30. package/dist/adapters/http/ui/console-packages/module.mjs.map +1 -1
  31. package/dist/app/modules/config/normalize.d.ts.map +1 -1
  32. package/dist/app/modules/config/normalize.mjs +2 -2
  33. package/dist/app/modules/config/normalize.mjs.map +1 -1
  34. package/dist/app/modules/config/types.d.ts +8 -0
  35. package/dist/app/modules/config/types.d.ts.map +1 -1
  36. package/dist/app/modules/database/repositories/identity-provider/repository.d.ts +3 -1
  37. package/dist/app/modules/database/repositories/identity-provider/repository.d.ts.map +1 -1
  38. package/dist/app/modules/database/repositories/identity-provider/repository.mjs +2 -2
  39. package/dist/app/modules/database/repositories/identity-provider/repository.mjs.map +1 -1
  40. package/dist/app/modules/http/modules/controller.d.ts.map +1 -1
  41. package/dist/app/modules/http/modules/controller.mjs +5 -2
  42. package/dist/app/modules/http/modules/controller.mjs.map +1 -1
  43. package/dist/app/modules/oauth2/constants.d.ts +2 -1
  44. package/dist/app/modules/oauth2/constants.d.ts.map +1 -1
  45. package/dist/app/modules/oauth2/constants.mjs +1 -0
  46. package/dist/app/modules/oauth2/constants.mjs.map +1 -1
  47. package/dist/app/modules/oauth2/module.d.ts.map +1 -1
  48. package/dist/app/modules/oauth2/module.mjs +5 -0
  49. package/dist/app/modules/oauth2/module.mjs.map +1 -1
  50. package/dist/app/modules/oauth2/repositories/constants.d.ts +1 -0
  51. package/dist/app/modules/oauth2/repositories/constants.d.ts.map +1 -1
  52. package/dist/app/modules/oauth2/repositories/constants.mjs +1 -0
  53. package/dist/app/modules/oauth2/repositories/constants.mjs.map +1 -1
  54. package/dist/app/modules/oauth2/repositories/federated-login/index.d.ts +2 -0
  55. package/dist/app/modules/oauth2/repositories/federated-login/index.d.ts.map +1 -0
  56. package/dist/app/modules/oauth2/repositories/federated-login/index.mjs +5 -0
  57. package/dist/app/modules/oauth2/repositories/federated-login/store.d.ts +9 -0
  58. package/dist/app/modules/oauth2/repositories/federated-login/store.d.ts.map +1 -0
  59. package/dist/app/modules/oauth2/repositories/federated-login/store.mjs +33 -0
  60. package/dist/app/modules/oauth2/repositories/federated-login/store.mjs.map +1 -0
  61. package/dist/app/modules/oauth2/repositories/index.d.ts +1 -0
  62. package/dist/app/modules/oauth2/repositories/index.d.ts.map +1 -1
  63. package/dist/app/modules/oauth2/repositories/index.mjs +3 -1
  64. package/dist/core/entities/event/entity-event-handler.d.ts.map +1 -1
  65. package/dist/core/entities/event/entity-event-handler.mjs +14 -1
  66. package/dist/core/entities/event/entity-event-handler.mjs.map +1 -1
  67. package/dist/core/entities/identity-provider/types.d.ts +9 -1
  68. package/dist/core/entities/identity-provider/types.d.ts.map +1 -1
  69. package/dist/core/identity/index.mjs +2 -1
  70. package/dist/core/identity/provider/account/module.mjs +1 -1
  71. package/dist/core/identity/provider/authentication/index.mjs +2 -1
  72. package/dist/core/identity/provider/authentication/protocols/index.mjs +2 -1
  73. package/dist/core/identity/provider/authentication/protocols/oauth2/assurance.d.ts +46 -0
  74. package/dist/core/identity/provider/authentication/protocols/oauth2/assurance.d.ts.map +1 -0
  75. package/dist/core/identity/provider/authentication/protocols/oauth2/assurance.mjs +89 -0
  76. package/dist/core/identity/provider/authentication/protocols/oauth2/assurance.mjs.map +1 -0
  77. package/dist/core/identity/provider/authentication/protocols/oauth2/index.d.ts +1 -0
  78. package/dist/core/identity/provider/authentication/protocols/oauth2/index.d.ts.map +1 -1
  79. package/dist/core/identity/provider/authentication/protocols/oauth2/index.mjs +2 -1
  80. package/dist/core/identity/provider/authentication/protocols/oauth2/module.d.ts.map +1 -1
  81. package/dist/core/identity/provider/authentication/protocols/oauth2/module.mjs +9 -2
  82. package/dist/core/identity/provider/authentication/protocols/oauth2/module.mjs.map +1 -1
  83. package/dist/core/identity/provider/index.mjs +2 -1
  84. package/dist/core/index.mjs +4 -2
  85. package/dist/core/oauth2/authorization/helpers.d.ts.map +1 -1
  86. package/dist/core/oauth2/authorization/helpers.mjs +6 -1
  87. package/dist/core/oauth2/authorization/helpers.mjs.map +1 -1
  88. package/dist/core/oauth2/authorization/module.d.ts.map +1 -1
  89. package/dist/core/oauth2/authorization/module.mjs +5 -3
  90. package/dist/core/oauth2/authorization/module.mjs.map +1 -1
  91. package/dist/core/oauth2/authorization/state/types.d.ts +8 -0
  92. package/dist/core/oauth2/authorization/state/types.d.ts.map +1 -1
  93. package/dist/core/oauth2/federated-login/constants.d.ts +22 -0
  94. package/dist/core/oauth2/federated-login/constants.d.ts.map +1 -0
  95. package/dist/core/oauth2/federated-login/constants.mjs +27 -0
  96. package/dist/core/oauth2/federated-login/constants.mjs.map +1 -0
  97. package/dist/core/oauth2/federated-login/index.d.ts +1 -0
  98. package/dist/core/oauth2/federated-login/index.d.ts.map +1 -1
  99. package/dist/core/oauth2/federated-login/index.mjs +2 -1
  100. package/dist/core/oauth2/federated-login/module.d.ts +12 -3
  101. package/dist/core/oauth2/federated-login/module.d.ts.map +1 -1
  102. package/dist/core/oauth2/federated-login/module.mjs +96 -40
  103. package/dist/core/oauth2/federated-login/module.mjs.map +1 -1
  104. package/dist/core/oauth2/federated-login/types.d.ts +82 -24
  105. package/dist/core/oauth2/federated-login/types.d.ts.map +1 -1
  106. package/dist/core/oauth2/federated-login/types.mjs +5 -0
  107. package/dist/core/oauth2/federated-login/types.mjs.map +1 -1
  108. package/dist/core/oauth2/grant-types/authorize.mjs +1 -1
  109. package/dist/core/oauth2/grant-types/client-credentials.mjs +1 -1
  110. package/dist/core/oauth2/grant-types/identity.mjs +1 -1
  111. package/dist/core/oauth2/grant-types/password.mjs +1 -1
  112. package/dist/core/oauth2/grant-types/refresh-token.mjs +1 -1
  113. package/dist/core/oauth2/index.mjs +3 -2
  114. package/dist/core/oauth2/token/verifier/module.d.ts.map +1 -1
  115. package/dist/core/oauth2/token/verifier/module.mjs +3 -3
  116. package/dist/core/oauth2/token/verifier/module.mjs.map +1 -1
  117. package/dist/swagger.json +107 -59
  118. package/package.json +13 -13
@@ -113,8 +113,10 @@ import { toIdentityPolicyData } from "./identity/permission/identity-policy-data
113
113
  import { OAuth2Authorization } from "./oauth2/authorization/module.mjs";
114
114
  import { OAuth2EndSessionService } from "./oauth2/end-session/service.mjs";
115
115
  import { OAuth2EndSessionRequestValidator } from "./oauth2/end-session/validator.mjs";
116
+ import { OAUTH2_FEDERATED_LOGIN_COOKIE, OAUTH2_FEDERATED_LOGIN_TTL } from "./oauth2/federated-login/constants.mjs";
116
117
  import { IdentityProviderLdapAuthenticator } from "./identity/provider/authentication/protocols/ldap/module.mjs";
117
118
  import { IdentityProviderLdapCollectionAuthenticator } from "./identity/provider/authentication/protocols/ldap/collection.mjs";
119
+ import { IDENTITY_PROVIDER_ASSURANCE_ERROR_INSTANCE, IdentityProviderAssuranceError, assertIdentityProviderAssurance, buildIdentityProviderAcrValues, isIdentityProviderAssuranceError } from "./identity/provider/authentication/protocols/oauth2/assurance.mjs";
118
120
  import { IdentityProviderOAuth2Authenticator } from "./identity/provider/authentication/protocols/oauth2/module.mjs";
119
121
  import { IdentityProviderOpenIDAuthenticator } from "./identity/provider/authentication/protocols/open-id/module.mjs";
120
122
  import { IdentityProviderFacebookAuthenticator } from "./identity/provider/authentication/presets/facebook.mjs";
@@ -123,9 +125,9 @@ import { IdentityProviderGoogleAuthenticator } from "./identity/provider/authent
123
125
  import { IdentityProviderInstagramAuthenticator } from "./identity/provider/authentication/presets/instagram.mjs";
124
126
  import { IdentityProviderPaypalAuthenticator } from "./identity/provider/authentication/presets/paypal.mjs";
125
127
  import { createIdentityProviderOAuth2Authenticator } from "./identity/provider/authentication/factory.mjs";
128
+ import { buildOAuth2BearerTokenResponse } from "./oauth2/response/bearer.mjs";
126
129
  import { OAuth2FederatedLoginRefusal } from "./oauth2/federated-login/types.mjs";
127
130
  import { OAuth2FederatedLoginService } from "./oauth2/federated-login/module.mjs";
128
- import { buildOAuth2BearerTokenResponse } from "./oauth2/response/bearer.mjs";
129
131
  import { OAuth2AuthorizeGrant } from "./oauth2/grant-types/authorize.mjs";
130
132
  import { ClientCredentialsGrant } from "./oauth2/grant-types/client-credentials.mjs";
131
133
  import { IdentityGrantType } from "./oauth2/grant-types/identity.mjs";
@@ -190,4 +192,4 @@ import { UserProvisioningSynchronizer } from "./provisioning/synchronizer/user/m
190
192
  import { WildcardRealmProvisioner, expandWildcardRealmEntry, extractWildcardRealmEntry } from "./provisioning/wildcard/module.mjs";
191
193
  import "./provisioning/index.mjs";
192
194
  import "./security/index.mjs";
193
- export { BaseCredentialsAuthenticator, CLIENT_READ_PERMISSIONS, CONSENT_FILTER_KEYS, CONSENT_SCOPE_MAX_LENGTH, ClientAuthenticator, ClientCertificateValidator, ClientCredentialsGrant, ClientCredentialsService, ClientPermissionService, ClientProvisioningSynchronizer, ClientProvisioningValidator, ClientRoleService, ClientScopeService, ClientService, ConsentService, CredentialsAuthenticator, EVENT_ACTOR_NAME_MAX_LENGTH, EVENT_DIFF_SECRET_KEY_REGEX, EVENT_DIFF_VALUE_MAX_LENGTH, EVENT_LOG_RETENTION_DAYS_DEFAULT, EVENT_RETENTION_SWEEP_BATCH_SIZE, EntityEventHandler, EventService, GraphProvisioningSynchronizer, IDENTITY_PROVIDER_ACCOUNT_ALREADY_LINKED_ERROR_INSTANCE, IDENTITY_PROVIDER_ACCOUNT_FILTER_KEYS, IDENTITY_PROVIDER_ACCOUNT_LINK_TTL, IDENTITY_PROVIDER_ACCOUNT_UNLINK_BLOCKED_ERROR_INSTANCE, IDENTITY_PROVIDER_LDAP_COLLECTION_AUTHENTICATOR_TOKEN, IdentityGrantType, IdentityPermissionProvider, IdentityProviderAccountAlreadyLinkedError, IdentityProviderAccountBaseMapper, IdentityProviderAccountManager, IdentityProviderAccountService, IdentityProviderAccountUnlinkBlockedError, IdentityProviderAttributeMapper, IdentityProviderFacebookAuthenticator, IdentityProviderGithubAuthenticator, IdentityProviderGoogleAuthenticator, IdentityProviderInstagramAuthenticator, IdentityProviderLdapAuthenticator, IdentityProviderLdapCollectionAuthenticator, IdentityProviderMapperOperation, IdentityProviderOAuth2Authenticator, IdentityProviderOpenIDAuthenticator, IdentityProviderPaypalAuthenticator, IdentityProviderPermissionMapper, IdentityProviderRoleMapper, IdentityProviderRoleMappingService, IdentityResolver, IdentityRoleProvider, KEY_CERTIFICATE_ERROR_INSTANCE, KeyCertificateError, KeyProvisioner, KeyService, LoginThrottleService, MAIL_TEMPLATE_REGISTRY, MailTemplateName, MailTemplateRenderer, NoopAuthFlowMetrics, OAuth2AccessPolicyEvaluator, OAuth2AccessTokenIssuer, OAuth2Authorization, OAuth2AuthorizationCodeIssuer, OAuth2AuthorizationCodeRequestValidator, OAuth2AuthorizationCodeRequestVerifier, OAuth2AuthorizationCodeVerifier, OAuth2AuthorizationStateManager, OAuth2AuthorizeGrant, OAuth2ClientAuthenticator, OAuth2EndSessionRequestValidator, OAuth2EndSessionService, OAuth2FederatedLoginRefusal, OAuth2FederatedLoginService, OAuth2MfaLoginService, OAuth2MfaTokenIssuer, OAuth2OpenIDClaimsBuilder, OAuth2OpenIDTokenIssuer, OAuth2RefreshTokenGrant, OAuth2RefreshTokenIssuer, OAuth2ScopeAttributesResolver, OAuth2TokenRevoker, OAuth2TokenSigner, OAuth2TokenVerifier, PASSWORD_RESET_EXPIRES_IN_MINUTES, PasswordGrantType, PasswordRecoveryService, PermissionBindingPolicyEvaluator, PermissionCheckerService, PermissionPolicyService, PermissionProvisioningRelationsValidator, PermissionProvisioningSynchronizer, PermissionProvisioningValidator, PermissionService, PolicyCheckerService, PolicyEngine, PolicyProvisioningSynchronizer, PolicyProvisioningValidator, PolicyService, ProvisioningEntityResolver, ProvisioningEntityStrategyType, ProvisioningJunctionSynchronizer, ProvisioningStrategyValidator, REALM_CIPHER_BLOB_ERROR_INSTANCE, REALM_CIPHER_BLOB_VERSION, REALM_WILDCARD_NAME, RECORD_QUERY_PARAMETERS, RealmCipher, RealmCipherBlobError, RealmProvisioningSynchronizer, RealmProvisioningValidator, RealmService, RealmWildcardProvisioningValidator, RegistrationService, RoleAttributeService, RolePermissionService, RoleProvisioningSynchronizer, RoleProvisioningValidator, RoleService, RootProvisioningValidator, SESSION_FILTER_KEYS, SESSION_TOKEN_FILTER_KEYS, SESSION_TOKEN_RELATION_MISSING_ERROR_INSTANCE, SYSTEM_CLIENT_DEFINITIONS, SYSTEM_CLIENT_SCOPE_NAMES, ScopeProvisioningSynchronizer, ScopeProvisioningValidator, ScopeService, SessionManager, SessionService, SessionTokenRelationMissingError, SessionTokenService, SystemClientProvisioner, TrustAnchorService, USER_AUTHENTICATOR_ATTEMPT_CACHE_PREFIX, USER_AUTHENTICATOR_ATTEMPT_LOCK_FACTOR, USER_AUTHENTICATOR_ATTEMPT_LOCK_MAX, USER_AUTHENTICATOR_ATTEMPT_WINDOW, USER_AUTHENTICATOR_EMAIL_CODE_CACHE_PREFIX, USER_AUTHENTICATOR_EMAIL_CODE_EXPIRES_IN_MINUTES, USER_AUTHENTICATOR_EMAIL_CODE_LENGTH, USER_AUTHENTICATOR_EMAIL_SEND_CACHE_PREFIX, USER_AUTHENTICATOR_EMAIL_SEND_COOLDOWN, USER_AUTHENTICATOR_FILTER_KEYS, USER_AUTHENTICATOR_RECOVERY_CODE_COUNT, USER_AUTHENTICATOR_THROTTLE_CACHE_PREFIX, USER_AUTHENTICATOR_TOTP_ALGORITHM, USER_AUTHENTICATOR_TOTP_DIGITS, USER_AUTHENTICATOR_TOTP_PERIOD, USER_AUTHENTICATOR_VERIFY_LOCK_CACHE_PREFIX, USER_AUTHENTICATOR_VERIFY_LOCK_RENEW_INTERVAL, USER_AUTHENTICATOR_VERIFY_LOCK_TTL, USER_AUTHENTICATOR_WEBAUTHN_AUTH_CACHE_PREFIX, USER_AUTHENTICATOR_WEBAUTHN_CHALLENGE_WINDOW, USER_AUTHENTICATOR_WEBAUTHN_REG_CACHE_PREFIX, UserAttributeService, UserAuthenticator, UserAuthenticatorService, UserCredentialsService, UserPermissionService, UserProvisioningSynchronizer, UserProvisioningValidator, UserRoleService, UserService, WRAPPED_KEY_MATERIAL_PREFIX, WildcardRealmProvisioner, appendQueryConditions, applyJunctionCreateGrant, assertCertificateMatchesKey, assertClientCertificateEvidenceValidForBinding, assertClientGrantAllowed, base64URLEncode, buildCertificateJwkFields, buildClientCertificateThumbprint, buildEntityDiff, buildJunctionUpdateData, buildOAuth2BearerTokenResponse, buildOAuth2CodeChallenge, buildOAuth2TokenHash, buildProvisioningEntityKey, buildSystemClientAttributes, buildWebauthnAuthenticationOptions, buildWebauthnRegistrationOptions, buildX5c, buildX5tS256, clientPermissionSchema, clientRoleSchema, clientSchema, clientScopeSchema, consentSchema, createIdentityProviderOAuth2Authenticator, createProvisioningEntitiesValidator, createRelationsReadGate, decodeQuery, defineMailTemplate, deriveAmrAcr, describeQuerySchema, eventSchema, expandWildcardRealmEntry, extractWildcardRealmEntry, generateNumericCode, generateOAuth2CodeVerifier, generateRecoveryCode, guessUserAuthenticatorKindByResponse, identityProviderAccountSchema, identityProviderRoleMappingSchema, identityProviderSchema, isIdentityProviderAccountAlreadyLinkedError, isIdentityProviderAccountUnlinkBlockedError, isRealmCipherBlobError, isSessionTokenRelationMissingError, isWrappedKeyMaterial, keySchema, mergeProvisioningEntities, mergeProvisioningEntity, normalizeEntityProvisioningStrategy, parseCertificateChain, parseClientCertificateChain, permissionPolicySchema, permissionSchema, policySchema, queryCodec, realmSchema, roleAttributeSchema, rolePermissionSchema, roleSchema, sanitizeEventData, schemaRegistry, schemas, scopeSchema, sessionSchema, sessionTokenSchema, toIdentityPolicyData, trustAnchorSchema, unwrapKeyMaterial, userAttributeSchema, userAuthenticatorSchema, userPermissionSchema, userRoleSchema, userSchema, verifyWebauthnAuthentication, verifyWebauthnRegistration, wrapKeyMaterial };
195
+ export { BaseCredentialsAuthenticator, CLIENT_READ_PERMISSIONS, CONSENT_FILTER_KEYS, CONSENT_SCOPE_MAX_LENGTH, ClientAuthenticator, ClientCertificateValidator, ClientCredentialsGrant, ClientCredentialsService, ClientPermissionService, ClientProvisioningSynchronizer, ClientProvisioningValidator, ClientRoleService, ClientScopeService, ClientService, ConsentService, CredentialsAuthenticator, EVENT_ACTOR_NAME_MAX_LENGTH, EVENT_DIFF_SECRET_KEY_REGEX, EVENT_DIFF_VALUE_MAX_LENGTH, EVENT_LOG_RETENTION_DAYS_DEFAULT, EVENT_RETENTION_SWEEP_BATCH_SIZE, EntityEventHandler, EventService, GraphProvisioningSynchronizer, IDENTITY_PROVIDER_ACCOUNT_ALREADY_LINKED_ERROR_INSTANCE, IDENTITY_PROVIDER_ACCOUNT_FILTER_KEYS, IDENTITY_PROVIDER_ACCOUNT_LINK_TTL, IDENTITY_PROVIDER_ACCOUNT_UNLINK_BLOCKED_ERROR_INSTANCE, IDENTITY_PROVIDER_ASSURANCE_ERROR_INSTANCE, IDENTITY_PROVIDER_LDAP_COLLECTION_AUTHENTICATOR_TOKEN, IdentityGrantType, IdentityPermissionProvider, IdentityProviderAccountAlreadyLinkedError, IdentityProviderAccountBaseMapper, IdentityProviderAccountManager, IdentityProviderAccountService, IdentityProviderAccountUnlinkBlockedError, IdentityProviderAssuranceError, IdentityProviderAttributeMapper, IdentityProviderFacebookAuthenticator, IdentityProviderGithubAuthenticator, IdentityProviderGoogleAuthenticator, IdentityProviderInstagramAuthenticator, IdentityProviderLdapAuthenticator, IdentityProviderLdapCollectionAuthenticator, IdentityProviderMapperOperation, IdentityProviderOAuth2Authenticator, IdentityProviderOpenIDAuthenticator, IdentityProviderPaypalAuthenticator, IdentityProviderPermissionMapper, IdentityProviderRoleMapper, IdentityProviderRoleMappingService, IdentityResolver, IdentityRoleProvider, KEY_CERTIFICATE_ERROR_INSTANCE, KeyCertificateError, KeyProvisioner, KeyService, LoginThrottleService, MAIL_TEMPLATE_REGISTRY, MailTemplateName, MailTemplateRenderer, NoopAuthFlowMetrics, OAUTH2_FEDERATED_LOGIN_COOKIE, OAUTH2_FEDERATED_LOGIN_TTL, OAuth2AccessPolicyEvaluator, OAuth2AccessTokenIssuer, OAuth2Authorization, OAuth2AuthorizationCodeIssuer, OAuth2AuthorizationCodeRequestValidator, OAuth2AuthorizationCodeRequestVerifier, OAuth2AuthorizationCodeVerifier, OAuth2AuthorizationStateManager, OAuth2AuthorizeGrant, OAuth2ClientAuthenticator, OAuth2EndSessionRequestValidator, OAuth2EndSessionService, OAuth2FederatedLoginRefusal, OAuth2FederatedLoginService, OAuth2MfaLoginService, OAuth2MfaTokenIssuer, OAuth2OpenIDClaimsBuilder, OAuth2OpenIDTokenIssuer, OAuth2RefreshTokenGrant, OAuth2RefreshTokenIssuer, OAuth2ScopeAttributesResolver, OAuth2TokenRevoker, OAuth2TokenSigner, OAuth2TokenVerifier, PASSWORD_RESET_EXPIRES_IN_MINUTES, PasswordGrantType, PasswordRecoveryService, PermissionBindingPolicyEvaluator, PermissionCheckerService, PermissionPolicyService, PermissionProvisioningRelationsValidator, PermissionProvisioningSynchronizer, PermissionProvisioningValidator, PermissionService, PolicyCheckerService, PolicyEngine, PolicyProvisioningSynchronizer, PolicyProvisioningValidator, PolicyService, ProvisioningEntityResolver, ProvisioningEntityStrategyType, ProvisioningJunctionSynchronizer, ProvisioningStrategyValidator, REALM_CIPHER_BLOB_ERROR_INSTANCE, REALM_CIPHER_BLOB_VERSION, REALM_WILDCARD_NAME, RECORD_QUERY_PARAMETERS, RealmCipher, RealmCipherBlobError, RealmProvisioningSynchronizer, RealmProvisioningValidator, RealmService, RealmWildcardProvisioningValidator, RegistrationService, RoleAttributeService, RolePermissionService, RoleProvisioningSynchronizer, RoleProvisioningValidator, RoleService, RootProvisioningValidator, SESSION_FILTER_KEYS, SESSION_TOKEN_FILTER_KEYS, SESSION_TOKEN_RELATION_MISSING_ERROR_INSTANCE, SYSTEM_CLIENT_DEFINITIONS, SYSTEM_CLIENT_SCOPE_NAMES, ScopeProvisioningSynchronizer, ScopeProvisioningValidator, ScopeService, SessionManager, SessionService, SessionTokenRelationMissingError, SessionTokenService, SystemClientProvisioner, TrustAnchorService, USER_AUTHENTICATOR_ATTEMPT_CACHE_PREFIX, USER_AUTHENTICATOR_ATTEMPT_LOCK_FACTOR, USER_AUTHENTICATOR_ATTEMPT_LOCK_MAX, USER_AUTHENTICATOR_ATTEMPT_WINDOW, USER_AUTHENTICATOR_EMAIL_CODE_CACHE_PREFIX, USER_AUTHENTICATOR_EMAIL_CODE_EXPIRES_IN_MINUTES, USER_AUTHENTICATOR_EMAIL_CODE_LENGTH, USER_AUTHENTICATOR_EMAIL_SEND_CACHE_PREFIX, USER_AUTHENTICATOR_EMAIL_SEND_COOLDOWN, USER_AUTHENTICATOR_FILTER_KEYS, USER_AUTHENTICATOR_RECOVERY_CODE_COUNT, USER_AUTHENTICATOR_THROTTLE_CACHE_PREFIX, USER_AUTHENTICATOR_TOTP_ALGORITHM, USER_AUTHENTICATOR_TOTP_DIGITS, USER_AUTHENTICATOR_TOTP_PERIOD, USER_AUTHENTICATOR_VERIFY_LOCK_CACHE_PREFIX, USER_AUTHENTICATOR_VERIFY_LOCK_RENEW_INTERVAL, USER_AUTHENTICATOR_VERIFY_LOCK_TTL, USER_AUTHENTICATOR_WEBAUTHN_AUTH_CACHE_PREFIX, USER_AUTHENTICATOR_WEBAUTHN_CHALLENGE_WINDOW, USER_AUTHENTICATOR_WEBAUTHN_REG_CACHE_PREFIX, UserAttributeService, UserAuthenticator, UserAuthenticatorService, UserCredentialsService, UserPermissionService, UserProvisioningSynchronizer, UserProvisioningValidator, UserRoleService, UserService, WRAPPED_KEY_MATERIAL_PREFIX, WildcardRealmProvisioner, appendQueryConditions, applyJunctionCreateGrant, assertCertificateMatchesKey, assertClientCertificateEvidenceValidForBinding, assertClientGrantAllowed, assertIdentityProviderAssurance, base64URLEncode, buildCertificateJwkFields, buildClientCertificateThumbprint, buildEntityDiff, buildIdentityProviderAcrValues, buildJunctionUpdateData, buildOAuth2BearerTokenResponse, buildOAuth2CodeChallenge, buildOAuth2TokenHash, buildProvisioningEntityKey, buildSystemClientAttributes, buildWebauthnAuthenticationOptions, buildWebauthnRegistrationOptions, buildX5c, buildX5tS256, clientPermissionSchema, clientRoleSchema, clientSchema, clientScopeSchema, consentSchema, createIdentityProviderOAuth2Authenticator, createProvisioningEntitiesValidator, createRelationsReadGate, decodeQuery, defineMailTemplate, deriveAmrAcr, describeQuerySchema, eventSchema, expandWildcardRealmEntry, extractWildcardRealmEntry, generateNumericCode, generateOAuth2CodeVerifier, generateRecoveryCode, guessUserAuthenticatorKindByResponse, identityProviderAccountSchema, identityProviderRoleMappingSchema, identityProviderSchema, isIdentityProviderAccountAlreadyLinkedError, isIdentityProviderAccountUnlinkBlockedError, isIdentityProviderAssuranceError, isRealmCipherBlobError, isSessionTokenRelationMissingError, isWrappedKeyMaterial, keySchema, mergeProvisioningEntities, mergeProvisioningEntity, normalizeEntityProvisioningStrategy, parseCertificateChain, parseClientCertificateChain, permissionPolicySchema, permissionSchema, policySchema, queryCodec, realmSchema, roleAttributeSchema, rolePermissionSchema, roleSchema, sanitizeEventData, schemaRegistry, schemas, scopeSchema, sessionSchema, sessionTokenSchema, toIdentityPolicyData, trustAnchorSchema, unwrapKeyMaterial, userAttributeSchema, userAuthenticatorSchema, userPermissionSchema, userRoleSchema, userSchema, verifyWebauthnAuthentication, verifyWebauthnRegistration, wrapKeyMaterial };
@@ -1 +1 @@
1
- {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../../src/core/oauth2/authorization/helpers.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAMtE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAIhD;;;;;;GAMG;AACH,wBAAgB,YAAY,CACxB,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,GAAG,IAAI,GACtD,IAAI,CAAC,kBAAkB,EAAE,KAAK,GAAG,KAAK,CAAC,CAiCzC;AAED,wBAAgB,0BAA0B,WAazC;AAED,wBAAgB,eAAe,CAAC,WAAW,EAAE,WAAW,UAWvD;AAWD,wBAAsB,oBAAoB,CACtC,KAAK,EAAE,MAAM,EACb,GAAG,CAAC,EAAE,GAAG,YAAY,EAAE,GAAG,IAAI,GAC9B,OAAO,CAAC,MAAM,CAAC,CAkBlB;AAED,wBAAsB,wBAAwB,CAAC,YAAY,EAAE,MAAM,mBAMlE"}
1
+ {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../../src/core/oauth2/authorization/helpers.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAMtE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAIhD;;;;;;GAMG;AACH,wBAAgB,YAAY,CACxB,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,GAAG,IAAI,GACtD,IAAI,CAAC,kBAAkB,EAAE,KAAK,GAAG,KAAK,CAAC,CA0CzC;AAED,wBAAgB,0BAA0B,WAazC;AAED,wBAAgB,eAAe,CAAC,WAAW,EAAE,WAAW,UAWvD;AAWD,wBAAsB,oBAAoB,CACtC,KAAK,EAAE,MAAM,EACb,GAAG,CAAC,EAAE,GAAG,YAAY,EAAE,GAAG,IAAI,GAC9B,OAAO,CAAC,MAAM,CAAC,CAkBlB;AAED,wBAAsB,wBAAwB,CAAC,YAAY,EAAE,MAAM,mBAMlE"}
@@ -25,9 +25,14 @@ import { subtle } from "uncrypto";
25
25
  default: return {};
26
26
  }
27
27
  if (session.mfaAt) amr.push(OAuth2AuthenticationMethodReference.OTP);
28
+ if (session.mfaAt) return {
29
+ amr,
30
+ acr: OAuth2AuthenticationContextClass.MFA
31
+ };
32
+ if (session.authMethod === SessionAuthMethod.EXTERNAL) return { amr };
28
33
  return {
29
34
  amr,
30
- acr: session.mfaAt ? OAuth2AuthenticationContextClass.MFA : OAuth2AuthenticationContextClass.PASSWORD
35
+ acr: OAuth2AuthenticationContextClass.PASSWORD
31
36
  };
32
37
  }
33
38
  function generateOAuth2CodeVerifier() {
@@ -1 +1 @@
1
- {"version":3,"file":"helpers.mjs","names":["JWTError","OAuth2AuthenticationContextClass","OAuth2AuthenticationMethodReference","SessionAuthMethod","subtle","deriveAmrAcr","session","authMethod","amr","PASSWORD","LDAP","push","EXTERNAL","mfaAt","OTP","acr","MFA","generateOAuth2CodeVerifier","length","charset","codeVerifier","randomValues","Uint8Array","crypto","getRandomValues","i","base64URLEncode","arrayBuffer","charCode","Array","from","btoa","String","fromCharCode","apply","replace","TOKEN_HASH_DIGESTS","name","halfLength","buildOAuth2TokenHash","value","alg","digest","slice","headerPropertyInvalid","encoder","TextEncoder","data","encode","hashBuffer","halfHash","buildOAuth2CodeChallenge"],"sources":["../../../../src/core/oauth2/authorization/helpers.ts"],"sourcesContent":["/*\n * Copyright (c) 2024.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport type { JWTAlgorithm, OAuth2TokenPayload } from '@authup/specs';\nimport {\n JWTError,\n OAuth2AuthenticationContextClass,\n OAuth2AuthenticationMethodReference,\n} from '@authup/specs';\nimport type { Session } from '@authup/core-kit';\nimport { SessionAuthMethod } from '@authup/core-kit';\nimport { subtle } from 'uncrypto';\n\n/**\n * Derive the OIDC `amr` / `acr` claims from the backing session's\n * authMethod + mfaAt (plan 050). Pre-column sessions (null authMethod)\n * yield no claims — authup cannot retroactively know how an old session\n * authenticated. M2M methods yield none either (amr/acr are user-flow\n * claims; those grants mint no id_token).\n */\nexport function deriveAmrAcr(\n session: Pick<Session, 'authMethod' | 'mfaAt'> | null,\n): Pick<OAuth2TokenPayload, 'amr' | 'acr'> {\n if (!session || !session.authMethod) {\n return {};\n }\n\n const amr : string[] = [];\n switch (session.authMethod) {\n case SessionAuthMethod.PASSWORD:\n case SessionAuthMethod.LDAP: {\n // LDAP is still a password factor for amr — the finer\n // distinction lives in the authup-local authMethod.\n amr.push(OAuth2AuthenticationMethodReference.PASSWORD);\n break;\n }\n case SessionAuthMethod.EXTERNAL: {\n amr.push(OAuth2AuthenticationMethodReference.EXTERNAL);\n break;\n }\n default: {\n return {};\n }\n }\n\n if (session.mfaAt) {\n amr.push(OAuth2AuthenticationMethodReference.OTP);\n }\n\n return {\n amr,\n acr: session.mfaAt ?\n OAuth2AuthenticationContextClass.MFA :\n OAuth2AuthenticationContextClass.PASSWORD,\n };\n}\n\nexport function generateOAuth2CodeVerifier() {\n const length = 64;\n const charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~';\n\n let codeVerifier = '';\n const randomValues = new Uint8Array(length);\n crypto.getRandomValues(randomValues);\n\n for (let i = 0; i < length; i++) {\n codeVerifier += charset[randomValues[i] % charset.length];\n }\n\n return codeVerifier;\n}\n\nexport function base64URLEncode(arrayBuffer: ArrayBuffer) {\n // Convert the ArrayBuffer to string using Uint8 array.\n // btoa takes chars from 0-255 and base64 encodes.\n // Then convert the base64 encoded to base64url encoded.\n // (replace + with -, replace / with _, trim trailing =)\n const charCode = Array.from(new Uint8Array(arrayBuffer));\n\n return btoa(String.fromCharCode.apply(null, charCode))\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=+$/, '');\n}\n\n// OIDC Core §3.1.3.6: at_hash / c_hash use the hash algorithm of the id_token's\n// JWS alg (…S256 → SHA-256, …S384 → SHA-384, …S512 → SHA-512) and keep the LEFT\n// HALF of the digest — so the half-length follows the digest (16/24/32 bytes).\nconst TOKEN_HASH_DIGESTS : Record<string, { name: string, halfLength: number }> = {\n 256: { name: 'SHA-256', halfLength: 16 },\n 384: { name: 'SHA-384', halfLength: 24 },\n 512: { name: 'SHA-512', halfLength: 32 },\n};\n\nexport async function buildOAuth2TokenHash(\n value: string,\n alg?: `${JWTAlgorithm}` | null,\n) : Promise<string> {\n // A key row without a persisted alg (nullable legacy column) signs with\n // the signer's *256 default (RS256/ES256/HS256) — derive the matching\n // SHA-256 digest. An unrecognized alg still fails loud — silently\n // defaulting there would mint a hash no conforming verifier accepts.\n const digest = alg ?\n TOKEN_HASH_DIGESTS[alg.slice(-3)] :\n TOKEN_HASH_DIGESTS['256'];\n if (!digest) {\n throw JWTError.headerPropertyInvalid('alg');\n }\n\n const encoder = new TextEncoder();\n const data = encoder.encode(value);\n const hashBuffer = await subtle.digest(digest.name, data);\n const halfHash = hashBuffer.slice(0, digest.halfLength);\n\n return base64URLEncode(halfHash);\n}\n\nexport async function buildOAuth2CodeChallenge(codeVerifier: string) {\n const encoder = new TextEncoder();\n const data = encoder.encode(codeVerifier);\n const arrayBuffer = await subtle.digest('SHA-256', data);\n\n return base64URLEncode(arrayBuffer);\n}\n"],"mappings":";;;;;;;;;;;;;GAwBA,SAAgBK,aACZC,SAAqD;CAErD,IAAI,CAACA,WAAW,CAACA,QAAQC,YACrB,OAAO,CAAC;CAGZ,MAAMC,MAAiB,CAAA;CACvB,QAAQF,QAAQC,YAAhB;EACI,KAAKJ,kBAAkBM;EACvB,KAAKN,kBAAkBO;GAGnBF,IAAIG,KAAKT,oCAAoCO,QAAQ;GACrD;EAEJ,KAAKN,kBAAkBS;GACnBJ,IAAIG,KAAKT,oCAAoCU,QAAQ;GACrD;EAEJ,SACI,OAAO,CAAC;CAEhB;CAEA,IAAIN,QAAQO,OACRL,IAAIG,KAAKT,oCAAoCY,GAAG;CAGpD,OAAO;EACHN;EACAO,KAAKT,QAAQO,QACTZ,iCAAiCe,MACjCf,iCAAiCQ;CACzC;AACJ;AAEA,SAAgBQ,6BAAAA;CACZ,MAAMC,SAAS;CACf,MAAMC,UAAU;CAEhB,IAAIC,eAAe;CACnB,MAAMC,eAAe,IAAIC,WAAWJ,MAAAA;CACpCK,OAAOC,gBAAgBH,YAAAA;CAEvB,KAAK,IAAII,IAAI,GAAGA,IAAIP,QAAQO,KACxBL,gBAAgBD,QAAQE,aAAaI,KAAKN;CAG9C,OAAOC;AACX;AAEA,SAAgBM,gBAAgBC,aAAwB;CAKpD,MAAMC,WAAWC,MAAMC,KAAK,IAAIR,WAAWK,WAAAA,CAAAA;CAE3C,OAAOI,KAAKC,OAAOC,aAAaC,MAAM,MAAMN,QAAAA,CAAAA,CAAAA,CACvCO,QAAQ,OAAO,GAAA,CAAA,CACfA,QAAQ,OAAO,GAAA,CAAA,CACfA,QAAQ,OAAO,EAAA;AACxB;AAKA,MAAMC,qBAA4E;CAC9E,KAAK;EAAEC,MAAM;EAAWC,YAAY;CAAG;CACvC,KAAK;EAAED,MAAM;EAAWC,YAAY;CAAG;CACvC,KAAK;EAAED,MAAM;EAAWC,YAAY;CAAG;AAC3C;AAEA,eAAsBC,qBAClBC,OACAC,KAA8B;CAM9B,MAAMC,SAASD,MACXL,mBAAmBK,IAAIE,MAAM,EAAC,KAC9BP,mBAAmB;CACvB,IAAI,CAACM,QACD,MAAM1C,SAAS4C,sBAAsB,KAAA;CAIzC,MAAMG,OAAOF,IADOC,YACPD,CAAAA,CAAQG,OAAOR,KAAAA;CAI5B,OAAOd,iBAFUuB,MADQ7C,OAAOsC,OAAOA,OAAOL,MAAMU,IAAAA,EAAAA,CACxBJ,MAAM,GAAGD,OAAOJ,UAErBY,CAAAA;AAC3B;AAEA,eAAsBC,yBAAyB/B,cAAoB;CAE/D,MAAM2B,OAAOF,IADOC,YACPD,CAAAA,CAAQG,OAAO5B,YAAAA;CAG5B,OAAOM,gBAAgBC,MAFGvB,OAAOsC,OAAO,WAAWK,IAAAA,CAE5BpB;AAC3B"}
1
+ {"version":3,"file":"helpers.mjs","names":["JWTError","OAuth2AuthenticationContextClass","OAuth2AuthenticationMethodReference","SessionAuthMethod","subtle","deriveAmrAcr","session","authMethod","amr","PASSWORD","LDAP","push","EXTERNAL","mfaAt","OTP","acr","MFA","generateOAuth2CodeVerifier","length","charset","codeVerifier","randomValues","Uint8Array","crypto","getRandomValues","i","base64URLEncode","arrayBuffer","charCode","Array","from","btoa","String","fromCharCode","apply","replace","TOKEN_HASH_DIGESTS","name","halfLength","buildOAuth2TokenHash","value","alg","digest","slice","headerPropertyInvalid","encoder","TextEncoder","data","encode","hashBuffer","halfHash","buildOAuth2CodeChallenge"],"sources":["../../../../src/core/oauth2/authorization/helpers.ts"],"sourcesContent":["/*\n * Copyright (c) 2024.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport type { JWTAlgorithm, OAuth2TokenPayload } from '@authup/specs';\nimport {\n JWTError,\n OAuth2AuthenticationContextClass,\n OAuth2AuthenticationMethodReference,\n} from '@authup/specs';\nimport type { Session } from '@authup/core-kit';\nimport { SessionAuthMethod } from '@authup/core-kit';\nimport { subtle } from 'uncrypto';\n\n/**\n * Derive the OIDC `amr` / `acr` claims from the backing session's\n * authMethod + mfaAt (plan 050). Pre-column sessions (null authMethod)\n * yield no claims — authup cannot retroactively know how an old session\n * authenticated. M2M methods yield none either (amr/acr are user-flow\n * claims; those grants mint no id_token).\n */\nexport function deriveAmrAcr(\n session: Pick<Session, 'authMethod' | 'mfaAt'> | null,\n): Pick<OAuth2TokenPayload, 'amr' | 'acr'> {\n if (!session || !session.authMethod) {\n return {};\n }\n\n const amr : string[] = [];\n switch (session.authMethod) {\n case SessionAuthMethod.PASSWORD:\n case SessionAuthMethod.LDAP: {\n // LDAP is still a password factor for amr — the finer\n // distinction lives in the authup-local authMethod.\n amr.push(OAuth2AuthenticationMethodReference.PASSWORD);\n break;\n }\n case SessionAuthMethod.EXTERNAL: {\n amr.push(OAuth2AuthenticationMethodReference.EXTERNAL);\n break;\n }\n default: {\n return {};\n }\n }\n\n if (session.mfaAt) {\n amr.push(OAuth2AuthenticationMethodReference.OTP);\n }\n\n if (session.mfaAt) {\n return { amr, acr: OAuth2AuthenticationContextClass.MFA };\n }\n\n // No `acr` for a session an external provider authenticated: authup\n // verified no credential of its own, so it asserts no assurance level.\n // `urn:authup:pwd` would say the subject authenticated with a password,\n // which is exactly what did NOT happen (issue #3478). Per OIDC Core\n // §2 the claim is voluntary, so the absence is the honest answer, and\n // `amr: ['ext']` still says how the login happened.\n if (session.authMethod === SessionAuthMethod.EXTERNAL) {\n return { amr };\n }\n\n return { amr, acr: OAuth2AuthenticationContextClass.PASSWORD };\n}\n\nexport function generateOAuth2CodeVerifier() {\n const length = 64;\n const charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~';\n\n let codeVerifier = '';\n const randomValues = new Uint8Array(length);\n crypto.getRandomValues(randomValues);\n\n for (let i = 0; i < length; i++) {\n codeVerifier += charset[randomValues[i] % charset.length];\n }\n\n return codeVerifier;\n}\n\nexport function base64URLEncode(arrayBuffer: ArrayBuffer) {\n // Convert the ArrayBuffer to string using Uint8 array.\n // btoa takes chars from 0-255 and base64 encodes.\n // Then convert the base64 encoded to base64url encoded.\n // (replace + with -, replace / with _, trim trailing =)\n const charCode = Array.from(new Uint8Array(arrayBuffer));\n\n return btoa(String.fromCharCode.apply(null, charCode))\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=+$/, '');\n}\n\n// OIDC Core §3.1.3.6: at_hash / c_hash use the hash algorithm of the id_token's\n// JWS alg (…S256 → SHA-256, …S384 → SHA-384, …S512 → SHA-512) and keep the LEFT\n// HALF of the digest — so the half-length follows the digest (16/24/32 bytes).\nconst TOKEN_HASH_DIGESTS : Record<string, { name: string, halfLength: number }> = {\n 256: { name: 'SHA-256', halfLength: 16 },\n 384: { name: 'SHA-384', halfLength: 24 },\n 512: { name: 'SHA-512', halfLength: 32 },\n};\n\nexport async function buildOAuth2TokenHash(\n value: string,\n alg?: `${JWTAlgorithm}` | null,\n) : Promise<string> {\n // A key row without a persisted alg (nullable legacy column) signs with\n // the signer's *256 default (RS256/ES256/HS256) — derive the matching\n // SHA-256 digest. An unrecognized alg still fails loud — silently\n // defaulting there would mint a hash no conforming verifier accepts.\n const digest = alg ?\n TOKEN_HASH_DIGESTS[alg.slice(-3)] :\n TOKEN_HASH_DIGESTS['256'];\n if (!digest) {\n throw JWTError.headerPropertyInvalid('alg');\n }\n\n const encoder = new TextEncoder();\n const data = encoder.encode(value);\n const hashBuffer = await subtle.digest(digest.name, data);\n const halfHash = hashBuffer.slice(0, digest.halfLength);\n\n return base64URLEncode(halfHash);\n}\n\nexport async function buildOAuth2CodeChallenge(codeVerifier: string) {\n const encoder = new TextEncoder();\n const data = encoder.encode(codeVerifier);\n const arrayBuffer = await subtle.digest('SHA-256', data);\n\n return base64URLEncode(arrayBuffer);\n}\n"],"mappings":";;;;;;;;;;;;;GAwBA,SAAgBK,aACZC,SAAqD;CAErD,IAAI,CAACA,WAAW,CAACA,QAAQC,YACrB,OAAO,CAAC;CAGZ,MAAMC,MAAiB,CAAA;CACvB,QAAQF,QAAQC,YAAhB;EACI,KAAKJ,kBAAkBM;EACvB,KAAKN,kBAAkBO;GAGnBF,IAAIG,KAAKT,oCAAoCO,QAAQ;GACrD;EAEJ,KAAKN,kBAAkBS;GACnBJ,IAAIG,KAAKT,oCAAoCU,QAAQ;GACrD;EAEJ,SACI,OAAO,CAAC;CAEhB;CAEA,IAAIN,QAAQO,OACRL,IAAIG,KAAKT,oCAAoCY,GAAG;CAGpD,IAAIR,QAAQO,OACR,OAAO;EAAEL;EAAKO,KAAKd,iCAAiCe;CAAI;CAS5D,IAAIV,QAAQC,eAAeJ,kBAAkBS,UACzC,OAAO,EAAEJ,IAAI;CAGjB,OAAO;EAAEA;EAAKO,KAAKd,iCAAiCQ;CAAS;AACjE;AAEA,SAAgBQ,6BAAAA;CACZ,MAAMC,SAAS;CACf,MAAMC,UAAU;CAEhB,IAAIC,eAAe;CACnB,MAAMC,eAAe,IAAIC,WAAWJ,MAAAA;CACpCK,OAAOC,gBAAgBH,YAAAA;CAEvB,KAAK,IAAII,IAAI,GAAGA,IAAIP,QAAQO,KACxBL,gBAAgBD,QAAQE,aAAaI,KAAKN;CAG9C,OAAOC;AACX;AAEA,SAAgBM,gBAAgBC,aAAwB;CAKpD,MAAMC,WAAWC,MAAMC,KAAK,IAAIR,WAAWK,WAAAA,CAAAA;CAE3C,OAAOI,KAAKC,OAAOC,aAAaC,MAAM,MAAMN,QAAAA,CAAAA,CAAAA,CACvCO,QAAQ,OAAO,GAAA,CAAA,CACfA,QAAQ,OAAO,GAAA,CAAA,CACfA,QAAQ,OAAO,EAAA;AACxB;AAKA,MAAMC,qBAA4E;CAC9E,KAAK;EAAEC,MAAM;EAAWC,YAAY;CAAG;CACvC,KAAK;EAAED,MAAM;EAAWC,YAAY;CAAG;CACvC,KAAK;EAAED,MAAM;EAAWC,YAAY;CAAG;AAC3C;AAEA,eAAsBC,qBAClBC,OACAC,KAA8B;CAM9B,MAAMC,SAASD,MACXL,mBAAmBK,IAAIE,MAAM,EAAC,KAC9BP,mBAAmB;CACvB,IAAI,CAACM,QACD,MAAM1C,SAAS4C,sBAAsB,KAAA;CAIzC,MAAMG,OAAOF,IADOC,YACPD,CAAAA,CAAQG,OAAOR,KAAAA;CAI5B,OAAOd,iBAFUuB,MADQ7C,OAAOsC,OAAOA,OAAOL,MAAMU,IAAAA,EAAAA,CACxBJ,MAAM,GAAGD,OAAOJ,UAErBY,CAAAA;AAC3B;AAEA,eAAsBC,yBAAyB/B,cAAoB;CAE/D,MAAM2B,OAAOF,IADOC,YACPD,CAAAA,CAAQG,OAAO5B,YAAAA;CAG5B,OAAOM,gBAAgBC,MAFGvB,OAAOsC,OAAO,WAAWK,IAAAA,CAE5BpB;AAC3B"}
@@ -1 +1 @@
1
- {"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../../../../src/core/oauth2/authorization/module.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EACR,QAAQ,EAER,8BAA8B,EAEjC,MAAM,kBAAkB,CAAC;AAsB1B,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,iBAAiB,CAAC;AACtE,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,2BAA2B,CAAC;AAE9E,OAAO,KAAK,EACR,iCAAiC,EACjC,0BAA0B,EAC1B,yBAAyB,EAC5B,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,KAAK,EAAE,aAAa,EAAE,mCAAmC,EAAE,MAAM,yBAAyB,CAAC;AAClG,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAS/D,qBAAa,mBAAmB;IAC5B,SAAS,CAAC,UAAU,EAAG,8BAA8B,CAAC;IAEtD,SAAS,CAAC,cAAc,EAAG,eAAe,CAAC;IAE3C,SAAS,CAAC,YAAY,CAAC,EAAG,aAAa,CAAC;IAExC,SAAS,CAAC,OAAO,CAAC,EAAG,gBAAgB,CAAC;IAEtC,SAAS,CAAC,iBAAiB,EAAG,MAAM,CAAC;IAErC,SAAS,CAAC,kBAAkB,EAAG,MAAM,CAAC;IAEtC,SAAS,CAAC,oBAAoB,CAAC,EAAG,mCAAmC,CAAC;IAEtE,SAAS,CAAC,qBAAqB,CAAC,EAAG,4BAA4B,CAAC;gBAEpD,GAAG,EAAE,iCAAiC;IAWlD;;;;;;OAMG;IACG,SAAS,CACX,IAAI,EAAE,8BAA8B,EACpC,QAAQ,EAAE,QAAQ,EAClB,OAAO,GAAE,0BAA+B,GACxC,OAAO,CAAC,yBAAyB,CAAC;cAmDtB,cAAc,CAC1B,IAAI,EAAE,8BAA8B,EACpC,QAAQ,EAAE,QAAQ,EAClB,OAAO,GAAE,0BAA+B,GACxC,OAAO,CAAC,yBAAyB,CAAC;CA4KzC"}
1
+ {"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../../../../src/core/oauth2/authorization/module.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EACR,QAAQ,EAER,8BAA8B,EAEjC,MAAM,kBAAkB,CAAC;AAuB1B,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,iBAAiB,CAAC;AACtE,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,2BAA2B,CAAC;AAE9E,OAAO,KAAK,EACR,iCAAiC,EACjC,0BAA0B,EAC1B,yBAAyB,EAC5B,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,KAAK,EAAE,aAAa,EAAE,mCAAmC,EAAE,MAAM,yBAAyB,CAAC;AAClG,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAS/D,qBAAa,mBAAmB;IAC5B,SAAS,CAAC,UAAU,EAAG,8BAA8B,CAAC;IAEtD,SAAS,CAAC,cAAc,EAAG,eAAe,CAAC;IAE3C,SAAS,CAAC,YAAY,CAAC,EAAG,aAAa,CAAC;IAExC,SAAS,CAAC,OAAO,CAAC,EAAG,gBAAgB,CAAC;IAEtC,SAAS,CAAC,iBAAiB,EAAG,MAAM,CAAC;IAErC,SAAS,CAAC,kBAAkB,EAAG,MAAM,CAAC;IAEtC,SAAS,CAAC,oBAAoB,CAAC,EAAG,mCAAmC,CAAC;IAEtE,SAAS,CAAC,qBAAqB,CAAC,EAAG,4BAA4B,CAAC;gBAEpD,GAAG,EAAE,iCAAiC;IAWlD;;;;;;OAMG;IACG,SAAS,CACX,IAAI,EAAE,8BAA8B,EACpC,QAAQ,EAAE,QAAQ,EAClB,OAAO,GAAE,0BAA+B,GACxC,OAAO,CAAC,yBAAyB,CAAC;cAmDtB,cAAc,CAC1B,IAAI,EAAE,8BAA8B,EACpC,QAAQ,EAAE,QAAQ,EAClB,OAAO,GAAE,0BAA+B,GACxC,OAAO,CAAC,yBAAyB,CAAC;CA+LzC"}
@@ -2,7 +2,7 @@ import "node:path";
2
2
  import "node:url";
3
3
  import.meta.url;
4
4
  import { toIdentityPolicyData } from "../../identity/permission/identity-policy-data.mjs";
5
- import { EventName, EventRefType, EventScope, IdentityType } from "@authup/core-kit";
5
+ import { EventName, EventRefType, EventScope, IdentityType, SessionAuthMethod } from "@authup/core-kit";
6
6
  import { hasInstanceof } from "@authup/errors";
7
7
  import { OAUTH2_ACCESS_DENIED_ERROR_INSTANCE, OAUTH2_LOGIN_REQUIRED_ERROR_INSTANCE, OAUTH2_MFA_REQUIRED_ERROR_INSTANCE, OAuth2AccessDeniedError, OAuth2AuthenticationContextClass, OAuth2AuthorizationPrompt, OAuth2AuthorizationResponseType, OAuth2GrantError, OAuth2LoginRequiredError, OAuth2MfaRequiredError, OAuth2RequestError, OAuth2ResponseTypeError } from "@authup/specs";
8
8
  //#region src/core/oauth2/authorization/module.ts
@@ -97,8 +97,10 @@ var OAuth2Authorization = class {
97
97
  }
98
98
  if (this.mfaChallengeProvider && identity.type === IdentityType.USER) {
99
99
  const challenge = await this.mfaChallengeProvider.challenge(identity.data.id, { issueMaterial: false });
100
- if (challenge.required && !session?.mfaAt) throw OAuth2MfaRequiredError.challengeRequired();
101
- if (challenge.enrollmentRequired) throw OAuth2MfaRequiredError.enrollmentRequired();
100
+ if (!(session?.authMethod === SessionAuthMethod.EXTERNAL)) {
101
+ if (challenge.required && !session?.mfaAt) throw OAuth2MfaRequiredError.challengeRequired();
102
+ if (challenge.enrollmentRequired) throw OAuth2MfaRequiredError.enrollmentRequired();
103
+ }
102
104
  if (challenge.required && data.acr_values) {
103
105
  if (data.acr_values.split(" ").includes(OAuth2AuthenticationContextClass.MFA)) {
104
106
  const mfaAtSeconds = session?.mfaAt ? Math.floor(new Date(session.mfaAt).getTime() / 1e3) : null;
@@ -1 +1 @@
1
- {"version":3,"file":"module.mjs","names":["EventName","EventRefType","EventScope","IdentityType","hasInstanceof","OAUTH2_ACCESS_DENIED_ERROR_INSTANCE","OAUTH2_LOGIN_REQUIRED_ERROR_INSTANCE","OAUTH2_MFA_REQUIRED_ERROR_INSTANCE","OAuth2AccessDeniedError","OAuth2AuthenticationContextClass","OAuth2AuthorizationPrompt","OAuth2AuthorizationResponseType","OAuth2GrantError","OAuth2LoginRequiredError","OAuth2MfaRequiredError","OAuth2RequestError","OAuth2ResponseTypeError","toIdentityPolicyData","DEFAULT_PROMPT_LOGIN_MAX_AGE","DEFAULT_MFA_FRESHNESS_MAX_AGE","OAuth2Authorization","codeIssuer","sessionManager","eventService","metrics","promptLoginMaxAge","mfaFreshnessMaxAge","mfaChallengeProvider","accessPolicyEvaluator","ctx","authorize","data","identity","options","result","authorizeInner","record","scope","OAUTH2","name","AUTHORIZE","refType","CLIENT","refId","client","id","client_id","clientId","sessionId","actorType","type","actorId","actorName","realmId","realm_id","reason","builtIn","recordAuthorize","e","AUTHORIZE_FAILED","responseTypes","response_type","Array","isArray","split","responseType","CODE","unsupported","includes","redirect_uri","redirectUriMismatch","output","redirectUri","state","identityInvalid","realmMismatch","nowSeconds","Math","floor","Date","now","authTime","session","findOneById","createdAt","getTime","USER","challenge","issueMaterial","required","mfaAt","challengeRequired","enrollmentRequired","acr_values","acrValues","MFA","mfaAtSeconds","stepUpRequired","prompts","prompt","LOGIN","reauthenticationRequired","max_age","maxAge","Number","isFinite","accessPolicyId","allowed","subject","evaluate","forClient","redirectUriVerified","undefined","codeEntity","issue","authMethod","authorizationCode"],"sources":["../../../../src/core/oauth2/authorization/module.ts"],"sourcesContent":["/*\n * Copyright (c) 2024.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport type {\n Identity, \n OAuth2AuthorizationCode, \n OAuth2AuthorizationCodeRequest, \n Session,\n} from '@authup/core-kit';\nimport {\n EventName, \n EventRefType, \n EventScope, \n IdentityType,\n} from '@authup/core-kit';\nimport { hasInstanceof } from '@authup/errors';\nimport {\n OAUTH2_ACCESS_DENIED_ERROR_INSTANCE,\n OAUTH2_LOGIN_REQUIRED_ERROR_INSTANCE,\n OAUTH2_MFA_REQUIRED_ERROR_INSTANCE,\n OAuth2AccessDeniedError,\n OAuth2AuthenticationContextClass,\n OAuth2AuthorizationPrompt,\n OAuth2AuthorizationResponseType,\n OAuth2GrantError,\n OAuth2LoginRequiredError,\n OAuth2MfaRequiredError,\n OAuth2RequestError,\n OAuth2ResponseTypeError,\n} from '@authup/specs';\nimport type { IOAuth2AuthorizationCodeIssuer } from './code/index.ts';\nimport type { IOAuth2AccessPolicyEvaluator } from '../access-policy/index.ts';\nimport { toIdentityPolicyData } from '../../identity/permission/identity-policy-data.ts';\nimport type {\n OAuth2AuthorizationManagerContext,\n OAuth2AuthorizationOptions,\n OAuth2AuthorizationResult,\n} from './types.ts';\nimport type { ISessionManager } from '../../authentication/index.ts';\nimport type { IEventService, IUserAuthenticatorChallengeProvider } from '../../entities/index.ts';\nimport type { IAuthFlowMetrics } from '../../metrics/index.ts';\n\nconst DEFAULT_PROMPT_LOGIN_MAX_AGE = 60;\n\n// Deliberately 60 (not 0, deviating from the plan-050 sketch): the hosted\n// challenge round-trip (stamp mfa_at → retry POST /authorize) takes seconds,\n// so a 0-window step-up could never be satisfied and would loop the ladder.\nconst DEFAULT_MFA_FRESHNESS_MAX_AGE = 60;\n\nexport class OAuth2Authorization {\n protected codeIssuer : IOAuth2AuthorizationCodeIssuer;\n\n protected sessionManager : ISessionManager;\n\n protected eventService? : IEventService;\n\n protected metrics? : IAuthFlowMetrics;\n\n protected promptLoginMaxAge : number;\n\n protected mfaFreshnessMaxAge : number;\n\n protected mfaChallengeProvider? : IUserAuthenticatorChallengeProvider;\n\n protected accessPolicyEvaluator? : IOAuth2AccessPolicyEvaluator;\n\n constructor(ctx: OAuth2AuthorizationManagerContext) {\n this.codeIssuer = ctx.codeIssuer;\n this.sessionManager = ctx.sessionManager;\n this.eventService = ctx.eventService;\n this.metrics = ctx.metrics;\n this.accessPolicyEvaluator = ctx.accessPolicyEvaluator;\n this.promptLoginMaxAge = ctx.promptLoginMaxAge ?? DEFAULT_PROMPT_LOGIN_MAX_AGE;\n this.mfaFreshnessMaxAge = ctx.mfaFreshnessMaxAge ?? DEFAULT_MFA_FRESHNESS_MAX_AGE;\n this.mfaChallengeProvider = ctx.mfaChallengeProvider;\n }\n\n /**\n * Authorize with validated codeRequest.\n *\n * @param data\n * @param identity\n * @param options\n */\n async authorize(\n data: OAuth2AuthorizationCodeRequest,\n identity: Identity,\n options: OAuth2AuthorizationOptions = {},\n ) : Promise<OAuth2AuthorizationResult> {\n try {\n const result = await this.authorizeInner(data, identity, options);\n\n await this.eventService?.record({\n scope: EventScope.OAUTH2,\n name: EventName.AUTHORIZE,\n refType: EventRefType.CLIENT,\n refId: options.client?.id ?? data.client_id ?? null,\n clientId: options.client?.id ?? data.client_id ?? null,\n sessionId: options.sessionId ?? null,\n actorType: identity.type,\n actorId: identity.data.id,\n actorName: identity.data.name,\n realmId: data.realm_id ?? null,\n data: {\n reason: options.client?.builtIn ? 'autoConsent' : 'consent',\n ...(data.scope ? { scope: data.scope } : {}),\n },\n });\n this.metrics?.recordAuthorize('issued');\n\n return result;\n } catch (e) {\n if (hasInstanceof(e, OAUTH2_ACCESS_DENIED_ERROR_INSTANCE)) {\n await this.eventService?.record({\n scope: EventScope.OAUTH2,\n name: EventName.AUTHORIZE_FAILED,\n refType: EventRefType.CLIENT,\n refId: options.client?.id ?? data.client_id ?? null,\n clientId: options.client?.id ?? data.client_id ?? null,\n sessionId: options.sessionId ?? null,\n actorType: identity.type,\n actorId: identity.data.id,\n actorName: identity.data.name,\n realmId: data.realm_id ?? null,\n data: { reason: 'accessPolicy' },\n });\n this.metrics?.recordAuthorize('denied');\n } else if (hasInstanceof(e, OAUTH2_LOGIN_REQUIRED_ERROR_INSTANCE)) {\n this.metrics?.recordAuthorize('login_required');\n } else if (hasInstanceof(e, OAUTH2_MFA_REQUIRED_ERROR_INSTANCE)) {\n this.metrics?.recordAuthorize('mfa_required');\n } else {\n this.metrics?.recordAuthorize('error');\n }\n\n throw e;\n }\n }\n\n protected async authorizeInner(\n data: OAuth2AuthorizationCodeRequest,\n identity: Identity,\n options: OAuth2AuthorizationOptions = {},\n ) : Promise<OAuth2AuthorizationResult> {\n // OAuth 2.1 posture: only the authorization-code response type is\n // supported — implicit/hybrid (token, id_token, none) were dropped\n // (plan 042 item 3). Defense in depth behind the request validator.\n let responseTypes : string[] = [];\n if (data.response_type) {\n responseTypes = Array.isArray(data.response_type) ?\n data.response_type :\n data.response_type.split(' ');\n }\n\n for (const responseType of responseTypes) {\n if (responseType !== OAuth2AuthorizationResponseType.CODE) {\n throw OAuth2ResponseTypeError.unsupported();\n }\n }\n\n if (!responseTypes.includes(OAuth2AuthorizationResponseType.CODE)) {\n throw OAuth2ResponseTypeError.unsupported();\n }\n\n if (!data.redirect_uri) {\n throw OAuth2GrantError.redirectUriMismatch();\n }\n\n const output : OAuth2AuthorizationResult = {\n redirectUri: data.redirect_uri,\n ...(data.state ? { state: data.state } : {}),\n };\n\n if (!identity) {\n throw OAuth2RequestError.identityInvalid();\n }\n\n // Realm binding: the code-request verifier stamped data.realm_id with the\n // resolved client's realm. The authenticated identity must belong to that\n // same realm — otherwise a lingering session for realm A could silently\n // mint a code/token for realm B's client (confused deputy). The error body\n // deliberately carries no identity data (no realm-enumeration oracle).\n // The comparison reads the scalar realmId column, NOT the realm\n // relation — the relation may simply not be loaded on the resolved\n // identity. An identity without a realmId fails closed the same way —\n // clean login_required, never a TypeError.\n if (\n data.realm_id &&\n identity.data.realmId !== data.realm_id\n ) {\n throw OAuth2LoginRequiredError.realmMismatch();\n }\n\n // Authentication time = the backing session's creation instant (NOT\n // refreshed_at — a token refresh must not reset it). Session-less flows\n // (e.g. HTTP Basic authorize) present live credentials on this request,\n // so the authentication time is \"now\".\n const nowSeconds = Math.floor(Date.now() / 1000);\n let authTime = nowSeconds;\n let session : Session | null = null;\n if (options.sessionId) {\n session = await this.sessionManager.findOneById(options.sessionId);\n if (session && session.createdAt) {\n authTime = Math.floor(new Date(session.createdAt).getTime() / 1000);\n }\n }\n\n // MFA backstop (plan 049) — the authoritative server-side gate; the\n // hosted UI's challenge step is convenience. The proof is session-bound\n // (mfa_at — stamped by the challenge endpoint or the password grant's\n // otp param), so a session-less flow (HTTP Basic) cannot carry one and\n // fails closed while the user holds a confirmed device. A user without\n // a device under mfaRequired is routed to inline enrollment.\n if (this.mfaChallengeProvider && identity.type === IdentityType.USER) {\n // requirement flags only — the interactive challenge material (the\n // webauthn nonce) is issued by the status endpoint, not this backstop.\n const challenge = await this.mfaChallengeProvider.challenge(\n identity.data.id,\n { issueMaterial: false },\n );\n if (challenge.required && !session?.mfaAt) {\n throw OAuth2MfaRequiredError.challengeRequired();\n }\n\n if (challenge.enrollmentRequired) {\n throw OAuth2MfaRequiredError.enrollmentRequired();\n }\n\n // Step-up (plan 050 stage 3): a requested `acr_values` containing\n // urn:authup:mfa is a TRIGGER (Auth0/Keycloak stance) — the proof\n // must additionally be FRESH (mfaFreshnessMaxAge window, mirroring\n // promptLoginMaxAge's absorb-the-round-trip semantics). Enforced\n // only while the user actually holds a factor — per OIDC Core\n // §5.5.1.1 acr is voluntary, so an unsatisfiable request degrades\n // to the achieved acr instead of bricking the RP.\n if (challenge.required && data.acr_values) {\n const acrValues = data.acr_values.split(' ');\n if (acrValues.includes(OAuth2AuthenticationContextClass.MFA)) {\n const mfaAtSeconds = session?.mfaAt ?\n Math.floor(new Date(session.mfaAt).getTime() / 1000) :\n null;\n if (mfaAtSeconds === null || nowSeconds - mfaAtSeconds > this.mfaFreshnessMaxAge) {\n throw OAuth2MfaRequiredError.stepUpRequired();\n }\n }\n }\n }\n\n // OIDC §3.1.2.1 prompt=login / max_age freshness (enforced only when\n // requested — a plain authorize never throws here). The hosted page\n // renders the login form for prompt=login; this is the server backstop.\n const prompts = data.prompt ? data.prompt.split(' ') : [];\n if (\n prompts.includes(OAuth2AuthorizationPrompt.LOGIN) &&\n nowSeconds - authTime > this.promptLoginMaxAge\n ) {\n throw OAuth2LoginRequiredError.reauthenticationRequired();\n }\n\n if (\n typeof data.max_age !== 'undefined' &&\n data.max_age !== null\n ) {\n const maxAge = Number(data.max_age);\n if (\n Number.isFinite(maxAge) &&\n nowSeconds - authTime > maxAge\n ) {\n throw OAuth2LoginRequiredError.reauthenticationRequired();\n }\n }\n\n // Application access policy (plan 052) — the last gate before\n // issuance: a denial is only revealed to a fully-authenticated\n // (incl. second-factor) identity. A client carrying a policy id with\n // no wired evaluator denies too (never silently allow a configured\n // gate). The redirect target rides the error only when the\n // redirect_uri was pattern-verified (RFC 6749 §4.1.2.1).\n if (options.client?.accessPolicyId) {\n let allowed = false;\n\n const subject = toIdentityPolicyData(identity);\n if (this.accessPolicyEvaluator && subject) {\n allowed = await this.accessPolicyEvaluator.evaluate(\n options.client.accessPolicyId,\n subject,\n );\n }\n\n if (!allowed) {\n throw OAuth2AccessDeniedError.forClient(\n options.redirectUriVerified && data.redirect_uri ?\n { redirectUri: data.redirect_uri, state: data.state ?? null } :\n undefined,\n );\n }\n }\n\n // The id_token is NOT minted here — the /token exchange mints it after\n // resolving the real backing session, so its `sid` is authoritative\n // (plan 042 item 6). The code carries the authentication instant.\n const codeEntity : OAuth2AuthorizationCode = await this.codeIssuer.issue(\n data,\n identity,\n {\n sessionId: options.sessionId,\n authTime,\n authMethod: session?.authMethod ?? null,\n },\n );\n\n output.authorizationCode = codeEntity.id;\n\n return output;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"module.mjs","names":["EventName","EventRefType","EventScope","IdentityType","SessionAuthMethod","hasInstanceof","OAUTH2_ACCESS_DENIED_ERROR_INSTANCE","OAUTH2_LOGIN_REQUIRED_ERROR_INSTANCE","OAUTH2_MFA_REQUIRED_ERROR_INSTANCE","OAuth2AccessDeniedError","OAuth2AuthenticationContextClass","OAuth2AuthorizationPrompt","OAuth2AuthorizationResponseType","OAuth2GrantError","OAuth2LoginRequiredError","OAuth2MfaRequiredError","OAuth2RequestError","OAuth2ResponseTypeError","toIdentityPolicyData","DEFAULT_PROMPT_LOGIN_MAX_AGE","DEFAULT_MFA_FRESHNESS_MAX_AGE","OAuth2Authorization","codeIssuer","sessionManager","eventService","metrics","promptLoginMaxAge","mfaFreshnessMaxAge","mfaChallengeProvider","accessPolicyEvaluator","ctx","authorize","data","identity","options","result","authorizeInner","record","scope","OAUTH2","name","AUTHORIZE","refType","CLIENT","refId","client","id","client_id","clientId","sessionId","actorType","type","actorId","actorName","realmId","realm_id","reason","builtIn","recordAuthorize","e","AUTHORIZE_FAILED","responseTypes","response_type","Array","isArray","split","responseType","CODE","unsupported","includes","redirect_uri","redirectUriMismatch","output","redirectUri","state","identityInvalid","realmMismatch","nowSeconds","Math","floor","Date","now","authTime","session","findOneById","createdAt","getTime","USER","challenge","issueMaterial","externallyAuthenticated","authMethod","EXTERNAL","required","mfaAt","challengeRequired","enrollmentRequired","acr_values","acrValues","MFA","mfaAtSeconds","stepUpRequired","prompts","prompt","LOGIN","reauthenticationRequired","max_age","maxAge","Number","isFinite","accessPolicyId","allowed","subject","evaluate","forClient","redirectUriVerified","undefined","codeEntity","issue","authorizationCode"],"sources":["../../../../src/core/oauth2/authorization/module.ts"],"sourcesContent":["/*\n * Copyright (c) 2024.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport type {\n Identity, \n OAuth2AuthorizationCode, \n OAuth2AuthorizationCodeRequest, \n Session,\n} from '@authup/core-kit';\nimport {\n EventName, \n EventRefType, \n EventScope, \n IdentityType,\n SessionAuthMethod,\n} from '@authup/core-kit';\nimport { hasInstanceof } from '@authup/errors';\nimport {\n OAUTH2_ACCESS_DENIED_ERROR_INSTANCE,\n OAUTH2_LOGIN_REQUIRED_ERROR_INSTANCE,\n OAUTH2_MFA_REQUIRED_ERROR_INSTANCE,\n OAuth2AccessDeniedError,\n OAuth2AuthenticationContextClass,\n OAuth2AuthorizationPrompt,\n OAuth2AuthorizationResponseType,\n OAuth2GrantError,\n OAuth2LoginRequiredError,\n OAuth2MfaRequiredError,\n OAuth2RequestError,\n OAuth2ResponseTypeError,\n} from '@authup/specs';\nimport type { IOAuth2AuthorizationCodeIssuer } from './code/index.ts';\nimport type { IOAuth2AccessPolicyEvaluator } from '../access-policy/index.ts';\nimport { toIdentityPolicyData } from '../../identity/permission/identity-policy-data.ts';\nimport type {\n OAuth2AuthorizationManagerContext,\n OAuth2AuthorizationOptions,\n OAuth2AuthorizationResult,\n} from './types.ts';\nimport type { ISessionManager } from '../../authentication/index.ts';\nimport type { IEventService, IUserAuthenticatorChallengeProvider } from '../../entities/index.ts';\nimport type { IAuthFlowMetrics } from '../../metrics/index.ts';\n\nconst DEFAULT_PROMPT_LOGIN_MAX_AGE = 60;\n\n// Deliberately 60 (not 0, deviating from the plan-050 sketch): the hosted\n// challenge round-trip (stamp mfa_at → retry POST /authorize) takes seconds,\n// so a 0-window step-up could never be satisfied and would loop the ladder.\nconst DEFAULT_MFA_FRESHNESS_MAX_AGE = 60;\n\nexport class OAuth2Authorization {\n protected codeIssuer : IOAuth2AuthorizationCodeIssuer;\n\n protected sessionManager : ISessionManager;\n\n protected eventService? : IEventService;\n\n protected metrics? : IAuthFlowMetrics;\n\n protected promptLoginMaxAge : number;\n\n protected mfaFreshnessMaxAge : number;\n\n protected mfaChallengeProvider? : IUserAuthenticatorChallengeProvider;\n\n protected accessPolicyEvaluator? : IOAuth2AccessPolicyEvaluator;\n\n constructor(ctx: OAuth2AuthorizationManagerContext) {\n this.codeIssuer = ctx.codeIssuer;\n this.sessionManager = ctx.sessionManager;\n this.eventService = ctx.eventService;\n this.metrics = ctx.metrics;\n this.accessPolicyEvaluator = ctx.accessPolicyEvaluator;\n this.promptLoginMaxAge = ctx.promptLoginMaxAge ?? DEFAULT_PROMPT_LOGIN_MAX_AGE;\n this.mfaFreshnessMaxAge = ctx.mfaFreshnessMaxAge ?? DEFAULT_MFA_FRESHNESS_MAX_AGE;\n this.mfaChallengeProvider = ctx.mfaChallengeProvider;\n }\n\n /**\n * Authorize with validated codeRequest.\n *\n * @param data\n * @param identity\n * @param options\n */\n async authorize(\n data: OAuth2AuthorizationCodeRequest,\n identity: Identity,\n options: OAuth2AuthorizationOptions = {},\n ) : Promise<OAuth2AuthorizationResult> {\n try {\n const result = await this.authorizeInner(data, identity, options);\n\n await this.eventService?.record({\n scope: EventScope.OAUTH2,\n name: EventName.AUTHORIZE,\n refType: EventRefType.CLIENT,\n refId: options.client?.id ?? data.client_id ?? null,\n clientId: options.client?.id ?? data.client_id ?? null,\n sessionId: options.sessionId ?? null,\n actorType: identity.type,\n actorId: identity.data.id,\n actorName: identity.data.name,\n realmId: data.realm_id ?? null,\n data: {\n reason: options.client?.builtIn ? 'autoConsent' : 'consent',\n ...(data.scope ? { scope: data.scope } : {}),\n },\n });\n this.metrics?.recordAuthorize('issued');\n\n return result;\n } catch (e) {\n if (hasInstanceof(e, OAUTH2_ACCESS_DENIED_ERROR_INSTANCE)) {\n await this.eventService?.record({\n scope: EventScope.OAUTH2,\n name: EventName.AUTHORIZE_FAILED,\n refType: EventRefType.CLIENT,\n refId: options.client?.id ?? data.client_id ?? null,\n clientId: options.client?.id ?? data.client_id ?? null,\n sessionId: options.sessionId ?? null,\n actorType: identity.type,\n actorId: identity.data.id,\n actorName: identity.data.name,\n realmId: data.realm_id ?? null,\n data: { reason: 'accessPolicy' },\n });\n this.metrics?.recordAuthorize('denied');\n } else if (hasInstanceof(e, OAUTH2_LOGIN_REQUIRED_ERROR_INSTANCE)) {\n this.metrics?.recordAuthorize('login_required');\n } else if (hasInstanceof(e, OAUTH2_MFA_REQUIRED_ERROR_INSTANCE)) {\n this.metrics?.recordAuthorize('mfa_required');\n } else {\n this.metrics?.recordAuthorize('error');\n }\n\n throw e;\n }\n }\n\n protected async authorizeInner(\n data: OAuth2AuthorizationCodeRequest,\n identity: Identity,\n options: OAuth2AuthorizationOptions = {},\n ) : Promise<OAuth2AuthorizationResult> {\n // OAuth 2.1 posture: only the authorization-code response type is\n // supported — implicit/hybrid (token, id_token, none) were dropped\n // (plan 042 item 3). Defense in depth behind the request validator.\n let responseTypes : string[] = [];\n if (data.response_type) {\n responseTypes = Array.isArray(data.response_type) ?\n data.response_type :\n data.response_type.split(' ');\n }\n\n for (const responseType of responseTypes) {\n if (responseType !== OAuth2AuthorizationResponseType.CODE) {\n throw OAuth2ResponseTypeError.unsupported();\n }\n }\n\n if (!responseTypes.includes(OAuth2AuthorizationResponseType.CODE)) {\n throw OAuth2ResponseTypeError.unsupported();\n }\n\n if (!data.redirect_uri) {\n throw OAuth2GrantError.redirectUriMismatch();\n }\n\n const output : OAuth2AuthorizationResult = {\n redirectUri: data.redirect_uri,\n ...(data.state ? { state: data.state } : {}),\n };\n\n if (!identity) {\n throw OAuth2RequestError.identityInvalid();\n }\n\n // Realm binding: the code-request verifier stamped data.realm_id with the\n // resolved client's realm. The authenticated identity must belong to that\n // same realm — otherwise a lingering session for realm A could silently\n // mint a code/token for realm B's client (confused deputy). The error body\n // deliberately carries no identity data (no realm-enumeration oracle).\n // The comparison reads the scalar realmId column, NOT the realm\n // relation — the relation may simply not be loaded on the resolved\n // identity. An identity without a realmId fails closed the same way —\n // clean login_required, never a TypeError.\n if (\n data.realm_id &&\n identity.data.realmId !== data.realm_id\n ) {\n throw OAuth2LoginRequiredError.realmMismatch();\n }\n\n // Authentication time = the backing session's creation instant (NOT\n // refreshed_at — a token refresh must not reset it). Session-less flows\n // (e.g. HTTP Basic authorize) present live credentials on this request,\n // so the authentication time is \"now\".\n const nowSeconds = Math.floor(Date.now() / 1000);\n let authTime = nowSeconds;\n let session : Session | null = null;\n if (options.sessionId) {\n session = await this.sessionManager.findOneById(options.sessionId);\n if (session && session.createdAt) {\n authTime = Math.floor(new Date(session.createdAt).getTime() / 1000);\n }\n }\n\n // MFA backstop (plan 049) — the authoritative server-side gate; the\n // hosted UI's challenge step is convenience. The proof is session-bound\n // (mfa_at — stamped by the challenge endpoint or the password grant's\n // otp param), so a session-less flow (HTTP Basic) cannot carry one and\n // fails closed while the user holds a confirmed device. A user without\n // a device under mfaRequired is routed to inline enrollment.\n if (this.mfaChallengeProvider && identity.type === IdentityType.USER) {\n // requirement flags only — the interactive challenge material (the\n // webauthn nonce) is issued by the status endpoint, not this backstop.\n const challenge = await this.mfaChallengeProvider.challenge(\n identity.data.id,\n { issueMaterial: false },\n );\n // The local second factor belongs to a local credential. A\n // session established by an external identity provider was\n // authenticated THERE, which is where MFA is configured and\n // enforced for it, so authup does not stack a factor of its own\n // on top and `mfaRequired` does not force local enrollment on\n // those users. The route is opt-in either way: an external\n // identity reaches an existing account only through the\n // bearer-authenticated link flow, since a first login provisions\n // a NEW user (plan 091). A session-less authorize (HTTP Basic) is\n // not external and keeps the gate.\n const externallyAuthenticated = session?.authMethod === SessionAuthMethod.EXTERNAL;\n\n if (!externallyAuthenticated) {\n if (challenge.required && !session?.mfaAt) {\n throw OAuth2MfaRequiredError.challengeRequired();\n }\n\n if (challenge.enrollmentRequired) {\n throw OAuth2MfaRequiredError.enrollmentRequired();\n }\n }\n\n // Step-up (plan 050 stage 3): a requested `acr_values` containing\n // urn:authup:mfa is a TRIGGER (Auth0/Keycloak stance) — the proof\n // must additionally be FRESH (mfaFreshnessMaxAge window, mirroring\n // promptLoginMaxAge's absorb-the-round-trip semantics). Enforced\n // only while the user actually holds a factor — per OIDC Core\n // §5.5.1.1 acr is voluntary, so an unsatisfiable request degrades\n // to the achieved acr instead of bricking the RP.\n //\n // This one DOES apply to an externally authenticated session: the\n // application asked for MFA explicitly, and a local factor the\n // user holds is the only way authup can answer that. Trusting the\n // upstream is the default, not a refusal to prove anything.\n if (challenge.required && data.acr_values) {\n const acrValues = data.acr_values.split(' ');\n if (acrValues.includes(OAuth2AuthenticationContextClass.MFA)) {\n const mfaAtSeconds = session?.mfaAt ?\n Math.floor(new Date(session.mfaAt).getTime() / 1000) :\n null;\n if (mfaAtSeconds === null || nowSeconds - mfaAtSeconds > this.mfaFreshnessMaxAge) {\n throw OAuth2MfaRequiredError.stepUpRequired();\n }\n }\n }\n }\n\n // OIDC §3.1.2.1 prompt=login / max_age freshness (enforced only when\n // requested — a plain authorize never throws here). The hosted page\n // renders the login form for prompt=login; this is the server backstop.\n const prompts = data.prompt ? data.prompt.split(' ') : [];\n if (\n prompts.includes(OAuth2AuthorizationPrompt.LOGIN) &&\n nowSeconds - authTime > this.promptLoginMaxAge\n ) {\n throw OAuth2LoginRequiredError.reauthenticationRequired();\n }\n\n if (\n typeof data.max_age !== 'undefined' &&\n data.max_age !== null\n ) {\n const maxAge = Number(data.max_age);\n if (\n Number.isFinite(maxAge) &&\n nowSeconds - authTime > maxAge\n ) {\n throw OAuth2LoginRequiredError.reauthenticationRequired();\n }\n }\n\n // Application access policy (plan 052) — the last gate before\n // issuance: a denial is only revealed to a fully-authenticated\n // (incl. second-factor) identity. A client carrying a policy id with\n // no wired evaluator denies too (never silently allow a configured\n // gate). The redirect target rides the error only when the\n // redirect_uri was pattern-verified (RFC 6749 §4.1.2.1).\n if (options.client?.accessPolicyId) {\n let allowed = false;\n\n const subject = toIdentityPolicyData(identity);\n if (this.accessPolicyEvaluator && subject) {\n allowed = await this.accessPolicyEvaluator.evaluate(\n options.client.accessPolicyId,\n subject,\n );\n }\n\n if (!allowed) {\n throw OAuth2AccessDeniedError.forClient(\n options.redirectUriVerified && data.redirect_uri ?\n { redirectUri: data.redirect_uri, state: data.state ?? null } :\n undefined,\n );\n }\n }\n\n // The id_token is NOT minted here — the /token exchange mints it after\n // resolving the real backing session, so its `sid` is authoritative\n // (plan 042 item 6). The code carries the authentication instant.\n const codeEntity : OAuth2AuthorizationCode = await this.codeIssuer.issue(\n data,\n identity,\n {\n sessionId: options.sessionId,\n authTime,\n authMethod: session?.authMethod ?? null,\n },\n );\n\n output.authorizationCode = codeEntity.id;\n\n return output;\n }\n}\n"],"mappings":"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"}
@@ -12,6 +12,14 @@ export type OAuth2AuthorizationState = {
12
12
  * endpoint, never from client input.
13
13
  */
14
14
  link?: OAuth2AuthorizationStateLink;
15
+ /**
16
+ * Ties a federated login to the browser that started it (plan 094). The
17
+ * value is set as a cookie when the login starts and required to match
18
+ * when the provider sends the browser back, so a crafted callback URL
19
+ * opened elsewhere establishes nothing. The state's own ip / user agent
20
+ * cannot do this: both are chosen by whoever mints the state (#3439).
21
+ */
22
+ browserNonce?: string;
15
23
  ip: string;
16
24
  userAgent?: string | null;
17
25
  };
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../../src/core/oauth2/authorization/state/types.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,kBAAkB,CAAC;AAEvE,MAAM,MAAM,4BAA4B,GAAG;IACvC,MAAM,EAAE,MAAM,CAAC;IAKf,UAAU,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG;IACnC,WAAW,CAAC,EAAE,8BAA8B,CAAC;IAC7C;;;;;OAKG;IACH,IAAI,CAAC,EAAE,4BAA4B,CAAC;IACpC,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAC5B,CAAC;AAEF,MAAM,WAAW,+BAA+B;IAC5C,MAAM,CAAC,IAAI,EAAE,wBAAwB,GAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAEzD;;;OAGG;IACH,UAAU,CAAC,EAAE,EAAE,MAAM,GAAI,OAAO,CAAC,wBAAwB,GAAG,IAAI,CAAC,CAAC;CACrE;AAED,MAAM,WAAW,gCAAgC;IAC7C,IAAI,CAAC,IAAI,EAAE,wBAAwB,GAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAEvD,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,wBAAwB,CAAC,GAAG,OAAO,CAAC,wBAAwB,CAAC,CAAC;CACtG"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../../src/core/oauth2/authorization/state/types.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,kBAAkB,CAAC;AAEvE,MAAM,MAAM,4BAA4B,GAAG;IACvC,MAAM,EAAE,MAAM,CAAC;IAKf,UAAU,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG;IACnC,WAAW,CAAC,EAAE,8BAA8B,CAAC;IAC7C;;;;;OAKG;IACH,IAAI,CAAC,EAAE,4BAA4B,CAAC;IACpC;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAC5B,CAAC;AAEF,MAAM,WAAW,+BAA+B;IAC5C,MAAM,CAAC,IAAI,EAAE,wBAAwB,GAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAEzD;;;OAGG;IACH,UAAU,CAAC,EAAE,EAAE,MAAM,GAAI,OAAO,CAAC,wBAAwB,GAAG,IAAI,CAAC,CAAC;CACrE;AAED,MAAM,WAAW,gCAAgC;IAC7C,IAAI,CAAC,IAAI,EAAE,wBAAwB,GAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAEvD,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,wBAAwB,CAAC,GAAG,OAAO,CAAC,wBAAwB,CAAC,CAAC;CACtG"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Lifetime of a pending federated login (plan 094).
3
+ *
4
+ * It is completed by the very next page load, so it needs to cover a redirect
5
+ * and a bootstrap, not a user's attention span. It is also the lifetime of the
6
+ * session the callback creates, so an abandoned login self-expires and is
7
+ * swept with the regular session sweep; completion extends it to the full
8
+ * session lifetime.
9
+ */
10
+ export declare const OAUTH2_FEDERATED_LOGIN_TTL: number;
11
+ /**
12
+ * The cookie carrying the pending login between the provider callback and the
13
+ * hosted page that completes it.
14
+ *
15
+ * A cookie rather than a query parameter, which is what Keycloak and Authentik
16
+ * both do: the browser that STARTED the login is the only one that can present
17
+ * it, no other origin can set it, and nothing redeemable travels in a URL that
18
+ * reaches history, logs or a referrer. `SameSite=Lax` keeps it off cross-site
19
+ * requests, so a page on another origin cannot drive the completion.
20
+ */
21
+ export declare const OAUTH2_FEDERATED_LOGIN_COOKIE = "authup_federated_login";
22
+ //# sourceMappingURL=constants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../../src/core/oauth2/federated-login/constants.ts"],"names":[],"mappings":"AAOA;;;;;;;;GAQG;AACH,eAAO,MAAM,0BAA0B,QAAgB,CAAC;AAExD;;;;;;;;;GASG;AACH,eAAO,MAAM,6BAA6B,2BAA2B,CAAC"}
@@ -0,0 +1,27 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ //#region src/core/oauth2/federated-login/constants.ts
5
+ /**
6
+ * Lifetime of a pending federated login (plan 094).
7
+ *
8
+ * It is completed by the very next page load, so it needs to cover a redirect
9
+ * and a bootstrap, not a user's attention span. It is also the lifetime of the
10
+ * session the callback creates, so an abandoned login self-expires and is
11
+ * swept with the regular session sweep; completion extends it to the full
12
+ * session lifetime.
13
+ */ const OAUTH2_FEDERATED_LOGIN_TTL = 3e5;
14
+ /**
15
+ * The cookie carrying the pending login between the provider callback and the
16
+ * hosted page that completes it.
17
+ *
18
+ * A cookie rather than a query parameter, which is what Keycloak and Authentik
19
+ * both do: the browser that STARTED the login is the only one that can present
20
+ * it, no other origin can set it, and nothing redeemable travels in a URL that
21
+ * reaches history, logs or a referrer. `SameSite=Lax` keeps it off cross-site
22
+ * requests, so a page on another origin cannot drive the completion.
23
+ */ const OAUTH2_FEDERATED_LOGIN_COOKIE = "authup_federated_login";
24
+ //#endregion
25
+ export { OAUTH2_FEDERATED_LOGIN_COOKIE, OAUTH2_FEDERATED_LOGIN_TTL };
26
+
27
+ //# sourceMappingURL=constants.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.mjs","names":["OAUTH2_FEDERATED_LOGIN_TTL","OAUTH2_FEDERATED_LOGIN_COOKIE"],"sources":["../../../../src/core/oauth2/federated-login/constants.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\n/**\n * Lifetime of a pending federated login (plan 094).\n *\n * It is completed by the very next page load, so it needs to cover a redirect\n * and a bootstrap, not a user's attention span. It is also the lifetime of the\n * session the callback creates, so an abandoned login self-expires and is\n * swept with the regular session sweep; completion extends it to the full\n * session lifetime.\n */\nexport const OAUTH2_FEDERATED_LOGIN_TTL = 1000 * 60 * 5;\n\n/**\n * The cookie carrying the pending login between the provider callback and the\n * hosted page that completes it.\n *\n * A cookie rather than a query parameter, which is what Keycloak and Authentik\n * both do: the browser that STARTED the login is the only one that can present\n * it, no other origin can set it, and nothing redeemable travels in a URL that\n * reaches history, logs or a referrer. `SameSite=Lax` keeps it off cross-site\n * requests, so a page on another origin cannot drive the completion.\n */\nexport const OAUTH2_FEDERATED_LOGIN_COOKIE = 'authup_federated_login';\n"],"mappings":";;;;;;;;;;;;GAgBA,MAAaA,6BAA6B;;;;;;;;;;GAY1C,MAAaC,gCAAgC"}
@@ -1,3 +1,4 @@
1
+ export * from './constants.ts';
1
2
  export * from './module.ts';
2
3
  export * from './types.ts';
3
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/core/oauth2/federated-login/index.ts"],"names":[],"mappings":"AAOA,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/core/oauth2/federated-login/index.ts"],"names":[],"mappings":"AAOA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC"}
@@ -1,6 +1,7 @@
1
1
  import "node:path";
2
2
  import "node:url";
3
3
  import.meta.url;
4
+ import { OAUTH2_FEDERATED_LOGIN_COOKIE, OAUTH2_FEDERATED_LOGIN_TTL } from "./constants.mjs";
4
5
  import { OAuth2FederatedLoginRefusal } from "./types.mjs";
5
6
  import { OAuth2FederatedLoginService } from "./module.mjs";
6
- export { OAuth2FederatedLoginRefusal, OAuth2FederatedLoginService };
7
+ export { OAUTH2_FEDERATED_LOGIN_COOKIE, OAUTH2_FEDERATED_LOGIN_TTL, OAuth2FederatedLoginRefusal, OAuth2FederatedLoginService };
@@ -1,20 +1,29 @@
1
1
  import type { Logger } from '@authup/server-kit';
2
+ import type { OAuth2TokenGrantResponse } from '@authup/specs';
2
3
  import type { IEventService, IRealmRepository } from '../../entities/index.ts';
4
+ import type { IAuthFlowMetrics } from '../../metrics/index.ts';
5
+ import type { ISessionManager } from '../../authentication/index.ts';
3
6
  import type { IIdentityProviderAccountManager } from '../../identity/provider/account/types.ts';
4
7
  import type { IOAuth2AccessPolicyEvaluator } from '../access-policy/index.ts';
5
- import type { IOAuth2AuthorizationCodeIssuer, IOAuth2AuthorizationCodeRequestVerifier } from '../authorization/index.ts';
6
- import type { IOAuth2FederatedLoginService, OAuth2FederatedLoginAuthenticatorFactory, OAuth2FederatedLoginCompleteInput, OAuth2FederatedLoginCompleteResult, OAuth2FederatedLoginServiceContext, OAuth2FederatedLoginServiceOptions } from './types.ts';
8
+ import type { IOAuth2AuthorizationCodeRequestVerifier } from '../authorization/index.ts';
9
+ import type { IOAuth2TokenIssuer } from '../token/index.ts';
10
+ import type { IOAuth2FederatedLoginService, IOAuth2FederatedLoginStore, OAuth2FederatedLoginAuthenticatorFactory, OAuth2FederatedLoginCompleteHandoffInput, OAuth2FederatedLoginCompleteInput, OAuth2FederatedLoginCompleteResult, OAuth2FederatedLoginServiceContext, OAuth2FederatedLoginServiceOptions } from './types.ts';
7
11
  export declare class OAuth2FederatedLoginService implements IOAuth2FederatedLoginService {
8
12
  protected options: OAuth2FederatedLoginServiceOptions;
9
13
  protected accountManager: IIdentityProviderAccountManager;
10
14
  protected realmRepository: IRealmRepository;
11
15
  protected codeRequestVerifier: IOAuth2AuthorizationCodeRequestVerifier;
12
- protected codeIssuer: IOAuth2AuthorizationCodeIssuer;
16
+ protected sessionManager: ISessionManager;
17
+ protected pendingLoginStore: IOAuth2FederatedLoginStore;
18
+ protected accessTokenIssuer: IOAuth2TokenIssuer;
19
+ protected refreshTokenIssuer: IOAuth2TokenIssuer;
13
20
  protected accessPolicyEvaluator?: IOAuth2AccessPolicyEvaluator;
14
21
  protected eventService?: IEventService;
22
+ protected metrics?: IAuthFlowMetrics;
15
23
  protected logger?: Logger;
16
24
  protected authenticatorFactory: OAuth2FederatedLoginAuthenticatorFactory;
17
25
  constructor(ctx: OAuth2FederatedLoginServiceContext);
18
26
  complete(input: OAuth2FederatedLoginCompleteInput): Promise<OAuth2FederatedLoginCompleteResult>;
27
+ completeHandoff(input: OAuth2FederatedLoginCompleteHandoffInput): Promise<OAuth2TokenGrantResponse>;
19
28
  }
20
29
  //# sourceMappingURL=module.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../../../../src/core/oauth2/federated-login/module.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAEjD,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAG/E,OAAO,KAAK,EAAE,+BAA+B,EAAE,MAAM,0CAA0C,CAAC;AAGhG,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,2BAA2B,CAAC;AAC9E,OAAO,KAAK,EACR,8BAA8B,EAC9B,uCAAuC,EAE1C,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EACR,4BAA4B,EAC5B,wCAAwC,EACxC,iCAAiC,EACjC,kCAAkC,EAClC,kCAAkC,EAClC,kCAAkC,EACrC,MAAM,YAAY,CAAC;AAGpB,qBAAa,2BAA4B,YAAW,4BAA4B;IAC5E,SAAS,CAAC,OAAO,EAAG,kCAAkC,CAAC;IAEvD,SAAS,CAAC,cAAc,EAAG,+BAA+B,CAAC;IAE3D,SAAS,CAAC,eAAe,EAAG,gBAAgB,CAAC;IAE7C,SAAS,CAAC,mBAAmB,EAAG,uCAAuC,CAAC;IAExE,SAAS,CAAC,UAAU,EAAG,8BAA8B,CAAC;IAEtD,SAAS,CAAC,qBAAqB,CAAC,EAAG,4BAA4B,CAAC;IAEhE,SAAS,CAAC,YAAY,CAAC,EAAG,aAAa,CAAC;IAExC,SAAS,CAAC,MAAM,CAAC,EAAG,MAAM,CAAC;IAE3B,SAAS,CAAC,oBAAoB,EAAG,wCAAwC,CAAC;gBAE9D,GAAG,EAAE,kCAAkC;IAqB7C,QAAQ,CAAC,KAAK,EAAE,iCAAiC,GAAG,OAAO,CAAC,kCAAkC,CAAC;CA0LxG"}
1
+ {"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../../../../src/core/oauth2/federated-login/module.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,KAAK,EAAE,wBAAwB,EAAsB,MAAM,eAAe,CAAC;AAQlF,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC/E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAGrE,OAAO,KAAK,EAAE,+BAA+B,EAAE,MAAM,0CAA0C,CAAC;AAIhG,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,2BAA2B,CAAC;AAC9E,OAAO,KAAK,EACR,uCAAuC,EAE1C,MAAM,2BAA2B,CAAC;AAGnC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAE5D,OAAO,KAAK,EACR,4BAA4B,EAC5B,0BAA0B,EAC1B,wCAAwC,EACxC,wCAAwC,EACxC,iCAAiC,EACjC,kCAAkC,EAClC,kCAAkC,EAClC,kCAAkC,EACrC,MAAM,YAAY,CAAC;AAGpB,qBAAa,2BAA4B,YAAW,4BAA4B;IAC5E,SAAS,CAAC,OAAO,EAAG,kCAAkC,CAAC;IAEvD,SAAS,CAAC,cAAc,EAAG,+BAA+B,CAAC;IAE3D,SAAS,CAAC,eAAe,EAAG,gBAAgB,CAAC;IAE7C,SAAS,CAAC,mBAAmB,EAAG,uCAAuC,CAAC;IAExE,SAAS,CAAC,cAAc,EAAG,eAAe,CAAC;IAE3C,SAAS,CAAC,iBAAiB,EAAG,0BAA0B,CAAC;IAEzD,SAAS,CAAC,iBAAiB,EAAG,kBAAkB,CAAC;IAEjD,SAAS,CAAC,kBAAkB,EAAG,kBAAkB,CAAC;IAElD,SAAS,CAAC,qBAAqB,CAAC,EAAG,4BAA4B,CAAC;IAEhE,SAAS,CAAC,YAAY,CAAC,EAAG,aAAa,CAAC;IAExC,SAAS,CAAC,OAAO,CAAC,EAAG,gBAAgB,CAAC;IAEtC,SAAS,CAAC,MAAM,CAAC,EAAG,MAAM,CAAC;IAE3B,SAAS,CAAC,oBAAoB,EAAG,wCAAwC,CAAC;gBAE9D,GAAG,EAAE,kCAAkC;IAyB7C,QAAQ,CAAC,KAAK,EAAE,iCAAiC,GAAG,OAAO,CAAC,kCAAkC,CAAC;IA4L/F,eAAe,CAAC,KAAK,EAAE,wCAAwC,GAAG,OAAO,CAAC,wBAAwB,CAAC;CAqF5G"}
@@ -1,22 +1,32 @@
1
1
  import "node:path";
2
2
  import "node:url";
3
3
  import.meta.url;
4
+ import { deriveAmrAcr } from "../authorization/helpers.mjs";
4
5
  import { toIdentityPolicyData } from "../../identity/permission/identity-policy-data.mjs";
6
+ import { OAUTH2_FEDERATED_LOGIN_TTL } from "./constants.mjs";
7
+ import { describeError } from "../../../utils/error.mjs";
8
+ import "../../../utils/index.mjs";
9
+ import { isIdentityProviderAssuranceError } from "../../identity/provider/authentication/protocols/oauth2/assurance.mjs";
5
10
  import { createIdentityProviderOAuth2Authenticator } from "../../identity/provider/authentication/factory.mjs";
11
+ import { buildOAuth2BearerTokenResponse } from "../response/bearer.mjs";
6
12
  import { OAuth2FederatedLoginRefusal } from "./types.mjs";
7
13
  import { isSafeRedirectURLScheme } from "@authup/kit";
8
- import { EventName, EventRefType, EventScope, IdentityType, SessionAuthMethod } from "@authup/core-kit";
9
- import { InternalError } from "@authup/errors";
10
- import { OAuth2ErrorCode, OAuth2RequestError, isOAuth2Error } from "@authup/specs";
14
+ import { EventName, EventRefType, EventScope, IdentityType, ScopeName, SessionAuthMethod } from "@authup/core-kit";
15
+ import { BadRequestError, InternalError } from "@authup/errors";
16
+ import { OAuth2ErrorCode, OAuth2RequestError, OAuth2SubKind, isOAuth2Error } from "@authup/specs";
11
17
  //#region src/core/oauth2/federated-login/module.ts
12
18
  var OAuth2FederatedLoginService = class {
13
19
  options;
14
20
  accountManager;
15
21
  realmRepository;
16
22
  codeRequestVerifier;
17
- codeIssuer;
23
+ sessionManager;
24
+ pendingLoginStore;
25
+ accessTokenIssuer;
26
+ refreshTokenIssuer;
18
27
  accessPolicyEvaluator;
19
28
  eventService;
29
+ metrics;
20
30
  logger;
21
31
  authenticatorFactory;
22
32
  constructor(ctx) {
@@ -24,9 +34,13 @@ var OAuth2FederatedLoginService = class {
24
34
  this.accountManager = ctx.accountManager;
25
35
  this.realmRepository = ctx.realmRepository;
26
36
  this.codeRequestVerifier = ctx.codeRequestVerifier;
27
- this.codeIssuer = ctx.codeIssuer;
37
+ this.sessionManager = ctx.sessionManager;
38
+ this.pendingLoginStore = ctx.pendingLoginStore;
39
+ this.accessTokenIssuer = ctx.accessTokenIssuer;
40
+ this.refreshTokenIssuer = ctx.refreshTokenIssuer;
28
41
  this.accessPolicyEvaluator = ctx.accessPolicyEvaluator;
29
42
  this.eventService = ctx.eventService;
43
+ this.metrics = ctx.metrics;
30
44
  this.logger = ctx.logger;
31
45
  this.authenticatorFactory = ctx.authenticatorFactory ?? ((provider, options) => createIdentityProviderOAuth2Authenticator({
32
46
  accountManager: this.accountManager,
@@ -61,19 +75,32 @@ var OAuth2FederatedLoginService = class {
61
75
  error: OAuth2ErrorCode.LOGIN_REQUIRED,
62
76
  codeRequest: verified.data
63
77
  };
64
- const user = await this.authenticatorFactory(provider, {
78
+ const authenticator = this.authenticatorFactory(provider, {
65
79
  baseURL: this.options.baseURL,
66
80
  clientId: verified.data.client_id,
67
81
  logger: this.logger
68
- }).authenticate({ code });
82
+ });
83
+ let user;
84
+ try {
85
+ user = await authenticator.authenticate({ code });
86
+ } catch (e) {
87
+ if (!isIdentityProviderAssuranceError(e)) throw e;
88
+ this.logger?.warn(describeError(e, `The identity provider (${provider.id}) login was refused.`));
89
+ return {
90
+ kind: "refused",
91
+ refusal: OAuth2FederatedLoginRefusal.ASSURANCE_INSUFFICIENT,
92
+ error: OAuth2ErrorCode.ACCESS_DENIED,
93
+ codeRequest: verified.data
94
+ };
95
+ }
69
96
  if (!user.active) return {
70
97
  kind: "refused",
71
98
  refusal: OAuth2FederatedLoginRefusal.USER_INACTIVE,
72
99
  error: OAuth2ErrorCode.ACCESS_DENIED,
73
100
  codeRequest: verified.data
74
101
  };
75
- const realm = await this.realmRepository.resolve(provider.realmId, true);
76
102
  if (verified.client.accessPolicyId) {
103
+ const realm = await this.realmRepository.resolve(provider.realmId, true);
77
104
  let allowed = false;
78
105
  const subject = toIdentityPolicyData({
79
106
  type: IdentityType.USER,
@@ -90,45 +117,74 @@ var OAuth2FederatedLoginService = class {
90
117
  codeRequest: verified.data
91
118
  };
92
119
  }
93
- const authorizationCode = await this.codeIssuer.issue(verified.data, {
94
- type: IdentityType.USER,
95
- data: {
96
- ...user,
97
- realm
98
- }
99
- }, { authMethod: SessionAuthMethod.EXTERNAL });
120
+ const session = await this.sessionManager.create({
121
+ userAgent: input.request?.userAgent ?? void 0,
122
+ ipAddress: input.request?.ipAddress ?? void 0,
123
+ realmId: user.realmId,
124
+ sub: user.id,
125
+ subKind: IdentityType.USER,
126
+ mfaAt: null,
127
+ authMethod: SessionAuthMethod.EXTERNAL,
128
+ expiresAt: new Date(Date.now() + OAUTH2_FEDERATED_LOGIN_TTL).toISOString()
129
+ });
130
+ return {
131
+ kind: "issued",
132
+ pendingLoginId: await this.pendingLoginStore.save({
133
+ sessionId: session.id,
134
+ providerId: provider.id,
135
+ userName: user.name
136
+ }),
137
+ codeRequest: verified.data
138
+ };
139
+ }
140
+ async completeHandoff(input) {
141
+ const refuse = () => new BadRequestError("The login request is unknown or expired.");
142
+ const pending = await this.pendingLoginStore.consume(input.pendingLoginId);
143
+ if (!pending) throw refuse();
144
+ if (pending.providerId !== input.providerId) throw refuse();
145
+ const existing = await this.sessionManager.findOneById(pending.sessionId);
146
+ if (!existing) throw refuse();
147
+ if (Date.parse(existing.expiresAt) <= Date.now()) throw refuse();
148
+ const realm = await this.realmRepository.resolve(existing.realmId, true);
149
+ const session = await this.sessionManager.refresh(existing);
150
+ const payload = {
151
+ session_id: session.id,
152
+ user_agent: session.userAgent,
153
+ remote_address: session.ipAddress,
154
+ scope: ScopeName.GLOBAL,
155
+ sub: session.sub,
156
+ sub_kind: OAuth2SubKind.USER,
157
+ realm_id: session.realmId,
158
+ realm_name: realm.name,
159
+ ...deriveAmrAcr(session)
160
+ };
161
+ const [accessToken, accessTokenPayload] = await this.accessTokenIssuer.issue(payload);
162
+ const [refreshToken, refreshTokenPayload] = await this.refreshTokenIssuer.issue(payload);
100
163
  await this.eventService?.record({
101
164
  scope: EventScope.OAUTH2,
102
- name: EventName.AUTHORIZE,
103
- refType: EventRefType.CLIENT,
104
- refId: verified.data.client_id ?? null,
105
- clientId: verified.data.client_id ?? null,
106
- sessionId: null,
165
+ name: EventName.LOGIN,
166
+ refType: EventRefType.SESSION,
167
+ refId: session.id,
168
+ clientId: null,
169
+ sessionId: session.id,
107
170
  actorType: IdentityType.USER,
108
- actorId: user.id,
109
- actorName: user.name,
110
- realmId: verified.data.realm_id ?? realm.id,
111
- requestIpAddress: input.request?.ipAddress ?? null,
112
- requestUserAgent: input.request?.userAgent ?? null,
171
+ actorId: session.sub,
172
+ actorName: pending.userName ?? null,
173
+ realmId: session.realmId,
174
+ requestIpAddress: session.ipAddress ?? null,
175
+ requestUserAgent: session.userAgent ?? null,
113
176
  data: {
114
177
  reason: "federated",
115
- providerId: provider.id,
116
- providerName: provider.name,
117
- ...verified.data.scope ? { scope: verified.data.scope } : {}
178
+ providerId: pending.providerId
118
179
  }
119
180
  });
120
- return {
121
- kind: "issued",
122
- redirectUri,
123
- code: authorizationCode.id,
124
- state: verified.data.state,
125
- codeRequest: verified.data,
126
- client: {
127
- id: verified.client.id,
128
- name: verified.client.name,
129
- displayName: verified.client.displayName ?? null
130
- }
131
- };
181
+ this.metrics?.recordLogin("success");
182
+ return buildOAuth2BearerTokenResponse({
183
+ accessToken,
184
+ accessTokenPayload,
185
+ refreshToken,
186
+ refreshTokenPayload
187
+ });
132
188
  }
133
189
  };
134
190
  //#endregion