@molecule/api-resource-user 1.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 (164) hide show
  1. package/LICENSE +115 -0
  2. package/dist/authorization.d.ts +107 -0
  3. package/dist/authorization.d.ts.map +1 -0
  4. package/dist/authorization.js +375 -0
  5. package/dist/authorization.js.map +1 -0
  6. package/dist/authorizers/auth.d.ts +8 -0
  7. package/dist/authorizers/auth.d.ts.map +1 -0
  8. package/dist/authorizers/auth.js +19 -0
  9. package/dist/authorizers/auth.js.map +1 -0
  10. package/dist/authorizers/authSelf.d.ts +14 -0
  11. package/dist/authorizers/authSelf.d.ts.map +1 -0
  12. package/dist/authorizers/authSelf.js +31 -0
  13. package/dist/authorizers/authSelf.js.map +1 -0
  14. package/dist/authorizers/index.d.ts +5 -0
  15. package/dist/authorizers/index.d.ts.map +1 -0
  16. package/dist/authorizers/index.js +5 -0
  17. package/dist/authorizers/index.js.map +1 -0
  18. package/dist/authorizers/rateLimit.d.ts +84 -0
  19. package/dist/authorizers/rateLimit.d.ts.map +1 -0
  20. package/dist/authorizers/rateLimit.js +144 -0
  21. package/dist/authorizers/rateLimit.js.map +1 -0
  22. package/dist/authorizers/requireWebhookAuthenticity.d.ts +23 -0
  23. package/dist/authorizers/requireWebhookAuthenticity.d.ts.map +1 -0
  24. package/dist/authorizers/requireWebhookAuthenticity.js +76 -0
  25. package/dist/authorizers/requireWebhookAuthenticity.js.map +1 -0
  26. package/dist/browser-guard.d.ts +2 -0
  27. package/dist/browser-guard.d.ts.map +1 -0
  28. package/dist/browser-guard.js +19 -0
  29. package/dist/browser-guard.js.map +1 -0
  30. package/dist/handlers/create.d.ts +52 -0
  31. package/dist/handlers/create.d.ts.map +1 -0
  32. package/dist/handlers/create.js +208 -0
  33. package/dist/handlers/create.js.map +1 -0
  34. package/dist/handlers/del.d.ts +30 -0
  35. package/dist/handlers/del.d.ts.map +1 -0
  36. package/dist/handlers/del.js +55 -0
  37. package/dist/handlers/del.js.map +1 -0
  38. package/dist/handlers/forgotPassword.d.ts +34 -0
  39. package/dist/handlers/forgotPassword.d.ts.map +1 -0
  40. package/dist/handlers/forgotPassword.js +119 -0
  41. package/dist/handlers/forgotPassword.js.map +1 -0
  42. package/dist/handlers/index.d.ts +16 -0
  43. package/dist/handlers/index.d.ts.map +1 -0
  44. package/dist/handlers/index.js +16 -0
  45. package/dist/handlers/index.js.map +1 -0
  46. package/dist/handlers/logIn.d.ts +91 -0
  47. package/dist/handlers/logIn.d.ts.map +1 -0
  48. package/dist/handlers/logIn.js +223 -0
  49. package/dist/handlers/logIn.js.map +1 -0
  50. package/dist/handlers/logInOAuth.d.ts +66 -0
  51. package/dist/handlers/logInOAuth.d.ts.map +1 -0
  52. package/dist/handlers/logInOAuth.js +462 -0
  53. package/dist/handlers/logInOAuth.js.map +1 -0
  54. package/dist/handlers/logout.d.ts +22 -0
  55. package/dist/handlers/logout.d.ts.map +1 -0
  56. package/dist/handlers/logout.js +57 -0
  57. package/dist/handlers/logout.js.map +1 -0
  58. package/dist/handlers/oauthAuthorize.d.ts +44 -0
  59. package/dist/handlers/oauthAuthorize.d.ts.map +1 -0
  60. package/dist/handlers/oauthAuthorize.js +141 -0
  61. package/dist/handlers/oauthAuthorize.js.map +1 -0
  62. package/dist/handlers/payments/handlePaymentNotification.d.ts +28 -0
  63. package/dist/handlers/payments/handlePaymentNotification.d.ts.map +1 -0
  64. package/dist/handlers/payments/handlePaymentNotification.js +217 -0
  65. package/dist/handlers/payments/handlePaymentNotification.js.map +1 -0
  66. package/dist/handlers/payments/registry.d.ts +13 -0
  67. package/dist/handlers/payments/registry.d.ts.map +1 -0
  68. package/dist/handlers/payments/registry.js +13 -0
  69. package/dist/handlers/payments/registry.js.map +1 -0
  70. package/dist/handlers/payments/verifyPayment.d.ts +37 -0
  71. package/dist/handlers/payments/verifyPayment.d.ts.map +1 -0
  72. package/dist/handlers/payments/verifyPayment.js +383 -0
  73. package/dist/handlers/payments/verifyPayment.js.map +1 -0
  74. package/dist/handlers/read.d.ts +17 -0
  75. package/dist/handlers/read.d.ts.map +1 -0
  76. package/dist/handlers/read.js +60 -0
  77. package/dist/handlers/read.js.map +1 -0
  78. package/dist/handlers/readSelf.d.ts +23 -0
  79. package/dist/handlers/readSelf.d.ts.map +1 -0
  80. package/dist/handlers/readSelf.js +77 -0
  81. package/dist/handlers/readSelf.js.map +1 -0
  82. package/dist/handlers/resetPassword.d.ts +40 -0
  83. package/dist/handlers/resetPassword.d.ts.map +1 -0
  84. package/dist/handlers/resetPassword.js +147 -0
  85. package/dist/handlers/resetPassword.js.map +1 -0
  86. package/dist/handlers/update.d.ts +28 -0
  87. package/dist/handlers/update.d.ts.map +1 -0
  88. package/dist/handlers/update.js +250 -0
  89. package/dist/handlers/update.js.map +1 -0
  90. package/dist/handlers/updatePassword.d.ts +35 -0
  91. package/dist/handlers/updatePassword.d.ts.map +1 -0
  92. package/dist/handlers/updatePassword.js +113 -0
  93. package/dist/handlers/updatePassword.js.map +1 -0
  94. package/dist/handlers/updatePlan.d.ts +41 -0
  95. package/dist/handlers/updatePlan.d.ts.map +1 -0
  96. package/dist/handlers/updatePlan.js +242 -0
  97. package/dist/handlers/updatePlan.js.map +1 -0
  98. package/dist/handlers/verifyTwoFactor.d.ts +46 -0
  99. package/dist/handlers/verifyTwoFactor.d.ts.map +1 -0
  100. package/dist/handlers/verifyTwoFactor.js +162 -0
  101. package/dist/handlers/verifyTwoFactor.js.map +1 -0
  102. package/dist/i18n.d.ts +10 -0
  103. package/dist/i18n.d.ts.map +1 -0
  104. package/dist/i18n.js +25 -0
  105. package/dist/i18n.js.map +1 -0
  106. package/dist/index.d.ts +88 -0
  107. package/dist/index.d.ts.map +1 -0
  108. package/dist/index.js +88 -0
  109. package/dist/index.js.map +1 -0
  110. package/dist/requestHandlerMap.d.ts +44 -0
  111. package/dist/requestHandlerMap.d.ts.map +1 -0
  112. package/dist/requestHandlerMap.js +68 -0
  113. package/dist/requestHandlerMap.js.map +1 -0
  114. package/dist/resource.d.ts +22 -0
  115. package/dist/resource.d.ts.map +1 -0
  116. package/dist/resource.js +23 -0
  117. package/dist/resource.js.map +1 -0
  118. package/dist/routes.d.ts +44 -0
  119. package/dist/routes.d.ts.map +1 -0
  120. package/dist/routes.js +121 -0
  121. package/dist/routes.js.map +1 -0
  122. package/dist/schema.d.ts +193 -0
  123. package/dist/schema.d.ts.map +1 -0
  124. package/dist/schema.js +229 -0
  125. package/dist/schema.js.map +1 -0
  126. package/dist/secrets.d.ts +16 -0
  127. package/dist/secrets.d.ts.map +1 -0
  128. package/dist/secrets.js +28 -0
  129. package/dist/secrets.js.map +1 -0
  130. package/dist/types.d.ts +14 -0
  131. package/dist/types.d.ts.map +1 -0
  132. package/dist/types.js +9 -0
  133. package/dist/types.js.map +1 -0
  134. package/dist/utilities/getPlan.d.ts +19 -0
  135. package/dist/utilities/getPlan.d.ts.map +1 -0
  136. package/dist/utilities/getPlan.js +26 -0
  137. package/dist/utilities/getPlan.js.map +1 -0
  138. package/dist/utilities/hashResetToken.d.ts +16 -0
  139. package/dist/utilities/hashResetToken.d.ts.map +1 -0
  140. package/dist/utilities/hashResetToken.js +19 -0
  141. package/dist/utilities/hashResetToken.js.map +1 -0
  142. package/dist/utilities/index.d.ts +5 -0
  143. package/dist/utilities/index.d.ts.map +1 -0
  144. package/dist/utilities/index.js +5 -0
  145. package/dist/utilities/index.js.map +1 -0
  146. package/dist/utilities/invalidateEntitlements.d.ts +24 -0
  147. package/dist/utilities/invalidateEntitlements.d.ts.map +1 -0
  148. package/dist/utilities/invalidateEntitlements.js +41 -0
  149. package/dist/utilities/invalidateEntitlements.js.map +1 -0
  150. package/dist/utilities/normalizeEmail.d.ts +22 -0
  151. package/dist/utilities/normalizeEmail.d.ts.map +1 -0
  152. package/dist/utilities/normalizeEmail.js +28 -0
  153. package/dist/utilities/normalizeEmail.js.map +1 -0
  154. package/dist/utilities/notify.d.ts +21 -0
  155. package/dist/utilities/notify.d.ts.map +1 -0
  156. package/dist/utilities/notify.js +39 -0
  157. package/dist/utilities/notify.js.map +1 -0
  158. package/dist/utilities/stripSensitiveUserColumns.d.ts +25 -0
  159. package/dist/utilities/stripSensitiveUserColumns.d.ts.map +1 -0
  160. package/dist/utilities/stripSensitiveUserColumns.js +43 -0
  161. package/dist/utilities/stripSensitiveUserColumns.js.map +1 -0
  162. package/package.json +94 -0
  163. package/setup/users.sql +22 -0
  164. package/setup/usersSecrets.sql +9 -0
package/dist/schema.js ADDED
@@ -0,0 +1,229 @@
1
+ /**
2
+ * User resource schema definitions using Zod.
3
+ *
4
+ * Types are automatically inferred from schemas using z.infer<>.
5
+ *
6
+ * @module
7
+ */
8
+ import { z } from 'zod';
9
+ import { basePropsSchema } from '@molecule/api-resource/schema';
10
+ /**
11
+ * Maximum length (in characters) of a user's `bio`.
12
+ */
13
+ export const MAX_BIO_LENGTH = 1000;
14
+ /**
15
+ * Maximum length (in characters) of a user's `avatar`. Sized to permit a small
16
+ * inline data-URI (~256KB) without requiring an external upload/storage bond.
17
+ * Larger avatars must be hosted elsewhere and referenced by URL.
18
+ */
19
+ export const MAX_AVATAR_LENGTH = 256 * 1024;
20
+ /**
21
+ * Creates a full schema for user props.
22
+ *
23
+ * OAuth servers and plan keys can be constrained by passing them as options.
24
+ * @param options - Optional configuration.
25
+ * @param options.oauthServers - Tuple of allowed OAuth server names. Constrains `oauthServer` to a Zod enum.
26
+ * @param options.planKeys - Tuple of allowed plan key strings. Constrains `planKey` to a Zod enum.
27
+ * @returns A Zod object schema extending `basePropsSchema` with user-specific fields (username, email, OAuth, plan).
28
+ */
29
+ export const createSchema = (options) => {
30
+ return basePropsSchema.extend({
31
+ /**
32
+ * An alphanumeric username.
33
+ */
34
+ username: z.string().optional(),
35
+ /**
36
+ * The user's given name.
37
+ */
38
+ name: z.string().optional(),
39
+ /**
40
+ * The user's email address.
41
+ */
42
+ email: z.string().email().nullable().optional(),
43
+ /**
44
+ * Whether the user's email address has been verified — either by an OAuth
45
+ * provider that affirmatively confirmed mailbox ownership (Google/Apple
46
+ * `email_verified`, GitHub public email, GitLab `confirmed_at`) or by a
47
+ * post-signup email-confirmation flow. Defaults to `false`. Used to decide
48
+ * whether a provider-supplied email may be trusted over an existing,
49
+ * unverified account so an unverified squatter cannot block the verified
50
+ * mailbox owner.
51
+ */
52
+ emailVerified: z.boolean().optional(),
53
+ /**
54
+ * The user's avatar — either a URL or a small inline data-URI
55
+ * (capped at {@link MAX_AVATAR_LENGTH} characters / ~256KB). Optional and
56
+ * nullable so it can be cleared and remains back-compatible with existing rows.
57
+ */
58
+ avatar: z.string().max(MAX_AVATAR_LENGTH).nullable().optional(),
59
+ /**
60
+ * A short free-text biography for the user
61
+ * (capped at {@link MAX_BIO_LENGTH} characters). Optional and nullable for
62
+ * back-compat and clearing.
63
+ */
64
+ bio: z.string().max(MAX_BIO_LENGTH).nullable().optional(),
65
+ /**
66
+ * Whether two-factor authentication is enabled.
67
+ */
68
+ twoFactorEnabled: z.boolean().optional(),
69
+ /**
70
+ * The OAuth server used to authenticate the user.
71
+ */
72
+ oauthServer: options?.oauthServers
73
+ ? z.enum(options.oauthServers).optional()
74
+ : z.string().optional(),
75
+ /**
76
+ * Unique identifier for the user from the OAuth server.
77
+ */
78
+ oauthId: z.string().optional(),
79
+ /**
80
+ * User data retrieved from the OAuth server.
81
+ */
82
+ oauthData: z.record(z.string(), z.unknown()).optional(),
83
+ /**
84
+ * The user's current plan key.
85
+ */
86
+ planKey: options?.planKeys ? z.enum(options.planKeys).optional() : z.string().optional(),
87
+ /**
88
+ * When the plan expires (ISO 8601 timestamp).
89
+ */
90
+ planExpiresAt: z.string().datetime().optional(),
91
+ /**
92
+ * True if the plan will automatically renew.
93
+ */
94
+ planAutoRenews: z.boolean().optional(),
95
+ });
96
+ };
97
+ /**
98
+ * Default schema for user props.
99
+ */
100
+ export const propsSchema = createSchema();
101
+ /**
102
+ * Secret properties stored in a separate table.
103
+ */
104
+ export const secretPropsSchema = z.object({
105
+ /**
106
+ * The user's ID.
107
+ */
108
+ id: z.string().uuid(),
109
+ /**
110
+ * The user's hashed password.
111
+ */
112
+ passwordHash: z.string().optional(),
113
+ /**
114
+ * Token for password reset.
115
+ */
116
+ passwordResetToken: z.string().optional(),
117
+ /**
118
+ * When the password reset token was created.
119
+ */
120
+ passwordResetTokenAt: z.string().datetime().optional(),
121
+ /**
122
+ * Pending two-factor secret during setup.
123
+ */
124
+ pendingTwoFactorSecret: z.string().optional(),
125
+ /**
126
+ * Active two-factor secret.
127
+ */
128
+ twoFactorSecret: z.string().optional(),
129
+ /**
130
+ * The last successfully-consumed TOTP time step (RFC 6238 time step counter).
131
+ * Persisted after each successful 2FA verification and passed back as
132
+ * `afterTimeStep` so the same (or an earlier) code is rejected within its
133
+ * validity window — single-use / replay protection.
134
+ */
135
+ lastTwoFactorTimeStep: z.number().int().optional(),
136
+ });
137
+ /**
138
+ * Schema for creating a user via password.
139
+ */
140
+ export const createPropsSchema = propsSchema.pick({
141
+ username: true,
142
+ name: true,
143
+ email: true,
144
+ });
145
+ /** Schema for creating secret props (password hash only). */
146
+ export const createSecretPropsSchema = secretPropsSchema.pick({
147
+ passwordHash: true,
148
+ });
149
+ /**
150
+ * Schema for creating a user via OAuth.
151
+ */
152
+ export const createOAuthPropsSchema = propsSchema.pick({
153
+ username: true,
154
+ name: true,
155
+ email: true,
156
+ emailVerified: true,
157
+ oauthServer: true,
158
+ oauthId: true,
159
+ oauthData: true,
160
+ });
161
+ /** Schema for updating a user (partial username, name, email, avatar, bio). */
162
+ export const updatePropsSchema = propsSchema
163
+ .pick({
164
+ username: true,
165
+ name: true,
166
+ email: true,
167
+ avatar: true,
168
+ bio: true,
169
+ })
170
+ .partial();
171
+ /** Schema for updating password secret props (partial password hash). */
172
+ export const updatePasswordSecretPropsSchema = secretPropsSchema
173
+ .pick({
174
+ passwordHash: true,
175
+ })
176
+ .partial();
177
+ /** Schema for updating a user's plan (partial planKey, planExpiresAt, planAutoRenews). */
178
+ export const updatePlanPropsSchema = propsSchema
179
+ .pick({
180
+ planKey: true,
181
+ planExpiresAt: true,
182
+ planAutoRenews: true,
183
+ })
184
+ .partial();
185
+ /**
186
+ * Schema for verifying two-factor authentication.
187
+ */
188
+ export const verifyTwoFactorPropsSchema = propsSchema
189
+ .pick({
190
+ twoFactorEnabled: true,
191
+ })
192
+ .partial();
193
+ /**
194
+ * Schema for two-factor secret props.
195
+ */
196
+ export const verifyTwoFactorSecretPropsSchema = secretPropsSchema
197
+ .pick({
198
+ pendingTwoFactorSecret: true,
199
+ twoFactorSecret: true,
200
+ })
201
+ .partial();
202
+ /** Zod schema for JWT session payloads (userId, deviceId, optional OAuth fields). */
203
+ export const sessionSchema = z.object({
204
+ /**
205
+ * The session ID (required for web browser clients).
206
+ */
207
+ id: z.string().optional(),
208
+ /**
209
+ * The user ID.
210
+ */
211
+ userId: z.string(),
212
+ /**
213
+ * The device ID.
214
+ */
215
+ deviceId: z.string(),
216
+ /**
217
+ * The OAuth server if logged in via OAuth.
218
+ */
219
+ oauthServer: z.string().optional(),
220
+ /**
221
+ * The OAuth ID if logged in via OAuth.
222
+ */
223
+ oauthId: z.string().optional(),
224
+ });
225
+ /**
226
+ * Re-export zod for convenience.
227
+ */
228
+ export { z };
229
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAA;AAE/D;;GAEG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,IAAI,CAAA;AAElC;;;;GAIG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,GAAG,GAAG,IAAI,CAAA;AAE3C;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAG1B,OAID,EAAE,EAAE;IACH,OAAO,eAAe,CAAC,MAAM,CAAC;QAC5B;;WAEG;QACH,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;QAC/B;;WAEG;QACH,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;QAC3B;;WAEG;QACH,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;QAC/C;;;;;;;;WAQG;QACH,aAAa,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;QACrC;;;;WAIG;QACH,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;QAC/D;;;;WAIG;QACH,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;QACzD;;WAEG;QACH,gBAAgB,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;QACxC;;WAEG;QACH,WAAW,EAAE,OAAO,EAAE,YAAY;YAChC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,QAAQ,EAAE;YACzC,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;QACzB;;WAEG;QACH,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;QAC9B;;WAEG;QACH,SAAS,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,QAAQ,EAAE;QACvD;;WAEG;QACH,OAAO,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;QACxF;;WAEG;QACH,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;QAC/C;;WAEG;QACH,cAAc,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;KACvC,CAAC,CAAA;AACJ,CAAC,CAAA;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,YAAY,EAAE,CAAA;AAOzC;;GAEG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC,MAAM,CAAC;IACxC;;OAEG;IACH,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE;IACrB;;OAEG;IACH,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACnC;;OAEG;IACH,kBAAkB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACzC;;OAEG;IACH,oBAAoB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;IACtD;;OAEG;IACH,sBAAsB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC7C;;OAEG;IACH,eAAe,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACtC;;;;;OAKG;IACH,qBAAqB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;CACnD,CAAC,CAAA;AAOF;;GAEG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,WAAW,CAAC,IAAI,CAAC;IAChD,QAAQ,EAAE,IAAI;IACd,IAAI,EAAE,IAAI;IACV,KAAK,EAAE,IAAI;CACZ,CAAC,CAAA;AAOF,6DAA6D;AAC7D,MAAM,CAAC,MAAM,uBAAuB,GAAG,iBAAiB,CAAC,IAAI,CAAC;IAC5D,YAAY,EAAE,IAAI;CACnB,CAAC,CAAA;AAOF;;GAEG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,WAAW,CAAC,IAAI,CAAC;IACrD,QAAQ,EAAE,IAAI;IACd,IAAI,EAAE,IAAI;IACV,KAAK,EAAE,IAAI;IACX,aAAa,EAAE,IAAI;IACnB,WAAW,EAAE,IAAI;IACjB,OAAO,EAAE,IAAI;IACb,SAAS,EAAE,IAAI;CAChB,CAAC,CAAA;AAOF,+EAA+E;AAC/E,MAAM,CAAC,MAAM,iBAAiB,GAAG,WAAW;KACzC,IAAI,CAAC;IACJ,QAAQ,EAAE,IAAI;IACd,IAAI,EAAE,IAAI;IACV,KAAK,EAAE,IAAI;IACX,MAAM,EAAE,IAAI;IACZ,GAAG,EAAE,IAAI;CACV,CAAC;KACD,OAAO,EAAE,CAAA;AAOZ,yEAAyE;AACzE,MAAM,CAAC,MAAM,+BAA+B,GAAG,iBAAiB;KAC7D,IAAI,CAAC;IACJ,YAAY,EAAE,IAAI;CACnB,CAAC;KACD,OAAO,EAAE,CAAA;AAOZ,0FAA0F;AAC1F,MAAM,CAAC,MAAM,qBAAqB,GAAG,WAAW;KAC7C,IAAI,CAAC;IACJ,OAAO,EAAE,IAAI;IACb,aAAa,EAAE,IAAI;IACnB,cAAc,EAAE,IAAI;CACrB,CAAC;KACD,OAAO,EAAE,CAAA;AAOZ;;GAEG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,WAAW;KAClD,IAAI,CAAC;IACJ,gBAAgB,EAAE,IAAI;CACvB,CAAC;KACD,OAAO,EAAE,CAAA;AAOZ;;GAEG;AACH,MAAM,CAAC,MAAM,gCAAgC,GAAG,iBAAiB;KAC9D,IAAI,CAAC;IACJ,sBAAsB,EAAE,IAAI;IAC5B,eAAe,EAAE,IAAI;CACtB,CAAC;KACD,OAAO,EAAE,CAAA;AAOZ,qFAAqF;AACrF,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,CAAC,MAAM,CAAC;IACpC;;OAEG;IACH,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACzB;;OAEG;IACH,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE;IAClB;;OAEG;IACH,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE;IACpB;;OAEG;IACH,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAClC;;OAEG;IACH,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAC/B,CAAC,CAAA;AAOF;;GAEG;AACH,OAAO,EAAE,CAAC,EAAE,CAAA"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * User resource secret definitions — self-registered at import time so the
3
+ * runtime secrets registry (`@molecule/api-secrets`) can drive boot-time
4
+ * configuration reports and actionable "not configured" errors.
5
+ *
6
+ * Content is derived MECHANICALLY from this package's mlcl registry secrets
7
+ * entry (label/instructions/setupUrl/example) via the fleet formula, so
8
+ * packages sharing a key register byte-identical definitions and
9
+ * registration order never matters.
10
+ *
11
+ * @module
12
+ */
13
+ import type { SecretDefinition } from '@molecule/api-secrets';
14
+ /** Secret definitions required by the user resource. */
15
+ export declare const resourceUserSecretDefinitions: SecretDefinition[];
16
+ //# sourceMappingURL=secrets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secrets.d.ts","sourceRoot":"","sources":["../src/secrets.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AAG7D,wDAAwD;AACxD,eAAO,MAAM,6BAA6B,EAAE,gBAAgB,EAa3D,CAAA"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * User resource secret definitions — self-registered at import time so the
3
+ * runtime secrets registry (`@molecule/api-secrets`) can drive boot-time
4
+ * configuration reports and actionable "not configured" errors.
5
+ *
6
+ * Content is derived MECHANICALLY from this package's mlcl registry secrets
7
+ * entry (label/instructions/setupUrl/example) via the fleet formula, so
8
+ * packages sharing a key register byte-identical definitions and
9
+ * registration order never matters.
10
+ *
11
+ * @module
12
+ */
13
+ import { registerSecrets } from '@molecule/api-secrets';
14
+ /** Secret definitions required by the user resource. */
15
+ export const resourceUserSecretDefinitions = [
16
+ {
17
+ key: 'JWT_PRIVATE_KEY',
18
+ description: 'JWT signing key (RSA private) — RSA private key (PEM) for signing JWTs; @molecule/api-jwt also self-generates key files on first boot if unset. Auto-generated at scaffold — no manual setup needed.',
19
+ required: true,
20
+ },
21
+ {
22
+ key: 'JWT_PUBLIC_KEY',
23
+ description: 'JWT verification key (RSA public) — RSA public key (PEM) matching JWT_PRIVATE_KEY. Auto-generated at scaffold — no manual setup needed.',
24
+ required: true,
25
+ },
26
+ ];
27
+ registerSecrets(resourceUserSecretDefinitions);
28
+ //# sourceMappingURL=secrets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secrets.js","sourceRoot":"","sources":["../src/secrets.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAEvD,wDAAwD;AACxD,MAAM,CAAC,MAAM,6BAA6B,GAAuB;IAC/D;QACE,GAAG,EAAE,iBAAiB;QACtB,WAAW,EACT,sMAAsM;QACxM,QAAQ,EAAE,IAAI;KACf;IACD;QACE,GAAG,EAAE,gBAAgB;QACrB,WAAW,EACT,yIAAyI;QAC3I,QAAQ,EAAE,IAAI;KACf;CACF,CAAA;AAED,eAAe,CAAC,6BAA6B,CAAC,CAAA"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * User type definitions.
3
+ *
4
+ * Types are inferred from Zod schemas in schema.ts.
5
+ *
6
+ * @module
7
+ */
8
+ import type * as resourceTypes from '@molecule/api-resource/types';
9
+ export type { CreateOAuthProps, CreateProps, CreateSecretProps, Props, SecretProps, Session, UpdatePasswordSecretProps, UpdatePlanProps, UpdateProps, VerifyTwoFactorProps, VerifyTwoFactorSecretProps, } from './schema.js';
10
+ /**
11
+ * An object describing the `user` resource.
12
+ */
13
+ export type Resource<T = unknown> = resourceTypes.Resource<T>;
14
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,KAAK,aAAa,MAAM,8BAA8B,CAAA;AAGlE,YAAY,EACV,gBAAgB,EAChB,WAAW,EACX,iBAAiB,EACjB,KAAK,EACL,WAAW,EACX,OAAO,EACP,yBAAyB,EACzB,eAAe,EACf,WAAW,EACX,oBAAoB,EACpB,0BAA0B,GAC3B,MAAM,aAAa,CAAA;AAEpB;;GAEG;AACH,MAAM,MAAM,QAAQ,CAAC,CAAC,GAAG,OAAO,IAAI,aAAa,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAA"}
package/dist/types.js ADDED
@@ -0,0 +1,9 @@
1
+ /**
2
+ * User type definitions.
3
+ *
4
+ * Types are inferred from Zod schemas in schema.ts.
5
+ *
6
+ * @module
7
+ */
8
+ export {};
9
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
@@ -0,0 +1,19 @@
1
+ import type { Plan } from '@molecule/api-payments';
2
+ import type * as types from '../types.js';
3
+ /**
4
+ * Extended plan info returned by getPlan, including expiration and renewal status.
5
+ */
6
+ interface PlanInfo extends Plan {
7
+ isExpired: boolean;
8
+ expiresAt: string | undefined;
9
+ autoRenews: boolean | undefined;
10
+ }
11
+ /**
12
+ * Get a user's current plan info.
13
+ * Uses the bonded PlanService if available.
14
+ * @param user - The user props (needs `planKey`, `planExpiresAt`, `planAutoRenews`).
15
+ * @returns The user's plan info with expiration status, or `null` if no `PlanService` is bonded.
16
+ */
17
+ export declare const getPlan: (user: types.Props) => Promise<PlanInfo | Plan | null>;
18
+ export {};
19
+ //# sourceMappingURL=getPlan.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getPlan.d.ts","sourceRoot":"","sources":["../../src/utilities/getPlan.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAe,MAAM,wBAAwB,CAAA;AAE/D,OAAO,KAAK,KAAK,KAAK,MAAM,aAAa,CAAA;AAEzC;;GAEG;AACH,UAAU,QAAS,SAAQ,IAAI;IAC7B,SAAS,EAAE,OAAO,CAAA;IAClB,SAAS,EAAE,MAAM,GAAG,SAAS,CAAA;IAC7B,UAAU,EAAE,OAAO,GAAG,SAAS,CAAA;CAChC;AAED;;;;;GAKG;AACH,eAAO,MAAM,OAAO,GAAU,MAAM,KAAK,CAAC,KAAK,KAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,GAAG,IAAI,CAoB/E,CAAA"}
@@ -0,0 +1,26 @@
1
+ import { get } from '@molecule/api-bond';
2
+ /**
3
+ * Get a user's current plan info.
4
+ * Uses the bonded PlanService if available.
5
+ * @param user - The user props (needs `planKey`, `planExpiresAt`, `planAutoRenews`).
6
+ * @returns The user's plan info with expiration status, or `null` if no `PlanService` is bonded.
7
+ */
8
+ export const getPlan = async (user) => {
9
+ const plans = get('plans');
10
+ if (!plans)
11
+ return null;
12
+ const plan = plans.findPlan(user.planKey || '');
13
+ if (plan) {
14
+ const isExpired = user.planExpiresAt
15
+ ? new Date(user.planExpiresAt).getTime() < Date.now()
16
+ : false;
17
+ return {
18
+ ...plan,
19
+ isExpired,
20
+ expiresAt: user.planExpiresAt,
21
+ autoRenews: user.planAutoRenews,
22
+ };
23
+ }
24
+ return plans.getDefaultPlan();
25
+ };
26
+ //# sourceMappingURL=getPlan.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getPlan.js","sourceRoot":"","sources":["../../src/utilities/getPlan.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,oBAAoB,CAAA;AAcxC;;;;;GAKG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,KAAK,EAAE,IAAiB,EAAmC,EAAE;IAClF,MAAM,KAAK,GAAG,GAAG,CAAc,OAAO,CAAC,CAAA;IACvC,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAA;IAEvB,MAAM,IAAI,GAAG,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC,CAAA;IAE/C,IAAI,IAAI,EAAE,CAAC;QACT,MAAM,SAAS,GAAG,IAAI,CAAC,aAAa;YAClC,CAAC,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE;YACrD,CAAC,CAAC,KAAK,CAAA;QAET,OAAO;YACL,GAAG,IAAI;YACP,SAAS;YACT,SAAS,EAAE,IAAI,CAAC,aAAa;YAC7B,UAAU,EAAE,IAAI,CAAC,cAAc;SAChC,CAAA;IACH,CAAC;IAED,OAAO,KAAK,CAAC,cAAc,EAAE,CAAA;AAC/B,CAAC,CAAA"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Hash a password-reset token for storage/lookup.
3
+ *
4
+ * The token emailed to the user is high-entropy random, but storing it VERBATIM
5
+ * in `usersSecrets.passwordResetToken` means any read of that table (a backup, a
6
+ * replica, the shared control-plane cluster) yields a live account-takeover
7
+ * credential. Store `sha256(token)` instead and compare digests: a DB reader
8
+ * gets only the hash, which cannot be replayed against the reset endpoint. The
9
+ * token itself is single-use with a short TTL, so a fast hash (no salt) is
10
+ * sufficient — this is not a password.
11
+ *
12
+ * @param token - The raw reset token (as generated / as received from the user).
13
+ * @returns The lowercase hex SHA-256 digest to store and compare.
14
+ */
15
+ export declare function hashResetToken(token: string): string;
16
+ //# sourceMappingURL=hashResetToken.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hashResetToken.d.ts","sourceRoot":"","sources":["../../src/utilities/hashResetToken.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEpD"}
@@ -0,0 +1,19 @@
1
+ import { createHash } from 'node:crypto';
2
+ /**
3
+ * Hash a password-reset token for storage/lookup.
4
+ *
5
+ * The token emailed to the user is high-entropy random, but storing it VERBATIM
6
+ * in `usersSecrets.passwordResetToken` means any read of that table (a backup, a
7
+ * replica, the shared control-plane cluster) yields a live account-takeover
8
+ * credential. Store `sha256(token)` instead and compare digests: a DB reader
9
+ * gets only the hash, which cannot be replayed against the reset endpoint. The
10
+ * token itself is single-use with a short TTL, so a fast hash (no salt) is
11
+ * sufficient — this is not a password.
12
+ *
13
+ * @param token - The raw reset token (as generated / as received from the user).
14
+ * @returns The lowercase hex SHA-256 digest to store and compare.
15
+ */
16
+ export function hashResetToken(token) {
17
+ return createHash('sha256').update(token).digest('hex');
18
+ }
19
+ //# sourceMappingURL=hashResetToken.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hashResetToken.js","sourceRoot":"","sources":["../../src/utilities/hashResetToken.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAExC;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,cAAc,CAAC,KAAa;IAC1C,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;AACzD,CAAC"}
@@ -0,0 +1,5 @@
1
+ export * from './getPlan.js';
2
+ export * from './invalidateEntitlements.js';
3
+ export * from './normalizeEmail.js';
4
+ export * from './notify.js';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utilities/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAA;AAC5B,cAAc,6BAA6B,CAAA;AAC3C,cAAc,qBAAqB,CAAA;AACnC,cAAc,aAAa,CAAA"}
@@ -0,0 +1,5 @@
1
+ export * from './getPlan.js';
2
+ export * from './invalidateEntitlements.js';
3
+ export * from './normalizeEmail.js';
4
+ export * from './notify.js';
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utilities/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAA;AAC5B,cAAc,6BAA6B,CAAA;AAC3C,cAAc,qBAAqB,CAAA;AACnC,cAAc,aAAa,CAAA"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Invalidates the entitlements plan-key cache for a user after their plan
3
+ * changed (paid upgrade via `verifyPayment`, webhook grant/cancellation via
4
+ * `handlePaymentNotification`, admin `updatePlan`).
5
+ *
6
+ * `@molecule/api-entitlements` caches each user's effective plan key for its
7
+ * TTL (default 5 minutes) on the `getEffectiveTier` hot path. Without this
8
+ * call, a user who just PAID keeps seeing their old (free) tier — and a
9
+ * canceled user keeps premium — until the TTL lapses. The dependency is an
10
+ * OPTIONAL peer: apps that don't use entitlements simply have nothing to
11
+ * invalidate, so the import failure is a documented no-op.
12
+ *
13
+ * @param userId - The user whose cached plan key must be dropped.
14
+ */
15
+ export declare const invalidateEntitlementsCache: (userId: string) => Promise<void>;
16
+ /**
17
+ * Fire-and-forget variant of {@link invalidateEntitlementsCache} for call
18
+ * sites that must not await (or fail) the surrounding response on cache
19
+ * maintenance.
20
+ *
21
+ * @param userId - The user whose cached plan key must be dropped.
22
+ */
23
+ export declare const invalidateEntitlementsCacheSafe: (userId: string) => void;
24
+ //# sourceMappingURL=invalidateEntitlements.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invalidateEntitlements.d.ts","sourceRoot":"","sources":["../../src/utilities/invalidateEntitlements.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,2BAA2B,GAAU,QAAQ,MAAM,KAAG,OAAO,CAAC,IAAI,CAQ9E,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,+BAA+B,GAAI,QAAQ,MAAM,KAAG,IAMhE,CAAA"}
@@ -0,0 +1,41 @@
1
+ import { getLogger } from '@molecule/api-bond';
2
+ const logger = getLogger();
3
+ /**
4
+ * Invalidates the entitlements plan-key cache for a user after their plan
5
+ * changed (paid upgrade via `verifyPayment`, webhook grant/cancellation via
6
+ * `handlePaymentNotification`, admin `updatePlan`).
7
+ *
8
+ * `@molecule/api-entitlements` caches each user's effective plan key for its
9
+ * TTL (default 5 minutes) on the `getEffectiveTier` hot path. Without this
10
+ * call, a user who just PAID keeps seeing their old (free) tier — and a
11
+ * canceled user keeps premium — until the TTL lapses. The dependency is an
12
+ * OPTIONAL peer: apps that don't use entitlements simply have nothing to
13
+ * invalidate, so the import failure is a documented no-op.
14
+ *
15
+ * @param userId - The user whose cached plan key must be dropped.
16
+ */
17
+ export const invalidateEntitlementsCache = async (userId) => {
18
+ try {
19
+ const entitlements = await import('@molecule/api-entitlements');
20
+ entitlements.invalidateCachedPlanKey?.(userId);
21
+ }
22
+ catch (_error) {
23
+ // @molecule/api-entitlements is an optional peer — when the app doesn't
24
+ // use entitlements there is no plan cache to invalidate.
25
+ }
26
+ };
27
+ /**
28
+ * Fire-and-forget variant of {@link invalidateEntitlementsCache} for call
29
+ * sites that must not await (or fail) the surrounding response on cache
30
+ * maintenance.
31
+ *
32
+ * @param userId - The user whose cached plan key must be dropped.
33
+ */
34
+ export const invalidateEntitlementsCacheSafe = (userId) => {
35
+ invalidateEntitlementsCache(userId).catch((error) => {
36
+ // Never let cache maintenance break a payment path — but a failure here
37
+ // means the user sees a stale tier until the TTL lapses, so leave a trace.
38
+ logger.warn('entitlements cache invalidation failed', { userId, error });
39
+ });
40
+ };
41
+ //# sourceMappingURL=invalidateEntitlements.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invalidateEntitlements.js","sourceRoot":"","sources":["../../src/utilities/invalidateEntitlements.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAE9C,MAAM,MAAM,GAAG,SAAS,EAAE,CAAA;AAE1B;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAG,KAAK,EAAE,MAAc,EAAiB,EAAE;IACjF,IAAI,CAAC;QACH,MAAM,YAAY,GAAG,MAAM,MAAM,CAAC,4BAA4B,CAAC,CAAA;QAC/D,YAAY,CAAC,uBAAuB,EAAE,CAAC,MAAM,CAAC,CAAA;IAChD,CAAC;IAAC,OAAO,MAAM,EAAE,CAAC;QAChB,wEAAwE;QACxE,yDAAyD;IAC3D,CAAC;AACH,CAAC,CAAA;AAED;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,+BAA+B,GAAG,CAAC,MAAc,EAAQ,EAAE;IACtE,2BAA2B,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;QAC3D,wEAAwE;QACxE,2EAA2E;QAC3E,MAAM,CAAC,IAAI,CAAC,wCAAwC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAA;IAC1E,CAAC,CAAC,CAAA;AACJ,CAAC,CAAA"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Email normalization for the user resource.
3
+ *
4
+ * @module
5
+ */
6
+ /**
7
+ * Normalizes an email address for storage and lookup so case/whitespace
8
+ * variants of the same mailbox cannot bypass the `email` UNIQUE constraint
9
+ * (e.g. `Foo@Example.com` vs `foo@example.com`).
10
+ *
11
+ * Trims surrounding whitespace and lowercases the whole address. While the
12
+ * local-part is technically case-sensitive per RFC 5321, every mainstream
13
+ * provider treats it case-insensitively, and the account-takeover risk of
14
+ * allowing variants far outweighs that pedantry. Both the local signup
15
+ * (`create`) and OAuth (`logInOAuth`) write paths MUST use this so the stored
16
+ * column and every collision lookup compare apples to apples.
17
+ *
18
+ * @param email - The raw email address (or null/undefined).
19
+ * @returns The normalized email, or `undefined` when the input is empty/blank.
20
+ */
21
+ export declare const normalizeEmail: (email: string | null | undefined) => string | undefined;
22
+ //# sourceMappingURL=normalizeEmail.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalizeEmail.d.ts","sourceRoot":"","sources":["../../src/utilities/normalizeEmail.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,cAAc,GAAI,OAAO,MAAM,GAAG,IAAI,GAAG,SAAS,KAAG,MAAM,GAAG,SAQ1E,CAAA"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Email normalization for the user resource.
3
+ *
4
+ * @module
5
+ */
6
+ /**
7
+ * Normalizes an email address for storage and lookup so case/whitespace
8
+ * variants of the same mailbox cannot bypass the `email` UNIQUE constraint
9
+ * (e.g. `Foo@Example.com` vs `foo@example.com`).
10
+ *
11
+ * Trims surrounding whitespace and lowercases the whole address. While the
12
+ * local-part is technically case-sensitive per RFC 5321, every mainstream
13
+ * provider treats it case-insensitively, and the account-takeover risk of
14
+ * allowing variants far outweighs that pedantry. Both the local signup
15
+ * (`create`) and OAuth (`logInOAuth`) write paths MUST use this so the stored
16
+ * column and every collision lookup compare apples to apples.
17
+ *
18
+ * @param email - The raw email address (or null/undefined).
19
+ * @returns The normalized email, or `undefined` when the input is empty/blank.
20
+ */
21
+ export const normalizeEmail = (email) => {
22
+ if (typeof email !== 'string') {
23
+ return undefined;
24
+ }
25
+ const normalized = email.trim().toLowerCase();
26
+ return normalized.length > 0 ? normalized : undefined;
27
+ };
28
+ //# sourceMappingURL=normalizeEmail.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalizeEmail.js","sourceRoot":"","sources":["../../src/utilities/normalizeEmail.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,KAAgC,EAAsB,EAAE;IACrF,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,SAAS,CAAA;IAClB,CAAC;IAED,MAAM,UAAU,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;IAE7C,OAAO,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,CAAA;AACvD,CAAC,CAAA"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Sends push notifications to all of a user's devices except the current one. Retrieves devices
3
+ * with active push subscriptions via the bonded DeviceService, then sends each notification via
4
+ * `@molecule/api-push-notifications`. Silently no-ops if either dependency is unavailable.
5
+ * @param options - Notification options.
6
+ * @param options.userId - The user whose devices should receive the notification.
7
+ * @param options.deviceId - The current device ID to exclude from notifications.
8
+ * @param options.title - The notification title text.
9
+ * @param options.titleKey - The i18n key for the title (sent as notification data for client-side localization).
10
+ * @param options.body - The notification body text.
11
+ * @param options.bodyKey - The i18n key for the body (sent as notification data for client-side localization).
12
+ */
13
+ export declare const notify: ({ userId, deviceId, title, titleKey, body, bodyKey, }: {
14
+ userId: string;
15
+ deviceId?: string;
16
+ title: string;
17
+ titleKey?: string;
18
+ body: string;
19
+ bodyKey?: string;
20
+ }) => Promise<void>;
21
+ //# sourceMappingURL=notify.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notify.d.ts","sourceRoot":"","sources":["../../src/utilities/notify.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,MAAM,GAAU,uDAO1B;IACD,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB,KAAG,OAAO,CAAC,IAAI,CA6Bf,CAAA"}
@@ -0,0 +1,39 @@
1
+ import { get, getLogger } from '@molecule/api-bond';
2
+ const logger = getLogger();
3
+ /**
4
+ * Sends push notifications to all of a user's devices except the current one. Retrieves devices
5
+ * with active push subscriptions via the bonded DeviceService, then sends each notification via
6
+ * `@molecule/api-push-notifications`. Silently no-ops if either dependency is unavailable.
7
+ * @param options - Notification options.
8
+ * @param options.userId - The user whose devices should receive the notification.
9
+ * @param options.deviceId - The current device ID to exclude from notifications.
10
+ * @param options.title - The notification title text.
11
+ * @param options.titleKey - The i18n key for the title (sent as notification data for client-side localization).
12
+ * @param options.body - The notification body text.
13
+ * @param options.bodyKey - The i18n key for the body (sent as notification data for client-side localization).
14
+ */
15
+ export const notify = async ({ userId, deviceId, title, titleKey, body, bodyKey, }) => {
16
+ try {
17
+ const pushNotifications = await import('@molecule/api-push-notifications').catch(() => null);
18
+ if (!pushNotifications)
19
+ return;
20
+ const deviceService = get('device');
21
+ if (!deviceService)
22
+ return;
23
+ const devices = await deviceService.getWithPushSubscription(userId);
24
+ for (const device of devices) {
25
+ if (device.id === deviceId)
26
+ continue;
27
+ try {
28
+ await pushNotifications.send(device.pushSubscription, { title, options: { body, data: { titleKey, bodyKey } } });
29
+ }
30
+ catch (error) {
31
+ logger.error(`Failed to send push to device ${device.id}:`, error);
32
+ }
33
+ }
34
+ }
35
+ catch (error) {
36
+ logger.error('notify error:', error);
37
+ }
38
+ };
39
+ //# sourceMappingURL=notify.js.map