@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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contember/engine-tenant-api",
|
|
3
|
-
"version": "2.2.0-alpha.
|
|
3
|
+
"version": "2.2.0-alpha.6",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"main": "./dist/production/index.js",
|
|
6
6
|
"typings": "./dist/types/index.d.ts",
|
|
@@ -26,16 +26,16 @@
|
|
|
26
26
|
"generate": "gql-gen --config graphql.codegen.yml"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@contember/authorization": "2.2.0-alpha.
|
|
30
|
-
"@contember/config-loader": "2.2.0-alpha.
|
|
31
|
-
"@contember/database": "2.2.0-alpha.
|
|
32
|
-
"@contember/database-migrations": "2.2.0-alpha.
|
|
33
|
-
"@contember/dic": "2.2.0-alpha.
|
|
34
|
-
"@contember/graphql-utils": "2.2.0-alpha.
|
|
35
|
-
"@contember/logger": "2.2.0-alpha.
|
|
36
|
-
"@contember/schema": "2.2.0-alpha.
|
|
37
|
-
"@contember/schema-utils": "2.2.0-alpha.
|
|
38
|
-
"@contember/typesafe": "2.2.0-alpha.
|
|
29
|
+
"@contember/authorization": "2.2.0-alpha.6",
|
|
30
|
+
"@contember/config-loader": "2.2.0-alpha.6",
|
|
31
|
+
"@contember/database": "2.2.0-alpha.6",
|
|
32
|
+
"@contember/database-migrations": "2.2.0-alpha.6",
|
|
33
|
+
"@contember/dic": "2.2.0-alpha.6",
|
|
34
|
+
"@contember/graphql-utils": "2.2.0-alpha.6",
|
|
35
|
+
"@contember/logger": "2.2.0-alpha.6",
|
|
36
|
+
"@contember/schema": "2.2.0-alpha.6",
|
|
37
|
+
"@contember/schema-utils": "2.2.0-alpha.6",
|
|
38
|
+
"@contember/typesafe": "2.2.0-alpha.6",
|
|
39
39
|
"@graphql-tools/utils": "^11.0.0",
|
|
40
40
|
"graphql-tag": "^2.12.6",
|
|
41
41
|
"jose": "^6.2.1",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"otpauth": "^9.5.0"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
|
-
"@contember/database-tester": "2.2.0-alpha.
|
|
49
|
+
"@contember/database-tester": "2.2.0-alpha.6",
|
|
50
50
|
"@graphql-tools/schema": "^10.0.31",
|
|
51
51
|
"@types/mustache": "^4.2.6",
|
|
52
52
|
"@types/node": "^24.12.0",
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { MigrationBuilder } from '@contember/database-migrations'
|
|
2
|
+
|
|
3
|
+
const sql = `
|
|
4
|
+
-- When TRUE, a person linked to this identity provider may authenticate ONLY through
|
|
5
|
+
-- it: password sign-in, passwordless sign-in and password-reset mails are refused for
|
|
6
|
+
-- them. The link itself is the marker, so it also covers a person who predates the
|
|
7
|
+
-- provider and got linked by e-mail matching.
|
|
8
|
+
--
|
|
9
|
+
-- Scoped to providers that are currently enabled: disabling the provider restores
|
|
10
|
+
-- local authentication for everyone linked to it, which is the break-glass for an IdP
|
|
11
|
+
-- outage. Defaults to FALSE to preserve existing behavior.
|
|
12
|
+
ALTER TABLE "identity_provider"
|
|
13
|
+
ADD COLUMN "disable_local_authentication" BOOLEAN NOT NULL DEFAULT FALSE;
|
|
14
|
+
`
|
|
15
|
+
|
|
16
|
+
export default async function(builder: MigrationBuilder) {
|
|
17
|
+
builder.sql(sql)
|
|
18
|
+
}
|
package/src/migrations/runner.ts
CHANGED
|
@@ -65,6 +65,7 @@ import _20260616130000idplogout from './2026-06-16-130000-idp-logout.js'
|
|
|
65
65
|
import _20260618120000idpclaimmapping from './2026-06-18-120000-idp-claim-mapping.js'
|
|
66
66
|
import _20260803120000personenableauthlog from './2026-08-03-120000-person-enable-auth-log.js'
|
|
67
67
|
import _20260804120000configpanel from './2026-08-04-120000-config-panel.js'
|
|
68
|
+
import _20260904120000idpdisablelocalauthentication from './2026-09-04-120000-idp-disable-local-authentication.js'
|
|
68
69
|
import snapshot from './snapshot.js'
|
|
69
70
|
import { computeTokenHash, Providers } from '../model/index.js'
|
|
70
71
|
import { Logger } from '@contember/logger'
|
|
@@ -153,6 +154,7 @@ const migrations = {
|
|
|
153
154
|
'2026-06-18-120000-idp-claim-mapping': _20260618120000idpclaimmapping,
|
|
154
155
|
'2026-08-03-120000-person-enable-auth-log': _20260803120000personenableauthlog,
|
|
155
156
|
'2026-08-04-120000-config-panel': _20260804120000configpanel,
|
|
157
|
+
'2026-09-04-120000-idp-disable-local-authentication': _20260904120000idpdisablelocalauthentication,
|
|
156
158
|
}
|
|
157
159
|
|
|
158
160
|
export class TenantMigrationsRunner {
|
|
@@ -204,7 +204,8 @@ CREATE TABLE "identity_provider" (
|
|
|
204
204
|
"exclusive" boolean DEFAULT false,
|
|
205
205
|
"init_returns_config" boolean DEFAULT false NOT NULL,
|
|
206
206
|
"require_verified_email" boolean DEFAULT false NOT NULL,
|
|
207
|
-
"assume_email_verified" boolean DEFAULT false NOT NULL
|
|
207
|
+
"assume_email_verified" boolean DEFAULT false NOT NULL,
|
|
208
|
+
"disable_local_authentication" boolean DEFAULT false NOT NULL
|
|
208
209
|
);
|
|
209
210
|
CREATE TABLE "idp_session" (
|
|
210
211
|
"id" "uuid" NOT NULL,
|
|
@@ -9,7 +9,12 @@ export class CreateIdpCommand implements Command<void> {
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
async execute({ db, providers }: Command.Args): Promise<void> {
|
|
12
|
-
const {
|
|
12
|
+
const {
|
|
13
|
+
configuration,
|
|
14
|
+
options: { autoSignUp, exclusive, initReturnsConfig, requireVerifiedEmail, assumeEmailVerified, disableLocalAuthentication },
|
|
15
|
+
slug,
|
|
16
|
+
type,
|
|
17
|
+
} = this.data
|
|
13
18
|
await InsertBuilder.create()
|
|
14
19
|
.into('identity_provider')
|
|
15
20
|
.values({
|
|
@@ -22,6 +27,7 @@ export class CreateIdpCommand implements Command<void> {
|
|
|
22
27
|
init_returns_config: initReturnsConfig,
|
|
23
28
|
require_verified_email: requireVerifiedEmail,
|
|
24
29
|
assume_email_verified: assumeEmailVerified,
|
|
30
|
+
disable_local_authentication: disableLocalAuthentication,
|
|
25
31
|
})
|
|
26
32
|
.execute(db)
|
|
27
33
|
}
|
|
@@ -10,7 +10,10 @@ export class UpdateIdpCommand implements Command<void> {
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
async execute({ db }: Command.Args): Promise<void> {
|
|
13
|
-
const {
|
|
13
|
+
const {
|
|
14
|
+
configuration,
|
|
15
|
+
options: { autoSignUp, exclusive, initReturnsConfig, requireVerifiedEmail, assumeEmailVerified, disableLocalAuthentication } = {},
|
|
16
|
+
} = this.data
|
|
14
17
|
await UpdateBuilder.create()
|
|
15
18
|
.table('identity_provider')
|
|
16
19
|
.values({
|
|
@@ -20,6 +23,7 @@ export class UpdateIdpCommand implements Command<void> {
|
|
|
20
23
|
init_returns_config: initReturnsConfig,
|
|
21
24
|
require_verified_email: requireVerifiedEmail,
|
|
22
25
|
assume_email_verified: assumeEmailVerified,
|
|
26
|
+
disable_local_authentication: disableLocalAuthentication,
|
|
23
27
|
})
|
|
24
28
|
.where({
|
|
25
29
|
id: this.id,
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { DatabaseQuery, DatabaseQueryable, Operator, SelectBuilder } from '@contember/database'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Slugs of the identity providers that forbid this person every local
|
|
5
|
+
* authentication method — password sign-in, passwordless sign-in and the
|
|
6
|
+
* password-reset mail. Empty means local authentication is available.
|
|
7
|
+
*
|
|
8
|
+
* The IdP link is the marker, so this also covers a person who predates the
|
|
9
|
+
* provider and was linked to it by e-mail matching, not only one created by
|
|
10
|
+
* auto-sign-up. A DISABLED provider does not count: turning the provider off
|
|
11
|
+
* hands local authentication back to everyone linked to it, which is the
|
|
12
|
+
* break-glass when the IdP is unreachable.
|
|
13
|
+
*/
|
|
14
|
+
export class LocalAuthDisablingIdpsQuery extends DatabaseQuery<string[]> {
|
|
15
|
+
constructor(
|
|
16
|
+
private readonly personId: string,
|
|
17
|
+
) {
|
|
18
|
+
super()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async fetch({ db }: DatabaseQueryable): Promise<string[]> {
|
|
22
|
+
const rows = await SelectBuilder.create<{ slug: string }>()
|
|
23
|
+
.select(['identity_provider', 'slug'], 'slug')
|
|
24
|
+
.from('person_identity_provider')
|
|
25
|
+
.join(
|
|
26
|
+
'identity_provider',
|
|
27
|
+
'identity_provider',
|
|
28
|
+
expr => expr.columnsEq(['identity_provider', 'id'], ['person_identity_provider', 'identity_provider_id']),
|
|
29
|
+
)
|
|
30
|
+
.where(where =>
|
|
31
|
+
where
|
|
32
|
+
.compare(['person_identity_provider', 'person_id'], Operator.eq, this.personId)
|
|
33
|
+
.compare(['identity_provider', 'disable_local_authentication'], Operator.eq, true)
|
|
34
|
+
.isNull(['identity_provider', 'disabled_at'])
|
|
35
|
+
)
|
|
36
|
+
.orderBy(['identity_provider', 'slug'])
|
|
37
|
+
.getResult(db)
|
|
38
|
+
|
|
39
|
+
return rows.map(it => it.slug)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -7,6 +7,7 @@ export interface PersonIdentityProviderRow {
|
|
|
7
7
|
readonly identityProviderSlug: string
|
|
8
8
|
readonly identityProviderType: string
|
|
9
9
|
readonly identityProviderDisabledAt: Date | null
|
|
10
|
+
readonly identityProviderDisableLocalAuthentication: boolean
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
/**
|
|
@@ -29,6 +30,7 @@ export class PersonIdentityProvidersQuery extends DatabaseQuery<PersonIdentityPr
|
|
|
29
30
|
.select(['identity_provider', 'slug'], 'identityProviderSlug')
|
|
30
31
|
.select(['identity_provider', 'type'], 'identityProviderType')
|
|
31
32
|
.select(['identity_provider', 'disabled_at'], 'identityProviderDisabledAt')
|
|
33
|
+
.select(['identity_provider', 'disable_local_authentication'], 'identityProviderDisableLocalAuthentication')
|
|
32
34
|
.from('person_identity_provider')
|
|
33
35
|
.join(
|
|
34
36
|
'identity_provider',
|
|
@@ -13,13 +13,14 @@ export const createBaseIdpQuery = () =>
|
|
|
13
13
|
.select('init_returns_config', 'initReturnsConfig')
|
|
14
14
|
.select('require_verified_email', 'requireVerifiedEmail')
|
|
15
15
|
.select('assume_email_verified', 'assumeEmailVerified')
|
|
16
|
+
.select('disable_local_authentication', 'disableLocalAuthentication')
|
|
16
17
|
.from('identity_provider')
|
|
17
18
|
|
|
18
19
|
export const createIdpDto = (
|
|
19
|
-
{ exclusive, autoSignUp, initReturnsConfig, requireVerifiedEmail, assumeEmailVerified, ...row }: IdentityProviderRow,
|
|
20
|
+
{ exclusive, autoSignUp, initReturnsConfig, requireVerifiedEmail, assumeEmailVerified, disableLocalAuthentication, ...row }: IdentityProviderRow,
|
|
20
21
|
): IdentityProviderDto => {
|
|
21
22
|
return {
|
|
22
23
|
...row,
|
|
23
|
-
options: { autoSignUp, exclusive, initReturnsConfig, requireVerifiedEmail, assumeEmailVerified },
|
|
24
|
+
options: { autoSignUp, exclusive, initReturnsConfig, requireVerifiedEmail, assumeEmailVerified, disableLocalAuthentication },
|
|
24
25
|
}
|
|
25
26
|
}
|
|
@@ -3,4 +3,5 @@ export * from './IdentityProvidersQuery.js'
|
|
|
3
3
|
export * from './IdpSessionByApiKeyQuery.js'
|
|
4
4
|
export * from './IdpSessionsBySidQuery.js'
|
|
5
5
|
export * from './IdpSessionsBySubQuery.js'
|
|
6
|
+
export * from './LocalAuthDisablingIdpsQuery.js'
|
|
6
7
|
export * from './PersonIdentityProvidersQuery.js'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Response, ResponseError, ResponseOk } from '../utils/Response.js'
|
|
2
2
|
import { UserMailer } from '../mailing/index.js'
|
|
3
|
-
import { ConfigurationQuery, PersonRow } from '../queries/index.js'
|
|
3
|
+
import { ConfigurationQuery, LocalAuthDisablingIdpsQuery, PersonRow } from '../queries/index.js'
|
|
4
4
|
import { PermissionContext } from '../authorization/index.js'
|
|
5
5
|
import { ProjectManager } from './ProjectManager.js'
|
|
6
6
|
import { DatabaseContext } from '../utils/index.js'
|
|
@@ -35,6 +35,14 @@ export class PasswordResetManager {
|
|
|
35
35
|
throw new ImplementationException()
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
// A person federated through a provider with `disableLocalAuthentication`
|
|
39
|
+
// cannot sign in with a password, so a reset mail would only offer them a
|
|
40
|
+
// credential the tenant refuses — and one more link worth phishing.
|
|
41
|
+
const exclusiveIdps = await dbContext.queryHandler.fetch(new LocalAuthDisablingIdpsQuery(person.id))
|
|
42
|
+
if (exclusiveIdps.length > 0) {
|
|
43
|
+
return
|
|
44
|
+
}
|
|
45
|
+
|
|
38
46
|
// Per-email exponential backoff on outbound mails — don't spam a real
|
|
39
47
|
// user with reset mails just because attackers keep poking the
|
|
40
48
|
// endpoint. Reuses login_* backoff config against person_auth_log.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ApiKeyManager } from './apiKey/index.js'
|
|
2
|
-
import { ConfigurationQuery, PersonQuery, PersonRow } from '../queries/index.js'
|
|
2
|
+
import { ConfigurationQuery, LocalAuthDisablingIdpsQuery, PersonQuery, PersonRow } from '../queries/index.js'
|
|
3
3
|
import { Response, ResponseError, ResponseOk } from '../utils/Response.js'
|
|
4
4
|
import {
|
|
5
5
|
ActivatePasswordlessOtpErrorCode,
|
|
@@ -62,6 +62,16 @@ class PasswordlessSignInManager {
|
|
|
62
62
|
}),
|
|
63
63
|
})
|
|
64
64
|
}
|
|
65
|
+
const exclusiveIdps = await db.queryHandler.fetch(new LocalAuthDisablingIdpsQuery(person.id))
|
|
66
|
+
if (exclusiveIdps.length > 0) {
|
|
67
|
+
return new ResponseError('IDP_REQUIRED', `Sign in using ${exclusiveIdps.join(' or ')}`, {
|
|
68
|
+
[AuthLogService.Key]: new AuthLogService.Bag({
|
|
69
|
+
personInput: email,
|
|
70
|
+
personId: person.id,
|
|
71
|
+
}),
|
|
72
|
+
})
|
|
73
|
+
}
|
|
74
|
+
|
|
65
75
|
if (!isPasswordlessEnabled(configuration.passwordless.enabled, person.passwordless_enabled)) {
|
|
66
76
|
return new ResponseError('PASSWORDLESS_DISABLED', 'Passwordless sign-in is disabled for this person', {
|
|
67
77
|
[AuthLogService.Key]: new AuthLogService.Bag({
|
|
@@ -185,6 +195,18 @@ class PasswordlessSignInManager {
|
|
|
185
195
|
})
|
|
186
196
|
}
|
|
187
197
|
|
|
198
|
+
// Re-checked here and not only at init: a link created while this magic link
|
|
199
|
+
// was in flight would otherwise still mint a locally authenticated session.
|
|
200
|
+
const exclusiveIdps = await db.queryHandler.fetch(new LocalAuthDisablingIdpsQuery(personRow.id))
|
|
201
|
+
if (exclusiveIdps.length > 0) {
|
|
202
|
+
return new ResponseError('IDP_REQUIRED', `Sign in using ${exclusiveIdps.join(' or ')}`, {
|
|
203
|
+
[AuthLogService.Key]: new AuthLogService.Bag({
|
|
204
|
+
personId: personRow.id,
|
|
205
|
+
tokenId: tokenResult?.id,
|
|
206
|
+
}),
|
|
207
|
+
})
|
|
208
|
+
}
|
|
209
|
+
|
|
188
210
|
let usedBackupCode = false
|
|
189
211
|
if (personRow.otp_secret && personRow.otp_activated_at) {
|
|
190
212
|
if (mfaOtp) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CreateSessionTokenErrorCode, MfaEnrollment, SignInErrorCode } from '../../schema/index.js'
|
|
2
2
|
import { ApiKeyManager, AuthPolicyResolver, BackupCodeManager, EmailOtpManager, LoginRiskAnalyzer, OtpManager } from '../service/index.js'
|
|
3
|
-
import { ConfigurationQuery, PersonQuery, PersonRow, PersonUniqueIdentifier } from '../queries/index.js'
|
|
3
|
+
import { ConfigurationQuery, LocalAuthDisablingIdpsQuery, PersonQuery, PersonRow, PersonUniqueIdentifier } from '../queries/index.js'
|
|
4
4
|
import { Config } from '../type/Config.js'
|
|
5
5
|
import { Providers } from '../providers.js'
|
|
6
6
|
import { DatabaseContext } from '../utils/index.js'
|
|
@@ -51,6 +51,14 @@ class SignInManager {
|
|
|
51
51
|
personId: person.id,
|
|
52
52
|
}),
|
|
53
53
|
}
|
|
54
|
+
// Refused before the password is even looked at: for a person federated
|
|
55
|
+
// through a provider with `disableLocalAuthentication`, a password is not a
|
|
56
|
+
// credential this tenant accepts, however it got set.
|
|
57
|
+
const exclusiveIdps = await dbContext.queryHandler.fetch(new LocalAuthDisablingIdpsQuery(person.id))
|
|
58
|
+
if (exclusiveIdps.length > 0) {
|
|
59
|
+
return new ResponseError('IDP_REQUIRED', `Sign in using ${exclusiveIdps.join(' or ')}`, authLogData)
|
|
60
|
+
}
|
|
61
|
+
|
|
54
62
|
if (!person.password_hash) {
|
|
55
63
|
return new ResponseError('NO_PASSWORD_SET', `No password set`, authLogData)
|
|
56
64
|
}
|
|
@@ -29,6 +29,14 @@ export class PersonIdentityProviderManager {
|
|
|
29
29
|
return new ResponseError('NOT_FOUND', `You are not connected to the identity provider connection "${connectionId}".`)
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
// The link is what binds the person to the provider's policy, so they cannot remove it themselves.
|
|
33
|
+
if (connection.identityProviderDisableLocalAuthentication) {
|
|
34
|
+
return new ResponseError(
|
|
35
|
+
'IDP_REQUIRED',
|
|
36
|
+
`The identity provider "${connection.identityProviderSlug}" is the only way you may sign in; its connection cannot be removed.`,
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
32
40
|
const configuration = await db.queryHandler.fetch(new ConfigurationQuery(db.providers))
|
|
33
41
|
const hasPassword = person.password_hash !== null
|
|
34
42
|
const hasPasswordless = isPasswordlessEnabled(configuration.passwordless.enabled, person.passwordless_enabled)
|
|
@@ -26,6 +26,7 @@ export class AddIDPMutationResolver implements MutationResolvers {
|
|
|
26
26
|
initReturnsConfig: args.options?.initReturnsConfig ?? false,
|
|
27
27
|
requireVerifiedEmail: args.options?.requireVerifiedEmail ?? false,
|
|
28
28
|
assumeEmailVerified: args.options?.assumeEmailVerified ?? false,
|
|
29
|
+
disableLocalAuthentication: args.options?.disableLocalAuthentication ?? false,
|
|
29
30
|
},
|
|
30
31
|
})
|
|
31
32
|
if (!result.ok) {
|
|
@@ -26,6 +26,7 @@ export class UpdateIDPMutationResolver implements MutationResolvers {
|
|
|
26
26
|
initReturnsConfig: args.options?.initReturnsConfig ?? undefined,
|
|
27
27
|
requireVerifiedEmail: args.options?.requireVerifiedEmail ?? undefined,
|
|
28
28
|
assumeEmailVerified: args.options?.assumeEmailVerified ?? undefined,
|
|
29
|
+
disableLocalAuthentication: args.options?.disableLocalAuthentication ?? undefined,
|
|
29
30
|
},
|
|
30
31
|
}, args.mergeConfiguration ?? false)
|
|
31
32
|
if (!result.ok) {
|
|
@@ -14,6 +14,7 @@ export const idpRowToAuditSnapshot = (row: IdentityProviderRow | null): JSONValu
|
|
|
14
14
|
initReturnsConfig: row.initReturnsConfig,
|
|
15
15
|
requireVerifiedEmail: row.requireVerifiedEmail,
|
|
16
16
|
assumeEmailVerified: row.assumeEmailVerified,
|
|
17
|
+
disableLocalAuthentication: row.disableLocalAuthentication,
|
|
17
18
|
configurationKeys: Object.keys(row.configuration).sort(),
|
|
18
19
|
}
|
|
19
20
|
}
|
|
@@ -68,6 +68,13 @@ export class PasswordlessMutationResolver
|
|
|
68
68
|
// If the user does not exist, we don't want to reveal that
|
|
69
69
|
return createErrorResponse('PASSWORDLESS_DISABLED', 'Passwordless sign-in is disabled for this person')
|
|
70
70
|
}
|
|
71
|
+
// Naming the provider tells the caller both that the person exists and how
|
|
72
|
+
// they authenticate, so it needs both reveal flags. PASSWORDLESS_DISABLED is
|
|
73
|
+
// the neutral answer this endpoint already gives for an unknown address.
|
|
74
|
+
const { revealUserExists, revealLoginMethod } = configuration.login
|
|
75
|
+
if (result.error === 'IDP_REQUIRED' && !(revealUserExists && revealLoginMethod)) {
|
|
76
|
+
return createErrorResponse('PASSWORDLESS_DISABLED', 'Passwordless sign-in is disabled for this person')
|
|
77
|
+
}
|
|
71
78
|
|
|
72
79
|
return createErrorResponse(result.error, result.errorMessage)
|
|
73
80
|
}
|
|
@@ -108,10 +108,10 @@ export class SignInMutationResolver implements MutationResolvers {
|
|
|
108
108
|
if (!response.ok) {
|
|
109
109
|
const { revealUserExists, revealLoginMethod } = configuration.login
|
|
110
110
|
const collapseAll = !revealUserExists
|
|
111
|
-
&& ['NO_PASSWORD_SET', 'PERSON_DISABLED', 'INVALID_PASSWORD', 'UNKNOWN_EMAIL'].includes(response.error)
|
|
111
|
+
&& ['NO_PASSWORD_SET', 'PERSON_DISABLED', 'INVALID_PASSWORD', 'UNKNOWN_EMAIL', 'IDP_REQUIRED'].includes(response.error)
|
|
112
112
|
const collapseMethodOnly = revealUserExists
|
|
113
113
|
&& !revealLoginMethod
|
|
114
|
-
&& ['NO_PASSWORD_SET', 'INVALID_PASSWORD'].includes(response.error)
|
|
114
|
+
&& ['NO_PASSWORD_SET', 'INVALID_PASSWORD', 'IDP_REQUIRED'].includes(response.error)
|
|
115
115
|
if (collapseAll || collapseMethodOnly) {
|
|
116
116
|
return createErrorResponse('INVALID_CREDENTIALS', 'Invalid credentials')
|
|
117
117
|
}
|
package/src/schema/index.ts
CHANGED
|
@@ -948,6 +948,11 @@ export type DisconnectIdpError = {
|
|
|
948
948
|
}
|
|
949
949
|
|
|
950
950
|
export type DisconnectIdpErrorCode =
|
|
951
|
+
/**
|
|
952
|
+
* The provider has `disableLocalAuthentication`, so this connection is the only way
|
|
953
|
+
* the person may sign in and cannot be removed by them — not even while the provider is disabled.
|
|
954
|
+
*/
|
|
955
|
+
| 'IDP_REQUIRED'
|
|
951
956
|
/** The person is not allowed to disconnect their last remaining sign-in method. */
|
|
952
957
|
| 'LAST_AUTH_METHOD'
|
|
953
958
|
/** The caller is not a person (e.g. an API key) and so has no IdP connections to disconnect. */
|
|
@@ -1013,6 +1018,7 @@ export type ForceSignOutPersonResponse = {
|
|
|
1013
1018
|
export type IdpOptions = {
|
|
1014
1019
|
readonly assumeEmailVerified?: InputMaybe<Scalars['Boolean']['input']>
|
|
1015
1020
|
readonly autoSignUp?: InputMaybe<Scalars['Boolean']['input']>
|
|
1021
|
+
readonly disableLocalAuthentication?: InputMaybe<Scalars['Boolean']['input']>
|
|
1016
1022
|
readonly exclusive?: InputMaybe<Scalars['Boolean']['input']>
|
|
1017
1023
|
readonly initReturnsConfig?: InputMaybe<Scalars['Boolean']['input']>
|
|
1018
1024
|
readonly requireVerifiedEmail?: InputMaybe<Scalars['Boolean']['input']>
|
|
@@ -1027,6 +1033,13 @@ export type IdpOptionsOutput = {
|
|
|
1027
1033
|
*/
|
|
1028
1034
|
readonly assumeEmailVerified: Scalars['Boolean']['output']
|
|
1029
1035
|
readonly autoSignUp: Scalars['Boolean']['output']
|
|
1036
|
+
/**
|
|
1037
|
+
* When true, a person linked to this provider may authenticate ONLY through it:
|
|
1038
|
+
* password sign-in and passwordless sign-in are refused and no password-reset
|
|
1039
|
+
* mail is sent. Disabling the provider lifts the restriction, which is the
|
|
1040
|
+
* break-glass for an IdP outage. Defaults to false.
|
|
1041
|
+
*/
|
|
1042
|
+
readonly disableLocalAuthentication: Scalars['Boolean']['output']
|
|
1030
1043
|
readonly exclusive: Scalars['Boolean']['output']
|
|
1031
1044
|
readonly initReturnsConfig: Scalars['Boolean']['output']
|
|
1032
1045
|
/**
|
|
@@ -1169,6 +1182,11 @@ export type InitSignInPasswordlessError = {
|
|
|
1169
1182
|
}
|
|
1170
1183
|
|
|
1171
1184
|
export type InitSignInPasswordlessErrorCode =
|
|
1185
|
+
/**
|
|
1186
|
+
* The person is linked to an identity provider with
|
|
1187
|
+
* `disableLocalAuthentication`, so passwordless sign-in is not available to them.
|
|
1188
|
+
*/
|
|
1189
|
+
| 'IDP_REQUIRED'
|
|
1172
1190
|
| 'INVALID_CAPTCHA'
|
|
1173
1191
|
| 'PASSWORDLESS_DISABLED'
|
|
1174
1192
|
| 'PERSON_NOT_FOUND'
|
|
@@ -2150,6 +2168,11 @@ export type SignInError = {
|
|
|
2150
2168
|
|
|
2151
2169
|
export type SignInErrorCode =
|
|
2152
2170
|
| 'EMAIL_NOT_VERIFIED'
|
|
2171
|
+
/**
|
|
2172
|
+
* The person is linked to an identity provider with
|
|
2173
|
+
* `disableLocalAuthentication`, so password sign-in is not available to them.
|
|
2174
|
+
*/
|
|
2175
|
+
| 'IDP_REQUIRED'
|
|
2153
2176
|
| 'INVALID_CREDENTIALS'
|
|
2154
2177
|
| 'INVALID_OTP_TOKEN'
|
|
2155
2178
|
| 'INVALID_PASSWORD'
|
|
@@ -2214,6 +2237,11 @@ export type SignInPasswordlessError = {
|
|
|
2214
2237
|
}
|
|
2215
2238
|
|
|
2216
2239
|
export type SignInPasswordlessErrorCode =
|
|
2240
|
+
/**
|
|
2241
|
+
* The person became linked to an identity provider with
|
|
2242
|
+
* `disableLocalAuthentication` after this magic link was issued.
|
|
2243
|
+
*/
|
|
2244
|
+
| 'IDP_REQUIRED'
|
|
2217
2245
|
| 'INVALID_OTP_TOKEN'
|
|
2218
2246
|
| 'MFA_ENROLLMENT_REQUIRED'
|
|
2219
2247
|
| 'OTP_REQUIRED'
|
|
@@ -3772,6 +3800,7 @@ export type IdpOptionsOutputResolvers<
|
|
|
3772
3800
|
> = {
|
|
3773
3801
|
assumeEmailVerified?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType>
|
|
3774
3802
|
autoSignUp?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType>
|
|
3803
|
+
disableLocalAuthentication?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType>
|
|
3775
3804
|
exclusive?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType>
|
|
3776
3805
|
initReturnsConfig?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType>
|
|
3777
3806
|
requireVerifiedEmail?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType>
|
|
@@ -566,6 +566,11 @@ const schema: DocumentNode = gql`
|
|
|
566
566
|
MFA_ENROLLMENT_REQUIRED
|
|
567
567
|
EMAIL_NOT_VERIFIED
|
|
568
568
|
RATE_LIMIT_EXCEEDED
|
|
569
|
+
"""
|
|
570
|
+
The person is linked to an identity provider with
|
|
571
|
+
\`disableLocalAuthentication\`, so password sign-in is not available to them.
|
|
572
|
+
"""
|
|
573
|
+
IDP_REQUIRED
|
|
569
574
|
}
|
|
570
575
|
|
|
571
576
|
type SignInResult implements CommonSignInResult {
|
|
@@ -867,6 +872,11 @@ const schema: DocumentNode = gql`
|
|
|
867
872
|
NOT_A_PERSON
|
|
868
873
|
""" The person is not allowed to disconnect their last remaining sign-in method. """
|
|
869
874
|
LAST_AUTH_METHOD
|
|
875
|
+
"""
|
|
876
|
+
The provider has \`disableLocalAuthentication\`, so this connection is the only way
|
|
877
|
+
the person may sign in and cannot be removed by them — not even while the provider is disabled.
|
|
878
|
+
"""
|
|
879
|
+
IDP_REQUIRED
|
|
870
880
|
}
|
|
871
881
|
|
|
872
882
|
type IdentityProvider {
|
|
@@ -893,6 +903,13 @@ const schema: DocumentNode = gql`
|
|
|
893
903
|
Defaults to false.
|
|
894
904
|
"""
|
|
895
905
|
assumeEmailVerified: Boolean!
|
|
906
|
+
"""
|
|
907
|
+
When true, a person linked to this provider may authenticate ONLY through it:
|
|
908
|
+
password sign-in and passwordless sign-in are refused and no password-reset
|
|
909
|
+
mail is sent. Disabling the provider lifts the restriction, which is the
|
|
910
|
+
break-glass for an IdP outage. Defaults to false.
|
|
911
|
+
"""
|
|
912
|
+
disableLocalAuthentication: Boolean!
|
|
896
913
|
}
|
|
897
914
|
|
|
898
915
|
input IDPOptions {
|
|
@@ -901,6 +918,7 @@ const schema: DocumentNode = gql`
|
|
|
901
918
|
initReturnsConfig: Boolean
|
|
902
919
|
requireVerifiedEmail: Boolean
|
|
903
920
|
assumeEmailVerified: Boolean
|
|
921
|
+
disableLocalAuthentication: Boolean
|
|
904
922
|
}
|
|
905
923
|
|
|
906
924
|
# === passwordless sign in ===
|
|
@@ -922,6 +940,11 @@ const schema: DocumentNode = gql`
|
|
|
922
940
|
PASSWORDLESS_DISABLED
|
|
923
941
|
INVALID_CAPTCHA
|
|
924
942
|
RATE_LIMIT_EXCEEDED
|
|
943
|
+
"""
|
|
944
|
+
The person is linked to an identity provider with
|
|
945
|
+
\`disableLocalAuthentication\`, so passwordless sign-in is not available to them.
|
|
946
|
+
"""
|
|
947
|
+
IDP_REQUIRED
|
|
925
948
|
}
|
|
926
949
|
|
|
927
950
|
type InitSignInPasswordlessResult {
|
|
@@ -959,6 +982,11 @@ const schema: DocumentNode = gql`
|
|
|
959
982
|
OTP_REQUIRED
|
|
960
983
|
INVALID_OTP_TOKEN
|
|
961
984
|
MFA_ENROLLMENT_REQUIRED
|
|
985
|
+
"""
|
|
986
|
+
The person became linked to an identity provider with
|
|
987
|
+
\`disableLocalAuthentication\` after this magic link was issued.
|
|
988
|
+
"""
|
|
989
|
+
IDP_REQUIRED
|
|
962
990
|
}
|
|
963
991
|
|
|
964
992
|
type SignInPasswordlessResult implements CommonSignInResult {
|
|
@@ -21,3 +21,13 @@ export const signInPasswordlessMutation = (
|
|
|
21
21
|
}`,
|
|
22
22
|
variables,
|
|
23
23
|
})
|
|
24
|
+
|
|
25
|
+
export const initSignInPasswordlessMutation = (variables: { email: string }): GraphQLTestQuery => ({
|
|
26
|
+
query: GQL`mutation($email: String!) {
|
|
27
|
+
initSignInPasswordless(email: $email) {
|
|
28
|
+
ok
|
|
29
|
+
error { code }
|
|
30
|
+
}
|
|
31
|
+
}`,
|
|
32
|
+
variables,
|
|
33
|
+
})
|
|
@@ -72,7 +72,7 @@ test('addIDP accepts a claimMapping whose membership role IS defined in the proj
|
|
|
72
72
|
...sqlTransaction(
|
|
73
73
|
{
|
|
74
74
|
sql:
|
|
75
|
-
'select "id", "slug", "type", "configuration", "disabled_at" as "disabledAt", "auto_sign_up" as "autoSignUp", "exclusive", "init_returns_config" as "initReturnsConfig", "require_verified_email" as "requireVerifiedEmail", "assume_email_verified" as "assumeEmailVerified" from "tenant"."identity_provider" where "slug" = ?',
|
|
75
|
+
'select "id", "slug", "type", "configuration", "disabled_at" as "disabledAt", "auto_sign_up" as "autoSignUp", "exclusive", "init_returns_config" as "initReturnsConfig", "require_verified_email" as "requireVerifiedEmail", "assume_email_verified" as "assumeEmailVerified", "disable_local_authentication" as "disableLocalAuthentication" from "tenant"."identity_provider" where "slug" = ?',
|
|
76
76
|
parameters: ['oidc'],
|
|
77
77
|
response: { rows: [{ id: '123', slug: 'oidc' }] },
|
|
78
78
|
},
|
|
@@ -227,7 +227,7 @@ test('addIDP accepts a condition variable bounded by an allow allowlist plus a v
|
|
|
227
227
|
...sqlTransaction(
|
|
228
228
|
{
|
|
229
229
|
sql:
|
|
230
|
-
'select "id", "slug", "type", "configuration", "disabled_at" as "disabledAt", "auto_sign_up" as "autoSignUp", "exclusive", "init_returns_config" as "initReturnsConfig", "require_verified_email" as "requireVerifiedEmail", "assume_email_verified" as "assumeEmailVerified" from "tenant"."identity_provider" where "slug" = ?',
|
|
230
|
+
'select "id", "slug", "type", "configuration", "disabled_at" as "disabledAt", "auto_sign_up" as "autoSignUp", "exclusive", "init_returns_config" as "initReturnsConfig", "require_verified_email" as "requireVerifiedEmail", "assume_email_verified" as "assumeEmailVerified", "disable_local_authentication" as "disableLocalAuthentication" from "tenant"."identity_provider" where "slug" = ?',
|
|
231
231
|
parameters: ['oidc'],
|
|
232
232
|
response: { rows: [{ id: '123', slug: 'oidc' }] },
|
|
233
233
|
},
|
|
@@ -274,7 +274,7 @@ test('addIDP accepts a condition variable whose constant value IS a valid condit
|
|
|
274
274
|
...sqlTransaction(
|
|
275
275
|
{
|
|
276
276
|
sql:
|
|
277
|
-
'select "id", "slug", "type", "configuration", "disabled_at" as "disabledAt", "auto_sign_up" as "autoSignUp", "exclusive", "init_returns_config" as "initReturnsConfig", "require_verified_email" as "requireVerifiedEmail", "assume_email_verified" as "assumeEmailVerified" from "tenant"."identity_provider" where "slug" = ?',
|
|
277
|
+
'select "id", "slug", "type", "configuration", "disabled_at" as "disabledAt", "auto_sign_up" as "autoSignUp", "exclusive", "init_returns_config" as "initReturnsConfig", "require_verified_email" as "requireVerifiedEmail", "assume_email_verified" as "assumeEmailVerified", "disable_local_authentication" as "disableLocalAuthentication" from "tenant"."identity_provider" where "slug" = ?',
|
|
278
278
|
parameters: ['oidc'],
|
|
279
279
|
response: { rows: [{ id: '123', slug: 'oidc' }] },
|
|
280
280
|
},
|
|
@@ -305,7 +305,7 @@ test('updateIDP runs the same claimMapping validation (rejects a merged-in grant
|
|
|
305
305
|
}
|
|
306
306
|
const selectIdp = {
|
|
307
307
|
sql:
|
|
308
|
-
'select "id", "slug", "type", "configuration", "disabled_at" as "disabledAt", "auto_sign_up" as "autoSignUp", "exclusive", "init_returns_config" as "initReturnsConfig", "require_verified_email" as "requireVerifiedEmail", "assume_email_verified" as "assumeEmailVerified" from "tenant"."identity_provider" where "slug" = ?',
|
|
308
|
+
'select "id", "slug", "type", "configuration", "disabled_at" as "disabledAt", "auto_sign_up" as "autoSignUp", "exclusive", "init_returns_config" as "initReturnsConfig", "require_verified_email" as "requireVerifiedEmail", "assume_email_verified" as "assumeEmailVerified", "disable_local_authentication" as "disableLocalAuthentication" from "tenant"."identity_provider" where "slug" = ?',
|
|
309
309
|
parameters: ['mock'],
|
|
310
310
|
response: { rows: [idpRow] },
|
|
311
311
|
}
|