@things-factory/auth-base 10.1.278 → 10.1.280

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 (284) hide show
  1. package/package.json +24 -7
  2. package/CHANGELOG.md +0 -55
  3. package/dist-client/actions/auth.js.map +0 -1
  4. package/dist-client/apply-domain-theme.js.map +0 -1
  5. package/dist-client/auth.js.map +0 -1
  6. package/dist-client/bootstrap.js.map +0 -1
  7. package/dist-client/directive/privileged.js.map +0 -1
  8. package/dist-client/domain-required.js.map +0 -1
  9. package/dist-client/index.js.map +0 -1
  10. package/dist-client/profiled.js.map +0 -1
  11. package/dist-client/reducers/auth.js.map +0 -1
  12. package/dist-client/tsconfig.tsbuildinfo +0 -1
  13. package/dist-client/verify-webauthn.js.map +0 -1
  14. package/dist-server/ai-entry/access-assistant.js.map +0 -1
  15. package/dist-server/ai-entry/access-entry-targets.js.map +0 -1
  16. package/dist-server/ai-entry/entry-target-shape.js.map +0 -1
  17. package/dist-server/constants/error-code.js.map +0 -1
  18. package/dist-server/constants/error-message.js.map +0 -1
  19. package/dist-server/constants/max-age.js.map +0 -1
  20. package/dist-server/controllers/access-request.js.map +0 -1
  21. package/dist-server/controllers/account-mail.js.map +0 -1
  22. package/dist-server/controllers/auth.js.map +0 -1
  23. package/dist-server/controllers/change-pwd.js.map +0 -1
  24. package/dist-server/controllers/confirm-identity.js.map +0 -1
  25. package/dist-server/controllers/delete-user.js.map +0 -1
  26. package/dist-server/controllers/passkey.js.map +0 -1
  27. package/dist-server/controllers/password-setup.js.map +0 -1
  28. package/dist-server/controllers/profile.js.map +0 -1
  29. package/dist-server/controllers/reset-password.js.map +0 -1
  30. package/dist-server/controllers/signin.js.map +0 -1
  31. package/dist-server/controllers/signup.js.map +0 -1
  32. package/dist-server/controllers/unlock-user.js.map +0 -1
  33. package/dist-server/controllers/utils/find-verification-token.js.map +0 -1
  34. package/dist-server/controllers/utils/make-verification-token.js.map +0 -1
  35. package/dist-server/controllers/utils/password-rule.js.map +0 -1
  36. package/dist-server/controllers/utils/save-verification-token.js.map +0 -1
  37. package/dist-server/controllers/verification.js.map +0 -1
  38. package/dist-server/errors/auth-error.js.map +0 -1
  39. package/dist-server/errors/index.js.map +0 -1
  40. package/dist-server/errors/user-domain-not-match-error.js.map +0 -1
  41. package/dist-server/index.js.map +0 -1
  42. package/dist-server/middlewares/audit-scope-middleware.js.map +0 -1
  43. package/dist-server/middlewares/authenticate-401-middleware.js.map +0 -1
  44. package/dist-server/middlewares/bypass-signin-middleware.js.map +0 -1
  45. package/dist-server/middlewares/domain-authenticate-middleware.js.map +0 -1
  46. package/dist-server/middlewares/graphql-authenticate-middleware.js.map +0 -1
  47. package/dist-server/middlewares/index.js.map +0 -1
  48. package/dist-server/middlewares/jwt-authenticate-middleware.js.map +0 -1
  49. package/dist-server/middlewares/signin-middleware.js.map +0 -1
  50. package/dist-server/middlewares/verify-recaptcha-middleware.js.map +0 -1
  51. package/dist-server/migrations/1548206416130-SeedUser.js.map +0 -1
  52. package/dist-server/migrations/1566805283882-SeedPrivilege.js.map +0 -1
  53. package/dist-server/migrations/1745000000000-MigrateDomainOwnersToTable.js.map +0 -1
  54. package/dist-server/migrations/index.js.map +0 -1
  55. package/dist-server/role-tasks.js.map +0 -1
  56. package/dist-server/router/auth-checkin-router.js.map +0 -1
  57. package/dist-server/router/auth-private-process-router.js.map +0 -1
  58. package/dist-server/router/auth-public-process-router.js.map +0 -1
  59. package/dist-server/router/auth-signin-router.js.map +0 -1
  60. package/dist-server/router/auth-signup-router.js.map +0 -1
  61. package/dist-server/router/index.js.map +0 -1
  62. package/dist-server/router/oauth2/index.js.map +0 -1
  63. package/dist-server/router/oauth2/oauth2-authorize-router.js.map +0 -1
  64. package/dist-server/router/oauth2/oauth2-router.js.map +0 -1
  65. package/dist-server/router/oauth2/oauth2-server.js.map +0 -1
  66. package/dist-server/router/oauth2/passport-oauth2-client-password.js.map +0 -1
  67. package/dist-server/router/oauth2/passport-refresh-token.js.map +0 -1
  68. package/dist-server/router/path-base-domain-router.js.map +0 -1
  69. package/dist-server/router/site-root-router.js.map +0 -1
  70. package/dist-server/router/webauthn-router.js.map +0 -1
  71. package/dist-server/routes.js.map +0 -1
  72. package/dist-server/service/access-request/access-request-mutation.js.map +0 -1
  73. package/dist-server/service/access-request/access-request-query.js.map +0 -1
  74. package/dist-server/service/access-request/access-request-types.js.map +0 -1
  75. package/dist-server/service/access-request/access-request.js.map +0 -1
  76. package/dist-server/service/access-request/index.js.map +0 -1
  77. package/dist-server/service/app-binding/app-binding-mutation.js.map +0 -1
  78. package/dist-server/service/app-binding/app-binding-query.js.map +0 -1
  79. package/dist-server/service/app-binding/app-binding-types.js.map +0 -1
  80. package/dist-server/service/app-binding/app-binding.js.map +0 -1
  81. package/dist-server/service/app-binding/index.js.map +0 -1
  82. package/dist-server/service/appliance/appliance-mutation.js.map +0 -1
  83. package/dist-server/service/appliance/appliance-query.js.map +0 -1
  84. package/dist-server/service/appliance/appliance-types.js.map +0 -1
  85. package/dist-server/service/appliance/appliance.js.map +0 -1
  86. package/dist-server/service/appliance/index.js.map +0 -1
  87. package/dist-server/service/application/application-mutation.js.map +0 -1
  88. package/dist-server/service/application/application-query.js.map +0 -1
  89. package/dist-server/service/application/application-types.js.map +0 -1
  90. package/dist-server/service/application/application.js.map +0 -1
  91. package/dist-server/service/application/index.js.map +0 -1
  92. package/dist-server/service/audit/audit-action.js.map +0 -1
  93. package/dist-server/service/audit/audit-kinds.js.map +0 -1
  94. package/dist-server/service/audit/audit-query.js.map +0 -1
  95. package/dist-server/service/audit/audit-recorder.js.map +0 -1
  96. package/dist-server/service/audit/audit-request.js.map +0 -1
  97. package/dist-server/service/audit/audit-retention.js.map +0 -1
  98. package/dist-server/service/audit/audit-subscriber.js.map +0 -1
  99. package/dist-server/service/audit/audit-target.js.map +0 -1
  100. package/dist-server/service/audit/audit-types.js.map +0 -1
  101. package/dist-server/service/audit/audit-work.js.map +0 -1
  102. package/dist-server/service/audit/index.js.map +0 -1
  103. package/dist-server/service/auth-provider/auth-provider-mutation.js.map +0 -1
  104. package/dist-server/service/auth-provider/auth-provider-parameter-spec.js.map +0 -1
  105. package/dist-server/service/auth-provider/auth-provider-query.js.map +0 -1
  106. package/dist-server/service/auth-provider/auth-provider-type.js.map +0 -1
  107. package/dist-server/service/auth-provider/auth-provider.js.map +0 -1
  108. package/dist-server/service/auth-provider/index.js.map +0 -1
  109. package/dist-server/service/domain-generator/domain-generator-mutation.js.map +0 -1
  110. package/dist-server/service/domain-generator/domain-generator-types.js.map +0 -1
  111. package/dist-server/service/domain-generator/index.js.map +0 -1
  112. package/dist-server/service/domain-link/domain-link-mutation.js.map +0 -1
  113. package/dist-server/service/domain-link/domain-link-query.js.map +0 -1
  114. package/dist-server/service/domain-link/domain-link-types.js.map +0 -1
  115. package/dist-server/service/domain-link/domain-link.js.map +0 -1
  116. package/dist-server/service/domain-link/index.js.map +0 -1
  117. package/dist-server/service/domain-owner/domain-owner-mutation.js.map +0 -1
  118. package/dist-server/service/domain-owner/domain-owner-query.js.map +0 -1
  119. package/dist-server/service/domain-owner/domain-owner.js.map +0 -1
  120. package/dist-server/service/domain-owner/index.js.map +0 -1
  121. package/dist-server/service/granted-role/granted-role-mutation.js.map +0 -1
  122. package/dist-server/service/granted-role/granted-role-query.js.map +0 -1
  123. package/dist-server/service/granted-role/granted-role.js.map +0 -1
  124. package/dist-server/service/granted-role/index.js.map +0 -1
  125. package/dist-server/service/identity-confirmation/identity-confirmation.js.map +0 -1
  126. package/dist-server/service/index.js.map +0 -1
  127. package/dist-server/service/open-fields.js.map +0 -1
  128. package/dist-server/service/partner/index.js.map +0 -1
  129. package/dist-server/service/partner/partner-mutation.js.map +0 -1
  130. package/dist-server/service/partner/partner-query.js.map +0 -1
  131. package/dist-server/service/partner/partner-types.js.map +0 -1
  132. package/dist-server/service/partner/partner.js.map +0 -1
  133. package/dist-server/service/password-history/index.js.map +0 -1
  134. package/dist-server/service/password-history/password-history.js.map +0 -1
  135. package/dist-server/service/privilege/index.js.map +0 -1
  136. package/dist-server/service/privilege/open-field-rule.js.map +0 -1
  137. package/dist-server/service/privilege/privilege-axis.js.map +0 -1
  138. package/dist-server/service/privilege/privilege-deprecation.js.map +0 -1
  139. package/dist-server/service/privilege/privilege-directive.js.map +0 -1
  140. package/dist-server/service/privilege/privilege-mutation.js.map +0 -1
  141. package/dist-server/service/privilege/privilege-query.js.map +0 -1
  142. package/dist-server/service/privilege/privilege-rejection.js.map +0 -1
  143. package/dist-server/service/privilege/privilege-sync.js.map +0 -1
  144. package/dist-server/service/privilege/privilege-types.js.map +0 -1
  145. package/dist-server/service/privilege/privilege.js.map +0 -1
  146. package/dist-server/service/role/index.js.map +0 -1
  147. package/dist-server/service/role/role-mutation.js.map +0 -1
  148. package/dist-server/service/role/role-privilege-query.js.map +0 -1
  149. package/dist-server/service/role/role-query.js.map +0 -1
  150. package/dist-server/service/role/role-types.js.map +0 -1
  151. package/dist-server/service/role/role.js.map +0 -1
  152. package/dist-server/service/role-template/index.js.map +0 -1
  153. package/dist-server/service/role-template/role-task.js.map +0 -1
  154. package/dist-server/service/role-template/role-template-gap-resolver.js.map +0 -1
  155. package/dist-server/service/role-template/role-template-mutation.js.map +0 -1
  156. package/dist-server/service/role-template/role-template-query.js.map +0 -1
  157. package/dist-server/service/role-template/role-template-types.js.map +0 -1
  158. package/dist-server/service/role-template/role-template.js.map +0 -1
  159. package/dist-server/service/user/domain-query.js.map +0 -1
  160. package/dist-server/service/user/index.js.map +0 -1
  161. package/dist-server/service/user/user-mutation.js.map +0 -1
  162. package/dist-server/service/user/user-query.js.map +0 -1
  163. package/dist-server/service/user/user-types.js.map +0 -1
  164. package/dist-server/service/user/user.js.map +0 -1
  165. package/dist-server/service/users-auth-providers/index.js.map +0 -1
  166. package/dist-server/service/users-auth-providers/users-auth-providers.js.map +0 -1
  167. package/dist-server/service/verification-token/index.js.map +0 -1
  168. package/dist-server/service/verification-token/verification-token.js.map +0 -1
  169. package/dist-server/service/web-auth-credential/index.js.map +0 -1
  170. package/dist-server/service/web-auth-credential/web-auth-credential-mutation.js.map +0 -1
  171. package/dist-server/service/web-auth-credential/web-auth-credential-query.js.map +0 -1
  172. package/dist-server/service/web-auth-credential/web-auth-credential.js.map +0 -1
  173. package/dist-server/tsconfig.tsbuildinfo +0 -1
  174. package/dist-server/types.js.map +0 -1
  175. package/dist-server/utils/accepts.js.map +0 -1
  176. package/dist-server/utils/access-request-review.js.map +0 -1
  177. package/dist-server/utils/access-token-cookie.js.map +0 -1
  178. package/dist-server/utils/account-lock-rule.js.map +0 -1
  179. package/dist-server/utils/assignable-roles.js.map +0 -1
  180. package/dist-server/utils/build-auth-url.js.map +0 -1
  181. package/dist-server/utils/can-user-checkin-domain.js.map +0 -1
  182. package/dist-server/utils/check-permission.js.map +0 -1
  183. package/dist-server/utils/check-user-belongs-domain.js.map +0 -1
  184. package/dist-server/utils/check-user-has-role.js.map +0 -1
  185. package/dist-server/utils/checkin-domain-facts.js.map +0 -1
  186. package/dist-server/utils/checkin-domain-rule.js.map +0 -1
  187. package/dist-server/utils/credential-at-rest.js.map +0 -1
  188. package/dist-server/utils/credential-features.js.map +0 -1
  189. package/dist-server/utils/credential-serial-rule.js.map +0 -1
  190. package/dist-server/utils/domain-ownership.js.map +0 -1
  191. package/dist-server/utils/encrypt-state.js.map +0 -1
  192. package/dist-server/utils/get-aes-256-key.js.map +0 -1
  193. package/dist-server/utils/get-domain-users.js.map +0 -1
  194. package/dist-server/utils/get-secret.js.map +0 -1
  195. package/dist-server/utils/get-user-domains.js.map +0 -1
  196. package/dist-server/utils/lock-duration.js.map +0 -1
  197. package/dist-server/utils/password-reset-rule.js.map +0 -1
  198. package/dist-server/utils/profile-user.js.map +0 -1
  199. package/dist-server/utils/refuse-role-name.js.map +0 -1
  200. package/dist-server/utils/role-name-standing.js.map +0 -1
  201. package/dist-server/utils/same-origin-redirect.js.map +0 -1
  202. package/dist-server/utils/verification-token-rule.js.map +0 -1
  203. package/spec/unit/checkin-domain.spec.ts +0 -307
  204. package/spec/unit/checkin-privilege.spec.ts +0 -205
  205. package/spec/unit/domain-owner-entity.spec.ts +0 -179
  206. package/spec/unit/domain-owner-granted.spec.ts +0 -215
  207. package/spec/unit/domain-owner-migration.spec.ts +0 -236
  208. package/spec/unit/domain-owner-mutation.spec.ts +0 -337
  209. package/spec/unit/domain-ownership.spec.ts +0 -188
  210. package/tests/access-assistant-db.test.ts +0 -155
  211. package/tests/access-entry-targets-db.test.ts +0 -176
  212. package/tests/access-request-db.test.ts +0 -394
  213. package/tests/access-request-pg.test.ts +0 -83
  214. package/tests/access-request-review.test.ts +0 -193
  215. package/tests/account-lock-db.test.ts +0 -216
  216. package/tests/account-lock-rule.test.ts +0 -141
  217. package/tests/account-mail.test.ts +0 -69
  218. package/tests/activate-page-case.test.ts +0 -53
  219. package/tests/add-user-gates.test.ts +0 -34
  220. package/tests/app-binding-db.test.ts +0 -210
  221. package/tests/app-binding-delete-db.test.ts +0 -188
  222. package/tests/appliance-credential-state-db.test.ts +0 -207
  223. package/tests/appliance-delete-db.test.ts +0 -119
  224. package/tests/appliance-revoke-db.test.ts +0 -294
  225. package/tests/appliance-schema.test.ts +0 -129
  226. package/tests/application-delete-db.test.ts +0 -190
  227. package/tests/application-token-payload-db.test.ts +0 -185
  228. package/tests/assignable-roles-db.test.ts +0 -219
  229. package/tests/audit-db.test.ts +0 -410
  230. package/tests/audit-work.test.ts +0 -58
  231. package/tests/auth-base-role-tasks.test.ts +0 -58
  232. package/tests/checkin-domain-rule.test.ts +0 -128
  233. package/tests/checkin-privilege-seam-db.test.ts +0 -158
  234. package/tests/compiled.ts +0 -41
  235. package/tests/confirm-identity-db.test.ts +0 -85
  236. package/tests/credential-at-rest-db.test.ts +0 -246
  237. package/tests/credential-features-off-db.test.ts +0 -189
  238. package/tests/credential-serial-rule.test.ts +0 -84
  239. package/tests/declare-privileges.test.ts +0 -29
  240. package/tests/domain-assignees-db.test.ts +0 -95
  241. package/tests/domain-inheritance-sentinel.test.ts +0 -93
  242. package/tests/domain-required.test.ts +0 -127
  243. package/tests/has-privilege-db.test.ts +0 -198
  244. package/tests/identity-confirmation.test.ts +0 -146
  245. package/tests/list-inheritance-db.test.ts +0 -127
  246. package/tests/lock-recovery.test.ts +0 -206
  247. package/tests/mail-request-answers-db.test.ts +0 -142
  248. package/tests/open-doors.test.ts +0 -121
  249. package/tests/open-fields-enforcement.test.ts +0 -215
  250. package/tests/open-fields-framework.test.ts +0 -212
  251. package/tests/open-redirect-db.test.ts +0 -143
  252. package/tests/passkey-authenticator.ts +0 -104
  253. package/tests/passkey-db.test.ts +0 -295
  254. package/tests/passkey-signin-route-db.test.ts +0 -139
  255. package/tests/password-hashing.test.ts +0 -131
  256. package/tests/password-persistence.test.ts +0 -147
  257. package/tests/password-reset-rule-db.test.ts +0 -159
  258. package/tests/password-setup-db.test.ts +0 -215
  259. package/tests/permission-gate.test.ts +0 -228
  260. package/tests/picture-reducer.test.ts +0 -25
  261. package/tests/privilege-axis.test.ts +0 -157
  262. package/tests/privilege-deprecation.test.ts +0 -65
  263. package/tests/privilege-directive.test.ts +0 -332
  264. package/tests/privilege-rejection.test.ts +0 -104
  265. package/tests/privilege-sync-db.test.ts +0 -58
  266. package/tests/profile-user.test.ts +0 -24
  267. package/tests/refusal-cause.test.ts +0 -80
  268. package/tests/refusals-have-korean.test.ts +0 -24
  269. package/tests/request-holds.test.ts +0 -53
  270. package/tests/role-inheritance-db.test.ts +0 -286
  271. package/tests/role-mutation-db.test.ts +0 -293
  272. package/tests/role-name-lookup-sentinel.test.ts +0 -79
  273. package/tests/role-name-standing.test.ts +0 -83
  274. package/tests/role-privileges-db.test.ts +0 -252
  275. package/tests/role-task.test.ts +0 -168
  276. package/tests/role-template-gap-db.test.ts +0 -268
  277. package/tests/role-template-seed-db.test.ts +0 -180
  278. package/tests/role-template.test.ts +0 -162
  279. package/tests/role-users-gate.test.ts +0 -22
  280. package/tests/signin-policy.test.ts +0 -200
  281. package/tests/signup-duplicate-db.test.ts +0 -68
  282. package/tests/token-issuance.test.ts +0 -244
  283. package/tests/user-roles-field-db.test.ts +0 -134
  284. package/tests/verification-token-db.test.ts +0 -285
@@ -1 +0,0 @@
1
- {"version":3,"file":"access-request.js","sourceRoot":"","sources":["../../server/controllers/access-request.ts"],"names":[],"mappings":"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","sourcesContent":["import { EntityManager, In } from 'typeorm'\n\nimport { applyDomainInheritanceFilter, Domain, getRepository, InheritedValueType, Refusal, whenCommitted } from '@things-factory/shell'\n\nimport { AccessRequest, AccessRequestStatus } from '../service/access-request/access-request.js'\nimport { Privilege } from '../service/privilege/privilege.js'\nimport { Role } from '../service/role/role.js'\nimport { User, UserStatus } from '../service/user/user.js'\nimport {\n AccessDecisionKind,\n DecisionFacts,\n DecisionIssue,\n effectiveState,\n expiryFrom,\n isBlocked,\n reviewAccessDecision,\n ROLE_GRANTING_PRIVILEGE\n} from '../utils/access-request-review.js'\nimport { assignRoles, findAssignableRoles, mayChangeParentRoles, whereAssignableBy } from '../utils/assignable-roles.js'\nimport { judgePermission } from '../utils/check-permission.js'\nimport { declaredTaskKeys, roleTaskById } from '../service/role-template/role-task.js'\nimport { findDomainOwnerUserIds, isDomainOwner } from '../utils/domain-ownership.js'\n\n/*\n * Access requests - asking, withdrawing, reviewing and deciding (ADR-0103, docs/design/access-request.md).\n *\n * Every door reads the domain and the person from the request, never from an argument.\n */\n\nexport type NewAccessRequestInput = { category: string; privilege: string; operationName?: string; taskId?: string; reason?: string }\nexport type AccessDecisionInput = { requestId: string; decision: AccessDecisionKind; roleId?: string; reason?: string }\n\n/** Refuses a decision whose review blocks it - the first blocking issue names the refusal, all codes go along. */\nexport class AccessDecisionRefused extends Refusal {\n constructor(readonly issues: DecisionIssue[]) {\n const first = issues.find(issue => issue.blocking)\n super(`access-decision-${first.code}`, { codes: issues.filter(i => i.blocking).map(i => i.code).join(',') }, `The decision cannot be made: ${first.code}.`)\n }\n}\n\n/* ── asking ──────────────────────────────────────────────────────────────── */\n\n/**\n * The caller asks for `category:privilege` in this domain.\n *\n * The server judges again whether a role would open it - the `because` a screen got with its refusal is not\n * taken on trust (ADR-0103 decision 2). Asking again while one is pending adds to that one.\n */\nexport async function requestAccess(input: NewAccessRequestInput, context: ResolverContext): Promise<AccessRequest> {\n const { domain, user, tx, unsafeIP, prohibitedPrivileges } = context.state\n const { category, privilege } = input\n const now = new Date()\n\n if (!category || !privilege) {\n throw new Refusal('access-request-names-nothing', {}, 'An access request names a category and a privilege.')\n }\n\n const known = await getRepository(Privilege, tx).findOneBy({ category, name: privilege })\n if (!known) {\n throw new Refusal('access-request-unknown-privilege', { privilege: `${category}:${privilege}` }, `There is no privilege ${category}:${privilege}.`)\n }\n\n /* The task asked for is one that holds the refused privilege - a request for other work is not this request. */\n const taskId = input.taskId || undefined\n if (taskId) {\n const task = roleTaskById(taskId)\n if (!task || !task.grants.some(grant => grant.category === category && grant.axes.includes(privilege))) {\n throw new Refusal('access-request-task-mismatch', { task: taskId, privilege: `${category}:${privilege}` }, `The task ${taskId} does not hold ${category}:${privilege}.`)\n }\n }\n\n /* Asking is not a reflex - the reason is what the person deciding weighs, so there is no request without one. */\n const reason = input.reason?.trim()\n if (!reason) {\n throw new Refusal('access-request-needs-reason', {}, 'Write why you need it.')\n }\n\n const judgement = await judgePermission({ category, privilege }, user, domain, unsafeIP, prohibitedPrivileges)\n if (judgement.permitted === true) {\n throw new Refusal('access-request-already-holds', { privilege: `${category}:${privilege}` }, `You already have ${category}:${privilege}.`)\n }\n if (judgement.because !== 'role') {\n throw new Refusal(\n 'access-request-not-by-role',\n { privilege: `${category}:${privilege}`, because: judgement.because },\n `A role would not open ${category}:${privilege} here (${judgement.because}).`\n )\n }\n\n await holdRequester(user.id, tx)\n\n /*\n * Denied lately: asking again is allowed, the same words are not - the person deciding has already answered them.\n * The screen shows that answer before the person writes (ACCESS_REQUEST_DENIAL_DAYS).\n */\n const denied = await latestDenial(domain, user.id, category, privilege, now, tx)\n if (denied && (denied.reason || '').trim() === reason) {\n throw new Refusal(\n 'access-request-same-reason-as-denied',\n { reason: denied.decisionReason || '' },\n 'This was denied for the same reason lately - write what has changed.'\n )\n }\n\n const repository = getRepository(AccessRequest, tx)\n const pending = await findMine(repository, domain.id, user.id, category, privilege, AccessRequestStatus.PENDING, tx)\n\n if (pending && effectiveState(pending.state, pending.expiresAt, now) === AccessRequestStatus.PENDING) {\n pending.count = (pending.count || 1) + 1\n pending.lastRequestedAt = now\n pending.reason = reason\n if (input.operationName) pending.operationName = input.operationName\n /* Merged - the people deciding were told once, when it was first asked. */\n return await repository.save(pending)\n }\n\n if (pending) {\n pending.state = AccessRequestStatus.EXPIRED\n await repository.save(pending)\n }\n\n /*\n * Withdrawn lately: the same request comes back - one row, and nobody is told again. Asking and withdrawing in a loop\n * would otherwise make a row and a notification to every decider on each turn.\n */\n const withdrawn = await findMine(repository, domain.id, user.id, category, privilege, AccessRequestStatus.WITHDRAWN, tx)\n if (withdrawn && now.getTime() - new Date(withdrawn.updatedAt).getTime() <= ACCESS_REQUEST_QUIET_HOURS * 60 * 60 * 1000) {\n withdrawn.state = AccessRequestStatus.PENDING\n withdrawn.count = (withdrawn.count || 1) + 1\n withdrawn.lastRequestedAt = now\n withdrawn.reason = reason\n withdrawn.expiresAt = expiryFrom(now)\n if (input.operationName) withdrawn.operationName = input.operationName\n return await repository.save(withdrawn)\n }\n\n const created = await repository.save({\n domain,\n requester: user,\n category,\n privilege,\n operationName: input.operationName,\n taskId,\n reason,\n count: 1,\n lastRequestedAt: now,\n state: AccessRequestStatus.PENDING,\n expiresAt: expiryFrom(now)\n } as Partial<AccessRequest>)\n\n const deciders = await findDeciderIds(domain, user.id, tx)\n tellLater(context, 'notify the people who decide access requests', async () =>\n notifyEach(context, deciders, 'access-request.notify.asked', {\n name: user.name || user.email || user.username,\n privilege: `${category}:${privilege}`\n }, `access-requests?id=${created.id}`)\n )\n\n return created\n}\n\n/** A request withdrawn within this many hours comes back as the same row, without telling anyone again. */\nexport const ACCESS_REQUEST_QUIET_HOURS = 24\n\n/**\n * The caller's latest request of one privilege in one state. On a database with row locks it is a locking read: after\n * `holdRequester` waits, a plain read under REPEATABLE READ (MySQL's default) would still see the snapshot taken before\n * the other ask committed, and insert a second pending row. A locking read sees the committed row.\n */\nasync function findMine(repository: any, domainId: string, requesterId: string, category: string, privilege: string, state: AccessRequestStatus, tx?: EntityManager) {\n const qb = repository\n .createQueryBuilder('mine')\n .leftJoinAndSelect('mine.domain', 'mine_domain')\n .leftJoinAndSelect('mine.requester', 'mine_requester')\n .where('mine.domain = :domainId AND mine.requester = :requesterId', { domainId, requesterId })\n .andWhere('mine.category = :category AND mine.privilege = :privilege AND mine.state = :state', { category, privilege, state })\n .orderBy('mine.updatedAt', 'DESC')\n /* Lowercase alias: postgres folds the unquoted name in `FOR UPDATE OF`, and only the request row is locked - not the outer-joined ones. */\n if (tx && !(tx.connection?.options?.type as string)?.includes('sqlite')) qb.setLock('pessimistic_write', undefined, ['mine'])\n return await qb.getOne()\n}\n\n/**\n * At most one pending request per person and privilege, under concurrency. A partial unique index would say it and\n * is not portable, so the person's own row is locked for the rest of the transaction: a second ask of the same\n * moment waits, then finds the first and adds to it (measured on postgres - access-request-pg.test.ts).\n *\n * sqlite has no row locks and one connection, so its transactions already run one after another.\n */\nasync function holdRequester(userId: string, tx?: EntityManager) {\n if (!tx || (tx.connection?.options?.type as string)?.includes('sqlite')) return\n await tx.getRepository(User).createQueryBuilder('HELD').setLock('pessimistic_write').where('HELD.id = :id', { id: userId }).getOne()\n}\n\n/** How long a denial is shown back to the person and holds against the same words (questions Q9 · 5). */\nexport const ACCESS_REQUEST_DENIAL_DAYS = 7\n\n/** The latest denial of this person and privilege within ACCESS_REQUEST_DENIAL_DAYS, if any. */\nexport async function latestDenial(domain: Domain, requesterId: string, category: string, privilege: string, now: Date, tx?: EntityManager) {\n const denied = await getRepository(AccessRequest, tx).findOne({\n where: { domain: { id: domain.id }, requester: { id: requesterId }, category, privilege, state: AccessRequestStatus.DENIED },\n order: { decidedAt: 'DESC' }\n })\n if (!denied?.decidedAt) return null\n return now.getTime() - new Date(denied.decidedAt).getTime() <= ACCESS_REQUEST_DENIAL_DAYS * 24 * 60 * 60 * 1000 ? denied : null\n}\n\n/** The requester takes back their own pending request. */\nexport async function withdrawAccessRequest(id: string, context: ResolverContext): Promise<AccessRequest> {\n const { domain, user, tx } = context.state\n const repository = getRepository(AccessRequest, tx)\n\n const updated = await repository\n .createQueryBuilder()\n .update(AccessRequest)\n .set({ state: AccessRequestStatus.WITHDRAWN })\n .where('id = :id AND domain_id = :domainId AND requester_id = :userId AND state = :pending', {\n id,\n domainId: domain.id,\n userId: user.id,\n pending: AccessRequestStatus.PENDING\n })\n .execute()\n\n if (!updated.affected) {\n throw new Refusal('access-request-not-withdrawable', { id }, 'There is no pending request of yours to withdraw.')\n }\n return await repository.findOne({ where: { id }, relations: ['domain', 'requester'] })\n}\n\n/* ── who decides ─────────────────────────────────────────────────────────── */\n\n/**\n * The people who can decide: domain owners and holders of the role-granting privilege - the same pair the\n * decision door declares (`@privilege(category: \"user\", privilege: \"mutation\", domainOwnerGranted: true)`).\n * The requester is never one of them.\n */\nexport async function findDeciderIds(domain: Domain, requesterId: string, tx?: EntityManager): Promise<string[]> {\n const owners = await findDomainOwnerUserIds(domain, tx)\n\n const qb = getRepository(User, tx)\n .createQueryBuilder('USER')\n .select('USER.id', 'id')\n .innerJoin('USER.domains', 'MEMBER', 'MEMBER.id = :memberOf', { memberOf: domain.id })\n .innerJoin('USER.roles', 'role')\n .innerJoin('role.privileges', 'PRIVILEGE')\n .where('PRIVILEGE.category = :grantCategory', { grantCategory: ROLE_GRANTING_PRIVILEGE.category })\n .andWhere('PRIVILEGE.name = :grantName', { grantName: ROLE_GRANTING_PRIVILEGE.privilege })\n .andWhere('USER.status = :active', { active: UserStatus.ACTIVATED })\n applyDomainInheritanceFilter(qb, 'role', domain, InheritedValueType.Include)\n const holders = (await qb.getRawMany()).map(row => row.id)\n\n return [...new Set([...owners, ...holders])].filter(id => id && id !== requesterId).sort()\n}\n\n/* ── reviewing ───────────────────────────────────────────────────────────── */\n\n/**\n * Every assignable role that grants `category:privilege`. With a task asked for (ADR-0103 decision 9), the roles that\n * hold the whole task come first; then fewest privileges, then name. One order for the screen, the review and the AI\n * draft card.\n */\nexport async function rolesGranting(category: string, privilege: string, domain: Domain, tx?: EntityManager, taskId?: string | null) {\n const qb = getRepository(Role, tx)\n .createQueryBuilder('ROLE')\n .innerJoin('ROLE.privileges', 'ASKED', 'ASKED.category = :askedCategory AND ASKED.name = :askedName', {\n askedCategory: category,\n askedName: privilege\n })\n const roles = await whereAssignableBy(qb, 'ROLE', domain).getMany()\n if (roles.length === 0) return []\n\n const withPrivileges = await getRepository(Role, tx).find({ where: { id: In(roles.map(role => role.id)) }, relations: ['privileges'] })\n const task = taskId ? roleTaskById(taskId) : undefined\n const wanted = task ? declaredTaskKeys(task) : []\n return withPrivileges\n .map(role => {\n const held = new Set((role.privileges || []).map(p => `${p.category} ${p.name}`))\n return { id: role.id, name: role.name, privilegeCount: role.privileges?.length ?? 0, holdsTask: wanted.length > 0 && wanted.every(key => held.has(key)), role }\n })\n .sort((a, b) => Number(b.holdsTask) - Number(a.holdsTask) || a.privilegeCount - b.privilegeCount || a.name.localeCompare(b.name))\n}\n\n/** The task asked for, as the review reads it - nothing when none was asked or it is no longer registered. */\nfunction taskFacts(taskId?: string | null): DecisionFacts['task'] {\n const task = taskId ? roleTaskById(taskId) : undefined\n if (!task) return null\n return { id: task.id, privileges: declaredTaskKeys(task).map(key => ({ category: key.split(' ')[0], privilege: key.split(' ')[1] })) }\n}\n\n/** The facts `reviewAccessDecision` reads - collected as the person deciding, inside this domain. */\nexport async function collectDecisionFacts(input: AccessDecisionInput, context: ResolverContext): Promise<DecisionFacts> {\n const { domain, user: decider, tx } = context.state\n const now = new Date()\n\n const request = await getRepository(AccessRequest, tx).findOne({\n where: { id: input.requestId, domain: { id: domain.id } },\n relations: ['requester', 'requester.domains']\n })\n\n const base: DecisionFacts = {\n request: null,\n deciderId: decider.id,\n decision: input.decision,\n reason: input.reason,\n requester: null,\n requesterHoldsIt: false,\n candidates: [],\n deciderPrivileges: [],\n deciderIsOwner: false,\n now\n }\n if (!request) return base\n\n const requester = request.requester\n const [candidates, chosen, deciderIsOwner, deciderPrivileges, requesterHoldsIt, deniedBefore] = await Promise.all([\n rolesGranting(request.category, request.privilege, domain, tx, request.taskId),\n input.roleId ? findAssignableRoles([input.roleId], domain, tx) : Promise.resolve([]),\n isDomainOwner(domain, decider, tx),\n User.getPrivilegesByDomain(decider, domain),\n requester ? User.hasPrivilege(request.privilege, request.category, domain, requester) : Promise.resolve(false),\n getRepository(AccessRequest, tx).findOne({\n where: {\n domain: { id: domain.id },\n requester: { id: requester?.id },\n category: request.category,\n privilege: request.privilege,\n state: AccessRequestStatus.DENIED\n },\n relations: ['decidedBy'],\n order: { decidedAt: 'DESC' }\n })\n ])\n\n const role = chosen[0]\n ? await getRepository(Role, tx).findOne({ where: { id: chosen[0].id }, relations: ['privileges'] })\n : null\n const roleIsParents = !!role && !!domain.parentId && role.domainId === domain.parentId\n const deciderMayChangeParentRoles = roleIsParents ? await mayChangeParentRoles(decider, domain, tx) : false\n\n return {\n ...base,\n request: {\n state: request.state as any,\n requesterId: requester?.id,\n category: request.category,\n privilege: request.privilege,\n expiresAt: request.expiresAt\n },\n role: role\n ? { id: role.id, name: role.name, privileges: (role.privileges || []).map(p => ({ category: p.category, privilege: p.name })) }\n : null,\n roleNamedButNotAssignable: !!input.roleId && !role,\n roleIsParents,\n deciderMayChangeParentRoles,\n requester: requester\n ? {\n active: requester.status === UserStatus.ACTIVATED,\n inDomain: (requester.domains || []).some(d => d.id === domain.id)\n }\n : null,\n requesterHoldsIt,\n candidates: candidates.map(({ id, name, privilegeCount, holdsTask }) => ({ id, name, privilegeCount, holdsTask })),\n task: taskFacts(request.taskId),\n deciderIsOwner,\n deciderPrivileges: (deciderPrivileges || []).map((p: any) => ({ category: p.category, privilege: p.privilege })),\n deniedBefore:\n deniedBefore && deniedBefore.id !== request.id\n ? {\n by: deniedBefore.decidedBy?.name || deniedBefore.decidedBy?.email || '',\n at: deniedBefore.decidedAt,\n reason: deniedBefore.decisionReason\n }\n : null\n }\n}\n\nexport async function reviewDecision(input: AccessDecisionInput, context: ResolverContext): Promise<DecisionIssue[]> {\n return reviewAccessDecision(await collectDecisionFacts(input, context))\n}\n\n/* ── deciding ────────────────────────────────────────────────────────────── */\n\n/**\n * Grants or denies. The review runs again here - the card and the screen were shown a moment ago, and another\n * person may have decided since. The state moves PENDING → decided in one conditional update, so two people\n * deciding at once cannot both win.\n */\nexport async function decideAccessRequest(input: AccessDecisionInput, context: ResolverContext): Promise<AccessRequest> {\n const { domain, user: decider, tx } = context.state\n const now = new Date()\n\n const issues = await reviewDecision(input, context)\n if (isBlocked(issues)) {\n throw new AccessDecisionRefused(issues)\n }\n\n const repository = getRepository(AccessRequest, tx)\n const granting = input.decision === 'GRANT'\n\n const moved = await repository\n .createQueryBuilder()\n .update(AccessRequest)\n .set({\n state: granting ? AccessRequestStatus.GRANTED : AccessRequestStatus.DENIED,\n decidedBy: { id: decider.id } as User,\n decidedAt: now,\n decisionReason: input.reason?.trim() || null,\n grantedRole: granting ? ({ id: input.roleId } as Role) : null\n })\n .where('id = :id AND domain_id = :domainId AND state = :pending', {\n id: input.requestId,\n domainId: domain.id,\n pending: AccessRequestStatus.PENDING\n })\n .execute()\n\n if (!moved.affected) {\n throw new AccessDecisionRefused([{ code: 'request-not-pending', blocking: true, field: 'request' }])\n }\n\n const request = await repository.findOne({ where: { id: input.requestId }, relations: ['domain', 'requester', 'requester.roles', 'grantedRole'] })\n const requester = request.requester\n\n if (granting) {\n await assignRoles(requester, { add: [input.roleId] }, domain, tx, decider)\n await closeWhatTheGrantOpened(request, context)\n }\n\n tellLater(context, 'notify the requester of the decision', async () =>\n notifyEach(\n context,\n [requester.id],\n granting ? 'access-request.notify.granted' : 'access-request.notify.denied',\n { privilege: `${request.category}:${request.privilege}`, role: request.grantedRole?.name ?? '', reason: request.decisionReason ?? '' },\n /* The requester cannot read the list of everyone's requests - their own is where the answer is. */\n 'my-access-requests'\n )\n )\n\n return request\n}\n\n/**\n * Other pending requests of the same person that the role just given now answers - closed as granted with it.\n *\n * Judged by the granted role's own privileges, read in this transaction. `User.hasPrivilege` reads through the default\n * connection, which on a pooled database does not see the role added a moment ago in this uncommitted transaction.\n */\nasync function closeWhatTheGrantOpened(granted: AccessRequest, context: ResolverContext) {\n const { domain, user: decider, tx } = context.state\n const repository = getRepository(AccessRequest, tx)\n const role = await getRepository(Role, tx).findOne({ where: { id: granted.grantedRole?.id }, relations: ['privileges'] })\n const gives = new Set((role?.privileges || []).map(p => `${p.category}:${p.name}`))\n if (gives.size === 0) return\n\n const others = await repository.find({\n where: { domain: { id: domain.id }, requester: { id: granted.requester.id }, state: AccessRequestStatus.PENDING }\n })\n\n for (const other of others) {\n if (other.id === granted.id || !gives.has(`${other.category}:${other.privilege}`)) continue\n await repository.update(\n { id: other.id, state: AccessRequestStatus.PENDING },\n { state: AccessRequestStatus.GRANTED, decidedBy: { id: decider.id } as User, decidedAt: new Date(), grantedRole: { id: role.id } as Role }\n )\n }\n}\n\n/* ── telling ─────────────────────────────────────────────────────────────── */\n\n/** After the commit when there is a transaction, at once otherwise - a failed notification never fails the decision. */\nfunction tellLater(context: ResolverContext, name: string, run: () => Promise<void>) {\n const deferred = whenCommitted(context.state.afterCommit, name, run)\n if (!deferred) void run().catch(() => {})\n}\n\n/**\n * One notice to the people, each in their own language (`User.locale`), linking to where they can act on it - kept,\n * live, web push and mail by notification (`sendNotice`). It is there when the notification module is loaded, and\n * nothing happens otherwise - the request list is the record.\n */\nasync function notifyEach(\n context: ResolverContext,\n userIds: string[],\n key: string,\n params: Record<string, string>,\n url: string\n) {\n /* notification's `sendNotice` - kept, live, web push, mail (ADR-0164 amendment 1); auth-base cannot import it */\n const sendNotice = (context.state as any).sendNotice\n if (typeof sendNotice !== 'function' || userIds.length === 0) return\n\n const people = await getRepository(User).find({ where: { id: In(userIds) } })\n sendNotice({\n type: 'access-request',\n receivers: people.map(person => ({ id: person.id, email: person.email, locale: person.locale })),\n words: (lng: string) => {\n const say = (suffix: string) => translate(context, `${key}.${suffix}`, params, lng)\n return { subject: say('title'), summary: say('body'), body: say('body'), action: say('title'), note: '' }\n },\n address: url,\n label: `access request ${key} to ${people.length}`\n })\n}\n\nfunction translate(context: any, key: string, params: Record<string, string>, locale: string): string {\n const i18next = context?.i18next\n if (locale && i18next?.t) return i18next.t(key, { ...params, lng: locale })\n return typeof context?.t === 'function' ? context.t(key, { ...params }) : key\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"account-mail.js","sourceRoot":"","sources":["../../server/controllers/account-mail.ts"],"names":[],"mappings":";;;AAgBA,4CAMC;AAOD,kCAgBC;AA7CD,2DAA2F;AAC3F,6CAA4C;AAU/B,QAAA,kBAAkB,GAA+B,CAAC,cAAc,EAAE,gBAAgB,EAAE,cAAc,EAAE,gBAAgB,CAAC,CAAA;AAIlI,iGAAiG;AACjG,SAAgB,gBAAgB,CAAC,CAAY,EAAE,IAAqB,EAAE,KAAa;IACjF,OAAO;QACL,OAAO,EAAE,CAAC,CAAC,sBAAsB,IAAI,EAAE,CAAC;QACxC,IAAI,EAAE,CAAC,CAAC,qBAAqB,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC;QAC/C,MAAM,EAAE,CAAC,CAAC,uBAAuB,IAAI,EAAE,CAAC;KACzC,CAAA;AACH,CAAC;AAED;;;;GAIG;AACH,SAAgB,WAAW,CAAC,EAC1B,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,OAAO,EAMR;IACC,MAAM,GAAG,GAAG,IAAA,uBAAU,EAAC,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,IAAI,YAAM,CAAC,GAAG,CAAC,sBAAsB,EAAE,IAAI,CAAC,CAAC,CAAA;IAC7F,MAAM,KAAK,GAAG,IAAA,qBAAQ,GAAE,CAAA;IACxB,MAAM,KAAK,GAAG,gBAAgB,CAAC,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAA;IAEhF,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,OAAO,EAAE,IAAA,qBAAQ,EAAC,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,CAAA;AACzF,CAAC","sourcesContent":["import { appBrand, languageOf, mailForm, type MailWords } from '@things-factory/email-base'\nimport { config } from '@things-factory/env'\n\n/**\n * The four mails an account gets, each with one link: confirm the address it signed up with, reset a forgotten\n * password, set the password an administrator left waiting (ADR-0161), and unlock after too many wrong passwords.\n * They are written in the person's language and sent in the same mail every module sends (email-base `mailForm`), so\n * they carry the application's brand like any notice.\n */\nexport type AccountMailKind = 'verify-email' | 'reset-password' | 'set-password' | 'unlock-account'\n\nexport const ACCOUNT_MAIL_KINDS: readonly AccountMailKind[] = ['verify-email', 'reset-password', 'set-password', 'unlock-account']\n\ntype Translate = (key: string, options?: Record<string, unknown>) => string\n\n/** What one account mail says, in the language `t` speaks. `brand` is the application's name. */\nexport function accountMailWords(t: Translate, kind: AccountMailKind, brand: string): MailWords {\n return {\n subject: t(`title.account-mail-${kind}`),\n body: t(`text.account-mail-${kind}`, { brand }),\n action: t(`button.account-mail-${kind}`)\n }\n}\n\n/**\n * The subject and HTML of an account mail to `user`. The language is the person's own, else the one their request was\n * made in, else the deployment's default - the request is theirs for every mail but the setup link, which an\n * administrator asks for.\n */\nexport function accountMail({\n kind,\n user,\n link,\n context\n}: {\n kind: AccountMailKind\n user: { locale?: string | null }\n link: string\n context: any\n}): { subject: string; content: string } {\n const lng = languageOf(user.locale, context?.lng || config.get('i18n/defaultLanguage', 'en'))\n const brand = appBrand()\n const words = accountMailWords(context.i18next.getFixedT(lng), kind, brand.name)\n\n return { subject: words.subject, content: mailForm({ lang: lng, words, link, brand }) }\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../server/controllers/auth.ts"],"names":[],"mappings":";;;AAAA,0DAA+B;AAC/B,sDAA2B;AAC3B,sDAA2B;AAC3B,4DAAiC","sourcesContent":["export * from './change-pwd.js'\nexport * from './signin.js'\nexport * from './signup.js'\nexport * from './verification.js'\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"change-pwd.js","sourceRoot":"","sources":["../../server/controllers/change-pwd.ts"],"names":[],"mappings":"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","sourcesContent":["import { config } from '@things-factory/env'\nimport { getRepository } from '@things-factory/shell'\n\nimport {\n CONFIRM_PASSWORD_NOT_MATCHED,\n PASSWORD_NOT_MATCHED,\n PASSWORD_USED_PAST,\n USER_NOT_FOUND\n} from '../constants/error-code.js'\nimport { AuthError } from '../errors/auth-error.js'\nimport { PasswordHistory } from '../service/password-history/password-history.js'\nimport { User } from '../service/user/user.js'\n\nconst HISTORY_SIZE = config.get('password', { history: 0 }).history\n\nexport async function changePwd(attrs, currentPass, newPass, confirmPass, context) {\n const { domain } = context.state\n\n // TODO 이 사용자가 이 도메인에 속한 사용자인지 확인해야함.\n const repository = getRepository(User)\n\n const user: User = await repository.findOne({ where: { id: attrs.id } })\n\n if (!user) {\n throw new AuthError({\n errorCode: USER_NOT_FOUND\n })\n }\n\n if (newPass !== confirmPass) {\n throw new AuthError({\n errorCode: CONFIRM_PASSWORD_NOT_MATCHED\n })\n }\n\n if (!User.verify(user.password, currentPass, user.salt)) {\n throw new AuthError({\n errorCode: PASSWORD_NOT_MATCHED,\n detail: {\n username: user.username,\n email: user.email,\n failCount: user.failCount\n }\n })\n }\n\n /* check if password is following the rule */\n User.validatePasswordByRule(newPass, context?.lng)\n\n user.password = User.encode(newPass, user.salt)\n\n if (HISTORY_SIZE > 0) {\n var passwordHistory: PasswordHistory = await getRepository(PasswordHistory).findOneBy({ userId: user.id })\n var history = []\n\n if (passwordHistory) {\n try {\n history = JSON.parse(passwordHistory.history)\n if (!(history instanceof Array)) {\n console.error('password history maybe currupted - not an array')\n history = []\n }\n } catch (e) {\n console.error('password history currupted - not json format')\n }\n\n const found = history.slice(0, HISTORY_SIZE).find(h => {\n return User.verify(h.password, newPass, h.salt)\n })\n\n if (found) {\n throw new AuthError({\n errorCode: PASSWORD_USED_PAST\n })\n }\n }\n }\n\n await repository.save({\n ...user,\n passwordUpdatedAt: new Date()\n })\n\n if (HISTORY_SIZE > 0) {\n history = [\n {\n password: user.password,\n salt: user.salt\n },\n ...history\n ].slice(0, HISTORY_SIZE)\n\n await getRepository(PasswordHistory).save({\n userId: user.id,\n history: JSON.stringify(history)\n })\n }\n\n return await user.sign({ subdomain: domain.subdomain })\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"confirm-identity.js","sourceRoot":"","sources":["../../server/controllers/confirm-identity.ts"],"names":[],"mappings":";;AAcA,0CAiBC;AA/BD,iDAAqD;AAErD,2DAAmD;AACnD,0EAAsE;AACtE,sEAA8D;AAC9D,wGAAgG;AAChG,qDAA8C;AAC9C,2CAA4D;AAE5D;;;;GAIG;AACI,KAAK,UAAU,eAAe,CAAC,MAAoB,EAAE,OAAO,EAAE,MAAqC;IACxG,MAAM,MAAM,GAAuB,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,CAAA;IACzD,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,IAAI,IAAI,CAAA;IACjD,MAAM,MAAM,GAAG,EAAE,MAAM,EAAE,CAAA;IAEzB,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,MAAM,IAAA,qBAAa,EAAC,cAAI,CAAC,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;QACjH,IAAI,CAAC,IAAI;YAAE,MAAM,IAAI,yBAAS,CAAC,EAAE,SAAS,EAAE,yBAAS,CAAC,WAAW,CAAC,cAAc,EAAE,CAAC,CAAA;QAEnF,MAAM,IAAA,uBAAW,EAAC,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAA;QACpD,IAAA,+CAAoB,EAAC,OAAO,EAAE,IAAI,CAAC,EAAE,CAAC,CAAA;QAEtC,KAAK,IAAA,qCAAiB,EAAC,EAAE,IAAI,EAAE,uBAAuB,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAA;IAClH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,KAAK,IAAA,qCAAiB,EAAC,EAAE,IAAI,EAAE,uBAAuB,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAA,6BAAW,EAAC,KAAK,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,MAAM,IAAI,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAA;QACpJ,MAAM,KAAK,CAAA;IACb,CAAC;AACH,CAAC","sourcesContent":["import { getRepository } from '@things-factory/shell'\n\nimport { AuthError } from '../errors/auth-error.js'\nimport { recordAuditAction } from '../service/audit/audit-recorder.js'\nimport { errorCodeOf } from '../service/audit/audit-target.js'\nimport { rememberConfirmation } from '../service/identity-confirmation/identity-confirmation.js'\nimport { User } from '../service/user/user.js'\nimport { admitSignin, type SigninMethod } from './signin.js'\n\n/**\n * The signed-in person confirms it is still them (ADR-0168): through the same account gate as signing in - a locked\n * account confirms nothing, and a miss counts toward the lock - then the confirmation is remembered. `verify` is the\n * secret's own check, a password or a passkey's assertion. Audited as `auth.confirm-identity`, a failure with its code.\n */\nexport async function confirmIdentity(method: SigninMethod, context, verify: (user: User) => Promise<void>) {\n const userId: string | undefined = context.state.user?.id\n const domainId = context.state.domain?.id ?? null\n const detail = { method }\n\n try {\n const user = userId ? await getRepository(User).findOne({ where: { id: userId }, relations: ['domains'] }) : null\n if (!user) throw new AuthError({ errorCode: AuthError.ERROR_CODES.USER_NOT_FOUND })\n\n await admitSignin(user, context, () => verify(user))\n rememberConfirmation(context, user.id)\n\n void recordAuditAction({ kind: 'auth.confirm-identity', outcome: 'ok', detail }, { actorId: user.id, domainId })\n } catch (error) {\n void recordAuditAction({ kind: 'auth.confirm-identity', outcome: 'error', code: errorCodeOf(error), detail }, { actorId: userId ?? null, domainId })\n throw error\n }\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"delete-user.js","sourceRoot":"","sources":["../../server/controllers/delete-user.ts"],"names":[],"mappings":";;AAKA,gCA6BC;AAED,kCAkCC;AAtED,qCAAkD;AAClD,qDAA0D;AAC1D,2DAAmD;AACnD,8DAA2D;AAEpD,KAAK,UAAU,UAAU,CAAC,KAAK,EAAE,EAAkB;IACxD,qCAAqC;IACrC,iEAAiE;IAEjE,MAAM,UAAU,GAAG,EAAE,EAAE,aAAa,CAAC,cAAI,CAAC,CAAA;IAC1C,MAAM,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAA;IAE1B,IAAI,IAAI,GAAG,MAAM,UAAU,CAAC,OAAO,CAAC;QAClC,KAAK,EAAE,EAAE,QAAQ,EAAE;QACnB,SAAS,EAAE,CAAC,SAAS,CAAC;KACvB,CAAC,CAAA;IAEF,IAAI,CAAC,IAAI,IAAI,4BAA4B,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QACzD,IAAI,GAAG,MAAM,UAAU,CAAC,OAAO,CAAC;YAC9B,KAAK,EAAE,EAAE,KAAK,EAAE,IAAA,eAAK,EAAC,QAAQ,CAAC,EAAE;YACjC,SAAS,EAAE,CAAC,SAAS,CAAC;SACvB,CAAC,CAAA;IACJ,CAAC;IAED,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,IAAI,yBAAS,CAAC;YAClB,SAAS,EAAE,8BAAc;SAC1B,CAAC,CAAA;IACJ,CAAC;IAED,IAAI,CAAC,MAAM,GAAG,oBAAU,CAAC,OAAO,CAAA;IAChC,IAAI,CAAC,OAAO,GAAG,EAAE,CAAA;IAEjB,MAAM,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AAC7B,CAAC;AAEM,KAAK,UAAU,WAAW,CAAC,KAAK,EAAE,EAAkB;IACzD,qCAAqC;IACrC,iEAAiE;IAEjE,MAAM,EAAE,SAAS,EAAE,GAAG,KAAK,CAAA;IAE3B,MAAM,IAAI,GAAG,EAAE,EAAE,aAAa,CAAC,cAAI,CAAC,CAAA;IAEpC,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC;QAC5B,KAAK,EAAE;YACL,QAAQ,EAAE,IAAA,YAAE,EAAC,SAAS,CAAC;SACxB;KACF,CAAC,CAAA;IAEF,MAAM,OAAO,GAAG,EAAE,CAAA;IAClB,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE;QACnB,IAAI,CAAC,MAAM,GAAG,oBAAU,CAAC,OAAO,CAAA;QAChC,IAAI,CAAC,OAAO,GAAG,EAAE,CAAA;QAEjB,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IACvB,CAAC,CAAC,CAAA;IAEF,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IAEtB,2BAA2B;IAC3B,kBAAkB;IAClB,0BAA0B;IAC1B,cAAc;IACd,2BAA2B;IAC3B,aAAa;IACb,2BAA2B;IAC3B,OAAO;IACP,eAAe;IACf,OAAO,IAAI,CAAA;AACb,CAAC","sourcesContent":["import { EntityManager, ILike, In } from 'typeorm'\nimport { User, UserStatus } from '../service/user/user.js'\nimport { AuthError } from '../errors/auth-error.js'\nimport { USER_NOT_FOUND } from '../constants/error-code.js'\n\nexport async function deleteUser(attrs, tx?: EntityManager) {\n // TODO 이 사용자가 이 도메인에 속한 사용자인지 확인해야함.\n // TODO 다른 도메인에도 포함되어있다면, domains-users 관게와 해당 도메인 관련 정보만 삭제해야 함.\n\n const repository = tx?.getRepository(User)\n const { username } = attrs\n\n var user = await repository.findOne({\n where: { username },\n relations: ['domains']\n })\n\n if (!user && /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(username)) {\n user = await repository.findOne({\n where: { email: ILike(username) },\n relations: ['domains']\n })\n }\n\n if (!user) {\n throw new AuthError({\n errorCode: USER_NOT_FOUND\n })\n }\n\n user.status = UserStatus.DELETED\n user.domains = []\n\n await repository.save(user)\n}\n\nexport async function deleteUsers(attrs, tx?: EntityManager) {\n // TODO 이 사용자가 이 도메인에 속한 사용자인지 확인해야함.\n // TODO 다른 도메인에도 포함되어있다면, domains-users 관게와 해당 도메인 관련 정보만 삭제해야 함.\n\n const { usernames } = attrs\n\n const repo = tx?.getRepository(User)\n\n const users = await repo.find({\n where: {\n username: In(usernames)\n }\n })\n\n const userIds = []\n users.forEach(user => {\n user.status = UserStatus.DELETED\n user.domains = []\n\n userIds.push(user.id)\n })\n\n await repo.save(users)\n\n // repository api는 작동하지 않음.\n // await txManager\n // .createQueryBuilder()\n // .delete()\n // .from('users_domains')\n // .where({\n // usersId: In(userIds)\n // })\n // .execute()\n return true\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"passkey.js","sourceRoot":"","sources":["../../server/controllers/passkey.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n generateAuthenticationOptions,\n generateRegistrationOptions,\n verifyAuthenticationResponse,\n verifyRegistrationResponse,\n type AuthenticationResponseJSON,\n type AuthenticatorTransportFuture,\n type PublicKeyCredentialCreationOptionsJSON,\n type PublicKeyCredentialRequestOptionsJSON,\n type RegistrationResponseJSON\n} from '@simplewebauthn/server'\n\nimport { getRepository } from '@things-factory/shell'\n\nimport { AuthError } from '../errors/auth-error.js'\nimport { User } from '../service/user/user.js'\nimport { WebAuthCredential } from '../service/web-auth-credential/web-auth-credential.js'\nimport { admitSignin, auditedSignin } from './signin.js'\n\n/**\n * Passkeys (ADR-0156).\n *\n * Every ceremony asks the authenticator to verify the person - fingerprint, face or device PIN - and\n * the server refuses an answer without that flag (decision 2). Registration leaves the authenticator\n * to the browser, so a phone or a security key can be chosen as well as this device; that is also\n * what lets a phone sign a computer in (decision 3).\n *\n * A challenge is good for one answer. It is taken out of the session before the answer is checked,\n * so a second try with the same challenge finds none.\n */\n\nconst USER_VERIFICATION = 'required' as const\n\n/** Where a ceremony runs. The relying party is the host the person reached (ADR-0156 「남기는 것」). */\nexport type PasskeySite = { rpID: string; origin: string }\n\nexport function siteOf(context): PasskeySite {\n return { rpID: context.hostname, origin: context.origin }\n}\n\nexport function rememberChallenge(context, challenge: string) {\n context.session.challenge = challenge\n}\n\n/** The session's challenge, removed - whatever the answer turns out to be. */\nexport function takeChallenge(context): string {\n const challenge = context.session?.challenge\n if (context.session) delete context.session.challenge\n\n if (!challenge) {\n throw new AuthError({ errorCode: AuthError.ERROR_CODES.AUTHN_VERIFICATION_FAILED, detail: { reason: 'no challenge' } })\n }\n\n return challenge\n}\n\n/** What to ask the browser when a signed-in person adds a passkey. */\nexport async function registrationOptions(\n user: User,\n rpName: string,\n site: PasskeySite\n): Promise<PublicKeyCredentialCreationOptionsJSON> {\n const registered = await getRepository(WebAuthCredential).find({ where: { user: { id: user.id } } })\n\n return await generateRegistrationOptions({\n rpName,\n rpID: site.rpID,\n userName: user.email,\n userDisplayName: user.name,\n attestationType: 'none',\n /* The browser says so when this authenticator already holds one of this person's passkeys. */\n excludeCredentials: registered.map(credential => ({\n id: credential.credentialId,\n transports: (credential.transports ?? undefined) as AuthenticatorTransportFuture[]\n })),\n authenticatorSelection: {\n /* A passkey that cannot sign in without a name typed first is not taken (decision 2). */\n residentKey: 'required',\n userVerification: USER_VERIFICATION\n }\n })\n}\n\n/**\n * What to ask the browser when somebody signs in. No credential is named: the browser offers what\n * it holds for this site, or a phone nearby.\n */\nexport async function signinOptions(site: PasskeySite): Promise<PublicKeyCredentialRequestOptionsJSON> {\n return await generateAuthenticationOptions({ rpID: site.rpID, userVerification: USER_VERIFICATION })\n}\n\n/** What to ask the browser when a signed-in person confirms it is them - only their own passkeys. */\nexport async function reverifyOptions(user: User, site: PasskeySite): Promise<PublicKeyCredentialRequestOptionsJSON | null> {\n const registered = await getRepository(WebAuthCredential).find({ where: { user: { id: user.id } } })\n if (registered.length === 0) return null\n\n return await generateAuthenticationOptions({\n rpID: site.rpID,\n userVerification: USER_VERIFICATION,\n allowCredentials: registered.map(credential => ({\n id: credential.credentialId,\n transports: (credential.transports ?? undefined) as AuthenticatorTransportFuture[]\n }))\n })\n}\n\nfunction verificationFailed(reason: string): AuthError {\n return new AuthError({ errorCode: AuthError.ERROR_CODES.AUTHN_VERIFICATION_FAILED, detail: { reason } })\n}\n\n/** Keeps the passkey the browser made for `user`. `name` is the person's label for it. */\nexport async function registerPasskey(\n user: User,\n response: RegistrationResponseJSON,\n name: string | undefined,\n challenge: string,\n site: PasskeySite\n): Promise<WebAuthCredential> {\n let verification: Awaited<ReturnType<typeof verifyRegistrationResponse>>\n try {\n verification = await verifyRegistrationResponse({\n response,\n expectedChallenge: challenge,\n expectedOrigin: site.origin,\n expectedRPID: site.rpID,\n expectedType: 'webauthn.create',\n requireUserVerification: true\n })\n } catch (error) {\n throw verificationFailed(error?.message || 'registration not verified')\n }\n\n if (!verification.verified || !verification.registrationInfo) {\n throw verificationFailed('registration not verified')\n }\n\n const { credential } = verification.registrationInfo\n const label = name?.trim().slice(0, 64)\n\n return await getRepository(WebAuthCredential).save({\n user,\n credentialId: credential.id,\n publicKey: Buffer.from(credential.publicKey).toString('base64'),\n counter: credential.counter,\n transports: credential.transports?.length ? credential.transports : (response.response?.transports ?? null),\n name: label || null,\n creator: user,\n updater: user\n })\n}\n\n/**\n * Checks an answer against the stored passkey and moves its counter on. `ownerId`, when given, is\n * who the passkey must belong to - a confirmation is answered only by the person's own passkey.\n * `attempt.userId` is filled as soon as the passkey is found, so a failed sign-in names its owner.\n */\nasync function verifyAssertion(\n response: AuthenticationResponseJSON,\n challenge: string,\n site: PasskeySite,\n attempt: { userId?: string } = {},\n ownerId?: string\n): Promise<WebAuthCredential> {\n const repository = getRepository(WebAuthCredential)\n const credential = response?.id\n ? await repository.findOne({ where: { credentialId: response.id }, relations: ['user', 'user.domains'] })\n : null\n\n if (!credential?.user) {\n throw verificationFailed('unknown credential')\n }\n\n attempt.userId = credential.user.id\n\n if (ownerId && credential.user.id !== ownerId) {\n throw verificationFailed('credential of another user')\n }\n\n let verification: Awaited<ReturnType<typeof verifyAuthenticationResponse>>\n try {\n verification = await verifyAuthenticationResponse({\n response,\n expectedChallenge: challenge,\n expectedOrigin: site.origin,\n expectedRPID: site.rpID,\n requireUserVerification: true,\n credential: {\n id: credential.credentialId,\n publicKey: new Uint8Array(Buffer.from(credential.publicKey, 'base64')),\n counter: credential.counter,\n transports: (credential.transports ?? undefined) as AuthenticatorTransportFuture[]\n }\n })\n } catch (error) {\n throw verificationFailed(error?.message || 'assertion not verified')\n }\n\n if (!verification.verified) {\n throw verificationFailed('assertion not verified')\n }\n\n await repository.update({ id: credential.id }, { counter: verification.authenticationInfo.newCounter })\n\n return credential\n}\n\n/**\n * Signing in with a passkey: the answer is checked, then the account goes through the same gate as\n * a password sign-in - deleted, locked, inactive - and the sign-in is audited with its method\n * (ADR-0156 decision 4). A passkey does not lift a lock and does not count as a failed password.\n */\nexport async function signinWithPasskey(response: AuthenticationResponseJSON, challenge: string, site: PasskeySite, context?) {\n return await auditedSignin('passkey', context, async attempt => {\n const credential = await verifyAssertion(response, challenge, site, attempt)\n const answer = await admitSignin(credential.user, context, async () => {})\n\n await getRepository(WebAuthCredential).update({ id: credential.id }, { lastUsedAt: new Date() })\n\n return answer\n })\n}\n\n/** A signed-in person confirming it is them, with one of their own passkeys. */\nexport async function reverifyWithPasskey(user: User, response: AuthenticationResponseJSON, challenge: string, site: PasskeySite) {\n const credential = await verifyAssertion(response, challenge, site, {}, user.id)\n await getRepository(WebAuthCredential).update({ id: credential.id }, { lastUsedAt: new Date() })\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"password-setup.js","sourceRoot":"","sources":["../../server/controllers/password-setup.ts"],"names":[],"mappings":";;AAsBA,oCAIC;AAcD,wDAoDC;AA1FD,2DAAsD;AACtD,iDAA8D;AAE9D,qDAA0D;AAC1D,+FAA8G;AAC9G,kEAAyD;AACzD,oFAAuG;AACvG,uDAA+C;AAC/C,2DAAyD;AACzD,mFAA0E;AAE1E;;;;;;GAMG;AAEH,yFAAyF;AACzF,SAAgB,YAAY,CAAC,IAAmB;IAC9C,IAAI,CAAC,IAAI,GAAG,cAAI,CAAC,YAAY,EAAE,CAAA;IAC/B,IAAI,CAAC,QAAQ,GAAG,cAAI,CAAC,MAAM,CAAC,IAAA,kDAAqB,GAAE,GAAG,IAAA,kDAAqB,GAAE,EAAE,IAAI,CAAC,IAAI,CAAC,CAAA;IACzF,IAAI,CAAC,MAAM,GAAG,oBAAU,CAAC,kBAAkB,CAAA;AAC7C,CAAC;AAED;;;;;;;;;;;GAWG;AACI,KAAK,UAAU,sBAAsB,CAAC,EAC3C,IAAI,EACJ,IAAI,EACJ,OAAO,EACP,EAAE,EAMH;IACC,YAAY,CAAC,IAAI,CAAC,CAAA;IAClB,IAAI,CAAC,SAAS,GAAG,CAAC,CAAA;IAClB,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAA;IACpB,IAAI,CAAC,gBAAgB,GAAG,CAAC,IAAI,CAAC,gBAAgB,IAAI,CAAC,CAAC,GAAG,CAAC,CAAA;IAExD,MAAM,IAAA,qBAAa,EAAC,cAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAExC,MAAM,KAAK,GAAG,IAAA,kDAAqB,GAAE,CAAA;IACrC,MAAM,SAAS,GAAG,IAAA,uDAA0B,EAAC,6CAAqB,CAAC,cAAc,CAAC,CAAA;IAElF,MAAM,IAAA,qBAAa,EAAC,yCAAiB,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC;QAC9C,MAAM,EAAE,IAAI,CAAC,EAAE;QACf,KAAK,EAAE,IAAA,kDAAqB,EAAC,KAAK,CAAC;QACnC,IAAI,EAAE,6CAAqB,CAAC,cAAc;QAC1C,SAAS;KACV,CAAC,CAAA;IAEF,MAAM,IAAI,GAAG,GAAG,IAAA,gCAAY,EAAC,uCAAmB,EAAE,OAAO,CAAC,IAAI,KAAK,EAAE,CAAA;IAErE,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,EAAE,CAAA;IAC5C,CAAC;IAED;;;OAGG;IACH,MAAM,IAAI,GAAY,MAAM,IAAA,sBAAS,EAAC;QACpC,QAAQ,EAAE,IAAI,CAAC,KAAK;QACpB,GAAG,IAAA,6BAAW,EAAC,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;KAC9D,CAAC,CAAA;IAEF,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,IAAI,eAAO,CACf,8BAA8B,EAC9B,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,EACrB,eAAe,IAAI,CAAC,KAAK,+DAA+D,CACzF,CAAA;IACH,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,CAAC,KAAK,EAAE,SAAS,EAAE,CAAA;AACxD,CAAC","sourcesContent":["import { EntityManager } from 'typeorm'\n\nimport { sendEmail } from '@things-factory/email-base'\nimport { getRepository, Refusal } from '@things-factory/shell'\n\nimport { User, UserStatus } from '../service/user/user.js'\nimport { VerificationToken, VerificationTokenType } from '../service/verification-token/verification-token.js'\nimport { buildAuthURL } from '../utils/build-auth-url.js'\nimport { hashVerificationToken, verificationTokenExpiresAt } from '../utils/verification-token-rule.js'\nimport { accountMail } from './account-mail.js'\nimport { RESET_PASSWORD_PATH } from './reset-password.js'\nimport { makeVerificationToken } from './utils/make-verification-token.js'\n\n/**\n * An administrator never sets anybody's password (ADR-0161).\n *\n * An account an administrator makes, or one whose password they take back, is put **waiting for its password**: the\n * status `password_reset_required` and a hash of a random value nobody holds. The person sets their own through a\n * one-time link - mailed, or handed over by the administrator - on the same page a forgotten password is reset on.\n */\n\n/** A password nobody knows: what a waiting account carries until its person sets one. */\nexport function sealPassword(user: Partial<User>): void {\n user.salt = User.generateSalt()\n user.password = User.encode(makeVerificationToken() + makeVerificationToken(), user.salt)\n user.status = UserStatus.PWD_RESET_REQUIRED\n}\n\n/**\n * Put `user` waiting for its password and issue the link.\n *\n * Whatever password the account had stops working now, and its generation moves on, so a session opened before this\n * - by the person or by whoever made the administrator take the password back - ends with it.\n *\n * The person holds one live token at a time (the table is keyed by person), so issuing again retires the last link.\n *\n * @param mail true mails the link to the account's address; the answer then carries no link. A mail that does not go\n * out refuses, and the transaction takes back the rest.\n * @returns the link when it is not mailed - this is the only time it exists outside the person's hands.\n */\nexport async function issuePasswordSetupLink({\n user,\n mail,\n context,\n tx\n}: {\n user: User\n mail: boolean\n context: any\n tx?: EntityManager\n}): Promise<{ link: string | null; mailedTo: string | null; expiresAt: Date }> {\n sealPassword(user)\n user.failCount = 0\n user.lockedAt = null\n user.credentialSerial = (user.credentialSerial ?? 0) + 1\n\n await getRepository(User, tx).save(user)\n\n const token = makeVerificationToken()\n const expiresAt = verificationTokenExpiresAt(VerificationTokenType.PASSWORD_SETUP)\n\n await getRepository(VerificationToken, tx).save({\n userId: user.id,\n token: hashVerificationToken(token),\n type: VerificationTokenType.PASSWORD_SETUP,\n expiresAt\n })\n\n const link = `${buildAuthURL(RESET_PASSWORD_PATH, context)}#${token}`\n\n if (!mail) {\n return { link, mailedTo: null, expiresAt }\n }\n\n /*\n * `sendEmail` swallows a failure and answers nothing; a sent mail answers the transport's receipt. Its declared type\n * is `void`, so it is read as unknown here - the receipt is the only sign the mail went out.\n */\n const sent: unknown = await sendEmail({\n receiver: user.email,\n ...accountMail({ kind: 'set-password', user, link, context })\n })\n\n if (!sent) {\n throw new Refusal(\n 'password-setup-mail-not-sent',\n { email: user.email },\n `The mail to ${user.email} did not go out. Nothing was changed - copy the link instead.`\n )\n }\n\n return { link: null, mailedTo: user.email, expiresAt }\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"profile.js","sourceRoot":"","sources":["../../server/controllers/profile.ts"],"names":[],"mappings":";;AAQA,sCA8CC;AAtDD,qCAA+B;AAE/B,iDAAqD;AAErD,8DAA2D;AAC3D,2DAAmD;AACnD,qDAA8C;AAEvC,KAAK,UAAU,aAAa,CAAC,EAAE,EAAE,EAAE,EAAE,WAAW;IACrD,MAAM,UAAU,GAAG,IAAA,qBAAa,EAAC,cAAI,CAAC,CAAA;IACtC,MAAM,IAAI,GAAG,MAAM,UAAU,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,CAAC,CAAA;IAC/C,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,IAAI,yBAAS,CAAC;YAClB,SAAS,EAAE,8BAAc;SAC1B,CAAC,CAAA;IACJ,CAAC;IAED,6EAA6E;IAC7E,IAAI,OAAO,GAKP,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAC;SACxC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,IAAI,WAAW,CAAC;SACnC,MAAM,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE;QACpB,GAAG,CAAC,IAAI,CAAC,GAAG,WAAW,CAAC,IAAI,CAAC,CAAA;QAC7B,OAAO,GAAG,CAAA;IACZ,CAAC,EAAE,EAAE,CAAC,CAAA;IAER,2CAA2C;IAC3C,IAAI,OAAO,IAAI,OAAO,EAAE,CAAC;QACvB,IAAI,KAAK,GAAS,MAAM,UAAU,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,IAAA,eAAK,EAAC,OAAO,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC,CAAA;QAEtF,IAAI,KAAK,IAAI,KAAK,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC;YAC5B,MAAM,IAAI,yBAAS,CAAC;gBAClB,SAAS,EAAE,yBAAS,CAAC,WAAW,CAAC,oBAAoB;aACtD,CAAC,CAAA;QACJ,CAAC;IACH,CAAC;IAED,IAAI,UAAU,IAAI,OAAO,EAAE,CAAC;QAC1B,IAAI,KAAK,GAAS,MAAM,UAAU,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAA;QACrF,IAAI,KAAK,IAAI,KAAK,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC;YAC5B,MAAM,IAAI,yBAAS,CAAC;gBAClB,SAAS,EAAE,yBAAS,CAAC,WAAW,CAAC,uBAAuB;aACzD,CAAC,CAAA;QACJ,CAAC;IACH,CAAC;IAED,OAAO,MAAM,UAAU,CAAC,IAAI,CAAC;QAC3B,GAAG,IAAI;QACP,GAAG,OAAO;KACX,CAAC,CAAA;AACJ,CAAC","sourcesContent":["import { ILike } from 'typeorm'\n\nimport { getRepository } from '@things-factory/shell'\n\nimport { USER_NOT_FOUND } from '../constants/error-code.js'\nimport { AuthError } from '../errors/auth-error.js'\nimport { User } from '../service/user/user.js'\n\nexport async function updateProfile({ id }, newProfiles) {\n const repository = getRepository(User)\n const user = await repository.findOneBy({ id })\n if (!user) {\n throw new AuthError({\n errorCode: USER_NOT_FOUND\n })\n }\n\n /* only 'username', 'name', 'email' and 'locale' attributes can be changed */\n var allowed: {\n username?: string\n name?: string\n email?: string\n locale?: string\n } = ['username', 'name', 'email', 'locale']\n .filter(attr => attr in newProfiles)\n .reduce((sum, attr) => {\n sum[attr] = newProfiles[attr]\n return sum\n }, {})\n\n /* check if email and username is unique */\n if ('email' in allowed) {\n var found: User = await repository.findOne({ where: { email: ILike(allowed.email) } })\n\n if (found && found.id != id) {\n throw new AuthError({\n errorCode: AuthError.ERROR_CODES.EMAIL_ALREADY_EXISTS\n })\n }\n }\n\n if ('username' in allowed) {\n var found: User = await repository.findOne({ where: { username: allowed.username } })\n if (found && found.id != id) {\n throw new AuthError({\n errorCode: AuthError.ERROR_CODES.USERNAME_ALREADY_EXISTS\n })\n }\n }\n\n return await repository.save({\n ...user,\n ...allowed\n })\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"reset-password.js","sourceRoot":"","sources":["../../server/controllers/reset-password.ts"],"names":[],"mappings":"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","sourcesContent":["import { sendEmail } from '@things-factory/email-base'\nimport { config } from '@things-factory/env'\nimport { getRepository } from '@things-factory/shell'\n\nimport { PASSWORD_USED_PAST } from '../constants/error-code.js'\nimport { AuthError } from '../errors/auth-error.js'\nimport { PasswordHistory } from '../service/password-history/password-history.js'\nimport { User, UserStatus } from '../service/user/user.js'\nimport { VerificationTokenType } from '../service/verification-token/verification-token.js'\nimport { buildAuthURL } from '../utils/build-auth-url.js'\nimport { accountMail } from './account-mail.js'\nimport { consumeVerificationToken, findLiveVerificationToken } from './utils/find-verification-token.js'\nimport { makeVerificationToken } from './utils/make-verification-token.js'\nimport { saveVerificationToken } from './utils/save-verification-token.js'\n\nconst HISTORY_SIZE = config.get('password', { history: 0 }).history\n\n/**\n * The page the mail links to. The token rides after `#`, so the browser never sends it — not to our\n * access log, not to a proxy's. The page reads it and posts it. `tests/invitation-route.test.ts`\n * holds this against the router's table.\n */\nexport const RESET_PASSWORD_PATH = '/auth/reset-password'\n\nexport async function sendPasswordResetEmail({ user, context }) {\n try {\n var token = makeVerificationToken()\n var verifaction = await saveVerificationToken(user.id, token, VerificationTokenType.PASSWORD_RESET)\n\n if (verifaction) {\n var serviceUrl = `${buildAuthURL(RESET_PASSWORD_PATH, context)}#${token}`\n await sendEmail({\n receiver: user.email,\n ...accountMail({ kind: 'reset-password', user, link: serviceUrl, context })\n })\n\n return true\n }\n } catch (e) {\n return false\n }\n}\n\n/**\n * Set a new password through a link: the one a person mails themselves when they forgot theirs, or the setup link an\n * administrator issued (ADR-0161) - one page and one door for both.\n *\n * The address the link was for has to be given as well. A link is forwarded, left in a chat, copied onto paper; holding\n * it is not enough on its own. A wrong token, a wrong address and an expired link are one answer.\n */\nexport async function resetPassword(token, password, email, context) {\n const { t } = context\n\n const verificationToken =\n (await findLiveVerificationToken(token, VerificationTokenType.PASSWORD_RESET)) ||\n (await findLiveVerificationToken(token, VerificationTokenType.PASSWORD_SETUP))\n\n var user = verificationToken?.userId ? await getRepository(User).findOneBy({ id: verificationToken.userId }) : null\n\n const given = typeof email === 'string' ? email.trim().toLowerCase() : ''\n if (!user || !given || user.email?.toLowerCase() !== given) {\n throw new Error(t('text.invalid verification token'))\n }\n\n // if (user.status == UserStatus.INACTIVE) {\n // throw new Error(t('text.inactive user'))\n // }\n\n /* check if password is following the rule */\n User.validatePasswordByRule(password, context?.lng)\n\n user.password = User.encode(password, user.salt)\n\n if (HISTORY_SIZE > 0) {\n var passwordHistory: PasswordHistory = await getRepository(PasswordHistory).findOneBy({ userId: user.id })\n var history = []\n\n if (passwordHistory) {\n try {\n history = JSON.parse(passwordHistory.history)\n if (!(history instanceof Array)) {\n console.error('password history maybe currupted - not an array')\n history = []\n }\n } catch (e) {\n console.error('password history currupted - not json format')\n }\n\n const found = history.slice(0, HISTORY_SIZE).find(h => {\n return User.verify(h.password, password, h.salt)\n })\n\n if (found) {\n throw new AuthError({\n errorCode: PASSWORD_USED_PAST\n })\n }\n }\n }\n\n /*\n * A waiting account opens here (ADR-0161 decision 3). Any other status is left as it was - a locked account stays\n * locked, which is the unlock link's to undo (`tests/lock-recovery.test.ts`).\n */\n if (user.status === UserStatus.PWD_RESET_REQUIRED) {\n user.status = UserStatus.ACTIVATED\n user.failCount = 0\n }\n\n await getRepository(User).save({\n ...user,\n passwordUpdatedAt: new Date()\n })\n\n await consumeVerificationToken(verificationToken)\n\n if (HISTORY_SIZE > 0) {\n history = [\n {\n password: user.password,\n salt: user.salt\n },\n ...history\n ].slice(0, HISTORY_SIZE)\n\n await getRepository(PasswordHistory).save({\n userId: user.id,\n history: JSON.stringify(history)\n })\n }\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"signin.js","sourceRoot":"","sources":["../../server/controllers/signin.ts"],"names":[],"mappings":";;AAoBA,wBAEC;AAOD,sCAqBC;AAmCD,wCAqCC;AASD,kCAsEC;AAzMD,qCAA+B;AAC/B,iDAAqD;AAErD,kEAAmE;AACnE,2DAAmD;AACnD,qDAA0D;AAC1D,wEAA+D;AAC/D,gEAA+D;AAC/D,0EAAsE;AACtE,sEAA8D;AAM9D;;;;GAIG;AACI,KAAK,UAAU,MAAM,CAAC,KAA6C,EAAE,OAAQ;IAClF,OAAO,MAAM,aAAa,CAAC,UAAU,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAA;AACjG,CAAC;AAED;;;;GAIG;AACI,KAAK,UAAU,aAAa,CACjC,MAAoB,EACpB,OAAO,EACP,GAA2C;IAE3C,MAAM,OAAO,GAAkB,EAAE,CAAA;IACjC,MAAM,QAAQ,GAAG,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,IAAI,IAAI,CAAA;IACnD,MAAM,MAAM,GAAG,EAAE,MAAM,EAAE,CAAA;IAEzB;;;OAGG;IACH,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,OAAO,CAAC,CAAA;QACjC,KAAK,IAAA,qCAAiB,EAAC,EAAE,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAA;QAC7G,OAAO,MAAM,CAAA;IACf,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,KAAK,IAAA,qCAAiB,EAAC,EAAE,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAA,6BAAW,EAAC,KAAK,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,OAAO,CAAC,MAAM,IAAI,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAA;QAClJ,MAAM,KAAK,CAAA;IACb,CAAC;AACH,CAAC;AAED,KAAK,UAAU,UAAU,CAAC,KAA6C,EAAE,OAAO,EAAE,OAAsB;IACtG,MAAM,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAA;IAE1B,MAAM,UAAU,GAAG,IAAA,qBAAa,EAAC,cAAI,CAAC,CAAA;IAEtC,IAAI,IAAI,GAAG,MAAM,UAAU,CAAC,OAAO,CAAC;QAClC,KAAK,EAAE,EAAE,QAAQ,EAAE;QACnB,SAAS,EAAE,CAAC,SAAS,CAAC;KACvB,CAAC,CAAA;IAEF,IAAI,CAAC,IAAI,IAAI,4BAA4B,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QACzD,IAAI,GAAG,MAAM,UAAU,CAAC,OAAO,CAAC;YAC9B,KAAK,EAAE;gBACL,KAAK,EAAE,IAAA,eAAK,EAAC,QAAQ,CAAC;aACvB;YACD,SAAS,EAAE,CAAC,SAAS,CAAC;SACvB,CAAC,CAAA;IACJ,CAAC;IAED,IAAI,CAAC,IAAI;QACP,MAAM,IAAI,yBAAS,CAAC;YAClB,SAAS,EAAE,yBAAS,CAAC,WAAW,CAAC,cAAc;SAChD,CAAC,CAAA;IAEJ,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,CAAA;IAExB,OAAO,MAAM,WAAW,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,cAAc,CAAC,IAAI,EAAE,KAAK,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAA;AAC9F,CAAC;AAED;;;GAGG;AACI,KAAK,UAAU,cAAc,CAAC,IAAU,EAAE,QAAgB,EAAE,OAAO;IACxE,MAAM,UAAU,GAAG,IAAA,qBAAa,EAAC,cAAI,CAAC,CAAA;IAEtC,IAAI,CAAC,cAAI,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACrD,IAAI,CAAC,SAAS,EAAE,CAAA;QAChB,IAAI,IAAI,CAAC,SAAS,IAAI,CAAC,EAAE,CAAC;YACxB,IAAI,CAAC,MAAM,GAAG,oBAAU,CAAC,MAAM,CAAA;YAC/B;mCACuB;YACvB,IAAI,CAAC,QAAQ,GAAG,IAAI,IAAI,EAAE,CAAA;QAC5B,CAAC;QACD,MAAM,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAC3B,IAAI,IAAI,CAAC,MAAM,IAAI,oBAAU,CAAC,MAAM,EAAE,CAAC;YACrC,IAAA,oCAAmB,EAAC;gBAClB,IAAI;gBACJ,OAAO;aACR,CAAC,CAAA;YACF,MAAM,IAAI,yBAAS,CAAC;gBAClB,SAAS,EAAE,yBAAS,CAAC,WAAW,CAAC,WAAW;gBAC5C,MAAM,EAAE;oBACN,QAAQ,EAAE,IAAI,CAAC,QAAQ;oBACvB,KAAK,EAAE,IAAI,CAAC,KAAK;iBAClB;aACF,CAAC,CAAA;QACJ,CAAC;QACD,MAAM,IAAI,yBAAS,CAAC;YAClB,SAAS,EAAE,yBAAS,CAAC,WAAW,CAAC,oBAAoB;YACrD,MAAM,EAAE;gBACN,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,KAAK,EAAE,IAAI,CAAC,KAAK;gBACjB,SAAS,EAAE,IAAI,CAAC,SAAS;aAC1B;SACF,CAAC,CAAA;IACJ,CAAC;SAAM,CAAC;QACN,IAAI,CAAC,SAAS,GAAG,CAAC,CAAA;QAClB,MAAM,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAC7B,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACI,KAAK,UAAU,WAAW,CAAC,IAAU,EAAE,OAAO,EAAE,YAAiC;IACtF,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,EAAE,KAAK,IAAI,EAAE,CAAA;IACvC,MAAM,UAAU,GAAG,IAAA,qBAAa,EAAC,cAAI,CAAC,CAAA;IAEtC,IAAI,IAAI,CAAC,MAAM,IAAI,oBAAU,CAAC,OAAO,EAAE,CAAC;QACtC,MAAM,IAAI,yBAAS,CAAC;YAClB,SAAS,EAAE,yBAAS,CAAC,WAAW,CAAC,YAAY;SAC9C,CAAC,CAAA;IACJ,CAAC;IAED,IAAI,IAAI,CAAC,MAAM,IAAI,oBAAU,CAAC,MAAM,EAAE,CAAC;QACrC,sDAAsD;QACtD,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAA;QAEtB,IAAI,IAAA,sCAAe,EAAC,IAAI,EAAE,GAAG,EAAE,IAAA,sCAAmB,GAAE,CAAC,EAAE,CAAC;YACtD,IAAA,oCAAmB,EAAC;gBAClB,IAAI;gBACJ,OAAO;aACR,CAAC,CAAA;YACF,MAAM,IAAI,yBAAS,CAAC;gBAClB,SAAS,EAAE,yBAAS,CAAC,WAAW,CAAC,WAAW;gBAC5C,MAAM,EAAE;oBACN,QAAQ,EAAE,IAAI,CAAC,QAAQ;oBACvB,KAAK,EAAE,IAAI,CAAC,KAAK;iBAClB;aACF,CAAC,CAAA;QACJ,CAAC;QAED;;;;;;WAMG;QACH,IAAI,CAAC,MAAM,GAAG,oBAAU,CAAC,SAAS,CAAA;QAClC,IAAI,CAAC,SAAS,GAAG,CAAC,CAAA;QAClB,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAA;QAEpB,MAAM,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAC7B,CAAC;IAED;;;;OAIG;IACH,IAAI,IAAI,CAAC,MAAM,IAAI,oBAAU,CAAC,kBAAkB,EAAE,CAAC;QACjD,MAAM,IAAI,yBAAS,CAAC;YAClB,SAAS,EAAE,yBAAS,CAAC,WAAW,CAAC,sBAAsB;SACxD,CAAC,CAAA;IACJ,CAAC;IAED,MAAM,YAAY,EAAE,CAAA;IAEpB,IAAI,IAAI,CAAC,MAAM,IAAI,oBAAU,CAAC,QAAQ,EAAE,CAAC;QACvC,MAAM,IAAI,yBAAS,CAAC;YAClB,SAAS,EAAE,yBAAS,CAAC,WAAW,CAAC,kBAAkB;YACnD,MAAM,EAAE;gBACN,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,KAAK,EAAE,IAAI,CAAC,KAAK;aAClB;SACF,CAAC,CAAA;IACJ,CAAC;IAED,OAAO;QACL,IAAI;QACJ,KAAK,EAAE,MAAM,IAAI,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;QACxD,OAAO,EAAE,IAAI,CAAC,OAAO,IAAI,EAAE;KAC5B,CAAA;AACH,CAAC","sourcesContent":["import { ILike } from 'typeorm'\nimport { getRepository } from '@things-factory/shell'\n\nimport { sendUnlockUserEmail } from '../controllers/unlock-user.js'\nimport { AuthError } from '../errors/auth-error.js'\nimport { User, UserStatus } from '../service/user/user.js'\nimport { isAccountLocked } from '../utils/account-lock-rule.js'\nimport { lockDurationMinutes } from '../utils/lock-duration.js'\nimport { recordAuditAction } from '../service/audit/audit-recorder.js'\nimport { errorCodeOf } from '../service/audit/audit-target.js'\n\nexport type SigninMethod = 'password' | 'passkey'\n\nexport type SigninAttempt = { userId?: string }\n\n/**\n * A sign-in, and its audit (ADR-0141 §3.3): a success or a failure with its code. A failure is\n * written on its own and stays when the request fails (§4-3). An account that does not exist leaves\n * no actor - the name typed in is not a person.\n */\nexport async function signin(attrs: { username: string; password: string }, context?) {\n return await auditedSignin('password', context, attempt => signinOnce(attrs, context, attempt))\n}\n\n/**\n * The audit around one way of signing in. `run` puts the person in `attempt` as soon as it knows\n * who it is, so a failure after that names them. `detail.method` tells a password from a passkey\n * (ADR-0156 decision 4).\n */\nexport async function auditedSignin<T extends { user: User }>(\n method: SigninMethod,\n context,\n run: (attempt: SigninAttempt) => Promise<T>\n): Promise<T> {\n const attempt: SigninAttempt = {}\n const domainId = context?.state?.domain?.id ?? null\n const detail = { method }\n\n /*\n * Not awaited: the answer does not wait on its record, and a write that fails is counted, never\n * thrown (`recordAuditAction` without a transaction).\n */\n try {\n const answer = await run(attempt)\n void recordAuditAction({ kind: 'auth.signin', outcome: 'ok', detail }, { actorId: answer.user.id, domainId })\n return answer\n } catch (error) {\n void recordAuditAction({ kind: 'auth.signin', outcome: 'error', code: errorCodeOf(error), detail }, { actorId: attempt.userId ?? null, domainId })\n throw error\n }\n}\n\nasync function signinOnce(attrs: { username: string; password: string }, context, attempt: SigninAttempt) {\n const { username } = attrs\n\n const repository = getRepository(User)\n\n var user = await repository.findOne({\n where: { username },\n relations: ['domains']\n })\n\n if (!user && /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(username)) {\n user = await repository.findOne({\n where: {\n email: ILike(username)\n },\n relations: ['domains']\n })\n }\n\n if (!user)\n throw new AuthError({\n errorCode: AuthError.ERROR_CODES.USER_NOT_FOUND\n })\n\n attempt.userId = user.id\n\n return await admitSignin(user, context, () => verifyPassword(user, attrs.password, context))\n}\n\n/**\n * The password against `user`, counting a miss: the fifth locks the account and mails how to unlock it. Signing in\n * and confirming it is still the person (ADR-0168) count misses together.\n */\nexport async function verifyPassword(user: User, password: string, context) {\n const repository = getRepository(User)\n\n if (!User.verify(user.password, password, user.salt)) {\n user.failCount++\n if (user.failCount >= 5) {\n user.status = UserStatus.LOCKED\n /* 언제 풀리나를 계산할 수 있게 시각을 남긴다. 이 칸이 비면 판정 규칙이\n 잠금을 유지하는 쪽으로 기운다. */\n user.lockedAt = new Date()\n }\n await repository.save(user)\n if (user.status == UserStatus.LOCKED) {\n sendUnlockUserEmail({\n user,\n context\n })\n throw new AuthError({\n errorCode: AuthError.ERROR_CODES.USER_LOCKED,\n detail: {\n username: user.username,\n email: user.email\n }\n })\n }\n throw new AuthError({\n errorCode: AuthError.ERROR_CODES.PASSWORD_NOT_MATCHED,\n detail: {\n username: user.username,\n email: user.email,\n failCount: user.failCount\n }\n })\n } else {\n user.failCount = 0\n await repository.save(user)\n }\n}\n\n/**\n * The account gate every way of signing in goes through (ADR-0156 decision 4): deleted, locked -\n * and a lock that has run out lifts here - then the secret, then inactive. Only `verifySecret`\n * differs between a password and a passkey; a passkey arrives already verified and passes nothing.\n *\n * `user` must carry `domains`.\n */\nexport async function admitSignin(user: User, context, verifySecret: () => Promise<void>) {\n const { domain } = context?.state || {}\n const repository = getRepository(User)\n\n if (user.status == UserStatus.DELETED) {\n throw new AuthError({\n errorCode: AuthError.ERROR_CODES.USER_DELETED\n })\n }\n\n if (user.status == UserStatus.LOCKED) {\n /* 시계는 한 번만 읽는다 — 두 번 읽으면 경계에 걸린 잠금이 두 판정 사이에서 갈린다. */\n const now = new Date()\n\n if (isAccountLocked(user, now, lockDurationMinutes())) {\n sendUnlockUserEmail({\n user,\n context\n })\n throw new AuthError({\n errorCode: AuthError.ERROR_CODES.USER_LOCKED,\n detail: {\n username: user.username,\n email: user.email\n }\n })\n }\n\n /*\n * 시간이 지나 스스로 풀렸다 — 그 자리에서 행을 되돌리고 계속 진행한다. 주기 작업으로\n * 풀지 않는 이유는 `account-lock-rule.ts` 에 있다(계정이 10만이면 그것이 따로 일이 된다).\n *\n * `failCount` 도 함께 0 으로 둔다. 그래서 **잠금이 곧 실패 셈의 창**이 된다 — 이 계정이\n * 다시 잠기려면 지금부터 다섯 번을 틀려야 한다.\n */\n user.status = UserStatus.ACTIVATED\n user.failCount = 0\n user.lockedAt = null\n\n await repository.save(user)\n }\n\n /*\n * Waiting for the password its person sets through a setup link (ADR-0161). Refused before the secret is looked at:\n * the password it carries is one nobody holds, so a guess against it is not a failure to count - and a passkey does\n * not open it either.\n */\n if (user.status == UserStatus.PWD_RESET_REQUIRED) {\n throw new AuthError({\n errorCode: AuthError.ERROR_CODES.PASSWORD_SETUP_PENDING\n })\n }\n\n await verifySecret()\n\n if (user.status == UserStatus.INACTIVE) {\n throw new AuthError({\n errorCode: AuthError.ERROR_CODES.USER_NOT_ACTIVATED,\n detail: {\n username: user.username,\n email: user.email\n }\n })\n }\n\n return {\n user,\n token: await user.sign({ subdomain: domain?.subdomain }),\n domains: user.domains || []\n }\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"signup.js","sourceRoot":"","sources":["../../server/controllers/signup.ts"],"names":[],"mappings":";;AASA,wBA8DC;AAvED,qCAA+B;AAC/B,iDAAqD;AAErD,8DAA4D;AAC5D,2DAAmD;AACnD,qDAA8C;AAC9C,2CAAoC;AACpC,uDAAyD;AAElD,KAAK,UAAU,MAAM,CAAC,KAAK,EAAE,qBAA+B;IACjE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,KAAK,CAAA;IAE3D,6CAA6C;IAC7C,cAAI,CAAC,sBAAsB,CAAC,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,CAAA;IAElD,MAAM,UAAU,GAAG,IAAA,qBAAa,EAAC,cAAI,CAAC,CAAA;IAEtC,IAAI,UAAU,GAAG,MAAM,UAAU,CAAC,OAAO,CAAC;QACxC,KAAK,EAAE,EAAE,QAAQ,EAAE;QACnB,SAAS,EAAE,CAAC,SAAS,CAAC;KACvB,CAAC,CAAA;IAEF,IAAI,CAAC,UAAU,IAAI,4BAA4B,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC/D,UAAU,GAAG,MAAM,UAAU,CAAC,OAAO,CAAC;YACpC,KAAK,EAAE,EAAE,KAAK,EAAE,IAAA,eAAK,EAAC,QAAQ,CAAC,EAAE;YACjC,SAAS,EAAE,CAAC,SAAS,CAAC;SACvB,CAAC,CAAA;IACJ,CAAC;IAED,IAAI,UAAU,EAAE,CAAC;QACf,MAAM,IAAI,yBAAS,CAAC;YAClB,SAAS,EAAE,+BAAe;YAC1B,MAAM,EAAE;gBACN,IAAI;gBACJ,QAAQ;aACT;SACF,CAAC,CAAA;IACJ,CAAC;IAED,MAAM,IAAI,GAAG,cAAI,CAAC,YAAY,EAAE,CAAA;IAEhC,IAAI,IAAI,GAAG,MAAM,UAAU,CAAC,IAAI,CAAC;QAC/B,QAAQ,EAAE,MAAM;QAChB,GAAG,KAAK;QACR,IAAI;QACJ,QAAQ,EAAE,cAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC;QACrC,iBAAiB,EAAE,IAAI,IAAI,EAAE;QAC7B,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE;KAChC,CAAC,CAAA;IAEF,IAAI,OAAO,GAAG,KAAK,CAAA;IACnB,IAAI,qBAAqB,EAAE,CAAC;QAC1B,OAAO,GAAG,MAAM,IAAA,uCAAqB,EAAC;YACpC,OAAO;YACP,IAAI;SACL,CAAC,CAAA;IACJ,CAAC;IAED,IAAI,CAAC;QACH,OAAO;YACL,KAAK,EAAE,MAAM,IAAA,kBAAM,EACjB;gBACE,QAAQ;gBACR,QAAQ;aACT,EACD,EAAE,MAAM,EAAE,CACX;SACF,CAAA;IACH,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAA;IACxB,CAAC;AACH,CAAC","sourcesContent":["import { ILike } from 'typeorm'\nimport { getRepository } from '@things-factory/shell'\n\nimport { USER_DUPLICATED } from '../constants/error-code.js'\nimport { AuthError } from '../errors/auth-error.js'\nimport { User } from '../service/user/user.js'\nimport { signin } from './signin.js'\nimport { sendVerificationEmail } from './verification.js'\n\nexport async function signup(attrs, withEmailVerification?: Boolean) {\n const { name, username, password, domain, context } = attrs\n\n /* check if password is following the rule */\n User.validatePasswordByRule(password, context.lng)\n\n const repository = getRepository(User)\n\n var duplicated = await repository.findOne({\n where: { username },\n relations: ['domains']\n })\n\n if (!duplicated && /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(username)) {\n duplicated = await repository.findOne({\n where: { email: ILike(username) },\n relations: ['domains']\n })\n }\n\n if (duplicated) {\n throw new AuthError({\n errorCode: USER_DUPLICATED,\n detail: {\n name,\n username\n }\n })\n }\n\n const salt = User.generateSalt()\n\n var user = await repository.save({\n userType: 'user',\n ...attrs,\n salt,\n password: User.encode(password, salt),\n passwordUpdatedAt: new Date(),\n domains: domain ? [domain] : []\n })\n\n var succeed = false\n if (withEmailVerification) {\n succeed = await sendVerificationEmail({\n context,\n user\n })\n }\n\n try {\n return {\n token: await signin(\n {\n username,\n password\n },\n { domain }\n )\n }\n } catch (e) {\n return { token: null }\n }\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"unlock-user.js","sourceRoot":"","sources":["../../server/controllers/unlock-user.ts"],"names":[],"mappings":";;;AAkBA,kDAiBC;AAED,gCAuCC;AA5ED,2DAAsD;AACtD,iDAAqD;AAErD,qDAA0D;AAC1D,+FAA2F;AAC3F,kEAAyD;AACzD,uDAA+C;AAC/C,mFAAwG;AACxG,mFAA0E;AAC1E,mFAA0E;AAE1E;;;;GAIG;AACU,QAAA,gBAAgB,GAAG,mBAAmB,CAAA;AAE5C,KAAK,UAAU,mBAAmB,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE;IACzD,IAAI,CAAC;QACH,IAAI,KAAK,GAAG,IAAA,kDAAqB,GAAE,CAAA;QACnC,IAAI,WAAW,GAAG,MAAM,IAAA,kDAAqB,EAAC,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,6CAAqB,CAAC,MAAM,CAAC,CAAA;QAE3F,IAAI,WAAW,EAAE,CAAC;YAChB,IAAI,UAAU,GAAG,GAAG,IAAA,gCAAY,EAAC,wBAAgB,EAAE,OAAO,CAAC,IAAI,KAAK,EAAE,CAAA;YACtE,MAAM,IAAA,sBAAS,EAAC;gBACd,QAAQ,EAAE,IAAI,CAAC,KAAK;gBACpB,GAAG,IAAA,6BAAW,EAAC,EAAE,IAAI,EAAE,gBAAgB,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC;aAC5E,CAAC,CAAA;YAEF,OAAO,IAAI,CAAA;QACb,CAAC;IACH,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,KAAK,CAAA;IACd,CAAC;AACH,CAAC;AAEM,KAAK,UAAU,UAAU,CAAC,KAAK,EAAE,QAAQ;IAC9C;;;;;;;;;;;OAWG;IACH,IAAI,CAAC,KAAK,IAAI,CAAC,QAAQ,EAAE,CAAC;QACxB,OAAO,KAAK,CAAA;IACd,CAAC;IAED,MAAM,YAAY,GAAG,MAAM,IAAA,sDAAyB,EAAC,KAAK,EAAE,6CAAqB,CAAC,MAAM,CAAC,CAAA;IAEzF;iFAC6E;IAC7E,IAAI,CAAC,YAAY,EAAE,MAAM,EAAE,CAAC;QAC1B,OAAO,KAAK,CAAA;IACd,CAAC;IAED,MAAM,EAAE,MAAM,EAAE,GAAG,YAAY,CAAA;IAE/B,IAAI,QAAQ,GAAG,MAAM,IAAA,qBAAa,EAAC,cAAI,CAAC,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,CAAA;IAClE,IAAI,CAAC,QAAQ;QAAE,OAAO,KAAK,CAAA;IAC3B,IAAI,QAAQ,CAAC,MAAM,IAAI,oBAAU,CAAC,MAAM;QAAE,OAAO,KAAK,CAAA;IAEtD,QAAQ,CAAC,MAAM,GAAG,oBAAU,CAAC,SAAS,CAAA;IACtC,QAAQ,CAAC,QAAQ,GAAG,cAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAA;IACxD,QAAQ,CAAC,SAAS,GAAG,CAAC,CAAA;IAEtB,MAAM,IAAA,qBAAa,EAAC,cAAI,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;IACxC,MAAM,IAAA,qDAAwB,EAAC,YAAY,CAAC,CAAA;IAE5C,OAAO,IAAI,CAAA;AACb,CAAC","sourcesContent":["import { sendEmail } from '@things-factory/email-base'\nimport { getRepository } from '@things-factory/shell'\n\nimport { User, UserStatus } from '../service/user/user.js'\nimport { VerificationTokenType } from '../service/verification-token/verification-token.js'\nimport { buildAuthURL } from '../utils/build-auth-url.js'\nimport { accountMail } from './account-mail.js'\nimport { consumeVerificationToken, findLiveVerificationToken } from './utils/find-verification-token.js'\nimport { makeVerificationToken } from './utils/make-verification-token.js'\nimport { saveVerificationToken } from './utils/save-verification-token.js'\n\n/**\n * The page the mail links to. The token rides after `#`, so the browser never sends it — not to our\n * access log, not to a proxy's. The page reads it and posts it. `tests/invitation-route.test.ts`\n * holds this against the router's table.\n */\nexport const UNLOCK_USER_PATH = '/auth/unlock-user'\n\nexport async function sendUnlockUserEmail({ user, context }) {\n try {\n var token = makeVerificationToken()\n var verifaction = await saveVerificationToken(user.id, token, VerificationTokenType.UNLOCK)\n\n if (verifaction) {\n var serviceUrl = `${buildAuthURL(UNLOCK_USER_PATH, context)}#${token}`\n await sendEmail({\n receiver: user.email,\n ...accountMail({ kind: 'unlock-account', user, link: serviceUrl, context })\n })\n\n return true\n }\n } catch (e) {\n return false\n }\n}\n\nexport async function unlockUser(token, password) {\n /*\n * An absent condition is not a narrow condition — TypeORM drops `undefined` from `where`.\n *\n * Without this guard, a call with no token searched `{ type: UNLOCK }` alone and matched\n * **anyone's** pending unlock token, then handed that account the password the caller chose.\n * Both halves are reachable without credentials: five wrong guesses lock a victim and mint the\n * token, and `/auth/unlock-user` accepted a body with a password and no token because its own\n * guard read `!(token || password)`.\n *\n * [확인함] 2026-09-09 · locked a victim by failing signin five times, posted a password with no\n * token, and signed in as them — status activated, failCount 0, password the attacker's.\n */\n if (!token || !password) {\n return false\n }\n\n const verification = await findLiveVerificationToken(token, VerificationTokenType.UNLOCK)\n\n /* Refuse a token that is gone, rather than destructuring null — a reused link showed the\n screen a 500 instead of a reason. `resetPassword` refuses the same way. */\n if (!verification?.userId) {\n return false\n }\n\n const { userId } = verification\n\n var userInfo = await getRepository(User).findOneBy({ id: userId })\n if (!userInfo) return false\n if (userInfo.status != UserStatus.LOCKED) return false\n\n userInfo.status = UserStatus.ACTIVATED\n userInfo.password = User.encode(password, userInfo.salt)\n userInfo.failCount = 0\n\n await getRepository(User).save(userInfo)\n await consumeVerificationToken(verification)\n\n return true\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"find-verification-token.js","sourceRoot":"","sources":["../../../server/controllers/utils/find-verification-token.ts"],"names":[],"mappings":";;AAiBA,8DAiBC;AAGD,4DAMC;AA3CD,iDAAqD;AAErD,kGAAiH;AACjH,uFAAuG;AAEvG;;;;;;;;;;;GAWG;AACI,KAAK,UAAU,yBAAyB,CAC7C,KAAc,EACd,IAA2B,EAC3B,MAAY,IAAI,IAAI,EAAE;IAEtB,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,EAAE,CAAC;QACxC,OAAO,IAAI,CAAA;IACb,CAAC;IAED,MAAM,KAAK,GAAG,MAAM,IAAA,qBAAa,EAAC,yCAAiB,CAAC,CAAC,OAAO,CAAC;QAC3D,KAAK,EAAE;YACL,KAAK,EAAE,IAAA,kDAAqB,EAAC,KAAK,CAAC;YACnC,IAAI;SACL;KACF,CAAC,CAAA;IAEF,OAAO,KAAK,EAAE,MAAM,IAAI,IAAA,oDAAuB,EAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAA;AAC5E,CAAC;AAED,4DAA4D;AACrD,KAAK,UAAU,wBAAwB,CAAC,KAAwB;IACrE,MAAM,IAAA,qBAAa,EAAC,yCAAiB,CAAC,CAAC,MAAM,CAAC;QAC5C,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,IAAI,EAAE,KAAK,CAAC,IAAI;KACjB,CAAC,CAAA;AACJ,CAAC","sourcesContent":["import { getRepository } from '@things-factory/shell'\n\nimport { VerificationToken, VerificationTokenType } from '../../service/verification-token/verification-token.js'\nimport { hashVerificationToken, isVerificationTokenLive } from '../../utils/verification-token-rule.js'\n\n/**\n * The stored token a mailed link carries, if it is still usable — the one lookup the reset, unlock\n * and sign-up confirmation paths share.\n *\n * Found by hash and by type: a reset link does not confirm an address, and an activation link does\n * not reset a password. A token past its `expiresAt`, or without one, answers `null` like a token\n * that was never issued; the caller already refuses that.\n *\n * `token` is checked before the query. An absent condition is not a narrow condition — TypeORM drops\n * `undefined` from `where` — and without the check a call with no token would match anyone's row of\n * that type (the 2026-09-09 takeover, `unlock-user.ts`).\n */\nexport async function findLiveVerificationToken(\n token: unknown,\n type: VerificationTokenType,\n now: Date = new Date()\n): Promise<VerificationToken | null> {\n if (typeof token !== 'string' || !token) {\n return null\n }\n\n const found = await getRepository(VerificationToken).findOne({\n where: {\n token: hashVerificationToken(token),\n type\n }\n })\n\n return found?.userId && isVerificationTokenLive(found, now) ? found : null\n}\n\n/** Uses up a token found by `findLiveVerificationToken`. */\nexport async function consumeVerificationToken(found: VerificationToken): Promise<void> {\n await getRepository(VerificationToken).delete({\n userId: found.userId,\n token: found.token,\n type: found.type\n })\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"make-verification-token.js","sourceRoot":"","sources":["../../../server/controllers/utils/make-verification-token.ts"],"names":[],"mappings":";;AACA,sDAEC;;AAHD,4DAA2B;AAC3B,SAAgB,qBAAqB;IACnC,OAAO,gBAAM,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;AAC/C,CAAC","sourcesContent":["import crypto from 'crypto'\nexport function makeVerificationToken() {\n return crypto.randomBytes(16).toString('hex')\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"password-rule.js","sourceRoot":"","sources":["../../../server/controllers/utils/password-rule.ts"],"names":[],"mappings":"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","sourcesContent":["import i18next from 'i18next'\n\nimport { config } from '@things-factory/env'\n\nimport { PASSWORD_PATTERN_NOT_MATCHED } from '../../constants/error-code.js'\nimport { AuthError } from '../../errors/auth-error.js'\n\nconst passwordConfig = config.get('password') || {\n lowerCase: true,\n upperCase: true,\n digit: true,\n specialCharacter: true,\n allowRepeat: false,\n useTightPattern: true,\n useLoosePattern: false,\n tightCharacterLength: 8,\n looseCharacterLength: 15\n}\n\nfunction generatePasswordPatternRegExp({\n lowerCase = true,\n upperCase = true,\n digit = true,\n specialCharacter = true,\n allowRepeat = false,\n useTightPattern = true,\n useLoosePattern = false,\n tightCharacterLength = 8,\n looseCharacterLength = 15\n} = {}) {\n var tightChecklist = useTightPattern\n ? [\n lowerCase ? '(?=.*[a-z])' : '', // has at least one lower case character\n upperCase ? '(?=.*[A-Z])' : '', // has at least one upper case character\n digit ? '(?=.*\\\\d)' : '', // has at least one digit\n specialCharacter ? '(?=.*[!@#$%^&*()])' : '', // has at least one special character\n !allowRepeat ? '(?!.*(.)\\\\1(?=\\\\1{1,}))' : '', // has not an repeated character more than twice\n `.{${tightCharacterLength},}` // has a length of 8 and more\n ]\n : []\n\n var looseChecklist = useLoosePattern\n ? [\n `.{${looseCharacterLength},}` // has a length of 15 and more\n ]\n : []\n\n var checkList = [\n '^', // from start\n ...tightChecklist,\n tightChecklist.length && looseChecklist.length ? '|' : '',\n ...looseChecklist,\n '$' //to the end\"\n ]\n\n return new RegExp(checkList.join(''))\n}\n\nexport function generatePasswordPatternHelp(\n {\n lowerCase = true,\n upperCase = true,\n digit = true,\n specialCharacter = true,\n allowRepeat = false,\n useTightPattern = true,\n useLoosePattern = false,\n tightCharacterLength = 8,\n looseCharacterLength = 15\n } = {},\n lng\n) {\n lng = lng || 'en-US'\n var descriptions = []\n\n const t = i18next.getFixedT(lng, 'translations')\n\n if (useLoosePattern) {\n descriptions.push(`more than ${looseCharacterLength} characters`)\n }\n\n if (useTightPattern) {\n !useLoosePattern &&\n descriptions.push(\n t('text.pattern_minimum_charaters', {\n length: tightCharacterLength\n })\n )\n lowerCase && descriptions.push(t('text.pattern_atleast_1_lowercase'))\n upperCase && descriptions.push(t('text.pattern_atleast_1_uppercase'))\n digit && descriptions.push(t('text.pattern_atleast_1_digit'))\n specialCharacter && descriptions.push(t('text.pattern_atleast_1_special'))\n !allowRepeat && descriptions.push(t('text.pattern_not_allowed'))\n }\n\n return descriptions.join(', ')\n}\n\nexport const passwordPattern = generatePasswordPatternRegExp(passwordConfig)\nexport function passwordHelp(lng) {\n return generatePasswordPatternHelp(passwordConfig, lng)\n}\n\nexport function validatePasswordByRule(password, lng) {\n if (!passwordPattern) {\n return\n }\n\n if (passwordPattern.test(password)) {\n return\n }\n\n const rule = generatePasswordPatternHelp(passwordConfig, lng)\n throw new AuthError({\n errorCode: PASSWORD_PATTERN_NOT_MATCHED,\n detail: {\n rule\n }\n })\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"save-verification-token.js","sourceRoot":"","sources":["../../../server/controllers/utils/save-verification-token.ts"],"names":[],"mappings":";;AAWA,sDAQC;AAnBD,iDAAqD;AAErD,kGAAiH;AACjH,uFAA0G;AAE1G;;;;;GAKG;AACI,KAAK,UAAU,qBAAqB,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,GAAG,6CAAqB,CAAC,UAAU;IAC5F,MAAM,gBAAgB,GAAG,IAAA,qBAAa,EAAC,yCAAiB,CAAC,CAAA;IACzD,OAAO,MAAM,gBAAgB,CAAC,IAAI,CAAC;QACjC,MAAM,EAAE,EAAE;QACV,KAAK,EAAE,IAAA,kDAAqB,EAAC,KAAK,CAAC;QACnC,IAAI;QACJ,SAAS,EAAE,IAAA,uDAA0B,EAAC,IAAI,CAAC;KAC5C,CAAC,CAAA;AACJ,CAAC","sourcesContent":["import { getRepository } from '@things-factory/shell'\n\nimport { VerificationToken, VerificationTokenType } from '../../service/verification-token/verification-token.js'\nimport { hashVerificationToken, verificationTokenExpiresAt } from '../../utils/verification-token-rule.js'\n\n/**\n * Keeps a mailed token: its hash, and when it stops working. The token itself goes only into the mail.\n *\n * ⚠ The primary key is `userId`, so a person holds one token of any type at a time — saving a reset\n * token replaces a pending activation or unlock token. `docs/design/auth-rebuild.md` §8-7.\n */\nexport async function saveVerificationToken(id, token, type = VerificationTokenType.ACTIVATION) {\n const verificationRepo = getRepository(VerificationToken)\n return await verificationRepo.save({\n userId: id,\n token: hashVerificationToken(token),\n type,\n expiresAt: verificationTokenExpiresAt(type)\n })\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"verification.js","sourceRoot":"","sources":["../../server/controllers/verification.ts"],"names":[],"mappings":"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","sourcesContent":["import { sendEmail } from '@things-factory/email-base'\nimport { getRepository } from '@things-factory/shell'\n\nimport { AuthError } from '../errors/auth-error.js'\nimport { User, UserStatus } from '../service/user/user.js'\nimport { VerificationTokenType } from '../service/verification-token/verification-token.js'\nimport { buildAuthURL } from '../utils/build-auth-url.js'\nimport { accountMail } from './account-mail.js'\nimport { consumeVerificationToken, findLiveVerificationToken } from './utils/find-verification-token.js'\nimport { makeVerificationToken } from './utils/make-verification-token.js'\nimport { saveVerificationToken } from './utils/save-verification-token.js'\n\n/**\n * The page the mail links to. The token rides after `#`, so the browser never sends it — not to our\n * access log, not to a proxy's. The page reads it and posts it. `tests/invitation-route.test.ts`\n * holds this against the router's table.\n */\nexport const VERIFY_EMAIL_PATH = '/auth/verify'\n\nexport async function sendVerificationEmail({ user, context }) {\n try {\n var token = makeVerificationToken()\n var verifaction = await saveVerificationToken(user.id, token)\n\n if (verifaction) {\n var serviceUrl = `${buildAuthURL(VERIFY_EMAIL_PATH, context)}#${token}`\n await sendEmail({\n receiver: user.email,\n ...accountMail({ kind: 'verify-email', user, link: serviceUrl, context })\n })\n\n return true\n }\n } catch (e) {\n return false\n }\n}\n\nexport async function verify(token) {\n /* Activation only. This read the token alone, so a reset or unlock link confirmed an address too. */\n var verification = await findLiveVerificationToken(token, VerificationTokenType.ACTIVATION)\n\n if (!verification) {\n throw new AuthError({\n errorCode: AuthError.ERROR_CODES.VERIFICATION_ERROR\n })\n }\n\n var userInfo = await getRepository(User).findOneBy({ id: verification.userId })\n if (!userInfo) {\n throw new AuthError({\n errorCode: AuthError.ERROR_CODES.VERIFICATION_ERROR\n })\n }\n\n if (!(userInfo.status == UserStatus.INACTIVE || userInfo.status == UserStatus.LOCKED)) {\n throw new AuthError({\n errorCode: AuthError.ERROR_CODES.VERIFICATION_ERROR\n })\n }\n\n userInfo.status = UserStatus.ACTIVATED\n userInfo.failCount = 0\n\n await getRepository(User).save(userInfo)\n await consumeVerificationToken(verification)\n}\n\n/**\n * `'not-due'` when no account at this address waits for confirmation, so the route can answer it\n * the way it answers a sent mail. `'failed'` only when a mail was due and did not go.\n */\nexport async function resendVerificationEmail(email, context): Promise<'sent' | 'not-due' | 'failed'> {\n /* TypeORM drops an undefined condition from `where` - without this, a post with no address\n matched the first account in the table and mailed it. */\n if (!email) return 'not-due'\n\n var user = await getRepository(User).findOne({\n where: {\n email\n }\n })\n\n if (!user) return 'not-due'\n if (user.status == UserStatus.ACTIVATED) return 'not-due'\n\n return (await sendVerificationEmail({ user, context })) ? 'sent' : 'failed'\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"auth-error.js","sourceRoot":"","sources":["../../server/errors/auth-error.ts"],"names":[],"mappings":";;;;AAAA,gFAAyD;AAMzD,MAAa,SAAU,SAAQ,KAAK;IAClC,MAAM,KAAK,WAAW;QACpB,OAAO;YACL,GAAG,WAAW;SACf,CAAA;IACH,CAAC;IAKD,YAAY,EAAE,SAAS,EAAE,MAAM,EAAqB;QAClD,KAAK,CAAC,SAAS,CAAC,CAAA;QAEhB,IAAI,CAAC,IAAI,GAAG,YAAY,CAAA;QACxB,IAAI,CAAC,SAAS,GAAG,SAAS,CAAA;QAC1B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAA;IACtB,CAAC;CACF;AAjBD,8BAiBC","sourcesContent":["import * as ERROR_CODES from '../constants/error-code.js'\n\ntype AuthErrorArgument = {\n errorCode: string\n detail?: Object\n}\nexport class AuthError extends Error {\n static get ERROR_CODES(): any {\n return {\n ...ERROR_CODES\n }\n }\n\n errorCode: any\n detail: Object\n\n constructor({ errorCode, detail }: AuthErrorArgument) {\n super(errorCode)\n\n this.name = 'auth-error'\n this.errorCode = errorCode\n this.detail = detail\n }\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/errors/index.ts"],"names":[],"mappings":";;;AAAA,0DAA+B;AAC/B,2EAAgD","sourcesContent":["export * from './auth-error.js'\nexport * from './user-domain-not-match-error.js'\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"user-domain-not-match-error.js","sourceRoot":"","sources":["../../server/errors/user-domain-not-match-error.ts"],"names":[],"mappings":";;;AAAA,8DAKmC;AACnC,mDAA2C;AAE3C,MAAa,WAAY,SAAQ,yBAAS;IACxC,MAAM,KAAK,WAAW;QACpB,OAAO;YACL,kBAAkB,EAAlB,kCAAkB;YAClB,mBAAmB,EAAnB,mCAAmB;YACnB,kBAAkB,EAAlB,kCAAkB;YAClB,0BAA0B,EAA1B,0CAA0B;SAC3B,CAAA;IACH,CAAC;IAID,YAAY,EAAE,SAAS,GAAG,kCAAkB,EAAE,OAAO,EAAE;QACrD,KAAK,CAAC;YACJ,SAAS;SACV,CAAC,CAAA;QAEF,IAAI,CAAC,IAAI,GAAG,uBAAuB,CAAA;QACnC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAA;IACxB,CAAC;CACF;AApBD,kCAoBC","sourcesContent":["import {\n NO_AVAILABLE_DOMAIN,\n NO_SELECTED_DOMAIN,\n REDIRECT_TO_DEFAULT_DOMAIN,\n UNAVAILABLE_DOMAIN\n} from '../constants/error-code.js'\nimport { AuthError } from './auth-error.js'\n\nexport class DomainError extends AuthError {\n static get ERROR_CODES(): any {\n return {\n UNAVAILABLE_DOMAIN,\n NO_AVAILABLE_DOMAIN,\n NO_SELECTED_DOMAIN,\n REDIRECT_TO_DEFAULT_DOMAIN\n }\n }\n\n domains: any\n\n constructor({ errorCode = UNAVAILABLE_DOMAIN, domains }) {\n super({\n errorCode\n })\n\n this.name = 'user-domain-not-match'\n this.domains = domains\n }\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../server/index.ts"],"names":[],"mappings":";;;AAAA,6EAA6E;AAC7E,6EAA0E;AAC1E,+EAAsF;AACtF,2EAAwE;AAExE,uBAAoB;AAEpB,6DAAkC;AAClC,uGAAuG;AACvG,6EAAkD;AAClD,yEAA8C;AAE9C,gEAAqC;AAErC,iEAAsC;AACtC,4DAAiC;AACjC,sDAA2B;AAE3B,sEAA2C;AAC3C,sEAA2C;AAC3C,yEAA8C;AAC9C,6EAAkD;AAClD,sEAA2C;AAC3C,gEAAqC;AACrC,+EAAoD;AACpD,yEAA8C;AAC9C,mEAAwC;AACxC,sEAA2C;AAC3C,yEAA8C;AAC9C,oEAAyC;AACzC,0GAA0G;AAC1G,0EAA+C;AAE/C,4DAAiC;AAEjC,qDAA0B;AAE1B,OAAO,CAAC,EAAE,CAAC,wBAA+B,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAO,EAAE,EAAE;IACjF,MAAM,IAAA,yCAAqB,EAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,YAAY,CAAqC,CAAC,CAAC,CAAA;IAErG,OAAO,CAAC,GAAG,CAAC,2EAA2E,CAAC,CAAA;IAExF;;;;;OAKG;IACH,IAAA,8CAA2B,GAAE,CAAA;IAE7B;;;;;OAKG;IACH,IAAA,sCAAkB,GAAE,CAAA;IAEpB,kFAAkF;IAClF,IAAA,wCAAmB,GAAE,CAAA;AACvB,CAAC,CAAC,CAAA;AAEF,uEAAuE;AACvE,mDAA4C;AAE5C,KAAK,MAAM,IAAI,IAAI,0BAAU;IAAE,IAAA,+BAAgB,EAAC,IAAI,CAAC,CAAA","sourcesContent":["import { synchronizePrivileges } from './service/privilege/privilege-sync.js'\nimport { reportDeclaredAxes } from './service/privilege/privilege-axis.js'\nimport { reportUnknownTemplateGrants } from './service/role-template/role-template.js'\nimport { startAuditRetention } from './service/audit/audit-retention.js'\n\nimport './routes.js'\n\nexport * from './service/index.js'\n/* AI 입력 declarations - plain data, registered by an application that runs the assistant (ADR-0103). */\nexport * from './ai-entry/access-entry-targets.js'\nexport * from './ai-entry/access-assistant.js'\nexport type { EntryTargetDeclaration as AuthEntryTarget } from './ai-entry/entry-target-shape.js'\nexport * from './migrations/index.js'\n\nexport * from './middlewares/index.js'\nexport * from './router/index.js'\nexport * from './routes.js'\n\nexport * from './utils/get-domain-users.js'\nexport * from './utils/get-user-domains.js'\nexport * from './utils/checkin-domain-rule.js'\nexport * from './utils/can-user-checkin-domain.js'\nexport * from './utils/domain-ownership.js'\nexport * from './utils/get-secret.js'\nexport * from './utils/check-user-belongs-domain.js'\nexport * from './utils/access-token-cookie.js'\nexport * from './utils/encrypt-state.js'\nexport * from './utils/check-permission.js'\nexport * from './utils/check-user-has-role.js'\nexport * from './utils/build-auth-url.js'\n/* A waiting account and its setup link (ADR-0161) - for a module that makes accounts, as contact does. */\nexport * from './controllers/password-setup.js'\n\nexport * from './errors/index.js'\n\nexport * from './types.js'\n\nprocess.on('bootstrap-module-start' as any, async ({ app, config, client }: any) => {\n await synchronizePrivileges(Object.values(process['PRIVILEGES'] as Record<string, [string, string]>))\n\n console.log('[auth-base:bootstrap] Synchronization for privilege master has just done.')\n\n /*\n * Templates name privileges by (category, axis), and a category can move. A template\n * that survives a rename becomes a role that grants nothing — the administrator picks\n * \"quality inspector\", gets a role, and no door opens. Check the names once, here, where\n * the privilege registry has just been filled.\n */\n reportUnknownTemplateGrants()\n\n /*\n * Say which axis vocabulary this installation ended up with. It cannot go in the SDL —\n * it differs per installation, which is the whole point — so a boot line is the honest\n * substitute, and it separates \"nobody declared, these are the defaults\" from \"someone\n * declared exactly these\".\n */\n reportDeclaredAxes()\n\n /* Audit rows past the retention go, once a day, on one instance (ADR-0141 §7). */\n startAuditRetention()\n})\n\nimport { registerRoleTask } from './service/role-template/role-task.js'\nimport { ROLE_TASKS } from './role-tasks.js'\n\nfor (const task of ROLE_TASKS) registerRoleTask(task)\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"audit-scope-middleware.js","sourceRoot":"","sources":["../../server/middlewares/audit-scope-middleware.ts"],"names":[],"mappings":";;AAQA,oDAIC;AAZD,0EAAoE;AAEpE;;;;;GAKG;AACI,KAAK,UAAU,oBAAoB,CAAC,OAAY,EAAE,IAAwB;IAC/E,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,IAAI,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAA;IAEpG,OAAO,MAAM,IAAA,mCAAe,EAAC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,CAAA;AACtD,CAAC","sourcesContent":["import { runInAuditScope } from '../service/audit/audit-recorder.js'\n\n/**\n * Every request runs as one audited request (ADR-0141 §3.1). A GraphQL request always leaves its\n * line; any other request only when something in it was recorded - a sign-in, a check-in.\n *\n * Installed first, so the sign-in routes and the GraphQL authentication after it are inside.\n */\nexport async function auditScopeMiddleware(context: any, next: () => Promise<any>) {\n const channel = context.method == 'POST' && context.path.startsWith('/graphql') ? 'graphql' : 'http'\n\n return await runInAuditScope(context, channel, next)\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"authenticate-401-middleware.js","sourceRoot":"","sources":["../../server/middlewares/authenticate-401-middleware.ts"],"names":[],"mappings":"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","sourcesContent":["import { config } from '@things-factory/env'\n\nimport { SUBDOMAIN_NOTFOUND, USER_DUPLICATED, USER_LOCKED, USER_NOT_ACTIVATED } from '../constants/error-code.js'\nimport { AuthError } from '../errors/auth-error.js'\nimport { accepts } from '../utils/accepts.js'\n\nconst disableUserSignupProcess = config.get('disableUserSignupProcess', false)\nconst disableUserFavoredLanguage = config.get('i18n/disableUserFavoredLanguage', false)\nconst languages = config.get('i18n/languages', false)\n\nexport async function authenticate401Middleware(context, next) {\n try {\n await next()\n } catch (err) {\n var message\n\n if (err instanceof AuthError) {\n message = (context.t && context.t(`error.${err.errorCode}`, err.detail || {})) || err.errorCode\n } else {\n if (err?.status !== 401) {\n throw err\n }\n\n message = err.message\n }\n\n context.status = 401\n context.body = message\n\n /*\n * 클라이언트 라우팅을 위한 ApiHistoryFallback의 상황과,\n * 서버라우팅의 상황에서 발생하는 던져지는 401 에러인 경우에는\n * error code에 맞춰서 적절하게 rewriting 되도록 한다.\n */\n\n const { method, header, path, originalUrl } = context\n\n if (method == 'POST' && path.startsWith('/graphql')) {\n if (err.errorCode == SUBDOMAIN_NOTFOUND) {\n context.status = 403\n }\n return\n }\n\n if (!accepts(header.accept, ['text/html', '*/*'])) {\n return\n }\n\n const { redirect_to } = context.query\n const { redirectTo = redirect_to || originalUrl } = context.request.body || {}\n\n switch (err.errorCode) {\n case SUBDOMAIN_NOTFOUND:\n context.redirect(`/auth/checkin?redirect_to=${encodeURIComponent(redirectTo)}`)\n break\n\n case USER_LOCKED:\n return await context.render('auth-page', {\n pageElement: 'auth-activate',\n elementScript: '/auth/activate.js',\n data: {\n ...err.detail,\n /* One page answers both - the code is how it tells a locked account from one not yet confirmed. */\n errorCode: err.errorCode,\n message,\n redirectTo,\n disableUserSignupProcess,\n disableUserFavoredLanguage,\n languages\n }\n })\n\n case USER_NOT_ACTIVATED:\n return await context.render('auth-page', {\n pageElement: 'auth-activate',\n elementScript: '/auth/activate.js',\n data: {\n ...err.detail,\n /* One page answers both - the code is how it tells a locked account from one not yet confirmed. */\n errorCode: err.errorCode,\n message,\n redirectTo,\n disableUserSignupProcess,\n disableUserFavoredLanguage,\n languages\n }\n })\n\n case USER_DUPLICATED:\n return await context.render('auth-page', {\n pageElement: 'auth-signup',\n elementScript: '/auth/signup.js',\n data: {\n ...err.detail,\n message,\n redirectTo,\n disableUserSignupProcess,\n disableUserFavoredLanguage,\n languages\n }\n })\n\n default:\n return await context.render('auth-page', {\n pageElement: 'auth-signin',\n elementScript: '/auth/signin.js',\n data: {\n ...err.detail,\n message: err instanceof AuthError ? message : '',\n redirectTo,\n disableUserSignupProcess,\n disableUserFavoredLanguage,\n languages\n }\n })\n }\n }\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"bypass-signin-middleware.js","sourceRoot":"","sources":["../../server/middlewares/bypass-signin-middleware.ts"],"names":[],"mappings":";;AAKA,wDAgBC;AArBD,iDAA6D;AAE7D,qDAA8C;AAC9C,4EAAsE;AAE/D,KAAK,UAAU,sBAAsB,CAAC,OAAO,EAAE,IAAI;IACxD,MAAM,YAAY,GAAG,MAAM,IAAA,qBAAa,EAAC,cAAM,CAAC,CAAC,OAAO,CAAC;QACvD,KAAK,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE;KAC5B,CAAC,CAAA;IAEF,MAAM,KAAK,GAAG,MAAM,IAAA,qBAAa,EAAC,cAAI,CAAC,CAAC,OAAO,CAAC;QAC9C,KAAK,EAAE,EAAE,EAAE,EAAE,YAAY,CAAC,KAAK,EAAE;QACjC,SAAS,EAAE,CAAC,SAAS,CAAC;KACvB,CAAC,CAAA;IAEF,OAAO,CAAC,KAAK,CAAC,IAAI,GAAG,KAAK,CAAA;IAE1B,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,IAAI,EAAE,CAAA;IAChC,IAAA,6CAAoB,EAAC,OAAO,EAAE,KAAK,CAAC,CAAA;IAEpC,MAAM,IAAI,EAAE,CAAA;AACd,CAAC","sourcesContent":["import { Domain, getRepository } from '@things-factory/shell'\n\nimport { User } from '../service/user/user.js'\nimport { setAccessTokenCookie } from '../utils/access-token-cookie.js'\n\nexport async function bypassSigninMiddleware(context, next) {\n const systemDomain = await getRepository(Domain).findOne({\n where: { systemFlag: true }\n })\n\n const admin = await getRepository(User).findOne({\n where: { id: systemDomain.owner },\n relations: ['domains']\n })\n\n context.state.user = admin\n\n const token = await admin.sign()\n setAccessTokenCookie(context, token)\n\n await next()\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"domain-authenticate-middleware.js","sourceRoot":"","sources":["../../server/middlewares/domain-authenticate-middleware.ts"],"names":[],"mappings":";;AAkFA,oEAqBC;AAvGD,iDAA6D;AAE7D,2DAAmD;AACnD,qDAA8C;AAC9C,oFAA0E;AAC1E,sEAA4D;AAW5D;;;GAGG;AACH,OAAO,CAAC,kBAAkB,GAAG,KAAK,EAAE,MAAc,EAAE,IAAU,EAAoB,EAAE;IAClF,IAAI,CAAC,IAAI,IAAI,CAAC,MAAM;QAAE,OAAO,KAAK,CAAA;IAElC,uCAAuC;IACvC,IAAI,MAAM,CAAC,KAAK,KAAK,IAAI,CAAC,EAAE;QAAE,OAAO,IAAI,CAAA;IAEzC,yDAAyD;IACzD,iDAAiD;IACjD,wCAAwC;IACxC,qDAAqD;IACrD,MAAM,QAAQ,GAAG,wBAAwB,MAAM,CAAC,EAAE,EAAE,CAAA;IACpD,MAAM,MAAM,GAAI,IAAY,CAAC,QAAQ,CAAC,CAAA;IACtC,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,MAAM,CAAA;IAEvC,MAAM,KAAK,GAAG,MAAM,IAAA,mCAAa,EAAC,MAAM,EAAE,IAAI,CAAC,CAC9C;IAAC,IAAY,CAAC,QAAQ,CAAC,GAAG,KAAK,CAAA;IAChC,OAAO,KAAK,CAAA;AACd,CAAC,CAAA;AAED;;;;;;;;;;GAUG;AACH,OAAO,CAAC,gBAAgB,GAAG,KAAK,EAAE,MAAc,EAAE,IAAU,EAAoB,EAAE;IAChF,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,KAAK,CAAA;IACd,CAAC;IAED,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;QACzB,IAAI,GAAG,MAAM,IAAA,qBAAa,EAAC,cAAI,CAAC,CAAC,OAAO,CAAC;YACvC,KAAK,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE;YACtB,SAAS,EAAE,CAAC,SAAS,CAAC;SACvB,CAAC,CAAA;IACJ,CAAC;IAED,MAAM,YAAY,GAAW,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,MAAc,EAAE,EAAE,CAAC,MAAM,CAAC,SAAS,KAAK,QAAQ,CAAC,CAAA;IACjG,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,OAAO,KAAK,CAAA;IACd,CAAC;IAED,OAAO,YAAY,CAAC,KAAK,KAAK,IAAI,CAAC,EAAE,CAAA;AACvC,CAAC,CAAA;AAED;;;;;;;;;;GAUG;AAEI,KAAK,UAAU,4BAA4B,CAAC,OAAY,EAAE,IAAS;IACxE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;IAEtC,MAAM,SAAS,GAAW,MAAM,EAAE,SAAS,CAAA;IAE3C,gCAAgC;IAChC,mBAAmB;IACnB,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,MAAM,IAAI,yBAAS,CAAC;YAClB,SAAS,EAAE,yBAAS,CAAC,WAAW,CAAC,kBAAkB;SACpD,CAAC,CAAA;IACJ,CAAC;IAED,4BAA4B;IAC5B,IAAI,CAAC,MAAM,IAAA,iDAAoB,EAAC,IAAI,EAAE,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,OAAO,CAAC,gBAAgB,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC;QACjG,OAAO,MAAM,IAAI,EAAE,CAAA;IACrB,CAAC;IAED,MAAM,IAAI,yBAAS,CAAC;QAClB,SAAS,EAAE,yBAAS,CAAC,WAAW,CAAC,kBAAkB;KACpD,CAAC,CAAA;AACJ,CAAC","sourcesContent":["import { Domain, getRepository } from '@things-factory/shell'\n\nimport { AuthError } from '../errors/auth-error.js'\nimport { User } from '../service/user/user.js'\nimport { canUserCheckinDomain } from '../utils/can-user-checkin-domain.js'\nimport { isDomainOwner } from '../utils/domain-ownership.js'\n\ndeclare global {\n namespace NodeJS {\n interface Process {\n domainOwnerGranted: (domain: Domain, user: User) => Promise<boolean>\n superUserGranted: (domain: Domain, user: User) => Promise<boolean>\n }\n }\n}\n\n/**\n * 도메인 소유권 확인. 판정 규칙은 `isDomainOwner` 가 단일 소스이며\n * (`Domain.owner` 캐시 컬럼 OR `DomainOwner` 테이블), 여기서는 요청 단위 메모이제이션만 더한다.\n */\nprocess.domainOwnerGranted = async (domain: Domain, user: User): Promise<boolean> => {\n if (!user || !domain) return false\n\n // 조회가 필요 없는 캐시 필드 경로는 메모이제이션 이전에 판정된다.\n if (domain.owner === user.id) return true\n\n // Request-level 메모이제이션 — 한 요청 안에서 @privilege directive 가\n // 걸린 field 마다 이 함수가 호출될 수 있는데 (list 쿼리의 경우 N 번),\n // 같은 (domain, user) 조합은 DB 조회를 1 번만 수행.\n // user 객체는 request-scoped 이므로 request 종료 시 자연스럽게 소멸.\n const cacheKey = `__domainOwnerGranted:${domain.id}`\n const cached = (user as any)[cacheKey]\n if (cached !== undefined) return cached\n\n const owned = await isDomainOwner(domain, user)\n ;(user as any)[cacheKey] = owned\n return owned\n}\n\n/**\n * 수퍼유저 확인 — 모든 도메인을 통과하는 전역 특권이므로 의도적으로 좁게 정의한다.\n *\n * SYSTEM 도메인에 소속되고, 그 도메인의 **대표 오너(`Domain.owner` 컬럼)** 인 한 사람만\n * 수퍼유저다. SYSTEM 도메인의 공동 오너(`DomainOwner` 테이블)는 SYSTEM 도메인의 오너로서만\n * 권한을 가지며 수퍼유저가 되지 않는다 — 그래서 여기서는 `isDomainOwner` 를 쓰지 않는다.\n *\n * 주의: `Domain.owner` 컬럼은 오너 제거 시 남은 공동 오너 중 가장 먼저 추가된 사람으로\n * 자동 재설정된다(`removeDomainOwner`). SYSTEM 도메인에서는 그 재설정이 곧 수퍼유저 이전을\n * 뜻한다.\n */\nprocess.superUserGranted = async (domain: Domain, user: User): Promise<boolean> => {\n if (!user) {\n return false\n }\n\n if (!user.domains.length) {\n user = await getRepository(User).findOne({\n where: { id: user.id },\n relations: ['domains']\n })\n }\n\n const systemDomain: Domain = user.domains.find((domain: Domain) => domain.subdomain === 'system')\n if (!systemDomain) {\n return false\n }\n\n return systemDomain.owner === user.id\n}\n\n/*\n * 현재 도메인에 대한 사용자의 체크인 자격을 판단해서,\n * 인증 성공 또는 인증 에러를 발생시킬 것인지를 결정한다.\n * 1. 현재 subdomain 이 결정되지 않은 경우.\n * - checkin로 이동한다.\n * 2. 현재 도메인에 체크인할 자격이 있는지 판단한다 (`canUserCheckinDomain`).\n * 3. superUser 판단.\n *\n * 매 요청 통과하는 경로이므로 체크인 가능 도메인 \"목록\" 을 계산하지 않고 현재 도메인\n * 하나만 판정한다.\n */\n\nexport async function domainAuthenticateMiddleware(context: any, next: any) {\n const { domain, user } = context.state\n\n const subdomain: string = domain?.subdomain\n\n // 1. 현재 subdomain 이 결정되지 않은 경우.\n // - checkin로 이동한다.\n if (!subdomain) {\n throw new AuthError({\n errorCode: AuthError.ERROR_CODES.SUBDOMAIN_NOTFOUND\n })\n }\n\n // 2. 현재 subdomain 이 결정된 경우.\n if ((await canUserCheckinDomain(user, domain)) || (await process.superUserGranted(domain, user))) {\n return await next()\n }\n\n throw new AuthError({\n errorCode: AuthError.ERROR_CODES.SUBDOMAIN_NOTFOUND\n })\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"graphql-authenticate-middleware.js","sourceRoot":"","sources":["../../server/middlewares/graphql-authenticate-middleware.ts"],"names":[],"mappings":";;AAGA,sEASC;AAZD,2FAAkF;AAClF,qFAA4E;AAErE,KAAK,UAAU,6BAA6B,CAAC,OAAO,EAAE,IAAI;IAC/D,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAA;IAEhC,IAAI,MAAM,IAAI,MAAM,IAAI,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QACpD,MAAM,IAAA,0DAAyB,EAAC,OAAO,EAAE,GAAG,EAAE,GAAE,CAAC,CAAC,CAAA;QAClD,MAAM,IAAA,gEAA4B,EAAC,OAAO,EAAE,GAAG,EAAE,GAAE,CAAC,CAAC,CAAA;IACvD,CAAC;IAED,MAAM,IAAI,EAAE,CAAA;AACd,CAAC","sourcesContent":["import { domainAuthenticateMiddleware } from './domain-authenticate-middleware.js'\nimport { jwtAuthenticateMiddleware } from './jwt-authenticate-middleware.js'\n\nexport async function graphqlAuthenticateMiddleware(context, next) {\n const { method, path } = context\n\n if (method == 'POST' && path.startsWith('/graphql')) {\n await jwtAuthenticateMiddleware(context, () => {})\n await domainAuthenticateMiddleware(context, () => {})\n }\n\n await next()\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/middlewares/index.ts"],"names":[],"mappings":";;AAmBA,0CAgDC;;AAlED,sEAAiC;AACjC,wEAAmC;AAEnC,6CAA4C;AAC5C,0DAA+C;AAE/C,qFAA4E;AAC5E,2FAAkF;AAClF,6FAAoF;AACpF,qFAA4E;AAC5E,+EAAsE;AACtE,2EAAkE;AAElE,qDAA8C;AAC9C,wDAAiD;AAEjD,MAAM,uBAAuB,GAAG,YAAM,CAAC,GAAG,CAAC,yBAAyB,EAAE,KAAK,CAAC,CAAA;AAE5E,SAAgB,eAAe,CAAC,GAAQ;IACtC,4GAA4G;IAC5G,GAAG,CAAC,GAAG,CAAC,gDAAoB,CAAC,CAAA;IAE7B,IAAI,uBAAuB,EAAE,CAAC;QAC5B,GAAG,CAAC,GAAG,CAAC,oDAAsB,CAAC,CAAA;QAC/B,OAAM;IACR,CAAC;IAED,yDAAyD;IACzD,GAAG,CAAC,IAAI,GAAG,CAAC,sBAAM,CAAC,CAAA;IACnB,GAAG,CAAC,GAAG,CACL,IAAA,qBAAO,EACL;QACE,GAAG,EAAE,WAAW;QAChB,MAAM,EAAE,oBAAO;QACf,SAAS,EAAE,IAAI;QACf,QAAQ,EAAE,IAAI;QACd,MAAM,EAAE,IAAI;QACZ,OAAO,EAAE,KAAK;QACd,KAAK,EAAE,KAAK;KACb,EACD,GAAG,CACJ,CACF,CAAA;IAED,sBAAQ,CAAC,aAAa,CAAC,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE;QACvC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;IACrB,CAAC,CAAC,CAAA;IAEF,sBAAQ,CAAC,eAAe,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE;QAC/C,IAAI,CAAC,IAAI,EAAE,MAAM,cAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAA;IAC3C,CAAC,CAAC,CAAA;IAEF,yBAAyB;IACzB,GAAG,CAAC,GAAG,CAAC,sBAAQ,CAAC,UAAU,EAAE,CAAC,CAAA;IAE9B,kDAAkD;IAClD,GAAG,CAAC,GAAG,CAAC,sBAAQ,CAAC,OAAO,EAAE,CAAC,CAAA;IAE3B,mCAAmC;IACnC,GAAG,CAAC,GAAG,CAAC,0DAAyB,CAAC,CAAA;IAElC;;;OAGG;IACH,GAAG,CAAC,GAAG,CAAC,kEAA6B,CAAC,CAAA;AACxC,CAAC;AAED,OAAO,CAAC,EAAE,CAAC,+BAAsC,EAAE,CAAC,GAAG,EAAE,sBAAsB,EAAE,EAAE;IACjF,sBAAsB,CAAC,IAAI,CAAC,0DAAyB,EAAE,gEAA4B,CAAC,CAAA;AACtF,CAAC,CAAC,CAAA;AAEF,2EAAgD;AAChD,8EAAmD;AACnD,iEAAsC;AACtC,2EAAgD","sourcesContent":["import Koa from 'koa'\nimport session from 'koa-session'\nimport passport from 'koa-passport'\n\nimport { config } from '@things-factory/env'\nimport { SECRET } from '../utils/get-secret.js'\n\nimport { authenticate401Middleware } from './authenticate-401-middleware.js'\nimport { domainAuthenticateMiddleware } from './domain-authenticate-middleware.js'\nimport { graphqlAuthenticateMiddleware } from './graphql-authenticate-middleware.js'\nimport { jwtAuthenticateMiddleware } from './jwt-authenticate-middleware.js'\nimport { bypassSigninMiddleware } from './bypass-signin-middleware.js'\nimport { auditScopeMiddleware } from './audit-scope-middleware.js'\n\nimport { User } from '../service/user/user.js'\nimport { MAX_AGE } from '../constants/max-age.js'\n\nconst bypassUserSigninProcess = config.get('bypassUserSigninProcess', false)\n\nexport function initMiddlewares(app: Koa) {\n /* Before everything else here, so sign-in and the GraphQL authentication run inside the request's audit. */\n app.use(auditScopeMiddleware)\n\n if (bypassUserSigninProcess) {\n app.use(bypassSigninMiddleware)\n return\n }\n\n /* oauth2orize-koa 에서 oauth 트랜잭션 관리를 위해서 session을 사용함. */\n app.keys = [SECRET]\n app.use(\n session(\n {\n key: 'tfsession',\n maxAge: MAX_AGE,\n overwrite: true,\n httpOnly: true,\n signed: true,\n rolling: false,\n renew: false\n },\n app\n )\n )\n\n passport.serializeUser((profile, done) => {\n done(null, profile)\n })\n\n passport.deserializeUser(async (profile, done) => {\n done(null, await User.checkAuth(profile))\n })\n\n /* passport initialize */\n app.use(passport.initialize())\n\n /* passport use session - for oauth transaction */\n app.use(passport.session())\n\n /* authentication error handling */\n app.use(authenticate401Middleware)\n\n /*\n * post:graphql 에 대해서는 graphqlAuthenticationMiddleware를 적용한다.\n * graphql app을 router에 적용하지 못하기 때문임.\n */\n app.use(graphqlAuthenticateMiddleware)\n}\n\nprocess.on('bootstrap-module-subscription' as any, (app, subscriptionMiddleware) => {\n subscriptionMiddleware.push(jwtAuthenticateMiddleware, domainAuthenticateMiddleware)\n})\n\nexport * from './jwt-authenticate-middleware.js'\nexport * from './domain-authenticate-middleware.js'\nexport * from './signin-middleware.js'\nexport * from './authenticate-401-middleware.js'\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"jwt-authenticate-middleware.js","sourceRoot":"","sources":["../../server/middlewares/jwt-authenticate-middleware.ts"],"names":[],"mappings":";;AAuCA,8DA+BC;;AAtED,wEAAmC;AACnC,+CAAkE;AAElE,6CAA4C;AAE5C,qDAA8C;AAC9C,4EAAoH;AACpH,0DAA+C;AAE/C,MAAM,mBAAmB,GAAG,YAAM,CAAC,GAAG,CAAC,sBAAsB,EAAE,OAAO,CAAC,CAAA;AAEvE,sBAAQ,CAAC,GAAG,CACV,IAAI,uBAAW,CACb;IACE,WAAW,EAAE,sBAAM;IACnB,iBAAiB,EAAE,IAAI;IACvB,cAAc,EAAE,yBAAU,CAAC,cAAc,CAAC;QACxC,yBAAU,CAAC,2BAA2B,EAAE;QACxC,yBAAU,CAAC,UAAU,CAAC,eAAe,CAAC;QACtC,yBAAU,CAAC,UAAU,CAAC,gBAAgB,CAAC;QACvC,yBAAU,CAAC,qBAAqB,CAAC,cAAc,CAAC;QAChD,yBAAU,CAAC,aAAa,CAAC,cAAc,CAAC;QACxC,GAAG,CAAC,EAAE;YACJ,IAAI,KAAK,GAAG,IAAI,CAAA;YAChB,KAAK,GAAG,IAAA,6CAAoB,EAAC,GAAG,EAAE,GAAG,CAAC,CAAA;YACtC,OAAO,KAAK,CAAA;QACd,CAAC;KACF,CAAC;CACH,EACD,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE;IAC/B,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;IAC5B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,IAAI,CAAC,KAAK,CAAC,CAAA;IACpB,CAAC;AACH,CAAC,CACF,CACF,CAAA;AAEM,KAAK,UAAU,yBAAyB,CAAC,OAAO,EAAE,IAAI;IAC3D,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAA;IACxB,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;IAC9B,IAAI,IAAI,EAAE,CAAC;QACT,OAAO,MAAM,IAAI,EAAE,CAAA;IACrB,CAAC;IAED,OAAO,MAAM,sBAAQ,CAAC,YAAY,CAAC,KAAK,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE;QACzF,IAAI,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;YACpB,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,GAAG,GAAG,IAAI,IAAI,CAAC,CAAA;YAE7C,IAAA,+CAAsB,EAAC,OAAO,CAAC,CAAA;YAE/B,OAAO,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,OAAO,CAAC,CAAA;QAC/B,CAAC;aAAM,CAAC;YACN,yGAAyG;YACzG,MAAM,UAAU,GAAG,MAAM,cAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAA;YAEhD,OAAO,CAAC,KAAK,CAAC,IAAI,GAAG,UAAU,CAAA;YAC/B,OAAO,CAAC,KAAK,CAAC,YAAY,GAAG,OAAO,CAAA;YAEpC,IAAI,mBAAmB,IAAI,SAAS,EAAE,CAAC;gBACrC,6FAA6F;gBAE7F,MAAM,KAAK,GAAG,MAAM,UAAU,CAAC,IAAI,EAAE,CAAA;gBACrC,IAAA,6CAAoB,EAAC,OAAO,EAAE,KAAK,CAAC,CAAA;YACtC,CAAC;YAED,MAAM,IAAI,EAAE,CAAA;QACd,CAAC;IACH,CAAC,CAAC,CAAC,OAAO,EAAE,IAAI,CAAC,CAAA;AACnB,CAAC","sourcesContent":["import passport from 'koa-passport'\nimport { ExtractJwt, Strategy as JWTstrategy } from 'passport-jwt'\n\nimport { config } from '@things-factory/env'\n\nimport { User } from '../service/user/user.js'\nimport { clearAccessTokenCookie, getAccessTokenCookie, setAccessTokenCookie } from '../utils/access-token-cookie.js'\nimport { SECRET } from '../utils/get-secret.js'\n\nconst sessionExpiryPolicy = config.get('session/expiryPolicy', 'fixed')\n\npassport.use(\n new JWTstrategy(\n {\n secretOrKey: SECRET,\n passReqToCallback: true,\n jwtFromRequest: ExtractJwt.fromExtractors([\n ExtractJwt.fromAuthHeaderAsBearerToken(),\n ExtractJwt.fromHeader('authorization'),\n ExtractJwt.fromHeader('x-access-token'),\n ExtractJwt.fromUrlQueryParameter('access_token'),\n ExtractJwt.fromBodyField('access_token'),\n req => {\n var token = null\n token = getAccessTokenCookie(req?.ctx)\n return token\n }\n ])\n },\n async (request, decoded, done) => {\n try {\n return done(null, decoded)\n } catch (error) {\n return done(error)\n }\n }\n )\n)\n\nexport async function jwtAuthenticateMiddleware(context, next) {\n const { path } = context\n const { user } = context.state\n if (user) {\n return await next()\n }\n\n return await passport.authenticate('jwt', { session: false }, async (err, decoded, info) => {\n if (err || !decoded) {\n const e = (context.state.error = err || info)\n\n clearAccessTokenCookie(context)\n\n context.throw(401, e.message)\n } else {\n /* A waiting account (ADR-0161) is refused in `checkAuth`, with every other status that opens nothing. */\n const userEntity = await User.checkAuth(decoded)\n\n context.state.user = userEntity\n context.state.decodedToken = decoded\n\n if (sessionExpiryPolicy == 'rolling') {\n /* To renew the expiry time on each request, a token is issued and the session is updated. */\n\n const token = await userEntity.sign()\n setAccessTokenCookie(context, token)\n }\n\n await next()\n }\n })(context, next)\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"signin-middleware.js","sourceRoot":"","sources":["../../server/middlewares/signin-middleware.ts"],"names":[],"mappings":";;AA0CA,4CAaC;;AAvDD,wEAAmC;AACnC,mDAA0D;AAE1D,wDAAiD;AAEjD,sBAAQ,CAAC,GAAG,CACV,QAAQ,EACR,IAAI,yBAAa,CACf;IACE,aAAa,EAAE,UAAU;IACzB,aAAa,EAAE,UAAU;IACzB,iBAAiB,EAAE,IAAI;CACxB,EACD,KAAK,EAAE,GAAG,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE;IACtC,IAAI,CAAC;QACH,MAAM,EACJ,IAAI,EAAE,QAAQ,EACd,KAAK,EACL,OAAO,EACR,GAAG,MAAM,IAAA,kBAAM,EAAC;YACf,QAAQ;YACR,QAAQ;SACT,CAAC,CAAA;QAEF,OAAO,IAAI,CACT,IAAI,EACJ;YACE,IAAI,EAAE,QAAQ;YACd,KAAK;YACL,OAAO;SACR,EACD;YACE,OAAO,EAAE,wBAAwB;SAClC,CACF,CAAA;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,IAAI,CAAC,KAAK,CAAC,CAAA;IACpB,CAAC;AACH,CAAC,CACF,CACF,CAAA;AAEM,KAAK,UAAU,gBAAgB,CAAC,OAAO,EAAE,IAAI;IAClD,OAAO,sBAAQ,CAAC,YAAY,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE;QACnF,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;YACjB,MAAM,GAAG,CAAA;QACX,CAAC;aAAM,CAAC;YACN,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,IAAI,CAAA;YAEtC,OAAO,CAAC,KAAK,CAAC,IAAI,GAAG,QAAQ,CAAA;YAC7B,OAAO,CAAC,KAAK,CAAC,KAAK,GAAG,KAAK,CAAA;YAE3B,MAAM,IAAI,EAAE,CAAA;QACd,CAAC;IACH,CAAC,CAAC,CAAC,OAAO,EAAE,IAAI,CAAC,CAAA;AACnB,CAAC","sourcesContent":["import passport from 'koa-passport'\nimport { Strategy as localStrategy } from 'passport-local'\n\nimport { signin } from '../controllers/signin.js'\n\npassport.use(\n 'signin',\n new localStrategy(\n {\n usernameField: 'username',\n passwordField: 'password',\n passReqToCallback: true\n },\n async (req, username, password, done) => {\n try {\n const {\n user: userInfo,\n token,\n domains\n } = await signin({\n username,\n password\n })\n\n return done(\n null,\n {\n user: userInfo,\n token,\n domains\n },\n {\n message: 'Logged in Successfully'\n }\n )\n } catch (error) {\n return done(error)\n }\n }\n )\n)\n\nexport async function signinMiddleware(context, next) {\n return passport.authenticate('signin', { session: false }, async (err, user, info) => {\n if (err || !user) {\n throw err\n } else {\n const { user: userInfo, token } = user\n\n context.state.user = userInfo\n context.state.token = token\n\n await next()\n }\n })(context, next)\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"verify-recaptcha-middleware.js","sourceRoot":"","sources":["../../server/middlewares/verify-recaptcha-middleware.ts"],"names":[],"mappings":"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","sourcesContent":["import { RecaptchaEnterpriseServiceClient } from '@google-cloud/recaptcha-enterprise'\nimport { config, logger } from '@things-factory/env'\n\nconst recaptchaConfig = config.get('recaptcha') || {}\nconst { serviceAccount, siteKey } = recaptchaConfig || {}\n\nvar client, projectPath\n\nif (serviceAccount) {\n try {\n logger.info('creating reCAPTCHA service client...')\n\n client = new RecaptchaEnterpriseServiceClient({\n projectId: serviceAccount.project_id,\n credentials: serviceAccount\n })\n\n projectPath = client.projectPath(serviceAccount.project_id)\n\n logger.info('reCAPTCHA service client created')\n } catch (err) {\n logger.error('incorrect reCAPTCHA configuration')\n logger.error(err)\n }\n}\n\n/**\n * Creates an assessment to analyze the risk of a UI action.\n *\n * projectID: The Google Cloud project ID.\n * token: The generated token obtained from the client.\n * recaptchaAction: The action name corresponding to the token.\n */\nasync function createReCaptchaAssessment({ token, recaptchaAction }: { token: string; recaptchaAction: string }) {\n // Create the assessment request.\n const request = {\n assessment: {\n event: {\n token: token,\n siteKey\n }\n },\n parent: projectPath\n }\n\n const [response] = await client.createAssessment(request)\n\n // Verify if the token is valid.\n if (!response.tokenProperties.valid) {\n console.log(`The CreateAssessment call failed because the token was: ${response.tokenProperties.invalidReason}`)\n return null\n }\n\n if (response.tokenProperties.action === recaptchaAction) {\n // Get the risk score and reasons.\n // For more details on interpreting the assessment, see:\n // https://cloud.google.com/recaptcha-enterprise/docs/interpret-assessment\n console.log(`The reCAPTCHA score is: ${response.riskAnalysis.score}`)\n response.riskAnalysis.reasons.forEach(reason => {\n console.log(reason)\n })\n\n return response.riskAnalysis.score\n } else {\n console.log('The action attribute in your reCAPTCHA tag does not match the action you are expecting to score')\n return null\n }\n}\n\nexport function verifyRecaptcaMiddleware({ action }: { action: string }) {\n return async (context, next) => {\n if (!client) {\n if (siteKey) {\n console.error(\n `The RecaptchaEnterpriseServiceClient creation failed. Please check the service account configuration.`\n )\n }\n\n await next()\n return\n }\n\n const { 'recaptcha-response': recaptchaToken } = context.request.body\n\n // Handle error if token does not exist\n if (!recaptchaToken) {\n context.status = 400\n context.body = { message: 'reCAPTCHA token is required' }\n return\n }\n\n // Google reCAPTCHA assessment request\n const score = await createReCaptchaAssessment({\n token: recaptchaToken,\n recaptchaAction: action\n })\n\n if (score === null || score < 0.5) {\n // Treat as spam if the score is low\n context.status = 400\n context.body = { message: 'Spam behavior detected. Please try again.' }\n return\n }\n\n // Continue processing the request if the score is valid\n console.log(`reCAPTCHA score: ${score}`)\n\n await next()\n }\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"1548206416130-SeedUser.js","sourceRoot":"","sources":["../../server/migrations/1548206416130-SeedUser.ts"],"names":[],"mappings":";;;AAAA,qCAAgE;AAEhE,6CAAoD;AACpD,iDAA6D;AAE7D,qDAA0D;AAE1D,MAAM,aAAa,GAAG,YAAM,CAAC,GAAG,CAAC,cAAc,EAAE;IAC/C,QAAQ,EAAE,OAAO;IACjB,IAAI,EAAE,OAAO;IACb,KAAK,EAAE,oBAAoB;IAC3B,QAAQ,EAAE,OAAO;CAClB,CAAC,CAAA;AAEF,MAAM,UAAU,GAAG;IACjB;QACE,GAAG,aAAa;QAChB,QAAQ,EAAE,MAAM;QAChB,MAAM,EAAE,oBAAU,CAAC,SAAS;KAC7B;CACF,CAAA;AACD,MAAa,sBAAsB;IAC1B,KAAK,CAAC,EAAE,CAAC,WAAwB;QACtC,MAAM,cAAc,GAAG,IAAA,qBAAa,EAAC,cAAI,CAAC,CAAA;QAC1C,MAAM,gBAAgB,GAAG,IAAA,qBAAa,EAAC,cAAM,CAAC,CAAA;QAE9C,MAAM,MAAM,GAAW,MAAM,gBAAgB,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAA;QAEpF,IAAI,CAAC;YACH,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,UAAU,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;gBAC3C,MAAM,IAAI,GAAG,UAAU,CAAC,CAAC,CAAC,CAAA;gBAC1B,MAAM,IAAI,GAAG,cAAI,CAAC,YAAY,EAAE,CAAA;gBAChC,MAAM,QAAQ,GAAG,cAAI,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAA;gBAEjD,MAAM,cAAc,CAAC,IAAI,CAAC;oBACxB,GAAG,IAAI;oBACP,IAAI;oBACJ,QAAQ;oBACR,OAAO,EAAE,CAAC,MAAM,CAAC;iBAClB,CAAC,CAAA;YACJ,CAAC;QACH,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,YAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;QACjB,CAAC;QAED,MAAM,KAAK,GAAG,MAAM,cAAc,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,IAAA,eAAK,EAAC,aAAa,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC,CAAA;QAC5F,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC,EAAE,CAAA;QAEvB,MAAM,gBAAgB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;IACrC,CAAC;IAEM,KAAK,CAAC,IAAI,CAAC,WAAwB;QACxC,MAAM,UAAU,GAAG,IAAA,qBAAa,EAAC,cAAI,CAAC,CAAA;QAEtC,UAAU,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,KAAK,EAAC,IAAI,EAAC,EAAE;YACxC,IAAI,MAAM,GAAG,MAAM,UAAU,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,IAAA,eAAK,EAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;YACrE,MAAM,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;QACjC,CAAC,CAAC,CAAA;IACJ,CAAC;CACF;AAtCD,wDAsCC","sourcesContent":["import { ILike, MigrationInterface, QueryRunner } from 'typeorm'\n\nimport { config, logger } from '@things-factory/env'\nimport { Domain, getRepository } from '@things-factory/shell'\n\nimport { User, UserStatus } from '../service/user/user.js'\n\nconst ADMIN_ACCOUNT = config.get('adminAccount', {\n username: 'admin',\n name: 'Admin',\n email: 'admin@hatiolab.com',\n password: 'admin'\n})\n\nconst SEED_USERS = [\n {\n ...ADMIN_ACCOUNT,\n userType: 'user',\n status: UserStatus.ACTIVATED\n }\n]\nexport class SeedUsers1548206416130 implements MigrationInterface {\n public async up(queryRunner: QueryRunner): Promise<any> {\n const userRepository = getRepository(User)\n const domainRepository = getRepository(Domain)\n\n const domain: Domain = await domainRepository.findOne({ where: { name: 'SYSTEM' } })\n\n try {\n for (let i = 0; i < SEED_USERS.length; i++) {\n const user = SEED_USERS[i]\n const salt = User.generateSalt()\n const password = User.encode(user.password, salt)\n\n await userRepository.save({\n ...user,\n salt,\n password,\n domains: [domain]\n })\n }\n } catch (e) {\n logger.error(e)\n }\n\n const admin = await userRepository.findOne({ where: { email: ILike(ADMIN_ACCOUNT.email) } })\n domain.owner = admin.id\n\n await domainRepository.save(domain)\n }\n\n public async down(queryRunner: QueryRunner): Promise<any> {\n const repository = getRepository(User)\n\n SEED_USERS.reverse().forEach(async user => {\n let record = await repository.findOneBy({ email: ILike(user.email) })\n await repository.remove(record)\n })\n }\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"1566805283882-SeedPrivilege.js","sourceRoot":"","sources":["../../server/migrations/1566805283882-SeedPrivilege.ts"],"names":[],"mappings":";;;AAEA,6CAA4C;AAC5C,iDAAqD;AAErD,oEAA6D;AAE7D,MAAa,0BAA0B;IAC9B,KAAK,CAAC,EAAE,CAAC,WAAwB;QACtC,MAAM,mBAAmB,GAAG,IAAA,qBAAa,EAAC,wBAAS,CAAC,CAAA;QAEpD,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,6CAA6C,CAAC,CAAA;QACzE,MAAM,MAAM,EAAE,CAAA;QACd,MAAM,UAAU,GAAG,OAAO,CAAC,YAAY,CAAC,CAAA;QAExC,IAAI,CAAC;YACH,KAAK,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,UAA8B,CAAC,EAAE,CAAC;gBAC7E,IAAI,CAAC,IAAI,CAAC,MAAM,mBAAmB,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC;oBAC1E,MAAM,mBAAmB,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAA;gBACpD,CAAC;YACH,CAAC;QACH,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,YAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;QACjB,CAAC;IACH,CAAC;IAEM,KAAK,CAAC,IAAI,CAAC,WAAwB,IAAiB,CAAC;CAC7D;AApBD,gEAoBC","sourcesContent":["import { MigrationInterface, QueryRunner } from 'typeorm'\n\nimport { logger } from '@things-factory/env'\nimport { getRepository } from '@things-factory/shell'\n\nimport { Privilege } from '../service/privilege/privilege.js'\n\nexport class SeedPrivilege1566805283882 implements MigrationInterface {\n public async up(queryRunner: QueryRunner): Promise<any> {\n const privilegeRepository = getRepository(Privilege)\n\n const { schema } = require('@things-factory/shell/dist-server/schema.js')\n await schema()\n const privileges = process['PRIVILEGES']\n\n try {\n for (const [category, name] of Object.values(privileges as [string, string])) {\n if (0 == (await privilegeRepository.count({ where: { category, name } }))) {\n await privilegeRepository.save({ category, name })\n }\n }\n } catch (e) {\n logger.error(e)\n }\n }\n\n public async down(queryRunner: QueryRunner): Promise<any> {}\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"1745000000000-MigrateDomainOwnersToTable.js","sourceRoot":"","sources":["../../server/migrations/1745000000000-MigrateDomainOwnersToTable.ts"],"names":[],"mappings":";;;AAEA,iDAA6D;AAE7D,6EAAqE;AAErE;;;;;;;;GAQG;AACH,MAAa,uCAAuC;IAC3C,KAAK,CAAC,EAAE,CAAC,YAAyB;QACvC,MAAM,gBAAgB,GAAG,IAAA,qBAAa,EAAC,cAAM,CAAC,CAAA;QAC9C,MAAM,eAAe,GAAG,IAAA,qBAAa,EAAC,6BAAW,CAAC,CAAA;QAElD,MAAM,OAAO,GAAG,MAAM,gBAAgB;aACnC,kBAAkB,CAAC,GAAG,CAAC;aACvB,KAAK,CAAC,qBAAqB,CAAC;aAC5B,OAAO,EAAE,CAAA;QAEZ,IAAI,QAAQ,GAAG,CAAC,CAAA;QAChB,IAAI,OAAO,GAAG,CAAC,CAAA;QAEf,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;YAC7B,IAAI,CAAC,MAAM,CAAC,KAAK;gBAAE,SAAQ;YAE3B,MAAM,QAAQ,GAAG,MAAM,eAAe;iBACnC,kBAAkB,CAAC,IAAI,CAAC;iBACxB,KAAK,CAAC,uCAAuC,EAAE,EAAE,CAAC,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC;iBACjF,MAAM,EAAE,CAAA;YAEX,IAAI,QAAQ,EAAE,CAAC;gBACb,OAAO,EAAE,CAAA;gBACT,SAAQ;YACV,CAAC;YAED,MAAM,KAAK,GAAG,eAAe,CAAC,MAAM,CAAC;gBACnC,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAY;gBACnC,IAAI,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,KAAK,EAAS;gBACjC,MAAM,EAAE,yCAAyC;aAClD,CAAC,CAAA;YACF,MAAM,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;YACjC,QAAQ,EAAE,CAAA;QACZ,CAAC;QAED,sCAAsC;QACtC,OAAO,CAAC,GAAG,CACT,yCAAyC,QAAQ,+BAA+B,OAAO,kBAAkB,CAC1G,CAAA;IACH,CAAC;IAEM,KAAK,CAAC,IAAI,CAAC,YAAyB;QACzC,iEAAiE;QACjE,oCAAoC;QACpC,MAAM,eAAe,GAAG,IAAA,qBAAa,EAAC,6BAAW,CAAC,CAAA;QAClD,MAAM,eAAe;aAClB,kBAAkB,EAAE;aACpB,MAAM,EAAE;aACR,IAAI,CAAC,6BAAW,CAAC;aACjB,KAAK,CAAC,kBAAkB,EAAE,EAAE,MAAM,EAAE,yCAAyC,EAAE,CAAC;aAChF,OAAO,EAAE,CAAA;IACd,CAAC;CACF;AApDD,0FAoDC","sourcesContent":["import { MigrationInterface, QueryRunner } from 'typeorm'\n\nimport { Domain, getRepository } from '@things-factory/shell'\n\nimport { DomainOwner } from '../service/domain-owner/domain-owner.js'\n\n/**\n * 기존 `Domain.owner` 단일 소유자 데이터를 신규 `DomainOwner` 테이블로 이관한다.\n *\n * `Domain.owner` 컬럼은 제거하지 않는다 — \"대표 owner 표시용 캐시\"로 유지되며\n * 하위 호환 API에서 계속 사용된다. 권위 있는 소유권 체크는 `DomainOwner` 테이블\n * 또는 `Domain.owner` 캐시 중 하나라도 일치하면 통과 (domain-authenticate-middleware).\n *\n * 이 마이그레이션은 idempotent — 이미 존재하는 (domain, user) 쌍은 skip.\n */\nexport class MigrateDomainOwnersToTable1745000000000 implements MigrationInterface {\n public async up(_queryRunner: QueryRunner): Promise<void> {\n const domainRepository = getRepository(Domain)\n const ownerRepository = getRepository(DomainOwner)\n\n const domains = await domainRepository\n .createQueryBuilder('d')\n .where('d.owner IS NOT NULL')\n .getMany()\n\n let migrated = 0\n let skipped = 0\n\n for (const domain of domains) {\n if (!domain.owner) continue\n\n const existing = await ownerRepository\n .createQueryBuilder('ow')\n .where('ow.domain_id = :d AND ow.user_id = :u', { d: domain.id, u: domain.owner })\n .getOne()\n\n if (existing) {\n skipped++\n continue\n }\n\n const entry = ownerRepository.create({\n domain: { id: domain.id } as Domain,\n user: { id: domain.owner } as any,\n reason: 'migrated from legacy single-owner field'\n })\n await ownerRepository.save(entry)\n migrated++\n }\n\n // eslint-disable-next-line no-console\n console.log(\n `[MigrateDomainOwnersToTable] migrated ${migrated} ownership entries, skipped ${skipped} (already exist)`\n )\n }\n\n public async down(_queryRunner: QueryRunner): Promise<void> {\n // rollback: domain_owners 중 'migrated from legacy...' reason만 제거\n // Domain.owner 필드는 그대로이므로 데이터 유실 없음\n const ownerRepository = getRepository(DomainOwner)\n await ownerRepository\n .createQueryBuilder()\n .delete()\n .from(DomainOwner)\n .where('reason = :reason', { reason: 'migrated from legacy single-owner field' })\n .execute()\n }\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/migrations/index.ts"],"names":[],"mappings":";;;AAAA,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;AAC5B,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;AAEjB,QAAA,UAAU,GAAG,EAAE,CAAA;AAE1B,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,UAAU,IAAI;IAC1E,IAAI,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;QAAE,OAAM;IAC3C,kBAAU,GAAG,kBAAU,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAA;AAClF,CAAC,CAAC,CAAA","sourcesContent":["const glob = require('glob')\nconst path = require('path')\n\nexport var migrations = []\n\nglob.sync(path.resolve(__dirname, '.', '**', '*.js')).forEach(function (file) {\n if (file.indexOf('index.js') !== -1) return\n migrations = migrations.concat(Object.values(require(path.resolve(file))) || [])\n})\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"role-tasks.js","sourceRoot":"","sources":["../server/role-tasks.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;;AAIU,QAAA,UAAU,GAAe;IACpC,EAAE,EAAE,EAAE,2BAA2B,EAAE,MAAM,EAAE,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,OAAO,EAAE,UAAU,CAAC,EAAE,CAAC,EAAE;IAChG,EAAE,EAAE,EAAE,6BAA6B,EAAE,MAAM,EAAE,CAAC,EAAE,QAAQ,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE;IAC3F,EAAE,EAAE,EAAE,oBAAoB,EAAE,MAAM,EAAE,CAAC,EAAE,QAAQ,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,OAAO,EAAE,UAAU,CAAC,EAAE,CAAC,EAAE;IAC7F,EAAE,EAAE,EAAE,oBAAoB,EAAE,MAAM,EAAE,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,OAAO,EAAE,UAAU,CAAC,EAAE,CAAC,EAAE;IAC5F,2DAA2D;IAC3D,EAAE,EAAE,EAAE,yBAAyB,EAAE,MAAM,EAAE,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE;CACpF,CAAA","sourcesContent":["/*\n * The work this module names, for the seats applications build (ADR-0139 decision 11).\n *\n * A task holds whole privileges: each opens everything its doors open, and the words under `task.<id>` in this\n * module's translations say what that is. Seats are put together by applications (`RoleTemplate.tasks`).\n *\n * Data only - `server/index.ts` registers it - so an application's tests read it without starting the framework.\n */\n\nimport type { RoleTask } from './service/role-template/role-task.js'\n\nexport const ROLE_TASKS: RoleTask[] = [\n { id: 'auth-base.users-and-roles', grants: [{ category: 'user', axes: ['query', 'mutation'] }] },\n { id: 'auth-base.privilege-reading', grants: [{ category: 'privilege', axes: ['query'] }] },\n { id: 'auth-base.security', grants: [{ category: 'security', axes: ['query', 'mutation'] }] },\n { id: 'auth-base.env-vars', grants: [{ category: 'env-var', axes: ['query', 'mutation'] }] },\n /* Everybody's audit record in the domain (ADR-0141 §5). */\n { id: 'auth-base.audit-reading', grants: [{ category: 'audit', axes: ['query'] }] }\n]\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"auth-checkin-router.js","sourceRoot":"","sources":["../../server/router/auth-checkin-router.ts"],"names":[],"mappings":"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","sourcesContent":["import Router from 'koa-router'\n\nimport { config } from '@things-factory/env'\nimport { Domain, findSubdomainFromPath, getRedirectSubdomainPath } from '@things-factory/shell'\n\nimport { recordAuditAction } from '../service/audit/audit-recorder.js'\nimport { User } from '../service/user/user.js'\nimport { accepts } from '../utils/accepts.js'\nimport { clearAccessTokenCookie } from '../utils/access-token-cookie.js'\nimport { getUserDomains } from '../utils/get-user-domains.js'\nimport { sameOriginRedirectPath } from '../utils/same-origin-redirect.js'\n\nconst domainTypes = config.get('domainTypes')\n\nexport const authCheckinRouter = new Router()\n\nauthCheckinRouter.get('/auth/checkin/:subdomain?', async (context, next) => {\n const { request, t } = context\n const header = request.header\n const { user } = context.state\n let { subdomain } = context.params\n\n let domains: Partial<Domain>[] = await getUserDomains(user)\n\n if (!accepts(header.accept, ['text/html', '*/*'])) {\n // When request expects non html response\n try {\n if (!subdomain) throw new Error(t('error.domain not specified', { subdomain })) // When params doesn't have subdomain\n const checkInDomain: Partial<Domain> | undefined = domains.find(d => d.subdomain === subdomain) // When no matched domain with subdomain\n if (!checkInDomain) throw new Error(t('error.domain not specified', { subdomain }))\n\n await checkIn(checkInDomain, null, context)\n context.body = true\n } catch (e) {\n clearAccessTokenCookie(context)\n throw e\n }\n } else {\n // When request expects html response\n const redirectTo = sameOriginRedirectPath(context.query.redirect_to, context)\n\n try {\n let message: string\n\n if (!subdomain) {\n /* try to find domain from redirectTo path */\n subdomain = findSubdomainFromPath(context, redirectTo)\n }\n\n let checkInDomain: Partial<Domain>\n if (subdomain) {\n checkInDomain = domains.find(d => d.subdomain == subdomain)\n if (!checkInDomain) message = t('error.domain not allowed', { subdomain })\n } else if (domains.length === 1) {\n checkInDomain = domains[0]\n }\n\n if (checkInDomain) {\n return await checkIn(checkInDomain, redirectTo, context)\n }\n\n await context.render('auth-page', {\n pageElement: 'auth-checkin',\n elementScript: '/auth/checkin.js',\n data: {\n user: {\n username: user.username,\n email: user.email,\n locale: user.locale,\n name: user.name,\n userType: user.userType\n },\n domains,\n domainTypes,\n redirectTo,\n message\n }\n })\n } catch (e) {\n clearAccessTokenCookie(context)\n context.redirect(\n `/auth/signin?username=${encodeURIComponent(user.username)}&redirect_to=${encodeURIComponent(redirectTo)}`\n )\n }\n }\n})\n\n// authCheckinRouter.get('/auth/domains', async context => {\n// const { user } = context.state\n// var domains = await getUserDomains(user)\n// if (domainTypes) {\n// domains = domains.filter(d => d.extType == domainTypes)\n// }\n\n// context.body = domains\n// })\n\nasync function checkIn(\n checkInDomain: Partial<Domain>,\n redirectTo: string | null,\n context: ResolverContext\n): Promise<void> {\n const { user }: { user: User } = context.state\n\n /* The check-in is the audit's (ADR-0141 §8): its address and browser are the request line's. */\n await recordAuditAction({ kind: 'auth.checkin', outcome: 'ok' }, { actorId: user.id, domainId: checkInDomain.id })\n\n if (redirectTo) {\n return context.redirect(getRedirectSubdomainPath(context, checkInDomain, redirectTo))\n }\n}\n"]}