@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
@@ -0,0 +1,208 @@
1
+ import { v4 as uuid } from 'uuid';
2
+ import { get, getAnalytics, getLogger } from '@molecule/api-bond';
3
+ import { create as storeCreate, findOne } from '@molecule/api-database';
4
+ import { t } from '@molecule/api-i18n';
5
+ import { hash } from '@molecule/api-password';
6
+ import { create as resourceCreate } from '@molecule/api-resource';
7
+ import * as authorization from '../authorization.js';
8
+ import { normalizeEmail } from '../utilities/normalizeEmail.js';
9
+ const analytics = getAnalytics();
10
+ const logger = getLogger();
11
+ /**
12
+ * Creates a user with username and password. Validates username uniqueness and email format,
13
+ * hashes the password into a separate secrets table, creates a session device via the bonded
14
+ * DeviceService, and sets JWT authorization on the response.
15
+ * @param resource - The user resource configuration (name, tableName, schema).
16
+ * @param resource.name - The resource name.
17
+ * @param resource.tableName - The database table name for users.
18
+ * @param resource.schema - The validation schema for user properties.
19
+ * @returns A request handler that responds with `{ statusCode: 201, body: { props } }` on success.
20
+ */
21
+ export const create = ({ name, tableName, schema }) => {
22
+ const createResource = resourceCreate({
23
+ name,
24
+ tableName,
25
+ schema,
26
+ });
27
+ return async (req, res) => {
28
+ let body;
29
+ let props;
30
+ try {
31
+ const { password, deviceName } = req.body;
32
+ body = { password, deviceName };
33
+ // SECURITY (P2E-1): build an explicit allow-list of client-writable signup
34
+ // fields. NEVER spread `req.body` into props — that mass-assigns privileged
35
+ // columns. planKey/planExpiresAt/planAutoRenews/emailVerified/oauthServer/
36
+ // oauthId/oauthData/twoFactorEnabled MUST NOT be settable from a signup
37
+ // body (free paid-tier access + email-confirmation bypass + OAuth
38
+ // account-takeover). Those columns are writable only through their
39
+ // dedicated verified flows: updatePlan -> provider confirmation,
40
+ // verifyPayment, the email-confirmation flow, and logInOAuth (which
41
+ // hand-builds its props from the verified provider, not from req.body).
42
+ // Mirrors update.ts, which likewise constructs props field-by-field.
43
+ props = {};
44
+ if (req.body.username !== undefined) {
45
+ // Limit username to 255 alphanumeric characters.
46
+ props.username = String(req.body.username)
47
+ .replace(/[^a-zA-Z0-9]/g, '')
48
+ .substring(0, 255);
49
+ }
50
+ if (req.body.name !== undefined) {
51
+ props.name = String(req.body.name).substring(0, 255);
52
+ }
53
+ if (!body.password) {
54
+ return {
55
+ statusCode: 400,
56
+ body: {
57
+ error: t('user.error.passwordRequired'),
58
+ errorKey: 'user.error.passwordRequired',
59
+ },
60
+ };
61
+ }
62
+ if (body.password.length > 1024) {
63
+ return {
64
+ statusCode: 400,
65
+ body: {
66
+ error: t('user.error.passwordTooLong', undefined, {
67
+ defaultValue: 'Password must be at most 1024 characters',
68
+ }),
69
+ errorKey: 'user.error.passwordTooLong',
70
+ },
71
+ };
72
+ }
73
+ if (body.password.length < 8) {
74
+ return {
75
+ statusCode: 400,
76
+ body: {
77
+ error: t('user.error.passwordTooShort', undefined, {
78
+ defaultValue: 'Password must be at least 8 characters',
79
+ }),
80
+ errorKey: 'user.error.passwordTooShort',
81
+ },
82
+ };
83
+ }
84
+ if (req.body.email !== undefined) {
85
+ if (req.body.email === '' || req.body.email === null) {
86
+ props.email = null;
87
+ }
88
+ else {
89
+ // Normalize (trim + lowercase) BEFORE validation/storage so case and
90
+ // whitespace variants of the same mailbox can't bypass the `email`
91
+ // UNIQUE constraint or the OAuth collision lookup (which also
92
+ // normalizes). Keep the existing length cap.
93
+ const normalizedEmail = normalizeEmail(String(req.body.email))?.substring(0, 1023);
94
+ props.email = normalizedEmail ?? null;
95
+ // Basic email validation
96
+ if (props.email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(props.email)) {
97
+ return {
98
+ statusCode: 400,
99
+ body: { error: t('user.error.emailInvalid'), errorKey: 'user.error.emailInvalid' },
100
+ };
101
+ }
102
+ }
103
+ }
104
+ }
105
+ catch (err) {
106
+ logger.warn('Failed to parse user create request body', { error: err });
107
+ return {
108
+ statusCode: 400,
109
+ body: { error: t('user.error.badRequest'), errorKey: 'user.error.badRequest' },
110
+ };
111
+ }
112
+ try {
113
+ // Check if username is taken.
114
+ const usernameTaken = Boolean((await findOne(tableName, [
115
+ { field: 'username', operator: '=', value: props.username },
116
+ ]))?.id);
117
+ if (usernameTaken) {
118
+ return {
119
+ statusCode: 403,
120
+ body: {
121
+ error: t('user.error.usernameUnavailable'),
122
+ errorKey: 'user.error.usernameUnavailable',
123
+ },
124
+ };
125
+ }
126
+ if (props.email) {
127
+ const emailTaken = Boolean((await findOne(tableName, [
128
+ { field: 'email', operator: '=', value: props.email },
129
+ ]))?.id);
130
+ if (emailTaken) {
131
+ return {
132
+ statusCode: 403,
133
+ body: {
134
+ error: t('user.error.emailAlreadyRegistered'),
135
+ errorKey: 'user.error.emailAlreadyRegistered',
136
+ },
137
+ };
138
+ }
139
+ }
140
+ }
141
+ catch (error) {
142
+ logger.error(error);
143
+ }
144
+ // Create the id in advance.
145
+ const id = uuid();
146
+ // Create the user first (usersSecrets has a FK to users.id).
147
+ const createdResponse = await createResource({ props, id: id });
148
+ if (!createdResponse || createdResponse.statusCode !== 201) {
149
+ return createdResponse;
150
+ }
151
+ // Hash and store the password.
152
+ try {
153
+ const passwordHash = await hash(body.password);
154
+ const result = await storeCreate(`${tableName}Secrets`, { id, passwordHash });
155
+ if (!result?.affected) {
156
+ throw new Error(`Failed to insert passwordHash into ${tableName}Secrets.`);
157
+ }
158
+ }
159
+ catch (error) {
160
+ logger.error(error);
161
+ return {
162
+ statusCode: 500,
163
+ body: {
164
+ error: t('user.error.failedToHashPassword'),
165
+ errorKey: 'user.error.failedToHashPassword',
166
+ },
167
+ };
168
+ }
169
+ // User and secrets created — now set up the session.
170
+ try {
171
+ const deviceId = await get('device')?.createOrUpdate(id, body.deviceName || 'Unknown');
172
+ if (deviceId) {
173
+ const accessToken = authorization.set(req, res, { userId: id, deviceId });
174
+ analytics
175
+ .track({ name: 'user.signup', userId: id, properties: { method: 'password' } })
176
+ .catch(() => { });
177
+ analytics
178
+ .identify({
179
+ userId: id,
180
+ email: props.email || undefined,
181
+ name: props.name || undefined,
182
+ })
183
+ .catch(() => { });
184
+ // Include accessToken and user in the response body so the frontend
185
+ // auth client can store them (it expects { accessToken, user }).
186
+ const user = createdResponse.body?.props ?? { id, ...props };
187
+ return {
188
+ statusCode: 201,
189
+ body: { props: user, accessToken, user },
190
+ };
191
+ }
192
+ else {
193
+ throw new Error('Failed to create session device.');
194
+ }
195
+ }
196
+ catch (error) {
197
+ logger.error(error);
198
+ return {
199
+ statusCode: 500,
200
+ body: {
201
+ error: t('user.error.failedToCreateSession'),
202
+ errorKey: 'user.error.failedToCreateSession',
203
+ },
204
+ };
205
+ }
206
+ };
207
+ };
208
+ //# sourceMappingURL=create.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create.js","sourceRoot":"","sources":["../../src/handlers/create.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ import type { MoleculeRequest } from '@molecule/api-resource';
2
+ import type * as types from '../types.js';
3
+ /**
4
+ * Deletes a user and their associated data. Removes secrets from the secrets table,
5
+ * devices via the bonded DeviceService, and payment records via the bonded PaymentRecordService
6
+ * before deleting the user row itself.
7
+ * @param resource - The user resource configuration (name, tableName, schema).
8
+ * @param resource.name - The resource name.
9
+ * @param resource.tableName - The database table name for users.
10
+ * @param resource.schema - The validation schema for user properties.
11
+ * @returns A request handler that responds with `{ statusCode: 200, body: { props: { id } } }` on success.
12
+ */
13
+ export declare const del: ({ name: _name, tableName, schema: _schema }: types.Resource) => (req: MoleculeRequest) => Promise<{
14
+ statusCode: number;
15
+ body: {
16
+ error: string;
17
+ errorKey: string;
18
+ props?: undefined;
19
+ };
20
+ } | {
21
+ statusCode: number;
22
+ body: {
23
+ props: {
24
+ id: string;
25
+ };
26
+ error?: undefined;
27
+ errorKey?: undefined;
28
+ };
29
+ }>;
30
+ //# sourceMappingURL=del.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"del.d.ts","sourceRoot":"","sources":["../../src/handlers/del.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAG7D,OAAO,KAAK,KAAK,KAAK,MAAM,aAAa,CAAA;AAKzC;;;;;;;;;GASG;AACH,eAAO,MAAM,GAAG,GAAI,6CAA6C,KAAK,CAAC,QAAQ,MAC/D,KAAK,eAAe;;;;;;;;;;;;;;;;EA0CnC,CAAA"}
@@ -0,0 +1,55 @@
1
+ import { get, getAnalytics, getLogger } from '@molecule/api-bond';
2
+ import { deleteById } from '@molecule/api-database';
3
+ import { t } from '@molecule/api-i18n';
4
+ import { invalidateAllDeviceExistsCache } from '../authorization.js';
5
+ const analytics = getAnalytics();
6
+ const logger = getLogger();
7
+ /**
8
+ * Deletes a user and their associated data. Removes secrets from the secrets table,
9
+ * devices via the bonded DeviceService, and payment records via the bonded PaymentRecordService
10
+ * before deleting the user row itself.
11
+ * @param resource - The user resource configuration (name, tableName, schema).
12
+ * @param resource.name - The resource name.
13
+ * @param resource.tableName - The database table name for users.
14
+ * @param resource.schema - The validation schema for user properties.
15
+ * @returns A request handler that responds with `{ statusCode: 200, body: { props: { id } } }` on success.
16
+ */
17
+ export const del = ({ name: _name, tableName, schema: _schema }) => {
18
+ return async (req) => {
19
+ try {
20
+ const id = req.params.id;
21
+ // Delete secrets.
22
+ await deleteById(`${tableName}Secrets`, id).catch((err) => {
23
+ logger.warn('Failed to delete user secrets', { userId: id, error: err });
24
+ });
25
+ // Delete devices via bond, then evict this process's positive
26
+ // device-exists cache so any in-flight token for a just-deleted device is
27
+ // rejected on its very next request (immediate in-process revocation).
28
+ await get('device')?.deleteByUserId(id);
29
+ invalidateAllDeviceExistsCache();
30
+ // Delete payments via bond.
31
+ await get('paymentRecords')?.deleteByUserId(id);
32
+ // Delete user.
33
+ const result = await deleteById(tableName, id);
34
+ if (!result?.affected) {
35
+ return {
36
+ statusCode: 404,
37
+ body: { error: t('user.error.notFound'), errorKey: 'user.error.notFound' },
38
+ };
39
+ }
40
+ analytics.track({ name: 'user.deleted', userId: id }).catch(() => { });
41
+ return { statusCode: 200, body: { props: { id } } };
42
+ }
43
+ catch (error) {
44
+ logger.error(error);
45
+ return {
46
+ statusCode: 500,
47
+ body: {
48
+ error: t('user.error.failedToDeleteUser'),
49
+ errorKey: 'user.error.failedToDeleteUser',
50
+ },
51
+ };
52
+ }
53
+ };
54
+ };
55
+ //# sourceMappingURL=del.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"del.js","sourceRoot":"","sources":["../../src/handlers/del.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AACjE,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AACnD,OAAO,EAAE,CAAC,EAAE,MAAM,oBAAoB,CAAA;AAItC,OAAO,EAAE,8BAA8B,EAAE,MAAM,qBAAqB,CAAA;AAGpE,MAAM,SAAS,GAAG,YAAY,EAAE,CAAA;AAChC,MAAM,MAAM,GAAG,SAAS,EAAE,CAAA;AAE1B;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,GAAG,GAAG,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAkB,EAAE,EAAE;IACjF,OAAO,KAAK,EAAE,GAAoB,EAAE,EAAE;QACpC,IAAI,CAAC;YACH,MAAM,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC,EAAY,CAAA;YAElC,kBAAkB;YAClB,MAAM,UAAU,CAAC,GAAG,SAAS,SAAS,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;gBACxD,MAAM,CAAC,IAAI,CAAC,+BAA+B,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAA;YAC1E,CAAC,CAAC,CAAA;YAEF,8DAA8D;YAC9D,0EAA0E;YAC1E,uEAAuE;YACvE,MAAM,GAAG,CAAoD,QAAQ,CAAC,EAAE,cAAc,CAAC,EAAE,CAAC,CAAA;YAC1F,8BAA8B,EAAE,CAAA;YAEhC,4BAA4B;YAC5B,MAAM,GAAG,CAAuB,gBAAgB,CAAC,EAAE,cAAc,CAAC,EAAE,CAAC,CAAA;YAErE,eAAe;YACf,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,SAAS,EAAE,EAAE,CAAC,CAAA;YAE9C,IAAI,CAAC,MAAM,EAAE,QAAQ,EAAE,CAAC;gBACtB,OAAO;oBACL,UAAU,EAAE,GAAG;oBACf,IAAI,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,qBAAqB,CAAC,EAAE,QAAQ,EAAE,qBAAqB,EAAE;iBAC3E,CAAA;YACH,CAAC;YAED,SAAS,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAA;YAErE,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAA;QACrD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;YACnB,OAAO;gBACL,UAAU,EAAE,GAAG;gBACf,IAAI,EAAE;oBACJ,KAAK,EAAE,CAAC,CAAC,+BAA+B,CAAC;oBACzC,QAAQ,EAAE,+BAA+B;iBAC1C;aACF,CAAA;QACH,CAAC;IACH,CAAC,CAAA;AACH,CAAC,CAAA"}
@@ -0,0 +1,34 @@
1
+ import type { MoleculeRequest } from '@molecule/api-resource';
2
+ import type * as types from '../types.js';
3
+ /** Request body for password reset initiation. */
4
+ export interface ForgotPasswordRequest extends MoleculeRequest {
5
+ body: {
6
+ email?: string;
7
+ };
8
+ }
9
+ /**
10
+ * Generates a UUID password reset token, stores it in the secrets table, and sends a reset
11
+ * email via the bonded email provider (if available). Always returns 200 regardless of whether
12
+ * the email exists, to prevent user enumeration.
13
+ * @param resource - The user resource configuration (name, tableName, schema).
14
+ * @param resource.name - The resource name.
15
+ * @param resource.tableName - The database table name for users.
16
+ * @param resource.schema - The validation schema for user properties.
17
+ * @returns A request handler that responds with `{ statusCode: 200, body: { success: true } }`.
18
+ */
19
+ export declare const forgotPassword: ({ name: _name, tableName, schema: _schema }: types.Resource) => (req: ForgotPasswordRequest) => Promise<{
20
+ statusCode: number;
21
+ body: {
22
+ error: string;
23
+ errorKey: string;
24
+ success?: undefined;
25
+ };
26
+ } | {
27
+ statusCode: number;
28
+ body: {
29
+ success: boolean;
30
+ error?: undefined;
31
+ errorKey?: undefined;
32
+ };
33
+ }>;
34
+ //# sourceMappingURL=forgotPassword.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"forgotPassword.d.ts","sourceRoot":"","sources":["../../src/handlers/forgotPassword.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAE7D,OAAO,KAAK,KAAK,KAAK,MAAM,aAAa,CAAA;AAOzC,kDAAkD;AAClD,MAAM,WAAW,qBAAsB,SAAQ,eAAe;IAC5D,IAAI,EAAE;QACJ,KAAK,CAAC,EAAE,MAAM,CAAA;KACf,CAAA;CACF;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,cAAc,GAAI,6CAA6C,KAAK,CAAC,QAAQ,MAC1E,KAAK,qBAAqB;;;;;;;;;;;;;;EAmIzC,CAAA"}
@@ -0,0 +1,119 @@
1
+ import crypto from 'crypto';
2
+ import { get, getAnalytics, getLogger } from '@molecule/api-bond';
3
+ import { get as getConfig } from '@molecule/api-config';
4
+ import { findOne, updateById } from '@molecule/api-database';
5
+ import { t } from '@molecule/api-i18n';
6
+ import { hashResetToken } from '../utilities/hashResetToken.js';
7
+ import { normalizeEmail } from '../utilities/normalizeEmail.js';
8
+ const analytics = getAnalytics();
9
+ const logger = getLogger();
10
+ /**
11
+ * Generates a UUID password reset token, stores it in the secrets table, and sends a reset
12
+ * email via the bonded email provider (if available). Always returns 200 regardless of whether
13
+ * the email exists, to prevent user enumeration.
14
+ * @param resource - The user resource configuration (name, tableName, schema).
15
+ * @param resource.name - The resource name.
16
+ * @param resource.tableName - The database table name for users.
17
+ * @param resource.schema - The validation schema for user properties.
18
+ * @returns A request handler that responds with `{ statusCode: 200, body: { success: true } }`.
19
+ */
20
+ export const forgotPassword = ({ name: _name, tableName, schema: _schema }) => {
21
+ return async (req) => {
22
+ try {
23
+ const { email } = req.body;
24
+ if (!email) {
25
+ return {
26
+ statusCode: 400,
27
+ body: { error: t('user.error.emailRequired'), errorKey: 'user.error.emailRequired' },
28
+ };
29
+ }
30
+ // Find user by email — normalized to match how signup stores it. A raw
31
+ // mixed-case lookup silently finds nothing, and the 200-no-reveal response
32
+ // (anti-enumeration) then hides the failure: the user never gets a reset.
33
+ const normalizedEmail = normalizeEmail(email);
34
+ const user = normalizedEmail
35
+ ? await findOne(tableName, [
36
+ { field: 'email', operator: '=', value: normalizedEmail },
37
+ ])
38
+ : null;
39
+ if (!user) {
40
+ // Don't reveal whether the email exists.
41
+ return { statusCode: 200, body: { success: true } };
42
+ }
43
+ // Generate a reset token. The RAW token is emailed to the user; only its
44
+ // sha256 hash is persisted, so a DB read never yields a usable token.
45
+ const passwordResetToken = crypto.randomBytes(32).toString('hex');
46
+ const passwordResetTokenAt = new Date().toISOString();
47
+ await updateById(`${tableName}Secrets`, user.id, {
48
+ passwordResetToken: hashResetToken(passwordResetToken),
49
+ passwordResetTokenAt,
50
+ });
51
+ // Try to send email if email service is bonded.
52
+ try {
53
+ const emailProvider = get('email');
54
+ if (!emailProvider) {
55
+ // The token was persisted but no reset email can go out: with no
56
+ // email bond the flow silently degrades to "token in the DB nobody
57
+ // ever receives", and the 200-no-reveal response hides it from the
58
+ // caller too. Surface it in the logs so an operator sees the reset
59
+ // path is dead instead of discovering it from user complaints.
60
+ logger.warn('Password reset token created but NO email provider is bonded — the reset email was not sent. Bond an email transport (e.g. @molecule/api-emails-sendgrid) for the reset flow to deliver.', { userId: user.id });
61
+ }
62
+ if (emailProvider) {
63
+ const appName = getConfig('APP_NAME', 'App') ?? 'App';
64
+ const siteOrigin = getConfig('SITE_ORIGIN', '');
65
+ const resetUrl = siteOrigin
66
+ ? `${siteOrigin}/reset-password?token=${passwordResetToken}`
67
+ : '';
68
+ // Default the sender to the app's OWN domain (derived from SITE_ORIGIN),
69
+ // never a placeholder like `noreply@example.com`: a provider (e.g. Mailgun)
70
+ // rejects a `from` whose domain isn't its verified sending domain, so a
71
+ // placeholder silently BOUNCES this reset email — a critical flow. An
72
+ // explicit EMAIL_FROM still wins; the localhost fallback is dev-only (prod
73
+ // sets SITE_ORIGIN).
74
+ let defaultFrom = 'no-reply@localhost';
75
+ try {
76
+ if (siteOrigin)
77
+ defaultFrom = `no-reply@${new URL(siteOrigin).hostname}`;
78
+ }
79
+ catch (_error) {
80
+ // Malformed SITE_ORIGIN — keep the localhost fallback.
81
+ }
82
+ await emailProvider.sendMail({
83
+ from: getConfig('EMAIL_FROM', defaultFrom),
84
+ to: email,
85
+ subject: t('user.email.passwordResetSubject', { appName }, { locale: user?.locale || 'en' }),
86
+ subjectKey: 'user.email.passwordResetSubject',
87
+ text: t('user.email.passwordResetText', { token: passwordResetToken }, { locale: user?.locale || 'en' }) +
88
+ '\n\n' +
89
+ (resetUrl
90
+ ? t('user.email.passwordResetLink', { url: resetUrl }, { locale: user?.locale || 'en' })
91
+ : ''),
92
+ textKey: 'user.email.passwordResetText',
93
+ html: t('user.email.passwordResetHtml', { token: passwordResetToken }, { locale: user?.locale || 'en' }) +
94
+ (resetUrl
95
+ ? t('user.email.passwordResetHtmlLink', { url: resetUrl }, { locale: user?.locale || 'en' })
96
+ : ''),
97
+ htmlKey: 'user.email.passwordResetHtml',
98
+ });
99
+ }
100
+ }
101
+ catch (error) {
102
+ logger.error('Failed to send password reset email:', error);
103
+ }
104
+ analytics.track({ name: 'user.password_reset_requested', userId: user.id }).catch(() => { });
105
+ return { statusCode: 200, body: { success: true } };
106
+ }
107
+ catch (error) {
108
+ logger.error(error);
109
+ return {
110
+ statusCode: 500,
111
+ body: {
112
+ error: t('user.error.failedToProcessPasswordReset'),
113
+ errorKey: 'user.error.failedToProcessPasswordReset',
114
+ },
115
+ };
116
+ }
117
+ };
118
+ };
119
+ //# sourceMappingURL=forgotPassword.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"forgotPassword.js","sourceRoot":"","sources":["../../src/handlers/forgotPassword.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ export * from './create.js';
2
+ export * from './del.js';
3
+ export * from './forgotPassword.js';
4
+ export * from './logIn.js';
5
+ export * from './logInOAuth.js';
6
+ export * from './logout.js';
7
+ export * from './oauthAuthorize.js';
8
+ export { handlePaymentNotification, verifyPayment } from './payments/registry.js';
9
+ export * from './read.js';
10
+ export * from './readSelf.js';
11
+ export * from './resetPassword.js';
12
+ export * from './update.js';
13
+ export * from './updatePassword.js';
14
+ export * from './updatePlan.js';
15
+ export * from './verifyTwoFactor.js';
16
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/handlers/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,cAAc,UAAU,CAAA;AACxB,cAAc,qBAAqB,CAAA;AACnC,cAAc,YAAY,CAAA;AAC1B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,aAAa,CAAA;AAC3B,cAAc,qBAAqB,CAAA;AACnC,OAAO,EAAE,yBAAyB,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAA;AACjF,cAAc,WAAW,CAAA;AACzB,cAAc,eAAe,CAAA;AAC7B,cAAc,oBAAoB,CAAA;AAClC,cAAc,aAAa,CAAA;AAC3B,cAAc,qBAAqB,CAAA;AACnC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,sBAAsB,CAAA"}
@@ -0,0 +1,16 @@
1
+ export * from './create.js';
2
+ export * from './del.js';
3
+ export * from './forgotPassword.js';
4
+ export * from './logIn.js';
5
+ export * from './logInOAuth.js';
6
+ export * from './logout.js';
7
+ export * from './oauthAuthorize.js';
8
+ export { handlePaymentNotification, verifyPayment } from './payments/registry.js';
9
+ export * from './read.js';
10
+ export * from './readSelf.js';
11
+ export * from './resetPassword.js';
12
+ export * from './update.js';
13
+ export * from './updatePassword.js';
14
+ export * from './updatePlan.js';
15
+ export * from './verifyTwoFactor.js';
16
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/handlers/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,cAAc,UAAU,CAAA;AACxB,cAAc,qBAAqB,CAAA;AACnC,cAAc,YAAY,CAAA;AAC1B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,aAAa,CAAA;AAC3B,cAAc,qBAAqB,CAAA;AACnC,OAAO,EAAE,yBAAyB,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAA;AACjF,cAAc,WAAW,CAAA;AACzB,cAAc,eAAe,CAAA;AAC7B,cAAc,oBAAoB,CAAA;AAClC,cAAc,aAAa,CAAA;AAC3B,cAAc,qBAAqB,CAAA;AACnC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,sBAAsB,CAAA"}
@@ -0,0 +1,91 @@
1
+ import type { MoleculeRequest, MoleculeResponse } from '@molecule/api-resource';
2
+ import type * as types from '../types.js';
3
+ /** Request body for user login, supporting password, reset token, and 2FA flows. */
4
+ export interface LogInRequest extends MoleculeRequest {
5
+ body: {
6
+ username?: string;
7
+ email?: string;
8
+ password?: string;
9
+ passwordResetToken?: string;
10
+ twoFactorToken?: string;
11
+ deviceName?: string;
12
+ };
13
+ }
14
+ /**
15
+ * Logs in a user by username or email. Supports password authentication, password reset token
16
+ * authentication (1-hour expiry), and optional two-factor verification via `@molecule/api-two-factor`.
17
+ * On success, creates/updates a device via the bonded DeviceService, sets JWT authorization,
18
+ * and notifies other devices about the new login.
19
+ * @param resource - The user resource configuration (name, tableName, schema).
20
+ * @param resource.name - The resource name.
21
+ * @param resource.tableName - The database table name for users.
22
+ * @param resource.schema - The validation schema for user properties.
23
+ * @returns A request handler that responds with `{ statusCode: 200, body: { props } }` on success,
24
+ * or `{ statusCode: 206, body: { twoFactorRequired: true } }` when 2FA is needed.
25
+ */
26
+ export declare const logIn: ({ name: _name, tableName, schema: _schema }: types.Resource) => (req: LogInRequest, res: MoleculeResponse) => Promise<{
27
+ statusCode: number;
28
+ body: {
29
+ error: string;
30
+ errorKey: string;
31
+ twoFactorRequired?: undefined;
32
+ props?: undefined;
33
+ accessToken?: undefined;
34
+ user?: undefined;
35
+ };
36
+ } | {
37
+ statusCode: number;
38
+ body: {
39
+ twoFactorRequired: boolean;
40
+ error?: undefined;
41
+ errorKey?: undefined;
42
+ props?: undefined;
43
+ accessToken?: undefined;
44
+ user?: undefined;
45
+ };
46
+ } | {
47
+ statusCode: number;
48
+ body: {
49
+ props: {
50
+ id: string;
51
+ createdAt: string;
52
+ updatedAt: string;
53
+ username?: string | undefined;
54
+ name?: string | undefined;
55
+ email?: string | null | undefined;
56
+ emailVerified?: boolean | undefined;
57
+ avatar?: string | null | undefined;
58
+ bio?: string | null | undefined;
59
+ twoFactorEnabled?: boolean | undefined;
60
+ oauthServer?: string | undefined;
61
+ oauthId?: string | undefined;
62
+ oauthData?: Record<string, unknown> | undefined;
63
+ planKey?: string | undefined;
64
+ planExpiresAt?: string | undefined;
65
+ planAutoRenews?: boolean | undefined;
66
+ };
67
+ accessToken: string | null;
68
+ user: {
69
+ id: string;
70
+ createdAt: string;
71
+ updatedAt: string;
72
+ username?: string | undefined;
73
+ name?: string | undefined;
74
+ email?: string | null | undefined;
75
+ emailVerified?: boolean | undefined;
76
+ avatar?: string | null | undefined;
77
+ bio?: string | null | undefined;
78
+ twoFactorEnabled?: boolean | undefined;
79
+ oauthServer?: string | undefined;
80
+ oauthId?: string | undefined;
81
+ oauthData?: Record<string, unknown> | undefined;
82
+ planKey?: string | undefined;
83
+ planExpiresAt?: string | undefined;
84
+ planAutoRenews?: boolean | undefined;
85
+ };
86
+ error?: undefined;
87
+ errorKey?: undefined;
88
+ twoFactorRequired?: undefined;
89
+ };
90
+ }>;
91
+ //# sourceMappingURL=logIn.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logIn.d.ts","sourceRoot":"","sources":["../../src/handlers/logIn.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AAG/E,OAAO,KAAK,KAAK,KAAK,MAAM,aAAa,CAAA;AAsBzC,oFAAoF;AACpF,MAAM,WAAW,YAAa,SAAQ,eAAe;IACnD,IAAI,EAAE;QACJ,QAAQ,CAAC,EAAE,MAAM,CAAA;QACjB,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,QAAQ,CAAC,EAAE,MAAM,CAAA;QACjB,kBAAkB,CAAC,EAAE,MAAM,CAAA;QAC3B,cAAc,CAAC,EAAE,MAAM,CAAA;QACvB,UAAU,CAAC,EAAE,MAAM,CAAA;KACpB,CAAA;CACF;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,KAAK,GAAI,6CAA6C,KAAK,CAAC,QAAQ,MACjE,KAAK,YAAY,EAAE,KAAK,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA6MvD,CAAA"}