@contember/engine-tenant-api 2.2.0-alpha.5 → 2.2.0-alpha.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +1 -0
- package/dist/development/index.cjs +2 -0
- package/dist/development/index.cjs.map +1 -1
- package/dist/development/index.js +2 -0
- package/dist/development/index.js.map +1 -1
- package/dist/development/src/migrations/2026-09-04-120000-idp-disable-local-authentication.cjs +18 -0
- package/dist/development/src/migrations/2026-09-04-120000-idp-disable-local-authentication.cjs.map +1 -0
- package/dist/development/src/migrations/2026-09-04-120000-idp-disable-local-authentication.js +19 -0
- package/dist/development/src/migrations/2026-09-04-120000-idp-disable-local-authentication.js.map +1 -0
- package/dist/development/src/migrations/runner.cjs +3 -1
- package/dist/development/src/migrations/runner.cjs.map +1 -1
- package/dist/development/src/migrations/runner.js +3 -1
- package/dist/development/src/migrations/runner.js.map +1 -1
- package/dist/development/src/migrations/snapshot.cjs +2 -1
- package/dist/development/src/migrations/snapshot.cjs.map +1 -1
- package/dist/development/src/migrations/snapshot.js +2 -1
- package/dist/development/src/migrations/snapshot.js.map +1 -1
- package/dist/development/src/model/commands/idp/CreateIdpCommand.cjs +8 -2
- package/dist/development/src/model/commands/idp/CreateIdpCommand.cjs.map +1 -1
- package/dist/development/src/model/commands/idp/CreateIdpCommand.js +8 -2
- package/dist/development/src/model/commands/idp/CreateIdpCommand.js.map +1 -1
- package/dist/development/src/model/commands/idp/UpdateIdpCommand.cjs +6 -2
- package/dist/development/src/model/commands/idp/UpdateIdpCommand.cjs.map +1 -1
- package/dist/development/src/model/commands/idp/UpdateIdpCommand.js +6 -2
- package/dist/development/src/model/commands/idp/UpdateIdpCommand.js.map +1 -1
- package/dist/development/src/model/queries/idp/LocalAuthDisablingIdpsQuery.cjs +21 -0
- package/dist/development/src/model/queries/idp/LocalAuthDisablingIdpsQuery.cjs.map +1 -0
- package/dist/development/src/model/queries/idp/LocalAuthDisablingIdpsQuery.js +21 -0
- package/dist/development/src/model/queries/idp/LocalAuthDisablingIdpsQuery.js.map +1 -0
- package/dist/development/src/model/queries/idp/PersonIdentityProvidersQuery.cjs +1 -1
- package/dist/development/src/model/queries/idp/PersonIdentityProvidersQuery.cjs.map +1 -1
- package/dist/development/src/model/queries/idp/PersonIdentityProvidersQuery.js +1 -1
- package/dist/development/src/model/queries/idp/PersonIdentityProvidersQuery.js.map +1 -1
- package/dist/development/src/model/queries/idp/helpers.cjs +3 -3
- package/dist/development/src/model/queries/idp/helpers.cjs.map +1 -1
- package/dist/development/src/model/queries/idp/helpers.js +3 -3
- package/dist/development/src/model/queries/idp/helpers.js.map +1 -1
- package/dist/development/src/model/service/PasswordResetManager.cjs +5 -0
- package/dist/development/src/model/service/PasswordResetManager.cjs.map +1 -1
- package/dist/development/src/model/service/PasswordResetManager.js +5 -0
- package/dist/development/src/model/service/PasswordResetManager.js.map +1 -1
- package/dist/development/src/model/service/PasswordlessSignInManager.cjs +19 -0
- package/dist/development/src/model/service/PasswordlessSignInManager.cjs.map +1 -1
- package/dist/development/src/model/service/PasswordlessSignInManager.js +19 -0
- package/dist/development/src/model/service/PasswordlessSignInManager.js.map +1 -1
- package/dist/development/src/model/service/SignInManager.cjs +5 -0
- package/dist/development/src/model/service/SignInManager.cjs.map +1 -1
- package/dist/development/src/model/service/SignInManager.js +5 -0
- package/dist/development/src/model/service/SignInManager.js.map +1 -1
- package/dist/development/src/model/service/idp/PersonIdentityProviderManager.cjs +6 -0
- package/dist/development/src/model/service/idp/PersonIdentityProviderManager.cjs.map +1 -1
- package/dist/development/src/model/service/idp/PersonIdentityProviderManager.js +6 -0
- package/dist/development/src/model/service/idp/PersonIdentityProviderManager.js.map +1 -1
- package/dist/development/src/resolvers/mutation/idp/AddIDPMutationResolver.cjs +2 -1
- package/dist/development/src/resolvers/mutation/idp/AddIDPMutationResolver.cjs.map +1 -1
- package/dist/development/src/resolvers/mutation/idp/AddIDPMutationResolver.js +2 -1
- package/dist/development/src/resolvers/mutation/idp/AddIDPMutationResolver.js.map +1 -1
- package/dist/development/src/resolvers/mutation/idp/UpdateIDPMutationResolver.cjs +2 -1
- package/dist/development/src/resolvers/mutation/idp/UpdateIDPMutationResolver.cjs.map +1 -1
- package/dist/development/src/resolvers/mutation/idp/UpdateIDPMutationResolver.js +2 -1
- package/dist/development/src/resolvers/mutation/idp/UpdateIDPMutationResolver.js.map +1 -1
- package/dist/development/src/resolvers/mutation/idp/audit.cjs +1 -0
- package/dist/development/src/resolvers/mutation/idp/audit.cjs.map +1 -1
- package/dist/development/src/resolvers/mutation/idp/audit.js +1 -0
- package/dist/development/src/resolvers/mutation/idp/audit.js.map +1 -1
- package/dist/development/src/resolvers/mutation/person/PasswordlessMutationResolver.cjs +4 -0
- package/dist/development/src/resolvers/mutation/person/PasswordlessMutationResolver.cjs.map +1 -1
- package/dist/development/src/resolvers/mutation/person/PasswordlessMutationResolver.js +4 -0
- package/dist/development/src/resolvers/mutation/person/PasswordlessMutationResolver.js.map +1 -1
- package/dist/development/src/resolvers/mutation/person/SignInMutationResolver.cjs +2 -2
- package/dist/development/src/resolvers/mutation/person/SignInMutationResolver.cjs.map +1 -1
- package/dist/development/src/resolvers/mutation/person/SignInMutationResolver.js +2 -2
- package/dist/development/src/resolvers/mutation/person/SignInMutationResolver.js.map +1 -1
- package/dist/development/src/schema/tenant.graphql.cjs +28 -0
- package/dist/development/src/schema/tenant.graphql.cjs.map +1 -1
- package/dist/development/src/schema/tenant.graphql.js +28 -0
- package/dist/development/src/schema/tenant.graphql.js.map +1 -1
- package/dist/production/index.cjs +2 -0
- package/dist/production/index.cjs.map +1 -1
- package/dist/production/index.js +2 -0
- package/dist/production/index.js.map +1 -1
- package/dist/production/src/migrations/2026-09-04-120000-idp-disable-local-authentication.cjs +18 -0
- package/dist/production/src/migrations/2026-09-04-120000-idp-disable-local-authentication.cjs.map +1 -0
- package/dist/production/src/migrations/2026-09-04-120000-idp-disable-local-authentication.js +19 -0
- package/dist/production/src/migrations/2026-09-04-120000-idp-disable-local-authentication.js.map +1 -0
- package/dist/production/src/migrations/runner.cjs +3 -1
- package/dist/production/src/migrations/runner.cjs.map +1 -1
- package/dist/production/src/migrations/runner.js +3 -1
- package/dist/production/src/migrations/runner.js.map +1 -1
- package/dist/production/src/migrations/snapshot.cjs +2 -1
- package/dist/production/src/migrations/snapshot.cjs.map +1 -1
- package/dist/production/src/migrations/snapshot.js +2 -1
- package/dist/production/src/migrations/snapshot.js.map +1 -1
- package/dist/production/src/model/commands/idp/CreateIdpCommand.cjs +8 -2
- package/dist/production/src/model/commands/idp/CreateIdpCommand.cjs.map +1 -1
- package/dist/production/src/model/commands/idp/CreateIdpCommand.js +8 -2
- package/dist/production/src/model/commands/idp/CreateIdpCommand.js.map +1 -1
- package/dist/production/src/model/commands/idp/UpdateIdpCommand.cjs +6 -2
- package/dist/production/src/model/commands/idp/UpdateIdpCommand.cjs.map +1 -1
- package/dist/production/src/model/commands/idp/UpdateIdpCommand.js +6 -2
- package/dist/production/src/model/commands/idp/UpdateIdpCommand.js.map +1 -1
- package/dist/production/src/model/queries/idp/LocalAuthDisablingIdpsQuery.cjs +21 -0
- package/dist/production/src/model/queries/idp/LocalAuthDisablingIdpsQuery.cjs.map +1 -0
- package/dist/production/src/model/queries/idp/LocalAuthDisablingIdpsQuery.js +21 -0
- package/dist/production/src/model/queries/idp/LocalAuthDisablingIdpsQuery.js.map +1 -0
- package/dist/production/src/model/queries/idp/PersonIdentityProvidersQuery.cjs +1 -1
- package/dist/production/src/model/queries/idp/PersonIdentityProvidersQuery.cjs.map +1 -1
- package/dist/production/src/model/queries/idp/PersonIdentityProvidersQuery.js +1 -1
- package/dist/production/src/model/queries/idp/PersonIdentityProvidersQuery.js.map +1 -1
- package/dist/production/src/model/queries/idp/helpers.cjs +3 -3
- package/dist/production/src/model/queries/idp/helpers.cjs.map +1 -1
- package/dist/production/src/model/queries/idp/helpers.js +3 -3
- package/dist/production/src/model/queries/idp/helpers.js.map +1 -1
- package/dist/production/src/model/service/PasswordResetManager.cjs +5 -0
- package/dist/production/src/model/service/PasswordResetManager.cjs.map +1 -1
- package/dist/production/src/model/service/PasswordResetManager.js +5 -0
- package/dist/production/src/model/service/PasswordResetManager.js.map +1 -1
- package/dist/production/src/model/service/PasswordlessSignInManager.cjs +19 -0
- package/dist/production/src/model/service/PasswordlessSignInManager.cjs.map +1 -1
- package/dist/production/src/model/service/PasswordlessSignInManager.js +19 -0
- package/dist/production/src/model/service/PasswordlessSignInManager.js.map +1 -1
- package/dist/production/src/model/service/SignInManager.cjs +5 -0
- package/dist/production/src/model/service/SignInManager.cjs.map +1 -1
- package/dist/production/src/model/service/SignInManager.js +5 -0
- package/dist/production/src/model/service/SignInManager.js.map +1 -1
- package/dist/production/src/model/service/idp/PersonIdentityProviderManager.cjs +6 -0
- package/dist/production/src/model/service/idp/PersonIdentityProviderManager.cjs.map +1 -1
- package/dist/production/src/model/service/idp/PersonIdentityProviderManager.js +6 -0
- package/dist/production/src/model/service/idp/PersonIdentityProviderManager.js.map +1 -1
- package/dist/production/src/resolvers/mutation/idp/AddIDPMutationResolver.cjs +2 -1
- package/dist/production/src/resolvers/mutation/idp/AddIDPMutationResolver.cjs.map +1 -1
- package/dist/production/src/resolvers/mutation/idp/AddIDPMutationResolver.js +2 -1
- package/dist/production/src/resolvers/mutation/idp/AddIDPMutationResolver.js.map +1 -1
- package/dist/production/src/resolvers/mutation/idp/UpdateIDPMutationResolver.cjs +2 -1
- package/dist/production/src/resolvers/mutation/idp/UpdateIDPMutationResolver.cjs.map +1 -1
- package/dist/production/src/resolvers/mutation/idp/UpdateIDPMutationResolver.js +2 -1
- package/dist/production/src/resolvers/mutation/idp/UpdateIDPMutationResolver.js.map +1 -1
- package/dist/production/src/resolvers/mutation/idp/audit.cjs +1 -0
- package/dist/production/src/resolvers/mutation/idp/audit.cjs.map +1 -1
- package/dist/production/src/resolvers/mutation/idp/audit.js +1 -0
- package/dist/production/src/resolvers/mutation/idp/audit.js.map +1 -1
- package/dist/production/src/resolvers/mutation/person/PasswordlessMutationResolver.cjs +4 -0
- package/dist/production/src/resolvers/mutation/person/PasswordlessMutationResolver.cjs.map +1 -1
- package/dist/production/src/resolvers/mutation/person/PasswordlessMutationResolver.js +4 -0
- package/dist/production/src/resolvers/mutation/person/PasswordlessMutationResolver.js.map +1 -1
- package/dist/production/src/resolvers/mutation/person/SignInMutationResolver.cjs +2 -2
- package/dist/production/src/resolvers/mutation/person/SignInMutationResolver.cjs.map +1 -1
- package/dist/production/src/resolvers/mutation/person/SignInMutationResolver.js +2 -2
- package/dist/production/src/resolvers/mutation/person/SignInMutationResolver.js.map +1 -1
- package/dist/production/src/schema/tenant.graphql.cjs +28 -0
- package/dist/production/src/schema/tenant.graphql.cjs.map +1 -1
- package/dist/production/src/schema/tenant.graphql.js +28 -0
- package/dist/production/src/schema/tenant.graphql.js.map +1 -1
- package/dist/tests/cases/integration/mocked/gql/passwordless.d.ts +3 -0
- package/dist/tests/cases/integration/mocked/gql/passwordless.d.ts.map +1 -1
- package/dist/tests/cases/integration/mocked/localAuthenticationDisabled.test.d.ts +2 -0
- package/dist/tests/cases/integration/mocked/localAuthenticationDisabled.test.d.ts.map +1 -0
- package/dist/tests/cases/integration/mocked/sql/localAuthDisablingIdpsSql.d.ts +10 -0
- package/dist/tests/cases/integration/mocked/sql/localAuthDisablingIdpsSql.d.ts.map +1 -0
- package/dist/tests/cases/integration/mocked/sql/personIdentityProvidersSql.d.ts +1 -0
- package/dist/tests/cases/integration/mocked/sql/personIdentityProvidersSql.d.ts.map +1 -1
- package/dist/tests/tsconfig.tsbuildinfo +1 -1
- package/dist/types/migrations/2026-09-04-120000-idp-disable-local-authentication.d.ts +3 -0
- package/dist/types/migrations/2026-09-04-120000-idp-disable-local-authentication.d.ts.map +1 -0
- package/dist/types/migrations/runner.d.ts.map +1 -1
- package/dist/types/migrations/snapshot.d.ts.map +1 -1
- package/dist/types/model/commands/idp/CreateIdpCommand.d.ts.map +1 -1
- package/dist/types/model/commands/idp/UpdateIdpCommand.d.ts.map +1 -1
- package/dist/types/model/queries/idp/LocalAuthDisablingIdpsQuery.d.ts +18 -0
- package/dist/types/model/queries/idp/LocalAuthDisablingIdpsQuery.d.ts.map +1 -0
- package/dist/types/model/queries/idp/PersonIdentityProvidersQuery.d.ts +1 -0
- package/dist/types/model/queries/idp/PersonIdentityProvidersQuery.d.ts.map +1 -1
- package/dist/types/model/queries/idp/helpers.d.ts +1 -1
- package/dist/types/model/queries/idp/helpers.d.ts.map +1 -1
- package/dist/types/model/queries/idp/index.d.ts +1 -0
- package/dist/types/model/queries/idp/index.d.ts.map +1 -1
- package/dist/types/model/queries/idp/types.d.ts +1 -0
- package/dist/types/model/queries/idp/types.d.ts.map +1 -1
- package/dist/types/model/service/PasswordResetManager.d.ts.map +1 -1
- package/dist/types/model/service/PasswordlessSignInManager.d.ts.map +1 -1
- package/dist/types/model/service/SignInManager.d.ts.map +1 -1
- package/dist/types/model/service/idp/PersonIdentityProviderManager.d.ts.map +1 -1
- package/dist/types/model/type/IdentityProvider.d.ts +2 -0
- package/dist/types/model/type/IdentityProvider.d.ts.map +1 -1
- package/dist/types/resolvers/mutation/idp/AddIDPMutationResolver.d.ts.map +1 -1
- package/dist/types/resolvers/mutation/idp/UpdateIDPMutationResolver.d.ts.map +1 -1
- package/dist/types/resolvers/mutation/idp/audit.d.ts.map +1 -1
- package/dist/types/resolvers/mutation/person/PasswordlessMutationResolver.d.ts.map +1 -1
- package/dist/types/schema/index.d.ts +33 -4
- package/dist/types/schema/index.d.ts.map +1 -1
- package/dist/types/schema/tenant.graphql.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +12 -12
- package/src/migrations/2026-09-04-120000-idp-disable-local-authentication.ts +18 -0
- package/src/migrations/runner.ts +2 -0
- package/src/migrations/snapshot.ts +2 -1
- package/src/model/commands/idp/CreateIdpCommand.ts +7 -1
- package/src/model/commands/idp/UpdateIdpCommand.ts +5 -1
- package/src/model/queries/idp/LocalAuthDisablingIdpsQuery.ts +41 -0
- package/src/model/queries/idp/PersonIdentityProvidersQuery.ts +2 -0
- package/src/model/queries/idp/helpers.ts +3 -2
- package/src/model/queries/idp/index.ts +1 -0
- package/src/model/queries/idp/types.ts +1 -0
- package/src/model/service/PasswordResetManager.ts +9 -1
- package/src/model/service/PasswordlessSignInManager.ts +23 -1
- package/src/model/service/SignInManager.ts +9 -1
- package/src/model/service/idp/PersonIdentityProviderManager.ts +8 -0
- package/src/model/type/IdentityProvider.ts +2 -0
- package/src/resolvers/mutation/idp/AddIDPMutationResolver.ts +1 -0
- package/src/resolvers/mutation/idp/UpdateIDPMutationResolver.ts +1 -0
- package/src/resolvers/mutation/idp/audit.ts +1 -0
- package/src/resolvers/mutation/person/PasswordlessMutationResolver.ts +7 -0
- package/src/resolvers/mutation/person/SignInMutationResolver.ts +2 -2
- package/src/schema/index.ts +29 -0
- package/src/schema/tenant.graphql.ts +28 -0
- package/tests/cases/integration/mocked/gql/passwordless.ts +10 -0
- package/tests/cases/integration/mocked/idpClaimMappingConfig.test.ts +4 -4
- package/tests/cases/integration/mocked/localAuthenticationDisabled.test.ts +182 -0
- package/tests/cases/integration/mocked/personIdentityProviders.test.ts +67 -0
- package/tests/cases/integration/mocked/signInAnomalyDetection.test.ts +11 -0
- package/tests/cases/integration/mocked/signInIdpClaimMapping.test.ts +14 -0
- package/tests/cases/integration/mocked/signInIdpClaimMappingFixes.test.ts +4 -0
- package/tests/cases/integration/mocked/signInIdpClaimMappingSafety.test.ts +6 -0
- package/tests/cases/integration/mocked/signInIdpMutation.test.ts +5 -0
- package/tests/cases/integration/mocked/signInMfaEnforcement.test.ts +7 -0
- package/tests/cases/integration/mocked/signInMutation.test.ts +14 -0
- package/tests/cases/integration/mocked/signInPasswordlessMutation.test.ts +36 -0
- package/tests/cases/integration/mocked/sql/getIdpBySlugSql.ts +1 -1
- package/tests/cases/integration/mocked/sql/localAuthDisablingIdpsSql.ts +21 -0
- package/tests/cases/integration/mocked/sql/personIdentityProvidersSql.ts +3 -1
- package/tests/cases/integration/mocked/trustForwardedClientInfo.test.ts +3 -0
- package/tests/cases/unit/idp/idpManagerUnknownConfigKeys.test.ts +8 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PasswordlessMutationResolver.cjs","sources":["../../../../../../../packages/engine-tenant-api/src/resolvers/mutation/person/PasswordlessMutationResolver.ts"],"sourcesContent":["import {\n\tActivatePasswordlessOtpResponse,\n\tInitSignInPasswordlessResponse,\n\tMutationActivatePasswordlessOtpArgs,\n\tMutationInitSignInPasswordlessArgs,\n\tMutationResolvers,\n\tMutationSignInPasswordlessArgs,\n\tSignInPasswordlessResponse,\n} from '../../../schema/index.js'\nimport { TenantResolverContext } from '../../TenantResolverContext.js'\nimport { CaptchaValidator, ConfigurationQuery, PermissionActions, RateLimiter } from '../../../model/index.js'\nimport { createErrorResponse } from '../../errorUtils.js'\nimport { SignInResponseFactory } from '../../responseHelpers/SignInResponseFactory.js'\nimport { PasswordlessSignInManager } from '../../../model/service/PasswordlessSignInManager.js'\n\nexport class PasswordlessMutationResolver\n\timplements Pick<MutationResolvers, 'initSignInPasswordless' | 'signInPasswordless' | 'activatePasswordlessOtp'>\n{\n\tconstructor(\n\t\tprivate readonly passwordlessSignInManager: PasswordlessSignInManager,\n\t\tprivate readonly signInResponseFactory: SignInResponseFactory,\n\t\tprivate readonly captchaValidator: CaptchaValidator,\n\t\tprivate readonly rateLimiter: RateLimiter,\n\t) {}\n\n\tasync initSignInPasswordless(\n\t\tparent: any,\n\t\targs: MutationInitSignInPasswordlessArgs,\n\t\tcontext: TenantResolverContext,\n\t): Promise<InitSignInPasswordlessResponse> {\n\t\tawait context.requireAccess({\n\t\t\taction: PermissionActions.PERSON_REQUEST_PASSWORDLESS_SIGN_IN,\n\t\t\tmessage: 'You are not allowed to request passwordless sign in',\n\t\t})\n\n\t\tconst configuration = await context.db.queryHandler.fetch(new ConfigurationQuery(context.db.providers))\n\n\t\tconst rl = await this.rateLimiter.consume(context.db, 'passwordless_init_per_ip', context.httpInfo.ip, configuration)\n\t\tif (!rl.ok) {\n\t\t\treturn createErrorResponse('RATE_LIMIT_EXCEEDED', `Too many passwordless sign-in requests. Retry after ${rl.retryAfterSeconds}s.`)\n\t\t}\n\n\t\tconst captchaConfig = this.captchaValidator.extractConfig(configuration)\n\t\tif (this.captchaValidator.isEnabledFor(captchaConfig, 'passwordlessInit')) {\n\t\t\tconst captcha = await this.captchaValidator.verify({\n\t\t\t\tconfig: captchaConfig,\n\t\t\t\ttoken: args.captchaToken ?? undefined,\n\t\t\t\tremoteIp: context.httpInfo.ip,\n\t\t\t})\n\t\t\tif (!captcha.ok) {\n\t\t\t\treturn createErrorResponse('INVALID_CAPTCHA', `Captcha verification failed: ${captcha.reason}`)\n\t\t\t}\n\t\t}\n\n\t\tconst result = await this.passwordlessSignInManager.initSignInPasswordless({\n\t\t\tdb: context.db,\n\t\t\tpermissionContext: context.permissionContext,\n\t\t\temail: args.email,\n\t\t\tmailVariant: args.options?.mailVariant ?? undefined,\n\t\t\tmailProject: args.options?.mailProject ?? undefined,\n\t\t})\n\t\tawait context.logAuthAction({\n\t\t\ttype: 'passwordless_login_init',\n\t\t\tresponse: result,\n\t\t})\n\t\tif (!result.ok) {\n\t\t\tif (result.error === 'PERSON_NOT_FOUND' && !configuration.login.revealUserExists) {\n\t\t\t\t// If the user does not exist, we don't want to reveal that\n\t\t\t\treturn createErrorResponse('PASSWORDLESS_DISABLED', 'Passwordless sign-in is disabled for this person')\n\t\t\t}\n\n\t\t\treturn createErrorResponse(result.error, result.errorMessage)\n\t\t}\n\t\treturn { ok: true, result: result.result }\n\t}\n\n\tasync signInPasswordless(parent: any, args: MutationSignInPasswordlessArgs, context: TenantResolverContext): Promise<SignInPasswordlessResponse> {\n\t\tawait context.requireAccess({\n\t\t\taction: PermissionActions.PERSON_PASSWORDLESS_SIGN_IN,\n\t\t\tmessage: 'You are not allowed to sign in using passwordless',\n\t\t})\n\t\tconst signIn = await this.passwordlessSignInManager.signInPasswordless({\n\t\t\tdb: context.db,\n\t\t\trequestId: args.requestId,\n\t\t\ttoken: args.token,\n\t\t\tmfaOtp: args.mfaOtp ?? undefined,\n\t\t\tbackupCode: args.backupCode ?? undefined,\n\t\t\tvalidationType: args.validationType,\n\t\t\texpiration: args.expiration ?? undefined,\n\t\t\trequestInfo: context.httpInfo,\n\t\t\ttrustForwardedInfo: args.options?.trustForwardedClientInfo === true && context.trustForwardedInfo,\n\t\t})\n\t\tawait context.logAuthAction({\n\t\t\ttype: 'passwordless_login',\n\t\t\tresponse: signIn,\n\t\t})\n\t\tif (signIn.ok && signIn.result.usedBackupCode) {\n\t\t\tawait context.logAuthAction({\n\t\t\t\ttype: 'backup_code_used',\n\t\t\t\tresponse: signIn,\n\t\t\t\tpersonId: signIn.result.person.id,\n\t\t\t})\n\t\t}\n\t\tif (!signIn.ok) {\n\t\t\treturn createErrorResponse(signIn.error, signIn.errorMessage)\n\t\t}\n\t\treturn {\n\t\t\tok: true,\n\t\t\tresult: await this.signInResponseFactory.createResponse(signIn.result, context),\n\t\t}\n\t}\n\n\tasync activatePasswordlessOtp(\n\t\tparent: any,\n\t\targs: MutationActivatePasswordlessOtpArgs,\n\t\tcontext: TenantResolverContext,\n\t): Promise<ActivatePasswordlessOtpResponse> {\n\t\tawait context.requireAccess({\n\t\t\taction: PermissionActions.PERSON_PASSWORDLESS_SIGN_IN,\n\t\t\tmessage: 'You are not allowed to activate passwordless OTP',\n\t\t})\n\t\tconst result = await this.passwordlessSignInManager.activatePasswordlessOtp({\n\t\t\tdb: context.db,\n\t\t\trequestId: args.requestId,\n\t\t\ttoken: args.token,\n\t\t\totpHash: args.otpHash,\n\t\t})\n\t\tawait context.logAuthAction({\n\t\t\ttype: 'passwordless_login_exchange',\n\t\t\tresponse: result,\n\t\t})\n\t\tif (!result.ok) {\n\t\t\treturn createErrorResponse(result.error, result.errorMessage)\n\t\t}\n\t\treturn { ok: true }\n\t}\n}\n"],"names":["PermissionActions","ConfigurationQuery","createErrorResponse"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PasswordlessMutationResolver.cjs","sources":["../../../../../../../packages/engine-tenant-api/src/resolvers/mutation/person/PasswordlessMutationResolver.ts"],"sourcesContent":["import {\n\tActivatePasswordlessOtpResponse,\n\tInitSignInPasswordlessResponse,\n\tMutationActivatePasswordlessOtpArgs,\n\tMutationInitSignInPasswordlessArgs,\n\tMutationResolvers,\n\tMutationSignInPasswordlessArgs,\n\tSignInPasswordlessResponse,\n} from '../../../schema/index.js'\nimport { TenantResolverContext } from '../../TenantResolverContext.js'\nimport { CaptchaValidator, ConfigurationQuery, PermissionActions, RateLimiter } from '../../../model/index.js'\nimport { createErrorResponse } from '../../errorUtils.js'\nimport { SignInResponseFactory } from '../../responseHelpers/SignInResponseFactory.js'\nimport { PasswordlessSignInManager } from '../../../model/service/PasswordlessSignInManager.js'\n\nexport class PasswordlessMutationResolver\n\timplements Pick<MutationResolvers, 'initSignInPasswordless' | 'signInPasswordless' | 'activatePasswordlessOtp'>\n{\n\tconstructor(\n\t\tprivate readonly passwordlessSignInManager: PasswordlessSignInManager,\n\t\tprivate readonly signInResponseFactory: SignInResponseFactory,\n\t\tprivate readonly captchaValidator: CaptchaValidator,\n\t\tprivate readonly rateLimiter: RateLimiter,\n\t) {}\n\n\tasync initSignInPasswordless(\n\t\tparent: any,\n\t\targs: MutationInitSignInPasswordlessArgs,\n\t\tcontext: TenantResolverContext,\n\t): Promise<InitSignInPasswordlessResponse> {\n\t\tawait context.requireAccess({\n\t\t\taction: PermissionActions.PERSON_REQUEST_PASSWORDLESS_SIGN_IN,\n\t\t\tmessage: 'You are not allowed to request passwordless sign in',\n\t\t})\n\n\t\tconst configuration = await context.db.queryHandler.fetch(new ConfigurationQuery(context.db.providers))\n\n\t\tconst rl = await this.rateLimiter.consume(context.db, 'passwordless_init_per_ip', context.httpInfo.ip, configuration)\n\t\tif (!rl.ok) {\n\t\t\treturn createErrorResponse('RATE_LIMIT_EXCEEDED', `Too many passwordless sign-in requests. Retry after ${rl.retryAfterSeconds}s.`)\n\t\t}\n\n\t\tconst captchaConfig = this.captchaValidator.extractConfig(configuration)\n\t\tif (this.captchaValidator.isEnabledFor(captchaConfig, 'passwordlessInit')) {\n\t\t\tconst captcha = await this.captchaValidator.verify({\n\t\t\t\tconfig: captchaConfig,\n\t\t\t\ttoken: args.captchaToken ?? undefined,\n\t\t\t\tremoteIp: context.httpInfo.ip,\n\t\t\t})\n\t\t\tif (!captcha.ok) {\n\t\t\t\treturn createErrorResponse('INVALID_CAPTCHA', `Captcha verification failed: ${captcha.reason}`)\n\t\t\t}\n\t\t}\n\n\t\tconst result = await this.passwordlessSignInManager.initSignInPasswordless({\n\t\t\tdb: context.db,\n\t\t\tpermissionContext: context.permissionContext,\n\t\t\temail: args.email,\n\t\t\tmailVariant: args.options?.mailVariant ?? undefined,\n\t\t\tmailProject: args.options?.mailProject ?? undefined,\n\t\t})\n\t\tawait context.logAuthAction({\n\t\t\ttype: 'passwordless_login_init',\n\t\t\tresponse: result,\n\t\t})\n\t\tif (!result.ok) {\n\t\t\tif (result.error === 'PERSON_NOT_FOUND' && !configuration.login.revealUserExists) {\n\t\t\t\t// If the user does not exist, we don't want to reveal that\n\t\t\t\treturn createErrorResponse('PASSWORDLESS_DISABLED', 'Passwordless sign-in is disabled for this person')\n\t\t\t}\n\t\t\t// Naming the provider tells the caller both that the person exists and how\n\t\t\t// they authenticate, so it needs both reveal flags. PASSWORDLESS_DISABLED is\n\t\t\t// the neutral answer this endpoint already gives for an unknown address.\n\t\t\tconst { revealUserExists, revealLoginMethod } = configuration.login\n\t\t\tif (result.error === 'IDP_REQUIRED' && !(revealUserExists && revealLoginMethod)) {\n\t\t\t\treturn createErrorResponse('PASSWORDLESS_DISABLED', 'Passwordless sign-in is disabled for this person')\n\t\t\t}\n\n\t\t\treturn createErrorResponse(result.error, result.errorMessage)\n\t\t}\n\t\treturn { ok: true, result: result.result }\n\t}\n\n\tasync signInPasswordless(parent: any, args: MutationSignInPasswordlessArgs, context: TenantResolverContext): Promise<SignInPasswordlessResponse> {\n\t\tawait context.requireAccess({\n\t\t\taction: PermissionActions.PERSON_PASSWORDLESS_SIGN_IN,\n\t\t\tmessage: 'You are not allowed to sign in using passwordless',\n\t\t})\n\t\tconst signIn = await this.passwordlessSignInManager.signInPasswordless({\n\t\t\tdb: context.db,\n\t\t\trequestId: args.requestId,\n\t\t\ttoken: args.token,\n\t\t\tmfaOtp: args.mfaOtp ?? undefined,\n\t\t\tbackupCode: args.backupCode ?? undefined,\n\t\t\tvalidationType: args.validationType,\n\t\t\texpiration: args.expiration ?? undefined,\n\t\t\trequestInfo: context.httpInfo,\n\t\t\ttrustForwardedInfo: args.options?.trustForwardedClientInfo === true && context.trustForwardedInfo,\n\t\t})\n\t\tawait context.logAuthAction({\n\t\t\ttype: 'passwordless_login',\n\t\t\tresponse: signIn,\n\t\t})\n\t\tif (signIn.ok && signIn.result.usedBackupCode) {\n\t\t\tawait context.logAuthAction({\n\t\t\t\ttype: 'backup_code_used',\n\t\t\t\tresponse: signIn,\n\t\t\t\tpersonId: signIn.result.person.id,\n\t\t\t})\n\t\t}\n\t\tif (!signIn.ok) {\n\t\t\treturn createErrorResponse(signIn.error, signIn.errorMessage)\n\t\t}\n\t\treturn {\n\t\t\tok: true,\n\t\t\tresult: await this.signInResponseFactory.createResponse(signIn.result, context),\n\t\t}\n\t}\n\n\tasync activatePasswordlessOtp(\n\t\tparent: any,\n\t\targs: MutationActivatePasswordlessOtpArgs,\n\t\tcontext: TenantResolverContext,\n\t): Promise<ActivatePasswordlessOtpResponse> {\n\t\tawait context.requireAccess({\n\t\t\taction: PermissionActions.PERSON_PASSWORDLESS_SIGN_IN,\n\t\t\tmessage: 'You are not allowed to activate passwordless OTP',\n\t\t})\n\t\tconst result = await this.passwordlessSignInManager.activatePasswordlessOtp({\n\t\t\tdb: context.db,\n\t\t\trequestId: args.requestId,\n\t\t\ttoken: args.token,\n\t\t\totpHash: args.otpHash,\n\t\t})\n\t\tawait context.logAuthAction({\n\t\t\ttype: 'passwordless_login_exchange',\n\t\t\tresponse: result,\n\t\t})\n\t\tif (!result.ok) {\n\t\t\treturn createErrorResponse(result.error, result.errorMessage)\n\t\t}\n\t\treturn { ok: true }\n\t}\n}\n"],"names":["PermissionActions","ConfigurationQuery","createErrorResponse"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAeO,MAAM,6BAEb;AAAA,EACC,YACkB,2BACA,uBACA,kBACA,aAChB;AAJgB,SAAA,4BAAA;AACA,SAAA,wBAAA;AACA,SAAA,mBAAA;AACA,SAAA,cAAA;AAAA,EACf;AAAA,EAEH,MAAM,uBACL,QACA,MACA,SAC0C;AAC1C,UAAM,QAAQ,cAAc;AAAA,MAC3B,QAAQA,kBAAAA,kBAAkB;AAAA,MAC1B,SAAS;AAAA,IAAA,CACT;AAED,UAAM,gBAAgB,MAAM,QAAQ,GAAG,aAAa,MAAM,IAAIC,sCAAmB,QAAQ,GAAG,SAAS,CAAC;AAEtG,UAAM,KAAK,MAAM,KAAK,YAAY,QAAQ,QAAQ,IAAI,4BAA4B,QAAQ,SAAS,IAAI,aAAa;AACpH,QAAI,CAAC,GAAG,IAAI;AACX,aAAOC,WAAAA,oBAAoB,uBAAuB,uDAAuD,GAAG,iBAAiB,IAAI;AAAA,IAClI;AAEA,UAAM,gBAAgB,KAAK,iBAAiB,cAAc,aAAa;AACvE,QAAI,KAAK,iBAAiB,aAAa,eAAe,kBAAkB,GAAG;AAC1E,YAAM,UAAU,MAAM,KAAK,iBAAiB,OAAO;AAAA,QAClD,QAAQ;AAAA,QACR,OAAO,KAAK,gBAAgB;AAAA,QAC5B,UAAU,QAAQ,SAAS;AAAA,MAAA,CAC3B;AACD,UAAI,CAAC,QAAQ,IAAI;AAChB,eAAOA,WAAAA,oBAAoB,mBAAmB,gCAAgC,QAAQ,MAAM,EAAE;AAAA,MAC/F;AAAA,IACD;AAEA,UAAM,SAAS,MAAM,KAAK,0BAA0B,uBAAuB;AAAA,MAC1E,IAAI,QAAQ;AAAA,MACZ,mBAAmB,QAAQ;AAAA,MAC3B,OAAO,KAAK;AAAA,MACZ,aAAa,KAAK,SAAS,eAAe;AAAA,MAC1C,aAAa,KAAK,SAAS,eAAe;AAAA,IAAA,CAC1C;AACD,UAAM,QAAQ,cAAc;AAAA,MAC3B,MAAM;AAAA,MACN,UAAU;AAAA,IAAA,CACV;AACD,QAAI,CAAC,OAAO,IAAI;AACf,UAAI,OAAO,UAAU,sBAAsB,CAAC,cAAc,MAAM,kBAAkB;AAEjF,eAAOA,WAAAA,oBAAoB,yBAAyB,kDAAkD;AAAA,MACvG;AAIA,YAAM,EAAE,kBAAkB,kBAAA,IAAsB,cAAc;AAC9D,UAAI,OAAO,UAAU,kBAAkB,EAAE,oBAAoB,oBAAoB;AAChF,eAAOA,WAAAA,oBAAoB,yBAAyB,kDAAkD;AAAA,MACvG;AAEA,aAAOA,WAAAA,oBAAoB,OAAO,OAAO,OAAO,YAAY;AAAA,IAC7D;AACA,WAAO,EAAE,IAAI,MAAM,QAAQ,OAAO,OAAA;AAAA,EACnC;AAAA,EAEA,MAAM,mBAAmB,QAAa,MAAsC,SAAqE;AAChJ,UAAM,QAAQ,cAAc;AAAA,MAC3B,QAAQF,kBAAAA,kBAAkB;AAAA,MAC1B,SAAS;AAAA,IAAA,CACT;AACD,UAAM,SAAS,MAAM,KAAK,0BAA0B,mBAAmB;AAAA,MACtE,IAAI,QAAQ;AAAA,MACZ,WAAW,KAAK;AAAA,MAChB,OAAO,KAAK;AAAA,MACZ,QAAQ,KAAK,UAAU;AAAA,MACvB,YAAY,KAAK,cAAc;AAAA,MAC/B,gBAAgB,KAAK;AAAA,MACrB,YAAY,KAAK,cAAc;AAAA,MAC/B,aAAa,QAAQ;AAAA,MACrB,oBAAoB,KAAK,SAAS,6BAA6B,QAAQ,QAAQ;AAAA,IAAA,CAC/E;AACD,UAAM,QAAQ,cAAc;AAAA,MAC3B,MAAM;AAAA,MACN,UAAU;AAAA,IAAA,CACV;AACD,QAAI,OAAO,MAAM,OAAO,OAAO,gBAAgB;AAC9C,YAAM,QAAQ,cAAc;AAAA,QAC3B,MAAM;AAAA,QACN,UAAU;AAAA,QACV,UAAU,OAAO,OAAO,OAAO;AAAA,MAAA,CAC/B;AAAA,IACF;AACA,QAAI,CAAC,OAAO,IAAI;AACf,aAAOE,WAAAA,oBAAoB,OAAO,OAAO,OAAO,YAAY;AAAA,IAC7D;AACA,WAAO;AAAA,MACN,IAAI;AAAA,MACJ,QAAQ,MAAM,KAAK,sBAAsB,eAAe,OAAO,QAAQ,OAAO;AAAA,IAAA;AAAA,EAEhF;AAAA,EAEA,MAAM,wBACL,QACA,MACA,SAC2C;AAC3C,UAAM,QAAQ,cAAc;AAAA,MAC3B,QAAQF,kBAAAA,kBAAkB;AAAA,MAC1B,SAAS;AAAA,IAAA,CACT;AACD,UAAM,SAAS,MAAM,KAAK,0BAA0B,wBAAwB;AAAA,MAC3E,IAAI,QAAQ;AAAA,MACZ,WAAW,KAAK;AAAA,MAChB,OAAO,KAAK;AAAA,MACZ,SAAS,KAAK;AAAA,IAAA,CACd;AACD,UAAM,QAAQ,cAAc;AAAA,MAC3B,MAAM;AAAA,MACN,UAAU;AAAA,IAAA,CACV;AACD,QAAI,CAAC,OAAO,IAAI;AACf,aAAOE,WAAAA,oBAAoB,OAAO,OAAO,OAAO,YAAY;AAAA,IAC7D;AACA,WAAO,EAAE,IAAI,KAAA;AAAA,EACd;AACD;;"}
|
|
@@ -64,6 +64,10 @@ class PasswordlessMutationResolver {
|
|
|
64
64
|
if (result.error === "PERSON_NOT_FOUND" && !configuration.login.revealUserExists) {
|
|
65
65
|
return createErrorResponse("PASSWORDLESS_DISABLED", "Passwordless sign-in is disabled for this person");
|
|
66
66
|
}
|
|
67
|
+
const { revealUserExists, revealLoginMethod } = configuration.login;
|
|
68
|
+
if (result.error === "IDP_REQUIRED" && !(revealUserExists && revealLoginMethod)) {
|
|
69
|
+
return createErrorResponse("PASSWORDLESS_DISABLED", "Passwordless sign-in is disabled for this person");
|
|
70
|
+
}
|
|
67
71
|
return createErrorResponse(result.error, result.errorMessage);
|
|
68
72
|
}
|
|
69
73
|
return { ok: true, result: result.result };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PasswordlessMutationResolver.js","sources":["../../../../../../../packages/engine-tenant-api/src/resolvers/mutation/person/PasswordlessMutationResolver.ts"],"sourcesContent":["import {\n\tActivatePasswordlessOtpResponse,\n\tInitSignInPasswordlessResponse,\n\tMutationActivatePasswordlessOtpArgs,\n\tMutationInitSignInPasswordlessArgs,\n\tMutationResolvers,\n\tMutationSignInPasswordlessArgs,\n\tSignInPasswordlessResponse,\n} from '../../../schema/index.js'\nimport { TenantResolverContext } from '../../TenantResolverContext.js'\nimport { CaptchaValidator, ConfigurationQuery, PermissionActions, RateLimiter } from '../../../model/index.js'\nimport { createErrorResponse } from '../../errorUtils.js'\nimport { SignInResponseFactory } from '../../responseHelpers/SignInResponseFactory.js'\nimport { PasswordlessSignInManager } from '../../../model/service/PasswordlessSignInManager.js'\n\nexport class PasswordlessMutationResolver\n\timplements Pick<MutationResolvers, 'initSignInPasswordless' | 'signInPasswordless' | 'activatePasswordlessOtp'>\n{\n\tconstructor(\n\t\tprivate readonly passwordlessSignInManager: PasswordlessSignInManager,\n\t\tprivate readonly signInResponseFactory: SignInResponseFactory,\n\t\tprivate readonly captchaValidator: CaptchaValidator,\n\t\tprivate readonly rateLimiter: RateLimiter,\n\t) {}\n\n\tasync initSignInPasswordless(\n\t\tparent: any,\n\t\targs: MutationInitSignInPasswordlessArgs,\n\t\tcontext: TenantResolverContext,\n\t): Promise<InitSignInPasswordlessResponse> {\n\t\tawait context.requireAccess({\n\t\t\taction: PermissionActions.PERSON_REQUEST_PASSWORDLESS_SIGN_IN,\n\t\t\tmessage: 'You are not allowed to request passwordless sign in',\n\t\t})\n\n\t\tconst configuration = await context.db.queryHandler.fetch(new ConfigurationQuery(context.db.providers))\n\n\t\tconst rl = await this.rateLimiter.consume(context.db, 'passwordless_init_per_ip', context.httpInfo.ip, configuration)\n\t\tif (!rl.ok) {\n\t\t\treturn createErrorResponse('RATE_LIMIT_EXCEEDED', `Too many passwordless sign-in requests. Retry after ${rl.retryAfterSeconds}s.`)\n\t\t}\n\n\t\tconst captchaConfig = this.captchaValidator.extractConfig(configuration)\n\t\tif (this.captchaValidator.isEnabledFor(captchaConfig, 'passwordlessInit')) {\n\t\t\tconst captcha = await this.captchaValidator.verify({\n\t\t\t\tconfig: captchaConfig,\n\t\t\t\ttoken: args.captchaToken ?? undefined,\n\t\t\t\tremoteIp: context.httpInfo.ip,\n\t\t\t})\n\t\t\tif (!captcha.ok) {\n\t\t\t\treturn createErrorResponse('INVALID_CAPTCHA', `Captcha verification failed: ${captcha.reason}`)\n\t\t\t}\n\t\t}\n\n\t\tconst result = await this.passwordlessSignInManager.initSignInPasswordless({\n\t\t\tdb: context.db,\n\t\t\tpermissionContext: context.permissionContext,\n\t\t\temail: args.email,\n\t\t\tmailVariant: args.options?.mailVariant ?? undefined,\n\t\t\tmailProject: args.options?.mailProject ?? undefined,\n\t\t})\n\t\tawait context.logAuthAction({\n\t\t\ttype: 'passwordless_login_init',\n\t\t\tresponse: result,\n\t\t})\n\t\tif (!result.ok) {\n\t\t\tif (result.error === 'PERSON_NOT_FOUND' && !configuration.login.revealUserExists) {\n\t\t\t\t// If the user does not exist, we don't want to reveal that\n\t\t\t\treturn createErrorResponse('PASSWORDLESS_DISABLED', 'Passwordless sign-in is disabled for this person')\n\t\t\t}\n\n\t\t\treturn createErrorResponse(result.error, result.errorMessage)\n\t\t}\n\t\treturn { ok: true, result: result.result }\n\t}\n\n\tasync signInPasswordless(parent: any, args: MutationSignInPasswordlessArgs, context: TenantResolverContext): Promise<SignInPasswordlessResponse> {\n\t\tawait context.requireAccess({\n\t\t\taction: PermissionActions.PERSON_PASSWORDLESS_SIGN_IN,\n\t\t\tmessage: 'You are not allowed to sign in using passwordless',\n\t\t})\n\t\tconst signIn = await this.passwordlessSignInManager.signInPasswordless({\n\t\t\tdb: context.db,\n\t\t\trequestId: args.requestId,\n\t\t\ttoken: args.token,\n\t\t\tmfaOtp: args.mfaOtp ?? undefined,\n\t\t\tbackupCode: args.backupCode ?? undefined,\n\t\t\tvalidationType: args.validationType,\n\t\t\texpiration: args.expiration ?? undefined,\n\t\t\trequestInfo: context.httpInfo,\n\t\t\ttrustForwardedInfo: args.options?.trustForwardedClientInfo === true && context.trustForwardedInfo,\n\t\t})\n\t\tawait context.logAuthAction({\n\t\t\ttype: 'passwordless_login',\n\t\t\tresponse: signIn,\n\t\t})\n\t\tif (signIn.ok && signIn.result.usedBackupCode) {\n\t\t\tawait context.logAuthAction({\n\t\t\t\ttype: 'backup_code_used',\n\t\t\t\tresponse: signIn,\n\t\t\t\tpersonId: signIn.result.person.id,\n\t\t\t})\n\t\t}\n\t\tif (!signIn.ok) {\n\t\t\treturn createErrorResponse(signIn.error, signIn.errorMessage)\n\t\t}\n\t\treturn {\n\t\t\tok: true,\n\t\t\tresult: await this.signInResponseFactory.createResponse(signIn.result, context),\n\t\t}\n\t}\n\n\tasync activatePasswordlessOtp(\n\t\tparent: any,\n\t\targs: MutationActivatePasswordlessOtpArgs,\n\t\tcontext: TenantResolverContext,\n\t): Promise<ActivatePasswordlessOtpResponse> {\n\t\tawait context.requireAccess({\n\t\t\taction: PermissionActions.PERSON_PASSWORDLESS_SIGN_IN,\n\t\t\tmessage: 'You are not allowed to activate passwordless OTP',\n\t\t})\n\t\tconst result = await this.passwordlessSignInManager.activatePasswordlessOtp({\n\t\t\tdb: context.db,\n\t\t\trequestId: args.requestId,\n\t\t\ttoken: args.token,\n\t\t\totpHash: args.otpHash,\n\t\t})\n\t\tawait context.logAuthAction({\n\t\t\ttype: 'passwordless_login_exchange',\n\t\t\tresponse: result,\n\t\t})\n\t\tif (!result.ok) {\n\t\t\treturn createErrorResponse(result.error, result.errorMessage)\n\t\t}\n\t\treturn { ok: true }\n\t}\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAeO,MAAM,6BAEb;AAAA,EACC,YACkB,2BACA,uBACA,kBACA,aAChB;AAJgB,SAAA,4BAAA;AACA,SAAA,wBAAA;AACA,SAAA,mBAAA;AACA,SAAA,cAAA;AAAA,EACf;AAAA,EAEH,MAAM,uBACL,QACA,MACA,SAC0C;AAC1C,UAAM,QAAQ,cAAc;AAAA,MAC3B,QAAQ,kBAAkB;AAAA,MAC1B,SAAS;AAAA,IAAA,CACT;AAED,UAAM,gBAAgB,MAAM,QAAQ,GAAG,aAAa,MAAM,IAAI,mBAAmB,QAAQ,GAAG,SAAS,CAAC;AAEtG,UAAM,KAAK,MAAM,KAAK,YAAY,QAAQ,QAAQ,IAAI,4BAA4B,QAAQ,SAAS,IAAI,aAAa;AACpH,QAAI,CAAC,GAAG,IAAI;AACX,aAAO,oBAAoB,uBAAuB,uDAAuD,GAAG,iBAAiB,IAAI;AAAA,IAClI;AAEA,UAAM,gBAAgB,KAAK,iBAAiB,cAAc,aAAa;AACvE,QAAI,KAAK,iBAAiB,aAAa,eAAe,kBAAkB,GAAG;AAC1E,YAAM,UAAU,MAAM,KAAK,iBAAiB,OAAO;AAAA,QAClD,QAAQ;AAAA,QACR,OAAO,KAAK,gBAAgB;AAAA,QAC5B,UAAU,QAAQ,SAAS;AAAA,MAAA,CAC3B;AACD,UAAI,CAAC,QAAQ,IAAI;AAChB,eAAO,oBAAoB,mBAAmB,gCAAgC,QAAQ,MAAM,EAAE;AAAA,MAC/F;AAAA,IACD;AAEA,UAAM,SAAS,MAAM,KAAK,0BAA0B,uBAAuB;AAAA,MAC1E,IAAI,QAAQ;AAAA,MACZ,mBAAmB,QAAQ;AAAA,MAC3B,OAAO,KAAK;AAAA,MACZ,aAAa,KAAK,SAAS,eAAe;AAAA,MAC1C,aAAa,KAAK,SAAS,eAAe;AAAA,IAAA,CAC1C;AACD,UAAM,QAAQ,cAAc;AAAA,MAC3B,MAAM;AAAA,MACN,UAAU;AAAA,IAAA,CACV;AACD,QAAI,CAAC,OAAO,IAAI;AACf,UAAI,OAAO,UAAU,sBAAsB,CAAC,cAAc,MAAM,kBAAkB;AAEjF,eAAO,oBAAoB,yBAAyB,kDAAkD;AAAA,MACvG;AAEA,aAAO,oBAAoB,OAAO,OAAO,OAAO,YAAY;AAAA,IAC7D;AACA,WAAO,EAAE,IAAI,MAAM,QAAQ,OAAO,OAAA;AAAA,EACnC;AAAA,EAEA,MAAM,mBAAmB,QAAa,MAAsC,SAAqE;AAChJ,UAAM,QAAQ,cAAc;AAAA,MAC3B,QAAQ,kBAAkB;AAAA,MAC1B,SAAS;AAAA,IAAA,CACT;AACD,UAAM,SAAS,MAAM,KAAK,0BAA0B,mBAAmB;AAAA,MACtE,IAAI,QAAQ;AAAA,MACZ,WAAW,KAAK;AAAA,MAChB,OAAO,KAAK;AAAA,MACZ,QAAQ,KAAK,UAAU;AAAA,MACvB,YAAY,KAAK,cAAc;AAAA,MAC/B,gBAAgB,KAAK;AAAA,MACrB,YAAY,KAAK,cAAc;AAAA,MAC/B,aAAa,QAAQ;AAAA,MACrB,oBAAoB,KAAK,SAAS,6BAA6B,QAAQ,QAAQ;AAAA,IAAA,CAC/E;AACD,UAAM,QAAQ,cAAc;AAAA,MAC3B,MAAM;AAAA,MACN,UAAU;AAAA,IAAA,CACV;AACD,QAAI,OAAO,MAAM,OAAO,OAAO,gBAAgB;AAC9C,YAAM,QAAQ,cAAc;AAAA,QAC3B,MAAM;AAAA,QACN,UAAU;AAAA,QACV,UAAU,OAAO,OAAO,OAAO;AAAA,MAAA,CAC/B;AAAA,IACF;AACA,QAAI,CAAC,OAAO,IAAI;AACf,aAAO,oBAAoB,OAAO,OAAO,OAAO,YAAY;AAAA,IAC7D;AACA,WAAO;AAAA,MACN,IAAI;AAAA,MACJ,QAAQ,MAAM,KAAK,sBAAsB,eAAe,OAAO,QAAQ,OAAO;AAAA,IAAA;AAAA,EAEhF;AAAA,EAEA,MAAM,wBACL,QACA,MACA,SAC2C;AAC3C,UAAM,QAAQ,cAAc;AAAA,MAC3B,QAAQ,kBAAkB;AAAA,MAC1B,SAAS;AAAA,IAAA,CACT;AACD,UAAM,SAAS,MAAM,KAAK,0BAA0B,wBAAwB;AAAA,MAC3E,IAAI,QAAQ;AAAA,MACZ,WAAW,KAAK;AAAA,MAChB,OAAO,KAAK;AAAA,MACZ,SAAS,KAAK;AAAA,IAAA,CACd;AACD,UAAM,QAAQ,cAAc;AAAA,MAC3B,MAAM;AAAA,MACN,UAAU;AAAA,IAAA,CACV;AACD,QAAI,CAAC,OAAO,IAAI;AACf,aAAO,oBAAoB,OAAO,OAAO,OAAO,YAAY;AAAA,IAC7D;AACA,WAAO,EAAE,IAAI,KAAA;AAAA,EACd;AACD;"}
|
|
1
|
+
{"version":3,"file":"PasswordlessMutationResolver.js","sources":["../../../../../../../packages/engine-tenant-api/src/resolvers/mutation/person/PasswordlessMutationResolver.ts"],"sourcesContent":["import {\n\tActivatePasswordlessOtpResponse,\n\tInitSignInPasswordlessResponse,\n\tMutationActivatePasswordlessOtpArgs,\n\tMutationInitSignInPasswordlessArgs,\n\tMutationResolvers,\n\tMutationSignInPasswordlessArgs,\n\tSignInPasswordlessResponse,\n} from '../../../schema/index.js'\nimport { TenantResolverContext } from '../../TenantResolverContext.js'\nimport { CaptchaValidator, ConfigurationQuery, PermissionActions, RateLimiter } from '../../../model/index.js'\nimport { createErrorResponse } from '../../errorUtils.js'\nimport { SignInResponseFactory } from '../../responseHelpers/SignInResponseFactory.js'\nimport { PasswordlessSignInManager } from '../../../model/service/PasswordlessSignInManager.js'\n\nexport class PasswordlessMutationResolver\n\timplements Pick<MutationResolvers, 'initSignInPasswordless' | 'signInPasswordless' | 'activatePasswordlessOtp'>\n{\n\tconstructor(\n\t\tprivate readonly passwordlessSignInManager: PasswordlessSignInManager,\n\t\tprivate readonly signInResponseFactory: SignInResponseFactory,\n\t\tprivate readonly captchaValidator: CaptchaValidator,\n\t\tprivate readonly rateLimiter: RateLimiter,\n\t) {}\n\n\tasync initSignInPasswordless(\n\t\tparent: any,\n\t\targs: MutationInitSignInPasswordlessArgs,\n\t\tcontext: TenantResolverContext,\n\t): Promise<InitSignInPasswordlessResponse> {\n\t\tawait context.requireAccess({\n\t\t\taction: PermissionActions.PERSON_REQUEST_PASSWORDLESS_SIGN_IN,\n\t\t\tmessage: 'You are not allowed to request passwordless sign in',\n\t\t})\n\n\t\tconst configuration = await context.db.queryHandler.fetch(new ConfigurationQuery(context.db.providers))\n\n\t\tconst rl = await this.rateLimiter.consume(context.db, 'passwordless_init_per_ip', context.httpInfo.ip, configuration)\n\t\tif (!rl.ok) {\n\t\t\treturn createErrorResponse('RATE_LIMIT_EXCEEDED', `Too many passwordless sign-in requests. Retry after ${rl.retryAfterSeconds}s.`)\n\t\t}\n\n\t\tconst captchaConfig = this.captchaValidator.extractConfig(configuration)\n\t\tif (this.captchaValidator.isEnabledFor(captchaConfig, 'passwordlessInit')) {\n\t\t\tconst captcha = await this.captchaValidator.verify({\n\t\t\t\tconfig: captchaConfig,\n\t\t\t\ttoken: args.captchaToken ?? undefined,\n\t\t\t\tremoteIp: context.httpInfo.ip,\n\t\t\t})\n\t\t\tif (!captcha.ok) {\n\t\t\t\treturn createErrorResponse('INVALID_CAPTCHA', `Captcha verification failed: ${captcha.reason}`)\n\t\t\t}\n\t\t}\n\n\t\tconst result = await this.passwordlessSignInManager.initSignInPasswordless({\n\t\t\tdb: context.db,\n\t\t\tpermissionContext: context.permissionContext,\n\t\t\temail: args.email,\n\t\t\tmailVariant: args.options?.mailVariant ?? undefined,\n\t\t\tmailProject: args.options?.mailProject ?? undefined,\n\t\t})\n\t\tawait context.logAuthAction({\n\t\t\ttype: 'passwordless_login_init',\n\t\t\tresponse: result,\n\t\t})\n\t\tif (!result.ok) {\n\t\t\tif (result.error === 'PERSON_NOT_FOUND' && !configuration.login.revealUserExists) {\n\t\t\t\t// If the user does not exist, we don't want to reveal that\n\t\t\t\treturn createErrorResponse('PASSWORDLESS_DISABLED', 'Passwordless sign-in is disabled for this person')\n\t\t\t}\n\t\t\t// Naming the provider tells the caller both that the person exists and how\n\t\t\t// they authenticate, so it needs both reveal flags. PASSWORDLESS_DISABLED is\n\t\t\t// the neutral answer this endpoint already gives for an unknown address.\n\t\t\tconst { revealUserExists, revealLoginMethod } = configuration.login\n\t\t\tif (result.error === 'IDP_REQUIRED' && !(revealUserExists && revealLoginMethod)) {\n\t\t\t\treturn createErrorResponse('PASSWORDLESS_DISABLED', 'Passwordless sign-in is disabled for this person')\n\t\t\t}\n\n\t\t\treturn createErrorResponse(result.error, result.errorMessage)\n\t\t}\n\t\treturn { ok: true, result: result.result }\n\t}\n\n\tasync signInPasswordless(parent: any, args: MutationSignInPasswordlessArgs, context: TenantResolverContext): Promise<SignInPasswordlessResponse> {\n\t\tawait context.requireAccess({\n\t\t\taction: PermissionActions.PERSON_PASSWORDLESS_SIGN_IN,\n\t\t\tmessage: 'You are not allowed to sign in using passwordless',\n\t\t})\n\t\tconst signIn = await this.passwordlessSignInManager.signInPasswordless({\n\t\t\tdb: context.db,\n\t\t\trequestId: args.requestId,\n\t\t\ttoken: args.token,\n\t\t\tmfaOtp: args.mfaOtp ?? undefined,\n\t\t\tbackupCode: args.backupCode ?? undefined,\n\t\t\tvalidationType: args.validationType,\n\t\t\texpiration: args.expiration ?? undefined,\n\t\t\trequestInfo: context.httpInfo,\n\t\t\ttrustForwardedInfo: args.options?.trustForwardedClientInfo === true && context.trustForwardedInfo,\n\t\t})\n\t\tawait context.logAuthAction({\n\t\t\ttype: 'passwordless_login',\n\t\t\tresponse: signIn,\n\t\t})\n\t\tif (signIn.ok && signIn.result.usedBackupCode) {\n\t\t\tawait context.logAuthAction({\n\t\t\t\ttype: 'backup_code_used',\n\t\t\t\tresponse: signIn,\n\t\t\t\tpersonId: signIn.result.person.id,\n\t\t\t})\n\t\t}\n\t\tif (!signIn.ok) {\n\t\t\treturn createErrorResponse(signIn.error, signIn.errorMessage)\n\t\t}\n\t\treturn {\n\t\t\tok: true,\n\t\t\tresult: await this.signInResponseFactory.createResponse(signIn.result, context),\n\t\t}\n\t}\n\n\tasync activatePasswordlessOtp(\n\t\tparent: any,\n\t\targs: MutationActivatePasswordlessOtpArgs,\n\t\tcontext: TenantResolverContext,\n\t): Promise<ActivatePasswordlessOtpResponse> {\n\t\tawait context.requireAccess({\n\t\t\taction: PermissionActions.PERSON_PASSWORDLESS_SIGN_IN,\n\t\t\tmessage: 'You are not allowed to activate passwordless OTP',\n\t\t})\n\t\tconst result = await this.passwordlessSignInManager.activatePasswordlessOtp({\n\t\t\tdb: context.db,\n\t\t\trequestId: args.requestId,\n\t\t\ttoken: args.token,\n\t\t\totpHash: args.otpHash,\n\t\t})\n\t\tawait context.logAuthAction({\n\t\t\ttype: 'passwordless_login_exchange',\n\t\t\tresponse: result,\n\t\t})\n\t\tif (!result.ok) {\n\t\t\treturn createErrorResponse(result.error, result.errorMessage)\n\t\t}\n\t\treturn { ok: true }\n\t}\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -111,8 +111,8 @@ class SignInMutationResolver {
|
|
|
111
111
|
}
|
|
112
112
|
if (!response.ok) {
|
|
113
113
|
const { revealUserExists, revealLoginMethod } = configuration.login;
|
|
114
|
-
const collapseAll = !revealUserExists && ["NO_PASSWORD_SET", "PERSON_DISABLED", "INVALID_PASSWORD", "UNKNOWN_EMAIL"].includes(response.error);
|
|
115
|
-
const collapseMethodOnly = revealUserExists && !revealLoginMethod && ["NO_PASSWORD_SET", "INVALID_PASSWORD"].includes(response.error);
|
|
114
|
+
const collapseAll = !revealUserExists && ["NO_PASSWORD_SET", "PERSON_DISABLED", "INVALID_PASSWORD", "UNKNOWN_EMAIL", "IDP_REQUIRED"].includes(response.error);
|
|
115
|
+
const collapseMethodOnly = revealUserExists && !revealLoginMethod && ["NO_PASSWORD_SET", "INVALID_PASSWORD", "IDP_REQUIRED"].includes(response.error);
|
|
116
116
|
if (collapseAll || collapseMethodOnly) {
|
|
117
117
|
return errorUtils.createErrorResponse("INVALID_CREDENTIALS", "Invalid credentials");
|
|
118
118
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SignInMutationResolver.cjs","sources":["../../../../../../../packages/engine-tenant-api/src/resolvers/mutation/person/SignInMutationResolver.ts"],"sourcesContent":["import {\n\tCreateSessionTokenResponse,\n\tMutationCreateSessionTokenArgs,\n\tMutationResolvers,\n\tMutationSignInArgs,\n\tSignInResponse,\n} from '../../../schema/index.js'\nimport { TenantResolverContext } from '../../TenantResolverContext.js'\nimport { ConfigurationQuery, PermissionActions, PersonUniqueIdentifier, RateLimiter, SignInManager } from '../../../model/index.js'\nimport { createErrorResponse } from '../../errorUtils.js'\nimport { SignInResponseFactory } from '../../responseHelpers/SignInResponseFactory.js'\nimport { UserInputError } from '@contember/graphql-utils'\nimport { NextLoginAttemptQuery } from '../../../model/queries/authLog/NextLoginAttemptQuery.js'\nimport { ResponseError } from '../../../model/utils/Response.js'\n\nexport class SignInMutationResolver implements MutationResolvers {\n\tconstructor(\n\t\tprivate readonly signInManager: SignInManager,\n\t\tprivate readonly signInResponseFactory: SignInResponseFactory,\n\t\tprivate readonly rateLimiter: RateLimiter,\n\t) {}\n\n\tasync signIn(parent: any, args: MutationSignInArgs, context: TenantResolverContext): Promise<SignInResponse> {\n\t\tawait context.requireAccess({\n\t\t\taction: PermissionActions.PERSON_SIGN_IN,\n\t\t\tmessage: 'You are not allowed to sign in',\n\t\t})\n\n\t\tconst configuration = await context.db.queryHandler.fetch(new ConfigurationQuery(context.db.providers))\n\n\t\tconst ipGate = await this.rateLimiter.consume(context.db, 'login_per_ip', context.httpInfo.ip, configuration)\n\t\tif (!ipGate.ok) {\n\t\t\treturn createErrorResponse(\n\t\t\t\tnew ResponseError('RATE_LIMIT_EXCEEDED', `Too many sign-in attempts from this IP. Retry after ${ipGate.retryAfterSeconds}s.`, {\n\t\t\t\t\tretryAfter: ipGate.retryAfterSeconds,\n\t\t\t\t}),\n\t\t\t)\n\t\t}\n\n\t\tconst nextAllowedSignIn = await context.db.queryHandler.fetch(new NextLoginAttemptQuery(args.email))\n\t\tif (nextAllowedSignIn > new Date()) {\n\t\t\treturn createErrorResponse(\n\t\t\t\tnew ResponseError('RATE_LIMIT_EXCEEDED', `Too many attempts, please try again later.`, {\n\t\t\t\t\tretryAfter: Math.ceil((nextAllowedSignIn.getTime() - Date.now()) / 1000),\n\t\t\t\t}),\n\t\t\t)\n\t\t}\n\n\t\tconst response = await this.signInManager.signIn(\n\t\t\tcontext.db,\n\t\t\targs.email,\n\t\t\targs.password,\n\t\t\targs.expiration || undefined,\n\t\t\targs.otpToken || undefined,\n\t\t\tcontext.httpInfo,\n\t\t\targs.options?.trustForwardedClientInfo === true && context.trustForwardedInfo,\n\t\t\targs.backupCode || undefined,\n\t\t\tconfiguration,\n\t\t\tcontext.httpInfo.geoCountry,\n\t\t)\n\t\tawait context.logAuthAction({\n\t\t\ttype: 'login',\n\t\t\tresponse,\n\t\t})\n\t\tif (response.ok && response.result.usedBackupCode) {\n\t\t\tawait context.logAuthAction({\n\t\t\t\ttype: 'backup_code_used',\n\t\t\t\tresponse,\n\t\t\t\tpersonId: response.result.person.id,\n\t\t\t})\n\t\t}\n\t\tif (!response.ok && response.metadata?.emailOtpSent) {\n\t\t\tawait context.logAuthAction({\n\t\t\t\ttype: 'email_otp_sent',\n\t\t\t\tresponse,\n\t\t\t})\n\t\t}\n\t\tif (!response.ok && response.metadata?.mfaEnrollmentRequired) {\n\t\t\tawait context.logAuthAction({\n\t\t\t\ttype: 'mfa_enrollment_required',\n\t\t\t\tresponse,\n\t\t\t})\n\t\t}\n\t\t// A03 anomaly audit. The risk scoring runs in SignInManager; the audit\n\t\t// entries are emitted here at the resolver layer (per convention), with the\n\t\t// score + reasons snapshot in event_data.\n\t\tif (response.ok && response.result.unusualLoginDetected) {\n\t\t\tawait context.logAuthAction({\n\t\t\t\ttype: 'unusual_login_detected',\n\t\t\t\tresponse,\n\t\t\t\tpersonId: response.result.person.id,\n\t\t\t\teventData: response.result.risk ? { ...response.result.risk } : undefined,\n\t\t\t})\n\t\t}\n\t\tif (!response.ok && response.metadata?.stepUpRequired) {\n\t\t\tawait context.logAuthAction({\n\t\t\t\ttype: 'unusual_login_detected',\n\t\t\t\tresponse,\n\t\t\t\teventData: response.metadata.risk ? { ...response.metadata.risk } : undefined,\n\t\t\t})\n\t\t\tawait context.logAuthAction({\n\t\t\t\ttype: 'step_up_required',\n\t\t\t\tresponse,\n\t\t\t\teventData: response.metadata.risk ? { ...response.metadata.risk } : undefined,\n\t\t\t})\n\t\t}\n\n\t\tif (!response.ok) {\n\t\t\tconst { revealUserExists, revealLoginMethod } = configuration.login\n\t\t\tconst collapseAll = !revealUserExists\n\t\t\t\t&& ['NO_PASSWORD_SET', 'PERSON_DISABLED', 'INVALID_PASSWORD', 'UNKNOWN_EMAIL'].includes(response.error)\n\t\t\tconst collapseMethodOnly = revealUserExists\n\t\t\t\t&& !revealLoginMethod\n\t\t\t\t&& ['NO_PASSWORD_SET', 'INVALID_PASSWORD'].includes(response.error)\n\t\t\tif (collapseAll || collapseMethodOnly) {\n\t\t\t\treturn createErrorResponse('INVALID_CREDENTIALS', 'Invalid credentials')\n\t\t\t}\n\t\t\t// Preserve the additive mfaEnrollment payload on MFA_ENROLLMENT_REQUIRED (A06).\n\t\t\tif (response.error === 'MFA_ENROLLMENT_REQUIRED' && response.metadata?.mfaEnrollment) {\n\t\t\t\tconst error = {\n\t\t\t\t\tcode: response.error,\n\t\t\t\t\tdeveloperMessage: response.errorMessage,\n\t\t\t\t\tmfaEnrollment: response.metadata.mfaEnrollment,\n\t\t\t\t}\n\t\t\t\treturn { ok: false, error, errors: [error] }\n\t\t\t}\n\t\t\treturn createErrorResponse(response.error, response.errorMessage)\n\t\t}\n\n\t\treturn {\n\t\t\tok: true,\n\t\t\terrors: [],\n\t\t\tresult: await this.signInResponseFactory.createResponse(response.result, context),\n\t\t}\n\t}\n\n\tasync createSessionToken(parent: any, args: MutationCreateSessionTokenArgs, context: TenantResolverContext): Promise<CreateSessionTokenResponse> {\n\t\tawait context.requireAccess({\n\t\t\taction: PermissionActions.PERSON_CREATE_SESSION_KEY(),\n\t\t\tmessage: 'You are not allowed to create a session key',\n\t\t})\n\t\tlet identifier: PersonUniqueIdentifier\n\t\tif (args.email) {\n\t\t\tidentifier = { type: 'email', email: args.email }\n\t\t} else if (args.personId) {\n\t\t\tidentifier = { type: 'id', id: args.personId }\n\t\t} else {\n\t\t\tthrow new UserInputError(`Please provide either email or personId`)\n\t\t}\n\n\t\tconst response = await this.signInManager.createSessionToken(\n\t\t\tcontext.db,\n\t\t\tidentifier,\n\t\t\targs.expiration || undefined,\n\t\t\tasync person =>\n\t\t\t\tawait context.requireAccess({\n\t\t\t\t\taction: PermissionActions.PERSON_CREATE_SESSION_KEY(person.roles),\n\t\t\t\t\tmessage: 'You are not allowed to create a session key for this person.',\n\t\t\t\t}),\n\t\t\tcontext.httpInfo,\n\t\t\targs.options?.trustForwardedClientInfo === true && context.trustForwardedInfo,\n\t\t)\n\t\tawait context.logAuthAction({\n\t\t\ttype: 'create_session_token',\n\t\t\tresponse: response,\n\t\t})\n\n\t\tif (!response.ok) {\n\t\t\treturn createErrorResponse(response.error, response.errorMessage)\n\t\t}\n\n\t\treturn {\n\t\t\tok: true,\n\t\t\tresult: await this.signInResponseFactory.createResponse(response.result, context),\n\t\t}\n\t}\n}\n"],"names":["PermissionActions","ConfigurationQuery","createErrorResponse","ResponseError","NextLoginAttemptQuery","UserInputError"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAeO,MAAM,uBAAoD;AAAA,EAChE,YACkB,eACA,uBACA,aAChB;AAHgB,SAAA,gBAAA;AACA,SAAA,wBAAA;AACA,SAAA,cAAA;AAAA,EACf;AAAA,EAEH,MAAM,OAAO,QAAa,MAA0B,SAAyD;AAC5G,UAAM,QAAQ,cAAc;AAAA,MAC3B,QAAQA,kBAAAA,kBAAkB;AAAA,MAC1B,SAAS;AAAA,IAAA,CACT;AAED,UAAM,gBAAgB,MAAM,QAAQ,GAAG,aAAa,MAAM,IAAIC,sCAAmB,QAAQ,GAAG,SAAS,CAAC;AAEtG,UAAM,SAAS,MAAM,KAAK,YAAY,QAAQ,QAAQ,IAAI,gBAAgB,QAAQ,SAAS,IAAI,aAAa;AAC5G,QAAI,CAAC,OAAO,IAAI;AACf,aAAOC,WAAAA;AAAAA,QACN,IAAIC,SAAAA,cAAc,uBAAuB,uDAAuD,OAAO,iBAAiB,MAAM;AAAA,UAC7H,YAAY,OAAO;AAAA,QAAA,CACnB;AAAA,MAAA;AAAA,IAEH;AAEA,UAAM,oBAAoB,MAAM,QAAQ,GAAG,aAAa,MAAM,IAAIC,sBAAAA,sBAAsB,KAAK,KAAK,CAAC;AACnG,QAAI,oBAAoB,oBAAI,QAAQ;AACnC,aAAOF,WAAAA;AAAAA,QACN,IAAIC,uBAAc,uBAAuB,8CAA8C;AAAA,UACtF,YAAY,KAAK,MAAM,kBAAkB,YAAY,KAAK,IAAA,KAAS,GAAI;AAAA,QAAA,CACvE;AAAA,MAAA;AAAA,IAEH;AAEA,UAAM,WAAW,MAAM,KAAK,cAAc;AAAA,MACzC,QAAQ;AAAA,MACR,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK,cAAc;AAAA,MACnB,KAAK,YAAY;AAAA,MACjB,QAAQ;AAAA,MACR,KAAK,SAAS,6BAA6B,QAAQ,QAAQ;AAAA,MAC3D,KAAK,cAAc;AAAA,MACnB;AAAA,MACA,QAAQ,SAAS;AAAA,IAAA;AAElB,UAAM,QAAQ,cAAc;AAAA,MAC3B,MAAM;AAAA,MACN;AAAA,IAAA,CACA;AACD,QAAI,SAAS,MAAM,SAAS,OAAO,gBAAgB;AAClD,YAAM,QAAQ,cAAc;AAAA,QAC3B,MAAM;AAAA,QACN;AAAA,QACA,UAAU,SAAS,OAAO,OAAO;AAAA,MAAA,CACjC;AAAA,IACF;AACA,QAAI,CAAC,SAAS,MAAM,SAAS,UAAU,cAAc;AACpD,YAAM,QAAQ,cAAc;AAAA,QAC3B,MAAM;AAAA,QACN;AAAA,MAAA,CACA;AAAA,IACF;AACA,QAAI,CAAC,SAAS,MAAM,SAAS,UAAU,uBAAuB;AAC7D,YAAM,QAAQ,cAAc;AAAA,QAC3B,MAAM;AAAA,QACN;AAAA,MAAA,CACA;AAAA,IACF;AAIA,QAAI,SAAS,MAAM,SAAS,OAAO,sBAAsB;AACxD,YAAM,QAAQ,cAAc;AAAA,QAC3B,MAAM;AAAA,QACN;AAAA,QACA,UAAU,SAAS,OAAO,OAAO;AAAA,QACjC,WAAW,SAAS,OAAO,OAAO,EAAE,GAAG,SAAS,OAAO,SAAS;AAAA,MAAA,CAChE;AAAA,IACF;AACA,QAAI,CAAC,SAAS,MAAM,SAAS,UAAU,gBAAgB;AACtD,YAAM,QAAQ,cAAc;AAAA,QAC3B,MAAM;AAAA,QACN;AAAA,QACA,WAAW,SAAS,SAAS,OAAO,EAAE,GAAG,SAAS,SAAS,SAAS;AAAA,MAAA,CACpE;AACD,YAAM,QAAQ,cAAc;AAAA,QAC3B,MAAM;AAAA,QACN;AAAA,QACA,WAAW,SAAS,SAAS,OAAO,EAAE,GAAG,SAAS,SAAS,SAAS;AAAA,MAAA,CACpE;AAAA,IACF;AAEA,QAAI,CAAC,SAAS,IAAI;AACjB,YAAM,EAAE,kBAAkB,kBAAA,IAAsB,cAAc;AAC9D,YAAM,cAAc,CAAC,oBACjB,CAAC,mBAAmB,mBAAmB,oBAAoB,eAAe,EAAE,SAAS,SAAS,KAAK;AACvG,YAAM,qBAAqB,oBACvB,CAAC,qBACD,CAAC,mBAAmB,kBAAkB,EAAE,SAAS,SAAS,KAAK;AACnE,UAAI,eAAe,oBAAoB;AACtC,eAAOD,WAAAA,oBAAoB,uBAAuB,qBAAqB;AAAA,MACxE;AAEA,UAAI,SAAS,UAAU,6BAA6B,SAAS,UAAU,eAAe;AACrF,cAAM,QAAQ;AAAA,UACb,MAAM,SAAS;AAAA,UACf,kBAAkB,SAAS;AAAA,UAC3B,eAAe,SAAS,SAAS;AAAA,QAAA;AAElC,eAAO,EAAE,IAAI,OAAO,OAAO,QAAQ,CAAC,KAAK,EAAA;AAAA,MAC1C;AACA,aAAOA,WAAAA,oBAAoB,SAAS,OAAO,SAAS,YAAY;AAAA,IACjE;AAEA,WAAO;AAAA,MACN,IAAI;AAAA,MACJ,QAAQ,CAAA;AAAA,MACR,QAAQ,MAAM,KAAK,sBAAsB,eAAe,SAAS,QAAQ,OAAO;AAAA,IAAA;AAAA,EAElF;AAAA,EAEA,MAAM,mBAAmB,QAAa,MAAsC,SAAqE;AAChJ,UAAM,QAAQ,cAAc;AAAA,MAC3B,QAAQF,kBAAAA,kBAAkB,0BAAA;AAAA,MAC1B,SAAS;AAAA,IAAA,CACT;AACD,QAAI;AACJ,QAAI,KAAK,OAAO;AACf,mBAAa,EAAE,MAAM,SAAS,OAAO,KAAK,MAAA;AAAA,IAC3C,WAAW,KAAK,UAAU;AACzB,mBAAa,EAAE,MAAM,MAAM,IAAI,KAAK,SAAA;AAAA,IACrC,OAAO;AACN,YAAM,IAAIK,aAAAA,eAAe,yCAAyC;AAAA,IACnE;AAEA,UAAM,WAAW,MAAM,KAAK,cAAc;AAAA,MACzC,QAAQ;AAAA,MACR;AAAA,MACA,KAAK,cAAc;AAAA,MACnB,OAAM,WACL,MAAM,QAAQ,cAAc;AAAA,QAC3B,QAAQL,kBAAAA,kBAAkB,0BAA0B,OAAO,KAAK;AAAA,QAChE,SAAS;AAAA,MAAA,CACT;AAAA,MACF,QAAQ;AAAA,MACR,KAAK,SAAS,6BAA6B,QAAQ,QAAQ;AAAA,IAAA;AAE5D,UAAM,QAAQ,cAAc;AAAA,MAC3B,MAAM;AAAA,MACN;AAAA,IAAA,CACA;AAED,QAAI,CAAC,SAAS,IAAI;AACjB,aAAOE,WAAAA,oBAAoB,SAAS,OAAO,SAAS,YAAY;AAAA,IACjE;AAEA,WAAO;AAAA,MACN,IAAI;AAAA,MACJ,QAAQ,MAAM,KAAK,sBAAsB,eAAe,SAAS,QAAQ,OAAO;AAAA,IAAA;AAAA,EAElF;AACD;;"}
|
|
1
|
+
{"version":3,"file":"SignInMutationResolver.cjs","sources":["../../../../../../../packages/engine-tenant-api/src/resolvers/mutation/person/SignInMutationResolver.ts"],"sourcesContent":["import {\n\tCreateSessionTokenResponse,\n\tMutationCreateSessionTokenArgs,\n\tMutationResolvers,\n\tMutationSignInArgs,\n\tSignInResponse,\n} from '../../../schema/index.js'\nimport { TenantResolverContext } from '../../TenantResolverContext.js'\nimport { ConfigurationQuery, PermissionActions, PersonUniqueIdentifier, RateLimiter, SignInManager } from '../../../model/index.js'\nimport { createErrorResponse } from '../../errorUtils.js'\nimport { SignInResponseFactory } from '../../responseHelpers/SignInResponseFactory.js'\nimport { UserInputError } from '@contember/graphql-utils'\nimport { NextLoginAttemptQuery } from '../../../model/queries/authLog/NextLoginAttemptQuery.js'\nimport { ResponseError } from '../../../model/utils/Response.js'\n\nexport class SignInMutationResolver implements MutationResolvers {\n\tconstructor(\n\t\tprivate readonly signInManager: SignInManager,\n\t\tprivate readonly signInResponseFactory: SignInResponseFactory,\n\t\tprivate readonly rateLimiter: RateLimiter,\n\t) {}\n\n\tasync signIn(parent: any, args: MutationSignInArgs, context: TenantResolverContext): Promise<SignInResponse> {\n\t\tawait context.requireAccess({\n\t\t\taction: PermissionActions.PERSON_SIGN_IN,\n\t\t\tmessage: 'You are not allowed to sign in',\n\t\t})\n\n\t\tconst configuration = await context.db.queryHandler.fetch(new ConfigurationQuery(context.db.providers))\n\n\t\tconst ipGate = await this.rateLimiter.consume(context.db, 'login_per_ip', context.httpInfo.ip, configuration)\n\t\tif (!ipGate.ok) {\n\t\t\treturn createErrorResponse(\n\t\t\t\tnew ResponseError('RATE_LIMIT_EXCEEDED', `Too many sign-in attempts from this IP. Retry after ${ipGate.retryAfterSeconds}s.`, {\n\t\t\t\t\tretryAfter: ipGate.retryAfterSeconds,\n\t\t\t\t}),\n\t\t\t)\n\t\t}\n\n\t\tconst nextAllowedSignIn = await context.db.queryHandler.fetch(new NextLoginAttemptQuery(args.email))\n\t\tif (nextAllowedSignIn > new Date()) {\n\t\t\treturn createErrorResponse(\n\t\t\t\tnew ResponseError('RATE_LIMIT_EXCEEDED', `Too many attempts, please try again later.`, {\n\t\t\t\t\tretryAfter: Math.ceil((nextAllowedSignIn.getTime() - Date.now()) / 1000),\n\t\t\t\t}),\n\t\t\t)\n\t\t}\n\n\t\tconst response = await this.signInManager.signIn(\n\t\t\tcontext.db,\n\t\t\targs.email,\n\t\t\targs.password,\n\t\t\targs.expiration || undefined,\n\t\t\targs.otpToken || undefined,\n\t\t\tcontext.httpInfo,\n\t\t\targs.options?.trustForwardedClientInfo === true && context.trustForwardedInfo,\n\t\t\targs.backupCode || undefined,\n\t\t\tconfiguration,\n\t\t\tcontext.httpInfo.geoCountry,\n\t\t)\n\t\tawait context.logAuthAction({\n\t\t\ttype: 'login',\n\t\t\tresponse,\n\t\t})\n\t\tif (response.ok && response.result.usedBackupCode) {\n\t\t\tawait context.logAuthAction({\n\t\t\t\ttype: 'backup_code_used',\n\t\t\t\tresponse,\n\t\t\t\tpersonId: response.result.person.id,\n\t\t\t})\n\t\t}\n\t\tif (!response.ok && response.metadata?.emailOtpSent) {\n\t\t\tawait context.logAuthAction({\n\t\t\t\ttype: 'email_otp_sent',\n\t\t\t\tresponse,\n\t\t\t})\n\t\t}\n\t\tif (!response.ok && response.metadata?.mfaEnrollmentRequired) {\n\t\t\tawait context.logAuthAction({\n\t\t\t\ttype: 'mfa_enrollment_required',\n\t\t\t\tresponse,\n\t\t\t})\n\t\t}\n\t\t// A03 anomaly audit. The risk scoring runs in SignInManager; the audit\n\t\t// entries are emitted here at the resolver layer (per convention), with the\n\t\t// score + reasons snapshot in event_data.\n\t\tif (response.ok && response.result.unusualLoginDetected) {\n\t\t\tawait context.logAuthAction({\n\t\t\t\ttype: 'unusual_login_detected',\n\t\t\t\tresponse,\n\t\t\t\tpersonId: response.result.person.id,\n\t\t\t\teventData: response.result.risk ? { ...response.result.risk } : undefined,\n\t\t\t})\n\t\t}\n\t\tif (!response.ok && response.metadata?.stepUpRequired) {\n\t\t\tawait context.logAuthAction({\n\t\t\t\ttype: 'unusual_login_detected',\n\t\t\t\tresponse,\n\t\t\t\teventData: response.metadata.risk ? { ...response.metadata.risk } : undefined,\n\t\t\t})\n\t\t\tawait context.logAuthAction({\n\t\t\t\ttype: 'step_up_required',\n\t\t\t\tresponse,\n\t\t\t\teventData: response.metadata.risk ? { ...response.metadata.risk } : undefined,\n\t\t\t})\n\t\t}\n\n\t\tif (!response.ok) {\n\t\t\tconst { revealUserExists, revealLoginMethod } = configuration.login\n\t\t\tconst collapseAll = !revealUserExists\n\t\t\t\t&& ['NO_PASSWORD_SET', 'PERSON_DISABLED', 'INVALID_PASSWORD', 'UNKNOWN_EMAIL', 'IDP_REQUIRED'].includes(response.error)\n\t\t\tconst collapseMethodOnly = revealUserExists\n\t\t\t\t&& !revealLoginMethod\n\t\t\t\t&& ['NO_PASSWORD_SET', 'INVALID_PASSWORD', 'IDP_REQUIRED'].includes(response.error)\n\t\t\tif (collapseAll || collapseMethodOnly) {\n\t\t\t\treturn createErrorResponse('INVALID_CREDENTIALS', 'Invalid credentials')\n\t\t\t}\n\t\t\t// Preserve the additive mfaEnrollment payload on MFA_ENROLLMENT_REQUIRED (A06).\n\t\t\tif (response.error === 'MFA_ENROLLMENT_REQUIRED' && response.metadata?.mfaEnrollment) {\n\t\t\t\tconst error = {\n\t\t\t\t\tcode: response.error,\n\t\t\t\t\tdeveloperMessage: response.errorMessage,\n\t\t\t\t\tmfaEnrollment: response.metadata.mfaEnrollment,\n\t\t\t\t}\n\t\t\t\treturn { ok: false, error, errors: [error] }\n\t\t\t}\n\t\t\treturn createErrorResponse(response.error, response.errorMessage)\n\t\t}\n\n\t\treturn {\n\t\t\tok: true,\n\t\t\terrors: [],\n\t\t\tresult: await this.signInResponseFactory.createResponse(response.result, context),\n\t\t}\n\t}\n\n\tasync createSessionToken(parent: any, args: MutationCreateSessionTokenArgs, context: TenantResolverContext): Promise<CreateSessionTokenResponse> {\n\t\tawait context.requireAccess({\n\t\t\taction: PermissionActions.PERSON_CREATE_SESSION_KEY(),\n\t\t\tmessage: 'You are not allowed to create a session key',\n\t\t})\n\t\tlet identifier: PersonUniqueIdentifier\n\t\tif (args.email) {\n\t\t\tidentifier = { type: 'email', email: args.email }\n\t\t} else if (args.personId) {\n\t\t\tidentifier = { type: 'id', id: args.personId }\n\t\t} else {\n\t\t\tthrow new UserInputError(`Please provide either email or personId`)\n\t\t}\n\n\t\tconst response = await this.signInManager.createSessionToken(\n\t\t\tcontext.db,\n\t\t\tidentifier,\n\t\t\targs.expiration || undefined,\n\t\t\tasync person =>\n\t\t\t\tawait context.requireAccess({\n\t\t\t\t\taction: PermissionActions.PERSON_CREATE_SESSION_KEY(person.roles),\n\t\t\t\t\tmessage: 'You are not allowed to create a session key for this person.',\n\t\t\t\t}),\n\t\t\tcontext.httpInfo,\n\t\t\targs.options?.trustForwardedClientInfo === true && context.trustForwardedInfo,\n\t\t)\n\t\tawait context.logAuthAction({\n\t\t\ttype: 'create_session_token',\n\t\t\tresponse: response,\n\t\t})\n\n\t\tif (!response.ok) {\n\t\t\treturn createErrorResponse(response.error, response.errorMessage)\n\t\t}\n\n\t\treturn {\n\t\t\tok: true,\n\t\t\tresult: await this.signInResponseFactory.createResponse(response.result, context),\n\t\t}\n\t}\n}\n"],"names":["PermissionActions","ConfigurationQuery","createErrorResponse","ResponseError","NextLoginAttemptQuery","UserInputError"],"mappings":"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"}
|
|
@@ -109,8 +109,8 @@ class SignInMutationResolver {
|
|
|
109
109
|
}
|
|
110
110
|
if (!response.ok) {
|
|
111
111
|
const { revealUserExists, revealLoginMethod } = configuration.login;
|
|
112
|
-
const collapseAll = !revealUserExists && ["NO_PASSWORD_SET", "PERSON_DISABLED", "INVALID_PASSWORD", "UNKNOWN_EMAIL"].includes(response.error);
|
|
113
|
-
const collapseMethodOnly = revealUserExists && !revealLoginMethod && ["NO_PASSWORD_SET", "INVALID_PASSWORD"].includes(response.error);
|
|
112
|
+
const collapseAll = !revealUserExists && ["NO_PASSWORD_SET", "PERSON_DISABLED", "INVALID_PASSWORD", "UNKNOWN_EMAIL", "IDP_REQUIRED"].includes(response.error);
|
|
113
|
+
const collapseMethodOnly = revealUserExists && !revealLoginMethod && ["NO_PASSWORD_SET", "INVALID_PASSWORD", "IDP_REQUIRED"].includes(response.error);
|
|
114
114
|
if (collapseAll || collapseMethodOnly) {
|
|
115
115
|
return createErrorResponse("INVALID_CREDENTIALS", "Invalid credentials");
|
|
116
116
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SignInMutationResolver.js","sources":["../../../../../../../packages/engine-tenant-api/src/resolvers/mutation/person/SignInMutationResolver.ts"],"sourcesContent":["import {\n\tCreateSessionTokenResponse,\n\tMutationCreateSessionTokenArgs,\n\tMutationResolvers,\n\tMutationSignInArgs,\n\tSignInResponse,\n} from '../../../schema/index.js'\nimport { TenantResolverContext } from '../../TenantResolverContext.js'\nimport { ConfigurationQuery, PermissionActions, PersonUniqueIdentifier, RateLimiter, SignInManager } from '../../../model/index.js'\nimport { createErrorResponse } from '../../errorUtils.js'\nimport { SignInResponseFactory } from '../../responseHelpers/SignInResponseFactory.js'\nimport { UserInputError } from '@contember/graphql-utils'\nimport { NextLoginAttemptQuery } from '../../../model/queries/authLog/NextLoginAttemptQuery.js'\nimport { ResponseError } from '../../../model/utils/Response.js'\n\nexport class SignInMutationResolver implements MutationResolvers {\n\tconstructor(\n\t\tprivate readonly signInManager: SignInManager,\n\t\tprivate readonly signInResponseFactory: SignInResponseFactory,\n\t\tprivate readonly rateLimiter: RateLimiter,\n\t) {}\n\n\tasync signIn(parent: any, args: MutationSignInArgs, context: TenantResolverContext): Promise<SignInResponse> {\n\t\tawait context.requireAccess({\n\t\t\taction: PermissionActions.PERSON_SIGN_IN,\n\t\t\tmessage: 'You are not allowed to sign in',\n\t\t})\n\n\t\tconst configuration = await context.db.queryHandler.fetch(new ConfigurationQuery(context.db.providers))\n\n\t\tconst ipGate = await this.rateLimiter.consume(context.db, 'login_per_ip', context.httpInfo.ip, configuration)\n\t\tif (!ipGate.ok) {\n\t\t\treturn createErrorResponse(\n\t\t\t\tnew ResponseError('RATE_LIMIT_EXCEEDED', `Too many sign-in attempts from this IP. Retry after ${ipGate.retryAfterSeconds}s.`, {\n\t\t\t\t\tretryAfter: ipGate.retryAfterSeconds,\n\t\t\t\t}),\n\t\t\t)\n\t\t}\n\n\t\tconst nextAllowedSignIn = await context.db.queryHandler.fetch(new NextLoginAttemptQuery(args.email))\n\t\tif (nextAllowedSignIn > new Date()) {\n\t\t\treturn createErrorResponse(\n\t\t\t\tnew ResponseError('RATE_LIMIT_EXCEEDED', `Too many attempts, please try again later.`, {\n\t\t\t\t\tretryAfter: Math.ceil((nextAllowedSignIn.getTime() - Date.now()) / 1000),\n\t\t\t\t}),\n\t\t\t)\n\t\t}\n\n\t\tconst response = await this.signInManager.signIn(\n\t\t\tcontext.db,\n\t\t\targs.email,\n\t\t\targs.password,\n\t\t\targs.expiration || undefined,\n\t\t\targs.otpToken || undefined,\n\t\t\tcontext.httpInfo,\n\t\t\targs.options?.trustForwardedClientInfo === true && context.trustForwardedInfo,\n\t\t\targs.backupCode || undefined,\n\t\t\tconfiguration,\n\t\t\tcontext.httpInfo.geoCountry,\n\t\t)\n\t\tawait context.logAuthAction({\n\t\t\ttype: 'login',\n\t\t\tresponse,\n\t\t})\n\t\tif (response.ok && response.result.usedBackupCode) {\n\t\t\tawait context.logAuthAction({\n\t\t\t\ttype: 'backup_code_used',\n\t\t\t\tresponse,\n\t\t\t\tpersonId: response.result.person.id,\n\t\t\t})\n\t\t}\n\t\tif (!response.ok && response.metadata?.emailOtpSent) {\n\t\t\tawait context.logAuthAction({\n\t\t\t\ttype: 'email_otp_sent',\n\t\t\t\tresponse,\n\t\t\t})\n\t\t}\n\t\tif (!response.ok && response.metadata?.mfaEnrollmentRequired) {\n\t\t\tawait context.logAuthAction({\n\t\t\t\ttype: 'mfa_enrollment_required',\n\t\t\t\tresponse,\n\t\t\t})\n\t\t}\n\t\t// A03 anomaly audit. The risk scoring runs in SignInManager; the audit\n\t\t// entries are emitted here at the resolver layer (per convention), with the\n\t\t// score + reasons snapshot in event_data.\n\t\tif (response.ok && response.result.unusualLoginDetected) {\n\t\t\tawait context.logAuthAction({\n\t\t\t\ttype: 'unusual_login_detected',\n\t\t\t\tresponse,\n\t\t\t\tpersonId: response.result.person.id,\n\t\t\t\teventData: response.result.risk ? { ...response.result.risk } : undefined,\n\t\t\t})\n\t\t}\n\t\tif (!response.ok && response.metadata?.stepUpRequired) {\n\t\t\tawait context.logAuthAction({\n\t\t\t\ttype: 'unusual_login_detected',\n\t\t\t\tresponse,\n\t\t\t\teventData: response.metadata.risk ? { ...response.metadata.risk } : undefined,\n\t\t\t})\n\t\t\tawait context.logAuthAction({\n\t\t\t\ttype: 'step_up_required',\n\t\t\t\tresponse,\n\t\t\t\teventData: response.metadata.risk ? { ...response.metadata.risk } : undefined,\n\t\t\t})\n\t\t}\n\n\t\tif (!response.ok) {\n\t\t\tconst { revealUserExists, revealLoginMethod } = configuration.login\n\t\t\tconst collapseAll = !revealUserExists\n\t\t\t\t&& ['NO_PASSWORD_SET', 'PERSON_DISABLED', 'INVALID_PASSWORD', 'UNKNOWN_EMAIL'].includes(response.error)\n\t\t\tconst collapseMethodOnly = revealUserExists\n\t\t\t\t&& !revealLoginMethod\n\t\t\t\t&& ['NO_PASSWORD_SET', 'INVALID_PASSWORD'].includes(response.error)\n\t\t\tif (collapseAll || collapseMethodOnly) {\n\t\t\t\treturn createErrorResponse('INVALID_CREDENTIALS', 'Invalid credentials')\n\t\t\t}\n\t\t\t// Preserve the additive mfaEnrollment payload on MFA_ENROLLMENT_REQUIRED (A06).\n\t\t\tif (response.error === 'MFA_ENROLLMENT_REQUIRED' && response.metadata?.mfaEnrollment) {\n\t\t\t\tconst error = {\n\t\t\t\t\tcode: response.error,\n\t\t\t\t\tdeveloperMessage: response.errorMessage,\n\t\t\t\t\tmfaEnrollment: response.metadata.mfaEnrollment,\n\t\t\t\t}\n\t\t\t\treturn { ok: false, error, errors: [error] }\n\t\t\t}\n\t\t\treturn createErrorResponse(response.error, response.errorMessage)\n\t\t}\n\n\t\treturn {\n\t\t\tok: true,\n\t\t\terrors: [],\n\t\t\tresult: await this.signInResponseFactory.createResponse(response.result, context),\n\t\t}\n\t}\n\n\tasync createSessionToken(parent: any, args: MutationCreateSessionTokenArgs, context: TenantResolverContext): Promise<CreateSessionTokenResponse> {\n\t\tawait context.requireAccess({\n\t\t\taction: PermissionActions.PERSON_CREATE_SESSION_KEY(),\n\t\t\tmessage: 'You are not allowed to create a session key',\n\t\t})\n\t\tlet identifier: PersonUniqueIdentifier\n\t\tif (args.email) {\n\t\t\tidentifier = { type: 'email', email: args.email }\n\t\t} else if (args.personId) {\n\t\t\tidentifier = { type: 'id', id: args.personId }\n\t\t} else {\n\t\t\tthrow new UserInputError(`Please provide either email or personId`)\n\t\t}\n\n\t\tconst response = await this.signInManager.createSessionToken(\n\t\t\tcontext.db,\n\t\t\tidentifier,\n\t\t\targs.expiration || undefined,\n\t\t\tasync person =>\n\t\t\t\tawait context.requireAccess({\n\t\t\t\t\taction: PermissionActions.PERSON_CREATE_SESSION_KEY(person.roles),\n\t\t\t\t\tmessage: 'You are not allowed to create a session key for this person.',\n\t\t\t\t}),\n\t\t\tcontext.httpInfo,\n\t\t\targs.options?.trustForwardedClientInfo === true && context.trustForwardedInfo,\n\t\t)\n\t\tawait context.logAuthAction({\n\t\t\ttype: 'create_session_token',\n\t\t\tresponse: response,\n\t\t})\n\n\t\tif (!response.ok) {\n\t\t\treturn createErrorResponse(response.error, response.errorMessage)\n\t\t}\n\n\t\treturn {\n\t\t\tok: true,\n\t\t\tresult: await this.signInResponseFactory.createResponse(response.result, context),\n\t\t}\n\t}\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SignInMutationResolver.js","sources":["../../../../../../../packages/engine-tenant-api/src/resolvers/mutation/person/SignInMutationResolver.ts"],"sourcesContent":["import {\n\tCreateSessionTokenResponse,\n\tMutationCreateSessionTokenArgs,\n\tMutationResolvers,\n\tMutationSignInArgs,\n\tSignInResponse,\n} from '../../../schema/index.js'\nimport { TenantResolverContext } from '../../TenantResolverContext.js'\nimport { ConfigurationQuery, PermissionActions, PersonUniqueIdentifier, RateLimiter, SignInManager } from '../../../model/index.js'\nimport { createErrorResponse } from '../../errorUtils.js'\nimport { SignInResponseFactory } from '../../responseHelpers/SignInResponseFactory.js'\nimport { UserInputError } from '@contember/graphql-utils'\nimport { NextLoginAttemptQuery } from '../../../model/queries/authLog/NextLoginAttemptQuery.js'\nimport { ResponseError } from '../../../model/utils/Response.js'\n\nexport class SignInMutationResolver implements MutationResolvers {\n\tconstructor(\n\t\tprivate readonly signInManager: SignInManager,\n\t\tprivate readonly signInResponseFactory: SignInResponseFactory,\n\t\tprivate readonly rateLimiter: RateLimiter,\n\t) {}\n\n\tasync signIn(parent: any, args: MutationSignInArgs, context: TenantResolverContext): Promise<SignInResponse> {\n\t\tawait context.requireAccess({\n\t\t\taction: PermissionActions.PERSON_SIGN_IN,\n\t\t\tmessage: 'You are not allowed to sign in',\n\t\t})\n\n\t\tconst configuration = await context.db.queryHandler.fetch(new ConfigurationQuery(context.db.providers))\n\n\t\tconst ipGate = await this.rateLimiter.consume(context.db, 'login_per_ip', context.httpInfo.ip, configuration)\n\t\tif (!ipGate.ok) {\n\t\t\treturn createErrorResponse(\n\t\t\t\tnew ResponseError('RATE_LIMIT_EXCEEDED', `Too many sign-in attempts from this IP. Retry after ${ipGate.retryAfterSeconds}s.`, {\n\t\t\t\t\tretryAfter: ipGate.retryAfterSeconds,\n\t\t\t\t}),\n\t\t\t)\n\t\t}\n\n\t\tconst nextAllowedSignIn = await context.db.queryHandler.fetch(new NextLoginAttemptQuery(args.email))\n\t\tif (nextAllowedSignIn > new Date()) {\n\t\t\treturn createErrorResponse(\n\t\t\t\tnew ResponseError('RATE_LIMIT_EXCEEDED', `Too many attempts, please try again later.`, {\n\t\t\t\t\tretryAfter: Math.ceil((nextAllowedSignIn.getTime() - Date.now()) / 1000),\n\t\t\t\t}),\n\t\t\t)\n\t\t}\n\n\t\tconst response = await this.signInManager.signIn(\n\t\t\tcontext.db,\n\t\t\targs.email,\n\t\t\targs.password,\n\t\t\targs.expiration || undefined,\n\t\t\targs.otpToken || undefined,\n\t\t\tcontext.httpInfo,\n\t\t\targs.options?.trustForwardedClientInfo === true && context.trustForwardedInfo,\n\t\t\targs.backupCode || undefined,\n\t\t\tconfiguration,\n\t\t\tcontext.httpInfo.geoCountry,\n\t\t)\n\t\tawait context.logAuthAction({\n\t\t\ttype: 'login',\n\t\t\tresponse,\n\t\t})\n\t\tif (response.ok && response.result.usedBackupCode) {\n\t\t\tawait context.logAuthAction({\n\t\t\t\ttype: 'backup_code_used',\n\t\t\t\tresponse,\n\t\t\t\tpersonId: response.result.person.id,\n\t\t\t})\n\t\t}\n\t\tif (!response.ok && response.metadata?.emailOtpSent) {\n\t\t\tawait context.logAuthAction({\n\t\t\t\ttype: 'email_otp_sent',\n\t\t\t\tresponse,\n\t\t\t})\n\t\t}\n\t\tif (!response.ok && response.metadata?.mfaEnrollmentRequired) {\n\t\t\tawait context.logAuthAction({\n\t\t\t\ttype: 'mfa_enrollment_required',\n\t\t\t\tresponse,\n\t\t\t})\n\t\t}\n\t\t// A03 anomaly audit. The risk scoring runs in SignInManager; the audit\n\t\t// entries are emitted here at the resolver layer (per convention), with the\n\t\t// score + reasons snapshot in event_data.\n\t\tif (response.ok && response.result.unusualLoginDetected) {\n\t\t\tawait context.logAuthAction({\n\t\t\t\ttype: 'unusual_login_detected',\n\t\t\t\tresponse,\n\t\t\t\tpersonId: response.result.person.id,\n\t\t\t\teventData: response.result.risk ? { ...response.result.risk } : undefined,\n\t\t\t})\n\t\t}\n\t\tif (!response.ok && response.metadata?.stepUpRequired) {\n\t\t\tawait context.logAuthAction({\n\t\t\t\ttype: 'unusual_login_detected',\n\t\t\t\tresponse,\n\t\t\t\teventData: response.metadata.risk ? { ...response.metadata.risk } : undefined,\n\t\t\t})\n\t\t\tawait context.logAuthAction({\n\t\t\t\ttype: 'step_up_required',\n\t\t\t\tresponse,\n\t\t\t\teventData: response.metadata.risk ? { ...response.metadata.risk } : undefined,\n\t\t\t})\n\t\t}\n\n\t\tif (!response.ok) {\n\t\t\tconst { revealUserExists, revealLoginMethod } = configuration.login\n\t\t\tconst collapseAll = !revealUserExists\n\t\t\t\t&& ['NO_PASSWORD_SET', 'PERSON_DISABLED', 'INVALID_PASSWORD', 'UNKNOWN_EMAIL', 'IDP_REQUIRED'].includes(response.error)\n\t\t\tconst collapseMethodOnly = revealUserExists\n\t\t\t\t&& !revealLoginMethod\n\t\t\t\t&& ['NO_PASSWORD_SET', 'INVALID_PASSWORD', 'IDP_REQUIRED'].includes(response.error)\n\t\t\tif (collapseAll || collapseMethodOnly) {\n\t\t\t\treturn createErrorResponse('INVALID_CREDENTIALS', 'Invalid credentials')\n\t\t\t}\n\t\t\t// Preserve the additive mfaEnrollment payload on MFA_ENROLLMENT_REQUIRED (A06).\n\t\t\tif (response.error === 'MFA_ENROLLMENT_REQUIRED' && response.metadata?.mfaEnrollment) {\n\t\t\t\tconst error = {\n\t\t\t\t\tcode: response.error,\n\t\t\t\t\tdeveloperMessage: response.errorMessage,\n\t\t\t\t\tmfaEnrollment: response.metadata.mfaEnrollment,\n\t\t\t\t}\n\t\t\t\treturn { ok: false, error, errors: [error] }\n\t\t\t}\n\t\t\treturn createErrorResponse(response.error, response.errorMessage)\n\t\t}\n\n\t\treturn {\n\t\t\tok: true,\n\t\t\terrors: [],\n\t\t\tresult: await this.signInResponseFactory.createResponse(response.result, context),\n\t\t}\n\t}\n\n\tasync createSessionToken(parent: any, args: MutationCreateSessionTokenArgs, context: TenantResolverContext): Promise<CreateSessionTokenResponse> {\n\t\tawait context.requireAccess({\n\t\t\taction: PermissionActions.PERSON_CREATE_SESSION_KEY(),\n\t\t\tmessage: 'You are not allowed to create a session key',\n\t\t})\n\t\tlet identifier: PersonUniqueIdentifier\n\t\tif (args.email) {\n\t\t\tidentifier = { type: 'email', email: args.email }\n\t\t} else if (args.personId) {\n\t\t\tidentifier = { type: 'id', id: args.personId }\n\t\t} else {\n\t\t\tthrow new UserInputError(`Please provide either email or personId`)\n\t\t}\n\n\t\tconst response = await this.signInManager.createSessionToken(\n\t\t\tcontext.db,\n\t\t\tidentifier,\n\t\t\targs.expiration || undefined,\n\t\t\tasync person =>\n\t\t\t\tawait context.requireAccess({\n\t\t\t\t\taction: PermissionActions.PERSON_CREATE_SESSION_KEY(person.roles),\n\t\t\t\t\tmessage: 'You are not allowed to create a session key for this person.',\n\t\t\t\t}),\n\t\t\tcontext.httpInfo,\n\t\t\targs.options?.trustForwardedClientInfo === true && context.trustForwardedInfo,\n\t\t)\n\t\tawait context.logAuthAction({\n\t\t\ttype: 'create_session_token',\n\t\t\tresponse: response,\n\t\t})\n\n\t\tif (!response.ok) {\n\t\t\treturn createErrorResponse(response.error, response.errorMessage)\n\t\t}\n\n\t\treturn {\n\t\t\tok: true,\n\t\t\tresult: await this.signInResponseFactory.createResponse(response.result, context),\n\t\t}\n\t}\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -565,6 +565,11 @@ const schema = graphqlTag.gql`
|
|
|
565
565
|
MFA_ENROLLMENT_REQUIRED
|
|
566
566
|
EMAIL_NOT_VERIFIED
|
|
567
567
|
RATE_LIMIT_EXCEEDED
|
|
568
|
+
"""
|
|
569
|
+
The person is linked to an identity provider with
|
|
570
|
+
\`disableLocalAuthentication\`, so password sign-in is not available to them.
|
|
571
|
+
"""
|
|
572
|
+
IDP_REQUIRED
|
|
568
573
|
}
|
|
569
574
|
|
|
570
575
|
type SignInResult implements CommonSignInResult {
|
|
@@ -866,6 +871,11 @@ const schema = graphqlTag.gql`
|
|
|
866
871
|
NOT_A_PERSON
|
|
867
872
|
""" The person is not allowed to disconnect their last remaining sign-in method. """
|
|
868
873
|
LAST_AUTH_METHOD
|
|
874
|
+
"""
|
|
875
|
+
The provider has \`disableLocalAuthentication\`, so this connection is the only way
|
|
876
|
+
the person may sign in and cannot be removed by them — not even while the provider is disabled.
|
|
877
|
+
"""
|
|
878
|
+
IDP_REQUIRED
|
|
869
879
|
}
|
|
870
880
|
|
|
871
881
|
type IdentityProvider {
|
|
@@ -892,6 +902,13 @@ const schema = graphqlTag.gql`
|
|
|
892
902
|
Defaults to false.
|
|
893
903
|
"""
|
|
894
904
|
assumeEmailVerified: Boolean!
|
|
905
|
+
"""
|
|
906
|
+
When true, a person linked to this provider may authenticate ONLY through it:
|
|
907
|
+
password sign-in and passwordless sign-in are refused and no password-reset
|
|
908
|
+
mail is sent. Disabling the provider lifts the restriction, which is the
|
|
909
|
+
break-glass for an IdP outage. Defaults to false.
|
|
910
|
+
"""
|
|
911
|
+
disableLocalAuthentication: Boolean!
|
|
895
912
|
}
|
|
896
913
|
|
|
897
914
|
input IDPOptions {
|
|
@@ -900,6 +917,7 @@ const schema = graphqlTag.gql`
|
|
|
900
917
|
initReturnsConfig: Boolean
|
|
901
918
|
requireVerifiedEmail: Boolean
|
|
902
919
|
assumeEmailVerified: Boolean
|
|
920
|
+
disableLocalAuthentication: Boolean
|
|
903
921
|
}
|
|
904
922
|
|
|
905
923
|
# === passwordless sign in ===
|
|
@@ -921,6 +939,11 @@ const schema = graphqlTag.gql`
|
|
|
921
939
|
PASSWORDLESS_DISABLED
|
|
922
940
|
INVALID_CAPTCHA
|
|
923
941
|
RATE_LIMIT_EXCEEDED
|
|
942
|
+
"""
|
|
943
|
+
The person is linked to an identity provider with
|
|
944
|
+
\`disableLocalAuthentication\`, so passwordless sign-in is not available to them.
|
|
945
|
+
"""
|
|
946
|
+
IDP_REQUIRED
|
|
924
947
|
}
|
|
925
948
|
|
|
926
949
|
type InitSignInPasswordlessResult {
|
|
@@ -958,6 +981,11 @@ const schema = graphqlTag.gql`
|
|
|
958
981
|
OTP_REQUIRED
|
|
959
982
|
INVALID_OTP_TOKEN
|
|
960
983
|
MFA_ENROLLMENT_REQUIRED
|
|
984
|
+
"""
|
|
985
|
+
The person became linked to an identity provider with
|
|
986
|
+
\`disableLocalAuthentication\` after this magic link was issued.
|
|
987
|
+
"""
|
|
988
|
+
IDP_REQUIRED
|
|
961
989
|
}
|
|
962
990
|
|
|
963
991
|
type SignInPasswordlessResult implements CommonSignInResult {
|