@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
@@ -24,6 +24,12 @@ class PersonIdentityProviderManager {
24
24
  if (!connection) {
25
25
  return new Response.ResponseError("NOT_FOUND", `You are not connected to the identity provider connection "${connectionId}".`);
26
26
  }
27
+ if (connection.identityProviderDisableLocalAuthentication) {
28
+ return new Response.ResponseError(
29
+ "IDP_REQUIRED",
30
+ `The identity provider "${connection.identityProviderSlug}" is the only way you may sign in; its connection cannot be removed.`
31
+ );
32
+ }
27
33
  const configuration = await db2.queryHandler.fetch(new ConfigurationQuery.ConfigurationQuery(db2.providers));
28
34
  const hasPassword = person.password_hash !== null;
29
35
  const hasPasswordless = isPasswordlessEnabled.isPasswordlessEnabled(configuration.passwordless.enabled, person.passwordless_enabled);
@@ -1 +1 @@
1
- {"version":3,"file":"PersonIdentityProviderManager.cjs","sources":["../../../../../../../packages/engine-tenant-api/src/model/service/idp/PersonIdentityProviderManager.ts"],"sourcesContent":["import { Response, ResponseError, ResponseOk } from '../../utils/Response.js'\nimport { DisconnectIdpErrorCode } from '../../../schema/index.js'\nimport { DatabaseContext } from '../../utils/index.js'\nimport { ConfigurationQuery, PersonIdentityProviderRow, PersonIdentityProvidersQuery, PersonRow } from '../../queries/index.js'\nimport { DisconnectPersonIdentityProviderCommand } from '../../commands/idp/DisconnectPersonIdentityProviderCommand.js'\nimport { isPasswordlessEnabled } from '../helpers/isPasswordlessEnabled.js'\n\n/**\n * Manages the external IdP connections of the *currently authenticated* person.\n *\n * Lock-out policy: a person may always disconnect a provider as long as it does\n * not remove their last remaining sign-in method. A connection is refused only\n * when, after removal, the person would have no password, no passwordless\n * sign-in enabled and no other *usable* IdP connection left — which would lock\n * them out of their account entirely. Whether passwordless counts is decided by\n * the same tenant policy the sign-in flow applies, and connections to disabled\n * providers are not counted as a usable fallback (they cannot sign in).\n */\nexport class PersonIdentityProviderManager {\n\tpublic async listPersonIdentityProviders(db: DatabaseContext, personId: string): Promise<PersonIdentityProviderRow[]> {\n\t\treturn await db.queryHandler.fetch(new PersonIdentityProvidersQuery(personId))\n\t}\n\n\tpublic async disconnectIdentityProvider(db: DatabaseContext, person: PersonRow, connectionId: string): Promise<DisconnectIDPResponse> {\n\t\treturn await db.transaction(async db => {\n\t\t\tconst connections = await db.queryHandler.fetch(new PersonIdentityProvidersQuery(person.id))\n\t\t\tconst connection = connections.find(it => it.id === connectionId)\n\t\t\tif (!connection) {\n\t\t\t\treturn new ResponseError('NOT_FOUND', `You are not connected to the identity provider connection \"${connectionId}\".`)\n\t\t\t}\n\n\t\t\tconst configuration = await db.queryHandler.fetch(new ConfigurationQuery(db.providers))\n\t\t\tconst hasPassword = person.password_hash !== null\n\t\t\tconst hasPasswordless = isPasswordlessEnabled(configuration.passwordless.enabled, person.passwordless_enabled)\n\t\t\tconst otherUsableConnections = connections.filter(it => it.id !== connection.id && it.identityProviderDisabledAt === null)\n\t\t\tconst wouldLockOut = !hasPassword && !hasPasswordless && otherUsableConnections.length === 0\n\t\t\tif (wouldLockOut) {\n\t\t\t\treturn new ResponseError(\n\t\t\t\t\t'LAST_AUTH_METHOD',\n\t\t\t\t\t'Cannot disconnect the only remaining sign-in method. Set up a password or another sign-in method first.',\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tawait db.commandBus.execute(new DisconnectPersonIdentityProviderCommand(person.id, connection.id))\n\t\t\treturn new ResponseOk({ slug: connection.identityProviderSlug })\n\t\t})\n\t}\n}\n\nexport interface DisconnectIDPResult {\n\treadonly slug: string\n}\n\nexport type DisconnectIDPResponse = Response<DisconnectIDPResult, DisconnectIdpErrorCode>\n"],"names":["PersonIdentityProvidersQuery","db","ResponseError","ConfigurationQuery","isPasswordlessEnabled","DisconnectPersonIdentityProviderCommand","ResponseOk"],"mappings":";;;;;;;;;;;;;;;AAkBO,MAAM,8BAA8B;AAAA,EAC1C,MAAa,4BAA4B,IAAqB,UAAwD;AACrH,WAAO,MAAM,GAAG,aAAa,MAAM,IAAIA,6BAAAA,6BAA6B,QAAQ,CAAC;AAAA,EAC9E;AAAA,EAEA,MAAa,2BAA2B,IAAqB,QAAmB,cAAsD;AACrI,WAAO,MAAM,GAAG,YAAY,OAAMC,QAAM;AACvC,YAAM,cAAc,MAAMA,IAAG,aAAa,MAAM,IAAID,6BAAAA,6BAA6B,OAAO,EAAE,CAAC;AAC3F,YAAM,aAAa,YAAY,KAAK,CAAA,OAAM,GAAG,OAAO,YAAY;AAChE,UAAI,CAAC,YAAY;AAChB,eAAO,IAAIE,SAAAA,cAAc,aAAa,8DAA8D,YAAY,IAAI;AAAA,MACrH;AAEA,YAAM,gBAAgB,MAAMD,IAAG,aAAa,MAAM,IAAIE,mBAAAA,mBAAmBF,IAAG,SAAS,CAAC;AACtF,YAAM,cAAc,OAAO,kBAAkB;AAC7C,YAAM,kBAAkBG,sBAAAA,sBAAsB,cAAc,aAAa,SAAS,OAAO,oBAAoB;AAC7G,YAAM,yBAAyB,YAAY,OAAO,CAAA,OAAM,GAAG,OAAO,WAAW,MAAM,GAAG,+BAA+B,IAAI;AACzH,YAAM,eAAe,CAAC,eAAe,CAAC,mBAAmB,uBAAuB,WAAW;AAC3F,UAAI,cAAc;AACjB,eAAO,IAAIF,SAAAA;AAAAA,UACV;AAAA,UACA;AAAA,QAAA;AAAA,MAEF;AAEA,YAAMD,IAAG,WAAW,QAAQ,IAAII,wCAAAA,wCAAwC,OAAO,IAAI,WAAW,EAAE,CAAC;AACjG,aAAO,IAAIC,SAAAA,WAAW,EAAE,MAAM,WAAW,sBAAsB;AAAA,IAChE,CAAC;AAAA,EACF;AACD;;"}
1
+ {"version":3,"file":"PersonIdentityProviderManager.cjs","sources":["../../../../../../../packages/engine-tenant-api/src/model/service/idp/PersonIdentityProviderManager.ts"],"sourcesContent":["import { Response, ResponseError, ResponseOk } from '../../utils/Response.js'\nimport { DisconnectIdpErrorCode } from '../../../schema/index.js'\nimport { DatabaseContext } from '../../utils/index.js'\nimport { ConfigurationQuery, PersonIdentityProviderRow, PersonIdentityProvidersQuery, PersonRow } from '../../queries/index.js'\nimport { DisconnectPersonIdentityProviderCommand } from '../../commands/idp/DisconnectPersonIdentityProviderCommand.js'\nimport { isPasswordlessEnabled } from '../helpers/isPasswordlessEnabled.js'\n\n/**\n * Manages the external IdP connections of the *currently authenticated* person.\n *\n * Lock-out policy: a person may always disconnect a provider as long as it does\n * not remove their last remaining sign-in method. A connection is refused only\n * when, after removal, the person would have no password, no passwordless\n * sign-in enabled and no other *usable* IdP connection left — which would lock\n * them out of their account entirely. Whether passwordless counts is decided by\n * the same tenant policy the sign-in flow applies, and connections to disabled\n * providers are not counted as a usable fallback (they cannot sign in).\n */\nexport class PersonIdentityProviderManager {\n\tpublic async listPersonIdentityProviders(db: DatabaseContext, personId: string): Promise<PersonIdentityProviderRow[]> {\n\t\treturn await db.queryHandler.fetch(new PersonIdentityProvidersQuery(personId))\n\t}\n\n\tpublic async disconnectIdentityProvider(db: DatabaseContext, person: PersonRow, connectionId: string): Promise<DisconnectIDPResponse> {\n\t\treturn await db.transaction(async db => {\n\t\t\tconst connections = await db.queryHandler.fetch(new PersonIdentityProvidersQuery(person.id))\n\t\t\tconst connection = connections.find(it => it.id === connectionId)\n\t\t\tif (!connection) {\n\t\t\t\treturn new ResponseError('NOT_FOUND', `You are not connected to the identity provider connection \"${connectionId}\".`)\n\t\t\t}\n\n\t\t\t// The link is what binds the person to the provider's policy, so they cannot remove it themselves.\n\t\t\tif (connection.identityProviderDisableLocalAuthentication) {\n\t\t\t\treturn new ResponseError(\n\t\t\t\t\t'IDP_REQUIRED',\n\t\t\t\t\t`The identity provider \"${connection.identityProviderSlug}\" is the only way you may sign in; its connection cannot be removed.`,\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tconst configuration = await db.queryHandler.fetch(new ConfigurationQuery(db.providers))\n\t\t\tconst hasPassword = person.password_hash !== null\n\t\t\tconst hasPasswordless = isPasswordlessEnabled(configuration.passwordless.enabled, person.passwordless_enabled)\n\t\t\tconst otherUsableConnections = connections.filter(it => it.id !== connection.id && it.identityProviderDisabledAt === null)\n\t\t\tconst wouldLockOut = !hasPassword && !hasPasswordless && otherUsableConnections.length === 0\n\t\t\tif (wouldLockOut) {\n\t\t\t\treturn new ResponseError(\n\t\t\t\t\t'LAST_AUTH_METHOD',\n\t\t\t\t\t'Cannot disconnect the only remaining sign-in method. Set up a password or another sign-in method first.',\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tawait db.commandBus.execute(new DisconnectPersonIdentityProviderCommand(person.id, connection.id))\n\t\t\treturn new ResponseOk({ slug: connection.identityProviderSlug })\n\t\t})\n\t}\n}\n\nexport interface DisconnectIDPResult {\n\treadonly slug: string\n}\n\nexport type DisconnectIDPResponse = Response<DisconnectIDPResult, DisconnectIdpErrorCode>\n"],"names":["PersonIdentityProvidersQuery","db","ResponseError","ConfigurationQuery","isPasswordlessEnabled","DisconnectPersonIdentityProviderCommand","ResponseOk"],"mappings":";;;;;;;;;;;;;;;AAkBO,MAAM,8BAA8B;AAAA,EAC1C,MAAa,4BAA4B,IAAqB,UAAwD;AACrH,WAAO,MAAM,GAAG,aAAa,MAAM,IAAIA,6BAAAA,6BAA6B,QAAQ,CAAC;AAAA,EAC9E;AAAA,EAEA,MAAa,2BAA2B,IAAqB,QAAmB,cAAsD;AACrI,WAAO,MAAM,GAAG,YAAY,OAAMC,QAAM;AACvC,YAAM,cAAc,MAAMA,IAAG,aAAa,MAAM,IAAID,6BAAAA,6BAA6B,OAAO,EAAE,CAAC;AAC3F,YAAM,aAAa,YAAY,KAAK,CAAA,OAAM,GAAG,OAAO,YAAY;AAChE,UAAI,CAAC,YAAY;AAChB,eAAO,IAAIE,SAAAA,cAAc,aAAa,8DAA8D,YAAY,IAAI;AAAA,MACrH;AAGA,UAAI,WAAW,4CAA4C;AAC1D,eAAO,IAAIA,SAAAA;AAAAA,UACV;AAAA,UACA,0BAA0B,WAAW,oBAAoB;AAAA,QAAA;AAAA,MAE3D;AAEA,YAAM,gBAAgB,MAAMD,IAAG,aAAa,MAAM,IAAIE,mBAAAA,mBAAmBF,IAAG,SAAS,CAAC;AACtF,YAAM,cAAc,OAAO,kBAAkB;AAC7C,YAAM,kBAAkBG,sBAAAA,sBAAsB,cAAc,aAAa,SAAS,OAAO,oBAAoB;AAC7G,YAAM,yBAAyB,YAAY,OAAO,CAAA,OAAM,GAAG,OAAO,WAAW,MAAM,GAAG,+BAA+B,IAAI;AACzH,YAAM,eAAe,CAAC,eAAe,CAAC,mBAAmB,uBAAuB,WAAW;AAC3F,UAAI,cAAc;AACjB,eAAO,IAAIF,SAAAA;AAAAA,UACV;AAAA,UACA;AAAA,QAAA;AAAA,MAEF;AAEA,YAAMD,IAAG,WAAW,QAAQ,IAAII,wCAAAA,wCAAwC,OAAO,IAAI,WAAW,EAAE,CAAC;AACjG,aAAO,IAAIC,SAAAA,WAAW,EAAE,MAAM,WAAW,sBAAsB;AAAA,IAChE,CAAC;AAAA,EACF;AACD;;"}
@@ -22,6 +22,12 @@ class PersonIdentityProviderManager {
22
22
  if (!connection) {
23
23
  return new ResponseError("NOT_FOUND", `You are not connected to the identity provider connection "${connectionId}".`);
24
24
  }
25
+ if (connection.identityProviderDisableLocalAuthentication) {
26
+ return new ResponseError(
27
+ "IDP_REQUIRED",
28
+ `The identity provider "${connection.identityProviderSlug}" is the only way you may sign in; its connection cannot be removed.`
29
+ );
30
+ }
25
31
  const configuration = await db2.queryHandler.fetch(new ConfigurationQuery(db2.providers));
26
32
  const hasPassword = person.password_hash !== null;
27
33
  const hasPasswordless = isPasswordlessEnabled(configuration.passwordless.enabled, person.passwordless_enabled);
@@ -1 +1 @@
1
- {"version":3,"file":"PersonIdentityProviderManager.js","sources":["../../../../../../../packages/engine-tenant-api/src/model/service/idp/PersonIdentityProviderManager.ts"],"sourcesContent":["import { Response, ResponseError, ResponseOk } from '../../utils/Response.js'\nimport { DisconnectIdpErrorCode } from '../../../schema/index.js'\nimport { DatabaseContext } from '../../utils/index.js'\nimport { ConfigurationQuery, PersonIdentityProviderRow, PersonIdentityProvidersQuery, PersonRow } from '../../queries/index.js'\nimport { DisconnectPersonIdentityProviderCommand } from '../../commands/idp/DisconnectPersonIdentityProviderCommand.js'\nimport { isPasswordlessEnabled } from '../helpers/isPasswordlessEnabled.js'\n\n/**\n * Manages the external IdP connections of the *currently authenticated* person.\n *\n * Lock-out policy: a person may always disconnect a provider as long as it does\n * not remove their last remaining sign-in method. A connection is refused only\n * when, after removal, the person would have no password, no passwordless\n * sign-in enabled and no other *usable* IdP connection left — which would lock\n * them out of their account entirely. Whether passwordless counts is decided by\n * the same tenant policy the sign-in flow applies, and connections to disabled\n * providers are not counted as a usable fallback (they cannot sign in).\n */\nexport class PersonIdentityProviderManager {\n\tpublic async listPersonIdentityProviders(db: DatabaseContext, personId: string): Promise<PersonIdentityProviderRow[]> {\n\t\treturn await db.queryHandler.fetch(new PersonIdentityProvidersQuery(personId))\n\t}\n\n\tpublic async disconnectIdentityProvider(db: DatabaseContext, person: PersonRow, connectionId: string): Promise<DisconnectIDPResponse> {\n\t\treturn await db.transaction(async db => {\n\t\t\tconst connections = await db.queryHandler.fetch(new PersonIdentityProvidersQuery(person.id))\n\t\t\tconst connection = connections.find(it => it.id === connectionId)\n\t\t\tif (!connection) {\n\t\t\t\treturn new ResponseError('NOT_FOUND', `You are not connected to the identity provider connection \"${connectionId}\".`)\n\t\t\t}\n\n\t\t\tconst configuration = await db.queryHandler.fetch(new ConfigurationQuery(db.providers))\n\t\t\tconst hasPassword = person.password_hash !== null\n\t\t\tconst hasPasswordless = isPasswordlessEnabled(configuration.passwordless.enabled, person.passwordless_enabled)\n\t\t\tconst otherUsableConnections = connections.filter(it => it.id !== connection.id && it.identityProviderDisabledAt === null)\n\t\t\tconst wouldLockOut = !hasPassword && !hasPasswordless && otherUsableConnections.length === 0\n\t\t\tif (wouldLockOut) {\n\t\t\t\treturn new ResponseError(\n\t\t\t\t\t'LAST_AUTH_METHOD',\n\t\t\t\t\t'Cannot disconnect the only remaining sign-in method. Set up a password or another sign-in method first.',\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tawait db.commandBus.execute(new DisconnectPersonIdentityProviderCommand(person.id, connection.id))\n\t\t\treturn new ResponseOk({ slug: connection.identityProviderSlug })\n\t\t})\n\t}\n}\n\nexport interface DisconnectIDPResult {\n\treadonly slug: string\n}\n\nexport type DisconnectIDPResponse = Response<DisconnectIDPResult, DisconnectIdpErrorCode>\n"],"names":["db"],"mappings":";;;;;;;;;;;;;AAkBO,MAAM,8BAA8B;AAAA,EAC1C,MAAa,4BAA4B,IAAqB,UAAwD;AACrH,WAAO,MAAM,GAAG,aAAa,MAAM,IAAI,6BAA6B,QAAQ,CAAC;AAAA,EAC9E;AAAA,EAEA,MAAa,2BAA2B,IAAqB,QAAmB,cAAsD;AACrI,WAAO,MAAM,GAAG,YAAY,OAAMA,QAAM;AACvC,YAAM,cAAc,MAAMA,IAAG,aAAa,MAAM,IAAI,6BAA6B,OAAO,EAAE,CAAC;AAC3F,YAAM,aAAa,YAAY,KAAK,CAAA,OAAM,GAAG,OAAO,YAAY;AAChE,UAAI,CAAC,YAAY;AAChB,eAAO,IAAI,cAAc,aAAa,8DAA8D,YAAY,IAAI;AAAA,MACrH;AAEA,YAAM,gBAAgB,MAAMA,IAAG,aAAa,MAAM,IAAI,mBAAmBA,IAAG,SAAS,CAAC;AACtF,YAAM,cAAc,OAAO,kBAAkB;AAC7C,YAAM,kBAAkB,sBAAsB,cAAc,aAAa,SAAS,OAAO,oBAAoB;AAC7G,YAAM,yBAAyB,YAAY,OAAO,CAAA,OAAM,GAAG,OAAO,WAAW,MAAM,GAAG,+BAA+B,IAAI;AACzH,YAAM,eAAe,CAAC,eAAe,CAAC,mBAAmB,uBAAuB,WAAW;AAC3F,UAAI,cAAc;AACjB,eAAO,IAAI;AAAA,UACV;AAAA,UACA;AAAA,QAAA;AAAA,MAEF;AAEA,YAAMA,IAAG,WAAW,QAAQ,IAAI,wCAAwC,OAAO,IAAI,WAAW,EAAE,CAAC;AACjG,aAAO,IAAI,WAAW,EAAE,MAAM,WAAW,sBAAsB;AAAA,IAChE,CAAC;AAAA,EACF;AACD;"}
1
+ {"version":3,"file":"PersonIdentityProviderManager.js","sources":["../../../../../../../packages/engine-tenant-api/src/model/service/idp/PersonIdentityProviderManager.ts"],"sourcesContent":["import { Response, ResponseError, ResponseOk } from '../../utils/Response.js'\nimport { DisconnectIdpErrorCode } from '../../../schema/index.js'\nimport { DatabaseContext } from '../../utils/index.js'\nimport { ConfigurationQuery, PersonIdentityProviderRow, PersonIdentityProvidersQuery, PersonRow } from '../../queries/index.js'\nimport { DisconnectPersonIdentityProviderCommand } from '../../commands/idp/DisconnectPersonIdentityProviderCommand.js'\nimport { isPasswordlessEnabled } from '../helpers/isPasswordlessEnabled.js'\n\n/**\n * Manages the external IdP connections of the *currently authenticated* person.\n *\n * Lock-out policy: a person may always disconnect a provider as long as it does\n * not remove their last remaining sign-in method. A connection is refused only\n * when, after removal, the person would have no password, no passwordless\n * sign-in enabled and no other *usable* IdP connection left — which would lock\n * them out of their account entirely. Whether passwordless counts is decided by\n * the same tenant policy the sign-in flow applies, and connections to disabled\n * providers are not counted as a usable fallback (they cannot sign in).\n */\nexport class PersonIdentityProviderManager {\n\tpublic async listPersonIdentityProviders(db: DatabaseContext, personId: string): Promise<PersonIdentityProviderRow[]> {\n\t\treturn await db.queryHandler.fetch(new PersonIdentityProvidersQuery(personId))\n\t}\n\n\tpublic async disconnectIdentityProvider(db: DatabaseContext, person: PersonRow, connectionId: string): Promise<DisconnectIDPResponse> {\n\t\treturn await db.transaction(async db => {\n\t\t\tconst connections = await db.queryHandler.fetch(new PersonIdentityProvidersQuery(person.id))\n\t\t\tconst connection = connections.find(it => it.id === connectionId)\n\t\t\tif (!connection) {\n\t\t\t\treturn new ResponseError('NOT_FOUND', `You are not connected to the identity provider connection \"${connectionId}\".`)\n\t\t\t}\n\n\t\t\t// The link is what binds the person to the provider's policy, so they cannot remove it themselves.\n\t\t\tif (connection.identityProviderDisableLocalAuthentication) {\n\t\t\t\treturn new ResponseError(\n\t\t\t\t\t'IDP_REQUIRED',\n\t\t\t\t\t`The identity provider \"${connection.identityProviderSlug}\" is the only way you may sign in; its connection cannot be removed.`,\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tconst configuration = await db.queryHandler.fetch(new ConfigurationQuery(db.providers))\n\t\t\tconst hasPassword = person.password_hash !== null\n\t\t\tconst hasPasswordless = isPasswordlessEnabled(configuration.passwordless.enabled, person.passwordless_enabled)\n\t\t\tconst otherUsableConnections = connections.filter(it => it.id !== connection.id && it.identityProviderDisabledAt === null)\n\t\t\tconst wouldLockOut = !hasPassword && !hasPasswordless && otherUsableConnections.length === 0\n\t\t\tif (wouldLockOut) {\n\t\t\t\treturn new ResponseError(\n\t\t\t\t\t'LAST_AUTH_METHOD',\n\t\t\t\t\t'Cannot disconnect the only remaining sign-in method. Set up a password or another sign-in method first.',\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tawait db.commandBus.execute(new DisconnectPersonIdentityProviderCommand(person.id, connection.id))\n\t\t\treturn new ResponseOk({ slug: connection.identityProviderSlug })\n\t\t})\n\t}\n}\n\nexport interface DisconnectIDPResult {\n\treadonly slug: string\n}\n\nexport type DisconnectIDPResponse = Response<DisconnectIDPResult, DisconnectIdpErrorCode>\n"],"names":["db"],"mappings":";;;;;;;;;;;;;AAkBO,MAAM,8BAA8B;AAAA,EAC1C,MAAa,4BAA4B,IAAqB,UAAwD;AACrH,WAAO,MAAM,GAAG,aAAa,MAAM,IAAI,6BAA6B,QAAQ,CAAC;AAAA,EAC9E;AAAA,EAEA,MAAa,2BAA2B,IAAqB,QAAmB,cAAsD;AACrI,WAAO,MAAM,GAAG,YAAY,OAAMA,QAAM;AACvC,YAAM,cAAc,MAAMA,IAAG,aAAa,MAAM,IAAI,6BAA6B,OAAO,EAAE,CAAC;AAC3F,YAAM,aAAa,YAAY,KAAK,CAAA,OAAM,GAAG,OAAO,YAAY;AAChE,UAAI,CAAC,YAAY;AAChB,eAAO,IAAI,cAAc,aAAa,8DAA8D,YAAY,IAAI;AAAA,MACrH;AAGA,UAAI,WAAW,4CAA4C;AAC1D,eAAO,IAAI;AAAA,UACV;AAAA,UACA,0BAA0B,WAAW,oBAAoB;AAAA,QAAA;AAAA,MAE3D;AAEA,YAAM,gBAAgB,MAAMA,IAAG,aAAa,MAAM,IAAI,mBAAmBA,IAAG,SAAS,CAAC;AACtF,YAAM,cAAc,OAAO,kBAAkB;AAC7C,YAAM,kBAAkB,sBAAsB,cAAc,aAAa,SAAS,OAAO,oBAAoB;AAC7G,YAAM,yBAAyB,YAAY,OAAO,CAAA,OAAM,GAAG,OAAO,WAAW,MAAM,GAAG,+BAA+B,IAAI;AACzH,YAAM,eAAe,CAAC,eAAe,CAAC,mBAAmB,uBAAuB,WAAW;AAC3F,UAAI,cAAc;AACjB,eAAO,IAAI;AAAA,UACV;AAAA,UACA;AAAA,QAAA;AAAA,MAEF;AAEA,YAAMA,IAAG,WAAW,QAAQ,IAAI,wCAAwC,OAAO,IAAI,WAAW,EAAE,CAAC;AACjG,aAAO,IAAI,WAAW,EAAE,MAAM,WAAW,sBAAsB;AAAA,IAChE,CAAC;AAAA,EACF;AACD;"}
@@ -43,7 +43,8 @@ class AddIDPMutationResolver {
43
43
  exclusive: args.options?.exclusive ?? false,
44
44
  initReturnsConfig: args.options?.initReturnsConfig ?? false,
45
45
  requireVerifiedEmail: args.options?.requireVerifiedEmail ?? false,
46
- assumeEmailVerified: args.options?.assumeEmailVerified ?? false
46
+ assumeEmailVerified: args.options?.assumeEmailVerified ?? false,
47
+ disableLocalAuthentication: args.options?.disableLocalAuthentication ?? false
47
48
  }
48
49
  });
49
50
  if (!result.ok) {
@@ -1 +1 @@
1
- {"version":3,"file":"AddIDPMutationResolver.cjs","sources":["../../../../../../../packages/engine-tenant-api/src/resolvers/mutation/idp/AddIDPMutationResolver.ts"],"sourcesContent":["import { AddIdpResponse, MutationAddIdpArgs, MutationResolvers } from '../../../schema/index.js'\nimport { PermissionActions } from '../../../model/index.js'\nimport { IDPManager } from '../../../model/service/idp/IDPManager.js'\nimport { createErrorResponse } from '../../errorUtils.js'\nimport { TenantResolverContext } from '../../TenantResolverContext.js'\nimport { ResponseOk } from '../../../model/utils/Response.js'\nimport { IdentityProviderBySlugQuery } from '../../../model/queries/index.js'\nimport { idpRowToAuditSnapshot } from './audit.js'\n\nexport class AddIDPMutationResolver implements MutationResolvers {\n\tconstructor(private readonly idpManager: IDPManager) {\n\t}\n\n\tasync addIDP(parent: unknown, args: MutationAddIdpArgs, context: TenantResolverContext): Promise<AddIdpResponse> {\n\t\tawait context.requireAccess({\n\t\t\taction: PermissionActions.IDP_ADD,\n\t\t\tmessage: 'You are not allowed to add IDP',\n\t\t})\n\t\tconst result = await this.idpManager.addIDP(context.db, {\n\t\t\tconfiguration: args.configuration as any,\n\t\t\tslug: args.identityProvider,\n\t\t\ttype: args.type,\n\t\t\toptions: {\n\t\t\t\tautoSignUp: args.options?.autoSignUp ?? false,\n\t\t\t\texclusive: args.options?.exclusive ?? false,\n\t\t\t\tinitReturnsConfig: args.options?.initReturnsConfig ?? false,\n\t\t\t\trequireVerifiedEmail: args.options?.requireVerifiedEmail ?? false,\n\t\t\t\tassumeEmailVerified: args.options?.assumeEmailVerified ?? false,\n\t\t\t},\n\t\t})\n\t\tif (!result.ok) {\n\t\t\treturn createErrorResponse(result.error, result.errorMessage)\n\t\t}\n\n\t\tconst after = await context.db.queryHandler.fetch(new IdentityProviderBySlugQuery(args.identityProvider))\n\t\tawait context.logAuthAction({\n\t\t\ttype: 'idp_create',\n\t\t\tresponse: new ResponseOk(null),\n\t\t\teventData: {\n\t\t\t\tslug: args.identityProvider,\n\t\t\t\tbefore: null,\n\t\t\t\tafter: idpRowToAuditSnapshot(after),\n\t\t\t},\n\t\t})\n\n\t\treturn { ok: true }\n\t}\n}\n"],"names":["PermissionActions","createErrorResponse","IdentityProviderBySlugQuery","ResponseOk","idpRowToAuditSnapshot"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AASO,MAAM,uBAAoD;AAAA,EAChE,YAA6B,YAAwB;AAAxB,SAAA,aAAA;AAAA,EAC7B;AAAA,EAEA,MAAM,OAAO,QAAiB,MAA0B,SAAyD;AAChH,UAAM,QAAQ,cAAc;AAAA,MAC3B,QAAQA,kBAAAA,kBAAkB;AAAA,MAC1B,SAAS;AAAA,IAAA,CACT;AACD,UAAM,SAAS,MAAM,KAAK,WAAW,OAAO,QAAQ,IAAI;AAAA,MACvD,eAAe,KAAK;AAAA,MACpB,MAAM,KAAK;AAAA,MACX,MAAM,KAAK;AAAA,MACX,SAAS;AAAA,QACR,YAAY,KAAK,SAAS,cAAc;AAAA,QACxC,WAAW,KAAK,SAAS,aAAa;AAAA,QACtC,mBAAmB,KAAK,SAAS,qBAAqB;AAAA,QACtD,sBAAsB,KAAK,SAAS,wBAAwB;AAAA,QAC5D,qBAAqB,KAAK,SAAS,uBAAuB;AAAA,MAAA;AAAA,IAC3D,CACA;AACD,QAAI,CAAC,OAAO,IAAI;AACf,aAAOC,WAAAA,oBAAoB,OAAO,OAAO,OAAO,YAAY;AAAA,IAC7D;AAEA,UAAM,QAAQ,MAAM,QAAQ,GAAG,aAAa,MAAM,IAAIC,4BAAAA,4BAA4B,KAAK,gBAAgB,CAAC;AACxG,UAAM,QAAQ,cAAc;AAAA,MAC3B,MAAM;AAAA,MACN,UAAU,IAAIC,SAAAA,WAAW,IAAI;AAAA,MAC7B,WAAW;AAAA,QACV,MAAM,KAAK;AAAA,QACX,QAAQ;AAAA,QACR,OAAOC,MAAAA,sBAAsB,KAAK;AAAA,MAAA;AAAA,IACnC,CACA;AAED,WAAO,EAAE,IAAI,KAAA;AAAA,EACd;AACD;;"}
1
+ {"version":3,"file":"AddIDPMutationResolver.cjs","sources":["../../../../../../../packages/engine-tenant-api/src/resolvers/mutation/idp/AddIDPMutationResolver.ts"],"sourcesContent":["import { AddIdpResponse, MutationAddIdpArgs, MutationResolvers } from '../../../schema/index.js'\nimport { PermissionActions } from '../../../model/index.js'\nimport { IDPManager } from '../../../model/service/idp/IDPManager.js'\nimport { createErrorResponse } from '../../errorUtils.js'\nimport { TenantResolverContext } from '../../TenantResolverContext.js'\nimport { ResponseOk } from '../../../model/utils/Response.js'\nimport { IdentityProviderBySlugQuery } from '../../../model/queries/index.js'\nimport { idpRowToAuditSnapshot } from './audit.js'\n\nexport class AddIDPMutationResolver implements MutationResolvers {\n\tconstructor(private readonly idpManager: IDPManager) {\n\t}\n\n\tasync addIDP(parent: unknown, args: MutationAddIdpArgs, context: TenantResolverContext): Promise<AddIdpResponse> {\n\t\tawait context.requireAccess({\n\t\t\taction: PermissionActions.IDP_ADD,\n\t\t\tmessage: 'You are not allowed to add IDP',\n\t\t})\n\t\tconst result = await this.idpManager.addIDP(context.db, {\n\t\t\tconfiguration: args.configuration as any,\n\t\t\tslug: args.identityProvider,\n\t\t\ttype: args.type,\n\t\t\toptions: {\n\t\t\t\tautoSignUp: args.options?.autoSignUp ?? false,\n\t\t\t\texclusive: args.options?.exclusive ?? false,\n\t\t\t\tinitReturnsConfig: args.options?.initReturnsConfig ?? false,\n\t\t\t\trequireVerifiedEmail: args.options?.requireVerifiedEmail ?? false,\n\t\t\t\tassumeEmailVerified: args.options?.assumeEmailVerified ?? false,\n\t\t\t\tdisableLocalAuthentication: args.options?.disableLocalAuthentication ?? false,\n\t\t\t},\n\t\t})\n\t\tif (!result.ok) {\n\t\t\treturn createErrorResponse(result.error, result.errorMessage)\n\t\t}\n\n\t\tconst after = await context.db.queryHandler.fetch(new IdentityProviderBySlugQuery(args.identityProvider))\n\t\tawait context.logAuthAction({\n\t\t\ttype: 'idp_create',\n\t\t\tresponse: new ResponseOk(null),\n\t\t\teventData: {\n\t\t\t\tslug: args.identityProvider,\n\t\t\t\tbefore: null,\n\t\t\t\tafter: idpRowToAuditSnapshot(after),\n\t\t\t},\n\t\t})\n\n\t\treturn { ok: true }\n\t}\n}\n"],"names":["PermissionActions","createErrorResponse","IdentityProviderBySlugQuery","ResponseOk","idpRowToAuditSnapshot"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AASO,MAAM,uBAAoD;AAAA,EAChE,YAA6B,YAAwB;AAAxB,SAAA,aAAA;AAAA,EAC7B;AAAA,EAEA,MAAM,OAAO,QAAiB,MAA0B,SAAyD;AAChH,UAAM,QAAQ,cAAc;AAAA,MAC3B,QAAQA,kBAAAA,kBAAkB;AAAA,MAC1B,SAAS;AAAA,IAAA,CACT;AACD,UAAM,SAAS,MAAM,KAAK,WAAW,OAAO,QAAQ,IAAI;AAAA,MACvD,eAAe,KAAK;AAAA,MACpB,MAAM,KAAK;AAAA,MACX,MAAM,KAAK;AAAA,MACX,SAAS;AAAA,QACR,YAAY,KAAK,SAAS,cAAc;AAAA,QACxC,WAAW,KAAK,SAAS,aAAa;AAAA,QACtC,mBAAmB,KAAK,SAAS,qBAAqB;AAAA,QACtD,sBAAsB,KAAK,SAAS,wBAAwB;AAAA,QAC5D,qBAAqB,KAAK,SAAS,uBAAuB;AAAA,QAC1D,4BAA4B,KAAK,SAAS,8BAA8B;AAAA,MAAA;AAAA,IACzE,CACA;AACD,QAAI,CAAC,OAAO,IAAI;AACf,aAAOC,WAAAA,oBAAoB,OAAO,OAAO,OAAO,YAAY;AAAA,IAC7D;AAEA,UAAM,QAAQ,MAAM,QAAQ,GAAG,aAAa,MAAM,IAAIC,4BAAAA,4BAA4B,KAAK,gBAAgB,CAAC;AACxG,UAAM,QAAQ,cAAc;AAAA,MAC3B,MAAM;AAAA,MACN,UAAU,IAAIC,SAAAA,WAAW,IAAI;AAAA,MAC7B,WAAW;AAAA,QACV,MAAM,KAAK;AAAA,QACX,QAAQ;AAAA,QACR,OAAOC,MAAAA,sBAAsB,KAAK;AAAA,MAAA;AAAA,IACnC,CACA;AAED,WAAO,EAAE,IAAI,KAAA;AAAA,EACd;AACD;;"}
@@ -41,7 +41,8 @@ class AddIDPMutationResolver {
41
41
  exclusive: args.options?.exclusive ?? false,
42
42
  initReturnsConfig: args.options?.initReturnsConfig ?? false,
43
43
  requireVerifiedEmail: args.options?.requireVerifiedEmail ?? false,
44
- assumeEmailVerified: args.options?.assumeEmailVerified ?? false
44
+ assumeEmailVerified: args.options?.assumeEmailVerified ?? false,
45
+ disableLocalAuthentication: args.options?.disableLocalAuthentication ?? false
45
46
  }
46
47
  });
47
48
  if (!result.ok) {
@@ -1 +1 @@
1
- {"version":3,"file":"AddIDPMutationResolver.js","sources":["../../../../../../../packages/engine-tenant-api/src/resolvers/mutation/idp/AddIDPMutationResolver.ts"],"sourcesContent":["import { AddIdpResponse, MutationAddIdpArgs, MutationResolvers } from '../../../schema/index.js'\nimport { PermissionActions } from '../../../model/index.js'\nimport { IDPManager } from '../../../model/service/idp/IDPManager.js'\nimport { createErrorResponse } from '../../errorUtils.js'\nimport { TenantResolverContext } from '../../TenantResolverContext.js'\nimport { ResponseOk } from '../../../model/utils/Response.js'\nimport { IdentityProviderBySlugQuery } from '../../../model/queries/index.js'\nimport { idpRowToAuditSnapshot } from './audit.js'\n\nexport class AddIDPMutationResolver implements MutationResolvers {\n\tconstructor(private readonly idpManager: IDPManager) {\n\t}\n\n\tasync addIDP(parent: unknown, args: MutationAddIdpArgs, context: TenantResolverContext): Promise<AddIdpResponse> {\n\t\tawait context.requireAccess({\n\t\t\taction: PermissionActions.IDP_ADD,\n\t\t\tmessage: 'You are not allowed to add IDP',\n\t\t})\n\t\tconst result = await this.idpManager.addIDP(context.db, {\n\t\t\tconfiguration: args.configuration as any,\n\t\t\tslug: args.identityProvider,\n\t\t\ttype: args.type,\n\t\t\toptions: {\n\t\t\t\tautoSignUp: args.options?.autoSignUp ?? false,\n\t\t\t\texclusive: args.options?.exclusive ?? false,\n\t\t\t\tinitReturnsConfig: args.options?.initReturnsConfig ?? false,\n\t\t\t\trequireVerifiedEmail: args.options?.requireVerifiedEmail ?? false,\n\t\t\t\tassumeEmailVerified: args.options?.assumeEmailVerified ?? false,\n\t\t\t},\n\t\t})\n\t\tif (!result.ok) {\n\t\t\treturn createErrorResponse(result.error, result.errorMessage)\n\t\t}\n\n\t\tconst after = await context.db.queryHandler.fetch(new IdentityProviderBySlugQuery(args.identityProvider))\n\t\tawait context.logAuthAction({\n\t\t\ttype: 'idp_create',\n\t\t\tresponse: new ResponseOk(null),\n\t\t\teventData: {\n\t\t\t\tslug: args.identityProvider,\n\t\t\t\tbefore: null,\n\t\t\t\tafter: idpRowToAuditSnapshot(after),\n\t\t\t},\n\t\t})\n\n\t\treturn { ok: true }\n\t}\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AASO,MAAM,uBAAoD;AAAA,EAChE,YAA6B,YAAwB;AAAxB,SAAA,aAAA;AAAA,EAC7B;AAAA,EAEA,MAAM,OAAO,QAAiB,MAA0B,SAAyD;AAChH,UAAM,QAAQ,cAAc;AAAA,MAC3B,QAAQ,kBAAkB;AAAA,MAC1B,SAAS;AAAA,IAAA,CACT;AACD,UAAM,SAAS,MAAM,KAAK,WAAW,OAAO,QAAQ,IAAI;AAAA,MACvD,eAAe,KAAK;AAAA,MACpB,MAAM,KAAK;AAAA,MACX,MAAM,KAAK;AAAA,MACX,SAAS;AAAA,QACR,YAAY,KAAK,SAAS,cAAc;AAAA,QACxC,WAAW,KAAK,SAAS,aAAa;AAAA,QACtC,mBAAmB,KAAK,SAAS,qBAAqB;AAAA,QACtD,sBAAsB,KAAK,SAAS,wBAAwB;AAAA,QAC5D,qBAAqB,KAAK,SAAS,uBAAuB;AAAA,MAAA;AAAA,IAC3D,CACA;AACD,QAAI,CAAC,OAAO,IAAI;AACf,aAAO,oBAAoB,OAAO,OAAO,OAAO,YAAY;AAAA,IAC7D;AAEA,UAAM,QAAQ,MAAM,QAAQ,GAAG,aAAa,MAAM,IAAI,4BAA4B,KAAK,gBAAgB,CAAC;AACxG,UAAM,QAAQ,cAAc;AAAA,MAC3B,MAAM;AAAA,MACN,UAAU,IAAI,WAAW,IAAI;AAAA,MAC7B,WAAW;AAAA,QACV,MAAM,KAAK;AAAA,QACX,QAAQ;AAAA,QACR,OAAO,sBAAsB,KAAK;AAAA,MAAA;AAAA,IACnC,CACA;AAED,WAAO,EAAE,IAAI,KAAA;AAAA,EACd;AACD;"}
1
+ {"version":3,"file":"AddIDPMutationResolver.js","sources":["../../../../../../../packages/engine-tenant-api/src/resolvers/mutation/idp/AddIDPMutationResolver.ts"],"sourcesContent":["import { AddIdpResponse, MutationAddIdpArgs, MutationResolvers } from '../../../schema/index.js'\nimport { PermissionActions } from '../../../model/index.js'\nimport { IDPManager } from '../../../model/service/idp/IDPManager.js'\nimport { createErrorResponse } from '../../errorUtils.js'\nimport { TenantResolverContext } from '../../TenantResolverContext.js'\nimport { ResponseOk } from '../../../model/utils/Response.js'\nimport { IdentityProviderBySlugQuery } from '../../../model/queries/index.js'\nimport { idpRowToAuditSnapshot } from './audit.js'\n\nexport class AddIDPMutationResolver implements MutationResolvers {\n\tconstructor(private readonly idpManager: IDPManager) {\n\t}\n\n\tasync addIDP(parent: unknown, args: MutationAddIdpArgs, context: TenantResolverContext): Promise<AddIdpResponse> {\n\t\tawait context.requireAccess({\n\t\t\taction: PermissionActions.IDP_ADD,\n\t\t\tmessage: 'You are not allowed to add IDP',\n\t\t})\n\t\tconst result = await this.idpManager.addIDP(context.db, {\n\t\t\tconfiguration: args.configuration as any,\n\t\t\tslug: args.identityProvider,\n\t\t\ttype: args.type,\n\t\t\toptions: {\n\t\t\t\tautoSignUp: args.options?.autoSignUp ?? false,\n\t\t\t\texclusive: args.options?.exclusive ?? false,\n\t\t\t\tinitReturnsConfig: args.options?.initReturnsConfig ?? false,\n\t\t\t\trequireVerifiedEmail: args.options?.requireVerifiedEmail ?? false,\n\t\t\t\tassumeEmailVerified: args.options?.assumeEmailVerified ?? false,\n\t\t\t\tdisableLocalAuthentication: args.options?.disableLocalAuthentication ?? false,\n\t\t\t},\n\t\t})\n\t\tif (!result.ok) {\n\t\t\treturn createErrorResponse(result.error, result.errorMessage)\n\t\t}\n\n\t\tconst after = await context.db.queryHandler.fetch(new IdentityProviderBySlugQuery(args.identityProvider))\n\t\tawait context.logAuthAction({\n\t\t\ttype: 'idp_create',\n\t\t\tresponse: new ResponseOk(null),\n\t\t\teventData: {\n\t\t\t\tslug: args.identityProvider,\n\t\t\t\tbefore: null,\n\t\t\t\tafter: idpRowToAuditSnapshot(after),\n\t\t\t},\n\t\t})\n\n\t\treturn { ok: true }\n\t}\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AASO,MAAM,uBAAoD;AAAA,EAChE,YAA6B,YAAwB;AAAxB,SAAA,aAAA;AAAA,EAC7B;AAAA,EAEA,MAAM,OAAO,QAAiB,MAA0B,SAAyD;AAChH,UAAM,QAAQ,cAAc;AAAA,MAC3B,QAAQ,kBAAkB;AAAA,MAC1B,SAAS;AAAA,IAAA,CACT;AACD,UAAM,SAAS,MAAM,KAAK,WAAW,OAAO,QAAQ,IAAI;AAAA,MACvD,eAAe,KAAK;AAAA,MACpB,MAAM,KAAK;AAAA,MACX,MAAM,KAAK;AAAA,MACX,SAAS;AAAA,QACR,YAAY,KAAK,SAAS,cAAc;AAAA,QACxC,WAAW,KAAK,SAAS,aAAa;AAAA,QACtC,mBAAmB,KAAK,SAAS,qBAAqB;AAAA,QACtD,sBAAsB,KAAK,SAAS,wBAAwB;AAAA,QAC5D,qBAAqB,KAAK,SAAS,uBAAuB;AAAA,QAC1D,4BAA4B,KAAK,SAAS,8BAA8B;AAAA,MAAA;AAAA,IACzE,CACA;AACD,QAAI,CAAC,OAAO,IAAI;AACf,aAAO,oBAAoB,OAAO,OAAO,OAAO,YAAY;AAAA,IAC7D;AAEA,UAAM,QAAQ,MAAM,QAAQ,GAAG,aAAa,MAAM,IAAI,4BAA4B,KAAK,gBAAgB,CAAC;AACxG,UAAM,QAAQ,cAAc;AAAA,MAC3B,MAAM;AAAA,MACN,UAAU,IAAI,WAAW,IAAI;AAAA,MAC7B,WAAW;AAAA,QACV,MAAM,KAAK;AAAA,QACX,QAAQ;AAAA,QACR,OAAO,sBAAsB,KAAK;AAAA,MAAA;AAAA,IACnC,CACA;AAED,WAAO,EAAE,IAAI,KAAA;AAAA,EACd;AACD;"}
@@ -43,7 +43,8 @@ class UpdateIDPMutationResolver {
43
43
  exclusive: args.options?.exclusive ?? void 0,
44
44
  initReturnsConfig: args.options?.initReturnsConfig ?? void 0,
45
45
  requireVerifiedEmail: args.options?.requireVerifiedEmail ?? void 0,
46
- assumeEmailVerified: args.options?.assumeEmailVerified ?? void 0
46
+ assumeEmailVerified: args.options?.assumeEmailVerified ?? void 0,
47
+ disableLocalAuthentication: args.options?.disableLocalAuthentication ?? void 0
47
48
  }
48
49
  }, args.mergeConfiguration ?? false);
49
50
  if (!result.ok) {
@@ -1 +1 @@
1
- {"version":3,"file":"UpdateIDPMutationResolver.cjs","sources":["../../../../../../../packages/engine-tenant-api/src/resolvers/mutation/idp/UpdateIDPMutationResolver.ts"],"sourcesContent":["import { MutationResolvers, MutationUpdateIdpArgs, UpdateIdpResponse } from '../../../schema/index.js'\nimport { PermissionActions } from '../../../model/index.js'\nimport { IDPManager } from '../../../model/service/idp/IDPManager.js'\nimport { createErrorResponse } from '../../errorUtils.js'\nimport { TenantResolverContext } from '../../TenantResolverContext.js'\nimport { ResponseOk } from '../../../model/utils/Response.js'\nimport { IdentityProviderBySlugQuery } from '../../../model/queries/index.js'\nimport { idpRowToAuditSnapshot } from './audit.js'\n\nexport class UpdateIDPMutationResolver implements MutationResolvers {\n\tconstructor(private readonly idpManager: IDPManager) {\n\t}\n\n\tasync updateIDP(parent: any, args: MutationUpdateIdpArgs, context: TenantResolverContext): Promise<UpdateIdpResponse> {\n\t\tawait context.requireAccess({\n\t\t\taction: PermissionActions.IDP_UPDATE,\n\t\t\tmessage: 'You are not allowed to update IDP',\n\t\t})\n\t\tconst before = await context.db.queryHandler.fetch(new IdentityProviderBySlugQuery(args.identityProvider))\n\t\tconst result = await this.idpManager.updateIDP(context.db, args.identityProvider, {\n\t\t\tconfiguration: args.configuration as any,\n\t\t\ttype: args.type ?? undefined,\n\t\t\toptions: {\n\t\t\t\tautoSignUp: args.options?.autoSignUp ?? undefined,\n\t\t\t\texclusive: args.options?.exclusive ?? undefined,\n\t\t\t\tinitReturnsConfig: args.options?.initReturnsConfig ?? undefined,\n\t\t\t\trequireVerifiedEmail: args.options?.requireVerifiedEmail ?? undefined,\n\t\t\t\tassumeEmailVerified: args.options?.assumeEmailVerified ?? undefined,\n\t\t\t},\n\t\t}, args.mergeConfiguration ?? false)\n\t\tif (!result.ok) {\n\t\t\treturn createErrorResponse(result.error, result.errorMessage)\n\t\t}\n\n\t\tconst after = await context.db.queryHandler.fetch(new IdentityProviderBySlugQuery(args.identityProvider))\n\t\tawait context.logAuthAction({\n\t\t\ttype: 'idp_update',\n\t\t\tresponse: new ResponseOk(null),\n\t\t\teventData: {\n\t\t\t\tslug: args.identityProvider,\n\t\t\t\tbefore: idpRowToAuditSnapshot(before),\n\t\t\t\tafter: idpRowToAuditSnapshot(after),\n\t\t\t},\n\t\t})\n\n\t\treturn { ok: true }\n\t}\n}\n"],"names":["PermissionActions","IdentityProviderBySlugQuery","createErrorResponse","ResponseOk","idpRowToAuditSnapshot"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AASO,MAAM,0BAAuD;AAAA,EACnE,YAA6B,YAAwB;AAAxB,SAAA,aAAA;AAAA,EAC7B;AAAA,EAEA,MAAM,UAAU,QAAa,MAA6B,SAA4D;AACrH,UAAM,QAAQ,cAAc;AAAA,MAC3B,QAAQA,kBAAAA,kBAAkB;AAAA,MAC1B,SAAS;AAAA,IAAA,CACT;AACD,UAAM,SAAS,MAAM,QAAQ,GAAG,aAAa,MAAM,IAAIC,4BAAAA,4BAA4B,KAAK,gBAAgB,CAAC;AACzG,UAAM,SAAS,MAAM,KAAK,WAAW,UAAU,QAAQ,IAAI,KAAK,kBAAkB;AAAA,MACjF,eAAe,KAAK;AAAA,MACpB,MAAM,KAAK,QAAQ;AAAA,MACnB,SAAS;AAAA,QACR,YAAY,KAAK,SAAS,cAAc;AAAA,QACxC,WAAW,KAAK,SAAS,aAAa;AAAA,QACtC,mBAAmB,KAAK,SAAS,qBAAqB;AAAA,QACtD,sBAAsB,KAAK,SAAS,wBAAwB;AAAA,QAC5D,qBAAqB,KAAK,SAAS,uBAAuB;AAAA,MAAA;AAAA,IAC3D,GACE,KAAK,sBAAsB,KAAK;AACnC,QAAI,CAAC,OAAO,IAAI;AACf,aAAOC,WAAAA,oBAAoB,OAAO,OAAO,OAAO,YAAY;AAAA,IAC7D;AAEA,UAAM,QAAQ,MAAM,QAAQ,GAAG,aAAa,MAAM,IAAID,4BAAAA,4BAA4B,KAAK,gBAAgB,CAAC;AACxG,UAAM,QAAQ,cAAc;AAAA,MAC3B,MAAM;AAAA,MACN,UAAU,IAAIE,SAAAA,WAAW,IAAI;AAAA,MAC7B,WAAW;AAAA,QACV,MAAM,KAAK;AAAA,QACX,QAAQC,MAAAA,sBAAsB,MAAM;AAAA,QACpC,OAAOA,MAAAA,sBAAsB,KAAK;AAAA,MAAA;AAAA,IACnC,CACA;AAED,WAAO,EAAE,IAAI,KAAA;AAAA,EACd;AACD;;"}
1
+ {"version":3,"file":"UpdateIDPMutationResolver.cjs","sources":["../../../../../../../packages/engine-tenant-api/src/resolvers/mutation/idp/UpdateIDPMutationResolver.ts"],"sourcesContent":["import { MutationResolvers, MutationUpdateIdpArgs, UpdateIdpResponse } from '../../../schema/index.js'\nimport { PermissionActions } from '../../../model/index.js'\nimport { IDPManager } from '../../../model/service/idp/IDPManager.js'\nimport { createErrorResponse } from '../../errorUtils.js'\nimport { TenantResolverContext } from '../../TenantResolverContext.js'\nimport { ResponseOk } from '../../../model/utils/Response.js'\nimport { IdentityProviderBySlugQuery } from '../../../model/queries/index.js'\nimport { idpRowToAuditSnapshot } from './audit.js'\n\nexport class UpdateIDPMutationResolver implements MutationResolvers {\n\tconstructor(private readonly idpManager: IDPManager) {\n\t}\n\n\tasync updateIDP(parent: any, args: MutationUpdateIdpArgs, context: TenantResolverContext): Promise<UpdateIdpResponse> {\n\t\tawait context.requireAccess({\n\t\t\taction: PermissionActions.IDP_UPDATE,\n\t\t\tmessage: 'You are not allowed to update IDP',\n\t\t})\n\t\tconst before = await context.db.queryHandler.fetch(new IdentityProviderBySlugQuery(args.identityProvider))\n\t\tconst result = await this.idpManager.updateIDP(context.db, args.identityProvider, {\n\t\t\tconfiguration: args.configuration as any,\n\t\t\ttype: args.type ?? undefined,\n\t\t\toptions: {\n\t\t\t\tautoSignUp: args.options?.autoSignUp ?? undefined,\n\t\t\t\texclusive: args.options?.exclusive ?? undefined,\n\t\t\t\tinitReturnsConfig: args.options?.initReturnsConfig ?? undefined,\n\t\t\t\trequireVerifiedEmail: args.options?.requireVerifiedEmail ?? undefined,\n\t\t\t\tassumeEmailVerified: args.options?.assumeEmailVerified ?? undefined,\n\t\t\t\tdisableLocalAuthentication: args.options?.disableLocalAuthentication ?? undefined,\n\t\t\t},\n\t\t}, args.mergeConfiguration ?? false)\n\t\tif (!result.ok) {\n\t\t\treturn createErrorResponse(result.error, result.errorMessage)\n\t\t}\n\n\t\tconst after = await context.db.queryHandler.fetch(new IdentityProviderBySlugQuery(args.identityProvider))\n\t\tawait context.logAuthAction({\n\t\t\ttype: 'idp_update',\n\t\t\tresponse: new ResponseOk(null),\n\t\t\teventData: {\n\t\t\t\tslug: args.identityProvider,\n\t\t\t\tbefore: idpRowToAuditSnapshot(before),\n\t\t\t\tafter: idpRowToAuditSnapshot(after),\n\t\t\t},\n\t\t})\n\n\t\treturn { ok: true }\n\t}\n}\n"],"names":["PermissionActions","IdentityProviderBySlugQuery","createErrorResponse","ResponseOk","idpRowToAuditSnapshot"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AASO,MAAM,0BAAuD;AAAA,EACnE,YAA6B,YAAwB;AAAxB,SAAA,aAAA;AAAA,EAC7B;AAAA,EAEA,MAAM,UAAU,QAAa,MAA6B,SAA4D;AACrH,UAAM,QAAQ,cAAc;AAAA,MAC3B,QAAQA,kBAAAA,kBAAkB;AAAA,MAC1B,SAAS;AAAA,IAAA,CACT;AACD,UAAM,SAAS,MAAM,QAAQ,GAAG,aAAa,MAAM,IAAIC,4BAAAA,4BAA4B,KAAK,gBAAgB,CAAC;AACzG,UAAM,SAAS,MAAM,KAAK,WAAW,UAAU,QAAQ,IAAI,KAAK,kBAAkB;AAAA,MACjF,eAAe,KAAK;AAAA,MACpB,MAAM,KAAK,QAAQ;AAAA,MACnB,SAAS;AAAA,QACR,YAAY,KAAK,SAAS,cAAc;AAAA,QACxC,WAAW,KAAK,SAAS,aAAa;AAAA,QACtC,mBAAmB,KAAK,SAAS,qBAAqB;AAAA,QACtD,sBAAsB,KAAK,SAAS,wBAAwB;AAAA,QAC5D,qBAAqB,KAAK,SAAS,uBAAuB;AAAA,QAC1D,4BAA4B,KAAK,SAAS,8BAA8B;AAAA,MAAA;AAAA,IACzE,GACE,KAAK,sBAAsB,KAAK;AACnC,QAAI,CAAC,OAAO,IAAI;AACf,aAAOC,WAAAA,oBAAoB,OAAO,OAAO,OAAO,YAAY;AAAA,IAC7D;AAEA,UAAM,QAAQ,MAAM,QAAQ,GAAG,aAAa,MAAM,IAAID,4BAAAA,4BAA4B,KAAK,gBAAgB,CAAC;AACxG,UAAM,QAAQ,cAAc;AAAA,MAC3B,MAAM;AAAA,MACN,UAAU,IAAIE,SAAAA,WAAW,IAAI;AAAA,MAC7B,WAAW;AAAA,QACV,MAAM,KAAK;AAAA,QACX,QAAQC,MAAAA,sBAAsB,MAAM;AAAA,QACpC,OAAOA,MAAAA,sBAAsB,KAAK;AAAA,MAAA;AAAA,IACnC,CACA;AAED,WAAO,EAAE,IAAI,KAAA;AAAA,EACd;AACD;;"}
@@ -41,7 +41,8 @@ class UpdateIDPMutationResolver {
41
41
  exclusive: args.options?.exclusive ?? void 0,
42
42
  initReturnsConfig: args.options?.initReturnsConfig ?? void 0,
43
43
  requireVerifiedEmail: args.options?.requireVerifiedEmail ?? void 0,
44
- assumeEmailVerified: args.options?.assumeEmailVerified ?? void 0
44
+ assumeEmailVerified: args.options?.assumeEmailVerified ?? void 0,
45
+ disableLocalAuthentication: args.options?.disableLocalAuthentication ?? void 0
45
46
  }
46
47
  }, args.mergeConfiguration ?? false);
47
48
  if (!result.ok) {
@@ -1 +1 @@
1
- {"version":3,"file":"UpdateIDPMutationResolver.js","sources":["../../../../../../../packages/engine-tenant-api/src/resolvers/mutation/idp/UpdateIDPMutationResolver.ts"],"sourcesContent":["import { MutationResolvers, MutationUpdateIdpArgs, UpdateIdpResponse } from '../../../schema/index.js'\nimport { PermissionActions } from '../../../model/index.js'\nimport { IDPManager } from '../../../model/service/idp/IDPManager.js'\nimport { createErrorResponse } from '../../errorUtils.js'\nimport { TenantResolverContext } from '../../TenantResolverContext.js'\nimport { ResponseOk } from '../../../model/utils/Response.js'\nimport { IdentityProviderBySlugQuery } from '../../../model/queries/index.js'\nimport { idpRowToAuditSnapshot } from './audit.js'\n\nexport class UpdateIDPMutationResolver implements MutationResolvers {\n\tconstructor(private readonly idpManager: IDPManager) {\n\t}\n\n\tasync updateIDP(parent: any, args: MutationUpdateIdpArgs, context: TenantResolverContext): Promise<UpdateIdpResponse> {\n\t\tawait context.requireAccess({\n\t\t\taction: PermissionActions.IDP_UPDATE,\n\t\t\tmessage: 'You are not allowed to update IDP',\n\t\t})\n\t\tconst before = await context.db.queryHandler.fetch(new IdentityProviderBySlugQuery(args.identityProvider))\n\t\tconst result = await this.idpManager.updateIDP(context.db, args.identityProvider, {\n\t\t\tconfiguration: args.configuration as any,\n\t\t\ttype: args.type ?? undefined,\n\t\t\toptions: {\n\t\t\t\tautoSignUp: args.options?.autoSignUp ?? undefined,\n\t\t\t\texclusive: args.options?.exclusive ?? undefined,\n\t\t\t\tinitReturnsConfig: args.options?.initReturnsConfig ?? undefined,\n\t\t\t\trequireVerifiedEmail: args.options?.requireVerifiedEmail ?? undefined,\n\t\t\t\tassumeEmailVerified: args.options?.assumeEmailVerified ?? undefined,\n\t\t\t},\n\t\t}, args.mergeConfiguration ?? false)\n\t\tif (!result.ok) {\n\t\t\treturn createErrorResponse(result.error, result.errorMessage)\n\t\t}\n\n\t\tconst after = await context.db.queryHandler.fetch(new IdentityProviderBySlugQuery(args.identityProvider))\n\t\tawait context.logAuthAction({\n\t\t\ttype: 'idp_update',\n\t\t\tresponse: new ResponseOk(null),\n\t\t\teventData: {\n\t\t\t\tslug: args.identityProvider,\n\t\t\t\tbefore: idpRowToAuditSnapshot(before),\n\t\t\t\tafter: idpRowToAuditSnapshot(after),\n\t\t\t},\n\t\t})\n\n\t\treturn { ok: true }\n\t}\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AASO,MAAM,0BAAuD;AAAA,EACnE,YAA6B,YAAwB;AAAxB,SAAA,aAAA;AAAA,EAC7B;AAAA,EAEA,MAAM,UAAU,QAAa,MAA6B,SAA4D;AACrH,UAAM,QAAQ,cAAc;AAAA,MAC3B,QAAQ,kBAAkB;AAAA,MAC1B,SAAS;AAAA,IAAA,CACT;AACD,UAAM,SAAS,MAAM,QAAQ,GAAG,aAAa,MAAM,IAAI,4BAA4B,KAAK,gBAAgB,CAAC;AACzG,UAAM,SAAS,MAAM,KAAK,WAAW,UAAU,QAAQ,IAAI,KAAK,kBAAkB;AAAA,MACjF,eAAe,KAAK;AAAA,MACpB,MAAM,KAAK,QAAQ;AAAA,MACnB,SAAS;AAAA,QACR,YAAY,KAAK,SAAS,cAAc;AAAA,QACxC,WAAW,KAAK,SAAS,aAAa;AAAA,QACtC,mBAAmB,KAAK,SAAS,qBAAqB;AAAA,QACtD,sBAAsB,KAAK,SAAS,wBAAwB;AAAA,QAC5D,qBAAqB,KAAK,SAAS,uBAAuB;AAAA,MAAA;AAAA,IAC3D,GACE,KAAK,sBAAsB,KAAK;AACnC,QAAI,CAAC,OAAO,IAAI;AACf,aAAO,oBAAoB,OAAO,OAAO,OAAO,YAAY;AAAA,IAC7D;AAEA,UAAM,QAAQ,MAAM,QAAQ,GAAG,aAAa,MAAM,IAAI,4BAA4B,KAAK,gBAAgB,CAAC;AACxG,UAAM,QAAQ,cAAc;AAAA,MAC3B,MAAM;AAAA,MACN,UAAU,IAAI,WAAW,IAAI;AAAA,MAC7B,WAAW;AAAA,QACV,MAAM,KAAK;AAAA,QACX,QAAQ,sBAAsB,MAAM;AAAA,QACpC,OAAO,sBAAsB,KAAK;AAAA,MAAA;AAAA,IACnC,CACA;AAED,WAAO,EAAE,IAAI,KAAA;AAAA,EACd;AACD;"}
1
+ {"version":3,"file":"UpdateIDPMutationResolver.js","sources":["../../../../../../../packages/engine-tenant-api/src/resolvers/mutation/idp/UpdateIDPMutationResolver.ts"],"sourcesContent":["import { MutationResolvers, MutationUpdateIdpArgs, UpdateIdpResponse } from '../../../schema/index.js'\nimport { PermissionActions } from '../../../model/index.js'\nimport { IDPManager } from '../../../model/service/idp/IDPManager.js'\nimport { createErrorResponse } from '../../errorUtils.js'\nimport { TenantResolverContext } from '../../TenantResolverContext.js'\nimport { ResponseOk } from '../../../model/utils/Response.js'\nimport { IdentityProviderBySlugQuery } from '../../../model/queries/index.js'\nimport { idpRowToAuditSnapshot } from './audit.js'\n\nexport class UpdateIDPMutationResolver implements MutationResolvers {\n\tconstructor(private readonly idpManager: IDPManager) {\n\t}\n\n\tasync updateIDP(parent: any, args: MutationUpdateIdpArgs, context: TenantResolverContext): Promise<UpdateIdpResponse> {\n\t\tawait context.requireAccess({\n\t\t\taction: PermissionActions.IDP_UPDATE,\n\t\t\tmessage: 'You are not allowed to update IDP',\n\t\t})\n\t\tconst before = await context.db.queryHandler.fetch(new IdentityProviderBySlugQuery(args.identityProvider))\n\t\tconst result = await this.idpManager.updateIDP(context.db, args.identityProvider, {\n\t\t\tconfiguration: args.configuration as any,\n\t\t\ttype: args.type ?? undefined,\n\t\t\toptions: {\n\t\t\t\tautoSignUp: args.options?.autoSignUp ?? undefined,\n\t\t\t\texclusive: args.options?.exclusive ?? undefined,\n\t\t\t\tinitReturnsConfig: args.options?.initReturnsConfig ?? undefined,\n\t\t\t\trequireVerifiedEmail: args.options?.requireVerifiedEmail ?? undefined,\n\t\t\t\tassumeEmailVerified: args.options?.assumeEmailVerified ?? undefined,\n\t\t\t\tdisableLocalAuthentication: args.options?.disableLocalAuthentication ?? undefined,\n\t\t\t},\n\t\t}, args.mergeConfiguration ?? false)\n\t\tif (!result.ok) {\n\t\t\treturn createErrorResponse(result.error, result.errorMessage)\n\t\t}\n\n\t\tconst after = await context.db.queryHandler.fetch(new IdentityProviderBySlugQuery(args.identityProvider))\n\t\tawait context.logAuthAction({\n\t\t\ttype: 'idp_update',\n\t\t\tresponse: new ResponseOk(null),\n\t\t\teventData: {\n\t\t\t\tslug: args.identityProvider,\n\t\t\t\tbefore: idpRowToAuditSnapshot(before),\n\t\t\t\tafter: idpRowToAuditSnapshot(after),\n\t\t\t},\n\t\t})\n\n\t\treturn { ok: true }\n\t}\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AASO,MAAM,0BAAuD;AAAA,EACnE,YAA6B,YAAwB;AAAxB,SAAA,aAAA;AAAA,EAC7B;AAAA,EAEA,MAAM,UAAU,QAAa,MAA6B,SAA4D;AACrH,UAAM,QAAQ,cAAc;AAAA,MAC3B,QAAQ,kBAAkB;AAAA,MAC1B,SAAS;AAAA,IAAA,CACT;AACD,UAAM,SAAS,MAAM,QAAQ,GAAG,aAAa,MAAM,IAAI,4BAA4B,KAAK,gBAAgB,CAAC;AACzG,UAAM,SAAS,MAAM,KAAK,WAAW,UAAU,QAAQ,IAAI,KAAK,kBAAkB;AAAA,MACjF,eAAe,KAAK;AAAA,MACpB,MAAM,KAAK,QAAQ;AAAA,MACnB,SAAS;AAAA,QACR,YAAY,KAAK,SAAS,cAAc;AAAA,QACxC,WAAW,KAAK,SAAS,aAAa;AAAA,QACtC,mBAAmB,KAAK,SAAS,qBAAqB;AAAA,QACtD,sBAAsB,KAAK,SAAS,wBAAwB;AAAA,QAC5D,qBAAqB,KAAK,SAAS,uBAAuB;AAAA,QAC1D,4BAA4B,KAAK,SAAS,8BAA8B;AAAA,MAAA;AAAA,IACzE,GACE,KAAK,sBAAsB,KAAK;AACnC,QAAI,CAAC,OAAO,IAAI;AACf,aAAO,oBAAoB,OAAO,OAAO,OAAO,YAAY;AAAA,IAC7D;AAEA,UAAM,QAAQ,MAAM,QAAQ,GAAG,aAAa,MAAM,IAAI,4BAA4B,KAAK,gBAAgB,CAAC;AACxG,UAAM,QAAQ,cAAc;AAAA,MAC3B,MAAM;AAAA,MACN,UAAU,IAAI,WAAW,IAAI;AAAA,MAC7B,WAAW;AAAA,QACV,MAAM,KAAK;AAAA,QACX,QAAQ,sBAAsB,MAAM;AAAA,QACpC,OAAO,sBAAsB,KAAK;AAAA,MAAA;AAAA,IACnC,CACA;AAED,WAAO,EAAE,IAAI,KAAA;AAAA,EACd;AACD;"}
@@ -13,6 +13,7 @@ const idpRowToAuditSnapshot = (row) => {
13
13
  initReturnsConfig: row.initReturnsConfig,
14
14
  requireVerifiedEmail: row.requireVerifiedEmail,
15
15
  assumeEmailVerified: row.assumeEmailVerified,
16
+ disableLocalAuthentication: row.disableLocalAuthentication,
16
17
  configurationKeys: Object.keys(row.configuration).sort()
17
18
  };
18
19
  };
@@ -1 +1 @@
1
- {"version":3,"file":"audit.cjs","sources":["../../../../../../../packages/engine-tenant-api/src/resolvers/mutation/idp/audit.ts"],"sourcesContent":["import { JSONValue } from '@contember/schema'\nimport { IdentityProviderRow } from '../../../model/queries/idp/types.js'\n\nexport const idpRowToAuditSnapshot = (row: IdentityProviderRow | null): JSONValue => {\n\tif (!row) {\n\t\treturn null\n\t}\n\treturn {\n\t\tslug: row.slug,\n\t\ttype: row.type,\n\t\tdisabledAt: row.disabledAt ? row.disabledAt.toISOString() : null,\n\t\tautoSignUp: row.autoSignUp,\n\t\texclusive: row.exclusive,\n\t\tinitReturnsConfig: row.initReturnsConfig,\n\t\trequireVerifiedEmail: row.requireVerifiedEmail,\n\t\tassumeEmailVerified: row.assumeEmailVerified,\n\t\tconfigurationKeys: Object.keys(row.configuration).sort(),\n\t}\n}\n"],"names":[],"mappings":";;AAGO,MAAM,wBAAwB,CAAC,QAA+C;AACpF,MAAI,CAAC,KAAK;AACT,WAAO;AAAA,EACR;AACA,SAAO;AAAA,IACN,MAAM,IAAI;AAAA,IACV,MAAM,IAAI;AAAA,IACV,YAAY,IAAI,aAAa,IAAI,WAAW,gBAAgB;AAAA,IAC5D,YAAY,IAAI;AAAA,IAChB,WAAW,IAAI;AAAA,IACf,mBAAmB,IAAI;AAAA,IACvB,sBAAsB,IAAI;AAAA,IAC1B,qBAAqB,IAAI;AAAA,IACzB,mBAAmB,OAAO,KAAK,IAAI,aAAa,EAAE,KAAA;AAAA,EAAK;AAEzD;;"}
1
+ {"version":3,"file":"audit.cjs","sources":["../../../../../../../packages/engine-tenant-api/src/resolvers/mutation/idp/audit.ts"],"sourcesContent":["import { JSONValue } from '@contember/schema'\nimport { IdentityProviderRow } from '../../../model/queries/idp/types.js'\n\nexport const idpRowToAuditSnapshot = (row: IdentityProviderRow | null): JSONValue => {\n\tif (!row) {\n\t\treturn null\n\t}\n\treturn {\n\t\tslug: row.slug,\n\t\ttype: row.type,\n\t\tdisabledAt: row.disabledAt ? row.disabledAt.toISOString() : null,\n\t\tautoSignUp: row.autoSignUp,\n\t\texclusive: row.exclusive,\n\t\tinitReturnsConfig: row.initReturnsConfig,\n\t\trequireVerifiedEmail: row.requireVerifiedEmail,\n\t\tassumeEmailVerified: row.assumeEmailVerified,\n\t\tdisableLocalAuthentication: row.disableLocalAuthentication,\n\t\tconfigurationKeys: Object.keys(row.configuration).sort(),\n\t}\n}\n"],"names":[],"mappings":";;AAGO,MAAM,wBAAwB,CAAC,QAA+C;AACpF,MAAI,CAAC,KAAK;AACT,WAAO;AAAA,EACR;AACA,SAAO;AAAA,IACN,MAAM,IAAI;AAAA,IACV,MAAM,IAAI;AAAA,IACV,YAAY,IAAI,aAAa,IAAI,WAAW,gBAAgB;AAAA,IAC5D,YAAY,IAAI;AAAA,IAChB,WAAW,IAAI;AAAA,IACf,mBAAmB,IAAI;AAAA,IACvB,sBAAsB,IAAI;AAAA,IAC1B,qBAAqB,IAAI;AAAA,IACzB,4BAA4B,IAAI;AAAA,IAChC,mBAAmB,OAAO,KAAK,IAAI,aAAa,EAAE,KAAA;AAAA,EAAK;AAEzD;;"}
@@ -11,6 +11,7 @@ const idpRowToAuditSnapshot = (row) => {
11
11
  initReturnsConfig: row.initReturnsConfig,
12
12
  requireVerifiedEmail: row.requireVerifiedEmail,
13
13
  assumeEmailVerified: row.assumeEmailVerified,
14
+ disableLocalAuthentication: row.disableLocalAuthentication,
14
15
  configurationKeys: Object.keys(row.configuration).sort()
15
16
  };
16
17
  };
@@ -1 +1 @@
1
- {"version":3,"file":"audit.js","sources":["../../../../../../../packages/engine-tenant-api/src/resolvers/mutation/idp/audit.ts"],"sourcesContent":["import { JSONValue } from '@contember/schema'\nimport { IdentityProviderRow } from '../../../model/queries/idp/types.js'\n\nexport const idpRowToAuditSnapshot = (row: IdentityProviderRow | null): JSONValue => {\n\tif (!row) {\n\t\treturn null\n\t}\n\treturn {\n\t\tslug: row.slug,\n\t\ttype: row.type,\n\t\tdisabledAt: row.disabledAt ? row.disabledAt.toISOString() : null,\n\t\tautoSignUp: row.autoSignUp,\n\t\texclusive: row.exclusive,\n\t\tinitReturnsConfig: row.initReturnsConfig,\n\t\trequireVerifiedEmail: row.requireVerifiedEmail,\n\t\tassumeEmailVerified: row.assumeEmailVerified,\n\t\tconfigurationKeys: Object.keys(row.configuration).sort(),\n\t}\n}\n"],"names":[],"mappings":"AAGO,MAAM,wBAAwB,CAAC,QAA+C;AACpF,MAAI,CAAC,KAAK;AACT,WAAO;AAAA,EACR;AACA,SAAO;AAAA,IACN,MAAM,IAAI;AAAA,IACV,MAAM,IAAI;AAAA,IACV,YAAY,IAAI,aAAa,IAAI,WAAW,gBAAgB;AAAA,IAC5D,YAAY,IAAI;AAAA,IAChB,WAAW,IAAI;AAAA,IACf,mBAAmB,IAAI;AAAA,IACvB,sBAAsB,IAAI;AAAA,IAC1B,qBAAqB,IAAI;AAAA,IACzB,mBAAmB,OAAO,KAAK,IAAI,aAAa,EAAE,KAAA;AAAA,EAAK;AAEzD;"}
1
+ {"version":3,"file":"audit.js","sources":["../../../../../../../packages/engine-tenant-api/src/resolvers/mutation/idp/audit.ts"],"sourcesContent":["import { JSONValue } from '@contember/schema'\nimport { IdentityProviderRow } from '../../../model/queries/idp/types.js'\n\nexport const idpRowToAuditSnapshot = (row: IdentityProviderRow | null): JSONValue => {\n\tif (!row) {\n\t\treturn null\n\t}\n\treturn {\n\t\tslug: row.slug,\n\t\ttype: row.type,\n\t\tdisabledAt: row.disabledAt ? row.disabledAt.toISOString() : null,\n\t\tautoSignUp: row.autoSignUp,\n\t\texclusive: row.exclusive,\n\t\tinitReturnsConfig: row.initReturnsConfig,\n\t\trequireVerifiedEmail: row.requireVerifiedEmail,\n\t\tassumeEmailVerified: row.assumeEmailVerified,\n\t\tdisableLocalAuthentication: row.disableLocalAuthentication,\n\t\tconfigurationKeys: Object.keys(row.configuration).sort(),\n\t}\n}\n"],"names":[],"mappings":"AAGO,MAAM,wBAAwB,CAAC,QAA+C;AACpF,MAAI,CAAC,KAAK;AACT,WAAO;AAAA,EACR;AACA,SAAO;AAAA,IACN,MAAM,IAAI;AAAA,IACV,MAAM,IAAI;AAAA,IACV,YAAY,IAAI,aAAa,IAAI,WAAW,gBAAgB;AAAA,IAC5D,YAAY,IAAI;AAAA,IAChB,WAAW,IAAI;AAAA,IACf,mBAAmB,IAAI;AAAA,IACvB,sBAAsB,IAAI;AAAA,IAC1B,qBAAqB,IAAI;AAAA,IACzB,4BAA4B,IAAI;AAAA,IAChC,mBAAmB,OAAO,KAAK,IAAI,aAAa,EAAE,KAAA;AAAA,EAAK;AAEzD;"}
@@ -66,6 +66,10 @@ class PasswordlessMutationResolver {
66
66
  if (result.error === "PERSON_NOT_FOUND" && !configuration.login.revealUserExists) {
67
67
  return errorUtils.createErrorResponse("PASSWORDLESS_DISABLED", "Passwordless sign-in is disabled for this person");
68
68
  }
69
+ const { revealUserExists, revealLoginMethod } = configuration.login;
70
+ if (result.error === "IDP_REQUIRED" && !(revealUserExists && revealLoginMethod)) {
71
+ return errorUtils.createErrorResponse("PASSWORDLESS_DISABLED", "Passwordless sign-in is disabled for this person");
72
+ }
69
73
  return errorUtils.createErrorResponse(result.error, result.errorMessage);
70
74
  }
71
75
  return { ok: true, result: result.result };
@@ -1 +1 @@
1
- {"version":3,"file":"PasswordlessMutationResolver.cjs","sources":["../../../../../../../packages/engine-tenant-api/src/resolvers/mutation/person/PasswordlessMutationResolver.ts"],"sourcesContent":["import {\n\tActivatePasswordlessOtpResponse,\n\tInitSignInPasswordlessResponse,\n\tMutationActivatePasswordlessOtpArgs,\n\tMutationInitSignInPasswordlessArgs,\n\tMutationResolvers,\n\tMutationSignInPasswordlessArgs,\n\tSignInPasswordlessResponse,\n} from '../../../schema/index.js'\nimport { TenantResolverContext } from '../../TenantResolverContext.js'\nimport { CaptchaValidator, ConfigurationQuery, PermissionActions, RateLimiter } from '../../../model/index.js'\nimport { createErrorResponse } from '../../errorUtils.js'\nimport { SignInResponseFactory } from '../../responseHelpers/SignInResponseFactory.js'\nimport { PasswordlessSignInManager } from '../../../model/service/PasswordlessSignInManager.js'\n\nexport class PasswordlessMutationResolver\n\timplements Pick<MutationResolvers, 'initSignInPasswordless' | 'signInPasswordless' | 'activatePasswordlessOtp'>\n{\n\tconstructor(\n\t\tprivate readonly passwordlessSignInManager: PasswordlessSignInManager,\n\t\tprivate readonly signInResponseFactory: SignInResponseFactory,\n\t\tprivate readonly captchaValidator: CaptchaValidator,\n\t\tprivate readonly rateLimiter: RateLimiter,\n\t) {}\n\n\tasync initSignInPasswordless(\n\t\tparent: any,\n\t\targs: MutationInitSignInPasswordlessArgs,\n\t\tcontext: TenantResolverContext,\n\t): Promise<InitSignInPasswordlessResponse> {\n\t\tawait context.requireAccess({\n\t\t\taction: PermissionActions.PERSON_REQUEST_PASSWORDLESS_SIGN_IN,\n\t\t\tmessage: 'You are not allowed to request passwordless sign in',\n\t\t})\n\n\t\tconst configuration = await context.db.queryHandler.fetch(new ConfigurationQuery(context.db.providers))\n\n\t\tconst rl = await this.rateLimiter.consume(context.db, 'passwordless_init_per_ip', context.httpInfo.ip, configuration)\n\t\tif (!rl.ok) {\n\t\t\treturn createErrorResponse('RATE_LIMIT_EXCEEDED', `Too many passwordless sign-in requests. Retry after ${rl.retryAfterSeconds}s.`)\n\t\t}\n\n\t\tconst captchaConfig = this.captchaValidator.extractConfig(configuration)\n\t\tif (this.captchaValidator.isEnabledFor(captchaConfig, 'passwordlessInit')) {\n\t\t\tconst captcha = await this.captchaValidator.verify({\n\t\t\t\tconfig: captchaConfig,\n\t\t\t\ttoken: args.captchaToken ?? undefined,\n\t\t\t\tremoteIp: context.httpInfo.ip,\n\t\t\t})\n\t\t\tif (!captcha.ok) {\n\t\t\t\treturn createErrorResponse('INVALID_CAPTCHA', `Captcha verification failed: ${captcha.reason}`)\n\t\t\t}\n\t\t}\n\n\t\tconst result = await this.passwordlessSignInManager.initSignInPasswordless({\n\t\t\tdb: context.db,\n\t\t\tpermissionContext: context.permissionContext,\n\t\t\temail: args.email,\n\t\t\tmailVariant: args.options?.mailVariant ?? undefined,\n\t\t\tmailProject: args.options?.mailProject ?? undefined,\n\t\t})\n\t\tawait context.logAuthAction({\n\t\t\ttype: 'passwordless_login_init',\n\t\t\tresponse: result,\n\t\t})\n\t\tif (!result.ok) {\n\t\t\tif (result.error === 'PERSON_NOT_FOUND' && !configuration.login.revealUserExists) {\n\t\t\t\t// If the user does not exist, we don't want to reveal that\n\t\t\t\treturn createErrorResponse('PASSWORDLESS_DISABLED', 'Passwordless sign-in is disabled for this person')\n\t\t\t}\n\n\t\t\treturn createErrorResponse(result.error, result.errorMessage)\n\t\t}\n\t\treturn { ok: true, result: result.result }\n\t}\n\n\tasync signInPasswordless(parent: any, args: MutationSignInPasswordlessArgs, context: TenantResolverContext): Promise<SignInPasswordlessResponse> {\n\t\tawait context.requireAccess({\n\t\t\taction: PermissionActions.PERSON_PASSWORDLESS_SIGN_IN,\n\t\t\tmessage: 'You are not allowed to sign in using passwordless',\n\t\t})\n\t\tconst signIn = await this.passwordlessSignInManager.signInPasswordless({\n\t\t\tdb: context.db,\n\t\t\trequestId: args.requestId,\n\t\t\ttoken: args.token,\n\t\t\tmfaOtp: args.mfaOtp ?? undefined,\n\t\t\tbackupCode: args.backupCode ?? undefined,\n\t\t\tvalidationType: args.validationType,\n\t\t\texpiration: args.expiration ?? undefined,\n\t\t\trequestInfo: context.httpInfo,\n\t\t\ttrustForwardedInfo: args.options?.trustForwardedClientInfo === true && context.trustForwardedInfo,\n\t\t})\n\t\tawait context.logAuthAction({\n\t\t\ttype: 'passwordless_login',\n\t\t\tresponse: signIn,\n\t\t})\n\t\tif (signIn.ok && signIn.result.usedBackupCode) {\n\t\t\tawait context.logAuthAction({\n\t\t\t\ttype: 'backup_code_used',\n\t\t\t\tresponse: signIn,\n\t\t\t\tpersonId: signIn.result.person.id,\n\t\t\t})\n\t\t}\n\t\tif (!signIn.ok) {\n\t\t\treturn createErrorResponse(signIn.error, signIn.errorMessage)\n\t\t}\n\t\treturn {\n\t\t\tok: true,\n\t\t\tresult: await this.signInResponseFactory.createResponse(signIn.result, context),\n\t\t}\n\t}\n\n\tasync activatePasswordlessOtp(\n\t\tparent: any,\n\t\targs: MutationActivatePasswordlessOtpArgs,\n\t\tcontext: TenantResolverContext,\n\t): Promise<ActivatePasswordlessOtpResponse> {\n\t\tawait context.requireAccess({\n\t\t\taction: PermissionActions.PERSON_PASSWORDLESS_SIGN_IN,\n\t\t\tmessage: 'You are not allowed to activate passwordless OTP',\n\t\t})\n\t\tconst result = await this.passwordlessSignInManager.activatePasswordlessOtp({\n\t\t\tdb: context.db,\n\t\t\trequestId: args.requestId,\n\t\t\ttoken: args.token,\n\t\t\totpHash: args.otpHash,\n\t\t})\n\t\tawait context.logAuthAction({\n\t\t\ttype: 'passwordless_login_exchange',\n\t\t\tresponse: result,\n\t\t})\n\t\tif (!result.ok) {\n\t\t\treturn createErrorResponse(result.error, result.errorMessage)\n\t\t}\n\t\treturn { ok: true }\n\t}\n}\n"],"names":["PermissionActions","ConfigurationQuery","createErrorResponse"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAeO,MAAM,6BAEb;AAAA,EACC,YACkB,2BACA,uBACA,kBACA,aAChB;AAJgB,SAAA,4BAAA;AACA,SAAA,wBAAA;AACA,SAAA,mBAAA;AACA,SAAA,cAAA;AAAA,EACf;AAAA,EAEH,MAAM,uBACL,QACA,MACA,SAC0C;AAC1C,UAAM,QAAQ,cAAc;AAAA,MAC3B,QAAQA,kBAAAA,kBAAkB;AAAA,MAC1B,SAAS;AAAA,IAAA,CACT;AAED,UAAM,gBAAgB,MAAM,QAAQ,GAAG,aAAa,MAAM,IAAIC,sCAAmB,QAAQ,GAAG,SAAS,CAAC;AAEtG,UAAM,KAAK,MAAM,KAAK,YAAY,QAAQ,QAAQ,IAAI,4BAA4B,QAAQ,SAAS,IAAI,aAAa;AACpH,QAAI,CAAC,GAAG,IAAI;AACX,aAAOC,WAAAA,oBAAoB,uBAAuB,uDAAuD,GAAG,iBAAiB,IAAI;AAAA,IAClI;AAEA,UAAM,gBAAgB,KAAK,iBAAiB,cAAc,aAAa;AACvE,QAAI,KAAK,iBAAiB,aAAa,eAAe,kBAAkB,GAAG;AAC1E,YAAM,UAAU,MAAM,KAAK,iBAAiB,OAAO;AAAA,QAClD,QAAQ;AAAA,QACR,OAAO,KAAK,gBAAgB;AAAA,QAC5B,UAAU,QAAQ,SAAS;AAAA,MAAA,CAC3B;AACD,UAAI,CAAC,QAAQ,IAAI;AAChB,eAAOA,WAAAA,oBAAoB,mBAAmB,gCAAgC,QAAQ,MAAM,EAAE;AAAA,MAC/F;AAAA,IACD;AAEA,UAAM,SAAS,MAAM,KAAK,0BAA0B,uBAAuB;AAAA,MAC1E,IAAI,QAAQ;AAAA,MACZ,mBAAmB,QAAQ;AAAA,MAC3B,OAAO,KAAK;AAAA,MACZ,aAAa,KAAK,SAAS,eAAe;AAAA,MAC1C,aAAa,KAAK,SAAS,eAAe;AAAA,IAAA,CAC1C;AACD,UAAM,QAAQ,cAAc;AAAA,MAC3B,MAAM;AAAA,MACN,UAAU;AAAA,IAAA,CACV;AACD,QAAI,CAAC,OAAO,IAAI;AACf,UAAI,OAAO,UAAU,sBAAsB,CAAC,cAAc,MAAM,kBAAkB;AAEjF,eAAOA,WAAAA,oBAAoB,yBAAyB,kDAAkD;AAAA,MACvG;AAEA,aAAOA,WAAAA,oBAAoB,OAAO,OAAO,OAAO,YAAY;AAAA,IAC7D;AACA,WAAO,EAAE,IAAI,MAAM,QAAQ,OAAO,OAAA;AAAA,EACnC;AAAA,EAEA,MAAM,mBAAmB,QAAa,MAAsC,SAAqE;AAChJ,UAAM,QAAQ,cAAc;AAAA,MAC3B,QAAQF,kBAAAA,kBAAkB;AAAA,MAC1B,SAAS;AAAA,IAAA,CACT;AACD,UAAM,SAAS,MAAM,KAAK,0BAA0B,mBAAmB;AAAA,MACtE,IAAI,QAAQ;AAAA,MACZ,WAAW,KAAK;AAAA,MAChB,OAAO,KAAK;AAAA,MACZ,QAAQ,KAAK,UAAU;AAAA,MACvB,YAAY,KAAK,cAAc;AAAA,MAC/B,gBAAgB,KAAK;AAAA,MACrB,YAAY,KAAK,cAAc;AAAA,MAC/B,aAAa,QAAQ;AAAA,MACrB,oBAAoB,KAAK,SAAS,6BAA6B,QAAQ,QAAQ;AAAA,IAAA,CAC/E;AACD,UAAM,QAAQ,cAAc;AAAA,MAC3B,MAAM;AAAA,MACN,UAAU;AAAA,IAAA,CACV;AACD,QAAI,OAAO,MAAM,OAAO,OAAO,gBAAgB;AAC9C,YAAM,QAAQ,cAAc;AAAA,QAC3B,MAAM;AAAA,QACN,UAAU;AAAA,QACV,UAAU,OAAO,OAAO,OAAO;AAAA,MAAA,CAC/B;AAAA,IACF;AACA,QAAI,CAAC,OAAO,IAAI;AACf,aAAOE,WAAAA,oBAAoB,OAAO,OAAO,OAAO,YAAY;AAAA,IAC7D;AACA,WAAO;AAAA,MACN,IAAI;AAAA,MACJ,QAAQ,MAAM,KAAK,sBAAsB,eAAe,OAAO,QAAQ,OAAO;AAAA,IAAA;AAAA,EAEhF;AAAA,EAEA,MAAM,wBACL,QACA,MACA,SAC2C;AAC3C,UAAM,QAAQ,cAAc;AAAA,MAC3B,QAAQF,kBAAAA,kBAAkB;AAAA,MAC1B,SAAS;AAAA,IAAA,CACT;AACD,UAAM,SAAS,MAAM,KAAK,0BAA0B,wBAAwB;AAAA,MAC3E,IAAI,QAAQ;AAAA,MACZ,WAAW,KAAK;AAAA,MAChB,OAAO,KAAK;AAAA,MACZ,SAAS,KAAK;AAAA,IAAA,CACd;AACD,UAAM,QAAQ,cAAc;AAAA,MAC3B,MAAM;AAAA,MACN,UAAU;AAAA,IAAA,CACV;AACD,QAAI,CAAC,OAAO,IAAI;AACf,aAAOE,WAAAA,oBAAoB,OAAO,OAAO,OAAO,YAAY;AAAA,IAC7D;AACA,WAAO,EAAE,IAAI,KAAA;AAAA,EACd;AACD;;"}
1
+ {"version":3,"file":"PasswordlessMutationResolver.cjs","sources":["../../../../../../../packages/engine-tenant-api/src/resolvers/mutation/person/PasswordlessMutationResolver.ts"],"sourcesContent":["import {\n\tActivatePasswordlessOtpResponse,\n\tInitSignInPasswordlessResponse,\n\tMutationActivatePasswordlessOtpArgs,\n\tMutationInitSignInPasswordlessArgs,\n\tMutationResolvers,\n\tMutationSignInPasswordlessArgs,\n\tSignInPasswordlessResponse,\n} from '../../../schema/index.js'\nimport { TenantResolverContext } from '../../TenantResolverContext.js'\nimport { CaptchaValidator, ConfigurationQuery, PermissionActions, RateLimiter } from '../../../model/index.js'\nimport { createErrorResponse } from '../../errorUtils.js'\nimport { SignInResponseFactory } from '../../responseHelpers/SignInResponseFactory.js'\nimport { PasswordlessSignInManager } from '../../../model/service/PasswordlessSignInManager.js'\n\nexport class PasswordlessMutationResolver\n\timplements Pick<MutationResolvers, 'initSignInPasswordless' | 'signInPasswordless' | 'activatePasswordlessOtp'>\n{\n\tconstructor(\n\t\tprivate readonly passwordlessSignInManager: PasswordlessSignInManager,\n\t\tprivate readonly signInResponseFactory: SignInResponseFactory,\n\t\tprivate readonly captchaValidator: CaptchaValidator,\n\t\tprivate readonly rateLimiter: RateLimiter,\n\t) {}\n\n\tasync initSignInPasswordless(\n\t\tparent: any,\n\t\targs: MutationInitSignInPasswordlessArgs,\n\t\tcontext: TenantResolverContext,\n\t): Promise<InitSignInPasswordlessResponse> {\n\t\tawait context.requireAccess({\n\t\t\taction: PermissionActions.PERSON_REQUEST_PASSWORDLESS_SIGN_IN,\n\t\t\tmessage: 'You are not allowed to request passwordless sign in',\n\t\t})\n\n\t\tconst configuration = await context.db.queryHandler.fetch(new ConfigurationQuery(context.db.providers))\n\n\t\tconst rl = await this.rateLimiter.consume(context.db, 'passwordless_init_per_ip', context.httpInfo.ip, configuration)\n\t\tif (!rl.ok) {\n\t\t\treturn createErrorResponse('RATE_LIMIT_EXCEEDED', `Too many passwordless sign-in requests. Retry after ${rl.retryAfterSeconds}s.`)\n\t\t}\n\n\t\tconst captchaConfig = this.captchaValidator.extractConfig(configuration)\n\t\tif (this.captchaValidator.isEnabledFor(captchaConfig, 'passwordlessInit')) {\n\t\t\tconst captcha = await this.captchaValidator.verify({\n\t\t\t\tconfig: captchaConfig,\n\t\t\t\ttoken: args.captchaToken ?? undefined,\n\t\t\t\tremoteIp: context.httpInfo.ip,\n\t\t\t})\n\t\t\tif (!captcha.ok) {\n\t\t\t\treturn createErrorResponse('INVALID_CAPTCHA', `Captcha verification failed: ${captcha.reason}`)\n\t\t\t}\n\t\t}\n\n\t\tconst result = await this.passwordlessSignInManager.initSignInPasswordless({\n\t\t\tdb: context.db,\n\t\t\tpermissionContext: context.permissionContext,\n\t\t\temail: args.email,\n\t\t\tmailVariant: args.options?.mailVariant ?? undefined,\n\t\t\tmailProject: args.options?.mailProject ?? undefined,\n\t\t})\n\t\tawait context.logAuthAction({\n\t\t\ttype: 'passwordless_login_init',\n\t\t\tresponse: result,\n\t\t})\n\t\tif (!result.ok) {\n\t\t\tif (result.error === 'PERSON_NOT_FOUND' && !configuration.login.revealUserExists) {\n\t\t\t\t// If the user does not exist, we don't want to reveal that\n\t\t\t\treturn createErrorResponse('PASSWORDLESS_DISABLED', 'Passwordless sign-in is disabled for this person')\n\t\t\t}\n\t\t\t// Naming the provider tells the caller both that the person exists and how\n\t\t\t// they authenticate, so it needs both reveal flags. PASSWORDLESS_DISABLED is\n\t\t\t// the neutral answer this endpoint already gives for an unknown address.\n\t\t\tconst { revealUserExists, revealLoginMethod } = configuration.login\n\t\t\tif (result.error === 'IDP_REQUIRED' && !(revealUserExists && revealLoginMethod)) {\n\t\t\t\treturn createErrorResponse('PASSWORDLESS_DISABLED', 'Passwordless sign-in is disabled for this person')\n\t\t\t}\n\n\t\t\treturn createErrorResponse(result.error, result.errorMessage)\n\t\t}\n\t\treturn { ok: true, result: result.result }\n\t}\n\n\tasync signInPasswordless(parent: any, args: MutationSignInPasswordlessArgs, context: TenantResolverContext): Promise<SignInPasswordlessResponse> {\n\t\tawait context.requireAccess({\n\t\t\taction: PermissionActions.PERSON_PASSWORDLESS_SIGN_IN,\n\t\t\tmessage: 'You are not allowed to sign in using passwordless',\n\t\t})\n\t\tconst signIn = await this.passwordlessSignInManager.signInPasswordless({\n\t\t\tdb: context.db,\n\t\t\trequestId: args.requestId,\n\t\t\ttoken: args.token,\n\t\t\tmfaOtp: args.mfaOtp ?? undefined,\n\t\t\tbackupCode: args.backupCode ?? undefined,\n\t\t\tvalidationType: args.validationType,\n\t\t\texpiration: args.expiration ?? undefined,\n\t\t\trequestInfo: context.httpInfo,\n\t\t\ttrustForwardedInfo: args.options?.trustForwardedClientInfo === true && context.trustForwardedInfo,\n\t\t})\n\t\tawait context.logAuthAction({\n\t\t\ttype: 'passwordless_login',\n\t\t\tresponse: signIn,\n\t\t})\n\t\tif (signIn.ok && signIn.result.usedBackupCode) {\n\t\t\tawait context.logAuthAction({\n\t\t\t\ttype: 'backup_code_used',\n\t\t\t\tresponse: signIn,\n\t\t\t\tpersonId: signIn.result.person.id,\n\t\t\t})\n\t\t}\n\t\tif (!signIn.ok) {\n\t\t\treturn createErrorResponse(signIn.error, signIn.errorMessage)\n\t\t}\n\t\treturn {\n\t\t\tok: true,\n\t\t\tresult: await this.signInResponseFactory.createResponse(signIn.result, context),\n\t\t}\n\t}\n\n\tasync activatePasswordlessOtp(\n\t\tparent: any,\n\t\targs: MutationActivatePasswordlessOtpArgs,\n\t\tcontext: TenantResolverContext,\n\t): Promise<ActivatePasswordlessOtpResponse> {\n\t\tawait context.requireAccess({\n\t\t\taction: PermissionActions.PERSON_PASSWORDLESS_SIGN_IN,\n\t\t\tmessage: 'You are not allowed to activate passwordless OTP',\n\t\t})\n\t\tconst result = await this.passwordlessSignInManager.activatePasswordlessOtp({\n\t\t\tdb: context.db,\n\t\t\trequestId: args.requestId,\n\t\t\ttoken: args.token,\n\t\t\totpHash: args.otpHash,\n\t\t})\n\t\tawait context.logAuthAction({\n\t\t\ttype: 'passwordless_login_exchange',\n\t\t\tresponse: result,\n\t\t})\n\t\tif (!result.ok) {\n\t\t\treturn createErrorResponse(result.error, result.errorMessage)\n\t\t}\n\t\treturn { ok: true }\n\t}\n}\n"],"names":["PermissionActions","ConfigurationQuery","createErrorResponse"],"mappings":"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"}
@@ -64,6 +64,10 @@ class PasswordlessMutationResolver {
64
64
  if (result.error === "PERSON_NOT_FOUND" && !configuration.login.revealUserExists) {
65
65
  return createErrorResponse("PASSWORDLESS_DISABLED", "Passwordless sign-in is disabled for this person");
66
66
  }
67
+ const { revealUserExists, revealLoginMethod } = configuration.login;
68
+ if (result.error === "IDP_REQUIRED" && !(revealUserExists && revealLoginMethod)) {
69
+ return createErrorResponse("PASSWORDLESS_DISABLED", "Passwordless sign-in is disabled for this person");
70
+ }
67
71
  return createErrorResponse(result.error, result.errorMessage);
68
72
  }
69
73
  return { ok: true, result: result.result };
@@ -1 +1 @@
1
- {"version":3,"file":"PasswordlessMutationResolver.js","sources":["../../../../../../../packages/engine-tenant-api/src/resolvers/mutation/person/PasswordlessMutationResolver.ts"],"sourcesContent":["import {\n\tActivatePasswordlessOtpResponse,\n\tInitSignInPasswordlessResponse,\n\tMutationActivatePasswordlessOtpArgs,\n\tMutationInitSignInPasswordlessArgs,\n\tMutationResolvers,\n\tMutationSignInPasswordlessArgs,\n\tSignInPasswordlessResponse,\n} from '../../../schema/index.js'\nimport { TenantResolverContext } from '../../TenantResolverContext.js'\nimport { CaptchaValidator, ConfigurationQuery, PermissionActions, RateLimiter } from '../../../model/index.js'\nimport { createErrorResponse } from '../../errorUtils.js'\nimport { SignInResponseFactory } from '../../responseHelpers/SignInResponseFactory.js'\nimport { PasswordlessSignInManager } from '../../../model/service/PasswordlessSignInManager.js'\n\nexport class PasswordlessMutationResolver\n\timplements Pick<MutationResolvers, 'initSignInPasswordless' | 'signInPasswordless' | 'activatePasswordlessOtp'>\n{\n\tconstructor(\n\t\tprivate readonly passwordlessSignInManager: PasswordlessSignInManager,\n\t\tprivate readonly signInResponseFactory: SignInResponseFactory,\n\t\tprivate readonly captchaValidator: CaptchaValidator,\n\t\tprivate readonly rateLimiter: RateLimiter,\n\t) {}\n\n\tasync initSignInPasswordless(\n\t\tparent: any,\n\t\targs: MutationInitSignInPasswordlessArgs,\n\t\tcontext: TenantResolverContext,\n\t): Promise<InitSignInPasswordlessResponse> {\n\t\tawait context.requireAccess({\n\t\t\taction: PermissionActions.PERSON_REQUEST_PASSWORDLESS_SIGN_IN,\n\t\t\tmessage: 'You are not allowed to request passwordless sign in',\n\t\t})\n\n\t\tconst configuration = await context.db.queryHandler.fetch(new ConfigurationQuery(context.db.providers))\n\n\t\tconst rl = await this.rateLimiter.consume(context.db, 'passwordless_init_per_ip', context.httpInfo.ip, configuration)\n\t\tif (!rl.ok) {\n\t\t\treturn createErrorResponse('RATE_LIMIT_EXCEEDED', `Too many passwordless sign-in requests. Retry after ${rl.retryAfterSeconds}s.`)\n\t\t}\n\n\t\tconst captchaConfig = this.captchaValidator.extractConfig(configuration)\n\t\tif (this.captchaValidator.isEnabledFor(captchaConfig, 'passwordlessInit')) {\n\t\t\tconst captcha = await this.captchaValidator.verify({\n\t\t\t\tconfig: captchaConfig,\n\t\t\t\ttoken: args.captchaToken ?? undefined,\n\t\t\t\tremoteIp: context.httpInfo.ip,\n\t\t\t})\n\t\t\tif (!captcha.ok) {\n\t\t\t\treturn createErrorResponse('INVALID_CAPTCHA', `Captcha verification failed: ${captcha.reason}`)\n\t\t\t}\n\t\t}\n\n\t\tconst result = await this.passwordlessSignInManager.initSignInPasswordless({\n\t\t\tdb: context.db,\n\t\t\tpermissionContext: context.permissionContext,\n\t\t\temail: args.email,\n\t\t\tmailVariant: args.options?.mailVariant ?? undefined,\n\t\t\tmailProject: args.options?.mailProject ?? undefined,\n\t\t})\n\t\tawait context.logAuthAction({\n\t\t\ttype: 'passwordless_login_init',\n\t\t\tresponse: result,\n\t\t})\n\t\tif (!result.ok) {\n\t\t\tif (result.error === 'PERSON_NOT_FOUND' && !configuration.login.revealUserExists) {\n\t\t\t\t// If the user does not exist, we don't want to reveal that\n\t\t\t\treturn createErrorResponse('PASSWORDLESS_DISABLED', 'Passwordless sign-in is disabled for this person')\n\t\t\t}\n\n\t\t\treturn createErrorResponse(result.error, result.errorMessage)\n\t\t}\n\t\treturn { ok: true, result: result.result }\n\t}\n\n\tasync signInPasswordless(parent: any, args: MutationSignInPasswordlessArgs, context: TenantResolverContext): Promise<SignInPasswordlessResponse> {\n\t\tawait context.requireAccess({\n\t\t\taction: PermissionActions.PERSON_PASSWORDLESS_SIGN_IN,\n\t\t\tmessage: 'You are not allowed to sign in using passwordless',\n\t\t})\n\t\tconst signIn = await this.passwordlessSignInManager.signInPasswordless({\n\t\t\tdb: context.db,\n\t\t\trequestId: args.requestId,\n\t\t\ttoken: args.token,\n\t\t\tmfaOtp: args.mfaOtp ?? undefined,\n\t\t\tbackupCode: args.backupCode ?? undefined,\n\t\t\tvalidationType: args.validationType,\n\t\t\texpiration: args.expiration ?? undefined,\n\t\t\trequestInfo: context.httpInfo,\n\t\t\ttrustForwardedInfo: args.options?.trustForwardedClientInfo === true && context.trustForwardedInfo,\n\t\t})\n\t\tawait context.logAuthAction({\n\t\t\ttype: 'passwordless_login',\n\t\t\tresponse: signIn,\n\t\t})\n\t\tif (signIn.ok && signIn.result.usedBackupCode) {\n\t\t\tawait context.logAuthAction({\n\t\t\t\ttype: 'backup_code_used',\n\t\t\t\tresponse: signIn,\n\t\t\t\tpersonId: signIn.result.person.id,\n\t\t\t})\n\t\t}\n\t\tif (!signIn.ok) {\n\t\t\treturn createErrorResponse(signIn.error, signIn.errorMessage)\n\t\t}\n\t\treturn {\n\t\t\tok: true,\n\t\t\tresult: await this.signInResponseFactory.createResponse(signIn.result, context),\n\t\t}\n\t}\n\n\tasync activatePasswordlessOtp(\n\t\tparent: any,\n\t\targs: MutationActivatePasswordlessOtpArgs,\n\t\tcontext: TenantResolverContext,\n\t): Promise<ActivatePasswordlessOtpResponse> {\n\t\tawait context.requireAccess({\n\t\t\taction: PermissionActions.PERSON_PASSWORDLESS_SIGN_IN,\n\t\t\tmessage: 'You are not allowed to activate passwordless OTP',\n\t\t})\n\t\tconst result = await this.passwordlessSignInManager.activatePasswordlessOtp({\n\t\t\tdb: context.db,\n\t\t\trequestId: args.requestId,\n\t\t\ttoken: args.token,\n\t\t\totpHash: args.otpHash,\n\t\t})\n\t\tawait context.logAuthAction({\n\t\t\ttype: 'passwordless_login_exchange',\n\t\t\tresponse: result,\n\t\t})\n\t\tif (!result.ok) {\n\t\t\treturn createErrorResponse(result.error, result.errorMessage)\n\t\t}\n\t\treturn { ok: true }\n\t}\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAeO,MAAM,6BAEb;AAAA,EACC,YACkB,2BACA,uBACA,kBACA,aAChB;AAJgB,SAAA,4BAAA;AACA,SAAA,wBAAA;AACA,SAAA,mBAAA;AACA,SAAA,cAAA;AAAA,EACf;AAAA,EAEH,MAAM,uBACL,QACA,MACA,SAC0C;AAC1C,UAAM,QAAQ,cAAc;AAAA,MAC3B,QAAQ,kBAAkB;AAAA,MAC1B,SAAS;AAAA,IAAA,CACT;AAED,UAAM,gBAAgB,MAAM,QAAQ,GAAG,aAAa,MAAM,IAAI,mBAAmB,QAAQ,GAAG,SAAS,CAAC;AAEtG,UAAM,KAAK,MAAM,KAAK,YAAY,QAAQ,QAAQ,IAAI,4BAA4B,QAAQ,SAAS,IAAI,aAAa;AACpH,QAAI,CAAC,GAAG,IAAI;AACX,aAAO,oBAAoB,uBAAuB,uDAAuD,GAAG,iBAAiB,IAAI;AAAA,IAClI;AAEA,UAAM,gBAAgB,KAAK,iBAAiB,cAAc,aAAa;AACvE,QAAI,KAAK,iBAAiB,aAAa,eAAe,kBAAkB,GAAG;AAC1E,YAAM,UAAU,MAAM,KAAK,iBAAiB,OAAO;AAAA,QAClD,QAAQ;AAAA,QACR,OAAO,KAAK,gBAAgB;AAAA,QAC5B,UAAU,QAAQ,SAAS;AAAA,MAAA,CAC3B;AACD,UAAI,CAAC,QAAQ,IAAI;AAChB,eAAO,oBAAoB,mBAAmB,gCAAgC,QAAQ,MAAM,EAAE;AAAA,MAC/F;AAAA,IACD;AAEA,UAAM,SAAS,MAAM,KAAK,0BAA0B,uBAAuB;AAAA,MAC1E,IAAI,QAAQ;AAAA,MACZ,mBAAmB,QAAQ;AAAA,MAC3B,OAAO,KAAK;AAAA,MACZ,aAAa,KAAK,SAAS,eAAe;AAAA,MAC1C,aAAa,KAAK,SAAS,eAAe;AAAA,IAAA,CAC1C;AACD,UAAM,QAAQ,cAAc;AAAA,MAC3B,MAAM;AAAA,MACN,UAAU;AAAA,IAAA,CACV;AACD,QAAI,CAAC,OAAO,IAAI;AACf,UAAI,OAAO,UAAU,sBAAsB,CAAC,cAAc,MAAM,kBAAkB;AAEjF,eAAO,oBAAoB,yBAAyB,kDAAkD;AAAA,MACvG;AAEA,aAAO,oBAAoB,OAAO,OAAO,OAAO,YAAY;AAAA,IAC7D;AACA,WAAO,EAAE,IAAI,MAAM,QAAQ,OAAO,OAAA;AAAA,EACnC;AAAA,EAEA,MAAM,mBAAmB,QAAa,MAAsC,SAAqE;AAChJ,UAAM,QAAQ,cAAc;AAAA,MAC3B,QAAQ,kBAAkB;AAAA,MAC1B,SAAS;AAAA,IAAA,CACT;AACD,UAAM,SAAS,MAAM,KAAK,0BAA0B,mBAAmB;AAAA,MACtE,IAAI,QAAQ;AAAA,MACZ,WAAW,KAAK;AAAA,MAChB,OAAO,KAAK;AAAA,MACZ,QAAQ,KAAK,UAAU;AAAA,MACvB,YAAY,KAAK,cAAc;AAAA,MAC/B,gBAAgB,KAAK;AAAA,MACrB,YAAY,KAAK,cAAc;AAAA,MAC/B,aAAa,QAAQ;AAAA,MACrB,oBAAoB,KAAK,SAAS,6BAA6B,QAAQ,QAAQ;AAAA,IAAA,CAC/E;AACD,UAAM,QAAQ,cAAc;AAAA,MAC3B,MAAM;AAAA,MACN,UAAU;AAAA,IAAA,CACV;AACD,QAAI,OAAO,MAAM,OAAO,OAAO,gBAAgB;AAC9C,YAAM,QAAQ,cAAc;AAAA,QAC3B,MAAM;AAAA,QACN,UAAU;AAAA,QACV,UAAU,OAAO,OAAO,OAAO;AAAA,MAAA,CAC/B;AAAA,IACF;AACA,QAAI,CAAC,OAAO,IAAI;AACf,aAAO,oBAAoB,OAAO,OAAO,OAAO,YAAY;AAAA,IAC7D;AACA,WAAO;AAAA,MACN,IAAI;AAAA,MACJ,QAAQ,MAAM,KAAK,sBAAsB,eAAe,OAAO,QAAQ,OAAO;AAAA,IAAA;AAAA,EAEhF;AAAA,EAEA,MAAM,wBACL,QACA,MACA,SAC2C;AAC3C,UAAM,QAAQ,cAAc;AAAA,MAC3B,QAAQ,kBAAkB;AAAA,MAC1B,SAAS;AAAA,IAAA,CACT;AACD,UAAM,SAAS,MAAM,KAAK,0BAA0B,wBAAwB;AAAA,MAC3E,IAAI,QAAQ;AAAA,MACZ,WAAW,KAAK;AAAA,MAChB,OAAO,KAAK;AAAA,MACZ,SAAS,KAAK;AAAA,IAAA,CACd;AACD,UAAM,QAAQ,cAAc;AAAA,MAC3B,MAAM;AAAA,MACN,UAAU;AAAA,IAAA,CACV;AACD,QAAI,CAAC,OAAO,IAAI;AACf,aAAO,oBAAoB,OAAO,OAAO,OAAO,YAAY;AAAA,IAC7D;AACA,WAAO,EAAE,IAAI,KAAA;AAAA,EACd;AACD;"}
1
+ {"version":3,"file":"PasswordlessMutationResolver.js","sources":["../../../../../../../packages/engine-tenant-api/src/resolvers/mutation/person/PasswordlessMutationResolver.ts"],"sourcesContent":["import {\n\tActivatePasswordlessOtpResponse,\n\tInitSignInPasswordlessResponse,\n\tMutationActivatePasswordlessOtpArgs,\n\tMutationInitSignInPasswordlessArgs,\n\tMutationResolvers,\n\tMutationSignInPasswordlessArgs,\n\tSignInPasswordlessResponse,\n} from '../../../schema/index.js'\nimport { TenantResolverContext } from '../../TenantResolverContext.js'\nimport { CaptchaValidator, ConfigurationQuery, PermissionActions, RateLimiter } from '../../../model/index.js'\nimport { createErrorResponse } from '../../errorUtils.js'\nimport { SignInResponseFactory } from '../../responseHelpers/SignInResponseFactory.js'\nimport { PasswordlessSignInManager } from '../../../model/service/PasswordlessSignInManager.js'\n\nexport class PasswordlessMutationResolver\n\timplements Pick<MutationResolvers, 'initSignInPasswordless' | 'signInPasswordless' | 'activatePasswordlessOtp'>\n{\n\tconstructor(\n\t\tprivate readonly passwordlessSignInManager: PasswordlessSignInManager,\n\t\tprivate readonly signInResponseFactory: SignInResponseFactory,\n\t\tprivate readonly captchaValidator: CaptchaValidator,\n\t\tprivate readonly rateLimiter: RateLimiter,\n\t) {}\n\n\tasync initSignInPasswordless(\n\t\tparent: any,\n\t\targs: MutationInitSignInPasswordlessArgs,\n\t\tcontext: TenantResolverContext,\n\t): Promise<InitSignInPasswordlessResponse> {\n\t\tawait context.requireAccess({\n\t\t\taction: PermissionActions.PERSON_REQUEST_PASSWORDLESS_SIGN_IN,\n\t\t\tmessage: 'You are not allowed to request passwordless sign in',\n\t\t})\n\n\t\tconst configuration = await context.db.queryHandler.fetch(new ConfigurationQuery(context.db.providers))\n\n\t\tconst rl = await this.rateLimiter.consume(context.db, 'passwordless_init_per_ip', context.httpInfo.ip, configuration)\n\t\tif (!rl.ok) {\n\t\t\treturn createErrorResponse('RATE_LIMIT_EXCEEDED', `Too many passwordless sign-in requests. Retry after ${rl.retryAfterSeconds}s.`)\n\t\t}\n\n\t\tconst captchaConfig = this.captchaValidator.extractConfig(configuration)\n\t\tif (this.captchaValidator.isEnabledFor(captchaConfig, 'passwordlessInit')) {\n\t\t\tconst captcha = await this.captchaValidator.verify({\n\t\t\t\tconfig: captchaConfig,\n\t\t\t\ttoken: args.captchaToken ?? undefined,\n\t\t\t\tremoteIp: context.httpInfo.ip,\n\t\t\t})\n\t\t\tif (!captcha.ok) {\n\t\t\t\treturn createErrorResponse('INVALID_CAPTCHA', `Captcha verification failed: ${captcha.reason}`)\n\t\t\t}\n\t\t}\n\n\t\tconst result = await this.passwordlessSignInManager.initSignInPasswordless({\n\t\t\tdb: context.db,\n\t\t\tpermissionContext: context.permissionContext,\n\t\t\temail: args.email,\n\t\t\tmailVariant: args.options?.mailVariant ?? undefined,\n\t\t\tmailProject: args.options?.mailProject ?? undefined,\n\t\t})\n\t\tawait context.logAuthAction({\n\t\t\ttype: 'passwordless_login_init',\n\t\t\tresponse: result,\n\t\t})\n\t\tif (!result.ok) {\n\t\t\tif (result.error === 'PERSON_NOT_FOUND' && !configuration.login.revealUserExists) {\n\t\t\t\t// If the user does not exist, we don't want to reveal that\n\t\t\t\treturn createErrorResponse('PASSWORDLESS_DISABLED', 'Passwordless sign-in is disabled for this person')\n\t\t\t}\n\t\t\t// Naming the provider tells the caller both that the person exists and how\n\t\t\t// they authenticate, so it needs both reveal flags. PASSWORDLESS_DISABLED is\n\t\t\t// the neutral answer this endpoint already gives for an unknown address.\n\t\t\tconst { revealUserExists, revealLoginMethod } = configuration.login\n\t\t\tif (result.error === 'IDP_REQUIRED' && !(revealUserExists && revealLoginMethod)) {\n\t\t\t\treturn createErrorResponse('PASSWORDLESS_DISABLED', 'Passwordless sign-in is disabled for this person')\n\t\t\t}\n\n\t\t\treturn createErrorResponse(result.error, result.errorMessage)\n\t\t}\n\t\treturn { ok: true, result: result.result }\n\t}\n\n\tasync signInPasswordless(parent: any, args: MutationSignInPasswordlessArgs, context: TenantResolverContext): Promise<SignInPasswordlessResponse> {\n\t\tawait context.requireAccess({\n\t\t\taction: PermissionActions.PERSON_PASSWORDLESS_SIGN_IN,\n\t\t\tmessage: 'You are not allowed to sign in using passwordless',\n\t\t})\n\t\tconst signIn = await this.passwordlessSignInManager.signInPasswordless({\n\t\t\tdb: context.db,\n\t\t\trequestId: args.requestId,\n\t\t\ttoken: args.token,\n\t\t\tmfaOtp: args.mfaOtp ?? undefined,\n\t\t\tbackupCode: args.backupCode ?? undefined,\n\t\t\tvalidationType: args.validationType,\n\t\t\texpiration: args.expiration ?? undefined,\n\t\t\trequestInfo: context.httpInfo,\n\t\t\ttrustForwardedInfo: args.options?.trustForwardedClientInfo === true && context.trustForwardedInfo,\n\t\t})\n\t\tawait context.logAuthAction({\n\t\t\ttype: 'passwordless_login',\n\t\t\tresponse: signIn,\n\t\t})\n\t\tif (signIn.ok && signIn.result.usedBackupCode) {\n\t\t\tawait context.logAuthAction({\n\t\t\t\ttype: 'backup_code_used',\n\t\t\t\tresponse: signIn,\n\t\t\t\tpersonId: signIn.result.person.id,\n\t\t\t})\n\t\t}\n\t\tif (!signIn.ok) {\n\t\t\treturn createErrorResponse(signIn.error, signIn.errorMessage)\n\t\t}\n\t\treturn {\n\t\t\tok: true,\n\t\t\tresult: await this.signInResponseFactory.createResponse(signIn.result, context),\n\t\t}\n\t}\n\n\tasync activatePasswordlessOtp(\n\t\tparent: any,\n\t\targs: MutationActivatePasswordlessOtpArgs,\n\t\tcontext: TenantResolverContext,\n\t): Promise<ActivatePasswordlessOtpResponse> {\n\t\tawait context.requireAccess({\n\t\t\taction: PermissionActions.PERSON_PASSWORDLESS_SIGN_IN,\n\t\t\tmessage: 'You are not allowed to activate passwordless OTP',\n\t\t})\n\t\tconst result = await this.passwordlessSignInManager.activatePasswordlessOtp({\n\t\t\tdb: context.db,\n\t\t\trequestId: args.requestId,\n\t\t\ttoken: args.token,\n\t\t\totpHash: args.otpHash,\n\t\t})\n\t\tawait context.logAuthAction({\n\t\t\ttype: 'passwordless_login_exchange',\n\t\t\tresponse: result,\n\t\t})\n\t\tif (!result.ok) {\n\t\t\treturn createErrorResponse(result.error, result.errorMessage)\n\t\t}\n\t\treturn { ok: true }\n\t}\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAeO,MAAM,6BAEb;AAAA,EACC,YACkB,2BACA,uBACA,kBACA,aAChB;AAJgB,SAAA,4BAAA;AACA,SAAA,wBAAA;AACA,SAAA,mBAAA;AACA,SAAA,cAAA;AAAA,EACf;AAAA,EAEH,MAAM,uBACL,QACA,MACA,SAC0C;AAC1C,UAAM,QAAQ,cAAc;AAAA,MAC3B,QAAQ,kBAAkB;AAAA,MAC1B,SAAS;AAAA,IAAA,CACT;AAED,UAAM,gBAAgB,MAAM,QAAQ,GAAG,aAAa,MAAM,IAAI,mBAAmB,QAAQ,GAAG,SAAS,CAAC;AAEtG,UAAM,KAAK,MAAM,KAAK,YAAY,QAAQ,QAAQ,IAAI,4BAA4B,QAAQ,SAAS,IAAI,aAAa;AACpH,QAAI,CAAC,GAAG,IAAI;AACX,aAAO,oBAAoB,uBAAuB,uDAAuD,GAAG,iBAAiB,IAAI;AAAA,IAClI;AAEA,UAAM,gBAAgB,KAAK,iBAAiB,cAAc,aAAa;AACvE,QAAI,KAAK,iBAAiB,aAAa,eAAe,kBAAkB,GAAG;AAC1E,YAAM,UAAU,MAAM,KAAK,iBAAiB,OAAO;AAAA,QAClD,QAAQ;AAAA,QACR,OAAO,KAAK,gBAAgB;AAAA,QAC5B,UAAU,QAAQ,SAAS;AAAA,MAAA,CAC3B;AACD,UAAI,CAAC,QAAQ,IAAI;AAChB,eAAO,oBAAoB,mBAAmB,gCAAgC,QAAQ,MAAM,EAAE;AAAA,MAC/F;AAAA,IACD;AAEA,UAAM,SAAS,MAAM,KAAK,0BAA0B,uBAAuB;AAAA,MAC1E,IAAI,QAAQ;AAAA,MACZ,mBAAmB,QAAQ;AAAA,MAC3B,OAAO,KAAK;AAAA,MACZ,aAAa,KAAK,SAAS,eAAe;AAAA,MAC1C,aAAa,KAAK,SAAS,eAAe;AAAA,IAAA,CAC1C;AACD,UAAM,QAAQ,cAAc;AAAA,MAC3B,MAAM;AAAA,MACN,UAAU;AAAA,IAAA,CACV;AACD,QAAI,CAAC,OAAO,IAAI;AACf,UAAI,OAAO,UAAU,sBAAsB,CAAC,cAAc,MAAM,kBAAkB;AAEjF,eAAO,oBAAoB,yBAAyB,kDAAkD;AAAA,MACvG;AAIA,YAAM,EAAE,kBAAkB,kBAAA,IAAsB,cAAc;AAC9D,UAAI,OAAO,UAAU,kBAAkB,EAAE,oBAAoB,oBAAoB;AAChF,eAAO,oBAAoB,yBAAyB,kDAAkD;AAAA,MACvG;AAEA,aAAO,oBAAoB,OAAO,OAAO,OAAO,YAAY;AAAA,IAC7D;AACA,WAAO,EAAE,IAAI,MAAM,QAAQ,OAAO,OAAA;AAAA,EACnC;AAAA,EAEA,MAAM,mBAAmB,QAAa,MAAsC,SAAqE;AAChJ,UAAM,QAAQ,cAAc;AAAA,MAC3B,QAAQ,kBAAkB;AAAA,MAC1B,SAAS;AAAA,IAAA,CACT;AACD,UAAM,SAAS,MAAM,KAAK,0BAA0B,mBAAmB;AAAA,MACtE,IAAI,QAAQ;AAAA,MACZ,WAAW,KAAK;AAAA,MAChB,OAAO,KAAK;AAAA,MACZ,QAAQ,KAAK,UAAU;AAAA,MACvB,YAAY,KAAK,cAAc;AAAA,MAC/B,gBAAgB,KAAK;AAAA,MACrB,YAAY,KAAK,cAAc;AAAA,MAC/B,aAAa,QAAQ;AAAA,MACrB,oBAAoB,KAAK,SAAS,6BAA6B,QAAQ,QAAQ;AAAA,IAAA,CAC/E;AACD,UAAM,QAAQ,cAAc;AAAA,MAC3B,MAAM;AAAA,MACN,UAAU;AAAA,IAAA,CACV;AACD,QAAI,OAAO,MAAM,OAAO,OAAO,gBAAgB;AAC9C,YAAM,QAAQ,cAAc;AAAA,QAC3B,MAAM;AAAA,QACN,UAAU;AAAA,QACV,UAAU,OAAO,OAAO,OAAO;AAAA,MAAA,CAC/B;AAAA,IACF;AACA,QAAI,CAAC,OAAO,IAAI;AACf,aAAO,oBAAoB,OAAO,OAAO,OAAO,YAAY;AAAA,IAC7D;AACA,WAAO;AAAA,MACN,IAAI;AAAA,MACJ,QAAQ,MAAM,KAAK,sBAAsB,eAAe,OAAO,QAAQ,OAAO;AAAA,IAAA;AAAA,EAEhF;AAAA,EAEA,MAAM,wBACL,QACA,MACA,SAC2C;AAC3C,UAAM,QAAQ,cAAc;AAAA,MAC3B,QAAQ,kBAAkB;AAAA,MAC1B,SAAS;AAAA,IAAA,CACT;AACD,UAAM,SAAS,MAAM,KAAK,0BAA0B,wBAAwB;AAAA,MAC3E,IAAI,QAAQ;AAAA,MACZ,WAAW,KAAK;AAAA,MAChB,OAAO,KAAK;AAAA,MACZ,SAAS,KAAK;AAAA,IAAA,CACd;AACD,UAAM,QAAQ,cAAc;AAAA,MAC3B,MAAM;AAAA,MACN,UAAU;AAAA,IAAA,CACV;AACD,QAAI,CAAC,OAAO,IAAI;AACf,aAAO,oBAAoB,OAAO,OAAO,OAAO,YAAY;AAAA,IAC7D;AACA,WAAO,EAAE,IAAI,KAAA;AAAA,EACd;AACD;"}
@@ -111,8 +111,8 @@ class SignInMutationResolver {
111
111
  }
112
112
  if (!response.ok) {
113
113
  const { revealUserExists, revealLoginMethod } = configuration.login;
114
- const collapseAll = !revealUserExists && ["NO_PASSWORD_SET", "PERSON_DISABLED", "INVALID_PASSWORD", "UNKNOWN_EMAIL"].includes(response.error);
115
- const collapseMethodOnly = revealUserExists && !revealLoginMethod && ["NO_PASSWORD_SET", "INVALID_PASSWORD"].includes(response.error);
114
+ const collapseAll = !revealUserExists && ["NO_PASSWORD_SET", "PERSON_DISABLED", "INVALID_PASSWORD", "UNKNOWN_EMAIL", "IDP_REQUIRED"].includes(response.error);
115
+ const collapseMethodOnly = revealUserExists && !revealLoginMethod && ["NO_PASSWORD_SET", "INVALID_PASSWORD", "IDP_REQUIRED"].includes(response.error);
116
116
  if (collapseAll || collapseMethodOnly) {
117
117
  return errorUtils.createErrorResponse("INVALID_CREDENTIALS", "Invalid credentials");
118
118
  }
@@ -1 +1 @@
1
- {"version":3,"file":"SignInMutationResolver.cjs","sources":["../../../../../../../packages/engine-tenant-api/src/resolvers/mutation/person/SignInMutationResolver.ts"],"sourcesContent":["import {\n\tCreateSessionTokenResponse,\n\tMutationCreateSessionTokenArgs,\n\tMutationResolvers,\n\tMutationSignInArgs,\n\tSignInResponse,\n} from '../../../schema/index.js'\nimport { TenantResolverContext } from '../../TenantResolverContext.js'\nimport { ConfigurationQuery, PermissionActions, PersonUniqueIdentifier, RateLimiter, SignInManager } from '../../../model/index.js'\nimport { createErrorResponse } from '../../errorUtils.js'\nimport { SignInResponseFactory } from '../../responseHelpers/SignInResponseFactory.js'\nimport { UserInputError } from '@contember/graphql-utils'\nimport { NextLoginAttemptQuery } from '../../../model/queries/authLog/NextLoginAttemptQuery.js'\nimport { ResponseError } from '../../../model/utils/Response.js'\n\nexport class SignInMutationResolver implements MutationResolvers {\n\tconstructor(\n\t\tprivate readonly signInManager: SignInManager,\n\t\tprivate readonly signInResponseFactory: SignInResponseFactory,\n\t\tprivate readonly rateLimiter: RateLimiter,\n\t) {}\n\n\tasync signIn(parent: any, args: MutationSignInArgs, context: TenantResolverContext): Promise<SignInResponse> {\n\t\tawait context.requireAccess({\n\t\t\taction: PermissionActions.PERSON_SIGN_IN,\n\t\t\tmessage: 'You are not allowed to sign in',\n\t\t})\n\n\t\tconst configuration = await context.db.queryHandler.fetch(new ConfigurationQuery(context.db.providers))\n\n\t\tconst ipGate = await this.rateLimiter.consume(context.db, 'login_per_ip', context.httpInfo.ip, configuration)\n\t\tif (!ipGate.ok) {\n\t\t\treturn createErrorResponse(\n\t\t\t\tnew ResponseError('RATE_LIMIT_EXCEEDED', `Too many sign-in attempts from this IP. Retry after ${ipGate.retryAfterSeconds}s.`, {\n\t\t\t\t\tretryAfter: ipGate.retryAfterSeconds,\n\t\t\t\t}),\n\t\t\t)\n\t\t}\n\n\t\tconst nextAllowedSignIn = await context.db.queryHandler.fetch(new NextLoginAttemptQuery(args.email))\n\t\tif (nextAllowedSignIn > new Date()) {\n\t\t\treturn createErrorResponse(\n\t\t\t\tnew ResponseError('RATE_LIMIT_EXCEEDED', `Too many attempts, please try again later.`, {\n\t\t\t\t\tretryAfter: Math.ceil((nextAllowedSignIn.getTime() - Date.now()) / 1000),\n\t\t\t\t}),\n\t\t\t)\n\t\t}\n\n\t\tconst response = await this.signInManager.signIn(\n\t\t\tcontext.db,\n\t\t\targs.email,\n\t\t\targs.password,\n\t\t\targs.expiration || undefined,\n\t\t\targs.otpToken || undefined,\n\t\t\tcontext.httpInfo,\n\t\t\targs.options?.trustForwardedClientInfo === true && context.trustForwardedInfo,\n\t\t\targs.backupCode || undefined,\n\t\t\tconfiguration,\n\t\t\tcontext.httpInfo.geoCountry,\n\t\t)\n\t\tawait context.logAuthAction({\n\t\t\ttype: 'login',\n\t\t\tresponse,\n\t\t})\n\t\tif (response.ok && response.result.usedBackupCode) {\n\t\t\tawait context.logAuthAction({\n\t\t\t\ttype: 'backup_code_used',\n\t\t\t\tresponse,\n\t\t\t\tpersonId: response.result.person.id,\n\t\t\t})\n\t\t}\n\t\tif (!response.ok && response.metadata?.emailOtpSent) {\n\t\t\tawait context.logAuthAction({\n\t\t\t\ttype: 'email_otp_sent',\n\t\t\t\tresponse,\n\t\t\t})\n\t\t}\n\t\tif (!response.ok && response.metadata?.mfaEnrollmentRequired) {\n\t\t\tawait context.logAuthAction({\n\t\t\t\ttype: 'mfa_enrollment_required',\n\t\t\t\tresponse,\n\t\t\t})\n\t\t}\n\t\t// A03 anomaly audit. The risk scoring runs in SignInManager; the audit\n\t\t// entries are emitted here at the resolver layer (per convention), with the\n\t\t// score + reasons snapshot in event_data.\n\t\tif (response.ok && response.result.unusualLoginDetected) {\n\t\t\tawait context.logAuthAction({\n\t\t\t\ttype: 'unusual_login_detected',\n\t\t\t\tresponse,\n\t\t\t\tpersonId: response.result.person.id,\n\t\t\t\teventData: response.result.risk ? { ...response.result.risk } : undefined,\n\t\t\t})\n\t\t}\n\t\tif (!response.ok && response.metadata?.stepUpRequired) {\n\t\t\tawait context.logAuthAction({\n\t\t\t\ttype: 'unusual_login_detected',\n\t\t\t\tresponse,\n\t\t\t\teventData: response.metadata.risk ? { ...response.metadata.risk } : undefined,\n\t\t\t})\n\t\t\tawait context.logAuthAction({\n\t\t\t\ttype: 'step_up_required',\n\t\t\t\tresponse,\n\t\t\t\teventData: response.metadata.risk ? { ...response.metadata.risk } : undefined,\n\t\t\t})\n\t\t}\n\n\t\tif (!response.ok) {\n\t\t\tconst { revealUserExists, revealLoginMethod } = configuration.login\n\t\t\tconst collapseAll = !revealUserExists\n\t\t\t\t&& ['NO_PASSWORD_SET', 'PERSON_DISABLED', 'INVALID_PASSWORD', 'UNKNOWN_EMAIL'].includes(response.error)\n\t\t\tconst collapseMethodOnly = revealUserExists\n\t\t\t\t&& !revealLoginMethod\n\t\t\t\t&& ['NO_PASSWORD_SET', 'INVALID_PASSWORD'].includes(response.error)\n\t\t\tif (collapseAll || collapseMethodOnly) {\n\t\t\t\treturn createErrorResponse('INVALID_CREDENTIALS', 'Invalid credentials')\n\t\t\t}\n\t\t\t// Preserve the additive mfaEnrollment payload on MFA_ENROLLMENT_REQUIRED (A06).\n\t\t\tif (response.error === 'MFA_ENROLLMENT_REQUIRED' && response.metadata?.mfaEnrollment) {\n\t\t\t\tconst error = {\n\t\t\t\t\tcode: response.error,\n\t\t\t\t\tdeveloperMessage: response.errorMessage,\n\t\t\t\t\tmfaEnrollment: response.metadata.mfaEnrollment,\n\t\t\t\t}\n\t\t\t\treturn { ok: false, error, errors: [error] }\n\t\t\t}\n\t\t\treturn createErrorResponse(response.error, response.errorMessage)\n\t\t}\n\n\t\treturn {\n\t\t\tok: true,\n\t\t\terrors: [],\n\t\t\tresult: await this.signInResponseFactory.createResponse(response.result, context),\n\t\t}\n\t}\n\n\tasync createSessionToken(parent: any, args: MutationCreateSessionTokenArgs, context: TenantResolverContext): Promise<CreateSessionTokenResponse> {\n\t\tawait context.requireAccess({\n\t\t\taction: PermissionActions.PERSON_CREATE_SESSION_KEY(),\n\t\t\tmessage: 'You are not allowed to create a session key',\n\t\t})\n\t\tlet identifier: PersonUniqueIdentifier\n\t\tif (args.email) {\n\t\t\tidentifier = { type: 'email', email: args.email }\n\t\t} else if (args.personId) {\n\t\t\tidentifier = { type: 'id', id: args.personId }\n\t\t} else {\n\t\t\tthrow new UserInputError(`Please provide either email or personId`)\n\t\t}\n\n\t\tconst response = await this.signInManager.createSessionToken(\n\t\t\tcontext.db,\n\t\t\tidentifier,\n\t\t\targs.expiration || undefined,\n\t\t\tasync person =>\n\t\t\t\tawait context.requireAccess({\n\t\t\t\t\taction: PermissionActions.PERSON_CREATE_SESSION_KEY(person.roles),\n\t\t\t\t\tmessage: 'You are not allowed to create a session key for this person.',\n\t\t\t\t}),\n\t\t\tcontext.httpInfo,\n\t\t\targs.options?.trustForwardedClientInfo === true && context.trustForwardedInfo,\n\t\t)\n\t\tawait context.logAuthAction({\n\t\t\ttype: 'create_session_token',\n\t\t\tresponse: response,\n\t\t})\n\n\t\tif (!response.ok) {\n\t\t\treturn createErrorResponse(response.error, response.errorMessage)\n\t\t}\n\n\t\treturn {\n\t\t\tok: true,\n\t\t\tresult: await this.signInResponseFactory.createResponse(response.result, context),\n\t\t}\n\t}\n}\n"],"names":["PermissionActions","ConfigurationQuery","createErrorResponse","ResponseError","NextLoginAttemptQuery","UserInputError"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAeO,MAAM,uBAAoD;AAAA,EAChE,YACkB,eACA,uBACA,aAChB;AAHgB,SAAA,gBAAA;AACA,SAAA,wBAAA;AACA,SAAA,cAAA;AAAA,EACf;AAAA,EAEH,MAAM,OAAO,QAAa,MAA0B,SAAyD;AAC5G,UAAM,QAAQ,cAAc;AAAA,MAC3B,QAAQA,kBAAAA,kBAAkB;AAAA,MAC1B,SAAS;AAAA,IAAA,CACT;AAED,UAAM,gBAAgB,MAAM,QAAQ,GAAG,aAAa,MAAM,IAAIC,sCAAmB,QAAQ,GAAG,SAAS,CAAC;AAEtG,UAAM,SAAS,MAAM,KAAK,YAAY,QAAQ,QAAQ,IAAI,gBAAgB,QAAQ,SAAS,IAAI,aAAa;AAC5G,QAAI,CAAC,OAAO,IAAI;AACf,aAAOC,WAAAA;AAAAA,QACN,IAAIC,SAAAA,cAAc,uBAAuB,uDAAuD,OAAO,iBAAiB,MAAM;AAAA,UAC7H,YAAY,OAAO;AAAA,QAAA,CACnB;AAAA,MAAA;AAAA,IAEH;AAEA,UAAM,oBAAoB,MAAM,QAAQ,GAAG,aAAa,MAAM,IAAIC,sBAAAA,sBAAsB,KAAK,KAAK,CAAC;AACnG,QAAI,oBAAoB,oBAAI,QAAQ;AACnC,aAAOF,WAAAA;AAAAA,QACN,IAAIC,uBAAc,uBAAuB,8CAA8C;AAAA,UACtF,YAAY,KAAK,MAAM,kBAAkB,YAAY,KAAK,IAAA,KAAS,GAAI;AAAA,QAAA,CACvE;AAAA,MAAA;AAAA,IAEH;AAEA,UAAM,WAAW,MAAM,KAAK,cAAc;AAAA,MACzC,QAAQ;AAAA,MACR,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK,cAAc;AAAA,MACnB,KAAK,YAAY;AAAA,MACjB,QAAQ;AAAA,MACR,KAAK,SAAS,6BAA6B,QAAQ,QAAQ;AAAA,MAC3D,KAAK,cAAc;AAAA,MACnB;AAAA,MACA,QAAQ,SAAS;AAAA,IAAA;AAElB,UAAM,QAAQ,cAAc;AAAA,MAC3B,MAAM;AAAA,MACN;AAAA,IAAA,CACA;AACD,QAAI,SAAS,MAAM,SAAS,OAAO,gBAAgB;AAClD,YAAM,QAAQ,cAAc;AAAA,QAC3B,MAAM;AAAA,QACN;AAAA,QACA,UAAU,SAAS,OAAO,OAAO;AAAA,MAAA,CACjC;AAAA,IACF;AACA,QAAI,CAAC,SAAS,MAAM,SAAS,UAAU,cAAc;AACpD,YAAM,QAAQ,cAAc;AAAA,QAC3B,MAAM;AAAA,QACN;AAAA,MAAA,CACA;AAAA,IACF;AACA,QAAI,CAAC,SAAS,MAAM,SAAS,UAAU,uBAAuB;AAC7D,YAAM,QAAQ,cAAc;AAAA,QAC3B,MAAM;AAAA,QACN;AAAA,MAAA,CACA;AAAA,IACF;AAIA,QAAI,SAAS,MAAM,SAAS,OAAO,sBAAsB;AACxD,YAAM,QAAQ,cAAc;AAAA,QAC3B,MAAM;AAAA,QACN;AAAA,QACA,UAAU,SAAS,OAAO,OAAO;AAAA,QACjC,WAAW,SAAS,OAAO,OAAO,EAAE,GAAG,SAAS,OAAO,SAAS;AAAA,MAAA,CAChE;AAAA,IACF;AACA,QAAI,CAAC,SAAS,MAAM,SAAS,UAAU,gBAAgB;AACtD,YAAM,QAAQ,cAAc;AAAA,QAC3B,MAAM;AAAA,QACN;AAAA,QACA,WAAW,SAAS,SAAS,OAAO,EAAE,GAAG,SAAS,SAAS,SAAS;AAAA,MAAA,CACpE;AACD,YAAM,QAAQ,cAAc;AAAA,QAC3B,MAAM;AAAA,QACN;AAAA,QACA,WAAW,SAAS,SAAS,OAAO,EAAE,GAAG,SAAS,SAAS,SAAS;AAAA,MAAA,CACpE;AAAA,IACF;AAEA,QAAI,CAAC,SAAS,IAAI;AACjB,YAAM,EAAE,kBAAkB,kBAAA,IAAsB,cAAc;AAC9D,YAAM,cAAc,CAAC,oBACjB,CAAC,mBAAmB,mBAAmB,oBAAoB,eAAe,EAAE,SAAS,SAAS,KAAK;AACvG,YAAM,qBAAqB,oBACvB,CAAC,qBACD,CAAC,mBAAmB,kBAAkB,EAAE,SAAS,SAAS,KAAK;AACnE,UAAI,eAAe,oBAAoB;AACtC,eAAOD,WAAAA,oBAAoB,uBAAuB,qBAAqB;AAAA,MACxE;AAEA,UAAI,SAAS,UAAU,6BAA6B,SAAS,UAAU,eAAe;AACrF,cAAM,QAAQ;AAAA,UACb,MAAM,SAAS;AAAA,UACf,kBAAkB,SAAS;AAAA,UAC3B,eAAe,SAAS,SAAS;AAAA,QAAA;AAElC,eAAO,EAAE,IAAI,OAAO,OAAO,QAAQ,CAAC,KAAK,EAAA;AAAA,MAC1C;AACA,aAAOA,WAAAA,oBAAoB,SAAS,OAAO,SAAS,YAAY;AAAA,IACjE;AAEA,WAAO;AAAA,MACN,IAAI;AAAA,MACJ,QAAQ,CAAA;AAAA,MACR,QAAQ,MAAM,KAAK,sBAAsB,eAAe,SAAS,QAAQ,OAAO;AAAA,IAAA;AAAA,EAElF;AAAA,EAEA,MAAM,mBAAmB,QAAa,MAAsC,SAAqE;AAChJ,UAAM,QAAQ,cAAc;AAAA,MAC3B,QAAQF,kBAAAA,kBAAkB,0BAAA;AAAA,MAC1B,SAAS;AAAA,IAAA,CACT;AACD,QAAI;AACJ,QAAI,KAAK,OAAO;AACf,mBAAa,EAAE,MAAM,SAAS,OAAO,KAAK,MAAA;AAAA,IAC3C,WAAW,KAAK,UAAU;AACzB,mBAAa,EAAE,MAAM,MAAM,IAAI,KAAK,SAAA;AAAA,IACrC,OAAO;AACN,YAAM,IAAIK,aAAAA,eAAe,yCAAyC;AAAA,IACnE;AAEA,UAAM,WAAW,MAAM,KAAK,cAAc;AAAA,MACzC,QAAQ;AAAA,MACR;AAAA,MACA,KAAK,cAAc;AAAA,MACnB,OAAM,WACL,MAAM,QAAQ,cAAc;AAAA,QAC3B,QAAQL,kBAAAA,kBAAkB,0BAA0B,OAAO,KAAK;AAAA,QAChE,SAAS;AAAA,MAAA,CACT;AAAA,MACF,QAAQ;AAAA,MACR,KAAK,SAAS,6BAA6B,QAAQ,QAAQ;AAAA,IAAA;AAE5D,UAAM,QAAQ,cAAc;AAAA,MAC3B,MAAM;AAAA,MACN;AAAA,IAAA,CACA;AAED,QAAI,CAAC,SAAS,IAAI;AACjB,aAAOE,WAAAA,oBAAoB,SAAS,OAAO,SAAS,YAAY;AAAA,IACjE;AAEA,WAAO;AAAA,MACN,IAAI;AAAA,MACJ,QAAQ,MAAM,KAAK,sBAAsB,eAAe,SAAS,QAAQ,OAAO;AAAA,IAAA;AAAA,EAElF;AACD;;"}
1
+ {"version":3,"file":"SignInMutationResolver.cjs","sources":["../../../../../../../packages/engine-tenant-api/src/resolvers/mutation/person/SignInMutationResolver.ts"],"sourcesContent":["import {\n\tCreateSessionTokenResponse,\n\tMutationCreateSessionTokenArgs,\n\tMutationResolvers,\n\tMutationSignInArgs,\n\tSignInResponse,\n} from '../../../schema/index.js'\nimport { TenantResolverContext } from '../../TenantResolverContext.js'\nimport { ConfigurationQuery, PermissionActions, PersonUniqueIdentifier, RateLimiter, SignInManager } from '../../../model/index.js'\nimport { createErrorResponse } from '../../errorUtils.js'\nimport { SignInResponseFactory } from '../../responseHelpers/SignInResponseFactory.js'\nimport { UserInputError } from '@contember/graphql-utils'\nimport { NextLoginAttemptQuery } from '../../../model/queries/authLog/NextLoginAttemptQuery.js'\nimport { ResponseError } from '../../../model/utils/Response.js'\n\nexport class SignInMutationResolver implements MutationResolvers {\n\tconstructor(\n\t\tprivate readonly signInManager: SignInManager,\n\t\tprivate readonly signInResponseFactory: SignInResponseFactory,\n\t\tprivate readonly rateLimiter: RateLimiter,\n\t) {}\n\n\tasync signIn(parent: any, args: MutationSignInArgs, context: TenantResolverContext): Promise<SignInResponse> {\n\t\tawait context.requireAccess({\n\t\t\taction: PermissionActions.PERSON_SIGN_IN,\n\t\t\tmessage: 'You are not allowed to sign in',\n\t\t})\n\n\t\tconst configuration = await context.db.queryHandler.fetch(new ConfigurationQuery(context.db.providers))\n\n\t\tconst ipGate = await this.rateLimiter.consume(context.db, 'login_per_ip', context.httpInfo.ip, configuration)\n\t\tif (!ipGate.ok) {\n\t\t\treturn createErrorResponse(\n\t\t\t\tnew ResponseError('RATE_LIMIT_EXCEEDED', `Too many sign-in attempts from this IP. Retry after ${ipGate.retryAfterSeconds}s.`, {\n\t\t\t\t\tretryAfter: ipGate.retryAfterSeconds,\n\t\t\t\t}),\n\t\t\t)\n\t\t}\n\n\t\tconst nextAllowedSignIn = await context.db.queryHandler.fetch(new NextLoginAttemptQuery(args.email))\n\t\tif (nextAllowedSignIn > new Date()) {\n\t\t\treturn createErrorResponse(\n\t\t\t\tnew ResponseError('RATE_LIMIT_EXCEEDED', `Too many attempts, please try again later.`, {\n\t\t\t\t\tretryAfter: Math.ceil((nextAllowedSignIn.getTime() - Date.now()) / 1000),\n\t\t\t\t}),\n\t\t\t)\n\t\t}\n\n\t\tconst response = await this.signInManager.signIn(\n\t\t\tcontext.db,\n\t\t\targs.email,\n\t\t\targs.password,\n\t\t\targs.expiration || undefined,\n\t\t\targs.otpToken || undefined,\n\t\t\tcontext.httpInfo,\n\t\t\targs.options?.trustForwardedClientInfo === true && context.trustForwardedInfo,\n\t\t\targs.backupCode || undefined,\n\t\t\tconfiguration,\n\t\t\tcontext.httpInfo.geoCountry,\n\t\t)\n\t\tawait context.logAuthAction({\n\t\t\ttype: 'login',\n\t\t\tresponse,\n\t\t})\n\t\tif (response.ok && response.result.usedBackupCode) {\n\t\t\tawait context.logAuthAction({\n\t\t\t\ttype: 'backup_code_used',\n\t\t\t\tresponse,\n\t\t\t\tpersonId: response.result.person.id,\n\t\t\t})\n\t\t}\n\t\tif (!response.ok && response.metadata?.emailOtpSent) {\n\t\t\tawait context.logAuthAction({\n\t\t\t\ttype: 'email_otp_sent',\n\t\t\t\tresponse,\n\t\t\t})\n\t\t}\n\t\tif (!response.ok && response.metadata?.mfaEnrollmentRequired) {\n\t\t\tawait context.logAuthAction({\n\t\t\t\ttype: 'mfa_enrollment_required',\n\t\t\t\tresponse,\n\t\t\t})\n\t\t}\n\t\t// A03 anomaly audit. The risk scoring runs in SignInManager; the audit\n\t\t// entries are emitted here at the resolver layer (per convention), with the\n\t\t// score + reasons snapshot in event_data.\n\t\tif (response.ok && response.result.unusualLoginDetected) {\n\t\t\tawait context.logAuthAction({\n\t\t\t\ttype: 'unusual_login_detected',\n\t\t\t\tresponse,\n\t\t\t\tpersonId: response.result.person.id,\n\t\t\t\teventData: response.result.risk ? { ...response.result.risk } : undefined,\n\t\t\t})\n\t\t}\n\t\tif (!response.ok && response.metadata?.stepUpRequired) {\n\t\t\tawait context.logAuthAction({\n\t\t\t\ttype: 'unusual_login_detected',\n\t\t\t\tresponse,\n\t\t\t\teventData: response.metadata.risk ? { ...response.metadata.risk } : undefined,\n\t\t\t})\n\t\t\tawait context.logAuthAction({\n\t\t\t\ttype: 'step_up_required',\n\t\t\t\tresponse,\n\t\t\t\teventData: response.metadata.risk ? { ...response.metadata.risk } : undefined,\n\t\t\t})\n\t\t}\n\n\t\tif (!response.ok) {\n\t\t\tconst { revealUserExists, revealLoginMethod } = configuration.login\n\t\t\tconst collapseAll = !revealUserExists\n\t\t\t\t&& ['NO_PASSWORD_SET', 'PERSON_DISABLED', 'INVALID_PASSWORD', 'UNKNOWN_EMAIL', 'IDP_REQUIRED'].includes(response.error)\n\t\t\tconst collapseMethodOnly = revealUserExists\n\t\t\t\t&& !revealLoginMethod\n\t\t\t\t&& ['NO_PASSWORD_SET', 'INVALID_PASSWORD', 'IDP_REQUIRED'].includes(response.error)\n\t\t\tif (collapseAll || collapseMethodOnly) {\n\t\t\t\treturn createErrorResponse('INVALID_CREDENTIALS', 'Invalid credentials')\n\t\t\t}\n\t\t\t// Preserve the additive mfaEnrollment payload on MFA_ENROLLMENT_REQUIRED (A06).\n\t\t\tif (response.error === 'MFA_ENROLLMENT_REQUIRED' && response.metadata?.mfaEnrollment) {\n\t\t\t\tconst error = {\n\t\t\t\t\tcode: response.error,\n\t\t\t\t\tdeveloperMessage: response.errorMessage,\n\t\t\t\t\tmfaEnrollment: response.metadata.mfaEnrollment,\n\t\t\t\t}\n\t\t\t\treturn { ok: false, error, errors: [error] }\n\t\t\t}\n\t\t\treturn createErrorResponse(response.error, response.errorMessage)\n\t\t}\n\n\t\treturn {\n\t\t\tok: true,\n\t\t\terrors: [],\n\t\t\tresult: await this.signInResponseFactory.createResponse(response.result, context),\n\t\t}\n\t}\n\n\tasync createSessionToken(parent: any, args: MutationCreateSessionTokenArgs, context: TenantResolverContext): Promise<CreateSessionTokenResponse> {\n\t\tawait context.requireAccess({\n\t\t\taction: PermissionActions.PERSON_CREATE_SESSION_KEY(),\n\t\t\tmessage: 'You are not allowed to create a session key',\n\t\t})\n\t\tlet identifier: PersonUniqueIdentifier\n\t\tif (args.email) {\n\t\t\tidentifier = { type: 'email', email: args.email }\n\t\t} else if (args.personId) {\n\t\t\tidentifier = { type: 'id', id: args.personId }\n\t\t} else {\n\t\t\tthrow new UserInputError(`Please provide either email or personId`)\n\t\t}\n\n\t\tconst response = await this.signInManager.createSessionToken(\n\t\t\tcontext.db,\n\t\t\tidentifier,\n\t\t\targs.expiration || undefined,\n\t\t\tasync person =>\n\t\t\t\tawait context.requireAccess({\n\t\t\t\t\taction: PermissionActions.PERSON_CREATE_SESSION_KEY(person.roles),\n\t\t\t\t\tmessage: 'You are not allowed to create a session key for this person.',\n\t\t\t\t}),\n\t\t\tcontext.httpInfo,\n\t\t\targs.options?.trustForwardedClientInfo === true && context.trustForwardedInfo,\n\t\t)\n\t\tawait context.logAuthAction({\n\t\t\ttype: 'create_session_token',\n\t\t\tresponse: response,\n\t\t})\n\n\t\tif (!response.ok) {\n\t\t\treturn createErrorResponse(response.error, response.errorMessage)\n\t\t}\n\n\t\treturn {\n\t\t\tok: true,\n\t\t\tresult: await this.signInResponseFactory.createResponse(response.result, context),\n\t\t}\n\t}\n}\n"],"names":["PermissionActions","ConfigurationQuery","createErrorResponse","ResponseError","NextLoginAttemptQuery","UserInputError"],"mappings":"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"}
@@ -109,8 +109,8 @@ class SignInMutationResolver {
109
109
  }
110
110
  if (!response.ok) {
111
111
  const { revealUserExists, revealLoginMethod } = configuration.login;
112
- const collapseAll = !revealUserExists && ["NO_PASSWORD_SET", "PERSON_DISABLED", "INVALID_PASSWORD", "UNKNOWN_EMAIL"].includes(response.error);
113
- const collapseMethodOnly = revealUserExists && !revealLoginMethod && ["NO_PASSWORD_SET", "INVALID_PASSWORD"].includes(response.error);
112
+ const collapseAll = !revealUserExists && ["NO_PASSWORD_SET", "PERSON_DISABLED", "INVALID_PASSWORD", "UNKNOWN_EMAIL", "IDP_REQUIRED"].includes(response.error);
113
+ const collapseMethodOnly = revealUserExists && !revealLoginMethod && ["NO_PASSWORD_SET", "INVALID_PASSWORD", "IDP_REQUIRED"].includes(response.error);
114
114
  if (collapseAll || collapseMethodOnly) {
115
115
  return createErrorResponse("INVALID_CREDENTIALS", "Invalid credentials");
116
116
  }
@@ -1 +1 @@
1
- {"version":3,"file":"SignInMutationResolver.js","sources":["../../../../../../../packages/engine-tenant-api/src/resolvers/mutation/person/SignInMutationResolver.ts"],"sourcesContent":["import {\n\tCreateSessionTokenResponse,\n\tMutationCreateSessionTokenArgs,\n\tMutationResolvers,\n\tMutationSignInArgs,\n\tSignInResponse,\n} from '../../../schema/index.js'\nimport { TenantResolverContext } from '../../TenantResolverContext.js'\nimport { ConfigurationQuery, PermissionActions, PersonUniqueIdentifier, RateLimiter, SignInManager } from '../../../model/index.js'\nimport { createErrorResponse } from '../../errorUtils.js'\nimport { SignInResponseFactory } from '../../responseHelpers/SignInResponseFactory.js'\nimport { UserInputError } from '@contember/graphql-utils'\nimport { NextLoginAttemptQuery } from '../../../model/queries/authLog/NextLoginAttemptQuery.js'\nimport { ResponseError } from '../../../model/utils/Response.js'\n\nexport class SignInMutationResolver implements MutationResolvers {\n\tconstructor(\n\t\tprivate readonly signInManager: SignInManager,\n\t\tprivate readonly signInResponseFactory: SignInResponseFactory,\n\t\tprivate readonly rateLimiter: RateLimiter,\n\t) {}\n\n\tasync signIn(parent: any, args: MutationSignInArgs, context: TenantResolverContext): Promise<SignInResponse> {\n\t\tawait context.requireAccess({\n\t\t\taction: PermissionActions.PERSON_SIGN_IN,\n\t\t\tmessage: 'You are not allowed to sign in',\n\t\t})\n\n\t\tconst configuration = await context.db.queryHandler.fetch(new ConfigurationQuery(context.db.providers))\n\n\t\tconst ipGate = await this.rateLimiter.consume(context.db, 'login_per_ip', context.httpInfo.ip, configuration)\n\t\tif (!ipGate.ok) {\n\t\t\treturn createErrorResponse(\n\t\t\t\tnew ResponseError('RATE_LIMIT_EXCEEDED', `Too many sign-in attempts from this IP. Retry after ${ipGate.retryAfterSeconds}s.`, {\n\t\t\t\t\tretryAfter: ipGate.retryAfterSeconds,\n\t\t\t\t}),\n\t\t\t)\n\t\t}\n\n\t\tconst nextAllowedSignIn = await context.db.queryHandler.fetch(new NextLoginAttemptQuery(args.email))\n\t\tif (nextAllowedSignIn > new Date()) {\n\t\t\treturn createErrorResponse(\n\t\t\t\tnew ResponseError('RATE_LIMIT_EXCEEDED', `Too many attempts, please try again later.`, {\n\t\t\t\t\tretryAfter: Math.ceil((nextAllowedSignIn.getTime() - Date.now()) / 1000),\n\t\t\t\t}),\n\t\t\t)\n\t\t}\n\n\t\tconst response = await this.signInManager.signIn(\n\t\t\tcontext.db,\n\t\t\targs.email,\n\t\t\targs.password,\n\t\t\targs.expiration || undefined,\n\t\t\targs.otpToken || undefined,\n\t\t\tcontext.httpInfo,\n\t\t\targs.options?.trustForwardedClientInfo === true && context.trustForwardedInfo,\n\t\t\targs.backupCode || undefined,\n\t\t\tconfiguration,\n\t\t\tcontext.httpInfo.geoCountry,\n\t\t)\n\t\tawait context.logAuthAction({\n\t\t\ttype: 'login',\n\t\t\tresponse,\n\t\t})\n\t\tif (response.ok && response.result.usedBackupCode) {\n\t\t\tawait context.logAuthAction({\n\t\t\t\ttype: 'backup_code_used',\n\t\t\t\tresponse,\n\t\t\t\tpersonId: response.result.person.id,\n\t\t\t})\n\t\t}\n\t\tif (!response.ok && response.metadata?.emailOtpSent) {\n\t\t\tawait context.logAuthAction({\n\t\t\t\ttype: 'email_otp_sent',\n\t\t\t\tresponse,\n\t\t\t})\n\t\t}\n\t\tif (!response.ok && response.metadata?.mfaEnrollmentRequired) {\n\t\t\tawait context.logAuthAction({\n\t\t\t\ttype: 'mfa_enrollment_required',\n\t\t\t\tresponse,\n\t\t\t})\n\t\t}\n\t\t// A03 anomaly audit. The risk scoring runs in SignInManager; the audit\n\t\t// entries are emitted here at the resolver layer (per convention), with the\n\t\t// score + reasons snapshot in event_data.\n\t\tif (response.ok && response.result.unusualLoginDetected) {\n\t\t\tawait context.logAuthAction({\n\t\t\t\ttype: 'unusual_login_detected',\n\t\t\t\tresponse,\n\t\t\t\tpersonId: response.result.person.id,\n\t\t\t\teventData: response.result.risk ? { ...response.result.risk } : undefined,\n\t\t\t})\n\t\t}\n\t\tif (!response.ok && response.metadata?.stepUpRequired) {\n\t\t\tawait context.logAuthAction({\n\t\t\t\ttype: 'unusual_login_detected',\n\t\t\t\tresponse,\n\t\t\t\teventData: response.metadata.risk ? { ...response.metadata.risk } : undefined,\n\t\t\t})\n\t\t\tawait context.logAuthAction({\n\t\t\t\ttype: 'step_up_required',\n\t\t\t\tresponse,\n\t\t\t\teventData: response.metadata.risk ? { ...response.metadata.risk } : undefined,\n\t\t\t})\n\t\t}\n\n\t\tif (!response.ok) {\n\t\t\tconst { revealUserExists, revealLoginMethod } = configuration.login\n\t\t\tconst collapseAll = !revealUserExists\n\t\t\t\t&& ['NO_PASSWORD_SET', 'PERSON_DISABLED', 'INVALID_PASSWORD', 'UNKNOWN_EMAIL'].includes(response.error)\n\t\t\tconst collapseMethodOnly = revealUserExists\n\t\t\t\t&& !revealLoginMethod\n\t\t\t\t&& ['NO_PASSWORD_SET', 'INVALID_PASSWORD'].includes(response.error)\n\t\t\tif (collapseAll || collapseMethodOnly) {\n\t\t\t\treturn createErrorResponse('INVALID_CREDENTIALS', 'Invalid credentials')\n\t\t\t}\n\t\t\t// Preserve the additive mfaEnrollment payload on MFA_ENROLLMENT_REQUIRED (A06).\n\t\t\tif (response.error === 'MFA_ENROLLMENT_REQUIRED' && response.metadata?.mfaEnrollment) {\n\t\t\t\tconst error = {\n\t\t\t\t\tcode: response.error,\n\t\t\t\t\tdeveloperMessage: response.errorMessage,\n\t\t\t\t\tmfaEnrollment: response.metadata.mfaEnrollment,\n\t\t\t\t}\n\t\t\t\treturn { ok: false, error, errors: [error] }\n\t\t\t}\n\t\t\treturn createErrorResponse(response.error, response.errorMessage)\n\t\t}\n\n\t\treturn {\n\t\t\tok: true,\n\t\t\terrors: [],\n\t\t\tresult: await this.signInResponseFactory.createResponse(response.result, context),\n\t\t}\n\t}\n\n\tasync createSessionToken(parent: any, args: MutationCreateSessionTokenArgs, context: TenantResolverContext): Promise<CreateSessionTokenResponse> {\n\t\tawait context.requireAccess({\n\t\t\taction: PermissionActions.PERSON_CREATE_SESSION_KEY(),\n\t\t\tmessage: 'You are not allowed to create a session key',\n\t\t})\n\t\tlet identifier: PersonUniqueIdentifier\n\t\tif (args.email) {\n\t\t\tidentifier = { type: 'email', email: args.email }\n\t\t} else if (args.personId) {\n\t\t\tidentifier = { type: 'id', id: args.personId }\n\t\t} else {\n\t\t\tthrow new UserInputError(`Please provide either email or personId`)\n\t\t}\n\n\t\tconst response = await this.signInManager.createSessionToken(\n\t\t\tcontext.db,\n\t\t\tidentifier,\n\t\t\targs.expiration || undefined,\n\t\t\tasync person =>\n\t\t\t\tawait context.requireAccess({\n\t\t\t\t\taction: PermissionActions.PERSON_CREATE_SESSION_KEY(person.roles),\n\t\t\t\t\tmessage: 'You are not allowed to create a session key for this person.',\n\t\t\t\t}),\n\t\t\tcontext.httpInfo,\n\t\t\targs.options?.trustForwardedClientInfo === true && context.trustForwardedInfo,\n\t\t)\n\t\tawait context.logAuthAction({\n\t\t\ttype: 'create_session_token',\n\t\t\tresponse: response,\n\t\t})\n\n\t\tif (!response.ok) {\n\t\t\treturn createErrorResponse(response.error, response.errorMessage)\n\t\t}\n\n\t\treturn {\n\t\t\tok: true,\n\t\t\tresult: await this.signInResponseFactory.createResponse(response.result, context),\n\t\t}\n\t}\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"SignInMutationResolver.js","sources":["../../../../../../../packages/engine-tenant-api/src/resolvers/mutation/person/SignInMutationResolver.ts"],"sourcesContent":["import {\n\tCreateSessionTokenResponse,\n\tMutationCreateSessionTokenArgs,\n\tMutationResolvers,\n\tMutationSignInArgs,\n\tSignInResponse,\n} from '../../../schema/index.js'\nimport { TenantResolverContext } from '../../TenantResolverContext.js'\nimport { ConfigurationQuery, PermissionActions, PersonUniqueIdentifier, RateLimiter, SignInManager } from '../../../model/index.js'\nimport { createErrorResponse } from '../../errorUtils.js'\nimport { SignInResponseFactory } from '../../responseHelpers/SignInResponseFactory.js'\nimport { UserInputError } from '@contember/graphql-utils'\nimport { NextLoginAttemptQuery } from '../../../model/queries/authLog/NextLoginAttemptQuery.js'\nimport { ResponseError } from '../../../model/utils/Response.js'\n\nexport class SignInMutationResolver implements MutationResolvers {\n\tconstructor(\n\t\tprivate readonly signInManager: SignInManager,\n\t\tprivate readonly signInResponseFactory: SignInResponseFactory,\n\t\tprivate readonly rateLimiter: RateLimiter,\n\t) {}\n\n\tasync signIn(parent: any, args: MutationSignInArgs, context: TenantResolverContext): Promise<SignInResponse> {\n\t\tawait context.requireAccess({\n\t\t\taction: PermissionActions.PERSON_SIGN_IN,\n\t\t\tmessage: 'You are not allowed to sign in',\n\t\t})\n\n\t\tconst configuration = await context.db.queryHandler.fetch(new ConfigurationQuery(context.db.providers))\n\n\t\tconst ipGate = await this.rateLimiter.consume(context.db, 'login_per_ip', context.httpInfo.ip, configuration)\n\t\tif (!ipGate.ok) {\n\t\t\treturn createErrorResponse(\n\t\t\t\tnew ResponseError('RATE_LIMIT_EXCEEDED', `Too many sign-in attempts from this IP. Retry after ${ipGate.retryAfterSeconds}s.`, {\n\t\t\t\t\tretryAfter: ipGate.retryAfterSeconds,\n\t\t\t\t}),\n\t\t\t)\n\t\t}\n\n\t\tconst nextAllowedSignIn = await context.db.queryHandler.fetch(new NextLoginAttemptQuery(args.email))\n\t\tif (nextAllowedSignIn > new Date()) {\n\t\t\treturn createErrorResponse(\n\t\t\t\tnew ResponseError('RATE_LIMIT_EXCEEDED', `Too many attempts, please try again later.`, {\n\t\t\t\t\tretryAfter: Math.ceil((nextAllowedSignIn.getTime() - Date.now()) / 1000),\n\t\t\t\t}),\n\t\t\t)\n\t\t}\n\n\t\tconst response = await this.signInManager.signIn(\n\t\t\tcontext.db,\n\t\t\targs.email,\n\t\t\targs.password,\n\t\t\targs.expiration || undefined,\n\t\t\targs.otpToken || undefined,\n\t\t\tcontext.httpInfo,\n\t\t\targs.options?.trustForwardedClientInfo === true && context.trustForwardedInfo,\n\t\t\targs.backupCode || undefined,\n\t\t\tconfiguration,\n\t\t\tcontext.httpInfo.geoCountry,\n\t\t)\n\t\tawait context.logAuthAction({\n\t\t\ttype: 'login',\n\t\t\tresponse,\n\t\t})\n\t\tif (response.ok && response.result.usedBackupCode) {\n\t\t\tawait context.logAuthAction({\n\t\t\t\ttype: 'backup_code_used',\n\t\t\t\tresponse,\n\t\t\t\tpersonId: response.result.person.id,\n\t\t\t})\n\t\t}\n\t\tif (!response.ok && response.metadata?.emailOtpSent) {\n\t\t\tawait context.logAuthAction({\n\t\t\t\ttype: 'email_otp_sent',\n\t\t\t\tresponse,\n\t\t\t})\n\t\t}\n\t\tif (!response.ok && response.metadata?.mfaEnrollmentRequired) {\n\t\t\tawait context.logAuthAction({\n\t\t\t\ttype: 'mfa_enrollment_required',\n\t\t\t\tresponse,\n\t\t\t})\n\t\t}\n\t\t// A03 anomaly audit. The risk scoring runs in SignInManager; the audit\n\t\t// entries are emitted here at the resolver layer (per convention), with the\n\t\t// score + reasons snapshot in event_data.\n\t\tif (response.ok && response.result.unusualLoginDetected) {\n\t\t\tawait context.logAuthAction({\n\t\t\t\ttype: 'unusual_login_detected',\n\t\t\t\tresponse,\n\t\t\t\tpersonId: response.result.person.id,\n\t\t\t\teventData: response.result.risk ? { ...response.result.risk } : undefined,\n\t\t\t})\n\t\t}\n\t\tif (!response.ok && response.metadata?.stepUpRequired) {\n\t\t\tawait context.logAuthAction({\n\t\t\t\ttype: 'unusual_login_detected',\n\t\t\t\tresponse,\n\t\t\t\teventData: response.metadata.risk ? { ...response.metadata.risk } : undefined,\n\t\t\t})\n\t\t\tawait context.logAuthAction({\n\t\t\t\ttype: 'step_up_required',\n\t\t\t\tresponse,\n\t\t\t\teventData: response.metadata.risk ? { ...response.metadata.risk } : undefined,\n\t\t\t})\n\t\t}\n\n\t\tif (!response.ok) {\n\t\t\tconst { revealUserExists, revealLoginMethod } = configuration.login\n\t\t\tconst collapseAll = !revealUserExists\n\t\t\t\t&& ['NO_PASSWORD_SET', 'PERSON_DISABLED', 'INVALID_PASSWORD', 'UNKNOWN_EMAIL', 'IDP_REQUIRED'].includes(response.error)\n\t\t\tconst collapseMethodOnly = revealUserExists\n\t\t\t\t&& !revealLoginMethod\n\t\t\t\t&& ['NO_PASSWORD_SET', 'INVALID_PASSWORD', 'IDP_REQUIRED'].includes(response.error)\n\t\t\tif (collapseAll || collapseMethodOnly) {\n\t\t\t\treturn createErrorResponse('INVALID_CREDENTIALS', 'Invalid credentials')\n\t\t\t}\n\t\t\t// Preserve the additive mfaEnrollment payload on MFA_ENROLLMENT_REQUIRED (A06).\n\t\t\tif (response.error === 'MFA_ENROLLMENT_REQUIRED' && response.metadata?.mfaEnrollment) {\n\t\t\t\tconst error = {\n\t\t\t\t\tcode: response.error,\n\t\t\t\t\tdeveloperMessage: response.errorMessage,\n\t\t\t\t\tmfaEnrollment: response.metadata.mfaEnrollment,\n\t\t\t\t}\n\t\t\t\treturn { ok: false, error, errors: [error] }\n\t\t\t}\n\t\t\treturn createErrorResponse(response.error, response.errorMessage)\n\t\t}\n\n\t\treturn {\n\t\t\tok: true,\n\t\t\terrors: [],\n\t\t\tresult: await this.signInResponseFactory.createResponse(response.result, context),\n\t\t}\n\t}\n\n\tasync createSessionToken(parent: any, args: MutationCreateSessionTokenArgs, context: TenantResolverContext): Promise<CreateSessionTokenResponse> {\n\t\tawait context.requireAccess({\n\t\t\taction: PermissionActions.PERSON_CREATE_SESSION_KEY(),\n\t\t\tmessage: 'You are not allowed to create a session key',\n\t\t})\n\t\tlet identifier: PersonUniqueIdentifier\n\t\tif (args.email) {\n\t\t\tidentifier = { type: 'email', email: args.email }\n\t\t} else if (args.personId) {\n\t\t\tidentifier = { type: 'id', id: args.personId }\n\t\t} else {\n\t\t\tthrow new UserInputError(`Please provide either email or personId`)\n\t\t}\n\n\t\tconst response = await this.signInManager.createSessionToken(\n\t\t\tcontext.db,\n\t\t\tidentifier,\n\t\t\targs.expiration || undefined,\n\t\t\tasync person =>\n\t\t\t\tawait context.requireAccess({\n\t\t\t\t\taction: PermissionActions.PERSON_CREATE_SESSION_KEY(person.roles),\n\t\t\t\t\tmessage: 'You are not allowed to create a session key for this person.',\n\t\t\t\t}),\n\t\t\tcontext.httpInfo,\n\t\t\targs.options?.trustForwardedClientInfo === true && context.trustForwardedInfo,\n\t\t)\n\t\tawait context.logAuthAction({\n\t\t\ttype: 'create_session_token',\n\t\t\tresponse: response,\n\t\t})\n\n\t\tif (!response.ok) {\n\t\t\treturn createErrorResponse(response.error, response.errorMessage)\n\t\t}\n\n\t\treturn {\n\t\t\tok: true,\n\t\t\tresult: await this.signInResponseFactory.createResponse(response.result, context),\n\t\t}\n\t}\n}\n"],"names":[],"mappings":"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"}
@@ -565,6 +565,11 @@ const schema = graphqlTag.gql`
565
565
  MFA_ENROLLMENT_REQUIRED
566
566
  EMAIL_NOT_VERIFIED
567
567
  RATE_LIMIT_EXCEEDED
568
+ """
569
+ The person is linked to an identity provider with
570
+ \`disableLocalAuthentication\`, so password sign-in is not available to them.
571
+ """
572
+ IDP_REQUIRED
568
573
  }
569
574
 
570
575
  type SignInResult implements CommonSignInResult {
@@ -866,6 +871,11 @@ const schema = graphqlTag.gql`
866
871
  NOT_A_PERSON
867
872
  """ The person is not allowed to disconnect their last remaining sign-in method. """
868
873
  LAST_AUTH_METHOD
874
+ """
875
+ The provider has \`disableLocalAuthentication\`, so this connection is the only way
876
+ the person may sign in and cannot be removed by them — not even while the provider is disabled.
877
+ """
878
+ IDP_REQUIRED
869
879
  }
870
880
 
871
881
  type IdentityProvider {
@@ -892,6 +902,13 @@ const schema = graphqlTag.gql`
892
902
  Defaults to false.
893
903
  """
894
904
  assumeEmailVerified: Boolean!
905
+ """
906
+ When true, a person linked to this provider may authenticate ONLY through it:
907
+ password sign-in and passwordless sign-in are refused and no password-reset
908
+ mail is sent. Disabling the provider lifts the restriction, which is the
909
+ break-glass for an IdP outage. Defaults to false.
910
+ """
911
+ disableLocalAuthentication: Boolean!
895
912
  }
896
913
 
897
914
  input IDPOptions {
@@ -900,6 +917,7 @@ const schema = graphqlTag.gql`
900
917
  initReturnsConfig: Boolean
901
918
  requireVerifiedEmail: Boolean
902
919
  assumeEmailVerified: Boolean
920
+ disableLocalAuthentication: Boolean
903
921
  }
904
922
 
905
923
  # === passwordless sign in ===
@@ -921,6 +939,11 @@ const schema = graphqlTag.gql`
921
939
  PASSWORDLESS_DISABLED
922
940
  INVALID_CAPTCHA
923
941
  RATE_LIMIT_EXCEEDED
942
+ """
943
+ The person is linked to an identity provider with
944
+ \`disableLocalAuthentication\`, so passwordless sign-in is not available to them.
945
+ """
946
+ IDP_REQUIRED
924
947
  }
925
948
 
926
949
  type InitSignInPasswordlessResult {
@@ -958,6 +981,11 @@ const schema = graphqlTag.gql`
958
981
  OTP_REQUIRED
959
982
  INVALID_OTP_TOKEN
960
983
  MFA_ENROLLMENT_REQUIRED
984
+ """
985
+ The person became linked to an identity provider with
986
+ \`disableLocalAuthentication\` after this magic link was issued.
987
+ """
988
+ IDP_REQUIRED
961
989
  }
962
990
 
963
991
  type SignInPasswordlessResult implements CommonSignInResult {