@contember/engine-tenant-api 2.2.0-alpha.5 → 2.2.0-alpha.7
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/ClaimIdpRevalidationCommand.cjs +3 -7
- package/dist/development/src/model/commands/idp/ClaimIdpRevalidationCommand.cjs.map +1 -1
- package/dist/development/src/model/commands/idp/ClaimIdpRevalidationCommand.js +3 -7
- package/dist/development/src/model/commands/idp/ClaimIdpRevalidationCommand.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/ClaimIdpRevalidationCommand.cjs +3 -7
- package/dist/production/src/model/commands/idp/ClaimIdpRevalidationCommand.cjs.map +1 -1
- package/dist/production/src/model/commands/idp/ClaimIdpRevalidationCommand.js +3 -7
- package/dist/production/src/model/commands/idp/ClaimIdpRevalidationCommand.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/cases/unit/idp/claimIdpRevalidation.test.d.ts +2 -0
- package/dist/tests/cases/unit/idp/claimIdpRevalidation.test.d.ts.map +1 -0
- 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/ClaimIdpRevalidationCommand.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/ClaimIdpRevalidationCommand.ts +8 -7
- 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/claimIdpRevalidation.test.ts +50 -0
- package/tests/cases/unit/idp/idpManagerUnknownConfigKeys.test.ts +8 -1
|
@@ -5,14 +5,18 @@ class UpdateIdpCommand {
|
|
|
5
5
|
this.data = data;
|
|
6
6
|
}
|
|
7
7
|
async execute({ db }) {
|
|
8
|
-
const {
|
|
8
|
+
const {
|
|
9
|
+
configuration,
|
|
10
|
+
options: { autoSignUp, exclusive, initReturnsConfig, requireVerifiedEmail, assumeEmailVerified, disableLocalAuthentication } = {}
|
|
11
|
+
} = this.data;
|
|
9
12
|
await UpdateBuilder$1.create().table("identity_provider").values({
|
|
10
13
|
configuration,
|
|
11
14
|
auto_sign_up: autoSignUp,
|
|
12
15
|
exclusive,
|
|
13
16
|
init_returns_config: initReturnsConfig,
|
|
14
17
|
require_verified_email: requireVerifiedEmail,
|
|
15
|
-
assume_email_verified: assumeEmailVerified
|
|
18
|
+
assume_email_verified: assumeEmailVerified,
|
|
19
|
+
disable_local_authentication: disableLocalAuthentication
|
|
16
20
|
}).where({
|
|
17
21
|
id: this.id
|
|
18
22
|
}).execute(db);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"UpdateIdpCommand.js","sources":["../../../../../../../packages/engine-tenant-api/src/model/commands/idp/UpdateIdpCommand.ts"],"sourcesContent":["import { Command } from '../Command.js'\nimport { UpdateBuilder } from '@contember/database'\nimport { IdentityProviderData } from '../../type/index.js'\n\nexport class UpdateIdpCommand implements Command<void> {\n\tconstructor(\n\t\tprivate id: string,\n\t\tprivate data: UpdateIdpData,\n\t) {\n\t}\n\n\tasync execute({ db }: Command.Args): Promise<void> {\n\t\tconst {
|
|
1
|
+
{"version":3,"file":"UpdateIdpCommand.js","sources":["../../../../../../../packages/engine-tenant-api/src/model/commands/idp/UpdateIdpCommand.ts"],"sourcesContent":["import { Command } from '../Command.js'\nimport { UpdateBuilder } from '@contember/database'\nimport { IdentityProviderData } from '../../type/index.js'\n\nexport class UpdateIdpCommand implements Command<void> {\n\tconstructor(\n\t\tprivate id: string,\n\t\tprivate data: UpdateIdpData,\n\t) {\n\t}\n\n\tasync execute({ db }: Command.Args): Promise<void> {\n\t\tconst {\n\t\t\tconfiguration,\n\t\t\toptions: { autoSignUp, exclusive, initReturnsConfig, requireVerifiedEmail, assumeEmailVerified, disableLocalAuthentication } = {},\n\t\t} = this.data\n\t\tawait UpdateBuilder.create()\n\t\t\t.table('identity_provider')\n\t\t\t.values({\n\t\t\t\tconfiguration,\n\t\t\t\tauto_sign_up: autoSignUp,\n\t\t\t\texclusive,\n\t\t\t\tinit_returns_config: initReturnsConfig,\n\t\t\t\trequire_verified_email: requireVerifiedEmail,\n\t\t\t\tassume_email_verified: assumeEmailVerified,\n\t\t\t\tdisable_local_authentication: disableLocalAuthentication,\n\t\t\t})\n\t\t\t.where({\n\t\t\t\tid: this.id,\n\t\t\t})\n\t\t\t.execute(db)\n\t}\n}\n\nexport type UpdateIdpData = {\n\toptions?: Partial<IdentityProviderData['options']>\n\tconfiguration?: IdentityProviderData['configuration']\n\ttype?: string\n}\n"],"names":["UpdateBuilder"],"mappings":";AAIO,MAAM,iBAA0C;AAAA,EACtD,YACS,IACA,MACP;AAFO,SAAA,KAAA;AACA,SAAA,OAAA;AAAA,EAET;AAAA,EAEA,MAAM,QAAQ,EAAE,MAAmC;AAClD,UAAM;AAAA,MACL;AAAA,MACA,SAAS,EAAE,YAAY,WAAW,mBAAmB,sBAAsB,qBAAqB,+BAA+B,CAAA;AAAA,IAAC,IAC7H,KAAK;AACT,UAAMA,gBAAc,OAAA,EAClB,MAAM,mBAAmB,EACzB,OAAO;AAAA,MACP;AAAA,MACA,cAAc;AAAA,MACd;AAAA,MACA,qBAAqB;AAAA,MACrB,wBAAwB;AAAA,MACxB,uBAAuB;AAAA,MACvB,8BAA8B;AAAA,IAAA,CAC9B,EACA,MAAM;AAAA,MACN,IAAI,KAAK;AAAA,IAAA,CACT,EACA,QAAQ,EAAE;AAAA,EACb;AACD;"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const Database = require("@contember/database");
|
|
4
|
+
class LocalAuthDisablingIdpsQuery extends Database.DatabaseQuery {
|
|
5
|
+
constructor(personId) {
|
|
6
|
+
super();
|
|
7
|
+
this.personId = personId;
|
|
8
|
+
}
|
|
9
|
+
async fetch({ db }) {
|
|
10
|
+
const rows = await Database.SelectBuilder.create().select(["identity_provider", "slug"], "slug").from("person_identity_provider").join(
|
|
11
|
+
"identity_provider",
|
|
12
|
+
"identity_provider",
|
|
13
|
+
(expr) => expr.columnsEq(["identity_provider", "id"], ["person_identity_provider", "identity_provider_id"])
|
|
14
|
+
).where(
|
|
15
|
+
(where) => where.compare(["person_identity_provider", "person_id"], Database.Operator.eq, this.personId).compare(["identity_provider", "disable_local_authentication"], Database.Operator.eq, true).isNull(["identity_provider", "disabled_at"])
|
|
16
|
+
).orderBy(["identity_provider", "slug"]).getResult(db);
|
|
17
|
+
return rows.map((it) => it.slug);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
exports.LocalAuthDisablingIdpsQuery = LocalAuthDisablingIdpsQuery;
|
|
21
|
+
//# sourceMappingURL=LocalAuthDisablingIdpsQuery.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LocalAuthDisablingIdpsQuery.cjs","sources":["../../../../../../../packages/engine-tenant-api/src/model/queries/idp/LocalAuthDisablingIdpsQuery.ts"],"sourcesContent":["import { DatabaseQuery, DatabaseQueryable, Operator, SelectBuilder } from '@contember/database'\n\n/**\n * Slugs of the identity providers that forbid this person every local\n * authentication method — password sign-in, passwordless sign-in and the\n * password-reset mail. Empty means local authentication is available.\n *\n * The IdP link is the marker, so this also covers a person who predates the\n * provider and was linked to it by e-mail matching, not only one created by\n * auto-sign-up. A DISABLED provider does not count: turning the provider off\n * hands local authentication back to everyone linked to it, which is the\n * break-glass when the IdP is unreachable.\n */\nexport class LocalAuthDisablingIdpsQuery extends DatabaseQuery<string[]> {\n\tconstructor(\n\t\tprivate readonly personId: string,\n\t) {\n\t\tsuper()\n\t}\n\n\tasync fetch({ db }: DatabaseQueryable): Promise<string[]> {\n\t\tconst rows = await SelectBuilder.create<{ slug: string }>()\n\t\t\t.select(['identity_provider', 'slug'], 'slug')\n\t\t\t.from('person_identity_provider')\n\t\t\t.join(\n\t\t\t\t'identity_provider',\n\t\t\t\t'identity_provider',\n\t\t\t\texpr => expr.columnsEq(['identity_provider', 'id'], ['person_identity_provider', 'identity_provider_id']),\n\t\t\t)\n\t\t\t.where(where =>\n\t\t\t\twhere\n\t\t\t\t\t.compare(['person_identity_provider', 'person_id'], Operator.eq, this.personId)\n\t\t\t\t\t.compare(['identity_provider', 'disable_local_authentication'], Operator.eq, true)\n\t\t\t\t\t.isNull(['identity_provider', 'disabled_at'])\n\t\t\t)\n\t\t\t.orderBy(['identity_provider', 'slug'])\n\t\t\t.getResult(db)\n\n\t\treturn rows.map(it => it.slug)\n\t}\n}\n"],"names":["DatabaseQuery","SelectBuilder","Operator"],"mappings":";;;AAaO,MAAM,oCAAoCA,SAAAA,cAAwB;AAAA,EACxE,YACkB,UAChB;AACD,UAAA;AAFiB,SAAA,WAAA;AAAA,EAGlB;AAAA,EAEA,MAAM,MAAM,EAAE,MAA4C;AACzD,UAAM,OAAO,MAAMC,SAAAA,cAAc,OAAA,EAC/B,OAAO,CAAC,qBAAqB,MAAM,GAAG,MAAM,EAC5C,KAAK,0BAA0B,EAC/B;AAAA,MACA;AAAA,MACA;AAAA,MACA,CAAA,SAAQ,KAAK,UAAU,CAAC,qBAAqB,IAAI,GAAG,CAAC,4BAA4B,sBAAsB,CAAC;AAAA,IAAA,EAExG;AAAA,MAAM,CAAA,UACN,MACE,QAAQ,CAAC,4BAA4B,WAAW,GAAGC,SAAAA,SAAS,IAAI,KAAK,QAAQ,EAC7E,QAAQ,CAAC,qBAAqB,8BAA8B,GAAGA,kBAAS,IAAI,IAAI,EAChF,OAAO,CAAC,qBAAqB,aAAa,CAAC;AAAA,IAAA,EAE7C,QAAQ,CAAC,qBAAqB,MAAM,CAAC,EACrC,UAAU,EAAE;AAEd,WAAO,KAAK,IAAI,CAAA,OAAM,GAAG,IAAI;AAAA,EAC9B;AACD;;"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { DatabaseQuery, SelectBuilder, Operator } from "@contember/database";
|
|
2
|
+
class LocalAuthDisablingIdpsQuery extends DatabaseQuery {
|
|
3
|
+
constructor(personId) {
|
|
4
|
+
super();
|
|
5
|
+
this.personId = personId;
|
|
6
|
+
}
|
|
7
|
+
async fetch({ db }) {
|
|
8
|
+
const rows = await SelectBuilder.create().select(["identity_provider", "slug"], "slug").from("person_identity_provider").join(
|
|
9
|
+
"identity_provider",
|
|
10
|
+
"identity_provider",
|
|
11
|
+
(expr) => expr.columnsEq(["identity_provider", "id"], ["person_identity_provider", "identity_provider_id"])
|
|
12
|
+
).where(
|
|
13
|
+
(where) => where.compare(["person_identity_provider", "person_id"], Operator.eq, this.personId).compare(["identity_provider", "disable_local_authentication"], Operator.eq, true).isNull(["identity_provider", "disabled_at"])
|
|
14
|
+
).orderBy(["identity_provider", "slug"]).getResult(db);
|
|
15
|
+
return rows.map((it) => it.slug);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export {
|
|
19
|
+
LocalAuthDisablingIdpsQuery
|
|
20
|
+
};
|
|
21
|
+
//# sourceMappingURL=LocalAuthDisablingIdpsQuery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LocalAuthDisablingIdpsQuery.js","sources":["../../../../../../../packages/engine-tenant-api/src/model/queries/idp/LocalAuthDisablingIdpsQuery.ts"],"sourcesContent":["import { DatabaseQuery, DatabaseQueryable, Operator, SelectBuilder } from '@contember/database'\n\n/**\n * Slugs of the identity providers that forbid this person every local\n * authentication method — password sign-in, passwordless sign-in and the\n * password-reset mail. Empty means local authentication is available.\n *\n * The IdP link is the marker, so this also covers a person who predates the\n * provider and was linked to it by e-mail matching, not only one created by\n * auto-sign-up. A DISABLED provider does not count: turning the provider off\n * hands local authentication back to everyone linked to it, which is the\n * break-glass when the IdP is unreachable.\n */\nexport class LocalAuthDisablingIdpsQuery extends DatabaseQuery<string[]> {\n\tconstructor(\n\t\tprivate readonly personId: string,\n\t) {\n\t\tsuper()\n\t}\n\n\tasync fetch({ db }: DatabaseQueryable): Promise<string[]> {\n\t\tconst rows = await SelectBuilder.create<{ slug: string }>()\n\t\t\t.select(['identity_provider', 'slug'], 'slug')\n\t\t\t.from('person_identity_provider')\n\t\t\t.join(\n\t\t\t\t'identity_provider',\n\t\t\t\t'identity_provider',\n\t\t\t\texpr => expr.columnsEq(['identity_provider', 'id'], ['person_identity_provider', 'identity_provider_id']),\n\t\t\t)\n\t\t\t.where(where =>\n\t\t\t\twhere\n\t\t\t\t\t.compare(['person_identity_provider', 'person_id'], Operator.eq, this.personId)\n\t\t\t\t\t.compare(['identity_provider', 'disable_local_authentication'], Operator.eq, true)\n\t\t\t\t\t.isNull(['identity_provider', 'disabled_at'])\n\t\t\t)\n\t\t\t.orderBy(['identity_provider', 'slug'])\n\t\t\t.getResult(db)\n\n\t\treturn rows.map(it => it.slug)\n\t}\n}\n"],"names":[],"mappings":";AAaO,MAAM,oCAAoC,cAAwB;AAAA,EACxE,YACkB,UAChB;AACD,UAAA;AAFiB,SAAA,WAAA;AAAA,EAGlB;AAAA,EAEA,MAAM,MAAM,EAAE,MAA4C;AACzD,UAAM,OAAO,MAAM,cAAc,OAAA,EAC/B,OAAO,CAAC,qBAAqB,MAAM,GAAG,MAAM,EAC5C,KAAK,0BAA0B,EAC/B;AAAA,MACA;AAAA,MACA;AAAA,MACA,CAAA,SAAQ,KAAK,UAAU,CAAC,qBAAqB,IAAI,GAAG,CAAC,4BAA4B,sBAAsB,CAAC;AAAA,IAAA,EAExG;AAAA,MAAM,CAAA,UACN,MACE,QAAQ,CAAC,4BAA4B,WAAW,GAAG,SAAS,IAAI,KAAK,QAAQ,EAC7E,QAAQ,CAAC,qBAAqB,8BAA8B,GAAG,SAAS,IAAI,IAAI,EAChF,OAAO,CAAC,qBAAqB,aAAa,CAAC;AAAA,IAAA,EAE7C,QAAQ,CAAC,qBAAqB,MAAM,CAAC,EACrC,UAAU,EAAE;AAEd,WAAO,KAAK,IAAI,CAAA,OAAM,GAAG,IAAI;AAAA,EAC9B;AACD;"}
|
|
@@ -7,7 +7,7 @@ class PersonIdentityProvidersQuery extends Database.DatabaseQuery {
|
|
|
7
7
|
this.personId = personId;
|
|
8
8
|
}
|
|
9
9
|
async fetch({ db }) {
|
|
10
|
-
return await Database.SelectBuilder.create().select(["person_identity_provider", "id"]).select(["person_identity_provider", "created_at"], "createdAt").select(["person_identity_provider", "external_identifier"], "externalIdentifier").select(["identity_provider", "slug"], "identityProviderSlug").select(["identity_provider", "type"], "identityProviderType").select(["identity_provider", "disabled_at"], "identityProviderDisabledAt").from("person_identity_provider").join(
|
|
10
|
+
return await Database.SelectBuilder.create().select(["person_identity_provider", "id"]).select(["person_identity_provider", "created_at"], "createdAt").select(["person_identity_provider", "external_identifier"], "externalIdentifier").select(["identity_provider", "slug"], "identityProviderSlug").select(["identity_provider", "type"], "identityProviderType").select(["identity_provider", "disabled_at"], "identityProviderDisabledAt").select(["identity_provider", "disable_local_authentication"], "identityProviderDisableLocalAuthentication").from("person_identity_provider").join(
|
|
11
11
|
"identity_provider",
|
|
12
12
|
"identity_provider",
|
|
13
13
|
(expr) => expr.columnsEq(["identity_provider", "id"], ["person_identity_provider", "identity_provider_id"])
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PersonIdentityProvidersQuery.cjs","sources":["../../../../../../../packages/engine-tenant-api/src/model/queries/idp/PersonIdentityProvidersQuery.ts"],"sourcesContent":["import { DatabaseQuery, DatabaseQueryable, SelectBuilder } from '@contember/database'\n\nexport interface PersonIdentityProviderRow {\n\treadonly id: string\n\treadonly createdAt: Date\n\treadonly externalIdentifier: string\n\treadonly identityProviderSlug: string\n\treadonly identityProviderType: string\n\treadonly identityProviderDisabledAt: Date | null\n}\n\n/**\n * Lists the external IdP connections of a single person, joined with the\n * identity provider they belong to. Used by the `Person.identityProviders`\n * field and by the disconnect flow to enforce ownership and lock-out protection.\n */\nexport class PersonIdentityProvidersQuery extends DatabaseQuery<PersonIdentityProviderRow[]> {\n\tconstructor(\n\t\tprivate readonly personId: string,\n\t) {\n\t\tsuper()\n\t}\n\n\tasync fetch({ db }: DatabaseQueryable): Promise<PersonIdentityProviderRow[]> {\n\t\treturn await SelectBuilder.create<PersonIdentityProviderRow>()\n\t\t\t.select(['person_identity_provider', 'id'])\n\t\t\t.select(['person_identity_provider', 'created_at'], 'createdAt')\n\t\t\t.select(['person_identity_provider', 'external_identifier'], 'externalIdentifier')\n\t\t\t.select(['identity_provider', 'slug'], 'identityProviderSlug')\n\t\t\t.select(['identity_provider', 'type'], 'identityProviderType')\n\t\t\t.select(['identity_provider', 'disabled_at'], 'identityProviderDisabledAt')\n\t\t\t.from('person_identity_provider')\n\t\t\t.join(\n\t\t\t\t'identity_provider',\n\t\t\t\t'identity_provider',\n\t\t\t\texpr => expr.columnsEq(['identity_provider', 'id'], ['person_identity_provider', 'identity_provider_id']),\n\t\t\t)\n\t\t\t.where({ person_id: this.personId })\n\t\t\t.orderBy(['person_identity_provider', 'created_at'])\n\t\t\t.orderBy(['person_identity_provider', 'id'])\n\t\t\t.getResult(db)\n\t}\n}\n"],"names":["DatabaseQuery","SelectBuilder"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"PersonIdentityProvidersQuery.cjs","sources":["../../../../../../../packages/engine-tenant-api/src/model/queries/idp/PersonIdentityProvidersQuery.ts"],"sourcesContent":["import { DatabaseQuery, DatabaseQueryable, SelectBuilder } from '@contember/database'\n\nexport interface PersonIdentityProviderRow {\n\treadonly id: string\n\treadonly createdAt: Date\n\treadonly externalIdentifier: string\n\treadonly identityProviderSlug: string\n\treadonly identityProviderType: string\n\treadonly identityProviderDisabledAt: Date | null\n\treadonly identityProviderDisableLocalAuthentication: boolean\n}\n\n/**\n * Lists the external IdP connections of a single person, joined with the\n * identity provider they belong to. Used by the `Person.identityProviders`\n * field and by the disconnect flow to enforce ownership and lock-out protection.\n */\nexport class PersonIdentityProvidersQuery extends DatabaseQuery<PersonIdentityProviderRow[]> {\n\tconstructor(\n\t\tprivate readonly personId: string,\n\t) {\n\t\tsuper()\n\t}\n\n\tasync fetch({ db }: DatabaseQueryable): Promise<PersonIdentityProviderRow[]> {\n\t\treturn await SelectBuilder.create<PersonIdentityProviderRow>()\n\t\t\t.select(['person_identity_provider', 'id'])\n\t\t\t.select(['person_identity_provider', 'created_at'], 'createdAt')\n\t\t\t.select(['person_identity_provider', 'external_identifier'], 'externalIdentifier')\n\t\t\t.select(['identity_provider', 'slug'], 'identityProviderSlug')\n\t\t\t.select(['identity_provider', 'type'], 'identityProviderType')\n\t\t\t.select(['identity_provider', 'disabled_at'], 'identityProviderDisabledAt')\n\t\t\t.select(['identity_provider', 'disable_local_authentication'], 'identityProviderDisableLocalAuthentication')\n\t\t\t.from('person_identity_provider')\n\t\t\t.join(\n\t\t\t\t'identity_provider',\n\t\t\t\t'identity_provider',\n\t\t\t\texpr => expr.columnsEq(['identity_provider', 'id'], ['person_identity_provider', 'identity_provider_id']),\n\t\t\t)\n\t\t\t.where({ person_id: this.personId })\n\t\t\t.orderBy(['person_identity_provider', 'created_at'])\n\t\t\t.orderBy(['person_identity_provider', 'id'])\n\t\t\t.getResult(db)\n\t}\n}\n"],"names":["DatabaseQuery","SelectBuilder"],"mappings":";;;AAiBO,MAAM,qCAAqCA,SAAAA,cAA2C;AAAA,EAC5F,YACkB,UAChB;AACD,UAAA;AAFiB,SAAA,WAAA;AAAA,EAGlB;AAAA,EAEA,MAAM,MAAM,EAAE,MAA+D;AAC5E,WAAO,MAAMC,SAAAA,cAAc,OAAA,EACzB,OAAO,CAAC,4BAA4B,IAAI,CAAC,EACzC,OAAO,CAAC,4BAA4B,YAAY,GAAG,WAAW,EAC9D,OAAO,CAAC,4BAA4B,qBAAqB,GAAG,oBAAoB,EAChF,OAAO,CAAC,qBAAqB,MAAM,GAAG,sBAAsB,EAC5D,OAAO,CAAC,qBAAqB,MAAM,GAAG,sBAAsB,EAC5D,OAAO,CAAC,qBAAqB,aAAa,GAAG,4BAA4B,EACzE,OAAO,CAAC,qBAAqB,8BAA8B,GAAG,4CAA4C,EAC1G,KAAK,0BAA0B,EAC/B;AAAA,MACA;AAAA,MACA;AAAA,MACA,CAAA,SAAQ,KAAK,UAAU,CAAC,qBAAqB,IAAI,GAAG,CAAC,4BAA4B,sBAAsB,CAAC;AAAA,IAAA,EAExG,MAAM,EAAE,WAAW,KAAK,SAAA,CAAU,EAClC,QAAQ,CAAC,4BAA4B,YAAY,CAAC,EAClD,QAAQ,CAAC,4BAA4B,IAAI,CAAC,EAC1C,UAAU,EAAE;AAAA,EACf;AACD;;"}
|
|
@@ -5,7 +5,7 @@ class PersonIdentityProvidersQuery extends DatabaseQuery {
|
|
|
5
5
|
this.personId = personId;
|
|
6
6
|
}
|
|
7
7
|
async fetch({ db }) {
|
|
8
|
-
return await SelectBuilder.create().select(["person_identity_provider", "id"]).select(["person_identity_provider", "created_at"], "createdAt").select(["person_identity_provider", "external_identifier"], "externalIdentifier").select(["identity_provider", "slug"], "identityProviderSlug").select(["identity_provider", "type"], "identityProviderType").select(["identity_provider", "disabled_at"], "identityProviderDisabledAt").from("person_identity_provider").join(
|
|
8
|
+
return await SelectBuilder.create().select(["person_identity_provider", "id"]).select(["person_identity_provider", "created_at"], "createdAt").select(["person_identity_provider", "external_identifier"], "externalIdentifier").select(["identity_provider", "slug"], "identityProviderSlug").select(["identity_provider", "type"], "identityProviderType").select(["identity_provider", "disabled_at"], "identityProviderDisabledAt").select(["identity_provider", "disable_local_authentication"], "identityProviderDisableLocalAuthentication").from("person_identity_provider").join(
|
|
9
9
|
"identity_provider",
|
|
10
10
|
"identity_provider",
|
|
11
11
|
(expr) => expr.columnsEq(["identity_provider", "id"], ["person_identity_provider", "identity_provider_id"])
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PersonIdentityProvidersQuery.js","sources":["../../../../../../../packages/engine-tenant-api/src/model/queries/idp/PersonIdentityProvidersQuery.ts"],"sourcesContent":["import { DatabaseQuery, DatabaseQueryable, SelectBuilder } from '@contember/database'\n\nexport interface PersonIdentityProviderRow {\n\treadonly id: string\n\treadonly createdAt: Date\n\treadonly externalIdentifier: string\n\treadonly identityProviderSlug: string\n\treadonly identityProviderType: string\n\treadonly identityProviderDisabledAt: Date | null\n}\n\n/**\n * Lists the external IdP connections of a single person, joined with the\n * identity provider they belong to. Used by the `Person.identityProviders`\n * field and by the disconnect flow to enforce ownership and lock-out protection.\n */\nexport class PersonIdentityProvidersQuery extends DatabaseQuery<PersonIdentityProviderRow[]> {\n\tconstructor(\n\t\tprivate readonly personId: string,\n\t) {\n\t\tsuper()\n\t}\n\n\tasync fetch({ db }: DatabaseQueryable): Promise<PersonIdentityProviderRow[]> {\n\t\treturn await SelectBuilder.create<PersonIdentityProviderRow>()\n\t\t\t.select(['person_identity_provider', 'id'])\n\t\t\t.select(['person_identity_provider', 'created_at'], 'createdAt')\n\t\t\t.select(['person_identity_provider', 'external_identifier'], 'externalIdentifier')\n\t\t\t.select(['identity_provider', 'slug'], 'identityProviderSlug')\n\t\t\t.select(['identity_provider', 'type'], 'identityProviderType')\n\t\t\t.select(['identity_provider', 'disabled_at'], 'identityProviderDisabledAt')\n\t\t\t.from('person_identity_provider')\n\t\t\t.join(\n\t\t\t\t'identity_provider',\n\t\t\t\t'identity_provider',\n\t\t\t\texpr => expr.columnsEq(['identity_provider', 'id'], ['person_identity_provider', 'identity_provider_id']),\n\t\t\t)\n\t\t\t.where({ person_id: this.personId })\n\t\t\t.orderBy(['person_identity_provider', 'created_at'])\n\t\t\t.orderBy(['person_identity_provider', 'id'])\n\t\t\t.getResult(db)\n\t}\n}\n"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"PersonIdentityProvidersQuery.js","sources":["../../../../../../../packages/engine-tenant-api/src/model/queries/idp/PersonIdentityProvidersQuery.ts"],"sourcesContent":["import { DatabaseQuery, DatabaseQueryable, SelectBuilder } from '@contember/database'\n\nexport interface PersonIdentityProviderRow {\n\treadonly id: string\n\treadonly createdAt: Date\n\treadonly externalIdentifier: string\n\treadonly identityProviderSlug: string\n\treadonly identityProviderType: string\n\treadonly identityProviderDisabledAt: Date | null\n\treadonly identityProviderDisableLocalAuthentication: boolean\n}\n\n/**\n * Lists the external IdP connections of a single person, joined with the\n * identity provider they belong to. Used by the `Person.identityProviders`\n * field and by the disconnect flow to enforce ownership and lock-out protection.\n */\nexport class PersonIdentityProvidersQuery extends DatabaseQuery<PersonIdentityProviderRow[]> {\n\tconstructor(\n\t\tprivate readonly personId: string,\n\t) {\n\t\tsuper()\n\t}\n\n\tasync fetch({ db }: DatabaseQueryable): Promise<PersonIdentityProviderRow[]> {\n\t\treturn await SelectBuilder.create<PersonIdentityProviderRow>()\n\t\t\t.select(['person_identity_provider', 'id'])\n\t\t\t.select(['person_identity_provider', 'created_at'], 'createdAt')\n\t\t\t.select(['person_identity_provider', 'external_identifier'], 'externalIdentifier')\n\t\t\t.select(['identity_provider', 'slug'], 'identityProviderSlug')\n\t\t\t.select(['identity_provider', 'type'], 'identityProviderType')\n\t\t\t.select(['identity_provider', 'disabled_at'], 'identityProviderDisabledAt')\n\t\t\t.select(['identity_provider', 'disable_local_authentication'], 'identityProviderDisableLocalAuthentication')\n\t\t\t.from('person_identity_provider')\n\t\t\t.join(\n\t\t\t\t'identity_provider',\n\t\t\t\t'identity_provider',\n\t\t\t\texpr => expr.columnsEq(['identity_provider', 'id'], ['person_identity_provider', 'identity_provider_id']),\n\t\t\t)\n\t\t\t.where({ person_id: this.personId })\n\t\t\t.orderBy(['person_identity_provider', 'created_at'])\n\t\t\t.orderBy(['person_identity_provider', 'id'])\n\t\t\t.getResult(db)\n\t}\n}\n"],"names":[],"mappings":";AAiBO,MAAM,qCAAqC,cAA2C;AAAA,EAC5F,YACkB,UAChB;AACD,UAAA;AAFiB,SAAA,WAAA;AAAA,EAGlB;AAAA,EAEA,MAAM,MAAM,EAAE,MAA+D;AAC5E,WAAO,MAAM,cAAc,OAAA,EACzB,OAAO,CAAC,4BAA4B,IAAI,CAAC,EACzC,OAAO,CAAC,4BAA4B,YAAY,GAAG,WAAW,EAC9D,OAAO,CAAC,4BAA4B,qBAAqB,GAAG,oBAAoB,EAChF,OAAO,CAAC,qBAAqB,MAAM,GAAG,sBAAsB,EAC5D,OAAO,CAAC,qBAAqB,MAAM,GAAG,sBAAsB,EAC5D,OAAO,CAAC,qBAAqB,aAAa,GAAG,4BAA4B,EACzE,OAAO,CAAC,qBAAqB,8BAA8B,GAAG,4CAA4C,EAC1G,KAAK,0BAA0B,EAC/B;AAAA,MACA;AAAA,MACA;AAAA,MACA,CAAA,SAAQ,KAAK,UAAU,CAAC,qBAAqB,IAAI,GAAG,CAAC,4BAA4B,sBAAsB,CAAC;AAAA,IAAA,EAExG,MAAM,EAAE,WAAW,KAAK,SAAA,CAAU,EAClC,QAAQ,CAAC,4BAA4B,YAAY,CAAC,EAClD,QAAQ,CAAC,4BAA4B,IAAI,CAAC,EAC1C,UAAU,EAAE;AAAA,EACf;AACD;"}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
3
|
const Database = require("@contember/database");
|
|
4
|
-
const createBaseIdpQuery = () => Database.SelectBuilder.create().select("id").select("slug").select("type").select("configuration").select("disabled_at", "disabledAt").select("auto_sign_up", "autoSignUp").select("exclusive").select("init_returns_config", "initReturnsConfig").select("require_verified_email", "requireVerifiedEmail").select("assume_email_verified", "assumeEmailVerified").from("identity_provider");
|
|
5
|
-
const createIdpDto = ({ exclusive, autoSignUp, initReturnsConfig, requireVerifiedEmail, assumeEmailVerified, ...row }) => {
|
|
4
|
+
const createBaseIdpQuery = () => Database.SelectBuilder.create().select("id").select("slug").select("type").select("configuration").select("disabled_at", "disabledAt").select("auto_sign_up", "autoSignUp").select("exclusive").select("init_returns_config", "initReturnsConfig").select("require_verified_email", "requireVerifiedEmail").select("assume_email_verified", "assumeEmailVerified").select("disable_local_authentication", "disableLocalAuthentication").from("identity_provider");
|
|
5
|
+
const createIdpDto = ({ exclusive, autoSignUp, initReturnsConfig, requireVerifiedEmail, assumeEmailVerified, disableLocalAuthentication, ...row }) => {
|
|
6
6
|
return {
|
|
7
7
|
...row,
|
|
8
|
-
options: { autoSignUp, exclusive, initReturnsConfig, requireVerifiedEmail, assumeEmailVerified }
|
|
8
|
+
options: { autoSignUp, exclusive, initReturnsConfig, requireVerifiedEmail, assumeEmailVerified, disableLocalAuthentication }
|
|
9
9
|
};
|
|
10
10
|
};
|
|
11
11
|
exports.createBaseIdpQuery = createBaseIdpQuery;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helpers.cjs","sources":["../../../../../../../packages/engine-tenant-api/src/model/queries/idp/helpers.ts"],"sourcesContent":["import { SelectBuilder } from '@contember/database'\nimport { IdentityProviderDto, IdentityProviderRow } from './types.js'\n\nexport const createBaseIdpQuery = () =>\n\tSelectBuilder.create<IdentityProviderRow>()\n\t\t.select('id')\n\t\t.select('slug')\n\t\t.select('type')\n\t\t.select('configuration')\n\t\t.select('disabled_at', 'disabledAt')\n\t\t.select('auto_sign_up', 'autoSignUp')\n\t\t.select('exclusive')\n\t\t.select('init_returns_config', 'initReturnsConfig')\n\t\t.select('require_verified_email', 'requireVerifiedEmail')\n\t\t.select('assume_email_verified', 'assumeEmailVerified')\n\t\t.from('identity_provider')\n\nexport const createIdpDto = (\n\t{ exclusive, autoSignUp, initReturnsConfig, requireVerifiedEmail, assumeEmailVerified, ...row }: IdentityProviderRow,\n): IdentityProviderDto => {\n\treturn {\n\t\t...row,\n\t\toptions: { autoSignUp, exclusive, initReturnsConfig, requireVerifiedEmail, assumeEmailVerified },\n\t}\n}\n"],"names":["SelectBuilder"],"mappings":";;;AAGO,MAAM,qBAAqB,MACjCA,SAAAA,cAAc,
|
|
1
|
+
{"version":3,"file":"helpers.cjs","sources":["../../../../../../../packages/engine-tenant-api/src/model/queries/idp/helpers.ts"],"sourcesContent":["import { SelectBuilder } from '@contember/database'\nimport { IdentityProviderDto, IdentityProviderRow } from './types.js'\n\nexport const createBaseIdpQuery = () =>\n\tSelectBuilder.create<IdentityProviderRow>()\n\t\t.select('id')\n\t\t.select('slug')\n\t\t.select('type')\n\t\t.select('configuration')\n\t\t.select('disabled_at', 'disabledAt')\n\t\t.select('auto_sign_up', 'autoSignUp')\n\t\t.select('exclusive')\n\t\t.select('init_returns_config', 'initReturnsConfig')\n\t\t.select('require_verified_email', 'requireVerifiedEmail')\n\t\t.select('assume_email_verified', 'assumeEmailVerified')\n\t\t.select('disable_local_authentication', 'disableLocalAuthentication')\n\t\t.from('identity_provider')\n\nexport const createIdpDto = (\n\t{ exclusive, autoSignUp, initReturnsConfig, requireVerifiedEmail, assumeEmailVerified, disableLocalAuthentication, ...row }: IdentityProviderRow,\n): IdentityProviderDto => {\n\treturn {\n\t\t...row,\n\t\toptions: { autoSignUp, exclusive, initReturnsConfig, requireVerifiedEmail, assumeEmailVerified, disableLocalAuthentication },\n\t}\n}\n"],"names":["SelectBuilder"],"mappings":";;;AAGO,MAAM,qBAAqB,MACjCA,SAAAA,cAAc,OAAA,EACZ,OAAO,IAAI,EACX,OAAO,MAAM,EACb,OAAO,MAAM,EACb,OAAO,eAAe,EACtB,OAAO,eAAe,YAAY,EAClC,OAAO,gBAAgB,YAAY,EACnC,OAAO,WAAW,EAClB,OAAO,uBAAuB,mBAAmB,EACjD,OAAO,0BAA0B,sBAAsB,EACvD,OAAO,yBAAyB,qBAAqB,EACrD,OAAO,gCAAgC,4BAA4B,EACnE,KAAK,mBAAmB;AAEpB,MAAM,eAAe,CAC3B,EAAE,WAAW,YAAY,mBAAmB,sBAAsB,qBAAqB,4BAA4B,GAAG,UAC7F;AACzB,SAAO;AAAA,IACN,GAAG;AAAA,IACH,SAAS,EAAE,YAAY,WAAW,mBAAmB,sBAAsB,qBAAqB,2BAAA;AAAA,EAA2B;AAE7H;;;"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { SelectBuilder } from "@contember/database";
|
|
2
|
-
const createBaseIdpQuery = () => SelectBuilder.create().select("id").select("slug").select("type").select("configuration").select("disabled_at", "disabledAt").select("auto_sign_up", "autoSignUp").select("exclusive").select("init_returns_config", "initReturnsConfig").select("require_verified_email", "requireVerifiedEmail").select("assume_email_verified", "assumeEmailVerified").from("identity_provider");
|
|
3
|
-
const createIdpDto = ({ exclusive, autoSignUp, initReturnsConfig, requireVerifiedEmail, assumeEmailVerified, ...row }) => {
|
|
2
|
+
const createBaseIdpQuery = () => SelectBuilder.create().select("id").select("slug").select("type").select("configuration").select("disabled_at", "disabledAt").select("auto_sign_up", "autoSignUp").select("exclusive").select("init_returns_config", "initReturnsConfig").select("require_verified_email", "requireVerifiedEmail").select("assume_email_verified", "assumeEmailVerified").select("disable_local_authentication", "disableLocalAuthentication").from("identity_provider");
|
|
3
|
+
const createIdpDto = ({ exclusive, autoSignUp, initReturnsConfig, requireVerifiedEmail, assumeEmailVerified, disableLocalAuthentication, ...row }) => {
|
|
4
4
|
return {
|
|
5
5
|
...row,
|
|
6
|
-
options: { autoSignUp, exclusive, initReturnsConfig, requireVerifiedEmail, assumeEmailVerified }
|
|
6
|
+
options: { autoSignUp, exclusive, initReturnsConfig, requireVerifiedEmail, assumeEmailVerified, disableLocalAuthentication }
|
|
7
7
|
};
|
|
8
8
|
};
|
|
9
9
|
export {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helpers.js","sources":["../../../../../../../packages/engine-tenant-api/src/model/queries/idp/helpers.ts"],"sourcesContent":["import { SelectBuilder } from '@contember/database'\nimport { IdentityProviderDto, IdentityProviderRow } from './types.js'\n\nexport const createBaseIdpQuery = () =>\n\tSelectBuilder.create<IdentityProviderRow>()\n\t\t.select('id')\n\t\t.select('slug')\n\t\t.select('type')\n\t\t.select('configuration')\n\t\t.select('disabled_at', 'disabledAt')\n\t\t.select('auto_sign_up', 'autoSignUp')\n\t\t.select('exclusive')\n\t\t.select('init_returns_config', 'initReturnsConfig')\n\t\t.select('require_verified_email', 'requireVerifiedEmail')\n\t\t.select('assume_email_verified', 'assumeEmailVerified')\n\t\t.from('identity_provider')\n\nexport const createIdpDto = (\n\t{ exclusive, autoSignUp, initReturnsConfig, requireVerifiedEmail, assumeEmailVerified, ...row }: IdentityProviderRow,\n): IdentityProviderDto => {\n\treturn {\n\t\t...row,\n\t\toptions: { autoSignUp, exclusive, initReturnsConfig, requireVerifiedEmail, assumeEmailVerified },\n\t}\n}\n"],"names":[],"mappings":";AAGO,MAAM,qBAAqB,MACjC,cAAc,
|
|
1
|
+
{"version":3,"file":"helpers.js","sources":["../../../../../../../packages/engine-tenant-api/src/model/queries/idp/helpers.ts"],"sourcesContent":["import { SelectBuilder } from '@contember/database'\nimport { IdentityProviderDto, IdentityProviderRow } from './types.js'\n\nexport const createBaseIdpQuery = () =>\n\tSelectBuilder.create<IdentityProviderRow>()\n\t\t.select('id')\n\t\t.select('slug')\n\t\t.select('type')\n\t\t.select('configuration')\n\t\t.select('disabled_at', 'disabledAt')\n\t\t.select('auto_sign_up', 'autoSignUp')\n\t\t.select('exclusive')\n\t\t.select('init_returns_config', 'initReturnsConfig')\n\t\t.select('require_verified_email', 'requireVerifiedEmail')\n\t\t.select('assume_email_verified', 'assumeEmailVerified')\n\t\t.select('disable_local_authentication', 'disableLocalAuthentication')\n\t\t.from('identity_provider')\n\nexport const createIdpDto = (\n\t{ exclusive, autoSignUp, initReturnsConfig, requireVerifiedEmail, assumeEmailVerified, disableLocalAuthentication, ...row }: IdentityProviderRow,\n): IdentityProviderDto => {\n\treturn {\n\t\t...row,\n\t\toptions: { autoSignUp, exclusive, initReturnsConfig, requireVerifiedEmail, assumeEmailVerified, disableLocalAuthentication },\n\t}\n}\n"],"names":[],"mappings":";AAGO,MAAM,qBAAqB,MACjC,cAAc,OAAA,EACZ,OAAO,IAAI,EACX,OAAO,MAAM,EACb,OAAO,MAAM,EACb,OAAO,eAAe,EACtB,OAAO,eAAe,YAAY,EAClC,OAAO,gBAAgB,YAAY,EACnC,OAAO,WAAW,EAClB,OAAO,uBAAuB,mBAAmB,EACjD,OAAO,0BAA0B,sBAAsB,EACvD,OAAO,yBAAyB,qBAAqB,EACrD,OAAO,gCAAgC,4BAA4B,EACnE,KAAK,mBAAmB;AAEpB,MAAM,eAAe,CAC3B,EAAE,WAAW,YAAY,mBAAmB,sBAAsB,qBAAqB,4BAA4B,GAAG,UAC7F;AACzB,SAAO;AAAA,IACN,GAAG;AAAA,IACH,SAAS,EAAE,YAAY,WAAW,mBAAmB,sBAAsB,qBAAqB,2BAAA;AAAA,EAA2B;AAE7H;"}
|
|
@@ -6,6 +6,7 @@ require("@contember/database");
|
|
|
6
6
|
require("../type/ApiKey.cjs");
|
|
7
7
|
const ConfigurationQuery = require("../queries/configuration/ConfigurationQuery.cjs");
|
|
8
8
|
const exceptions = require("../../exceptions.cjs");
|
|
9
|
+
const LocalAuthDisablingIdpsQuery = require("../queries/idp/LocalAuthDisablingIdpsQuery.cjs");
|
|
9
10
|
require("../authorization/PermissionActions.cjs");
|
|
10
11
|
require("@contember/authorization");
|
|
11
12
|
require("../authorization/PermissionContext.cjs");
|
|
@@ -28,6 +29,10 @@ class PasswordResetManager {
|
|
|
28
29
|
if (!person.email) {
|
|
29
30
|
throw new exceptions.ImplementationException();
|
|
30
31
|
}
|
|
32
|
+
const exclusiveIdps = await dbContext.queryHandler.fetch(new LocalAuthDisablingIdpsQuery.LocalAuthDisablingIdpsQuery(person.id));
|
|
33
|
+
if (exclusiveIdps.length > 0) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
31
36
|
const nextAllowed = await dbContext.queryHandler.fetch(
|
|
32
37
|
new NextMailAttemptQuery.NextMailAttemptQuery(person.email, "password_reset_init", "password_reset")
|
|
33
38
|
);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PasswordResetManager.cjs","sources":["../../../../../../packages/engine-tenant-api/src/model/service/PasswordResetManager.ts"],"sourcesContent":["import { Response, ResponseError, ResponseOk } from '../utils/Response.js'\nimport { UserMailer } from '../mailing/index.js'\nimport { ConfigurationQuery, PersonRow } from '../queries/index.js'\nimport { PermissionContext } from '../authorization/index.js'\nimport { ProjectManager } from './ProjectManager.js'\nimport { DatabaseContext } from '../utils/index.js'\nimport { ImplementationException } from '../../exceptions.js'\nimport { getPreferredProject } from './helpers/getPreferredProject.js'\nimport { CreatePersonTokenCommand, ResetPasswordCommand } from '../commands/personToken/index.js'\nimport { PasswordStrengthValidator } from './PasswordStrengthValidator.js'\nimport { ResetPasswordErrorCode, WeakPasswordReason } from '../../schema/index.js'\nimport { PersonTokenQuery } from '../queries/personToken/PersonTokenQuery.js'\nimport { AuthLogService } from './AuthLogService.js'\nimport { NextMailAttemptQuery } from '../queries/authLog/NextMailAttemptQuery.js'\n\ninterface MailOptions {\n\tproject?: string\n\tmailVariant?: string\n}\n\nexport class PasswordResetManager {\n\tconstructor(\n\t\tprivate readonly mailer: UserMailer,\n\t\tprivate readonly projectManager: ProjectManager,\n\t\tprivate readonly passwordStrengthValidator: PasswordStrengthValidator,\n\t) {}\n\n\tpublic async createPasswordResetRequest(\n\t\tdbContext: DatabaseContext,\n\t\tpermissionContext: PermissionContext,\n\t\tperson: PersonRow,\n\t\tmailOptions: MailOptions = {},\n\t): Promise<void> {\n\t\tif (!person.email) {\n\t\t\tthrow new ImplementationException()\n\t\t}\n\n\t\t// Per-email exponential backoff on outbound mails — don't spam a real\n\t\t// user with reset mails just because attackers keep poking the\n\t\t// endpoint. Reuses login_* backoff config against person_auth_log.\n\t\tconst nextAllowed = await dbContext.queryHandler.fetch(\n\t\t\tnew NextMailAttemptQuery(person.email, 'password_reset_init', 'password_reset'),\n\t\t)\n\t\tif (nextAllowed > new Date()) {\n\t\t\treturn\n\t\t}\n\n\t\tconst result = await dbContext.commandBus.execute(CreatePersonTokenCommand.createPasswordResetRequest(person.id))\n\t\tconst projects = await this.projectManager.getProjectsByIdentity(dbContext, person.identity_id, permissionContext)\n\t\tconst project = getPreferredProject(projects, mailOptions.project ?? null)\n\n\t\tawait this.mailer.sendPasswordResetEmail(\n\t\t\tdbContext,\n\t\t\t{\n\t\t\t\temail: person.email,\n\t\t\t\ttoken: result.token,\n\t\t\t\tproject: project?.name,\n\t\t\t\tprojectSlug: project?.slug,\n\t\t\t},\n\t\t\t{\n\t\t\t\tvariant: mailOptions.mailVariant || '',\n\t\t\t\tprojectId: project?.id ?? null,\n\t\t\t},\n\t\t)\n\t}\n\n\tpublic async resetPassword(dbContext: DatabaseContext, token: string, password: string): Promise<ResetPasswordResponse> {\n\t\tconst config = await dbContext.queryHandler.fetch(new ConfigurationQuery(dbContext.providers))\n\t\tconst tokenRow = await dbContext.queryHandler.fetch(PersonTokenQuery.byToken(token, 'password_reset'))\n\t\tif (!tokenRow) {\n\t\t\treturn new ResponseError('TOKEN_NOT_FOUND', 'Token not found', {\n\t\t\t\t[AuthLogService.Key]: new AuthLogService.Bag({}),\n\t\t\t})\n\t\t}\n\n\t\tconst authLogData = new AuthLogService.Bag({\n\t\t\tpersonId: tokenRow.person_id,\n\t\t\ttokenId: tokenRow.id,\n\t\t})\n\t\tconst weakPassword = await this.passwordStrengthValidator.verify(password, config.password, 'PASSWORD_TOO_WEAK')\n\t\tif (!weakPassword.ok) {\n\t\t\treturn new ResponseError(weakPassword.error, weakPassword.errorMessage, {\n\t\t\t\t...weakPassword.metadata ?? {},\n\t\t\t\t[AuthLogService.Key]: authLogData,\n\t\t\t})\n\t\t}\n\t\tconst resetPasswordResult = await dbContext.commandBus.execute(new ResetPasswordCommand(token, password))\n\t\tif (!resetPasswordResult.ok) {\n\t\t\treturn new ResponseError(resetPasswordResult.error, resetPasswordResult.errorMessage, {\n\t\t\t\t...resetPasswordResult.metadata ?? {},\n\t\t\t\t[AuthLogService.Key]: authLogData,\n\t\t\t})\n\t\t}\n\t\treturn new ResponseOk({\n\t\t\t[AuthLogService.Key]: authLogData,\n\t\t})\n\t}\n}\n\nexport type ResetPasswordResponse = Response<\n\t{\n\t\t[AuthLogService.Key]: AuthLogService.Bag\n\t},\n\tResetPasswordErrorCode,\n\t{\n\t\tweakPasswordReasons?: WeakPasswordReason[]\n\t\t[AuthLogService.Key]: AuthLogService.Bag\n\t}\n>\n"],"names":["ImplementationException","NextMailAttemptQuery","CreatePersonTokenCommand","getPreferredProject","ConfigurationQuery","PersonTokenQuery","ResponseError","AuthLogService","ResetPasswordCommand","ResponseOk"],"mappings":"
|
|
1
|
+
{"version":3,"file":"PasswordResetManager.cjs","sources":["../../../../../../packages/engine-tenant-api/src/model/service/PasswordResetManager.ts"],"sourcesContent":["import { Response, ResponseError, ResponseOk } from '../utils/Response.js'\nimport { UserMailer } from '../mailing/index.js'\nimport { ConfigurationQuery, LocalAuthDisablingIdpsQuery, PersonRow } from '../queries/index.js'\nimport { PermissionContext } from '../authorization/index.js'\nimport { ProjectManager } from './ProjectManager.js'\nimport { DatabaseContext } from '../utils/index.js'\nimport { ImplementationException } from '../../exceptions.js'\nimport { getPreferredProject } from './helpers/getPreferredProject.js'\nimport { CreatePersonTokenCommand, ResetPasswordCommand } from '../commands/personToken/index.js'\nimport { PasswordStrengthValidator } from './PasswordStrengthValidator.js'\nimport { ResetPasswordErrorCode, WeakPasswordReason } from '../../schema/index.js'\nimport { PersonTokenQuery } from '../queries/personToken/PersonTokenQuery.js'\nimport { AuthLogService } from './AuthLogService.js'\nimport { NextMailAttemptQuery } from '../queries/authLog/NextMailAttemptQuery.js'\n\ninterface MailOptions {\n\tproject?: string\n\tmailVariant?: string\n}\n\nexport class PasswordResetManager {\n\tconstructor(\n\t\tprivate readonly mailer: UserMailer,\n\t\tprivate readonly projectManager: ProjectManager,\n\t\tprivate readonly passwordStrengthValidator: PasswordStrengthValidator,\n\t) {}\n\n\tpublic async createPasswordResetRequest(\n\t\tdbContext: DatabaseContext,\n\t\tpermissionContext: PermissionContext,\n\t\tperson: PersonRow,\n\t\tmailOptions: MailOptions = {},\n\t): Promise<void> {\n\t\tif (!person.email) {\n\t\t\tthrow new ImplementationException()\n\t\t}\n\n\t\t// A person federated through a provider with `disableLocalAuthentication`\n\t\t// cannot sign in with a password, so a reset mail would only offer them a\n\t\t// credential the tenant refuses — and one more link worth phishing.\n\t\tconst exclusiveIdps = await dbContext.queryHandler.fetch(new LocalAuthDisablingIdpsQuery(person.id))\n\t\tif (exclusiveIdps.length > 0) {\n\t\t\treturn\n\t\t}\n\n\t\t// Per-email exponential backoff on outbound mails — don't spam a real\n\t\t// user with reset mails just because attackers keep poking the\n\t\t// endpoint. Reuses login_* backoff config against person_auth_log.\n\t\tconst nextAllowed = await dbContext.queryHandler.fetch(\n\t\t\tnew NextMailAttemptQuery(person.email, 'password_reset_init', 'password_reset'),\n\t\t)\n\t\tif (nextAllowed > new Date()) {\n\t\t\treturn\n\t\t}\n\n\t\tconst result = await dbContext.commandBus.execute(CreatePersonTokenCommand.createPasswordResetRequest(person.id))\n\t\tconst projects = await this.projectManager.getProjectsByIdentity(dbContext, person.identity_id, permissionContext)\n\t\tconst project = getPreferredProject(projects, mailOptions.project ?? null)\n\n\t\tawait this.mailer.sendPasswordResetEmail(\n\t\t\tdbContext,\n\t\t\t{\n\t\t\t\temail: person.email,\n\t\t\t\ttoken: result.token,\n\t\t\t\tproject: project?.name,\n\t\t\t\tprojectSlug: project?.slug,\n\t\t\t},\n\t\t\t{\n\t\t\t\tvariant: mailOptions.mailVariant || '',\n\t\t\t\tprojectId: project?.id ?? null,\n\t\t\t},\n\t\t)\n\t}\n\n\tpublic async resetPassword(dbContext: DatabaseContext, token: string, password: string): Promise<ResetPasswordResponse> {\n\t\tconst config = await dbContext.queryHandler.fetch(new ConfigurationQuery(dbContext.providers))\n\t\tconst tokenRow = await dbContext.queryHandler.fetch(PersonTokenQuery.byToken(token, 'password_reset'))\n\t\tif (!tokenRow) {\n\t\t\treturn new ResponseError('TOKEN_NOT_FOUND', 'Token not found', {\n\t\t\t\t[AuthLogService.Key]: new AuthLogService.Bag({}),\n\t\t\t})\n\t\t}\n\n\t\tconst authLogData = new AuthLogService.Bag({\n\t\t\tpersonId: tokenRow.person_id,\n\t\t\ttokenId: tokenRow.id,\n\t\t})\n\t\tconst weakPassword = await this.passwordStrengthValidator.verify(password, config.password, 'PASSWORD_TOO_WEAK')\n\t\tif (!weakPassword.ok) {\n\t\t\treturn new ResponseError(weakPassword.error, weakPassword.errorMessage, {\n\t\t\t\t...weakPassword.metadata ?? {},\n\t\t\t\t[AuthLogService.Key]: authLogData,\n\t\t\t})\n\t\t}\n\t\tconst resetPasswordResult = await dbContext.commandBus.execute(new ResetPasswordCommand(token, password))\n\t\tif (!resetPasswordResult.ok) {\n\t\t\treturn new ResponseError(resetPasswordResult.error, resetPasswordResult.errorMessage, {\n\t\t\t\t...resetPasswordResult.metadata ?? {},\n\t\t\t\t[AuthLogService.Key]: authLogData,\n\t\t\t})\n\t\t}\n\t\treturn new ResponseOk({\n\t\t\t[AuthLogService.Key]: authLogData,\n\t\t})\n\t}\n}\n\nexport type ResetPasswordResponse = Response<\n\t{\n\t\t[AuthLogService.Key]: AuthLogService.Bag\n\t},\n\tResetPasswordErrorCode,\n\t{\n\t\tweakPasswordReasons?: WeakPasswordReason[]\n\t\t[AuthLogService.Key]: AuthLogService.Bag\n\t}\n>\n"],"names":["ImplementationException","LocalAuthDisablingIdpsQuery","NextMailAttemptQuery","CreatePersonTokenCommand","getPreferredProject","ConfigurationQuery","PersonTokenQuery","ResponseError","AuthLogService","ResetPasswordCommand","ResponseOk"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAoBO,MAAM,qBAAqB;AAAA,EACjC,YACkB,QACA,gBACA,2BAChB;AAHgB,SAAA,SAAA;AACA,SAAA,iBAAA;AACA,SAAA,4BAAA;AAAA,EACf;AAAA,EAEH,MAAa,2BACZ,WACA,mBACA,QACA,cAA2B,CAAA,GACX;AAChB,QAAI,CAAC,OAAO,OAAO;AAClB,YAAM,IAAIA,WAAAA,wBAAA;AAAA,IACX;AAKA,UAAM,gBAAgB,MAAM,UAAU,aAAa,MAAM,IAAIC,4BAAAA,4BAA4B,OAAO,EAAE,CAAC;AACnG,QAAI,cAAc,SAAS,GAAG;AAC7B;AAAA,IACD;AAKA,UAAM,cAAc,MAAM,UAAU,aAAa;AAAA,MAChD,IAAIC,0CAAqB,OAAO,OAAO,uBAAuB,gBAAgB;AAAA,IAAA;AAE/E,QAAI,cAAc,oBAAI,QAAQ;AAC7B;AAAA,IACD;AAEA,UAAM,SAAS,MAAM,UAAU,WAAW,QAAQC,yBAAAA,yBAAyB,2BAA2B,OAAO,EAAE,CAAC;AAChH,UAAM,WAAW,MAAM,KAAK,eAAe,sBAAsB,WAAW,OAAO,aAAa,iBAAiB;AACjH,UAAM,UAAUC,oBAAAA,oBAAoB,UAAU,YAAY,WAAW,IAAI;AAEzE,UAAM,KAAK,OAAO;AAAA,MACjB;AAAA,MACA;AAAA,QACC,OAAO,OAAO;AAAA,QACd,OAAO,OAAO;AAAA,QACd,SAAS,SAAS;AAAA,QAClB,aAAa,SAAS;AAAA,MAAA;AAAA,MAEvB;AAAA,QACC,SAAS,YAAY,eAAe;AAAA,QACpC,WAAW,SAAS,MAAM;AAAA,MAAA;AAAA,IAC3B;AAAA,EAEF;AAAA,EAEA,MAAa,cAAc,WAA4B,OAAe,UAAkD;AACvH,UAAM,SAAS,MAAM,UAAU,aAAa,MAAM,IAAIC,mBAAAA,mBAAmB,UAAU,SAAS,CAAC;AAC7F,UAAM,WAAW,MAAM,UAAU,aAAa,MAAMC,iBAAAA,iBAAiB,QAAQ,OAAO,gBAAgB,CAAC;AACrG,QAAI,CAAC,UAAU;AACd,aAAO,IAAIC,SAAAA,cAAc,mBAAmB,mBAAmB;AAAA,QAC9D,CAACC,eAAAA,eAAe,GAAG,GAAG,IAAIA,eAAAA,eAAe,IAAI,CAAA,CAAE;AAAA,MAAA,CAC/C;AAAA,IACF;AAEA,UAAM,cAAc,IAAIA,eAAAA,eAAe,IAAI;AAAA,MAC1C,UAAU,SAAS;AAAA,MACnB,SAAS,SAAS;AAAA,IAAA,CAClB;AACD,UAAM,eAAe,MAAM,KAAK,0BAA0B,OAAO,UAAU,OAAO,UAAU,mBAAmB;AAC/G,QAAI,CAAC,aAAa,IAAI;AACrB,aAAO,IAAID,SAAAA,cAAc,aAAa,OAAO,aAAa,cAAc;AAAA,QACvE,GAAG,aAAa,YAAY,CAAA;AAAA,QAC5B,CAACC,eAAAA,eAAe,GAAG,GAAG;AAAA,MAAA,CACtB;AAAA,IACF;AACA,UAAM,sBAAsB,MAAM,UAAU,WAAW,QAAQ,IAAIC,qBAAAA,qBAAqB,OAAO,QAAQ,CAAC;AACxG,QAAI,CAAC,oBAAoB,IAAI;AAC5B,aAAO,IAAIF,SAAAA,cAAc,oBAAoB,OAAO,oBAAoB,cAAc;AAAA,QACrF,GAAG,oBAAoB,YAAY,CAAA;AAAA,QACnC,CAACC,eAAAA,eAAe,GAAG,GAAG;AAAA,MAAA,CACtB;AAAA,IACF;AACA,WAAO,IAAIE,SAAAA,WAAW;AAAA,MACrB,CAACF,eAAAA,eAAe,GAAG,GAAG;AAAA,IAAA,CACtB;AAAA,EACF;AACD;;"}
|
|
@@ -4,6 +4,7 @@ import "@contember/database";
|
|
|
4
4
|
import "../type/ApiKey.js";
|
|
5
5
|
import { ConfigurationQuery } from "../queries/configuration/ConfigurationQuery.js";
|
|
6
6
|
import { ImplementationException } from "../../exceptions.js";
|
|
7
|
+
import { LocalAuthDisablingIdpsQuery } from "../queries/idp/LocalAuthDisablingIdpsQuery.js";
|
|
7
8
|
import "../authorization/PermissionActions.js";
|
|
8
9
|
import "@contember/authorization";
|
|
9
10
|
import "../authorization/PermissionContext.js";
|
|
@@ -26,6 +27,10 @@ class PasswordResetManager {
|
|
|
26
27
|
if (!person.email) {
|
|
27
28
|
throw new ImplementationException();
|
|
28
29
|
}
|
|
30
|
+
const exclusiveIdps = await dbContext.queryHandler.fetch(new LocalAuthDisablingIdpsQuery(person.id));
|
|
31
|
+
if (exclusiveIdps.length > 0) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
29
34
|
const nextAllowed = await dbContext.queryHandler.fetch(
|
|
30
35
|
new NextMailAttemptQuery(person.email, "password_reset_init", "password_reset")
|
|
31
36
|
);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PasswordResetManager.js","sources":["../../../../../../packages/engine-tenant-api/src/model/service/PasswordResetManager.ts"],"sourcesContent":["import { Response, ResponseError, ResponseOk } from '../utils/Response.js'\nimport { UserMailer } from '../mailing/index.js'\nimport { ConfigurationQuery, PersonRow } from '../queries/index.js'\nimport { PermissionContext } from '../authorization/index.js'\nimport { ProjectManager } from './ProjectManager.js'\nimport { DatabaseContext } from '../utils/index.js'\nimport { ImplementationException } from '../../exceptions.js'\nimport { getPreferredProject } from './helpers/getPreferredProject.js'\nimport { CreatePersonTokenCommand, ResetPasswordCommand } from '../commands/personToken/index.js'\nimport { PasswordStrengthValidator } from './PasswordStrengthValidator.js'\nimport { ResetPasswordErrorCode, WeakPasswordReason } from '../../schema/index.js'\nimport { PersonTokenQuery } from '../queries/personToken/PersonTokenQuery.js'\nimport { AuthLogService } from './AuthLogService.js'\nimport { NextMailAttemptQuery } from '../queries/authLog/NextMailAttemptQuery.js'\n\ninterface MailOptions {\n\tproject?: string\n\tmailVariant?: string\n}\n\nexport class PasswordResetManager {\n\tconstructor(\n\t\tprivate readonly mailer: UserMailer,\n\t\tprivate readonly projectManager: ProjectManager,\n\t\tprivate readonly passwordStrengthValidator: PasswordStrengthValidator,\n\t) {}\n\n\tpublic async createPasswordResetRequest(\n\t\tdbContext: DatabaseContext,\n\t\tpermissionContext: PermissionContext,\n\t\tperson: PersonRow,\n\t\tmailOptions: MailOptions = {},\n\t): Promise<void> {\n\t\tif (!person.email) {\n\t\t\tthrow new ImplementationException()\n\t\t}\n\n\t\t// Per-email exponential backoff on outbound mails — don't spam a real\n\t\t// user with reset mails just because attackers keep poking the\n\t\t// endpoint. Reuses login_* backoff config against person_auth_log.\n\t\tconst nextAllowed = await dbContext.queryHandler.fetch(\n\t\t\tnew NextMailAttemptQuery(person.email, 'password_reset_init', 'password_reset'),\n\t\t)\n\t\tif (nextAllowed > new Date()) {\n\t\t\treturn\n\t\t}\n\n\t\tconst result = await dbContext.commandBus.execute(CreatePersonTokenCommand.createPasswordResetRequest(person.id))\n\t\tconst projects = await this.projectManager.getProjectsByIdentity(dbContext, person.identity_id, permissionContext)\n\t\tconst project = getPreferredProject(projects, mailOptions.project ?? null)\n\n\t\tawait this.mailer.sendPasswordResetEmail(\n\t\t\tdbContext,\n\t\t\t{\n\t\t\t\temail: person.email,\n\t\t\t\ttoken: result.token,\n\t\t\t\tproject: project?.name,\n\t\t\t\tprojectSlug: project?.slug,\n\t\t\t},\n\t\t\t{\n\t\t\t\tvariant: mailOptions.mailVariant || '',\n\t\t\t\tprojectId: project?.id ?? null,\n\t\t\t},\n\t\t)\n\t}\n\n\tpublic async resetPassword(dbContext: DatabaseContext, token: string, password: string): Promise<ResetPasswordResponse> {\n\t\tconst config = await dbContext.queryHandler.fetch(new ConfigurationQuery(dbContext.providers))\n\t\tconst tokenRow = await dbContext.queryHandler.fetch(PersonTokenQuery.byToken(token, 'password_reset'))\n\t\tif (!tokenRow) {\n\t\t\treturn new ResponseError('TOKEN_NOT_FOUND', 'Token not found', {\n\t\t\t\t[AuthLogService.Key]: new AuthLogService.Bag({}),\n\t\t\t})\n\t\t}\n\n\t\tconst authLogData = new AuthLogService.Bag({\n\t\t\tpersonId: tokenRow.person_id,\n\t\t\ttokenId: tokenRow.id,\n\t\t})\n\t\tconst weakPassword = await this.passwordStrengthValidator.verify(password, config.password, 'PASSWORD_TOO_WEAK')\n\t\tif (!weakPassword.ok) {\n\t\t\treturn new ResponseError(weakPassword.error, weakPassword.errorMessage, {\n\t\t\t\t...weakPassword.metadata ?? {},\n\t\t\t\t[AuthLogService.Key]: authLogData,\n\t\t\t})\n\t\t}\n\t\tconst resetPasswordResult = await dbContext.commandBus.execute(new ResetPasswordCommand(token, password))\n\t\tif (!resetPasswordResult.ok) {\n\t\t\treturn new ResponseError(resetPasswordResult.error, resetPasswordResult.errorMessage, {\n\t\t\t\t...resetPasswordResult.metadata ?? {},\n\t\t\t\t[AuthLogService.Key]: authLogData,\n\t\t\t})\n\t\t}\n\t\treturn new ResponseOk({\n\t\t\t[AuthLogService.Key]: authLogData,\n\t\t})\n\t}\n}\n\nexport type ResetPasswordResponse = Response<\n\t{\n\t\t[AuthLogService.Key]: AuthLogService.Bag\n\t},\n\tResetPasswordErrorCode,\n\t{\n\t\tweakPasswordReasons?: WeakPasswordReason[]\n\t\t[AuthLogService.Key]: AuthLogService.Bag\n\t}\n>\n"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"PasswordResetManager.js","sources":["../../../../../../packages/engine-tenant-api/src/model/service/PasswordResetManager.ts"],"sourcesContent":["import { Response, ResponseError, ResponseOk } from '../utils/Response.js'\nimport { UserMailer } from '../mailing/index.js'\nimport { ConfigurationQuery, LocalAuthDisablingIdpsQuery, PersonRow } from '../queries/index.js'\nimport { PermissionContext } from '../authorization/index.js'\nimport { ProjectManager } from './ProjectManager.js'\nimport { DatabaseContext } from '../utils/index.js'\nimport { ImplementationException } from '../../exceptions.js'\nimport { getPreferredProject } from './helpers/getPreferredProject.js'\nimport { CreatePersonTokenCommand, ResetPasswordCommand } from '../commands/personToken/index.js'\nimport { PasswordStrengthValidator } from './PasswordStrengthValidator.js'\nimport { ResetPasswordErrorCode, WeakPasswordReason } from '../../schema/index.js'\nimport { PersonTokenQuery } from '../queries/personToken/PersonTokenQuery.js'\nimport { AuthLogService } from './AuthLogService.js'\nimport { NextMailAttemptQuery } from '../queries/authLog/NextMailAttemptQuery.js'\n\ninterface MailOptions {\n\tproject?: string\n\tmailVariant?: string\n}\n\nexport class PasswordResetManager {\n\tconstructor(\n\t\tprivate readonly mailer: UserMailer,\n\t\tprivate readonly projectManager: ProjectManager,\n\t\tprivate readonly passwordStrengthValidator: PasswordStrengthValidator,\n\t) {}\n\n\tpublic async createPasswordResetRequest(\n\t\tdbContext: DatabaseContext,\n\t\tpermissionContext: PermissionContext,\n\t\tperson: PersonRow,\n\t\tmailOptions: MailOptions = {},\n\t): Promise<void> {\n\t\tif (!person.email) {\n\t\t\tthrow new ImplementationException()\n\t\t}\n\n\t\t// A person federated through a provider with `disableLocalAuthentication`\n\t\t// cannot sign in with a password, so a reset mail would only offer them a\n\t\t// credential the tenant refuses — and one more link worth phishing.\n\t\tconst exclusiveIdps = await dbContext.queryHandler.fetch(new LocalAuthDisablingIdpsQuery(person.id))\n\t\tif (exclusiveIdps.length > 0) {\n\t\t\treturn\n\t\t}\n\n\t\t// Per-email exponential backoff on outbound mails — don't spam a real\n\t\t// user with reset mails just because attackers keep poking the\n\t\t// endpoint. Reuses login_* backoff config against person_auth_log.\n\t\tconst nextAllowed = await dbContext.queryHandler.fetch(\n\t\t\tnew NextMailAttemptQuery(person.email, 'password_reset_init', 'password_reset'),\n\t\t)\n\t\tif (nextAllowed > new Date()) {\n\t\t\treturn\n\t\t}\n\n\t\tconst result = await dbContext.commandBus.execute(CreatePersonTokenCommand.createPasswordResetRequest(person.id))\n\t\tconst projects = await this.projectManager.getProjectsByIdentity(dbContext, person.identity_id, permissionContext)\n\t\tconst project = getPreferredProject(projects, mailOptions.project ?? null)\n\n\t\tawait this.mailer.sendPasswordResetEmail(\n\t\t\tdbContext,\n\t\t\t{\n\t\t\t\temail: person.email,\n\t\t\t\ttoken: result.token,\n\t\t\t\tproject: project?.name,\n\t\t\t\tprojectSlug: project?.slug,\n\t\t\t},\n\t\t\t{\n\t\t\t\tvariant: mailOptions.mailVariant || '',\n\t\t\t\tprojectId: project?.id ?? null,\n\t\t\t},\n\t\t)\n\t}\n\n\tpublic async resetPassword(dbContext: DatabaseContext, token: string, password: string): Promise<ResetPasswordResponse> {\n\t\tconst config = await dbContext.queryHandler.fetch(new ConfigurationQuery(dbContext.providers))\n\t\tconst tokenRow = await dbContext.queryHandler.fetch(PersonTokenQuery.byToken(token, 'password_reset'))\n\t\tif (!tokenRow) {\n\t\t\treturn new ResponseError('TOKEN_NOT_FOUND', 'Token not found', {\n\t\t\t\t[AuthLogService.Key]: new AuthLogService.Bag({}),\n\t\t\t})\n\t\t}\n\n\t\tconst authLogData = new AuthLogService.Bag({\n\t\t\tpersonId: tokenRow.person_id,\n\t\t\ttokenId: tokenRow.id,\n\t\t})\n\t\tconst weakPassword = await this.passwordStrengthValidator.verify(password, config.password, 'PASSWORD_TOO_WEAK')\n\t\tif (!weakPassword.ok) {\n\t\t\treturn new ResponseError(weakPassword.error, weakPassword.errorMessage, {\n\t\t\t\t...weakPassword.metadata ?? {},\n\t\t\t\t[AuthLogService.Key]: authLogData,\n\t\t\t})\n\t\t}\n\t\tconst resetPasswordResult = await dbContext.commandBus.execute(new ResetPasswordCommand(token, password))\n\t\tif (!resetPasswordResult.ok) {\n\t\t\treturn new ResponseError(resetPasswordResult.error, resetPasswordResult.errorMessage, {\n\t\t\t\t...resetPasswordResult.metadata ?? {},\n\t\t\t\t[AuthLogService.Key]: authLogData,\n\t\t\t})\n\t\t}\n\t\treturn new ResponseOk({\n\t\t\t[AuthLogService.Key]: authLogData,\n\t\t})\n\t}\n}\n\nexport type ResetPasswordResponse = Response<\n\t{\n\t\t[AuthLogService.Key]: AuthLogService.Bag\n\t},\n\tResetPasswordErrorCode,\n\t{\n\t\tweakPasswordReasons?: WeakPasswordReason[]\n\t\t[AuthLogService.Key]: AuthLogService.Bag\n\t}\n>\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;AAoBO,MAAM,qBAAqB;AAAA,EACjC,YACkB,QACA,gBACA,2BAChB;AAHgB,SAAA,SAAA;AACA,SAAA,iBAAA;AACA,SAAA,4BAAA;AAAA,EACf;AAAA,EAEH,MAAa,2BACZ,WACA,mBACA,QACA,cAA2B,CAAA,GACX;AAChB,QAAI,CAAC,OAAO,OAAO;AAClB,YAAM,IAAI,wBAAA;AAAA,IACX;AAKA,UAAM,gBAAgB,MAAM,UAAU,aAAa,MAAM,IAAI,4BAA4B,OAAO,EAAE,CAAC;AACnG,QAAI,cAAc,SAAS,GAAG;AAC7B;AAAA,IACD;AAKA,UAAM,cAAc,MAAM,UAAU,aAAa;AAAA,MAChD,IAAI,qBAAqB,OAAO,OAAO,uBAAuB,gBAAgB;AAAA,IAAA;AAE/E,QAAI,cAAc,oBAAI,QAAQ;AAC7B;AAAA,IACD;AAEA,UAAM,SAAS,MAAM,UAAU,WAAW,QAAQ,yBAAyB,2BAA2B,OAAO,EAAE,CAAC;AAChH,UAAM,WAAW,MAAM,KAAK,eAAe,sBAAsB,WAAW,OAAO,aAAa,iBAAiB;AACjH,UAAM,UAAU,oBAAoB,UAAU,YAAY,WAAW,IAAI;AAEzE,UAAM,KAAK,OAAO;AAAA,MACjB;AAAA,MACA;AAAA,QACC,OAAO,OAAO;AAAA,QACd,OAAO,OAAO;AAAA,QACd,SAAS,SAAS;AAAA,QAClB,aAAa,SAAS;AAAA,MAAA;AAAA,MAEvB;AAAA,QACC,SAAS,YAAY,eAAe;AAAA,QACpC,WAAW,SAAS,MAAM;AAAA,MAAA;AAAA,IAC3B;AAAA,EAEF;AAAA,EAEA,MAAa,cAAc,WAA4B,OAAe,UAAkD;AACvH,UAAM,SAAS,MAAM,UAAU,aAAa,MAAM,IAAI,mBAAmB,UAAU,SAAS,CAAC;AAC7F,UAAM,WAAW,MAAM,UAAU,aAAa,MAAM,iBAAiB,QAAQ,OAAO,gBAAgB,CAAC;AACrG,QAAI,CAAC,UAAU;AACd,aAAO,IAAI,cAAc,mBAAmB,mBAAmB;AAAA,QAC9D,CAAC,eAAe,GAAG,GAAG,IAAI,eAAe,IAAI,CAAA,CAAE;AAAA,MAAA,CAC/C;AAAA,IACF;AAEA,UAAM,cAAc,IAAI,eAAe,IAAI;AAAA,MAC1C,UAAU,SAAS;AAAA,MACnB,SAAS,SAAS;AAAA,IAAA,CAClB;AACD,UAAM,eAAe,MAAM,KAAK,0BAA0B,OAAO,UAAU,OAAO,UAAU,mBAAmB;AAC/G,QAAI,CAAC,aAAa,IAAI;AACrB,aAAO,IAAI,cAAc,aAAa,OAAO,aAAa,cAAc;AAAA,QACvE,GAAG,aAAa,YAAY,CAAA;AAAA,QAC5B,CAAC,eAAe,GAAG,GAAG;AAAA,MAAA,CACtB;AAAA,IACF;AACA,UAAM,sBAAsB,MAAM,UAAU,WAAW,QAAQ,IAAI,qBAAqB,OAAO,QAAQ,CAAC;AACxG,QAAI,CAAC,oBAAoB,IAAI;AAC5B,aAAO,IAAI,cAAc,oBAAoB,OAAO,oBAAoB,cAAc;AAAA,QACrF,GAAG,oBAAoB,YAAY,CAAA;AAAA,QACnC,CAAC,eAAe,GAAG,GAAG;AAAA,MAAA,CACtB;AAAA,IACF;AACA,WAAO,IAAI,WAAW;AAAA,MACrB,CAAC,eAAe,GAAG,GAAG;AAAA,IAAA,CACtB;AAAA,EACF;AACD;"}
|
|
@@ -6,6 +6,7 @@ require("../type/ApiKey.cjs");
|
|
|
6
6
|
const ConfigurationQuery = require("../queries/configuration/ConfigurationQuery.cjs");
|
|
7
7
|
const exceptions = require("../../exceptions.cjs");
|
|
8
8
|
const PersonQuery = require("../queries/person/PersonQuery.cjs");
|
|
9
|
+
const LocalAuthDisablingIdpsQuery = require("../queries/idp/LocalAuthDisablingIdpsQuery.cjs");
|
|
9
10
|
require("../authorization/PermissionActions.cjs");
|
|
10
11
|
require("@contember/authorization");
|
|
11
12
|
require("../authorization/PermissionContext.cjs");
|
|
@@ -48,6 +49,15 @@ class PasswordlessSignInManager {
|
|
|
48
49
|
})
|
|
49
50
|
});
|
|
50
51
|
}
|
|
52
|
+
const exclusiveIdps = await db2.queryHandler.fetch(new LocalAuthDisablingIdpsQuery.LocalAuthDisablingIdpsQuery(person.id));
|
|
53
|
+
if (exclusiveIdps.length > 0) {
|
|
54
|
+
return new Response.ResponseError("IDP_REQUIRED", `Sign in using ${exclusiveIdps.join(" or ")}`, {
|
|
55
|
+
[AuthLogService.AuthLogService.Key]: new AuthLogService.AuthLogService.Bag({
|
|
56
|
+
personInput: email,
|
|
57
|
+
personId: person.id
|
|
58
|
+
})
|
|
59
|
+
});
|
|
60
|
+
}
|
|
51
61
|
if (!isPasswordlessEnabled.isPasswordlessEnabled(configuration.passwordless.enabled, person.passwordless_enabled)) {
|
|
52
62
|
return new Response.ResponseError("PASSWORDLESS_DISABLED", "Passwordless sign-in is disabled for this person", {
|
|
53
63
|
[AuthLogService.AuthLogService.Key]: new AuthLogService.AuthLogService.Bag({
|
|
@@ -149,6 +159,15 @@ class PasswordlessSignInManager {
|
|
|
149
159
|
})
|
|
150
160
|
});
|
|
151
161
|
}
|
|
162
|
+
const exclusiveIdps = await db2.queryHandler.fetch(new LocalAuthDisablingIdpsQuery.LocalAuthDisablingIdpsQuery(personRow.id));
|
|
163
|
+
if (exclusiveIdps.length > 0) {
|
|
164
|
+
return new Response.ResponseError("IDP_REQUIRED", `Sign in using ${exclusiveIdps.join(" or ")}`, {
|
|
165
|
+
[AuthLogService.AuthLogService.Key]: new AuthLogService.AuthLogService.Bag({
|
|
166
|
+
personId: personRow.id,
|
|
167
|
+
tokenId: tokenResult?.id
|
|
168
|
+
})
|
|
169
|
+
});
|
|
170
|
+
}
|
|
152
171
|
let usedBackupCode = false;
|
|
153
172
|
if (personRow.otp_secret && personRow.otp_activated_at) {
|
|
154
173
|
if (mfaOtp) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PasswordlessSignInManager.cjs","sources":["../../../../../../packages/engine-tenant-api/src/model/service/PasswordlessSignInManager.ts"],"sourcesContent":["import { ApiKeyManager } from './apiKey/index.js'\nimport { ConfigurationQuery, PersonQuery, PersonRow } from '../queries/index.js'\nimport { Response, ResponseError, ResponseOk } from '../utils/Response.js'\nimport {\n\tActivatePasswordlessOtpErrorCode,\n\tInitSignInPasswordlessErrorCode,\n\tInitSignInPasswordlessResult,\n\tSignInPasswordlessErrorCode,\n} from '../../schema/index.js'\nimport { DatabaseContext, validateToken } from '../utils/index.js'\nimport { UserMailer } from '../mailing/index.js'\nimport {\n\tActivateOtpCommand,\n\tApiKeyRequestInfo,\n\tCreatePersonTokenCommand,\n\tIncreaseOtpAttemptCommand,\n\tInvalidateTokenCommand,\n\tMarkEmailVerifiedCommand,\n} from '../commands/index.js'\nimport { getPreferredProject } from './helpers/getPreferredProject.js'\nimport { isPasswordlessEnabled } from './helpers/isPasswordlessEnabled.js'\nimport { ProjectManager } from './ProjectManager.js'\nimport { PermissionContext } from '../authorization/index.js'\nimport { PersonTokenQuery } from '../queries/personToken/PersonTokenQuery.js'\nimport { ImplementationException } from '../../exceptions.js'\nimport { OtpManager } from './OtpManager.js'\nimport { BackupCodeManager } from './BackupCodeManager.js'\nimport { PersonToken } from '../type/index.js'\nimport { AuthLogService } from './AuthLogService.js'\nimport { intervalToSeconds } from '../utils/interval.js'\nimport { NextMailAttemptQuery } from '../queries/authLog/NextMailAttemptQuery.js'\n\nclass PasswordlessSignInManager {\n\tconstructor(\n\t\tprivate readonly apiKeyManager: ApiKeyManager,\n\t\tprivate readonly mailer: UserMailer,\n\t\tprivate readonly projectManager: ProjectManager,\n\t\tprivate readonly otpManager: OtpManager,\n\t\tprivate readonly backupCodeManager: BackupCodeManager,\n\t) {}\n\n\tasync initSignInPasswordless({ db, permissionContext, mailVariant, mailProject, email }: {\n\t\tdb: DatabaseContext\n\t\tpermissionContext: PermissionContext\n\t\temail: string\n\t\tmailVariant?: string\n\t\tmailProject?: string\n\t}): Promise<PasswordlessSignInManager.InitSignInPasswordlessResponse> {\n\t\treturn db.transaction(async (db): Promise<PasswordlessSignInManager.InitSignInPasswordlessResponse> => {\n\t\t\tconst configuration = await db.queryHandler.fetch(new ConfigurationQuery(db.providers))\n\t\t\tif (configuration.passwordless.enabled === 'never') {\n\t\t\t\treturn new ResponseError('PASSWORDLESS_DISABLED', 'Passwordless sign-in is disabled', {\n\t\t\t\t\t[AuthLogService.Key]: new AuthLogService.Bag({}),\n\t\t\t\t})\n\t\t\t}\n\n\t\t\tconst person = await db.queryHandler.fetch(PersonQuery.byEmail(email))\n\t\t\tif (!person) {\n\t\t\t\treturn new ResponseError('PERSON_NOT_FOUND', `Person ${email} not found`, {\n\t\t\t\t\t[AuthLogService.Key]: new AuthLogService.Bag({\n\t\t\t\t\t\tpersonInput: email,\n\t\t\t\t\t}),\n\t\t\t\t})\n\t\t\t}\n\t\t\tif (!isPasswordlessEnabled(configuration.passwordless.enabled, person.passwordless_enabled)) {\n\t\t\t\treturn new ResponseError('PASSWORDLESS_DISABLED', 'Passwordless sign-in is disabled for this person', {\n\t\t\t\t\t[AuthLogService.Key]: new AuthLogService.Bag({\n\t\t\t\t\t\tpersonInput: email,\n\t\t\t\t\t\tpersonId: person.id,\n\t\t\t\t\t}),\n\t\t\t\t})\n\t\t\t}\n\n\t\t\t// Per-email exponential backoff on outbound mails — bound the spam\n\t\t\t// pressure on a real user even if attackers spray the endpoint.\n\t\t\t// Reuses login_* backoff config against person_auth_log.\n\t\t\tconst nextAllowed = await db.queryHandler.fetch(\n\t\t\t\tnew NextMailAttemptQuery(email, 'passwordless_login_init', 'passwordless_login'),\n\t\t\t)\n\t\t\tif (nextAllowed > new Date()) {\n\t\t\t\tconst retryAfterSeconds = Math.max(1, Math.ceil((nextAllowed.getTime() - Date.now()) / 1000))\n\t\t\t\treturn new ResponseError(\n\t\t\t\t\t'RATE_LIMIT_EXCEEDED',\n\t\t\t\t\t`Too many passwordless sign-in requests for this email. Retry after ${retryAfterSeconds}s.`,\n\t\t\t\t\t{\n\t\t\t\t\t\t[AuthLogService.Key]: new AuthLogService.Bag({\n\t\t\t\t\t\t\tpersonInput: email,\n\t\t\t\t\t\t\tpersonId: person.id,\n\t\t\t\t\t\t}),\n\t\t\t\t\t},\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tconst createTokenCommand = CreatePersonTokenCommand.createPasswordlessRequest(\n\t\t\t\tperson.id,\n\t\t\t\tintervalToSeconds(configuration.passwordless.expiration) / 60,\n\t\t\t)\n\t\t\tconst result = await db.commandBus.execute(createTokenCommand)\n\n\t\t\tconst url = this.formatUrl(configuration.passwordless.url ?? null, result.token, result.id, email) ?? undefined\n\n\t\t\tconst projects = await this.projectManager.getProjectsByIdentity(db, person.identity_id, permissionContext)\n\t\t\tconst project = getPreferredProject(projects, mailProject ?? null)\n\n\t\t\tawait this.mailer.sendPasswordlessEmail(db, {\n\t\t\t\temail,\n\t\t\t\ttoken: result.token,\n\t\t\t\turl,\n\t\t\t\tproject: project?.name,\n\t\t\t\tprojectSlug: project?.slug,\n\t\t\t\trequestId: result.id,\n\t\t\t}, {\n\t\t\t\tvariant: mailVariant || '',\n\t\t\t\tprojectId: project?.id ?? null,\n\t\t\t})\n\n\t\t\treturn new ResponseOk({\n\t\t\t\trequestId: result.id,\n\t\t\t\texpiresAt: result.expiresAt,\n\t\t\t\tperson,\n\t\t\t\t[AuthLogService.Key]: new AuthLogService.Bag({\n\t\t\t\t\tpersonInput: email,\n\t\t\t\t\tpersonId: person.id,\n\t\t\t\t\ttokenId: result.id,\n\t\t\t\t}),\n\t\t\t})\n\t\t})\n\t}\n\n\tprivate formatUrl(url: string | null, token: string, requestId: string, email: string): string | null {\n\t\tif (!url) {\n\t\t\treturn null\n\t\t}\n\t\tconst urlObj = new URL(url)\n\t\turlObj.searchParams.set('token', token)\n\t\turlObj.searchParams.set('request_id', requestId)\n\t\turlObj.searchParams.set('email', email)\n\t\treturn urlObj.toString()\n\t}\n\n\tasync signInPasswordless({ db, expiration, token, requestId, mfaOtp, backupCode, validationType, requestInfo, trustForwardedInfo }: {\n\t\tdb: DatabaseContext\n\t\tvalidationType: PersonToken.ValidationType\n\t\trequestId: string\n\t\ttoken: string\n\t\tmfaOtp?: string\n\t\tbackupCode?: string\n\t\texpiration?: number\n\t\trequestInfo?: ApiKeyRequestInfo\n\t\ttrustForwardedInfo?: boolean\n\t}): Promise<PasswordlessSignInManager.SignInPasswordlessResponse> {\n\t\treturn db.transaction(async (db): Promise<PasswordlessSignInManager.SignInPasswordlessResponse> => {\n\t\t\tconst tokenResult = await db.queryHandler.fetch(PersonTokenQuery.byId(requestId, 'passwordless'))\n\t\t\tconst tokenValidationResult = validateToken({\n\t\t\t\tentry: tokenResult,\n\t\t\t\ttoken,\n\t\t\t\tnow: db.providers.now(),\n\t\t\t\tvalidationType,\n\t\t\t})\n\t\t\tif (!tokenValidationResult.ok) {\n\t\t\t\tif (tokenValidationResult.error === 'TOKEN_INVALID' && validationType === 'otp') {\n\t\t\t\t\tawait db.commandBus.execute(new IncreaseOtpAttemptCommand(tokenResult!.id))\n\t\t\t\t}\n\t\t\t\treturn new ResponseError(tokenValidationResult.error, tokenValidationResult.errorMessage, {\n\t\t\t\t\t...tokenValidationResult.metadata ?? {},\n\t\t\t\t\t[AuthLogService.Key]: new AuthLogService.Bag({\n\t\t\t\t\t\tpersonId: tokenResult?.person_id,\n\t\t\t\t\t\ttokenId: tokenResult?.id,\n\t\t\t\t\t}),\n\t\t\t\t})\n\t\t\t}\n\t\t\tif (!tokenResult) {\n\t\t\t\tthrow new ImplementationException()\n\t\t\t}\n\t\t\tconst personRow = await db.queryHandler.fetch(PersonQuery.byId(tokenValidationResult.result.person_id))\n\t\t\tif (!personRow) {\n\t\t\t\tthrow new ImplementationException()\n\t\t\t}\n\t\t\tif (personRow.disabled_at !== null) {\n\t\t\t\treturn new ResponseError('PERSON_DISABLED', `Person is disabled`, {\n\t\t\t\t\t[AuthLogService.Key]: new AuthLogService.Bag({\n\t\t\t\t\t\tpersonId: personRow.id,\n\t\t\t\t\t\ttokenId: tokenResult?.id,\n\t\t\t\t\t}),\n\t\t\t\t})\n\t\t\t}\n\n\t\t\tlet usedBackupCode = false\n\t\t\tif (personRow.otp_secret && personRow.otp_activated_at) {\n\t\t\t\tif (mfaOtp) {\n\t\t\t\t\tif (!await this.otpManager.verifyOtp(db, personRow, mfaOtp)) {\n\t\t\t\t\t\treturn new ResponseError('INVALID_OTP_TOKEN', 'OTP token validation has failed', {\n\t\t\t\t\t\t\t[AuthLogService.Key]: new AuthLogService.Bag({\n\t\t\t\t\t\t\t\tpersonId: personRow.id,\n\t\t\t\t\t\t\t\ttokenId: tokenResult?.id,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t} else if (backupCode) {\n\t\t\t\t\tif (!await this.backupCodeManager.verifyAndConsume(db, personRow, backupCode)) {\n\t\t\t\t\t\treturn new ResponseError('INVALID_OTP_TOKEN', 'OTP token validation has failed', {\n\t\t\t\t\t\t\t[AuthLogService.Key]: new AuthLogService.Bag({\n\t\t\t\t\t\t\t\tpersonId: personRow.id,\n\t\t\t\t\t\t\t\ttokenId: tokenResult?.id,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t\tusedBackupCode = true\n\t\t\t\t} else {\n\t\t\t\t\treturn new ResponseError('OTP_REQUIRED', `2FA is enabled. OTP token is required`, {\n\t\t\t\t\t\t[AuthLogService.Key]: new AuthLogService.Bag({\n\t\t\t\t\t\t\tpersonId: personRow.id,\n\t\t\t\t\t\t\ttokenId: tokenResult?.id,\n\t\t\t\t\t\t}),\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t\t// NOTE: passwordless sign-in does NOT enforce mandatory-MFA enrollment.\n\t\t\t// A person with no MFA factor is let through even when an auth_policy would\n\t\t\t// require MFA — enroll-via-sign-in is password-only, and passwordless email\n\t\t\t// access is itself a possession factor. An existing factor (TOTP/backup\n\t\t\t// code, handled above) is still verified. Documented limitation (A06).\n\n\t\t\tawait db.commandBus.execute(new InvalidateTokenCommand(tokenValidationResult.result.id))\n\t\t\t// Receiving and clicking the magic link proves ownership of the address,\n\t\t\t// so a successful passwordless sign-in also satisfies the e-mail\n\t\t\t// verification requirement. Without this, a required-but-unverified\n\t\t\t// account could obtain a session here while being blocked from password\n\t\t\t// sign-in (SignInManager) — an inconsistent, exploitable gap.\n\t\t\tconst emailVerifiedNow = personRow.email_verified_at === null\n\t\t\tif (emailVerifiedNow) {\n\t\t\t\tawait db.commandBus.execute(new MarkEmailVerifiedCommand(personRow.id))\n\t\t\t}\n\t\t\tconst sessionToken = await this.apiKeyManager.createSessionApiKey(db, personRow.identity_id, expiration, requestInfo, trustForwardedInfo)\n\n\t\t\treturn new ResponseOk({\n\t\t\t\tperson: personRow,\n\t\t\t\ttoken: sessionToken,\n\t\t\t\tusedBackupCode,\n\t\t\t\t[AuthLogService.Key]: new AuthLogService.Bag({\n\t\t\t\t\tpersonId: personRow.id,\n\t\t\t\t\ttokenId: tokenResult?.id,\n\t\t\t\t\t// Record that this sign-in also satisfied e-mail verification, so\n\t\t\t\t\t// the audit trail shows when/how the address became verified.\n\t\t\t\t\teventData: emailVerifiedNow ? { emailVerified: true } : undefined,\n\t\t\t\t}),\n\t\t\t})\n\t\t})\n\t}\n\n\tasync activatePasswordlessOtp({ db, token, requestId, otpHash }: {\n\t\tdb: DatabaseContext\n\t\trequestId: string\n\t\ttoken: string\n\t\totpHash: string\n\t}): Promise<PasswordlessSignInManager.ActivatePasswordlessOtpResponse> {\n\t\treturn db.transaction(async (db): Promise<PasswordlessSignInManager.ActivatePasswordlessOtpResponse> => {\n\t\t\tconst tokenResult = await db.queryHandler.fetch(PersonTokenQuery.byId(requestId, 'passwordless'))\n\t\t\tconst tokenValidationResult = validateToken({\n\t\t\t\tentry: tokenResult,\n\t\t\t\ttoken,\n\t\t\t\tnow: db.providers.now(),\n\t\t\t\tvalidationType: 'token',\n\t\t\t})\n\t\t\tif (!tokenValidationResult.ok) {\n\t\t\t\treturn new ResponseError(tokenValidationResult.error, tokenValidationResult.errorMessage, {\n\t\t\t\t\t...tokenValidationResult.metadata ?? {},\n\t\t\t\t\t[AuthLogService.Key]: new AuthLogService.Bag({\n\t\t\t\t\t\tpersonId: tokenResult?.person_id,\n\t\t\t\t\t\ttokenId: tokenResult?.id,\n\t\t\t\t\t}),\n\t\t\t\t})\n\t\t\t}\n\n\t\t\tawait db.commandBus.execute(new ActivateOtpCommand(tokenValidationResult.result.id, otpHash))\n\n\t\t\treturn new ResponseOk({\n\t\t\t\tperson: tokenResult?.person_id,\n\t\t\t\t[AuthLogService.Key]: new AuthLogService.Bag({\n\t\t\t\t\tpersonId: tokenResult?.person_id,\n\t\t\t\t\ttokenId: tokenResult?.id,\n\t\t\t\t}),\n\t\t\t})\n\t\t})\n\t}\n}\n\nnamespace PasswordlessSignInManager {\n\texport type InitSignInPasswordlessResponse = Response<\n\t\tInitSignInPasswordlessResult & {\n\t\t\t[AuthLogService.Key]: AuthLogService.Bag\n\t\t},\n\t\tInitSignInPasswordlessErrorCode,\n\t\t{\n\t\t\t[AuthLogService.Key]: AuthLogService.Bag\n\t\t}\n\t>\n\n\tinterface SignInPasswordlessResult {\n\t\treadonly person: PersonRow\n\t\treadonly token: string\n\t\t/** True when MFA was satisfied by consuming a backup code (instead of a TOTP token). */\n\t\treadonly usedBackupCode?: boolean\n\t\t[AuthLogService.Key]: AuthLogService.Bag\n\t}\n\texport type SignInPasswordlessResponse = Response<SignInPasswordlessResult, SignInPasswordlessErrorCode, {\n\t\t[AuthLogService.Key]: AuthLogService.Bag\n\t}>\n\n\texport type ActivatePasswordlessOtpResponse = Response<\n\t\t{\n\t\t\t[AuthLogService.Key]: AuthLogService.Bag\n\t\t},\n\t\tActivatePasswordlessOtpErrorCode,\n\t\t{\n\t\t\t[AuthLogService.Key]: AuthLogService.Bag\n\t\t}\n\t>\n}\n\nexport { PasswordlessSignInManager }\n"],"names":["db","ConfigurationQuery","ResponseError","AuthLogService","PersonQuery","isPasswordlessEnabled","NextMailAttemptQuery","CreatePersonTokenCommand","intervalToSeconds","getPreferredProject","ResponseOk","token","PersonTokenQuery","validateToken","IncreaseOtpAttemptCommand","ImplementationException","InvalidateTokenCommand","MarkEmailVerifiedCommand","ActivateOtpCommand"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PasswordlessSignInManager.cjs","sources":["../../../../../../packages/engine-tenant-api/src/model/service/PasswordlessSignInManager.ts"],"sourcesContent":["import { ApiKeyManager } from './apiKey/index.js'\nimport { ConfigurationQuery, LocalAuthDisablingIdpsQuery, PersonQuery, PersonRow } from '../queries/index.js'\nimport { Response, ResponseError, ResponseOk } from '../utils/Response.js'\nimport {\n\tActivatePasswordlessOtpErrorCode,\n\tInitSignInPasswordlessErrorCode,\n\tInitSignInPasswordlessResult,\n\tSignInPasswordlessErrorCode,\n} from '../../schema/index.js'\nimport { DatabaseContext, validateToken } from '../utils/index.js'\nimport { UserMailer } from '../mailing/index.js'\nimport {\n\tActivateOtpCommand,\n\tApiKeyRequestInfo,\n\tCreatePersonTokenCommand,\n\tIncreaseOtpAttemptCommand,\n\tInvalidateTokenCommand,\n\tMarkEmailVerifiedCommand,\n} from '../commands/index.js'\nimport { getPreferredProject } from './helpers/getPreferredProject.js'\nimport { isPasswordlessEnabled } from './helpers/isPasswordlessEnabled.js'\nimport { ProjectManager } from './ProjectManager.js'\nimport { PermissionContext } from '../authorization/index.js'\nimport { PersonTokenQuery } from '../queries/personToken/PersonTokenQuery.js'\nimport { ImplementationException } from '../../exceptions.js'\nimport { OtpManager } from './OtpManager.js'\nimport { BackupCodeManager } from './BackupCodeManager.js'\nimport { PersonToken } from '../type/index.js'\nimport { AuthLogService } from './AuthLogService.js'\nimport { intervalToSeconds } from '../utils/interval.js'\nimport { NextMailAttemptQuery } from '../queries/authLog/NextMailAttemptQuery.js'\n\nclass PasswordlessSignInManager {\n\tconstructor(\n\t\tprivate readonly apiKeyManager: ApiKeyManager,\n\t\tprivate readonly mailer: UserMailer,\n\t\tprivate readonly projectManager: ProjectManager,\n\t\tprivate readonly otpManager: OtpManager,\n\t\tprivate readonly backupCodeManager: BackupCodeManager,\n\t) {}\n\n\tasync initSignInPasswordless({ db, permissionContext, mailVariant, mailProject, email }: {\n\t\tdb: DatabaseContext\n\t\tpermissionContext: PermissionContext\n\t\temail: string\n\t\tmailVariant?: string\n\t\tmailProject?: string\n\t}): Promise<PasswordlessSignInManager.InitSignInPasswordlessResponse> {\n\t\treturn db.transaction(async (db): Promise<PasswordlessSignInManager.InitSignInPasswordlessResponse> => {\n\t\t\tconst configuration = await db.queryHandler.fetch(new ConfigurationQuery(db.providers))\n\t\t\tif (configuration.passwordless.enabled === 'never') {\n\t\t\t\treturn new ResponseError('PASSWORDLESS_DISABLED', 'Passwordless sign-in is disabled', {\n\t\t\t\t\t[AuthLogService.Key]: new AuthLogService.Bag({}),\n\t\t\t\t})\n\t\t\t}\n\n\t\t\tconst person = await db.queryHandler.fetch(PersonQuery.byEmail(email))\n\t\t\tif (!person) {\n\t\t\t\treturn new ResponseError('PERSON_NOT_FOUND', `Person ${email} not found`, {\n\t\t\t\t\t[AuthLogService.Key]: new AuthLogService.Bag({\n\t\t\t\t\t\tpersonInput: email,\n\t\t\t\t\t}),\n\t\t\t\t})\n\t\t\t}\n\t\t\tconst exclusiveIdps = await db.queryHandler.fetch(new LocalAuthDisablingIdpsQuery(person.id))\n\t\t\tif (exclusiveIdps.length > 0) {\n\t\t\t\treturn new ResponseError('IDP_REQUIRED', `Sign in using ${exclusiveIdps.join(' or ')}`, {\n\t\t\t\t\t[AuthLogService.Key]: new AuthLogService.Bag({\n\t\t\t\t\t\tpersonInput: email,\n\t\t\t\t\t\tpersonId: person.id,\n\t\t\t\t\t}),\n\t\t\t\t})\n\t\t\t}\n\n\t\t\tif (!isPasswordlessEnabled(configuration.passwordless.enabled, person.passwordless_enabled)) {\n\t\t\t\treturn new ResponseError('PASSWORDLESS_DISABLED', 'Passwordless sign-in is disabled for this person', {\n\t\t\t\t\t[AuthLogService.Key]: new AuthLogService.Bag({\n\t\t\t\t\t\tpersonInput: email,\n\t\t\t\t\t\tpersonId: person.id,\n\t\t\t\t\t}),\n\t\t\t\t})\n\t\t\t}\n\n\t\t\t// Per-email exponential backoff on outbound mails — bound the spam\n\t\t\t// pressure on a real user even if attackers spray the endpoint.\n\t\t\t// Reuses login_* backoff config against person_auth_log.\n\t\t\tconst nextAllowed = await db.queryHandler.fetch(\n\t\t\t\tnew NextMailAttemptQuery(email, 'passwordless_login_init', 'passwordless_login'),\n\t\t\t)\n\t\t\tif (nextAllowed > new Date()) {\n\t\t\t\tconst retryAfterSeconds = Math.max(1, Math.ceil((nextAllowed.getTime() - Date.now()) / 1000))\n\t\t\t\treturn new ResponseError(\n\t\t\t\t\t'RATE_LIMIT_EXCEEDED',\n\t\t\t\t\t`Too many passwordless sign-in requests for this email. Retry after ${retryAfterSeconds}s.`,\n\t\t\t\t\t{\n\t\t\t\t\t\t[AuthLogService.Key]: new AuthLogService.Bag({\n\t\t\t\t\t\t\tpersonInput: email,\n\t\t\t\t\t\t\tpersonId: person.id,\n\t\t\t\t\t\t}),\n\t\t\t\t\t},\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tconst createTokenCommand = CreatePersonTokenCommand.createPasswordlessRequest(\n\t\t\t\tperson.id,\n\t\t\t\tintervalToSeconds(configuration.passwordless.expiration) / 60,\n\t\t\t)\n\t\t\tconst result = await db.commandBus.execute(createTokenCommand)\n\n\t\t\tconst url = this.formatUrl(configuration.passwordless.url ?? null, result.token, result.id, email) ?? undefined\n\n\t\t\tconst projects = await this.projectManager.getProjectsByIdentity(db, person.identity_id, permissionContext)\n\t\t\tconst project = getPreferredProject(projects, mailProject ?? null)\n\n\t\t\tawait this.mailer.sendPasswordlessEmail(db, {\n\t\t\t\temail,\n\t\t\t\ttoken: result.token,\n\t\t\t\turl,\n\t\t\t\tproject: project?.name,\n\t\t\t\tprojectSlug: project?.slug,\n\t\t\t\trequestId: result.id,\n\t\t\t}, {\n\t\t\t\tvariant: mailVariant || '',\n\t\t\t\tprojectId: project?.id ?? null,\n\t\t\t})\n\n\t\t\treturn new ResponseOk({\n\t\t\t\trequestId: result.id,\n\t\t\t\texpiresAt: result.expiresAt,\n\t\t\t\tperson,\n\t\t\t\t[AuthLogService.Key]: new AuthLogService.Bag({\n\t\t\t\t\tpersonInput: email,\n\t\t\t\t\tpersonId: person.id,\n\t\t\t\t\ttokenId: result.id,\n\t\t\t\t}),\n\t\t\t})\n\t\t})\n\t}\n\n\tprivate formatUrl(url: string | null, token: string, requestId: string, email: string): string | null {\n\t\tif (!url) {\n\t\t\treturn null\n\t\t}\n\t\tconst urlObj = new URL(url)\n\t\turlObj.searchParams.set('token', token)\n\t\turlObj.searchParams.set('request_id', requestId)\n\t\turlObj.searchParams.set('email', email)\n\t\treturn urlObj.toString()\n\t}\n\n\tasync signInPasswordless({ db, expiration, token, requestId, mfaOtp, backupCode, validationType, requestInfo, trustForwardedInfo }: {\n\t\tdb: DatabaseContext\n\t\tvalidationType: PersonToken.ValidationType\n\t\trequestId: string\n\t\ttoken: string\n\t\tmfaOtp?: string\n\t\tbackupCode?: string\n\t\texpiration?: number\n\t\trequestInfo?: ApiKeyRequestInfo\n\t\ttrustForwardedInfo?: boolean\n\t}): Promise<PasswordlessSignInManager.SignInPasswordlessResponse> {\n\t\treturn db.transaction(async (db): Promise<PasswordlessSignInManager.SignInPasswordlessResponse> => {\n\t\t\tconst tokenResult = await db.queryHandler.fetch(PersonTokenQuery.byId(requestId, 'passwordless'))\n\t\t\tconst tokenValidationResult = validateToken({\n\t\t\t\tentry: tokenResult,\n\t\t\t\ttoken,\n\t\t\t\tnow: db.providers.now(),\n\t\t\t\tvalidationType,\n\t\t\t})\n\t\t\tif (!tokenValidationResult.ok) {\n\t\t\t\tif (tokenValidationResult.error === 'TOKEN_INVALID' && validationType === 'otp') {\n\t\t\t\t\tawait db.commandBus.execute(new IncreaseOtpAttemptCommand(tokenResult!.id))\n\t\t\t\t}\n\t\t\t\treturn new ResponseError(tokenValidationResult.error, tokenValidationResult.errorMessage, {\n\t\t\t\t\t...tokenValidationResult.metadata ?? {},\n\t\t\t\t\t[AuthLogService.Key]: new AuthLogService.Bag({\n\t\t\t\t\t\tpersonId: tokenResult?.person_id,\n\t\t\t\t\t\ttokenId: tokenResult?.id,\n\t\t\t\t\t}),\n\t\t\t\t})\n\t\t\t}\n\t\t\tif (!tokenResult) {\n\t\t\t\tthrow new ImplementationException()\n\t\t\t}\n\t\t\tconst personRow = await db.queryHandler.fetch(PersonQuery.byId(tokenValidationResult.result.person_id))\n\t\t\tif (!personRow) {\n\t\t\t\tthrow new ImplementationException()\n\t\t\t}\n\t\t\tif (personRow.disabled_at !== null) {\n\t\t\t\treturn new ResponseError('PERSON_DISABLED', `Person is disabled`, {\n\t\t\t\t\t[AuthLogService.Key]: new AuthLogService.Bag({\n\t\t\t\t\t\tpersonId: personRow.id,\n\t\t\t\t\t\ttokenId: tokenResult?.id,\n\t\t\t\t\t}),\n\t\t\t\t})\n\t\t\t}\n\n\t\t\t// Re-checked here and not only at init: a link created while this magic link\n\t\t\t// was in flight would otherwise still mint a locally authenticated session.\n\t\t\tconst exclusiveIdps = await db.queryHandler.fetch(new LocalAuthDisablingIdpsQuery(personRow.id))\n\t\t\tif (exclusiveIdps.length > 0) {\n\t\t\t\treturn new ResponseError('IDP_REQUIRED', `Sign in using ${exclusiveIdps.join(' or ')}`, {\n\t\t\t\t\t[AuthLogService.Key]: new AuthLogService.Bag({\n\t\t\t\t\t\tpersonId: personRow.id,\n\t\t\t\t\t\ttokenId: tokenResult?.id,\n\t\t\t\t\t}),\n\t\t\t\t})\n\t\t\t}\n\n\t\t\tlet usedBackupCode = false\n\t\t\tif (personRow.otp_secret && personRow.otp_activated_at) {\n\t\t\t\tif (mfaOtp) {\n\t\t\t\t\tif (!await this.otpManager.verifyOtp(db, personRow, mfaOtp)) {\n\t\t\t\t\t\treturn new ResponseError('INVALID_OTP_TOKEN', 'OTP token validation has failed', {\n\t\t\t\t\t\t\t[AuthLogService.Key]: new AuthLogService.Bag({\n\t\t\t\t\t\t\t\tpersonId: personRow.id,\n\t\t\t\t\t\t\t\ttokenId: tokenResult?.id,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t} else if (backupCode) {\n\t\t\t\t\tif (!await this.backupCodeManager.verifyAndConsume(db, personRow, backupCode)) {\n\t\t\t\t\t\treturn new ResponseError('INVALID_OTP_TOKEN', 'OTP token validation has failed', {\n\t\t\t\t\t\t\t[AuthLogService.Key]: new AuthLogService.Bag({\n\t\t\t\t\t\t\t\tpersonId: personRow.id,\n\t\t\t\t\t\t\t\ttokenId: tokenResult?.id,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t\tusedBackupCode = true\n\t\t\t\t} else {\n\t\t\t\t\treturn new ResponseError('OTP_REQUIRED', `2FA is enabled. OTP token is required`, {\n\t\t\t\t\t\t[AuthLogService.Key]: new AuthLogService.Bag({\n\t\t\t\t\t\t\tpersonId: personRow.id,\n\t\t\t\t\t\t\ttokenId: tokenResult?.id,\n\t\t\t\t\t\t}),\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t\t// NOTE: passwordless sign-in does NOT enforce mandatory-MFA enrollment.\n\t\t\t// A person with no MFA factor is let through even when an auth_policy would\n\t\t\t// require MFA — enroll-via-sign-in is password-only, and passwordless email\n\t\t\t// access is itself a possession factor. An existing factor (TOTP/backup\n\t\t\t// code, handled above) is still verified. Documented limitation (A06).\n\n\t\t\tawait db.commandBus.execute(new InvalidateTokenCommand(tokenValidationResult.result.id))\n\t\t\t// Receiving and clicking the magic link proves ownership of the address,\n\t\t\t// so a successful passwordless sign-in also satisfies the e-mail\n\t\t\t// verification requirement. Without this, a required-but-unverified\n\t\t\t// account could obtain a session here while being blocked from password\n\t\t\t// sign-in (SignInManager) — an inconsistent, exploitable gap.\n\t\t\tconst emailVerifiedNow = personRow.email_verified_at === null\n\t\t\tif (emailVerifiedNow) {\n\t\t\t\tawait db.commandBus.execute(new MarkEmailVerifiedCommand(personRow.id))\n\t\t\t}\n\t\t\tconst sessionToken = await this.apiKeyManager.createSessionApiKey(db, personRow.identity_id, expiration, requestInfo, trustForwardedInfo)\n\n\t\t\treturn new ResponseOk({\n\t\t\t\tperson: personRow,\n\t\t\t\ttoken: sessionToken,\n\t\t\t\tusedBackupCode,\n\t\t\t\t[AuthLogService.Key]: new AuthLogService.Bag({\n\t\t\t\t\tpersonId: personRow.id,\n\t\t\t\t\ttokenId: tokenResult?.id,\n\t\t\t\t\t// Record that this sign-in also satisfied e-mail verification, so\n\t\t\t\t\t// the audit trail shows when/how the address became verified.\n\t\t\t\t\teventData: emailVerifiedNow ? { emailVerified: true } : undefined,\n\t\t\t\t}),\n\t\t\t})\n\t\t})\n\t}\n\n\tasync activatePasswordlessOtp({ db, token, requestId, otpHash }: {\n\t\tdb: DatabaseContext\n\t\trequestId: string\n\t\ttoken: string\n\t\totpHash: string\n\t}): Promise<PasswordlessSignInManager.ActivatePasswordlessOtpResponse> {\n\t\treturn db.transaction(async (db): Promise<PasswordlessSignInManager.ActivatePasswordlessOtpResponse> => {\n\t\t\tconst tokenResult = await db.queryHandler.fetch(PersonTokenQuery.byId(requestId, 'passwordless'))\n\t\t\tconst tokenValidationResult = validateToken({\n\t\t\t\tentry: tokenResult,\n\t\t\t\ttoken,\n\t\t\t\tnow: db.providers.now(),\n\t\t\t\tvalidationType: 'token',\n\t\t\t})\n\t\t\tif (!tokenValidationResult.ok) {\n\t\t\t\treturn new ResponseError(tokenValidationResult.error, tokenValidationResult.errorMessage, {\n\t\t\t\t\t...tokenValidationResult.metadata ?? {},\n\t\t\t\t\t[AuthLogService.Key]: new AuthLogService.Bag({\n\t\t\t\t\t\tpersonId: tokenResult?.person_id,\n\t\t\t\t\t\ttokenId: tokenResult?.id,\n\t\t\t\t\t}),\n\t\t\t\t})\n\t\t\t}\n\n\t\t\tawait db.commandBus.execute(new ActivateOtpCommand(tokenValidationResult.result.id, otpHash))\n\n\t\t\treturn new ResponseOk({\n\t\t\t\tperson: tokenResult?.person_id,\n\t\t\t\t[AuthLogService.Key]: new AuthLogService.Bag({\n\t\t\t\t\tpersonId: tokenResult?.person_id,\n\t\t\t\t\ttokenId: tokenResult?.id,\n\t\t\t\t}),\n\t\t\t})\n\t\t})\n\t}\n}\n\nnamespace PasswordlessSignInManager {\n\texport type InitSignInPasswordlessResponse = Response<\n\t\tInitSignInPasswordlessResult & {\n\t\t\t[AuthLogService.Key]: AuthLogService.Bag\n\t\t},\n\t\tInitSignInPasswordlessErrorCode,\n\t\t{\n\t\t\t[AuthLogService.Key]: AuthLogService.Bag\n\t\t}\n\t>\n\n\tinterface SignInPasswordlessResult {\n\t\treadonly person: PersonRow\n\t\treadonly token: string\n\t\t/** True when MFA was satisfied by consuming a backup code (instead of a TOTP token). */\n\t\treadonly usedBackupCode?: boolean\n\t\t[AuthLogService.Key]: AuthLogService.Bag\n\t}\n\texport type SignInPasswordlessResponse = Response<SignInPasswordlessResult, SignInPasswordlessErrorCode, {\n\t\t[AuthLogService.Key]: AuthLogService.Bag\n\t}>\n\n\texport type ActivatePasswordlessOtpResponse = Response<\n\t\t{\n\t\t\t[AuthLogService.Key]: AuthLogService.Bag\n\t\t},\n\t\tActivatePasswordlessOtpErrorCode,\n\t\t{\n\t\t\t[AuthLogService.Key]: AuthLogService.Bag\n\t\t}\n\t>\n}\n\nexport { PasswordlessSignInManager }\n"],"names":["db","ConfigurationQuery","ResponseError","AuthLogService","PersonQuery","LocalAuthDisablingIdpsQuery","isPasswordlessEnabled","NextMailAttemptQuery","CreatePersonTokenCommand","intervalToSeconds","getPreferredProject","ResponseOk","token","PersonTokenQuery","validateToken","IncreaseOtpAttemptCommand","ImplementationException","InvalidateTokenCommand","MarkEmailVerifiedCommand","ActivateOtpCommand"],"mappings":"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"}
|
|
@@ -4,6 +4,7 @@ import "../type/ApiKey.js";
|
|
|
4
4
|
import { ConfigurationQuery } from "../queries/configuration/ConfigurationQuery.js";
|
|
5
5
|
import { ImplementationException } from "../../exceptions.js";
|
|
6
6
|
import { PersonQuery } from "../queries/person/PersonQuery.js";
|
|
7
|
+
import { LocalAuthDisablingIdpsQuery } from "../queries/idp/LocalAuthDisablingIdpsQuery.js";
|
|
7
8
|
import "../authorization/PermissionActions.js";
|
|
8
9
|
import "@contember/authorization";
|
|
9
10
|
import "../authorization/PermissionContext.js";
|
|
@@ -46,6 +47,15 @@ class PasswordlessSignInManager {
|
|
|
46
47
|
})
|
|
47
48
|
});
|
|
48
49
|
}
|
|
50
|
+
const exclusiveIdps = await db2.queryHandler.fetch(new LocalAuthDisablingIdpsQuery(person.id));
|
|
51
|
+
if (exclusiveIdps.length > 0) {
|
|
52
|
+
return new ResponseError("IDP_REQUIRED", `Sign in using ${exclusiveIdps.join(" or ")}`, {
|
|
53
|
+
[AuthLogService.Key]: new AuthLogService.Bag({
|
|
54
|
+
personInput: email,
|
|
55
|
+
personId: person.id
|
|
56
|
+
})
|
|
57
|
+
});
|
|
58
|
+
}
|
|
49
59
|
if (!isPasswordlessEnabled(configuration.passwordless.enabled, person.passwordless_enabled)) {
|
|
50
60
|
return new ResponseError("PASSWORDLESS_DISABLED", "Passwordless sign-in is disabled for this person", {
|
|
51
61
|
[AuthLogService.Key]: new AuthLogService.Bag({
|
|
@@ -147,6 +157,15 @@ class PasswordlessSignInManager {
|
|
|
147
157
|
})
|
|
148
158
|
});
|
|
149
159
|
}
|
|
160
|
+
const exclusiveIdps = await db2.queryHandler.fetch(new LocalAuthDisablingIdpsQuery(personRow.id));
|
|
161
|
+
if (exclusiveIdps.length > 0) {
|
|
162
|
+
return new ResponseError("IDP_REQUIRED", `Sign in using ${exclusiveIdps.join(" or ")}`, {
|
|
163
|
+
[AuthLogService.Key]: new AuthLogService.Bag({
|
|
164
|
+
personId: personRow.id,
|
|
165
|
+
tokenId: tokenResult?.id
|
|
166
|
+
})
|
|
167
|
+
});
|
|
168
|
+
}
|
|
150
169
|
let usedBackupCode = false;
|
|
151
170
|
if (personRow.otp_secret && personRow.otp_activated_at) {
|
|
152
171
|
if (mfaOtp) {
|