@contember/engine-tenant-api 2.2.0-alpha.5 → 2.2.0-alpha.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +1 -0
- package/dist/development/index.cjs +2 -0
- package/dist/development/index.cjs.map +1 -1
- package/dist/development/index.js +2 -0
- package/dist/development/index.js.map +1 -1
- package/dist/development/src/migrations/2026-09-04-120000-idp-disable-local-authentication.cjs +18 -0
- package/dist/development/src/migrations/2026-09-04-120000-idp-disable-local-authentication.cjs.map +1 -0
- package/dist/development/src/migrations/2026-09-04-120000-idp-disable-local-authentication.js +19 -0
- package/dist/development/src/migrations/2026-09-04-120000-idp-disable-local-authentication.js.map +1 -0
- package/dist/development/src/migrations/runner.cjs +3 -1
- package/dist/development/src/migrations/runner.cjs.map +1 -1
- package/dist/development/src/migrations/runner.js +3 -1
- package/dist/development/src/migrations/runner.js.map +1 -1
- package/dist/development/src/migrations/snapshot.cjs +2 -1
- package/dist/development/src/migrations/snapshot.cjs.map +1 -1
- package/dist/development/src/migrations/snapshot.js +2 -1
- package/dist/development/src/migrations/snapshot.js.map +1 -1
- package/dist/development/src/model/commands/idp/CreateIdpCommand.cjs +8 -2
- package/dist/development/src/model/commands/idp/CreateIdpCommand.cjs.map +1 -1
- package/dist/development/src/model/commands/idp/CreateIdpCommand.js +8 -2
- package/dist/development/src/model/commands/idp/CreateIdpCommand.js.map +1 -1
- package/dist/development/src/model/commands/idp/UpdateIdpCommand.cjs +6 -2
- package/dist/development/src/model/commands/idp/UpdateIdpCommand.cjs.map +1 -1
- package/dist/development/src/model/commands/idp/UpdateIdpCommand.js +6 -2
- package/dist/development/src/model/commands/idp/UpdateIdpCommand.js.map +1 -1
- package/dist/development/src/model/queries/idp/LocalAuthDisablingIdpsQuery.cjs +21 -0
- package/dist/development/src/model/queries/idp/LocalAuthDisablingIdpsQuery.cjs.map +1 -0
- package/dist/development/src/model/queries/idp/LocalAuthDisablingIdpsQuery.js +21 -0
- package/dist/development/src/model/queries/idp/LocalAuthDisablingIdpsQuery.js.map +1 -0
- package/dist/development/src/model/queries/idp/PersonIdentityProvidersQuery.cjs +1 -1
- package/dist/development/src/model/queries/idp/PersonIdentityProvidersQuery.cjs.map +1 -1
- package/dist/development/src/model/queries/idp/PersonIdentityProvidersQuery.js +1 -1
- package/dist/development/src/model/queries/idp/PersonIdentityProvidersQuery.js.map +1 -1
- package/dist/development/src/model/queries/idp/helpers.cjs +3 -3
- package/dist/development/src/model/queries/idp/helpers.cjs.map +1 -1
- package/dist/development/src/model/queries/idp/helpers.js +3 -3
- package/dist/development/src/model/queries/idp/helpers.js.map +1 -1
- package/dist/development/src/model/service/PasswordResetManager.cjs +5 -0
- package/dist/development/src/model/service/PasswordResetManager.cjs.map +1 -1
- package/dist/development/src/model/service/PasswordResetManager.js +5 -0
- package/dist/development/src/model/service/PasswordResetManager.js.map +1 -1
- package/dist/development/src/model/service/PasswordlessSignInManager.cjs +19 -0
- package/dist/development/src/model/service/PasswordlessSignInManager.cjs.map +1 -1
- package/dist/development/src/model/service/PasswordlessSignInManager.js +19 -0
- package/dist/development/src/model/service/PasswordlessSignInManager.js.map +1 -1
- package/dist/development/src/model/service/SignInManager.cjs +5 -0
- package/dist/development/src/model/service/SignInManager.cjs.map +1 -1
- package/dist/development/src/model/service/SignInManager.js +5 -0
- package/dist/development/src/model/service/SignInManager.js.map +1 -1
- package/dist/development/src/model/service/idp/PersonIdentityProviderManager.cjs +6 -0
- package/dist/development/src/model/service/idp/PersonIdentityProviderManager.cjs.map +1 -1
- package/dist/development/src/model/service/idp/PersonIdentityProviderManager.js +6 -0
- package/dist/development/src/model/service/idp/PersonIdentityProviderManager.js.map +1 -1
- package/dist/development/src/resolvers/mutation/idp/AddIDPMutationResolver.cjs +2 -1
- package/dist/development/src/resolvers/mutation/idp/AddIDPMutationResolver.cjs.map +1 -1
- package/dist/development/src/resolvers/mutation/idp/AddIDPMutationResolver.js +2 -1
- package/dist/development/src/resolvers/mutation/idp/AddIDPMutationResolver.js.map +1 -1
- package/dist/development/src/resolvers/mutation/idp/UpdateIDPMutationResolver.cjs +2 -1
- package/dist/development/src/resolvers/mutation/idp/UpdateIDPMutationResolver.cjs.map +1 -1
- package/dist/development/src/resolvers/mutation/idp/UpdateIDPMutationResolver.js +2 -1
- package/dist/development/src/resolvers/mutation/idp/UpdateIDPMutationResolver.js.map +1 -1
- package/dist/development/src/resolvers/mutation/idp/audit.cjs +1 -0
- package/dist/development/src/resolvers/mutation/idp/audit.cjs.map +1 -1
- package/dist/development/src/resolvers/mutation/idp/audit.js +1 -0
- package/dist/development/src/resolvers/mutation/idp/audit.js.map +1 -1
- package/dist/development/src/resolvers/mutation/person/PasswordlessMutationResolver.cjs +4 -0
- package/dist/development/src/resolvers/mutation/person/PasswordlessMutationResolver.cjs.map +1 -1
- package/dist/development/src/resolvers/mutation/person/PasswordlessMutationResolver.js +4 -0
- package/dist/development/src/resolvers/mutation/person/PasswordlessMutationResolver.js.map +1 -1
- package/dist/development/src/resolvers/mutation/person/SignInMutationResolver.cjs +2 -2
- package/dist/development/src/resolvers/mutation/person/SignInMutationResolver.cjs.map +1 -1
- package/dist/development/src/resolvers/mutation/person/SignInMutationResolver.js +2 -2
- package/dist/development/src/resolvers/mutation/person/SignInMutationResolver.js.map +1 -1
- package/dist/development/src/schema/tenant.graphql.cjs +28 -0
- package/dist/development/src/schema/tenant.graphql.cjs.map +1 -1
- package/dist/development/src/schema/tenant.graphql.js +28 -0
- package/dist/development/src/schema/tenant.graphql.js.map +1 -1
- package/dist/production/index.cjs +2 -0
- package/dist/production/index.cjs.map +1 -1
- package/dist/production/index.js +2 -0
- package/dist/production/index.js.map +1 -1
- package/dist/production/src/migrations/2026-09-04-120000-idp-disable-local-authentication.cjs +18 -0
- package/dist/production/src/migrations/2026-09-04-120000-idp-disable-local-authentication.cjs.map +1 -0
- package/dist/production/src/migrations/2026-09-04-120000-idp-disable-local-authentication.js +19 -0
- package/dist/production/src/migrations/2026-09-04-120000-idp-disable-local-authentication.js.map +1 -0
- package/dist/production/src/migrations/runner.cjs +3 -1
- package/dist/production/src/migrations/runner.cjs.map +1 -1
- package/dist/production/src/migrations/runner.js +3 -1
- package/dist/production/src/migrations/runner.js.map +1 -1
- package/dist/production/src/migrations/snapshot.cjs +2 -1
- package/dist/production/src/migrations/snapshot.cjs.map +1 -1
- package/dist/production/src/migrations/snapshot.js +2 -1
- package/dist/production/src/migrations/snapshot.js.map +1 -1
- package/dist/production/src/model/commands/idp/CreateIdpCommand.cjs +8 -2
- package/dist/production/src/model/commands/idp/CreateIdpCommand.cjs.map +1 -1
- package/dist/production/src/model/commands/idp/CreateIdpCommand.js +8 -2
- package/dist/production/src/model/commands/idp/CreateIdpCommand.js.map +1 -1
- package/dist/production/src/model/commands/idp/UpdateIdpCommand.cjs +6 -2
- package/dist/production/src/model/commands/idp/UpdateIdpCommand.cjs.map +1 -1
- package/dist/production/src/model/commands/idp/UpdateIdpCommand.js +6 -2
- package/dist/production/src/model/commands/idp/UpdateIdpCommand.js.map +1 -1
- package/dist/production/src/model/queries/idp/LocalAuthDisablingIdpsQuery.cjs +21 -0
- package/dist/production/src/model/queries/idp/LocalAuthDisablingIdpsQuery.cjs.map +1 -0
- package/dist/production/src/model/queries/idp/LocalAuthDisablingIdpsQuery.js +21 -0
- package/dist/production/src/model/queries/idp/LocalAuthDisablingIdpsQuery.js.map +1 -0
- package/dist/production/src/model/queries/idp/PersonIdentityProvidersQuery.cjs +1 -1
- package/dist/production/src/model/queries/idp/PersonIdentityProvidersQuery.cjs.map +1 -1
- package/dist/production/src/model/queries/idp/PersonIdentityProvidersQuery.js +1 -1
- package/dist/production/src/model/queries/idp/PersonIdentityProvidersQuery.js.map +1 -1
- package/dist/production/src/model/queries/idp/helpers.cjs +3 -3
- package/dist/production/src/model/queries/idp/helpers.cjs.map +1 -1
- package/dist/production/src/model/queries/idp/helpers.js +3 -3
- package/dist/production/src/model/queries/idp/helpers.js.map +1 -1
- package/dist/production/src/model/service/PasswordResetManager.cjs +5 -0
- package/dist/production/src/model/service/PasswordResetManager.cjs.map +1 -1
- package/dist/production/src/model/service/PasswordResetManager.js +5 -0
- package/dist/production/src/model/service/PasswordResetManager.js.map +1 -1
- package/dist/production/src/model/service/PasswordlessSignInManager.cjs +19 -0
- package/dist/production/src/model/service/PasswordlessSignInManager.cjs.map +1 -1
- package/dist/production/src/model/service/PasswordlessSignInManager.js +19 -0
- package/dist/production/src/model/service/PasswordlessSignInManager.js.map +1 -1
- package/dist/production/src/model/service/SignInManager.cjs +5 -0
- package/dist/production/src/model/service/SignInManager.cjs.map +1 -1
- package/dist/production/src/model/service/SignInManager.js +5 -0
- package/dist/production/src/model/service/SignInManager.js.map +1 -1
- package/dist/production/src/model/service/idp/PersonIdentityProviderManager.cjs +6 -0
- package/dist/production/src/model/service/idp/PersonIdentityProviderManager.cjs.map +1 -1
- package/dist/production/src/model/service/idp/PersonIdentityProviderManager.js +6 -0
- package/dist/production/src/model/service/idp/PersonIdentityProviderManager.js.map +1 -1
- package/dist/production/src/resolvers/mutation/idp/AddIDPMutationResolver.cjs +2 -1
- package/dist/production/src/resolvers/mutation/idp/AddIDPMutationResolver.cjs.map +1 -1
- package/dist/production/src/resolvers/mutation/idp/AddIDPMutationResolver.js +2 -1
- package/dist/production/src/resolvers/mutation/idp/AddIDPMutationResolver.js.map +1 -1
- package/dist/production/src/resolvers/mutation/idp/UpdateIDPMutationResolver.cjs +2 -1
- package/dist/production/src/resolvers/mutation/idp/UpdateIDPMutationResolver.cjs.map +1 -1
- package/dist/production/src/resolvers/mutation/idp/UpdateIDPMutationResolver.js +2 -1
- package/dist/production/src/resolvers/mutation/idp/UpdateIDPMutationResolver.js.map +1 -1
- package/dist/production/src/resolvers/mutation/idp/audit.cjs +1 -0
- package/dist/production/src/resolvers/mutation/idp/audit.cjs.map +1 -1
- package/dist/production/src/resolvers/mutation/idp/audit.js +1 -0
- package/dist/production/src/resolvers/mutation/idp/audit.js.map +1 -1
- package/dist/production/src/resolvers/mutation/person/PasswordlessMutationResolver.cjs +4 -0
- package/dist/production/src/resolvers/mutation/person/PasswordlessMutationResolver.cjs.map +1 -1
- package/dist/production/src/resolvers/mutation/person/PasswordlessMutationResolver.js +4 -0
- package/dist/production/src/resolvers/mutation/person/PasswordlessMutationResolver.js.map +1 -1
- package/dist/production/src/resolvers/mutation/person/SignInMutationResolver.cjs +2 -2
- package/dist/production/src/resolvers/mutation/person/SignInMutationResolver.cjs.map +1 -1
- package/dist/production/src/resolvers/mutation/person/SignInMutationResolver.js +2 -2
- package/dist/production/src/resolvers/mutation/person/SignInMutationResolver.js.map +1 -1
- package/dist/production/src/schema/tenant.graphql.cjs +28 -0
- package/dist/production/src/schema/tenant.graphql.cjs.map +1 -1
- package/dist/production/src/schema/tenant.graphql.js +28 -0
- package/dist/production/src/schema/tenant.graphql.js.map +1 -1
- package/dist/tests/cases/integration/mocked/gql/passwordless.d.ts +3 -0
- package/dist/tests/cases/integration/mocked/gql/passwordless.d.ts.map +1 -1
- package/dist/tests/cases/integration/mocked/localAuthenticationDisabled.test.d.ts +2 -0
- package/dist/tests/cases/integration/mocked/localAuthenticationDisabled.test.d.ts.map +1 -0
- package/dist/tests/cases/integration/mocked/sql/localAuthDisablingIdpsSql.d.ts +10 -0
- package/dist/tests/cases/integration/mocked/sql/localAuthDisablingIdpsSql.d.ts.map +1 -0
- package/dist/tests/cases/integration/mocked/sql/personIdentityProvidersSql.d.ts +1 -0
- package/dist/tests/cases/integration/mocked/sql/personIdentityProvidersSql.d.ts.map +1 -1
- package/dist/tests/tsconfig.tsbuildinfo +1 -1
- package/dist/types/migrations/2026-09-04-120000-idp-disable-local-authentication.d.ts +3 -0
- package/dist/types/migrations/2026-09-04-120000-idp-disable-local-authentication.d.ts.map +1 -0
- package/dist/types/migrations/runner.d.ts.map +1 -1
- package/dist/types/migrations/snapshot.d.ts.map +1 -1
- package/dist/types/model/commands/idp/CreateIdpCommand.d.ts.map +1 -1
- package/dist/types/model/commands/idp/UpdateIdpCommand.d.ts.map +1 -1
- package/dist/types/model/queries/idp/LocalAuthDisablingIdpsQuery.d.ts +18 -0
- package/dist/types/model/queries/idp/LocalAuthDisablingIdpsQuery.d.ts.map +1 -0
- package/dist/types/model/queries/idp/PersonIdentityProvidersQuery.d.ts +1 -0
- package/dist/types/model/queries/idp/PersonIdentityProvidersQuery.d.ts.map +1 -1
- package/dist/types/model/queries/idp/helpers.d.ts +1 -1
- package/dist/types/model/queries/idp/helpers.d.ts.map +1 -1
- package/dist/types/model/queries/idp/index.d.ts +1 -0
- package/dist/types/model/queries/idp/index.d.ts.map +1 -1
- package/dist/types/model/queries/idp/types.d.ts +1 -0
- package/dist/types/model/queries/idp/types.d.ts.map +1 -1
- package/dist/types/model/service/PasswordResetManager.d.ts.map +1 -1
- package/dist/types/model/service/PasswordlessSignInManager.d.ts.map +1 -1
- package/dist/types/model/service/SignInManager.d.ts.map +1 -1
- package/dist/types/model/service/idp/PersonIdentityProviderManager.d.ts.map +1 -1
- package/dist/types/model/type/IdentityProvider.d.ts +2 -0
- package/dist/types/model/type/IdentityProvider.d.ts.map +1 -1
- package/dist/types/resolvers/mutation/idp/AddIDPMutationResolver.d.ts.map +1 -1
- package/dist/types/resolvers/mutation/idp/UpdateIDPMutationResolver.d.ts.map +1 -1
- package/dist/types/resolvers/mutation/idp/audit.d.ts.map +1 -1
- package/dist/types/resolvers/mutation/person/PasswordlessMutationResolver.d.ts.map +1 -1
- package/dist/types/schema/index.d.ts +33 -4
- package/dist/types/schema/index.d.ts.map +1 -1
- package/dist/types/schema/tenant.graphql.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +12 -12
- package/src/migrations/2026-09-04-120000-idp-disable-local-authentication.ts +18 -0
- package/src/migrations/runner.ts +2 -0
- package/src/migrations/snapshot.ts +2 -1
- package/src/model/commands/idp/CreateIdpCommand.ts +7 -1
- package/src/model/commands/idp/UpdateIdpCommand.ts +5 -1
- package/src/model/queries/idp/LocalAuthDisablingIdpsQuery.ts +41 -0
- package/src/model/queries/idp/PersonIdentityProvidersQuery.ts +2 -0
- package/src/model/queries/idp/helpers.ts +3 -2
- package/src/model/queries/idp/index.ts +1 -0
- package/src/model/queries/idp/types.ts +1 -0
- package/src/model/service/PasswordResetManager.ts +9 -1
- package/src/model/service/PasswordlessSignInManager.ts +23 -1
- package/src/model/service/SignInManager.ts +9 -1
- package/src/model/service/idp/PersonIdentityProviderManager.ts +8 -0
- package/src/model/type/IdentityProvider.ts +2 -0
- package/src/resolvers/mutation/idp/AddIDPMutationResolver.ts +1 -0
- package/src/resolvers/mutation/idp/UpdateIDPMutationResolver.ts +1 -0
- package/src/resolvers/mutation/idp/audit.ts +1 -0
- package/src/resolvers/mutation/person/PasswordlessMutationResolver.ts +7 -0
- package/src/resolvers/mutation/person/SignInMutationResolver.ts +2 -2
- package/src/schema/index.ts +29 -0
- package/src/schema/tenant.graphql.ts +28 -0
- package/tests/cases/integration/mocked/gql/passwordless.ts +10 -0
- package/tests/cases/integration/mocked/idpClaimMappingConfig.test.ts +4 -4
- package/tests/cases/integration/mocked/localAuthenticationDisabled.test.ts +182 -0
- package/tests/cases/integration/mocked/personIdentityProviders.test.ts +67 -0
- package/tests/cases/integration/mocked/signInAnomalyDetection.test.ts +11 -0
- package/tests/cases/integration/mocked/signInIdpClaimMapping.test.ts +14 -0
- package/tests/cases/integration/mocked/signInIdpClaimMappingFixes.test.ts +4 -0
- package/tests/cases/integration/mocked/signInIdpClaimMappingSafety.test.ts +6 -0
- package/tests/cases/integration/mocked/signInIdpMutation.test.ts +5 -0
- package/tests/cases/integration/mocked/signInMfaEnforcement.test.ts +7 -0
- package/tests/cases/integration/mocked/signInMutation.test.ts +14 -0
- package/tests/cases/integration/mocked/signInPasswordlessMutation.test.ts +36 -0
- package/tests/cases/integration/mocked/sql/getIdpBySlugSql.ts +1 -1
- package/tests/cases/integration/mocked/sql/localAuthDisablingIdpsSql.ts +21 -0
- package/tests/cases/integration/mocked/sql/personIdentityProvidersSql.ts +3 -1
- package/tests/cases/integration/mocked/trustForwardedClientInfo.test.ts +3 -0
- package/tests/cases/unit/idp/idpManagerUnknownConfigKeys.test.ts +8 -1
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { expect, test } from 'bun:test'
|
|
2
|
+
import { executeTenantTest } from '../../../src/testTenant.js'
|
|
3
|
+
import { testUuid } from '../../../src/testUuid.js'
|
|
4
|
+
import { signInMutation } from './gql/signIn.js'
|
|
5
|
+
import { initSignInPasswordlessMutation } from './gql/passwordless.js'
|
|
6
|
+
import { createResetPasswordRequestMutation } from './gql/createResetPasswordRequest.js'
|
|
7
|
+
import { getConfigSql } from './sql/getConfigSql.js'
|
|
8
|
+
import { getNextLoginAttemptSql } from './sql/getNextLoginAttemptSql.js'
|
|
9
|
+
import { getPersonByEmailSql } from './sql/getPersonByEmailSql.js'
|
|
10
|
+
import { localAuthDisablingIdpsSql } from './sql/localAuthDisablingIdpsSql.js'
|
|
11
|
+
import { createSessionKeySql } from './sql/createSessionKeySql.js'
|
|
12
|
+
import { getIdentityProjectsSql } from './sql/getIdentityProjectsSql.js'
|
|
13
|
+
import { getIdentityByIdSql } from './sql/getIdentityByIdSql.js'
|
|
14
|
+
import { getAuthPoliciesSql } from './sql/authPolicySql.js'
|
|
15
|
+
import { selectMembershipsSql } from './sql/selectMembershipsSql.js'
|
|
16
|
+
import { sqlTransaction } from './sql/sqlTransaction.js'
|
|
17
|
+
|
|
18
|
+
const email = 'john@doe.com'
|
|
19
|
+
const password = '123'
|
|
20
|
+
const personId = testUuid(7)
|
|
21
|
+
const identityId = testUuid(2)
|
|
22
|
+
|
|
23
|
+
test('signIn - a provider that disables local authentication refuses the password', async () => {
|
|
24
|
+
await executeTenantTest({
|
|
25
|
+
query: signInMutation({ email, password }),
|
|
26
|
+
executes: [
|
|
27
|
+
getConfigSql(),
|
|
28
|
+
getNextLoginAttemptSql(email),
|
|
29
|
+
getPersonByEmailSql({ email, response: { personId, identityId, password, roles: [] } }),
|
|
30
|
+
localAuthDisablingIdpsSql({ personId, slugs: ['corporateSso'] }),
|
|
31
|
+
],
|
|
32
|
+
return: {
|
|
33
|
+
data: {
|
|
34
|
+
signIn: {
|
|
35
|
+
ok: false,
|
|
36
|
+
errors: [{ code: 'IDP_REQUIRED' }],
|
|
37
|
+
result: null,
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
expectedAuthLog: {
|
|
42
|
+
type: 'login',
|
|
43
|
+
response: expect.objectContaining({ ok: false }),
|
|
44
|
+
},
|
|
45
|
+
})
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
test('signIn - the wrong password is not even consulted, so the answer cannot double as a password oracle', async () => {
|
|
49
|
+
await executeTenantTest({
|
|
50
|
+
query: signInMutation({ email, password: 'not-the-password' }),
|
|
51
|
+
executes: [
|
|
52
|
+
getConfigSql(),
|
|
53
|
+
getNextLoginAttemptSql(email),
|
|
54
|
+
getPersonByEmailSql({ email, response: { personId, identityId, password, roles: [] } }),
|
|
55
|
+
localAuthDisablingIdpsSql({ personId, slugs: ['corporateSso'] }),
|
|
56
|
+
],
|
|
57
|
+
return: {
|
|
58
|
+
data: {
|
|
59
|
+
signIn: {
|
|
60
|
+
ok: false,
|
|
61
|
+
errors: [{ code: 'IDP_REQUIRED' }],
|
|
62
|
+
result: null,
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
expectedAuthLog: {
|
|
67
|
+
type: 'login',
|
|
68
|
+
response: expect.objectContaining({ ok: false }),
|
|
69
|
+
},
|
|
70
|
+
})
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
test('signIn - revealLoginMethod off collapses it into INVALID_CREDENTIALS', async () => {
|
|
74
|
+
await executeTenantTest({
|
|
75
|
+
query: signInMutation({ email, password }),
|
|
76
|
+
executes: [
|
|
77
|
+
getConfigSql({ login_reveal_login_method: false }),
|
|
78
|
+
getNextLoginAttemptSql(email),
|
|
79
|
+
getPersonByEmailSql({ email, response: { personId, identityId, password, roles: [] } }),
|
|
80
|
+
localAuthDisablingIdpsSql({ personId, slugs: ['corporateSso'] }),
|
|
81
|
+
],
|
|
82
|
+
return: {
|
|
83
|
+
data: {
|
|
84
|
+
signIn: {
|
|
85
|
+
ok: false,
|
|
86
|
+
errors: [{ code: 'INVALID_CREDENTIALS' }],
|
|
87
|
+
result: null,
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
expectedAuthLog: {
|
|
92
|
+
type: 'login',
|
|
93
|
+
response: expect.objectContaining({ ok: false }),
|
|
94
|
+
},
|
|
95
|
+
})
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
test('signIn - a person with no such link signs in as before', async () => {
|
|
99
|
+
const projectId = testUuid(10)
|
|
100
|
+
const apiKeyId = testUuid(1)
|
|
101
|
+
await executeTenantTest({
|
|
102
|
+
query: signInMutation({ email, password }),
|
|
103
|
+
executes: [
|
|
104
|
+
getConfigSql(),
|
|
105
|
+
getNextLoginAttemptSql(email),
|
|
106
|
+
getPersonByEmailSql({ email, response: { personId, identityId, password, roles: [] } }),
|
|
107
|
+
// The asserted SQL carries `disabled_at IS NULL`, so a DISABLED provider never
|
|
108
|
+
// reaches this list — turning the provider off is the break-glass.
|
|
109
|
+
localAuthDisablingIdpsSql({ personId }),
|
|
110
|
+
getAuthPoliciesSql(),
|
|
111
|
+
getConfigSql(),
|
|
112
|
+
getIdentityByIdSql({ identityId }),
|
|
113
|
+
getAuthPoliciesSql(),
|
|
114
|
+
createSessionKeySql({ apiKeyId, identityId }),
|
|
115
|
+
getIdentityProjectsSql({ identityId, projectId }),
|
|
116
|
+
selectMembershipsSql({ identityId, projectId, membershipsResponse: [] }),
|
|
117
|
+
],
|
|
118
|
+
return: {
|
|
119
|
+
data: {
|
|
120
|
+
signIn: {
|
|
121
|
+
ok: true,
|
|
122
|
+
errors: [],
|
|
123
|
+
result: { token: '0000000000000000000000000000000000000000' },
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
expectedAuthLog: {
|
|
128
|
+
type: 'login',
|
|
129
|
+
response: expect.objectContaining({ ok: true }),
|
|
130
|
+
},
|
|
131
|
+
})
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
test('initSignInPasswordless - the magic link is refused as well, so no second door stays open', async () => {
|
|
135
|
+
await executeTenantTest({
|
|
136
|
+
query: initSignInPasswordlessMutation({ email }),
|
|
137
|
+
executes: [
|
|
138
|
+
getConfigSql({ passwordless_enabled: 'always' }),
|
|
139
|
+
...sqlTransaction(
|
|
140
|
+
getConfigSql({ passwordless_enabled: 'always' }),
|
|
141
|
+
getPersonByEmailSql({ email, response: { personId, identityId, password, roles: [] } }),
|
|
142
|
+
localAuthDisablingIdpsSql({ personId, slugs: ['corporateSso'] }),
|
|
143
|
+
),
|
|
144
|
+
],
|
|
145
|
+
return: {
|
|
146
|
+
data: {
|
|
147
|
+
initSignInPasswordless: {
|
|
148
|
+
ok: false,
|
|
149
|
+
error: { code: 'IDP_REQUIRED' },
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
expectedAuthLog: {
|
|
154
|
+
type: 'passwordless_login_init',
|
|
155
|
+
response: expect.objectContaining({ ok: false }),
|
|
156
|
+
},
|
|
157
|
+
})
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
test('createResetPasswordRequest - reports ok but sends nothing, because the password it would set is unusable', async () => {
|
|
161
|
+
await executeTenantTest({
|
|
162
|
+
query: createResetPasswordRequestMutation({ email }),
|
|
163
|
+
executes: [
|
|
164
|
+
getConfigSql(),
|
|
165
|
+
getPersonByEmailSql({ email, response: { personId, identityId, password, roles: [] } }),
|
|
166
|
+
localAuthDisablingIdpsSql({ personId, slugs: ['corporateSso'] }),
|
|
167
|
+
],
|
|
168
|
+
return: {
|
|
169
|
+
data: {
|
|
170
|
+
createResetPasswordRequest: {
|
|
171
|
+
ok: true,
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
expectedAuthLog: {
|
|
176
|
+
type: 'password_reset_init',
|
|
177
|
+
personInput: email,
|
|
178
|
+
personId,
|
|
179
|
+
response: expect.objectContaining({ ok: true }),
|
|
180
|
+
},
|
|
181
|
+
})
|
|
182
|
+
})
|
|
@@ -278,6 +278,73 @@ test('refuses to disconnect when the only other connection is to a disabled prov
|
|
|
278
278
|
})
|
|
279
279
|
})
|
|
280
280
|
|
|
281
|
+
test('refuses to disconnect a provider that disables local authentication, even though a password remains', async () => {
|
|
282
|
+
const personId = testUuid(1)
|
|
283
|
+
const connectionId = testUuid(2)
|
|
284
|
+
await executeTenantTest({
|
|
285
|
+
query: disconnectMyIdentityProviderMutation({ id: connectionId }),
|
|
286
|
+
executes: [
|
|
287
|
+
// the password would be usable again the moment the link is gone, which is exactly the escape to close
|
|
288
|
+
getPersonByIdentityForIdp({ identityId: authenticatedIdentityId, personId, passwordHash: 'BCRYPTED-123' }),
|
|
289
|
+
...sqlTransaction(
|
|
290
|
+
getPersonIdentityProvidersSql({
|
|
291
|
+
personId,
|
|
292
|
+
response: [
|
|
293
|
+
{ id: connectionId, createdAt, externalIdentifier: 'ext-1', slug: 'corporateSso', type: 'oidc', disableLocalAuthentication: true },
|
|
294
|
+
],
|
|
295
|
+
}),
|
|
296
|
+
),
|
|
297
|
+
],
|
|
298
|
+
return: {
|
|
299
|
+
data: {
|
|
300
|
+
disconnectMyIdentityProvider: {
|
|
301
|
+
ok: false,
|
|
302
|
+
error: {
|
|
303
|
+
code: 'IDP_REQUIRED',
|
|
304
|
+
},
|
|
305
|
+
},
|
|
306
|
+
},
|
|
307
|
+
},
|
|
308
|
+
})
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
test('still refuses while that provider is disabled: break-glass reopens local sign-in, not self-unlinking', async () => {
|
|
312
|
+
const personId = testUuid(1)
|
|
313
|
+
const connectionId = testUuid(2)
|
|
314
|
+
await executeTenantTest({
|
|
315
|
+
query: disconnectMyIdentityProviderMutation({ id: connectionId }),
|
|
316
|
+
executes: [
|
|
317
|
+
getPersonByIdentityForIdp({ identityId: authenticatedIdentityId, personId, passwordHash: 'BCRYPTED-123' }),
|
|
318
|
+
...sqlTransaction(
|
|
319
|
+
getPersonIdentityProvidersSql({
|
|
320
|
+
personId,
|
|
321
|
+
response: [
|
|
322
|
+
{
|
|
323
|
+
id: connectionId,
|
|
324
|
+
createdAt,
|
|
325
|
+
externalIdentifier: 'ext-1',
|
|
326
|
+
slug: 'corporateSso',
|
|
327
|
+
type: 'oidc',
|
|
328
|
+
disabledAt: new Date('2021-01-01T00:00:00.000Z'),
|
|
329
|
+
disableLocalAuthentication: true,
|
|
330
|
+
},
|
|
331
|
+
],
|
|
332
|
+
}),
|
|
333
|
+
),
|
|
334
|
+
],
|
|
335
|
+
return: {
|
|
336
|
+
data: {
|
|
337
|
+
disconnectMyIdentityProvider: {
|
|
338
|
+
ok: false,
|
|
339
|
+
error: {
|
|
340
|
+
code: 'IDP_REQUIRED',
|
|
341
|
+
},
|
|
342
|
+
},
|
|
343
|
+
},
|
|
344
|
+
},
|
|
345
|
+
})
|
|
346
|
+
})
|
|
347
|
+
|
|
281
348
|
test('returns NOT_FOUND when the person is not connected to the given connection', async () => {
|
|
282
349
|
const personId = testUuid(1)
|
|
283
350
|
await executeTenantTest({
|
|
@@ -3,6 +3,7 @@ import { testUuid } from '../../../src/testUuid.js'
|
|
|
3
3
|
import { selectMembershipsSql } from './sql/selectMembershipsSql.js'
|
|
4
4
|
import { signInMutation } from './gql/signIn.js'
|
|
5
5
|
import { getPersonByEmailSql } from './sql/getPersonByEmailSql.js'
|
|
6
|
+
import { localAuthDisablingIdpsSql } from './sql/localAuthDisablingIdpsSql.js'
|
|
6
7
|
import { expect, test } from 'bun:test'
|
|
7
8
|
import { OtpAuthenticator } from '../../../../src/index.js'
|
|
8
9
|
import { Buffer } from 'buffer'
|
|
@@ -51,6 +52,7 @@ test('anomaly off by default: a new country does not add any queries (baseline p
|
|
|
51
52
|
getConfigSql(),
|
|
52
53
|
getNextLoginAttemptSql(email),
|
|
53
54
|
getPersonByEmailSql({ email, response: { personId, identityId, password, roles: [] } }),
|
|
55
|
+
localAuthDisablingIdpsSql({ personId }),
|
|
54
56
|
getAuthPoliciesSql(),
|
|
55
57
|
getConfigSql(),
|
|
56
58
|
getIdentityByIdSql({ identityId }),
|
|
@@ -83,6 +85,7 @@ test('anomaly on, new device only (medium): sends UNUSUAL_LOGIN email, still sig
|
|
|
83
85
|
anomalyConfig,
|
|
84
86
|
getNextLoginAttemptSql(email),
|
|
85
87
|
getPersonByEmailSql({ email, response: { personId, identityId, password, roles: [] } }),
|
|
88
|
+
localAuthDisablingIdpsSql({ personId }),
|
|
86
89
|
// enforceMfaEnrollment (no policies → inert, single query)
|
|
87
90
|
getAuthPoliciesSql(),
|
|
88
91
|
// applyRiskPolicy: history (same country, different device → medium 2) then email
|
|
@@ -131,6 +134,7 @@ test('anomaly on, new country (high): requires step-up, dispatches email OTP, re
|
|
|
131
134
|
anomalyConfig,
|
|
132
135
|
getNextLoginAttemptSql(email),
|
|
133
136
|
getPersonByEmailSql({ email, response: { personId, identityId, password, roles: [] } }),
|
|
137
|
+
localAuthDisablingIdpsSql({ personId }),
|
|
134
138
|
// enforceMfaEnrollment (no policies → inert, single query)
|
|
135
139
|
getAuthPoliciesSql(),
|
|
136
140
|
// history: previously only ever seen in CZ → US is a new country (high, 3) → step-up.
|
|
@@ -185,6 +189,7 @@ test('anomaly on, step-up retry with the emailed code: consumes it and signs in
|
|
|
185
189
|
anomalyConfig,
|
|
186
190
|
getNextLoginAttemptSql(email),
|
|
187
191
|
getPersonByEmailSql({ email, response: { personId, identityId, password, roles: [] } }),
|
|
192
|
+
localAuthDisablingIdpsSql({ personId }),
|
|
188
193
|
// enforceMfaEnrollment (no policies → inert; otpToken is ignored here)
|
|
189
194
|
getAuthPoliciesSql(),
|
|
190
195
|
// previously only ever seen in CZ → US is a new country (high, 3) → step-up.
|
|
@@ -235,6 +240,7 @@ test('anomaly on, step-up retry with a wrong code: INVALID_OTP_TOKEN, no session
|
|
|
235
240
|
anomalyConfig,
|
|
236
241
|
getNextLoginAttemptSql(email),
|
|
237
242
|
getPersonByEmailSql({ email, response: { personId, identityId, password, roles: [] } }),
|
|
243
|
+
localAuthDisablingIdpsSql({ personId }),
|
|
238
244
|
getAuthPoliciesSql(),
|
|
239
245
|
getLoginHistorySql({ personId, rows: [{ geoCountry: 'CZ', deviceFingerprint: KNOWN_FINGERPRINT, ip: '10.0.0.5' }] }),
|
|
240
246
|
// wrong code: token is found and an attempt is claimed, but validation fails.
|
|
@@ -289,6 +295,7 @@ test('anomaly on, step-up scored for a user who already cleared TOTP this reques
|
|
|
289
295
|
anomalyConfig,
|
|
290
296
|
getNextLoginAttemptSql(email),
|
|
291
297
|
getPersonByEmailSql({ email, response: { personId, identityId, password, roles: [], otpUri: otp.uri } }),
|
|
298
|
+
localAuthDisablingIdpsSql({ personId }),
|
|
292
299
|
// active TOTP + valid token → verified here (no SQL); mfaSatisfiedThisRequest = true.
|
|
293
300
|
// applyRiskPolicy: US is a new country (high, 3 → stepUp) BUT MFA already satisfied
|
|
294
301
|
// this request → NO email-OTP challenge (no sendEmailOtpSql/getLatestEmailOtpTokenSql
|
|
@@ -343,6 +350,7 @@ test('anomaly on, new IP prefix only (low): the client IP flows into scoring →
|
|
|
343
350
|
recordLoginRateLimitSql(clientIp),
|
|
344
351
|
getNextLoginAttemptSql(email),
|
|
345
352
|
getPersonByEmailSql({ email, response: { personId, identityId, password, roles: [] } }),
|
|
353
|
+
localAuthDisablingIdpsSql({ personId }),
|
|
346
354
|
getAuthPoliciesSql(),
|
|
347
355
|
// history IP is 10.0.0.5 (/24 = 10.0.0.0) → current 203.0.113.9 (/24 = 203.0.113.0)
|
|
348
356
|
// is a new prefix; country + device match → score 1 → email.
|
|
@@ -388,6 +396,7 @@ test('anomaly on, custom historySize flows into the baseline query limit', async
|
|
|
388
396
|
anomalyConfig3,
|
|
389
397
|
getNextLoginAttemptSql(email),
|
|
390
398
|
getPersonByEmailSql({ email, response: { personId, identityId, password, roles: [] } }),
|
|
399
|
+
localAuthDisablingIdpsSql({ personId }),
|
|
391
400
|
getAuthPoliciesSql(),
|
|
392
401
|
// limit 3, not the default 10. Same country + device → score 0 → clean.
|
|
393
402
|
getLoginHistorySql({ personId, limit: 3, rows: [{ geoCountry: 'CZ', deviceFingerprint: KNOWN_FINGERPRINT, ip: '10.0.0.5' }] }),
|
|
@@ -422,6 +431,7 @@ test('anomaly on, historySize <= 0 is clamped to a limit of 1', async () => {
|
|
|
422
431
|
anomalyConfig0,
|
|
423
432
|
getNextLoginAttemptSql(email),
|
|
424
433
|
getPersonByEmailSql({ email, response: { personId, identityId, password, roles: [] } }),
|
|
434
|
+
localAuthDisablingIdpsSql({ personId }),
|
|
425
435
|
getAuthPoliciesSql(),
|
|
426
436
|
getLoginHistorySql({ personId, limit: 1, rows: [{ geoCountry: 'CZ', deviceFingerprint: KNOWN_FINGERPRINT, ip: '10.0.0.5' }] }),
|
|
427
437
|
anomalyConfig0,
|
|
@@ -452,6 +462,7 @@ test('anomaly on, all signals match history: no email, no step-up, signs in clea
|
|
|
452
462
|
anomalyConfig,
|
|
453
463
|
getNextLoginAttemptSql(email),
|
|
454
464
|
getPersonByEmailSql({ email, response: { personId, identityId, password, roles: [] } }),
|
|
465
|
+
localAuthDisablingIdpsSql({ personId }),
|
|
455
466
|
// enforceMfaEnrollment (no policies → inert, single query)
|
|
456
467
|
getAuthPoliciesSql(),
|
|
457
468
|
// same country, same device → score 0 → allow, no email.
|
|
@@ -56,6 +56,7 @@ test('claim mapping grants a project membership on sign-in', async () => {
|
|
|
56
56
|
initReturnsConfig: false,
|
|
57
57
|
requireVerifiedEmail: false,
|
|
58
58
|
assumeEmailVerified: false,
|
|
59
|
+
disableLocalAuthentication: false,
|
|
59
60
|
configuration: {
|
|
60
61
|
externalIdentifier,
|
|
61
62
|
email,
|
|
@@ -143,6 +144,7 @@ test('claim mapping grants a membership with a claim-derived variable (mapped va
|
|
|
143
144
|
initReturnsConfig: false,
|
|
144
145
|
requireVerifiedEmail: false,
|
|
145
146
|
assumeEmailVerified: false,
|
|
147
|
+
disableLocalAuthentication: false,
|
|
146
148
|
configuration: {
|
|
147
149
|
externalIdentifier,
|
|
148
150
|
email,
|
|
@@ -242,6 +244,7 @@ test('a granted membership whose declared variable resolves to no values keeps t
|
|
|
242
244
|
initReturnsConfig: false,
|
|
243
245
|
requireVerifiedEmail: false,
|
|
244
246
|
assumeEmailVerified: false,
|
|
247
|
+
disableLocalAuthentication: false,
|
|
245
248
|
configuration: {
|
|
246
249
|
externalIdentifier,
|
|
247
250
|
email,
|
|
@@ -332,6 +335,7 @@ test('sticky sync policy skips re-mapping for an existing person (no membership
|
|
|
332
335
|
initReturnsConfig: false,
|
|
333
336
|
requireVerifiedEmail: false,
|
|
334
337
|
assumeEmailVerified: false,
|
|
338
|
+
disableLocalAuthentication: false,
|
|
335
339
|
configuration: {
|
|
336
340
|
externalIdentifier,
|
|
337
341
|
email,
|
|
@@ -393,6 +397,7 @@ test('a malformed stored claimMapping is skipped (fail-open) — sign-in succeed
|
|
|
393
397
|
initReturnsConfig: false,
|
|
394
398
|
requireVerifiedEmail: false,
|
|
395
399
|
assumeEmailVerified: false,
|
|
400
|
+
disableLocalAuthentication: false,
|
|
396
401
|
configuration: {
|
|
397
402
|
externalIdentifier,
|
|
398
403
|
email,
|
|
@@ -456,6 +461,7 @@ test('a claim mapping referencing a non-existent project is skipped, sign-in sti
|
|
|
456
461
|
initReturnsConfig: false,
|
|
457
462
|
requireVerifiedEmail: false,
|
|
458
463
|
assumeEmailVerified: false,
|
|
464
|
+
disableLocalAuthentication: false,
|
|
459
465
|
configuration: {
|
|
460
466
|
externalIdentifier,
|
|
461
467
|
email,
|
|
@@ -521,6 +527,7 @@ test('unmatched: remove strips a no-longer-granted membership in a mapped projec
|
|
|
521
527
|
initReturnsConfig: false,
|
|
522
528
|
requireVerifiedEmail: false,
|
|
523
529
|
assumeEmailVerified: false,
|
|
530
|
+
disableLocalAuthentication: false,
|
|
524
531
|
configuration: {
|
|
525
532
|
externalIdentifier,
|
|
526
533
|
email,
|
|
@@ -623,6 +630,7 @@ test('unmatched: remove revokes the whole membership when a declared variable re
|
|
|
623
630
|
initReturnsConfig: false,
|
|
624
631
|
requireVerifiedEmail: false,
|
|
625
632
|
assumeEmailVerified: false,
|
|
633
|
+
disableLocalAuthentication: false,
|
|
626
634
|
configuration: {
|
|
627
635
|
externalIdentifier,
|
|
628
636
|
email,
|
|
@@ -737,6 +745,7 @@ test('sticky sync policy APPLIES on a brand-new auto-signed-up person', async ()
|
|
|
737
745
|
initReturnsConfig: false,
|
|
738
746
|
requireVerifiedEmail: false,
|
|
739
747
|
assumeEmailVerified: false,
|
|
748
|
+
disableLocalAuthentication: false,
|
|
740
749
|
configuration: {
|
|
741
750
|
externalIdentifier,
|
|
742
751
|
email,
|
|
@@ -822,6 +831,7 @@ test('unmatched: remove revokes a vocabulary membership even when no rule matche
|
|
|
822
831
|
initReturnsConfig: false,
|
|
823
832
|
requireVerifiedEmail: false,
|
|
824
833
|
assumeEmailVerified: false,
|
|
834
|
+
disableLocalAuthentication: false,
|
|
825
835
|
configuration: {
|
|
826
836
|
externalIdentifier,
|
|
827
837
|
email,
|
|
@@ -919,6 +929,7 @@ test('unmatched: remove never strips a membership outside the mapping vocabulary
|
|
|
919
929
|
initReturnsConfig: false,
|
|
920
930
|
requireVerifiedEmail: false,
|
|
921
931
|
assumeEmailVerified: false,
|
|
932
|
+
disableLocalAuthentication: false,
|
|
922
933
|
configuration: {
|
|
923
934
|
externalIdentifier,
|
|
924
935
|
email,
|
|
@@ -1023,6 +1034,7 @@ test('a passthrough claim-derived variable writes only allow-listed values (the
|
|
|
1023
1034
|
initReturnsConfig: false,
|
|
1024
1035
|
requireVerifiedEmail: false,
|
|
1025
1036
|
assumeEmailVerified: false,
|
|
1037
|
+
disableLocalAuthentication: false,
|
|
1026
1038
|
configuration: {
|
|
1027
1039
|
externalIdentifier,
|
|
1028
1040
|
email,
|
|
@@ -1121,6 +1133,7 @@ test('TEST-4: a claimMapping carrying only the OIDC identity-field remap (no rul
|
|
|
1121
1133
|
initReturnsConfig: false,
|
|
1122
1134
|
requireVerifiedEmail: false,
|
|
1123
1135
|
assumeEmailVerified: false,
|
|
1136
|
+
disableLocalAuthentication: false,
|
|
1124
1137
|
configuration: {
|
|
1125
1138
|
externalIdentifier,
|
|
1126
1139
|
email,
|
|
@@ -1177,6 +1190,7 @@ test('a passthrough claim-derived variable with allow:[] writes no variable, kee
|
|
|
1177
1190
|
initReturnsConfig: false,
|
|
1178
1191
|
requireVerifiedEmail: false,
|
|
1179
1192
|
assumeEmailVerified: false,
|
|
1193
|
+
disableLocalAuthentication: false,
|
|
1180
1194
|
configuration: {
|
|
1181
1195
|
externalIdentifier,
|
|
1182
1196
|
email,
|
|
@@ -60,6 +60,7 @@ test('SEC-1: a grant for a project that does not exist yet is KEPT but inert —
|
|
|
60
60
|
initReturnsConfig: false,
|
|
61
61
|
requireVerifiedEmail: false,
|
|
62
62
|
assumeEmailVerified: false,
|
|
63
|
+
disableLocalAuthentication: false,
|
|
63
64
|
configuration: { externalIdentifier, email, claims: { department: 'Editorial' }, claimMapping: { rules: [editorRule] } },
|
|
64
65
|
disabledAt: null,
|
|
65
66
|
slug: 'mock',
|
|
@@ -111,6 +112,7 @@ test('SEC-1: a grant for an EXISTING project whose schema is unresolvable is DRO
|
|
|
111
112
|
initReturnsConfig: false,
|
|
112
113
|
requireVerifiedEmail: false,
|
|
113
114
|
assumeEmailVerified: false,
|
|
115
|
+
disableLocalAuthentication: false,
|
|
114
116
|
configuration: { externalIdentifier, email, claims: { department: 'Editorial' }, claimMapping: { rules: [editorRule] } },
|
|
115
117
|
disabledAt: null,
|
|
116
118
|
slug: 'mock',
|
|
@@ -170,6 +172,7 @@ test('TEST-3: a partial drop applies the safe grant AND still emits the fail-ope
|
|
|
170
172
|
initReturnsConfig: false,
|
|
171
173
|
requireVerifiedEmail: false,
|
|
172
174
|
assumeEmailVerified: false,
|
|
175
|
+
disableLocalAuthentication: false,
|
|
173
176
|
configuration: {
|
|
174
177
|
externalIdentifier,
|
|
175
178
|
email,
|
|
@@ -264,6 +267,7 @@ test('TEST-4: an allow-bounded claim-derived `condition` variable writes ONLY th
|
|
|
264
267
|
initReturnsConfig: false,
|
|
265
268
|
requireVerifiedEmail: false,
|
|
266
269
|
assumeEmailVerified: false,
|
|
270
|
+
disableLocalAuthentication: false,
|
|
267
271
|
configuration: {
|
|
268
272
|
externalIdentifier,
|
|
269
273
|
email,
|
|
@@ -81,6 +81,7 @@ test('a DB error during the membership apply rolls back the whole sign-in (no se
|
|
|
81
81
|
initReturnsConfig: false,
|
|
82
82
|
requireVerifiedEmail: false,
|
|
83
83
|
assumeEmailVerified: false,
|
|
84
|
+
disableLocalAuthentication: false,
|
|
84
85
|
configuration: {
|
|
85
86
|
externalIdentifier,
|
|
86
87
|
email,
|
|
@@ -151,6 +152,7 @@ test('sticky leaves an existing local account being LINKED to the IdP untouched
|
|
|
151
152
|
initReturnsConfig: false,
|
|
152
153
|
requireVerifiedEmail: false,
|
|
153
154
|
assumeEmailVerified: false,
|
|
155
|
+
disableLocalAuthentication: false,
|
|
154
156
|
configuration: {
|
|
155
157
|
externalIdentifier,
|
|
156
158
|
email,
|
|
@@ -219,6 +221,7 @@ test('apply-time guard drops a grant that would inject a claim into a condition
|
|
|
219
221
|
initReturnsConfig: false,
|
|
220
222
|
requireVerifiedEmail: false,
|
|
221
223
|
assumeEmailVerified: false,
|
|
224
|
+
disableLocalAuthentication: false,
|
|
222
225
|
configuration: {
|
|
223
226
|
externalIdentifier,
|
|
224
227
|
email,
|
|
@@ -299,6 +302,7 @@ test('TEST-1: under unmatched:remove, a rule dropped by the apply-time guard doe
|
|
|
299
302
|
initReturnsConfig: false,
|
|
300
303
|
requireVerifiedEmail: false,
|
|
301
304
|
assumeEmailVerified: false,
|
|
305
|
+
disableLocalAuthentication: false,
|
|
302
306
|
configuration: {
|
|
303
307
|
externalIdentifier,
|
|
304
308
|
email,
|
|
@@ -382,6 +386,7 @@ test('SEC: apply-time guard drops an `admin` grant whose claim-derived variable
|
|
|
382
386
|
initReturnsConfig: false,
|
|
383
387
|
requireVerifiedEmail: false,
|
|
384
388
|
assumeEmailVerified: false,
|
|
389
|
+
disableLocalAuthentication: false,
|
|
385
390
|
configuration: {
|
|
386
391
|
externalIdentifier,
|
|
387
392
|
email,
|
|
@@ -465,6 +470,7 @@ test('SEC: a legitimate `admin` grant (role has no required variable) is still a
|
|
|
465
470
|
initReturnsConfig: false,
|
|
466
471
|
requireVerifiedEmail: false,
|
|
467
472
|
assumeEmailVerified: false,
|
|
473
|
+
disableLocalAuthentication: false,
|
|
468
474
|
configuration: {
|
|
469
475
|
externalIdentifier,
|
|
470
476
|
email,
|
|
@@ -49,6 +49,7 @@ test('signs in idp with existing identity', async () => {
|
|
|
49
49
|
initReturnsConfig: false,
|
|
50
50
|
requireVerifiedEmail: false,
|
|
51
51
|
assumeEmailVerified: false,
|
|
52
|
+
disableLocalAuthentication: false,
|
|
52
53
|
slug: 'mock',
|
|
53
54
|
type: 'mock',
|
|
54
55
|
},
|
|
@@ -125,6 +126,7 @@ test('does NOT link by e-mail when provider requires a verified e-mail and the c
|
|
|
125
126
|
initReturnsConfig: false,
|
|
126
127
|
requireVerifiedEmail: true,
|
|
127
128
|
assumeEmailVerified: false,
|
|
129
|
+
disableLocalAuthentication: false,
|
|
128
130
|
configuration: {
|
|
129
131
|
externalIdentifier,
|
|
130
132
|
email,
|
|
@@ -204,6 +206,7 @@ test('links by e-mail when provider requires a verified e-mail and the claim is
|
|
|
204
206
|
initReturnsConfig: false,
|
|
205
207
|
requireVerifiedEmail: true,
|
|
206
208
|
assumeEmailVerified: false,
|
|
209
|
+
disableLocalAuthentication: false,
|
|
207
210
|
configuration: {
|
|
208
211
|
externalIdentifier,
|
|
209
212
|
email,
|
|
@@ -291,6 +294,7 @@ test('links by e-mail when require + assumeEmailVerified are set, even though th
|
|
|
291
294
|
requireVerifiedEmail: true,
|
|
292
295
|
// trusted IdP: e-mails are treated as verified even though the claim says false
|
|
293
296
|
assumeEmailVerified: true,
|
|
297
|
+
disableLocalAuthentication: false,
|
|
294
298
|
configuration: {
|
|
295
299
|
externalIdentifier,
|
|
296
300
|
email,
|
|
@@ -378,6 +382,7 @@ test('signs in exclusive idp', async () => {
|
|
|
378
382
|
initReturnsConfig: false,
|
|
379
383
|
requireVerifiedEmail: false,
|
|
380
384
|
assumeEmailVerified: false,
|
|
385
|
+
disableLocalAuthentication: false,
|
|
381
386
|
configuration: {
|
|
382
387
|
externalIdentifier: externalIdentifier,
|
|
383
388
|
email,
|
|
@@ -3,6 +3,7 @@ import { SQL } from '../../../src/tags.js'
|
|
|
3
3
|
import { testUuid } from '../../../src/testUuid.js'
|
|
4
4
|
import { signInMutation } from './gql/signIn.js'
|
|
5
5
|
import { getPersonByEmailSql } from './sql/getPersonByEmailSql.js'
|
|
6
|
+
import { localAuthDisablingIdpsSql } from './sql/localAuthDisablingIdpsSql.js'
|
|
6
7
|
import { getConfigSql } from './sql/getConfigSql.js'
|
|
7
8
|
import { getNextLoginAttemptSql } from './sql/getNextLoginAttemptSql.js'
|
|
8
9
|
import { createSessionKeySql } from './sql/createSessionKeySql.js'
|
|
@@ -48,6 +49,7 @@ test('MFA required + no factor + no code → MFA_ENROLLMENT_REQUIRED with pendin
|
|
|
48
49
|
getConfigSql(),
|
|
49
50
|
getNextLoginAttemptSql(email),
|
|
50
51
|
getPersonByEmailSql({ email, response: { personId, identityId, password, roles: ['editor'] } }),
|
|
52
|
+
localAuthDisablingIdpsSql({ personId }),
|
|
51
53
|
requiringPolicy(),
|
|
52
54
|
getAllProjectRolesByIdentitySql({ identityId }),
|
|
53
55
|
// Enforcement resolves the grace duration: the matched policy has no
|
|
@@ -113,6 +115,7 @@ test('MFA required + valid pending code → enrolls (pending→active), returns
|
|
|
113
115
|
getConfigSql(),
|
|
114
116
|
getNextLoginAttemptSql(email),
|
|
115
117
|
getPersonByEmailSql({ email, response: { personId, identityId, password, roles: ['editor'], otpPendingUri: otp.uri } }),
|
|
118
|
+
localAuthDisablingIdpsSql({ personId }),
|
|
116
119
|
requiringPolicy(),
|
|
117
120
|
getAllProjectRolesByIdentitySql({ identityId }),
|
|
118
121
|
// Enforcement resolves the grace duration from the global config default.
|
|
@@ -173,6 +176,7 @@ test('MFA required + invalid pending code → INVALID_OTP_TOKEN', async () => {
|
|
|
173
176
|
getConfigSql(),
|
|
174
177
|
getNextLoginAttemptSql(email),
|
|
175
178
|
getPersonByEmailSql({ email, response: { personId, identityId, password, roles: ['editor'], otpPendingUri: pendingUri } }),
|
|
179
|
+
localAuthDisablingIdpsSql({ personId }),
|
|
176
180
|
requiringPolicy(),
|
|
177
181
|
getAllProjectRolesByIdentitySql({ identityId }),
|
|
178
182
|
// Enforcement resolves the grace duration from the global config default.
|
|
@@ -206,6 +210,7 @@ test('active TOTP + requiring policy → existing OTP_REQUIRED path (policy not
|
|
|
206
210
|
getConfigSql(),
|
|
207
211
|
getNextLoginAttemptSql(email),
|
|
208
212
|
getPersonByEmailSql({ email, response: { personId, identityId, password, roles: ['editor'], otpUri } }),
|
|
213
|
+
localAuthDisablingIdpsSql({ personId }),
|
|
209
214
|
],
|
|
210
215
|
return: {
|
|
211
216
|
data: {
|
|
@@ -241,6 +246,7 @@ test('MFA required + per-policy grace → opens grace window, sets mfa_grace_unt
|
|
|
241
246
|
getConfigSql(),
|
|
242
247
|
getNextLoginAttemptSql(email),
|
|
243
248
|
getPersonByEmailSql({ email, response: { personId, identityId, password, roles: ['editor'] } }),
|
|
249
|
+
localAuthDisablingIdpsSql({ personId }),
|
|
244
250
|
gracePolicy(),
|
|
245
251
|
getAllProjectRolesByIdentitySql({ identityId }),
|
|
246
252
|
// Per-policy grace override is present → the global config is NOT read here.
|
|
@@ -288,6 +294,7 @@ test('MFA required + no policy grace override → global config default opens th
|
|
|
288
294
|
getConfigSql(),
|
|
289
295
|
getNextLoginAttemptSql(email),
|
|
290
296
|
getPersonByEmailSql({ email, response: { personId, identityId, password, roles: ['editor'] } }),
|
|
297
|
+
localAuthDisablingIdpsSql({ personId }),
|
|
291
298
|
requiringPolicy(),
|
|
292
299
|
getAllProjectRolesByIdentitySql({ identityId }),
|
|
293
300
|
// No per-policy override → the global config default is read; here it is
|