@shipfox/api-auth 2.0.0

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 (319) hide show
  1. package/.turbo/turbo-build.log +2 -0
  2. package/.turbo/turbo-type$colon$emit.log +1 -0
  3. package/.turbo/turbo-type.log +1 -0
  4. package/CHANGELOG.md +116 -0
  5. package/LICENSE +21 -0
  6. package/README.md +336 -0
  7. package/dist/config.d.ts +22 -0
  8. package/dist/config.d.ts.map +1 -0
  9. package/dist/config.js +95 -0
  10. package/dist/config.js.map +1 -0
  11. package/dist/core/auth.d.ts +108 -0
  12. package/dist/core/auth.d.ts.map +1 -0
  13. package/dist/core/auth.js +476 -0
  14. package/dist/core/auth.js.map +1 -0
  15. package/dist/core/entities/email-verification.d.ts +9 -0
  16. package/dist/core/entities/email-verification.d.ts.map +1 -0
  17. package/dist/core/entities/email-verification.js +3 -0
  18. package/dist/core/entities/email-verification.js.map +1 -0
  19. package/dist/core/entities/password-reset.d.ts +9 -0
  20. package/dist/core/entities/password-reset.d.ts.map +1 -0
  21. package/dist/core/entities/password-reset.js +3 -0
  22. package/dist/core/entities/password-reset.js.map +1 -0
  23. package/dist/core/entities/refresh-token.d.ts +12 -0
  24. package/dist/core/entities/refresh-token.d.ts.map +1 -0
  25. package/dist/core/entities/refresh-token.js +3 -0
  26. package/dist/core/entities/refresh-token.js.map +1 -0
  27. package/dist/core/entities/user.d.ts +12 -0
  28. package/dist/core/entities/user.d.ts.map +1 -0
  29. package/dist/core/entities/user.js +3 -0
  30. package/dist/core/entities/user.js.map +1 -0
  31. package/dist/core/errors.d.ts +54 -0
  32. package/dist/core/errors.d.ts.map +1 -0
  33. package/dist/core/errors.js +106 -0
  34. package/dist/core/errors.js.map +1 -0
  35. package/dist/core/job-lease-token.d.ts +12 -0
  36. package/dist/core/job-lease-token.d.ts.map +1 -0
  37. package/dist/core/job-lease-token.js +58 -0
  38. package/dist/core/job-lease-token.js.map +1 -0
  39. package/dist/core/jwt.d.ts +37 -0
  40. package/dist/core/jwt.d.ts.map +1 -0
  41. package/dist/core/jwt.js +46 -0
  42. package/dist/core/jwt.js.map +1 -0
  43. package/dist/core/password.d.ts +10 -0
  44. package/dist/core/password.d.ts.map +1 -0
  45. package/dist/core/password.js +13 -0
  46. package/dist/core/password.js.map +1 -0
  47. package/dist/core/rate-limit.d.ts +34 -0
  48. package/dist/core/rate-limit.d.ts.map +1 -0
  49. package/dist/core/rate-limit.js +76 -0
  50. package/dist/core/rate-limit.js.map +1 -0
  51. package/dist/core/runner-session-token.d.ts +6 -0
  52. package/dist/core/runner-session-token.d.ts.map +1 -0
  53. package/dist/core/runner-session-token.js +37 -0
  54. package/dist/core/runner-session-token.js.map +1 -0
  55. package/dist/db/db.d.ts +1723 -0
  56. package/dist/db/db.d.ts.map +1 -0
  57. package/dist/db/db.js +28 -0
  58. package/dist/db/db.js.map +1 -0
  59. package/dist/db/email-verifications.d.ts +39 -0
  60. package/dist/db/email-verifications.d.ts.map +1 -0
  61. package/dist/db/email-verifications.js +95 -0
  62. package/dist/db/email-verifications.js.map +1 -0
  63. package/dist/db/migrations.d.ts +2 -0
  64. package/dist/db/migrations.d.ts.map +1 -0
  65. package/dist/db/migrations.js +5 -0
  66. package/dist/db/migrations.js.map +1 -0
  67. package/dist/db/password-resets.d.ts +23 -0
  68. package/dist/db/password-resets.d.ts.map +1 -0
  69. package/dist/db/password-resets.js +37 -0
  70. package/dist/db/password-resets.js.map +1 -0
  71. package/dist/db/rate-limits.d.ts +18 -0
  72. package/dist/db/rate-limits.d.ts.map +1 -0
  73. package/dist/db/rate-limits.js +44 -0
  74. package/dist/db/rate-limits.js.map +1 -0
  75. package/dist/db/refresh-tokens.d.ts +44 -0
  76. package/dist/db/refresh-tokens.d.ts.map +1 -0
  77. package/dist/db/refresh-tokens.js +71 -0
  78. package/dist/db/refresh-tokens.js.map +1 -0
  79. package/dist/db/schema/common.d.ts +2 -0
  80. package/dist/db/schema/common.d.ts.map +1 -0
  81. package/dist/db/schema/common.js +4 -0
  82. package/dist/db/schema/common.js.map +1 -0
  83. package/dist/db/schema/email-verifications.d.ts +114 -0
  84. package/dist/db/schema/email-verifications.d.ts.map +1 -0
  85. package/dist/db/schema/email-verifications.js +35 -0
  86. package/dist/db/schema/email-verifications.js.map +1 -0
  87. package/dist/db/schema/outbox.d.ts +195 -0
  88. package/dist/db/schema/outbox.d.ts.map +1 -0
  89. package/dist/db/schema/outbox.js +5 -0
  90. package/dist/db/schema/outbox.js.map +1 -0
  91. package/dist/db/schema/password-resets.d.ts +114 -0
  92. package/dist/db/schema/password-resets.d.ts.map +1 -0
  93. package/dist/db/schema/password-resets.js +35 -0
  94. package/dist/db/schema/password-resets.js.map +1 -0
  95. package/dist/db/schema/rate-limits.d.ts +145 -0
  96. package/dist/db/schema/rate-limits.d.ts.map +1 -0
  97. package/dist/db/schema/rate-limits.js +25 -0
  98. package/dist/db/schema/rate-limits.js.map +1 -0
  99. package/dist/db/schema/refresh-tokens.d.ts +165 -0
  100. package/dist/db/schema/refresh-tokens.d.ts.map +1 -0
  101. package/dist/db/schema/refresh-tokens.js +47 -0
  102. package/dist/db/schema/refresh-tokens.js.map +1 -0
  103. package/dist/db/schema/users.d.ts +149 -0
  104. package/dist/db/schema/users.d.ts.map +1 -0
  105. package/dist/db/schema/users.js +40 -0
  106. package/dist/db/schema/users.js.map +1 -0
  107. package/dist/db/users.d.ts +28 -0
  108. package/dist/db/users.d.ts.map +1 -0
  109. package/dist/db/users.js +83 -0
  110. package/dist/db/users.js.map +1 -0
  111. package/dist/index.d.ts +14 -0
  112. package/dist/index.d.ts.map +1 -0
  113. package/dist/index.js +52 -0
  114. package/dist/index.js.map +1 -0
  115. package/dist/metrics/index.d.ts +2 -0
  116. package/dist/metrics/index.d.ts.map +1 -0
  117. package/dist/metrics/index.js +3 -0
  118. package/dist/metrics/index.js.map +1 -0
  119. package/dist/metrics/instance.d.ts +16 -0
  120. package/dist/metrics/instance.d.ts.map +1 -0
  121. package/dist/metrics/instance.js +52 -0
  122. package/dist/metrics/instance.js.map +1 -0
  123. package/dist/presentation/auth/bearer-token-auth.d.ts +15 -0
  124. package/dist/presentation/auth/bearer-token-auth.d.ts.map +1 -0
  125. package/dist/presentation/auth/bearer-token-auth.js +31 -0
  126. package/dist/presentation/auth/bearer-token-auth.js.map +1 -0
  127. package/dist/presentation/auth/jwt-auth.d.ts +10 -0
  128. package/dist/presentation/auth/jwt-auth.d.ts.map +1 -0
  129. package/dist/presentation/auth/jwt-auth.js +31 -0
  130. package/dist/presentation/auth/jwt-auth.js.map +1 -0
  131. package/dist/presentation/auth/lease-token-auth.d.ts +15 -0
  132. package/dist/presentation/auth/lease-token-auth.d.ts.map +1 -0
  133. package/dist/presentation/auth/lease-token-auth.js +27 -0
  134. package/dist/presentation/auth/lease-token-auth.js.map +1 -0
  135. package/dist/presentation/auth/refresh-cookie.d.ts +7 -0
  136. package/dist/presentation/auth/refresh-cookie.d.ts.map +1 -0
  137. package/dist/presentation/auth/refresh-cookie.js +29 -0
  138. package/dist/presentation/auth/refresh-cookie.js.map +1 -0
  139. package/dist/presentation/auth/runner-session-auth.d.ts +3 -0
  140. package/dist/presentation/auth/runner-session-auth.d.ts.map +1 -0
  141. package/dist/presentation/auth/runner-session-auth.js +16 -0
  142. package/dist/presentation/auth/runner-session-auth.js.map +1 -0
  143. package/dist/presentation/dto/user.d.ts +8 -0
  144. package/dist/presentation/dto/user.d.ts.map +1 -0
  145. package/dist/presentation/dto/user.js +19 -0
  146. package/dist/presentation/dto/user.js.map +1 -0
  147. package/dist/presentation/e2eRoutes/create-session.d.ts +2 -0
  148. package/dist/presentation/e2eRoutes/create-session.d.ts.map +1 -0
  149. package/dist/presentation/e2eRoutes/create-session.js +49 -0
  150. package/dist/presentation/e2eRoutes/create-session.js.map +1 -0
  151. package/dist/presentation/e2eRoutes/create-user.d.ts +2 -0
  152. package/dist/presentation/e2eRoutes/create-user.d.ts.map +1 -0
  153. package/dist/presentation/e2eRoutes/create-user.js +42 -0
  154. package/dist/presentation/e2eRoutes/create-user.js.map +1 -0
  155. package/dist/presentation/e2eRoutes/index.d.ts +3 -0
  156. package/dist/presentation/e2eRoutes/index.d.ts.map +1 -0
  157. package/dist/presentation/e2eRoutes/index.js +15 -0
  158. package/dist/presentation/e2eRoutes/index.js.map +1 -0
  159. package/dist/presentation/routes/email-verification/verify-email-confirm.d.ts +2 -0
  160. package/dist/presentation/routes/email-verification/verify-email-confirm.d.ts.map +1 -0
  161. package/dist/presentation/routes/email-verification/verify-email-confirm.js +35 -0
  162. package/dist/presentation/routes/email-verification/verify-email-confirm.js.map +1 -0
  163. package/dist/presentation/routes/email-verification/verify-email-resend.d.ts +2 -0
  164. package/dist/presentation/routes/email-verification/verify-email-resend.d.ts.map +1 -0
  165. package/dist/presentation/routes/email-verification/verify-email-resend.js +27 -0
  166. package/dist/presentation/routes/email-verification/verify-email-resend.js.map +1 -0
  167. package/dist/presentation/routes/index.d.ts +3 -0
  168. package/dist/presentation/routes/index.d.ts.map +1 -0
  169. package/dist/presentation/routes/index.js +31 -0
  170. package/dist/presentation/routes/index.js.map +1 -0
  171. package/dist/presentation/routes/password/change-password.d.ts +2 -0
  172. package/dist/presentation/routes/password/change-password.d.ts.map +1 -0
  173. package/dist/presentation/routes/password/change-password.js +50 -0
  174. package/dist/presentation/routes/password/change-password.js.map +1 -0
  175. package/dist/presentation/routes/password/password-reset-confirm.d.ts +2 -0
  176. package/dist/presentation/routes/password/password-reset-confirm.d.ts.map +1 -0
  177. package/dist/presentation/routes/password/password-reset-confirm.js +36 -0
  178. package/dist/presentation/routes/password/password-reset-confirm.js.map +1 -0
  179. package/dist/presentation/routes/password/password-reset-request.d.ts +2 -0
  180. package/dist/presentation/routes/password/password-reset-request.d.ts.map +1 -0
  181. package/dist/presentation/routes/password/password-reset-request.js +26 -0
  182. package/dist/presentation/routes/password/password-reset-request.js.map +1 -0
  183. package/dist/presentation/routes/rate-limit.d.ts +10 -0
  184. package/dist/presentation/routes/rate-limit.d.ts.map +1 -0
  185. package/dist/presentation/routes/rate-limit.js +101 -0
  186. package/dist/presentation/routes/rate-limit.js.map +1 -0
  187. package/dist/presentation/routes/registration/signup.d.ts +2 -0
  188. package/dist/presentation/routes/registration/signup.d.ts.map +1 -0
  189. package/dist/presentation/routes/registration/signup.js +82 -0
  190. package/dist/presentation/routes/registration/signup.js.map +1 -0
  191. package/dist/presentation/routes/session/login.d.ts +2 -0
  192. package/dist/presentation/routes/session/login.d.ts.map +1 -0
  193. package/dist/presentation/routes/session/login.js +48 -0
  194. package/dist/presentation/routes/session/login.js.map +1 -0
  195. package/dist/presentation/routes/session/logout.d.ts +2 -0
  196. package/dist/presentation/routes/session/logout.d.ts.map +1 -0
  197. package/dist/presentation/routes/session/logout.js +25 -0
  198. package/dist/presentation/routes/session/logout.js.map +1 -0
  199. package/dist/presentation/routes/session/me.d.ts +2 -0
  200. package/dist/presentation/routes/session/me.d.ts.map +1 -0
  201. package/dist/presentation/routes/session/me.js +42 -0
  202. package/dist/presentation/routes/session/me.js.map +1 -0
  203. package/dist/presentation/routes/session/refresh.d.ts +2 -0
  204. package/dist/presentation/routes/session/refresh.d.ts.map +1 -0
  205. package/dist/presentation/routes/session/refresh.js +49 -0
  206. package/dist/presentation/routes/session/refresh.js.map +1 -0
  207. package/dist/presentation/subscribers/index.d.ts +3 -0
  208. package/dist/presentation/subscribers/index.d.ts.map +1 -0
  209. package/dist/presentation/subscribers/index.js +4 -0
  210. package/dist/presentation/subscribers/index.js.map +1 -0
  211. package/dist/presentation/subscribers/on-email-verification-send-requested.d.ts +3 -0
  212. package/dist/presentation/subscribers/on-email-verification-send-requested.d.ts.map +1 -0
  213. package/dist/presentation/subscribers/on-email-verification-send-requested.js +13 -0
  214. package/dist/presentation/subscribers/on-email-verification-send-requested.js.map +1 -0
  215. package/dist/presentation/subscribers/on-password-reset-send-requested.d.ts +3 -0
  216. package/dist/presentation/subscribers/on-password-reset-send-requested.d.ts.map +1 -0
  217. package/dist/presentation/subscribers/on-password-reset-send-requested.js +14 -0
  218. package/dist/presentation/subscribers/on-password-reset-send-requested.js.map +1 -0
  219. package/dist/tsconfig.test.tsbuildinfo +1 -0
  220. package/drizzle/0000_initial.sql +81 -0
  221. package/drizzle/meta/0000_snapshot.json +643 -0
  222. package/drizzle/meta/_journal.json +13 -0
  223. package/drizzle.config.ts +7 -0
  224. package/package.json +88 -0
  225. package/src/config.ts +88 -0
  226. package/src/core/auth.test.ts +675 -0
  227. package/src/core/auth.ts +560 -0
  228. package/src/core/entities/email-verification.ts +8 -0
  229. package/src/core/entities/password-reset.ts +8 -0
  230. package/src/core/entities/refresh-token.ts +11 -0
  231. package/src/core/entities/user.ts +12 -0
  232. package/src/core/errors.ts +123 -0
  233. package/src/core/job-lease-token.test.ts +201 -0
  234. package/src/core/job-lease-token.ts +83 -0
  235. package/src/core/jwt.test.ts +180 -0
  236. package/src/core/jwt.ts +66 -0
  237. package/src/core/password.test.ts +28 -0
  238. package/src/core/password.ts +21 -0
  239. package/src/core/rate-limit.test.ts +462 -0
  240. package/src/core/rate-limit.ts +130 -0
  241. package/src/core/runner-session-token.test.ts +126 -0
  242. package/src/core/runner-session-token.ts +49 -0
  243. package/src/db/db.ts +28 -0
  244. package/src/db/email-verifications.test.ts +61 -0
  245. package/src/db/email-verifications.ts +160 -0
  246. package/src/db/migrations.ts +4 -0
  247. package/src/db/password-resets.test.ts +61 -0
  248. package/src/db/password-resets.ts +69 -0
  249. package/src/db/rate-limits.ts +69 -0
  250. package/src/db/refresh-tokens.test.ts +164 -0
  251. package/src/db/refresh-tokens.ts +149 -0
  252. package/src/db/schema/common.ts +3 -0
  253. package/src/db/schema/email-verifications.ts +37 -0
  254. package/src/db/schema/outbox.ts +4 -0
  255. package/src/db/schema/password-resets.ts +37 -0
  256. package/src/db/schema/rate-limits.ts +27 -0
  257. package/src/db/schema/refresh-tokens.ts +43 -0
  258. package/src/db/schema/users.ts +37 -0
  259. package/src/db/users.test.ts +75 -0
  260. package/src/db/users.ts +121 -0
  261. package/src/index.test.ts +39 -0
  262. package/src/index.ts +69 -0
  263. package/src/metrics/index.ts +13 -0
  264. package/src/metrics/instance.test.ts +69 -0
  265. package/src/metrics/instance.ts +77 -0
  266. package/src/presentation/auth/bearer-token-auth.test.ts +99 -0
  267. package/src/presentation/auth/bearer-token-auth.ts +51 -0
  268. package/src/presentation/auth/jwt-auth.test.ts +136 -0
  269. package/src/presentation/auth/jwt-auth.ts +39 -0
  270. package/src/presentation/auth/lease-token-auth.test.ts +56 -0
  271. package/src/presentation/auth/lease-token-auth.ts +25 -0
  272. package/src/presentation/auth/refresh-cookie.test.ts +39 -0
  273. package/src/presentation/auth/refresh-cookie.ts +34 -0
  274. package/src/presentation/auth/runner-session-auth.test.ts +54 -0
  275. package/src/presentation/auth/runner-session-auth.ts +13 -0
  276. package/src/presentation/dto/user.ts +21 -0
  277. package/src/presentation/e2eRoutes/create-session.ts +39 -0
  278. package/src/presentation/e2eRoutes/create-user.ts +38 -0
  279. package/src/presentation/e2eRoutes/index.test.ts +102 -0
  280. package/src/presentation/e2eRoutes/index.ts +10 -0
  281. package/src/presentation/routes/email-verification/verify-email-confirm.test.ts +66 -0
  282. package/src/presentation/routes/email-verification/verify-email-confirm.ts +37 -0
  283. package/src/presentation/routes/email-verification/verify-email-resend.test.ts +100 -0
  284. package/src/presentation/routes/email-verification/verify-email-resend.ts +26 -0
  285. package/src/presentation/routes/index.ts +29 -0
  286. package/src/presentation/routes/password/change-password.test.ts +106 -0
  287. package/src/presentation/routes/password/change-password.ts +45 -0
  288. package/src/presentation/routes/password/password-reset-confirm.test.ts +63 -0
  289. package/src/presentation/routes/password/password-reset-confirm.ts +35 -0
  290. package/src/presentation/routes/password/password-reset-request.test.ts +56 -0
  291. package/src/presentation/routes/password/password-reset-request.ts +25 -0
  292. package/src/presentation/routes/rate-limit.test.ts +341 -0
  293. package/src/presentation/routes/rate-limit.ts +119 -0
  294. package/src/presentation/routes/registration/signup.test.ts +284 -0
  295. package/src/presentation/routes/registration/signup.ts +81 -0
  296. package/src/presentation/routes/session/login.test.ts +77 -0
  297. package/src/presentation/routes/session/login.ts +50 -0
  298. package/src/presentation/routes/session/logout.test.ts +55 -0
  299. package/src/presentation/routes/session/logout.ts +23 -0
  300. package/src/presentation/routes/session/me.test.ts +56 -0
  301. package/src/presentation/routes/session/me.ts +35 -0
  302. package/src/presentation/routes/session/refresh.test.ts +89 -0
  303. package/src/presentation/routes/session/refresh.ts +54 -0
  304. package/src/presentation/subscribers/email-send-requested.test.ts +77 -0
  305. package/src/presentation/subscribers/index.ts +2 -0
  306. package/src/presentation/subscribers/on-email-verification-send-requested.ts +10 -0
  307. package/src/presentation/subscribers/on-password-reset-send-requested.ts +13 -0
  308. package/test/env.ts +10 -0
  309. package/test/factories/index.ts +1 -0
  310. package/test/factories/user.ts +36 -0
  311. package/test/globalSetup.ts +18 -0
  312. package/test/index.ts +1 -0
  313. package/test/routes.ts +275 -0
  314. package/test/setup.ts +17 -0
  315. package/tsconfig.build.json +9 -0
  316. package/tsconfig.build.tsbuildinfo +1 -0
  317. package/tsconfig.json +4 -0
  318. package/tsconfig.test.json +8 -0
  319. package/vitest.config.ts +11 -0
@@ -0,0 +1,108 @@
1
+ import type { User } from './entities/user.js';
2
+ import { AuthDependencyUnavailableError, EmailNotVerifiedError, InvalidCredentialsError, UserNotFoundError } from './errors.js';
3
+ export interface SignupParams {
4
+ email: string;
5
+ password: string;
6
+ name?: string | undefined;
7
+ }
8
+ export interface ProvisionUserParams {
9
+ email: string;
10
+ name?: string | null | undefined;
11
+ }
12
+ /**
13
+ * Creates a verified, password-less user for an external identity provider.
14
+ * Existing users are returned unchanged, including their password and profile.
15
+ */
16
+ export declare function provisionUser(params: ProvisionUserParams): Promise<User>;
17
+ export declare function signup(params: SignupParams): Promise<User>;
18
+ export interface SignupWithInvitationParams extends SignupParams {
19
+ invitationToken: string;
20
+ }
21
+ export interface SignupWithInvitationResult extends LoginResult {
22
+ membership: {
23
+ id: string;
24
+ userId: string;
25
+ workspaceId: string;
26
+ } | null;
27
+ acceptError?: {
28
+ code: string;
29
+ message: string;
30
+ };
31
+ }
32
+ export declare function signupWithInvitation(params: SignupWithInvitationParams): Promise<SignupWithInvitationResult>;
33
+ export interface CreateUserParams extends SignupParams {
34
+ verified: boolean;
35
+ }
36
+ export declare function createUser(params: CreateUserParams): Promise<User>;
37
+ export interface LoginParams {
38
+ email: string;
39
+ password: string;
40
+ }
41
+ export interface LoginResult {
42
+ token: string;
43
+ refreshToken: string;
44
+ user: User;
45
+ }
46
+ export declare function login(params: LoginParams): Promise<LoginResult>;
47
+ export interface CreateSessionForUserParams {
48
+ userId?: string | undefined;
49
+ email?: string | undefined;
50
+ }
51
+ export interface CreateSessionForUserResult {
52
+ token: string;
53
+ refreshToken: string;
54
+ user: User;
55
+ }
56
+ export type CreateSessionForUserError = AuthDependencyUnavailableError | EmailNotVerifiedError | InvalidCredentialsError | UserNotFoundError;
57
+ export declare function createSessionForUser(params: CreateSessionForUserParams): Promise<CreateSessionForUserResult>;
58
+ export interface RefreshAccessTokenResult {
59
+ token: string;
60
+ /** Undefined on a grace-window hit: keep the existing cookie instead of rotating it. */
61
+ refreshToken: string | undefined;
62
+ user: User;
63
+ }
64
+ export declare function refreshAccessToken(params: {
65
+ refreshToken: string;
66
+ }): Promise<RefreshAccessTokenResult>;
67
+ export interface ConfirmEmailVerificationResult {
68
+ token: string;
69
+ refreshToken: string;
70
+ user: User;
71
+ }
72
+ export interface ResendEmailVerificationResult {
73
+ nextResendAvailableAt: Date;
74
+ }
75
+ export declare function confirmEmailVerification(params: {
76
+ token: string;
77
+ }): Promise<ConfirmEmailVerificationResult>;
78
+ export declare function resendEmailVerification(params: {
79
+ email: string;
80
+ }): Promise<ResendEmailVerificationResult>;
81
+ export declare function requestPasswordReset(params: {
82
+ email: string;
83
+ }): Promise<void>;
84
+ export interface ConfirmPasswordResetResult {
85
+ token: string;
86
+ refreshToken: string;
87
+ user: User;
88
+ }
89
+ export declare function confirmPasswordReset(params: {
90
+ token: string;
91
+ newPassword: string;
92
+ }): Promise<ConfirmPasswordResetResult>;
93
+ export declare function changePassword(params: {
94
+ userId: string;
95
+ currentPassword: string;
96
+ newPassword: string;
97
+ refreshToken?: string | undefined;
98
+ }): Promise<void>;
99
+ export declare function logout(params: {
100
+ refreshToken?: string | undefined;
101
+ }): Promise<void>;
102
+ export interface GetCurrentUserResult {
103
+ user: User;
104
+ }
105
+ export declare function getCurrentUser(params: {
106
+ userId: string;
107
+ }): Promise<GetCurrentUserResult>;
108
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/core/auth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,476 @@
1
+ import { EMAIL_VERIFICATION_RESEND_COOLDOWN_SECONDS, emailSchema } from '@shipfox/api-auth-dto';
2
+ import { acceptWorkspaceInvitation, InvitationEmailMismatchError, listMembershipsByUser, peekInvitationByRawToken, TokenAlreadyUsedError, TokenExpiredError, TokenInvalidError as WorkspacesTokenInvalidError } from '@shipfox/api-workspaces';
3
+ import { generateOpaqueToken, hashOpaqueToken } from '@shipfox/node-tokens';
4
+ import { config } from '#config.js';
5
+ import { consumeEmailVerification, createEmailVerification, createResendEmailVerification } from '#db/email-verifications.js';
6
+ import { consumePasswordReset, createPasswordReset } from '#db/password-resets.js';
7
+ import { createRefreshToken, findActiveRefreshTokenByHash, findRefreshTokenByHash, revokeRefreshTokenByHash, revokeRefreshTokensForUser, rotateRefreshToken } from '#db/refresh-tokens.js';
8
+ import { createUser as createDbUser, findUserByEmail, findUserById, markEmailVerified, provisionUser as provisionDbUser, updateUserPassword } from '#db/users.js';
9
+ import { recordTokenRefreshed } from '#metrics/index.js';
10
+ import { AuthDependencyUnavailableError, EmailNotVerifiedError, EmailTakenError, InvalidCredentialsError, TokenInvalidError, UserNotFoundError } from './errors.js';
11
+ import { signUserToken } from './jwt.js';
12
+ import { hashPassword, verifyPassword } from './password.js';
13
+ const VERIFICATION_TTL_HOURS = 24;
14
+ const RESET_TTL_HOURS = 1;
15
+ let dummyHashCache;
16
+ async function getDummyHash() {
17
+ if (!dummyHashCache) {
18
+ dummyHashCache = await hashPassword({
19
+ password: 'dummy-for-timing-parity'
20
+ });
21
+ }
22
+ return dummyHashCache;
23
+ }
24
+ function hoursFromNow(hours) {
25
+ return new Date(Date.now() + hours * 60 * 60 * 1000);
26
+ }
27
+ function daysFromNow(days) {
28
+ return new Date(Date.now() + days * 24 * 60 * 60 * 1000);
29
+ }
30
+ function recordRefreshOutcome(outcome) {
31
+ recordTokenRefreshed(outcome);
32
+ }
33
+ async function signAccessToken(user) {
34
+ const memberships = await listMembershipsByUser({
35
+ userId: user.id
36
+ }).catch((error)=>{
37
+ throw new AuthDependencyUnavailableError('workspaces', error);
38
+ });
39
+ return await signUserToken({
40
+ userId: user.id,
41
+ email: user.email,
42
+ name: user.name,
43
+ memberships: memberships.map((m)=>({
44
+ workspaceId: m.workspaceId,
45
+ role: 'admin'
46
+ })),
47
+ secret: config.AUTH_JWT_SECRET,
48
+ expiresIn: config.AUTH_JWT_EXPIRES_IN
49
+ });
50
+ }
51
+ function isWithinRotationGrace(refreshToken) {
52
+ if (!refreshToken.rotatedAt) return false;
53
+ const graceMs = config.AUTH_REFRESH_ROTATION_GRACE_SECONDS * 1000;
54
+ return Date.now() - refreshToken.rotatedAt.getTime() <= graceMs;
55
+ }
56
+ async function createRefreshSession(user) {
57
+ const refreshToken = generateOpaqueToken('refreshToken');
58
+ await createRefreshToken({
59
+ userId: user.id,
60
+ hashedToken: hashOpaqueToken(refreshToken),
61
+ expiresAt: daysFromNow(config.AUTH_REFRESH_TOKEN_EXPIRES_IN_DAYS)
62
+ });
63
+ return refreshToken;
64
+ }
65
+ function verificationLink(rawToken) {
66
+ return `${config.CLIENT_BASE_URL}/auth/verify-email?token=${rawToken}`;
67
+ }
68
+ function passwordResetLink(rawToken) {
69
+ return `${config.CLIENT_BASE_URL}/auth/reset?token=${rawToken}`;
70
+ }
71
+ async function createAndQueueEmailVerification(user) {
72
+ const rawToken = generateOpaqueToken('emailVerification');
73
+ await createEmailVerification({
74
+ userId: user.id,
75
+ hashedToken: hashOpaqueToken(rawToken),
76
+ expiresAt: hoursFromNow(VERIFICATION_TTL_HOURS),
77
+ sendEmail: {
78
+ email: user.email,
79
+ verifyLink: verificationLink(rawToken)
80
+ }
81
+ });
82
+ }
83
+ /**
84
+ * Creates a verified, password-less user for an external identity provider.
85
+ * Existing users are returned unchanged, including their password and profile.
86
+ */ export async function provisionUser(params) {
87
+ return await provisionDbUser({
88
+ email: emailSchema.parse(params.email),
89
+ name: params.name ?? null
90
+ });
91
+ }
92
+ export async function signup(params) {
93
+ const existing = await findUserByEmail({
94
+ email: params.email
95
+ });
96
+ if (existing) {
97
+ throw new EmailTakenError(params.email);
98
+ }
99
+ const hashedPassword = await hashPassword({
100
+ password: params.password
101
+ });
102
+ const user = await createDbUser({
103
+ email: params.email,
104
+ hashedPassword,
105
+ name: params.name ?? null
106
+ });
107
+ await createAndQueueEmailVerification(user);
108
+ return user;
109
+ }
110
+ export async function signupWithInvitation(params) {
111
+ // Step 1: Pre-validate the invitation BEFORE any user write so an invalid
112
+ // token never produces an orphan user. Re-validation happens again inside
113
+ // acceptWorkspaceInvitation (race-safe) after the user is created.
114
+ const invitation = await peekInvitationByRawToken({
115
+ token: params.invitationToken
116
+ });
117
+ if (!invitation) {
118
+ throw new WorkspacesTokenInvalidError('Invitation token is invalid');
119
+ }
120
+ if (invitation.acceptedAt !== null) {
121
+ throw new TokenAlreadyUsedError();
122
+ }
123
+ if (invitation.expiresAt.getTime() <= Date.now()) {
124
+ throw new TokenExpiredError();
125
+ }
126
+ if (invitation.email !== params.email) {
127
+ throw new InvitationEmailMismatchError();
128
+ }
129
+ // Step 2: Create the user as verified. The invitation email is the proof of
130
+ // ownership, and this avoids a second auth-table write just to mark verified.
131
+ const existing = await findUserByEmail({
132
+ email: params.email
133
+ });
134
+ if (existing) {
135
+ throw new EmailTakenError(params.email);
136
+ }
137
+ const hashedPassword = await hashPassword({
138
+ password: params.password
139
+ });
140
+ const user = await createDbUser({
141
+ email: params.email,
142
+ hashedPassword,
143
+ name: params.name ?? null,
144
+ emailVerifiedAt: new Date()
145
+ });
146
+ // Step 3: Accept the invitation through the workspaces module boundary.
147
+ // This is intentionally not one database transaction with auth; treat it like
148
+ // calling another service. If it fails after user creation, return a verified
149
+ // signed-in user and an explicit retryable accept error.
150
+ let membership = null;
151
+ let acceptError;
152
+ try {
153
+ const result = await acceptWorkspaceInvitation({
154
+ token: params.invitationToken,
155
+ userId: user.id,
156
+ email: user.email,
157
+ name: user.name
158
+ });
159
+ membership = {
160
+ id: result.membership.id,
161
+ userId: result.membership.userId,
162
+ workspaceId: result.membership.workspaceId
163
+ };
164
+ } catch (error) {
165
+ if (error instanceof WorkspacesTokenInvalidError || error instanceof TokenAlreadyUsedError || error instanceof TokenExpiredError || error instanceof InvitationEmailMismatchError) {
166
+ acceptError = {
167
+ code: error.name,
168
+ message: error.message
169
+ };
170
+ } else {
171
+ acceptError = {
172
+ code: 'AcceptFailed',
173
+ message: 'Could not accept the invitation; please retry from the invite link.'
174
+ };
175
+ }
176
+ }
177
+ // Step 4: Issue session. signAccessToken reads memberships through the
178
+ // workspaces module API, so a successful accept is reflected in the JWT.
179
+ const token = await signAccessToken(user);
180
+ const refreshToken = await createRefreshSession(user);
181
+ if (acceptError) {
182
+ return {
183
+ token,
184
+ refreshToken,
185
+ user,
186
+ membership,
187
+ acceptError
188
+ };
189
+ }
190
+ return {
191
+ token,
192
+ refreshToken,
193
+ user,
194
+ membership
195
+ };
196
+ }
197
+ export async function createUser(params) {
198
+ const existing = await findUserByEmail({
199
+ email: params.email
200
+ });
201
+ if (existing) {
202
+ throw new EmailTakenError(params.email);
203
+ }
204
+ const hashedPassword = await hashPassword({
205
+ password: params.password
206
+ });
207
+ const user = await createDbUser({
208
+ email: params.email,
209
+ hashedPassword,
210
+ name: params.name ?? null,
211
+ emailVerifiedAt: params.verified ? new Date() : null
212
+ });
213
+ return user;
214
+ }
215
+ export async function login(params) {
216
+ const user = await findUserByEmail({
217
+ email: params.email
218
+ });
219
+ if (!user) {
220
+ await verifyPassword({
221
+ password: params.password,
222
+ hash: await getDummyHash()
223
+ });
224
+ throw new InvalidCredentialsError();
225
+ }
226
+ const hasPassword = user.hashedPassword !== null;
227
+ const ok = await verifyPassword({
228
+ password: params.password,
229
+ hash: user.hashedPassword ?? await getDummyHash()
230
+ });
231
+ if (!hasPassword || !ok || user.status !== 'active') {
232
+ throw new InvalidCredentialsError();
233
+ }
234
+ if (user.emailVerifiedAt === null) {
235
+ throw new EmailNotVerifiedError();
236
+ }
237
+ const token = await signAccessToken(user);
238
+ const refreshToken = await createRefreshSession(user);
239
+ return {
240
+ token,
241
+ refreshToken,
242
+ user
243
+ };
244
+ }
245
+ export async function createSessionForUser(params) {
246
+ const user = params.userId ? await findUserById({
247
+ id: params.userId
248
+ }) : params.email ? await findUserByEmail({
249
+ email: params.email
250
+ }) : undefined;
251
+ if (!user) {
252
+ throw new UserNotFoundError(params.userId ?? params.email ?? 'unknown');
253
+ }
254
+ if (user.emailVerifiedAt === null) {
255
+ throw new EmailNotVerifiedError();
256
+ }
257
+ if (user.status !== 'active') {
258
+ throw new InvalidCredentialsError();
259
+ }
260
+ const token = await signAccessToken(user);
261
+ const refreshToken = await createRefreshSession(user);
262
+ return {
263
+ token,
264
+ refreshToken,
265
+ user
266
+ };
267
+ }
268
+ export async function refreshAccessToken(params) {
269
+ const currentHashedToken = hashOpaqueToken(params.refreshToken);
270
+ const current = await findRefreshTokenByHash({
271
+ hashedToken: currentHashedToken
272
+ });
273
+ if (!current) {
274
+ recordRefreshOutcome('rejected');
275
+ throw new TokenInvalidError('Refresh token is invalid or expired');
276
+ }
277
+ const user = await findUserById({
278
+ id: current.userId
279
+ });
280
+ if (user?.status !== 'active') {
281
+ await revokeRefreshTokenByHash({
282
+ hashedToken: currentHashedToken
283
+ });
284
+ recordRefreshOutcome('rejected');
285
+ throw new TokenInvalidError('Refresh token is invalid or expired');
286
+ }
287
+ // Within the grace window a rotated token means a concurrent refresh (e.g. a
288
+ // second tab); past it, reuse of a retired token means a compromised session.
289
+ if (current.rotatedAt) {
290
+ if (isWithinRotationGrace(current)) {
291
+ const token = await signAccessToken(user);
292
+ recordRefreshOutcome('grace');
293
+ return {
294
+ token,
295
+ refreshToken: undefined,
296
+ user
297
+ };
298
+ }
299
+ await revokeRefreshTokensForUser({
300
+ userId: user.id
301
+ });
302
+ recordRefreshOutcome('rejected');
303
+ throw new TokenInvalidError('Refresh token reused after rotation');
304
+ }
305
+ // Losing the CAS requires a state check: another request may have rotated,
306
+ // revoked, or expired the token before this refresh could claim it.
307
+ const nextRefreshToken = generateOpaqueToken('refreshToken');
308
+ const rotated = await rotateRefreshToken({
309
+ id: current.id,
310
+ currentHashedToken,
311
+ nextHashedToken: hashOpaqueToken(nextRefreshToken),
312
+ expiresAt: daysFromNow(config.AUTH_REFRESH_TOKEN_EXPIRES_IN_DAYS)
313
+ });
314
+ if (!rotated) {
315
+ const latest = await findRefreshTokenByHash({
316
+ hashedToken: currentHashedToken
317
+ });
318
+ if (!latest || !isWithinRotationGrace(latest)) {
319
+ recordRefreshOutcome('rejected');
320
+ throw new TokenInvalidError('Refresh token is invalid or expired');
321
+ }
322
+ const token = await signAccessToken(user);
323
+ recordRefreshOutcome('grace');
324
+ return {
325
+ token,
326
+ refreshToken: undefined,
327
+ user
328
+ };
329
+ }
330
+ const token = await signAccessToken(user);
331
+ recordRefreshOutcome('rotated');
332
+ return {
333
+ token,
334
+ refreshToken: nextRefreshToken,
335
+ user
336
+ };
337
+ }
338
+ export async function confirmEmailVerification(params) {
339
+ const consumed = await consumeEmailVerification({
340
+ hashedToken: hashOpaqueToken(params.token)
341
+ });
342
+ if (!consumed) {
343
+ throw new TokenInvalidError('Verification token is invalid or expired');
344
+ }
345
+ const user = await markEmailVerified({
346
+ userId: consumed.userId
347
+ });
348
+ if (user?.status !== 'active') {
349
+ throw new TokenInvalidError('Verification token is invalid or expired');
350
+ }
351
+ const token = await signAccessToken(user);
352
+ const refreshToken = await createRefreshSession(user);
353
+ return {
354
+ token,
355
+ refreshToken,
356
+ user
357
+ };
358
+ }
359
+ export async function resendEmailVerification(params) {
360
+ const rawToken = generateOpaqueToken('emailVerification');
361
+ const result = await createResendEmailVerification({
362
+ email: params.email,
363
+ hashedToken: hashOpaqueToken(rawToken),
364
+ expiresAt: hoursFromNow(VERIFICATION_TTL_HOURS),
365
+ cooldownSeconds: EMAIL_VERIFICATION_RESEND_COOLDOWN_SECONDS,
366
+ sendEmail: {
367
+ verifyLink: verificationLink(rawToken)
368
+ }
369
+ });
370
+ return {
371
+ nextResendAvailableAt: result.nextResendAvailableAt
372
+ };
373
+ }
374
+ export async function requestPasswordReset(params) {
375
+ const user = await findUserByEmail({
376
+ email: params.email
377
+ });
378
+ if (user?.status !== 'active' || user.hashedPassword === null) {
379
+ return;
380
+ }
381
+ const rawToken = generateOpaqueToken('passwordReset');
382
+ await createPasswordReset({
383
+ userId: user.id,
384
+ hashedToken: hashOpaqueToken(rawToken),
385
+ expiresAt: hoursFromNow(RESET_TTL_HOURS),
386
+ sendEmail: {
387
+ email: user.email,
388
+ resetLink: passwordResetLink(rawToken),
389
+ expiresInHours: RESET_TTL_HOURS
390
+ }
391
+ });
392
+ }
393
+ export async function confirmPasswordReset(params) {
394
+ const consumed = await consumePasswordReset({
395
+ hashedToken: hashOpaqueToken(params.token)
396
+ });
397
+ if (!consumed) {
398
+ throw new TokenInvalidError('Reset token is invalid or expired');
399
+ }
400
+ const existingUser = await findUserById({
401
+ id: consumed.userId
402
+ });
403
+ if (existingUser?.status !== 'active' || existingUser.hashedPassword === null) {
404
+ throw new TokenInvalidError('Reset token is invalid or expired');
405
+ }
406
+ const hashedPassword = await hashPassword({
407
+ password: params.newPassword
408
+ });
409
+ const user = await updateUserPassword({
410
+ userId: consumed.userId,
411
+ hashedPassword
412
+ });
413
+ if (user?.status !== 'active') {
414
+ throw new TokenInvalidError('Reset token is invalid or expired');
415
+ }
416
+ await revokeRefreshTokensForUser({
417
+ userId: consumed.userId
418
+ });
419
+ const token = await signAccessToken(user);
420
+ const refreshToken = await createRefreshSession(user);
421
+ return {
422
+ token,
423
+ refreshToken,
424
+ user
425
+ };
426
+ }
427
+ export async function changePassword(params) {
428
+ const user = await findUserById({
429
+ id: params.userId
430
+ });
431
+ if (!user) {
432
+ throw new UserNotFoundError(params.userId);
433
+ }
434
+ const hasPassword = user.hashedPassword !== null;
435
+ const ok = await verifyPassword({
436
+ password: params.currentPassword,
437
+ hash: user.hashedPassword ?? await getDummyHash()
438
+ });
439
+ if (!hasPassword || !ok) {
440
+ throw new InvalidCredentialsError();
441
+ }
442
+ const hashedPassword = await hashPassword({
443
+ password: params.newPassword
444
+ });
445
+ const currentRefreshSession = params.refreshToken ? await findActiveRefreshTokenByHash({
446
+ hashedToken: hashOpaqueToken(params.refreshToken)
447
+ }) : undefined;
448
+ const exceptRefreshTokenId = currentRefreshSession?.userId === user.id ? currentRefreshSession.id : undefined;
449
+ await updateUserPassword({
450
+ userId: user.id,
451
+ hashedPassword
452
+ });
453
+ await revokeRefreshTokensForUser({
454
+ userId: user.id,
455
+ exceptRefreshTokenId
456
+ });
457
+ }
458
+ export async function logout(params) {
459
+ if (!params.refreshToken) return;
460
+ await revokeRefreshTokenByHash({
461
+ hashedToken: hashOpaqueToken(params.refreshToken)
462
+ });
463
+ }
464
+ export async function getCurrentUser(params) {
465
+ const user = await findUserById({
466
+ id: params.userId
467
+ });
468
+ if (!user) {
469
+ throw new UserNotFoundError(params.userId);
470
+ }
471
+ return {
472
+ user
473
+ };
474
+ }
475
+
476
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/core/auth.ts"],"sourcesContent":["import {EMAIL_VERIFICATION_RESEND_COOLDOWN_SECONDS, emailSchema} from '@shipfox/api-auth-dto';\nimport {\n acceptWorkspaceInvitation,\n InvitationEmailMismatchError,\n listMembershipsByUser,\n peekInvitationByRawToken,\n TokenAlreadyUsedError,\n TokenExpiredError,\n TokenInvalidError as WorkspacesTokenInvalidError,\n} from '@shipfox/api-workspaces';\nimport {generateOpaqueToken, hashOpaqueToken} from '@shipfox/node-tokens';\nimport {config} from '#config.js';\nimport {\n consumeEmailVerification,\n createEmailVerification,\n createResendEmailVerification,\n} from '#db/email-verifications.js';\nimport {consumePasswordReset, createPasswordReset} from '#db/password-resets.js';\nimport {\n createRefreshToken,\n findActiveRefreshTokenByHash,\n findRefreshTokenByHash,\n revokeRefreshTokenByHash,\n revokeRefreshTokensForUser,\n rotateRefreshToken,\n} from '#db/refresh-tokens.js';\nimport {\n createUser as createDbUser,\n findUserByEmail,\n findUserById,\n markEmailVerified,\n provisionUser as provisionDbUser,\n updateUserPassword,\n} from '#db/users.js';\nimport {type AuthTokenRefreshOutcome, recordTokenRefreshed} from '#metrics/index.js';\nimport type {RefreshToken} from './entities/refresh-token.js';\nimport type {User} from './entities/user.js';\nimport {\n AuthDependencyUnavailableError,\n EmailNotVerifiedError,\n EmailTakenError,\n InvalidCredentialsError,\n TokenInvalidError,\n UserNotFoundError,\n} from './errors.js';\nimport {signUserToken} from './jwt.js';\nimport {hashPassword, verifyPassword} from './password.js';\n\nconst VERIFICATION_TTL_HOURS = 24;\nconst RESET_TTL_HOURS = 1;\n\nlet dummyHashCache: string | undefined;\nasync function getDummyHash(): Promise<string> {\n if (!dummyHashCache) {\n dummyHashCache = await hashPassword({password: 'dummy-for-timing-parity'});\n }\n return dummyHashCache;\n}\n\nfunction hoursFromNow(hours: number): Date {\n return new Date(Date.now() + hours * 60 * 60 * 1000);\n}\n\nfunction daysFromNow(days: number): Date {\n return new Date(Date.now() + days * 24 * 60 * 60 * 1000);\n}\n\nfunction recordRefreshOutcome(outcome: AuthTokenRefreshOutcome): void {\n recordTokenRefreshed(outcome);\n}\n\nasync function signAccessToken(user: User): Promise<string> {\n const memberships = await listMembershipsByUser({userId: user.id}).catch((error: unknown) => {\n throw new AuthDependencyUnavailableError('workspaces', error);\n });\n return await signUserToken({\n userId: user.id,\n email: user.email,\n name: user.name,\n memberships: memberships.map((m) => ({workspaceId: m.workspaceId, role: 'admin' as const})),\n secret: config.AUTH_JWT_SECRET,\n expiresIn: config.AUTH_JWT_EXPIRES_IN,\n });\n}\n\nfunction isWithinRotationGrace(refreshToken: RefreshToken): boolean {\n if (!refreshToken.rotatedAt) return false;\n const graceMs = config.AUTH_REFRESH_ROTATION_GRACE_SECONDS * 1000;\n return Date.now() - refreshToken.rotatedAt.getTime() <= graceMs;\n}\n\nasync function createRefreshSession(user: User): Promise<string> {\n const refreshToken = generateOpaqueToken('refreshToken');\n await createRefreshToken({\n userId: user.id,\n hashedToken: hashOpaqueToken(refreshToken),\n expiresAt: daysFromNow(config.AUTH_REFRESH_TOKEN_EXPIRES_IN_DAYS),\n });\n return refreshToken;\n}\n\nfunction verificationLink(rawToken: string): string {\n return `${config.CLIENT_BASE_URL}/auth/verify-email?token=${rawToken}`;\n}\n\nfunction passwordResetLink(rawToken: string): string {\n return `${config.CLIENT_BASE_URL}/auth/reset?token=${rawToken}`;\n}\n\nasync function createAndQueueEmailVerification(user: User): Promise<void> {\n const rawToken = generateOpaqueToken('emailVerification');\n await createEmailVerification({\n userId: user.id,\n hashedToken: hashOpaqueToken(rawToken),\n expiresAt: hoursFromNow(VERIFICATION_TTL_HOURS),\n sendEmail: {\n email: user.email,\n verifyLink: verificationLink(rawToken),\n },\n });\n}\n\nexport interface SignupParams {\n email: string;\n password: string;\n name?: string | undefined;\n}\n\nexport interface ProvisionUserParams {\n email: string;\n name?: string | null | undefined;\n}\n\n/**\n * Creates a verified, password-less user for an external identity provider.\n * Existing users are returned unchanged, including their password and profile.\n */\nexport async function provisionUser(params: ProvisionUserParams): Promise<User> {\n return await provisionDbUser({\n email: emailSchema.parse(params.email),\n name: params.name ?? null,\n });\n}\n\nexport async function signup(params: SignupParams): Promise<User> {\n const existing = await findUserByEmail({email: params.email});\n if (existing) {\n throw new EmailTakenError(params.email);\n }\n\n const hashedPassword = await hashPassword({password: params.password});\n const user = await createDbUser({\n email: params.email,\n hashedPassword,\n name: params.name ?? null,\n });\n\n await createAndQueueEmailVerification(user);\n\n return user;\n}\n\nexport interface SignupWithInvitationParams extends SignupParams {\n invitationToken: string;\n}\n\nexport interface SignupWithInvitationResult extends LoginResult {\n membership: {id: string; userId: string; workspaceId: string} | null;\n acceptError?: {code: string; message: string};\n}\n\nexport async function signupWithInvitation(\n params: SignupWithInvitationParams,\n): Promise<SignupWithInvitationResult> {\n // Step 1: Pre-validate the invitation BEFORE any user write so an invalid\n // token never produces an orphan user. Re-validation happens again inside\n // acceptWorkspaceInvitation (race-safe) after the user is created.\n const invitation = await peekInvitationByRawToken({token: params.invitationToken});\n if (!invitation) {\n throw new WorkspacesTokenInvalidError('Invitation token is invalid');\n }\n if (invitation.acceptedAt !== null) {\n throw new TokenAlreadyUsedError();\n }\n if (invitation.expiresAt.getTime() <= Date.now()) {\n throw new TokenExpiredError();\n }\n if (invitation.email !== params.email) {\n throw new InvitationEmailMismatchError();\n }\n\n // Step 2: Create the user as verified. The invitation email is the proof of\n // ownership, and this avoids a second auth-table write just to mark verified.\n const existing = await findUserByEmail({email: params.email});\n if (existing) {\n throw new EmailTakenError(params.email);\n }\n const hashedPassword = await hashPassword({password: params.password});\n const user = await createDbUser({\n email: params.email,\n hashedPassword,\n name: params.name ?? null,\n emailVerifiedAt: new Date(),\n });\n\n // Step 3: Accept the invitation through the workspaces module boundary.\n // This is intentionally not one database transaction with auth; treat it like\n // calling another service. If it fails after user creation, return a verified\n // signed-in user and an explicit retryable accept error.\n let membership: SignupWithInvitationResult['membership'] = null;\n let acceptError: SignupWithInvitationResult['acceptError'];\n try {\n const result = await acceptWorkspaceInvitation({\n token: params.invitationToken,\n userId: user.id,\n email: user.email,\n name: user.name,\n });\n membership = {\n id: result.membership.id,\n userId: result.membership.userId,\n workspaceId: result.membership.workspaceId,\n };\n } catch (error) {\n if (\n error instanceof WorkspacesTokenInvalidError ||\n error instanceof TokenAlreadyUsedError ||\n error instanceof TokenExpiredError ||\n error instanceof InvitationEmailMismatchError\n ) {\n acceptError = {code: error.name, message: error.message};\n } else {\n acceptError = {\n code: 'AcceptFailed',\n message: 'Could not accept the invitation; please retry from the invite link.',\n };\n }\n }\n\n // Step 4: Issue session. signAccessToken reads memberships through the\n // workspaces module API, so a successful accept is reflected in the JWT.\n const token = await signAccessToken(user);\n const refreshToken = await createRefreshSession(user);\n\n if (acceptError) {\n return {token, refreshToken, user, membership, acceptError};\n }\n return {token, refreshToken, user, membership};\n}\n\nexport interface CreateUserParams extends SignupParams {\n verified: boolean;\n}\n\nexport async function createUser(params: CreateUserParams): Promise<User> {\n const existing = await findUserByEmail({email: params.email});\n if (existing) {\n throw new EmailTakenError(params.email);\n }\n\n const hashedPassword = await hashPassword({password: params.password});\n const user = await createDbUser({\n email: params.email,\n hashedPassword,\n name: params.name ?? null,\n emailVerifiedAt: params.verified ? new Date() : null,\n });\n\n return user;\n}\n\nexport interface LoginParams {\n email: string;\n password: string;\n}\n\nexport interface LoginResult {\n token: string;\n refreshToken: string;\n user: User;\n}\n\nexport async function login(params: LoginParams): Promise<LoginResult> {\n const user = await findUserByEmail({email: params.email});\n\n if (!user) {\n await verifyPassword({password: params.password, hash: await getDummyHash()});\n throw new InvalidCredentialsError();\n }\n\n const hasPassword = user.hashedPassword !== null;\n const ok = await verifyPassword({\n password: params.password,\n hash: user.hashedPassword ?? (await getDummyHash()),\n });\n if (!hasPassword || !ok || user.status !== 'active') {\n throw new InvalidCredentialsError();\n }\n\n if (user.emailVerifiedAt === null) {\n throw new EmailNotVerifiedError();\n }\n\n const token = await signAccessToken(user);\n const refreshToken = await createRefreshSession(user);\n\n return {token, refreshToken, user};\n}\n\nexport interface CreateSessionForUserParams {\n userId?: string | undefined;\n email?: string | undefined;\n}\n\nexport interface CreateSessionForUserResult {\n token: string;\n refreshToken: string;\n user: User;\n}\n\nexport type CreateSessionForUserError =\n | AuthDependencyUnavailableError\n | EmailNotVerifiedError\n | InvalidCredentialsError\n | UserNotFoundError;\n\nexport async function createSessionForUser(\n params: CreateSessionForUserParams,\n): Promise<CreateSessionForUserResult> {\n const user = params.userId\n ? await findUserById({id: params.userId})\n : params.email\n ? await findUserByEmail({email: params.email})\n : undefined;\n\n if (!user) {\n throw new UserNotFoundError(params.userId ?? params.email ?? 'unknown');\n }\n if (user.emailVerifiedAt === null) {\n throw new EmailNotVerifiedError();\n }\n if (user.status !== 'active') {\n throw new InvalidCredentialsError();\n }\n\n const token = await signAccessToken(user);\n const refreshToken = await createRefreshSession(user);\n\n return {token, refreshToken, user};\n}\n\nexport interface RefreshAccessTokenResult {\n token: string;\n /** Undefined on a grace-window hit: keep the existing cookie instead of rotating it. */\n refreshToken: string | undefined;\n user: User;\n}\n\nexport async function refreshAccessToken(params: {\n refreshToken: string;\n}): Promise<RefreshAccessTokenResult> {\n const currentHashedToken = hashOpaqueToken(params.refreshToken);\n const current = await findRefreshTokenByHash({hashedToken: currentHashedToken});\n if (!current) {\n recordRefreshOutcome('rejected');\n throw new TokenInvalidError('Refresh token is invalid or expired');\n }\n\n const user = await findUserById({id: current.userId});\n if (user?.status !== 'active') {\n await revokeRefreshTokenByHash({hashedToken: currentHashedToken});\n recordRefreshOutcome('rejected');\n throw new TokenInvalidError('Refresh token is invalid or expired');\n }\n\n // Within the grace window a rotated token means a concurrent refresh (e.g. a\n // second tab); past it, reuse of a retired token means a compromised session.\n if (current.rotatedAt) {\n if (isWithinRotationGrace(current)) {\n const token = await signAccessToken(user);\n recordRefreshOutcome('grace');\n return {token, refreshToken: undefined, user};\n }\n await revokeRefreshTokensForUser({userId: user.id});\n recordRefreshOutcome('rejected');\n throw new TokenInvalidError('Refresh token reused after rotation');\n }\n\n // Losing the CAS requires a state check: another request may have rotated,\n // revoked, or expired the token before this refresh could claim it.\n const nextRefreshToken = generateOpaqueToken('refreshToken');\n const rotated = await rotateRefreshToken({\n id: current.id,\n currentHashedToken,\n nextHashedToken: hashOpaqueToken(nextRefreshToken),\n expiresAt: daysFromNow(config.AUTH_REFRESH_TOKEN_EXPIRES_IN_DAYS),\n });\n if (!rotated) {\n const latest = await findRefreshTokenByHash({hashedToken: currentHashedToken});\n if (!latest || !isWithinRotationGrace(latest)) {\n recordRefreshOutcome('rejected');\n throw new TokenInvalidError('Refresh token is invalid or expired');\n }\n const token = await signAccessToken(user);\n recordRefreshOutcome('grace');\n return {token, refreshToken: undefined, user};\n }\n\n const token = await signAccessToken(user);\n recordRefreshOutcome('rotated');\n return {token, refreshToken: nextRefreshToken, user};\n}\n\nexport interface ConfirmEmailVerificationResult {\n token: string;\n refreshToken: string;\n user: User;\n}\n\nexport interface ResendEmailVerificationResult {\n nextResendAvailableAt: Date;\n}\n\nexport async function confirmEmailVerification(params: {\n token: string;\n}): Promise<ConfirmEmailVerificationResult> {\n const consumed = await consumeEmailVerification({hashedToken: hashOpaqueToken(params.token)});\n if (!consumed) {\n throw new TokenInvalidError('Verification token is invalid or expired');\n }\n\n const user = await markEmailVerified({userId: consumed.userId});\n if (user?.status !== 'active') {\n throw new TokenInvalidError('Verification token is invalid or expired');\n }\n\n const token = await signAccessToken(user);\n const refreshToken = await createRefreshSession(user);\n\n return {token, refreshToken, user};\n}\n\nexport async function resendEmailVerification(params: {\n email: string;\n}): Promise<ResendEmailVerificationResult> {\n const rawToken = generateOpaqueToken('emailVerification');\n const result = await createResendEmailVerification({\n email: params.email,\n hashedToken: hashOpaqueToken(rawToken),\n expiresAt: hoursFromNow(VERIFICATION_TTL_HOURS),\n cooldownSeconds: EMAIL_VERIFICATION_RESEND_COOLDOWN_SECONDS,\n sendEmail: {\n verifyLink: verificationLink(rawToken),\n },\n });\n\n return {nextResendAvailableAt: result.nextResendAvailableAt};\n}\n\nexport async function requestPasswordReset(params: {email: string}): Promise<void> {\n const user = await findUserByEmail({email: params.email});\n if (user?.status !== 'active' || user.hashedPassword === null) {\n return;\n }\n\n const rawToken = generateOpaqueToken('passwordReset');\n await createPasswordReset({\n userId: user.id,\n hashedToken: hashOpaqueToken(rawToken),\n expiresAt: hoursFromNow(RESET_TTL_HOURS),\n sendEmail: {\n email: user.email,\n resetLink: passwordResetLink(rawToken),\n expiresInHours: RESET_TTL_HOURS,\n },\n });\n}\n\nexport interface ConfirmPasswordResetResult {\n token: string;\n refreshToken: string;\n user: User;\n}\n\nexport async function confirmPasswordReset(params: {\n token: string;\n newPassword: string;\n}): Promise<ConfirmPasswordResetResult> {\n const consumed = await consumePasswordReset({hashedToken: hashOpaqueToken(params.token)});\n if (!consumed) {\n throw new TokenInvalidError('Reset token is invalid or expired');\n }\n\n const existingUser = await findUserById({id: consumed.userId});\n if (existingUser?.status !== 'active' || existingUser.hashedPassword === null) {\n throw new TokenInvalidError('Reset token is invalid or expired');\n }\n\n const hashedPassword = await hashPassword({password: params.newPassword});\n const user = await updateUserPassword({userId: consumed.userId, hashedPassword});\n if (user?.status !== 'active') {\n throw new TokenInvalidError('Reset token is invalid or expired');\n }\n\n await revokeRefreshTokensForUser({userId: consumed.userId});\n\n const token = await signAccessToken(user);\n const refreshToken = await createRefreshSession(user);\n\n return {token, refreshToken, user};\n}\n\nexport async function changePassword(params: {\n userId: string;\n currentPassword: string;\n newPassword: string;\n refreshToken?: string | undefined;\n}): Promise<void> {\n const user = await findUserById({id: params.userId});\n if (!user) {\n throw new UserNotFoundError(params.userId);\n }\n\n const hasPassword = user.hashedPassword !== null;\n const ok = await verifyPassword({\n password: params.currentPassword,\n hash: user.hashedPassword ?? (await getDummyHash()),\n });\n if (!hasPassword || !ok) {\n throw new InvalidCredentialsError();\n }\n\n const hashedPassword = await hashPassword({password: params.newPassword});\n const currentRefreshSession = params.refreshToken\n ? await findActiveRefreshTokenByHash({hashedToken: hashOpaqueToken(params.refreshToken)})\n : undefined;\n const exceptRefreshTokenId =\n currentRefreshSession?.userId === user.id ? currentRefreshSession.id : undefined;\n\n await updateUserPassword({userId: user.id, hashedPassword});\n await revokeRefreshTokensForUser({userId: user.id, exceptRefreshTokenId});\n}\n\nexport async function logout(params: {refreshToken?: string | undefined}): Promise<void> {\n if (!params.refreshToken) return;\n await revokeRefreshTokenByHash({hashedToken: hashOpaqueToken(params.refreshToken)});\n}\n\nexport interface GetCurrentUserResult {\n user: User;\n}\n\nexport async function getCurrentUser(params: {userId: string}): Promise<GetCurrentUserResult> {\n const user = await findUserById({id: params.userId});\n if (!user) {\n throw new UserNotFoundError(params.userId);\n }\n\n return {user};\n}\n"],"names":["EMAIL_VERIFICATION_RESEND_COOLDOWN_SECONDS","emailSchema","acceptWorkspaceInvitation","InvitationEmailMismatchError","listMembershipsByUser","peekInvitationByRawToken","TokenAlreadyUsedError","TokenExpiredError","TokenInvalidError","WorkspacesTokenInvalidError","generateOpaqueToken","hashOpaqueToken","config","consumeEmailVerification","createEmailVerification","createResendEmailVerification","consumePasswordReset","createPasswordReset","createRefreshToken","findActiveRefreshTokenByHash","findRefreshTokenByHash","revokeRefreshTokenByHash","revokeRefreshTokensForUser","rotateRefreshToken","createUser","createDbUser","findUserByEmail","findUserById","markEmailVerified","provisionUser","provisionDbUser","updateUserPassword","recordTokenRefreshed","AuthDependencyUnavailableError","EmailNotVerifiedError","EmailTakenError","InvalidCredentialsError","UserNotFoundError","signUserToken","hashPassword","verifyPassword","VERIFICATION_TTL_HOURS","RESET_TTL_HOURS","dummyHashCache","getDummyHash","password","hoursFromNow","hours","Date","now","daysFromNow","days","recordRefreshOutcome","outcome","signAccessToken","user","memberships","userId","id","catch","error","email","name","map","m","workspaceId","role","secret","AUTH_JWT_SECRET","expiresIn","AUTH_JWT_EXPIRES_IN","isWithinRotationGrace","refreshToken","rotatedAt","graceMs","AUTH_REFRESH_ROTATION_GRACE_SECONDS","getTime","createRefreshSession","hashedToken","expiresAt","AUTH_REFRESH_TOKEN_EXPIRES_IN_DAYS","verificationLink","rawToken","CLIENT_BASE_URL","passwordResetLink","createAndQueueEmailVerification","sendEmail","verifyLink","params","parse","signup","existing","hashedPassword","signupWithInvitation","invitation","token","invitationToken","acceptedAt","emailVerifiedAt","membership","acceptError","result","code","message","verified","login","hash","hasPassword","ok","status","createSessionForUser","undefined","refreshAccessToken","currentHashedToken","current","nextRefreshToken","rotated","nextHashedToken","latest","confirmEmailVerification","consumed","resendEmailVerification","cooldownSeconds","nextResendAvailableAt","requestPasswordReset","resetLink","expiresInHours","confirmPasswordReset","existingUser","newPassword","changePassword","currentPassword","currentRefreshSession","exceptRefreshTokenId","logout","getCurrentUser"],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ export interface EmailVerification {
2
+ id: string;
3
+ userId: string;
4
+ hashedToken: string;
5
+ expiresAt: Date;
6
+ usedAt: Date | null;
7
+ createdAt: Date;
8
+ }
9
+ //# sourceMappingURL=email-verification.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"email-verification.d.ts","sourceRoot":"","sources":["../../../src/core/entities/email-verification.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,IAAI,CAAC;IAChB,MAAM,EAAE,IAAI,GAAG,IAAI,CAAC;IACpB,SAAS,EAAE,IAAI,CAAC;CACjB"}
@@ -0,0 +1,3 @@
1
+ export { };
2
+
3
+ //# sourceMappingURL=email-verification.js.map