@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.
Files changed (232) hide show
  1. package/CLAUDE.md +1 -0
  2. package/dist/development/index.cjs +2 -0
  3. package/dist/development/index.cjs.map +1 -1
  4. package/dist/development/index.js +2 -0
  5. package/dist/development/index.js.map +1 -1
  6. package/dist/development/src/migrations/2026-09-04-120000-idp-disable-local-authentication.cjs +18 -0
  7. package/dist/development/src/migrations/2026-09-04-120000-idp-disable-local-authentication.cjs.map +1 -0
  8. package/dist/development/src/migrations/2026-09-04-120000-idp-disable-local-authentication.js +19 -0
  9. package/dist/development/src/migrations/2026-09-04-120000-idp-disable-local-authentication.js.map +1 -0
  10. package/dist/development/src/migrations/runner.cjs +3 -1
  11. package/dist/development/src/migrations/runner.cjs.map +1 -1
  12. package/dist/development/src/migrations/runner.js +3 -1
  13. package/dist/development/src/migrations/runner.js.map +1 -1
  14. package/dist/development/src/migrations/snapshot.cjs +2 -1
  15. package/dist/development/src/migrations/snapshot.cjs.map +1 -1
  16. package/dist/development/src/migrations/snapshot.js +2 -1
  17. package/dist/development/src/migrations/snapshot.js.map +1 -1
  18. package/dist/development/src/model/commands/idp/CreateIdpCommand.cjs +8 -2
  19. package/dist/development/src/model/commands/idp/CreateIdpCommand.cjs.map +1 -1
  20. package/dist/development/src/model/commands/idp/CreateIdpCommand.js +8 -2
  21. package/dist/development/src/model/commands/idp/CreateIdpCommand.js.map +1 -1
  22. package/dist/development/src/model/commands/idp/UpdateIdpCommand.cjs +6 -2
  23. package/dist/development/src/model/commands/idp/UpdateIdpCommand.cjs.map +1 -1
  24. package/dist/development/src/model/commands/idp/UpdateIdpCommand.js +6 -2
  25. package/dist/development/src/model/commands/idp/UpdateIdpCommand.js.map +1 -1
  26. package/dist/development/src/model/queries/idp/LocalAuthDisablingIdpsQuery.cjs +21 -0
  27. package/dist/development/src/model/queries/idp/LocalAuthDisablingIdpsQuery.cjs.map +1 -0
  28. package/dist/development/src/model/queries/idp/LocalAuthDisablingIdpsQuery.js +21 -0
  29. package/dist/development/src/model/queries/idp/LocalAuthDisablingIdpsQuery.js.map +1 -0
  30. package/dist/development/src/model/queries/idp/PersonIdentityProvidersQuery.cjs +1 -1
  31. package/dist/development/src/model/queries/idp/PersonIdentityProvidersQuery.cjs.map +1 -1
  32. package/dist/development/src/model/queries/idp/PersonIdentityProvidersQuery.js +1 -1
  33. package/dist/development/src/model/queries/idp/PersonIdentityProvidersQuery.js.map +1 -1
  34. package/dist/development/src/model/queries/idp/helpers.cjs +3 -3
  35. package/dist/development/src/model/queries/idp/helpers.cjs.map +1 -1
  36. package/dist/development/src/model/queries/idp/helpers.js +3 -3
  37. package/dist/development/src/model/queries/idp/helpers.js.map +1 -1
  38. package/dist/development/src/model/service/PasswordResetManager.cjs +5 -0
  39. package/dist/development/src/model/service/PasswordResetManager.cjs.map +1 -1
  40. package/dist/development/src/model/service/PasswordResetManager.js +5 -0
  41. package/dist/development/src/model/service/PasswordResetManager.js.map +1 -1
  42. package/dist/development/src/model/service/PasswordlessSignInManager.cjs +19 -0
  43. package/dist/development/src/model/service/PasswordlessSignInManager.cjs.map +1 -1
  44. package/dist/development/src/model/service/PasswordlessSignInManager.js +19 -0
  45. package/dist/development/src/model/service/PasswordlessSignInManager.js.map +1 -1
  46. package/dist/development/src/model/service/SignInManager.cjs +5 -0
  47. package/dist/development/src/model/service/SignInManager.cjs.map +1 -1
  48. package/dist/development/src/model/service/SignInManager.js +5 -0
  49. package/dist/development/src/model/service/SignInManager.js.map +1 -1
  50. package/dist/development/src/model/service/idp/PersonIdentityProviderManager.cjs +6 -0
  51. package/dist/development/src/model/service/idp/PersonIdentityProviderManager.cjs.map +1 -1
  52. package/dist/development/src/model/service/idp/PersonIdentityProviderManager.js +6 -0
  53. package/dist/development/src/model/service/idp/PersonIdentityProviderManager.js.map +1 -1
  54. package/dist/development/src/resolvers/mutation/idp/AddIDPMutationResolver.cjs +2 -1
  55. package/dist/development/src/resolvers/mutation/idp/AddIDPMutationResolver.cjs.map +1 -1
  56. package/dist/development/src/resolvers/mutation/idp/AddIDPMutationResolver.js +2 -1
  57. package/dist/development/src/resolvers/mutation/idp/AddIDPMutationResolver.js.map +1 -1
  58. package/dist/development/src/resolvers/mutation/idp/UpdateIDPMutationResolver.cjs +2 -1
  59. package/dist/development/src/resolvers/mutation/idp/UpdateIDPMutationResolver.cjs.map +1 -1
  60. package/dist/development/src/resolvers/mutation/idp/UpdateIDPMutationResolver.js +2 -1
  61. package/dist/development/src/resolvers/mutation/idp/UpdateIDPMutationResolver.js.map +1 -1
  62. package/dist/development/src/resolvers/mutation/idp/audit.cjs +1 -0
  63. package/dist/development/src/resolvers/mutation/idp/audit.cjs.map +1 -1
  64. package/dist/development/src/resolvers/mutation/idp/audit.js +1 -0
  65. package/dist/development/src/resolvers/mutation/idp/audit.js.map +1 -1
  66. package/dist/development/src/resolvers/mutation/person/PasswordlessMutationResolver.cjs +4 -0
  67. package/dist/development/src/resolvers/mutation/person/PasswordlessMutationResolver.cjs.map +1 -1
  68. package/dist/development/src/resolvers/mutation/person/PasswordlessMutationResolver.js +4 -0
  69. package/dist/development/src/resolvers/mutation/person/PasswordlessMutationResolver.js.map +1 -1
  70. package/dist/development/src/resolvers/mutation/person/SignInMutationResolver.cjs +2 -2
  71. package/dist/development/src/resolvers/mutation/person/SignInMutationResolver.cjs.map +1 -1
  72. package/dist/development/src/resolvers/mutation/person/SignInMutationResolver.js +2 -2
  73. package/dist/development/src/resolvers/mutation/person/SignInMutationResolver.js.map +1 -1
  74. package/dist/development/src/schema/tenant.graphql.cjs +28 -0
  75. package/dist/development/src/schema/tenant.graphql.cjs.map +1 -1
  76. package/dist/development/src/schema/tenant.graphql.js +28 -0
  77. package/dist/development/src/schema/tenant.graphql.js.map +1 -1
  78. package/dist/production/index.cjs +2 -0
  79. package/dist/production/index.cjs.map +1 -1
  80. package/dist/production/index.js +2 -0
  81. package/dist/production/index.js.map +1 -1
  82. package/dist/production/src/migrations/2026-09-04-120000-idp-disable-local-authentication.cjs +18 -0
  83. package/dist/production/src/migrations/2026-09-04-120000-idp-disable-local-authentication.cjs.map +1 -0
  84. package/dist/production/src/migrations/2026-09-04-120000-idp-disable-local-authentication.js +19 -0
  85. package/dist/production/src/migrations/2026-09-04-120000-idp-disable-local-authentication.js.map +1 -0
  86. package/dist/production/src/migrations/runner.cjs +3 -1
  87. package/dist/production/src/migrations/runner.cjs.map +1 -1
  88. package/dist/production/src/migrations/runner.js +3 -1
  89. package/dist/production/src/migrations/runner.js.map +1 -1
  90. package/dist/production/src/migrations/snapshot.cjs +2 -1
  91. package/dist/production/src/migrations/snapshot.cjs.map +1 -1
  92. package/dist/production/src/migrations/snapshot.js +2 -1
  93. package/dist/production/src/migrations/snapshot.js.map +1 -1
  94. package/dist/production/src/model/commands/idp/CreateIdpCommand.cjs +8 -2
  95. package/dist/production/src/model/commands/idp/CreateIdpCommand.cjs.map +1 -1
  96. package/dist/production/src/model/commands/idp/CreateIdpCommand.js +8 -2
  97. package/dist/production/src/model/commands/idp/CreateIdpCommand.js.map +1 -1
  98. package/dist/production/src/model/commands/idp/UpdateIdpCommand.cjs +6 -2
  99. package/dist/production/src/model/commands/idp/UpdateIdpCommand.cjs.map +1 -1
  100. package/dist/production/src/model/commands/idp/UpdateIdpCommand.js +6 -2
  101. package/dist/production/src/model/commands/idp/UpdateIdpCommand.js.map +1 -1
  102. package/dist/production/src/model/queries/idp/LocalAuthDisablingIdpsQuery.cjs +21 -0
  103. package/dist/production/src/model/queries/idp/LocalAuthDisablingIdpsQuery.cjs.map +1 -0
  104. package/dist/production/src/model/queries/idp/LocalAuthDisablingIdpsQuery.js +21 -0
  105. package/dist/production/src/model/queries/idp/LocalAuthDisablingIdpsQuery.js.map +1 -0
  106. package/dist/production/src/model/queries/idp/PersonIdentityProvidersQuery.cjs +1 -1
  107. package/dist/production/src/model/queries/idp/PersonIdentityProvidersQuery.cjs.map +1 -1
  108. package/dist/production/src/model/queries/idp/PersonIdentityProvidersQuery.js +1 -1
  109. package/dist/production/src/model/queries/idp/PersonIdentityProvidersQuery.js.map +1 -1
  110. package/dist/production/src/model/queries/idp/helpers.cjs +3 -3
  111. package/dist/production/src/model/queries/idp/helpers.cjs.map +1 -1
  112. package/dist/production/src/model/queries/idp/helpers.js +3 -3
  113. package/dist/production/src/model/queries/idp/helpers.js.map +1 -1
  114. package/dist/production/src/model/service/PasswordResetManager.cjs +5 -0
  115. package/dist/production/src/model/service/PasswordResetManager.cjs.map +1 -1
  116. package/dist/production/src/model/service/PasswordResetManager.js +5 -0
  117. package/dist/production/src/model/service/PasswordResetManager.js.map +1 -1
  118. package/dist/production/src/model/service/PasswordlessSignInManager.cjs +19 -0
  119. package/dist/production/src/model/service/PasswordlessSignInManager.cjs.map +1 -1
  120. package/dist/production/src/model/service/PasswordlessSignInManager.js +19 -0
  121. package/dist/production/src/model/service/PasswordlessSignInManager.js.map +1 -1
  122. package/dist/production/src/model/service/SignInManager.cjs +5 -0
  123. package/dist/production/src/model/service/SignInManager.cjs.map +1 -1
  124. package/dist/production/src/model/service/SignInManager.js +5 -0
  125. package/dist/production/src/model/service/SignInManager.js.map +1 -1
  126. package/dist/production/src/model/service/idp/PersonIdentityProviderManager.cjs +6 -0
  127. package/dist/production/src/model/service/idp/PersonIdentityProviderManager.cjs.map +1 -1
  128. package/dist/production/src/model/service/idp/PersonIdentityProviderManager.js +6 -0
  129. package/dist/production/src/model/service/idp/PersonIdentityProviderManager.js.map +1 -1
  130. package/dist/production/src/resolvers/mutation/idp/AddIDPMutationResolver.cjs +2 -1
  131. package/dist/production/src/resolvers/mutation/idp/AddIDPMutationResolver.cjs.map +1 -1
  132. package/dist/production/src/resolvers/mutation/idp/AddIDPMutationResolver.js +2 -1
  133. package/dist/production/src/resolvers/mutation/idp/AddIDPMutationResolver.js.map +1 -1
  134. package/dist/production/src/resolvers/mutation/idp/UpdateIDPMutationResolver.cjs +2 -1
  135. package/dist/production/src/resolvers/mutation/idp/UpdateIDPMutationResolver.cjs.map +1 -1
  136. package/dist/production/src/resolvers/mutation/idp/UpdateIDPMutationResolver.js +2 -1
  137. package/dist/production/src/resolvers/mutation/idp/UpdateIDPMutationResolver.js.map +1 -1
  138. package/dist/production/src/resolvers/mutation/idp/audit.cjs +1 -0
  139. package/dist/production/src/resolvers/mutation/idp/audit.cjs.map +1 -1
  140. package/dist/production/src/resolvers/mutation/idp/audit.js +1 -0
  141. package/dist/production/src/resolvers/mutation/idp/audit.js.map +1 -1
  142. package/dist/production/src/resolvers/mutation/person/PasswordlessMutationResolver.cjs +4 -0
  143. package/dist/production/src/resolvers/mutation/person/PasswordlessMutationResolver.cjs.map +1 -1
  144. package/dist/production/src/resolvers/mutation/person/PasswordlessMutationResolver.js +4 -0
  145. package/dist/production/src/resolvers/mutation/person/PasswordlessMutationResolver.js.map +1 -1
  146. package/dist/production/src/resolvers/mutation/person/SignInMutationResolver.cjs +2 -2
  147. package/dist/production/src/resolvers/mutation/person/SignInMutationResolver.cjs.map +1 -1
  148. package/dist/production/src/resolvers/mutation/person/SignInMutationResolver.js +2 -2
  149. package/dist/production/src/resolvers/mutation/person/SignInMutationResolver.js.map +1 -1
  150. package/dist/production/src/schema/tenant.graphql.cjs +28 -0
  151. package/dist/production/src/schema/tenant.graphql.cjs.map +1 -1
  152. package/dist/production/src/schema/tenant.graphql.js +28 -0
  153. package/dist/production/src/schema/tenant.graphql.js.map +1 -1
  154. package/dist/tests/cases/integration/mocked/gql/passwordless.d.ts +3 -0
  155. package/dist/tests/cases/integration/mocked/gql/passwordless.d.ts.map +1 -1
  156. package/dist/tests/cases/integration/mocked/localAuthenticationDisabled.test.d.ts +2 -0
  157. package/dist/tests/cases/integration/mocked/localAuthenticationDisabled.test.d.ts.map +1 -0
  158. package/dist/tests/cases/integration/mocked/sql/localAuthDisablingIdpsSql.d.ts +10 -0
  159. package/dist/tests/cases/integration/mocked/sql/localAuthDisablingIdpsSql.d.ts.map +1 -0
  160. package/dist/tests/cases/integration/mocked/sql/personIdentityProvidersSql.d.ts +1 -0
  161. package/dist/tests/cases/integration/mocked/sql/personIdentityProvidersSql.d.ts.map +1 -1
  162. package/dist/tests/tsconfig.tsbuildinfo +1 -1
  163. package/dist/types/migrations/2026-09-04-120000-idp-disable-local-authentication.d.ts +3 -0
  164. package/dist/types/migrations/2026-09-04-120000-idp-disable-local-authentication.d.ts.map +1 -0
  165. package/dist/types/migrations/runner.d.ts.map +1 -1
  166. package/dist/types/migrations/snapshot.d.ts.map +1 -1
  167. package/dist/types/model/commands/idp/CreateIdpCommand.d.ts.map +1 -1
  168. package/dist/types/model/commands/idp/UpdateIdpCommand.d.ts.map +1 -1
  169. package/dist/types/model/queries/idp/LocalAuthDisablingIdpsQuery.d.ts +18 -0
  170. package/dist/types/model/queries/idp/LocalAuthDisablingIdpsQuery.d.ts.map +1 -0
  171. package/dist/types/model/queries/idp/PersonIdentityProvidersQuery.d.ts +1 -0
  172. package/dist/types/model/queries/idp/PersonIdentityProvidersQuery.d.ts.map +1 -1
  173. package/dist/types/model/queries/idp/helpers.d.ts +1 -1
  174. package/dist/types/model/queries/idp/helpers.d.ts.map +1 -1
  175. package/dist/types/model/queries/idp/index.d.ts +1 -0
  176. package/dist/types/model/queries/idp/index.d.ts.map +1 -1
  177. package/dist/types/model/queries/idp/types.d.ts +1 -0
  178. package/dist/types/model/queries/idp/types.d.ts.map +1 -1
  179. package/dist/types/model/service/PasswordResetManager.d.ts.map +1 -1
  180. package/dist/types/model/service/PasswordlessSignInManager.d.ts.map +1 -1
  181. package/dist/types/model/service/SignInManager.d.ts.map +1 -1
  182. package/dist/types/model/service/idp/PersonIdentityProviderManager.d.ts.map +1 -1
  183. package/dist/types/model/type/IdentityProvider.d.ts +2 -0
  184. package/dist/types/model/type/IdentityProvider.d.ts.map +1 -1
  185. package/dist/types/resolvers/mutation/idp/AddIDPMutationResolver.d.ts.map +1 -1
  186. package/dist/types/resolvers/mutation/idp/UpdateIDPMutationResolver.d.ts.map +1 -1
  187. package/dist/types/resolvers/mutation/idp/audit.d.ts.map +1 -1
  188. package/dist/types/resolvers/mutation/person/PasswordlessMutationResolver.d.ts.map +1 -1
  189. package/dist/types/schema/index.d.ts +33 -4
  190. package/dist/types/schema/index.d.ts.map +1 -1
  191. package/dist/types/schema/tenant.graphql.d.ts.map +1 -1
  192. package/dist/types/tsconfig.tsbuildinfo +1 -1
  193. package/package.json +12 -12
  194. package/src/migrations/2026-09-04-120000-idp-disable-local-authentication.ts +18 -0
  195. package/src/migrations/runner.ts +2 -0
  196. package/src/migrations/snapshot.ts +2 -1
  197. package/src/model/commands/idp/CreateIdpCommand.ts +7 -1
  198. package/src/model/commands/idp/UpdateIdpCommand.ts +5 -1
  199. package/src/model/queries/idp/LocalAuthDisablingIdpsQuery.ts +41 -0
  200. package/src/model/queries/idp/PersonIdentityProvidersQuery.ts +2 -0
  201. package/src/model/queries/idp/helpers.ts +3 -2
  202. package/src/model/queries/idp/index.ts +1 -0
  203. package/src/model/queries/idp/types.ts +1 -0
  204. package/src/model/service/PasswordResetManager.ts +9 -1
  205. package/src/model/service/PasswordlessSignInManager.ts +23 -1
  206. package/src/model/service/SignInManager.ts +9 -1
  207. package/src/model/service/idp/PersonIdentityProviderManager.ts +8 -0
  208. package/src/model/type/IdentityProvider.ts +2 -0
  209. package/src/resolvers/mutation/idp/AddIDPMutationResolver.ts +1 -0
  210. package/src/resolvers/mutation/idp/UpdateIDPMutationResolver.ts +1 -0
  211. package/src/resolvers/mutation/idp/audit.ts +1 -0
  212. package/src/resolvers/mutation/person/PasswordlessMutationResolver.ts +7 -0
  213. package/src/resolvers/mutation/person/SignInMutationResolver.ts +2 -2
  214. package/src/schema/index.ts +29 -0
  215. package/src/schema/tenant.graphql.ts +28 -0
  216. package/tests/cases/integration/mocked/gql/passwordless.ts +10 -0
  217. package/tests/cases/integration/mocked/idpClaimMappingConfig.test.ts +4 -4
  218. package/tests/cases/integration/mocked/localAuthenticationDisabled.test.ts +182 -0
  219. package/tests/cases/integration/mocked/personIdentityProviders.test.ts +67 -0
  220. package/tests/cases/integration/mocked/signInAnomalyDetection.test.ts +11 -0
  221. package/tests/cases/integration/mocked/signInIdpClaimMapping.test.ts +14 -0
  222. package/tests/cases/integration/mocked/signInIdpClaimMappingFixes.test.ts +4 -0
  223. package/tests/cases/integration/mocked/signInIdpClaimMappingSafety.test.ts +6 -0
  224. package/tests/cases/integration/mocked/signInIdpMutation.test.ts +5 -0
  225. package/tests/cases/integration/mocked/signInMfaEnforcement.test.ts +7 -0
  226. package/tests/cases/integration/mocked/signInMutation.test.ts +14 -0
  227. package/tests/cases/integration/mocked/signInPasswordlessMutation.test.ts +36 -0
  228. package/tests/cases/integration/mocked/sql/getIdpBySlugSql.ts +1 -1
  229. package/tests/cases/integration/mocked/sql/localAuthDisablingIdpsSql.ts +21 -0
  230. package/tests/cases/integration/mocked/sql/personIdentityProvidersSql.ts +3 -1
  231. package/tests/cases/integration/mocked/trustForwardedClientInfo.test.ts +3 -0
  232. package/tests/cases/unit/idp/idpManagerUnknownConfigKeys.test.ts +8 -1
@@ -4,6 +4,7 @@ import { testUuid } from '../../../src/testUuid.js'
4
4
  import { selectMembershipsSql } from './sql/selectMembershipsSql.js'
5
5
  import { signInMutation } from './gql/signIn.js'
6
6
  import { getPersonByEmailSql } from './sql/getPersonByEmailSql.js'
7
+ import { localAuthDisablingIdpsSql } from './sql/localAuthDisablingIdpsSql.js'
7
8
  import { SignInErrorCode } from '../../../../src/schema/index.js'
8
9
  import { expect, test } from 'bun:test'
9
10
  import { OtpAuthenticator } from '../../../../src/index.js'
@@ -31,6 +32,7 @@ test('signs in', async () => {
31
32
  getConfigSql(),
32
33
  getNextLoginAttemptSql(email),
33
34
  getPersonByEmailSql({ email, response: { personId, identityId, password, roles: [] } }),
35
+ localAuthDisablingIdpsSql({ personId }),
34
36
  getAuthPoliciesSql(),
35
37
  getConfigSql(),
36
38
  getIdentityByIdSql({ identityId }),
@@ -94,6 +96,7 @@ test('signs in - normalize email', async () => {
94
96
  getNextLoginAttemptSql(email),
95
97
  getPersonByEmailSql({ email, response: null }),
96
98
  getPersonByEmailSql({ email: 'john@doe.com', response: { personId, identityId, password, roles: [] } }),
99
+ localAuthDisablingIdpsSql({ personId }),
97
100
  getAuthPoliciesSql(),
98
101
  getConfigSql(),
99
102
  getIdentityByIdSql({ identityId }),
@@ -137,6 +140,7 @@ test('sign in - invalid password', async () => {
137
140
  getConfigSql(),
138
141
  getNextLoginAttemptSql(email),
139
142
  getPersonByEmailSql({ email, response: { personId, identityId, password, roles: [] } }),
143
+ localAuthDisablingIdpsSql({ personId }),
140
144
  ],
141
145
  return: {
142
146
  data: {
@@ -170,6 +174,7 @@ test('sign in - email not verified', async () => {
170
174
  email,
171
175
  response: { personId, identityId, password, roles: [], emailVerificationRequired: true, emailVerifiedAt: null },
172
176
  }),
177
+ localAuthDisablingIdpsSql({ personId }),
173
178
  ],
174
179
  return: {
175
180
  data: {
@@ -205,6 +210,7 @@ test('sign in - email verification required but already verified', async () => {
205
210
  email,
206
211
  response: { personId, identityId, password, roles: [], emailVerificationRequired: true, emailVerifiedAt: now },
207
212
  }),
213
+ localAuthDisablingIdpsSql({ personId }),
208
214
  getAuthPoliciesSql(),
209
215
  getConfigSql(),
210
216
  getIdentityByIdSql({ identityId }),
@@ -253,6 +259,7 @@ test('otp token not provided', async () => {
253
259
  otpUri: 'otpauth://totp/contember:john?secret=ABCDEFG&period=30&digits=6&algorithm=SHA1&issuer=contember',
254
260
  },
255
261
  }),
262
+ localAuthDisablingIdpsSql({ personId }),
256
263
  ],
257
264
  return: {
258
265
  data: {
@@ -290,6 +297,7 @@ test('sign in - invalid otp token', async () => {
290
297
  getConfigSql(),
291
298
  getNextLoginAttemptSql(email),
292
299
  getPersonByEmailSql({ email, response: { personId, identityId, password, roles: [], otpUri: otp.uri } }),
300
+ localAuthDisablingIdpsSql({ personId }),
293
301
  ],
294
302
  return: {
295
303
  data: {
@@ -327,6 +335,7 @@ test('sign in - valid otp token', async () => {
327
335
  getConfigSql(),
328
336
  getNextLoginAttemptSql(email),
329
337
  getPersonByEmailSql({ email, response: { personId, identityId, password, roles: [], otpUri: otp.uri } }),
338
+ localAuthDisablingIdpsSql({ personId }),
330
339
  getConfigSql(),
331
340
  getIdentityByIdSql({ identityId }),
332
341
  getAuthPoliciesSql(),
@@ -380,6 +389,7 @@ test('sign in - valid backup code (when OTP is required)', async () => {
380
389
  getConfigSql(),
381
390
  getNextLoginAttemptSql(email),
382
391
  getPersonByEmailSql({ email, response: { personId, identityId, password, roles: [], otpUri: otp.uri } }),
392
+ localAuthDisablingIdpsSql({ personId }),
383
393
  consumeBackupCodeSql({ personId, codeHash: BACKUP_CODE_HASH, consumed: true }),
384
394
  countUnusedBackupCodesSql({ personId, count: 4 }),
385
395
  getConfigSql(),
@@ -432,6 +442,7 @@ test('sign in - email OTP enabled, no code provided: dispatches a code and retur
432
442
  getConfigSql(),
433
443
  getNextLoginAttemptSql(email),
434
444
  getPersonByEmailSql({ email, response: { personId, identityId, password, roles: [], emailOtpEnabled: true } }),
445
+ localAuthDisablingIdpsSql({ personId }),
435
446
  // signIn lazily re-fetches the config to resolve the email-OTP send rate limit.
436
447
  getConfigSql(),
437
448
  ...sendEmailOtpSql({ personId, rateLimitEventId: testUuid(1), tokenId: testUuid(2) }),
@@ -475,6 +486,7 @@ test('sign in - email OTP enabled, valid code: signs in', async () => {
475
486
  getConfigSql(),
476
487
  getNextLoginAttemptSql(email),
477
488
  getPersonByEmailSql({ email, response: { personId, identityId, password, roles: [], emailOtpEnabled: true } }),
489
+ localAuthDisablingIdpsSql({ personId }),
478
490
  getLatestEmailOtpTokenSql({ personId, tokenId }),
479
491
  claimEmailOtpAttemptSql({ tokenId }),
480
492
  consumeEmailOtpTokenSql({ tokenId }),
@@ -515,6 +527,7 @@ test('sign in - email OTP enabled, invalid code: INVALID_OTP_TOKEN', async () =>
515
527
  getConfigSql(),
516
528
  getNextLoginAttemptSql(email),
517
529
  getPersonByEmailSql({ email, response: { personId, identityId, password, roles: [], emailOtpEnabled: true } }),
530
+ localAuthDisablingIdpsSql({ personId }),
518
531
  getLatestEmailOtpTokenSql({ personId, tokenId }),
519
532
  claimEmailOtpAttemptSql({ tokenId }),
520
533
  ],
@@ -550,6 +563,7 @@ test('sign in - already-used backup code is rejected', async () => {
550
563
  getConfigSql(),
551
564
  getNextLoginAttemptSql(email),
552
565
  getPersonByEmailSql({ email, response: { personId, identityId, password, roles: [], otpUri: otp.uri } }),
566
+ localAuthDisablingIdpsSql({ personId }),
553
567
  consumeBackupCodeSql({ personId, codeHash: BACKUP_CODE_HASH, consumed: false }),
554
568
  ],
555
569
  return: {
@@ -6,6 +6,7 @@ import { signInPasswordlessMutation } from './gql/passwordless.js'
6
6
  import { computeTokenHash } from '../../../../src/index.js'
7
7
  import { sqlTransaction } from './sql/sqlTransaction.js'
8
8
  import { getPersonByIdSql } from './sql/getPersonByIdSql.js'
9
+ import { localAuthDisablingIdpsSql } from './sql/localAuthDisablingIdpsSql.js'
9
10
  import { getConfigSql } from './sql/getConfigSql.js'
10
11
  import { createSessionKeySql } from './sql/createSessionKeySql.js'
11
12
  import { getIdentityProjectsSql } from './sql/getIdentityProjectsSql.js'
@@ -54,6 +55,7 @@ test('signInPasswordless - marks an unverified email verified before issuing the
54
55
  personId,
55
56
  response: { personId, identityId, password: '123', roles: [], email: 'john@doe.com', emailVerifiedAt: null },
56
57
  }),
58
+ localAuthDisablingIdpsSql({ personId }),
57
59
  {
58
60
  sql: SQL`UPDATE "tenant"."person_token" SET "used_at" = ? WHERE "id" = ? AND "used_at" IS NULL`,
59
61
  parameters: [isDate, requestId],
@@ -110,6 +112,7 @@ test('signInPasswordless - skips the email-verified update when already verified
110
112
  personId,
111
113
  response: { personId, identityId, password: '123', roles: [], email: 'john@doe.com', emailVerifiedAt: now },
112
114
  }),
115
+ localAuthDisablingIdpsSql({ personId }),
113
116
  {
114
117
  sql: SQL`UPDATE "tenant"."person_token" SET "used_at" = ? WHERE "id" = ? AND "used_at" IS NULL`,
115
118
  parameters: [isDate, requestId],
@@ -144,3 +147,36 @@ test('signInPasswordless - skips the email-verified update when already verified
144
147
  },
145
148
  })
146
149
  })
150
+
151
+ test('signInPasswordless - a link created while the magic link was in flight still refuses the session', async () => {
152
+ const requestId = testUuid(1)
153
+ const personId = testUuid(2)
154
+ const identityId = testUuid(3)
155
+ const token = 'passwordless-token'
156
+ await executeTenantTest({
157
+ query: signInPasswordlessMutation({ requestId, token, validationType: 'token' }),
158
+ executes: [
159
+ ...sqlTransaction(
160
+ personTokenByIdSql({ requestId, personId, token }),
161
+ getPersonByIdSql({
162
+ personId,
163
+ response: { personId, identityId, password: '123', roles: [], email: 'john@doe.com', emailVerifiedAt: now },
164
+ }),
165
+ localAuthDisablingIdpsSql({ personId, slugs: ['corporateSso'] }),
166
+ ),
167
+ ],
168
+ return: {
169
+ data: {
170
+ signInPasswordless: {
171
+ ok: false,
172
+ error: { code: 'IDP_REQUIRED' },
173
+ result: null,
174
+ },
175
+ },
176
+ },
177
+ expectedAuthLog: {
178
+ type: 'passwordless_login',
179
+ response: expect.objectContaining({ ok: false }),
180
+ },
181
+ })
182
+ })
@@ -7,7 +7,7 @@ export const getIdpBySlugSql = (args: {
7
7
  response: null | IdentityProviderRow
8
8
  }): ExpectedQuery => ({
9
9
  sql:
10
- SQL`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"
10
+ SQL`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"
11
11
  from "tenant"."identity_provider"
12
12
  where "slug" = ?`,
13
13
  parameters: [args.slug],
@@ -0,0 +1,21 @@
1
+ import { ExpectedQuery } from '@contember/database-tester'
2
+ import { SQL } from '../../../../src/tags.js'
3
+
4
+ /**
5
+ * The `LocalAuthDisablingIdpsQuery` lookup every local authentication path runs.
6
+ * `slugs` defaults to none, i.e. local authentication stays available.
7
+ */
8
+ export const localAuthDisablingIdpsSql = (args: {
9
+ personId: string
10
+ slugs?: string[]
11
+ }): ExpectedQuery => ({
12
+ sql: SQL`SELECT "identity_provider"."slug" AS "slug"
13
+ FROM "tenant"."person_identity_provider"
14
+ INNER JOIN "tenant"."identity_provider" AS "identity_provider" ON "identity_provider"."id" = "person_identity_provider"."identity_provider_id"
15
+ WHERE "person_identity_provider"."person_id" = ? AND "identity_provider"."disable_local_authentication" = ? AND "identity_provider"."disabled_at" IS NULL
16
+ ORDER BY "identity_provider"."slug" ASC`,
17
+ parameters: [args.personId, true],
18
+ response: {
19
+ rows: (args.slugs ?? []).map(slug => ({ slug })),
20
+ },
21
+ })
@@ -8,6 +8,7 @@ export interface PersonIdpConnectionRow {
8
8
  slug: string
9
9
  type: string
10
10
  disabledAt?: Date | null
11
+ disableLocalAuthentication?: boolean
11
12
  }
12
13
 
13
14
  export const getPersonIdentityProvidersSql = (args: {
@@ -15,7 +16,7 @@ export const getPersonIdentityProvidersSql = (args: {
15
16
  response: PersonIdpConnectionRow[]
16
17
  }): ExpectedQuery => ({
17
18
  sql:
18
- SQL`SELECT "person_identity_provider"."id", "person_identity_provider"."created_at" as "createdAt", "person_identity_provider"."external_identifier" as "externalIdentifier", "identity_provider"."slug" as "identityProviderSlug", "identity_provider"."type" as "identityProviderType", "identity_provider"."disabled_at" as "identityProviderDisabledAt"
19
+ SQL`SELECT "person_identity_provider"."id", "person_identity_provider"."created_at" as "createdAt", "person_identity_provider"."external_identifier" as "externalIdentifier", "identity_provider"."slug" as "identityProviderSlug", "identity_provider"."type" as "identityProviderType", "identity_provider"."disabled_at" as "identityProviderDisabledAt", "identity_provider"."disable_local_authentication" as "identityProviderDisableLocalAuthentication"
19
20
  FROM "tenant"."person_identity_provider"
20
21
  INNER JOIN "tenant"."identity_provider" AS "identity_provider" ON "identity_provider"."id" = "person_identity_provider"."identity_provider_id"
21
22
  WHERE "person_id" = ?
@@ -29,6 +30,7 @@ export const getPersonIdentityProvidersSql = (args: {
29
30
  identityProviderSlug: it.slug,
30
31
  identityProviderType: it.type,
31
32
  identityProviderDisabledAt: it.disabledAt ?? null,
33
+ identityProviderDisableLocalAuthentication: it.disableLocalAuthentication ?? false,
32
34
  })),
33
35
  },
34
36
  })
@@ -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 { createSessionKeySql } from './sql/createSessionKeySql.js'
8
9
  import { getIdentityProjectsSql } from './sql/getIdentityProjectsSql.js'
@@ -29,6 +30,7 @@ test('signIn: trustForwardedClientInfo=true is propagated when caller has the fl
29
30
  getConfigSql(),
30
31
  getNextLoginAttemptSql(email),
31
32
  getPersonByEmailSql({ email, response: { personId, identityId, password, roles: [] } }),
33
+ localAuthDisablingIdpsSql({ personId }),
32
34
  getAuthPoliciesSql(),
33
35
  getConfigSql(),
34
36
  getIdentityByIdSql({ identityId }),
@@ -68,6 +70,7 @@ test('signIn: trustForwardedClientInfo=true is silently dropped when caller has
68
70
  getConfigSql(),
69
71
  getNextLoginAttemptSql(email),
70
72
  getPersonByEmailSql({ email, response: { personId, identityId, password, roles: [] } }),
73
+ localAuthDisablingIdpsSql({ personId }),
71
74
  getAuthPoliciesSql(),
72
75
  getConfigSql(),
73
76
  getIdentityByIdSql({ identityId }),
@@ -10,7 +10,14 @@ import { DatabaseContext } from '../../../../src/index.js'
10
10
  // no network), so the real provider can be used here rather than a pass-through double.
11
11
 
12
12
  const OIDC_BASE = { url: 'https://idp.example.com', clientId: 'client', clientSecret: 'secret' }
13
- const OPTIONS = { autoSignUp: false, exclusive: false, initReturnsConfig: false, requireVerifiedEmail: false, assumeEmailVerified: false }
13
+ const OPTIONS = {
14
+ autoSignUp: false,
15
+ exclusive: false,
16
+ initReturnsConfig: false,
17
+ requireVerifiedEmail: false,
18
+ assumeEmailVerified: false,
19
+ disableLocalAuthentication: false,
20
+ }
14
21
 
15
22
  const makeManager = () => {
16
23
  const registry = new IDPHandlerRegistry()