@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,14 @@
1
+ import type { MoleculeRequestHandler } from '@molecule/api-resource';
2
+ /**
3
+ * Middleware that checks if the authenticated user's ID matches the `:id` route parameter.
4
+ * Ensures users can only access their own resource. Calls `next()` on match or `next('Unauthorized')` otherwise.
5
+ *
6
+ * As a convenience, `:id === 'me'` is treated as the current authenticated
7
+ * user — `req.params.id` is rewritten to the session's userId so downstream
8
+ * handlers receive the real id. This lets pages call `/api/users/me`
9
+ * without needing the user's UUID in scope.
10
+ *
11
+ * @returns An Express-compatible middleware function.
12
+ */
13
+ export declare const authSelf: () => MoleculeRequestHandler;
14
+ //# sourceMappingURL=authSelf.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authSelf.d.ts","sourceRoot":"","sources":["../../src/authorizers/authSelf.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAA;AAEpE;;;;;;;;;;GAUG;AACH,eAAO,MAAM,QAAQ,QAAO,sBAgB3B,CAAA"}
@@ -0,0 +1,31 @@
1
+ import { t } from '@molecule/api-i18n';
2
+ /**
3
+ * Middleware that checks if the authenticated user's ID matches the `:id` route parameter.
4
+ * Ensures users can only access their own resource. Calls `next()` on match or `next('Unauthorized')` otherwise.
5
+ *
6
+ * As a convenience, `:id === 'me'` is treated as the current authenticated
7
+ * user — `req.params.id` is rewritten to the session's userId so downstream
8
+ * handlers receive the real id. This lets pages call `/api/users/me`
9
+ * without needing the user's UUID in scope.
10
+ *
11
+ * @returns An Express-compatible middleware function.
12
+ */
13
+ export const authSelf = () => (req, res, next) => {
14
+ try {
15
+ const { session } = res.locals;
16
+ if (session?.userId) {
17
+ if (req.params.id === 'me') {
18
+ req.params.id = session.userId;
19
+ return next();
20
+ }
21
+ if (session.userId === req.params.id) {
22
+ return next();
23
+ }
24
+ }
25
+ }
26
+ catch (_error) {
27
+ // session read failed — fall through to next('Unauthorized') below, which is the correct safe result
28
+ }
29
+ return next(t('resource.error.unauthorized', undefined, { defaultValue: 'Unauthorized' }));
30
+ };
31
+ //# sourceMappingURL=authSelf.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authSelf.js","sourceRoot":"","sources":["../../src/authorizers/authSelf.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,oBAAoB,CAAA;AAGtC;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,GAA2B,EAAE,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;IACvE,IAAI,CAAC;QACH,MAAM,EAAE,OAAO,EAAE,GAAG,GAAG,CAAC,MAAM,CAAA;QAC9B,IAAI,OAAO,EAAE,MAAM,EAAE,CAAC;YACpB,IAAI,GAAG,CAAC,MAAM,CAAC,EAAE,KAAK,IAAI,EAAE,CAAC;gBAC3B,GAAG,CAAC,MAAM,CAAC,EAAE,GAAG,OAAO,CAAC,MAAM,CAAA;gBAC9B,OAAO,IAAI,EAAE,CAAA;YACf,CAAC;YACD,IAAI,OAAO,CAAC,MAAM,KAAK,GAAG,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;gBACrC,OAAO,IAAI,EAAE,CAAA;YACf,CAAC;QACH,CAAC;IACH,CAAC;IAAC,OAAO,MAAM,EAAE,CAAC;QAChB,qGAAqG;IACvG,CAAC;IACD,OAAO,IAAI,CAAC,CAAC,CAAC,6BAA6B,EAAE,SAAS,EAAE,EAAE,YAAY,EAAE,cAAc,EAAE,CAAC,CAAC,CAAA;AAC5F,CAAC,CAAA"}
@@ -0,0 +1,5 @@
1
+ export * from './auth.js';
2
+ export * from './authSelf.js';
3
+ export * from './rateLimit.js';
4
+ export * from './requireWebhookAuthenticity.js';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/authorizers/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAA;AACzB,cAAc,eAAe,CAAA;AAC7B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,iCAAiC,CAAA"}
@@ -0,0 +1,5 @@
1
+ export * from './auth.js';
2
+ export * from './authSelf.js';
3
+ export * from './rateLimit.js';
4
+ export * from './requireWebhookAuthenticity.js';
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/authorizers/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAA;AACzB,cAAc,eAAe,CAAA;AAC7B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,iCAAiC,CAAA"}
@@ -0,0 +1,84 @@
1
+ import type { MoleculeRequest, MoleculeRequestHandler } from '@molecule/api-resource';
2
+ /**
3
+ * Configuration for {@link rateLimit}.
4
+ */
5
+ export interface RateLimitAuthOptions {
6
+ /**
7
+ * Key namespace so each protected surface gets an isolated bucket
8
+ * (e.g. `'login'`, `'forgot-password'`, `'2fa'`). Buckets never collide
9
+ * across scopes.
10
+ */
11
+ scope: string;
12
+ /** Sliding window in milliseconds (default: 15 minutes). */
13
+ windowMs?: number;
14
+ /**
15
+ * Maximum attempts per window for the per-account bucket before a 429
16
+ * (default: 10). This is the tight brute-force gate; a legitimate user makes
17
+ * only a handful of attempts, so it never trips for them.
18
+ */
19
+ max?: number;
20
+ /**
21
+ * Maximum attempts per window for the per-IP bucket (default: 60). Kept
22
+ * higher than {@link RateLimitAuthOptions.max} so shared public IPs aren't
23
+ * locked out; primarily catches single-source flooding and password-spray
24
+ * across many accounts, and is the only guard when no account is resolvable
25
+ * (e.g. an OAuth login carrying no email).
26
+ */
27
+ ipMax?: number;
28
+ /**
29
+ * Optional extractors for an account identifier (submitted email/username,
30
+ * the target user id, etc.). When one resolves, the request is ALSO bucketed
31
+ * per account — so a distributed attack against a single account is throttled
32
+ * even when the source IP rotates, and the second factor is temp-locked after
33
+ * `max` consecutive misses regardless of IP. Each extractor is tried; every
34
+ * value that resolves contributes a bucket.
35
+ */
36
+ accountFrom?: ReadonlyArray<(req: MoleculeRequest) => string | undefined>;
37
+ }
38
+ /**
39
+ * Creates an authorizer middleware that brute-force-protects an auth endpoint.
40
+ *
41
+ * Consumes a rate-limit token per request, keyed by client IP and — where
42
+ * resolvable — by account identifier, returning HTTP 429 once the low ceiling
43
+ * is exceeded. This closes the unthrottled-auth default that left passwords and
44
+ * the 6-digit TOTP second factor brute-forceable on `mlcl`-generated apps.
45
+ *
46
+ * Availability is preserved over absolute enforcement: if no rate-limit
47
+ * provider is bonded (an app deliberately removed it) or the limiter itself
48
+ * errors, the request is allowed through (with a logged warning/error) so a
49
+ * limiter outage never locks out every legitimate login. Generated apps wire
50
+ * the default in-memory provider via the registry's `alwaysInclude`/
51
+ * `defaultProvider`, so the throttle is active out of the box.
52
+ *
53
+ * @param options - Scope, window, ceiling, and optional account-key extractors.
54
+ * @returns A `MoleculeRequestHandler` to place ahead of the protected handler.
55
+ */
56
+ export declare const rateLimit: (options: RateLimitAuthOptions) => MoleculeRequestHandler;
57
+ /**
58
+ * Account-identifier extractor for the login endpoint: the submitted username
59
+ * or email. Bucketing on this temp-locks a single targeted account after `max`
60
+ * failed attempts even across rotating IPs (which also covers TOTP-via-login
61
+ * brute force, since the login handler verifies the second factor).
62
+ *
63
+ * @param req - The incoming request.
64
+ * @returns The submitted username or email (if present).
65
+ */
66
+ export declare const loginAccountKey: (req: MoleculeRequest) => string | undefined;
67
+ /**
68
+ * Account-identifier extractor for the forgot-password endpoint: the submitted
69
+ * email.
70
+ *
71
+ * @param req - The incoming request.
72
+ * @returns The submitted email (if present).
73
+ */
74
+ export declare const emailAccountKey: (req: MoleculeRequest) => string | undefined;
75
+ /**
76
+ * Account-identifier extractor for the verify-two-factor endpoint: the target
77
+ * user id in the route params. Temp-locks the second factor per account after
78
+ * `max` consecutive misses.
79
+ *
80
+ * @param req - The incoming request.
81
+ * @returns The target user id (if present).
82
+ */
83
+ export declare const paramIdAccountKey: (req: MoleculeRequest) => string | undefined;
84
+ //# sourceMappingURL=rateLimit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rateLimit.d.ts","sourceRoot":"","sources":["../../src/authorizers/rateLimit.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAA;AAmBrF;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC;;;;OAIG;IACH,KAAK,EAAE,MAAM,CAAA;IAEb,4DAA4D;IAC5D,QAAQ,CAAC,EAAE,MAAM,CAAA;IAEjB;;;;OAIG;IACH,GAAG,CAAC,EAAE,MAAM,CAAA;IAEZ;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IAEd;;;;;;;OAOG;IACH,WAAW,CAAC,EAAE,aAAa,CAAC,CAAC,GAAG,EAAE,eAAe,KAAK,MAAM,GAAG,SAAS,CAAC,CAAA;CAC1E;AAeD;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,SAAS,GAAI,SAAS,oBAAoB,KAAG,sBAmEzD,CAAA;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,GAAI,KAAK,eAAe,KAAG,MAAM,GAAG,SAK/D,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,GAAI,KAAK,eAAe,KAAG,MAAM,GAAG,SAG/D,CAAA;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB,GAAI,KAAK,eAAe,KAAG,MAAM,GAAG,SAGjE,CAAA"}
@@ -0,0 +1,144 @@
1
+ import { getLogger } from '@molecule/api-bond';
2
+ import { t } from '@molecule/api-i18n';
3
+ import { configure, consume, hasProvider } from '@molecule/api-rate-limit';
4
+ const logger = getLogger();
5
+ /** Default sliding window for auth endpoints: 15 minutes. */
6
+ const DEFAULT_WINDOW_MS = 15 * 60_000;
7
+ /** Default per-account max attempts per window — a deliberately low ceiling. */
8
+ const DEFAULT_MAX = 10;
9
+ /**
10
+ * Default per-IP max attempts per window. Deliberately more generous than the
11
+ * per-account ceiling so a shared-NAT / office / mobile-carrier IP isn't locked
12
+ * out by many distinct legitimate users — the tight per-account bucket is the
13
+ * real anti-brute-force gate. Still bounds raw flooding and password-spray from
14
+ * a single source.
15
+ */
16
+ const DEFAULT_IP_MAX = 60;
17
+ /**
18
+ * Resolves the client IP for rate-limit bucketing. Prefers the
19
+ * framework-populated `req.ip` (Express honors `trust proxy`, so it is not
20
+ * client-spoofable) and falls back to `'unknown'` so a missing IP still shares
21
+ * a single throttled bucket rather than bypassing the limit.
22
+ *
23
+ * @param req - The incoming request.
24
+ * @returns A stable IP key.
25
+ */
26
+ const resolveIp = (req) => {
27
+ return req.ip ?? 'unknown';
28
+ };
29
+ /**
30
+ * Creates an authorizer middleware that brute-force-protects an auth endpoint.
31
+ *
32
+ * Consumes a rate-limit token per request, keyed by client IP and — where
33
+ * resolvable — by account identifier, returning HTTP 429 once the low ceiling
34
+ * is exceeded. This closes the unthrottled-auth default that left passwords and
35
+ * the 6-digit TOTP second factor brute-forceable on `mlcl`-generated apps.
36
+ *
37
+ * Availability is preserved over absolute enforcement: if no rate-limit
38
+ * provider is bonded (an app deliberately removed it) or the limiter itself
39
+ * errors, the request is allowed through (with a logged warning/error) so a
40
+ * limiter outage never locks out every legitimate login. Generated apps wire
41
+ * the default in-memory provider via the registry's `alwaysInclude`/
42
+ * `defaultProvider`, so the throttle is active out of the box.
43
+ *
44
+ * @param options - Scope, window, ceiling, and optional account-key extractors.
45
+ * @returns A `MoleculeRequestHandler` to place ahead of the protected handler.
46
+ */
47
+ export const rateLimit = (options) => {
48
+ const { scope } = options;
49
+ const windowMs = options.windowMs ?? DEFAULT_WINDOW_MS;
50
+ const max = options.max ?? DEFAULT_MAX;
51
+ const ipMax = options.ipMax ?? Math.max(DEFAULT_IP_MAX, max);
52
+ const accountFrom = options.accountFrom ?? [];
53
+ return async (req, res, next) => {
54
+ if (!hasProvider()) {
55
+ logger.warn(`Rate-limit provider not bonded; auth endpoint "${scope}" is running unthrottled. ` +
56
+ 'Wire @molecule/api-rate-limit-memory (or -redis) to enable brute-force protection.');
57
+ return next();
58
+ }
59
+ try {
60
+ // Each bucket carries its own ceiling: the per-IP bucket is generous (so
61
+ // shared public IPs aren't locked out) while each per-account bucket is
62
+ // tight (the real brute-force gate). `configure` is applied right before
63
+ // each `consume` so the bucket-specific limit is in effect for it.
64
+ const buckets = [
65
+ { key: `auth:${scope}:ip:${resolveIp(req)}`, max: ipMax },
66
+ ...[...new Set(accountFrom.map((extract) => extract(req)).filter(Boolean))].map((value) => ({ key: `auth:${scope}:acct:${value.toLowerCase()}`, max })),
67
+ ];
68
+ let tightest;
69
+ let limited;
70
+ for (const bucket of buckets) {
71
+ configure({ windowMs, max: bucket.max });
72
+ const result = await consume(bucket.key);
73
+ if (!tightest || result.remaining < tightest.remaining) {
74
+ tightest = result;
75
+ }
76
+ if (!result.allowed && (!limited || (result.retryAfter ?? 0) > (limited.retryAfter ?? 0))) {
77
+ limited = result;
78
+ }
79
+ }
80
+ if (tightest) {
81
+ res.setHeader('RateLimit-Limit', String(tightest.total));
82
+ res.setHeader('RateLimit-Remaining', String(tightest.remaining));
83
+ res.setHeader('RateLimit-Reset', String(Math.ceil(tightest.resetAt.getTime() / 1000)));
84
+ }
85
+ if (limited) {
86
+ res.setHeader('Retry-After', String(limited.retryAfter ?? 1));
87
+ res.status(429).json({
88
+ error: t('user.error.tooManyRequests', undefined, {
89
+ defaultValue: 'Too many attempts. Please wait and try again.',
90
+ }),
91
+ errorKey: 'user.error.tooManyRequests',
92
+ });
93
+ return;
94
+ }
95
+ }
96
+ catch (error) {
97
+ // A rate-limit infrastructure failure (e.g. Redis down) must never lock
98
+ // out legitimate users: log and let the request through. The handler
99
+ // still enforces credentials/authorization.
100
+ logger.error(`Rate-limit check failed for auth endpoint "${scope}"`, { error });
101
+ return next();
102
+ }
103
+ return next();
104
+ };
105
+ };
106
+ /**
107
+ * Account-identifier extractor for the login endpoint: the submitted username
108
+ * or email. Bucketing on this temp-locks a single targeted account after `max`
109
+ * failed attempts even across rotating IPs (which also covers TOTP-via-login
110
+ * brute force, since the login handler verifies the second factor).
111
+ *
112
+ * @param req - The incoming request.
113
+ * @returns The submitted username or email (if present).
114
+ */
115
+ export const loginAccountKey = (req) => {
116
+ const body = (req.body ?? {});
117
+ const username = typeof body.username === 'string' ? body.username : undefined;
118
+ const email = typeof body.email === 'string' ? body.email : undefined;
119
+ return username || email;
120
+ };
121
+ /**
122
+ * Account-identifier extractor for the forgot-password endpoint: the submitted
123
+ * email.
124
+ *
125
+ * @param req - The incoming request.
126
+ * @returns The submitted email (if present).
127
+ */
128
+ export const emailAccountKey = (req) => {
129
+ const body = (req.body ?? {});
130
+ return typeof body.email === 'string' ? body.email : undefined;
131
+ };
132
+ /**
133
+ * Account-identifier extractor for the verify-two-factor endpoint: the target
134
+ * user id in the route params. Temp-locks the second factor per account after
135
+ * `max` consecutive misses.
136
+ *
137
+ * @param req - The incoming request.
138
+ * @returns The target user id (if present).
139
+ */
140
+ export const paramIdAccountKey = (req) => {
141
+ const id = req.params?.id;
142
+ return typeof id === 'string' ? id : undefined;
143
+ };
144
+ //# sourceMappingURL=rateLimit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rateLimit.js","sourceRoot":"","sources":["../../src/authorizers/rateLimit.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import type { MoleculeRequestHandler } from '@molecule/api-resource';
2
+ /**
3
+ * Middleware guarding the public `POST /users/payment-notification/:provider`
4
+ * endpoint against forged server-to-server (Apple/Google IAP) notifications.
5
+ *
6
+ * Self-verifying webhook providers (e.g. Stripe) authenticate the payload by
7
+ * cryptographic signature INSIDE the handler (`constructEvent`), so they pass
8
+ * through untouched.
9
+ *
10
+ * Server-to-server providers (Apple/Google RTDN) carry no transport signature —
11
+ * their endpoint is otherwise open to anyone — so a shared secret is REQUIRED by
12
+ * default ("IAP apps are not open by default"). The secret is supplied via the
13
+ * `x-payment-notification-secret` header or `?secret=` query param (configure
14
+ * the provider's notification URL accordingly) and is compared in constant time
15
+ * against `PAYMENT_NOTIFICATION_SECRET`. Deployments that rely solely on the
16
+ * provider's in-handler receipt re-verification can opt out with
17
+ * `PAYMENT_NOTIFICATION_REQUIRE_SECRET=false` — but the secure behavior ships ON
18
+ * by default and is never relaxed based on request presence.
19
+ *
20
+ * @returns An Express-compatible middleware function.
21
+ */
22
+ export declare const requireWebhookAuthenticity: () => MoleculeRequestHandler;
23
+ //# sourceMappingURL=requireWebhookAuthenticity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"requireWebhookAuthenticity.d.ts","sourceRoot":"","sources":["../../src/authorizers/requireWebhookAuthenticity.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAA;AAmBpE;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,0BAA0B,QAAO,sBA2C7C,CAAA"}
@@ -0,0 +1,76 @@
1
+ import crypto from 'node:crypto';
2
+ import { get } from '@molecule/api-bond';
3
+ import { get as getConfig } from '@molecule/api-config';
4
+ import { t } from '@molecule/api-i18n';
5
+ /**
6
+ * Constant-time string comparison. Returns `false` on length mismatch and
7
+ * otherwise uses `crypto.timingSafeEqual`, so the shared-secret check does not
8
+ * leak via comparison timing.
9
+ * @param a - First string.
10
+ * @param b - Second string.
11
+ * @returns `true` if the strings are equal.
12
+ */
13
+ const timingSafeEqualString = (a, b) => {
14
+ const ab = Buffer.from(a);
15
+ const bb = Buffer.from(b);
16
+ if (ab.length !== bb.length) {
17
+ return false;
18
+ }
19
+ return crypto.timingSafeEqual(ab, bb);
20
+ };
21
+ /**
22
+ * Middleware guarding the public `POST /users/payment-notification/:provider`
23
+ * endpoint against forged server-to-server (Apple/Google IAP) notifications.
24
+ *
25
+ * Self-verifying webhook providers (e.g. Stripe) authenticate the payload by
26
+ * cryptographic signature INSIDE the handler (`constructEvent`), so they pass
27
+ * through untouched.
28
+ *
29
+ * Server-to-server providers (Apple/Google RTDN) carry no transport signature —
30
+ * their endpoint is otherwise open to anyone — so a shared secret is REQUIRED by
31
+ * default ("IAP apps are not open by default"). The secret is supplied via the
32
+ * `x-payment-notification-secret` header or `?secret=` query param (configure
33
+ * the provider's notification URL accordingly) and is compared in constant time
34
+ * against `PAYMENT_NOTIFICATION_SECRET`. Deployments that rely solely on the
35
+ * provider's in-handler receipt re-verification can opt out with
36
+ * `PAYMENT_NOTIFICATION_REQUIRE_SECRET=false` — but the secure behavior ships ON
37
+ * by default and is never relaxed based on request presence.
38
+ *
39
+ * @returns An Express-compatible middleware function.
40
+ */
41
+ export const requireWebhookAuthenticity = () => (req, res, next) => {
42
+ try {
43
+ const rawProvider = req.params.provider;
44
+ const providerName = (Array.isArray(rawProvider) ? rawProvider[0] : rawProvider);
45
+ const provider = providerName ? get('payments', providerName) : null;
46
+ // Self-verifying webhook providers (Stripe) verify the signature in the
47
+ // handler — let them through.
48
+ if (provider &&
49
+ (provider.notificationFlow === 'webhook' ||
50
+ (!provider.notificationFlow && provider.handleWebhookEvent))) {
51
+ return next();
52
+ }
53
+ // Explicit opt-out for operators relying on in-provider receipt
54
+ // re-verification (Apple/Google) instead of an edge secret.
55
+ const requireSecret = getConfig('PAYMENT_NOTIFICATION_REQUIRE_SECRET', 'true') !== 'false';
56
+ if (!requireSecret) {
57
+ return next();
58
+ }
59
+ const configured = getConfig('PAYMENT_NOTIFICATION_SECRET', '') || '';
60
+ const headerVal = req.headers['x-payment-notification-secret'];
61
+ const headerSecret = Array.isArray(headerVal) ? headerVal[0] : headerVal;
62
+ const querySecret = typeof req.query?.secret === 'string' ? req.query.secret : undefined;
63
+ const supplied = headerSecret || querySecret;
64
+ if (configured && supplied && timingSafeEqualString(configured, supplied)) {
65
+ return next();
66
+ }
67
+ }
68
+ catch (_error) {
69
+ // Any failure resolving the provider/secret falls through to the safe
70
+ // reject below — fail closed, never open.
71
+ }
72
+ return next(t('user.error.webhookUnauthorized', undefined, {
73
+ defaultValue: 'Unauthorized payment notification',
74
+ }));
75
+ };
76
+ //# sourceMappingURL=requireWebhookAuthenticity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"requireWebhookAuthenticity.js","sourceRoot":"","sources":["../../src/authorizers/requireWebhookAuthenticity.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,aAAa,CAAA;AAEhC,OAAO,EAAE,GAAG,EAAE,MAAM,oBAAoB,CAAA;AACxC,OAAO,EAAE,GAAG,IAAI,SAAS,EAAE,MAAM,sBAAsB,CAAA;AACvD,OAAO,EAAE,CAAC,EAAE,MAAM,oBAAoB,CAAA;AAItC;;;;;;;GAOG;AACH,MAAM,qBAAqB,GAAG,CAAC,CAAS,EAAE,CAAS,EAAW,EAAE;IAC9D,MAAM,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACzB,MAAM,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACzB,IAAI,EAAE,CAAC,MAAM,KAAK,EAAE,CAAC,MAAM,EAAE,CAAC;QAC5B,OAAO,KAAK,CAAA;IACd,CAAC;IACD,OAAO,MAAM,CAAC,eAAe,CAAC,EAAE,EAAE,EAAE,CAAC,CAAA;AACvC,CAAC,CAAA;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,GAA2B,EAAE,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;IACzF,IAAI,CAAC;QACH,MAAM,WAAW,GAAG,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAA;QACvC,MAAM,YAAY,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,CAC3D,CAAA;QACpB,MAAM,QAAQ,GAAG,YAAY,CAAC,CAAC,CAAC,GAAG,CAAkB,UAAU,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;QAErF,wEAAwE;QACxE,8BAA8B;QAC9B,IACE,QAAQ;YACR,CAAC,QAAQ,CAAC,gBAAgB,KAAK,SAAS;gBACtC,CAAC,CAAC,QAAQ,CAAC,gBAAgB,IAAI,QAAQ,CAAC,kBAAkB,CAAC,CAAC,EAC9D,CAAC;YACD,OAAO,IAAI,EAAE,CAAA;QACf,CAAC;QAED,gEAAgE;QAChE,4DAA4D;QAC5D,MAAM,aAAa,GACjB,SAAS,CAAS,qCAAqC,EAAE,MAAM,CAAC,KAAK,OAAO,CAAA;QAC9E,IAAI,CAAC,aAAa,EAAE,CAAC;YACnB,OAAO,IAAI,EAAE,CAAA;QACf,CAAC;QAED,MAAM,UAAU,GAAG,SAAS,CAAS,6BAA6B,EAAE,EAAE,CAAC,IAAI,EAAE,CAAA;QAC7E,MAAM,SAAS,GAAG,GAAG,CAAC,OAAO,CAAC,+BAA+B,CAAC,CAAA;QAC9D,MAAM,YAAY,GAAG,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;QACxE,MAAM,WAAW,GAAG,OAAO,GAAG,CAAC,KAAK,EAAE,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAA;QACxF,MAAM,QAAQ,GAAG,YAAY,IAAI,WAAW,CAAA;QAE5C,IAAI,UAAU,IAAI,QAAQ,IAAI,qBAAqB,CAAC,UAAU,EAAE,QAAQ,CAAC,EAAE,CAAC;YAC1E,OAAO,IAAI,EAAE,CAAA;QACf,CAAC;IACH,CAAC;IAAC,OAAO,MAAM,EAAE,CAAC;QAChB,sEAAsE;QACtE,0CAA0C;IAC5C,CAAC;IACD,OAAO,IAAI,CACT,CAAC,CAAC,gCAAgC,EAAE,SAAS,EAAE;QAC7C,YAAY,EAAE,mCAAmC;KAClD,CAAC,CACH,CAAA;AACH,CAAC,CAAA"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=browser-guard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser-guard.d.ts","sourceRoot":"","sources":["../src/browser-guard.ts"],"names":[],"mappings":"AAuBA,OAAO,EAAE,CAAA"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Browser guard — `@molecule/api-resource-user` is SERVER-ONLY.
3
+ *
4
+ * Generated by scripts/gen-browser-guards.mjs (workspace root) — edit THAT, not this.
5
+ * Evaluating a server package in a browser bundle is always an import-graph mistake
6
+ * (node APIs, secrets); without this guard it surfaces as a cryptic downstream crash
7
+ * ("Buffer is not defined") far from the culprit. Throwing here names the package and
8
+ * the fix at the exact moment the client bundle evaluates it. jsdom tests and SSR are
9
+ * unaffected: the throw requires browser globals AND the absence of a node runtime.
10
+ */
11
+ const g = globalThis;
12
+ if (g.window !== undefined && g.document !== undefined && !g.process?.versions?.node) {
13
+ throw new Error('@molecule/api-resource-user is SERVER-ONLY: it was bundled into browser/client code. Import it only ' +
14
+ 'from server code (a server route/function or your API), or dynamic-import it inside ' +
15
+ 'the server handler — never from components or shared client modules, and never ' +
16
+ 'polyfill Buffer/process to silence this.');
17
+ }
18
+ export {};
19
+ //# sourceMappingURL=browser-guard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser-guard.js","sourceRoot":"","sources":["../src/browser-guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,CAAC,GAAG,UAIT,CAAA;AACD,IAAI,CAAC,CAAC,MAAM,KAAK,SAAS,IAAI,CAAC,CAAC,QAAQ,KAAK,SAAS,IAAI,CAAC,CAAC,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IACrF,MAAM,IAAI,KAAK,CACb,sGAAsG;QACpG,sFAAsF;QACtF,iFAAiF;QACjF,0CAA0C,CAC7C,CAAA;AACH,CAAC"}
@@ -0,0 +1,52 @@
1
+ import type { MoleculeRequest, MoleculeResponse } from '@molecule/api-resource';
2
+ import type * as types from '../types.js';
3
+ /** Request body for user creation, including password and optional device name. */
4
+ export interface CreateRequest extends MoleculeRequest {
5
+ body: types.CreateProps & {
6
+ password?: string;
7
+ deviceName?: string;
8
+ };
9
+ }
10
+ /**
11
+ * Creates a user with username and password. Validates username uniqueness and email format,
12
+ * hashes the password into a separate secrets table, creates a session device via the bonded
13
+ * DeviceService, and sets JWT authorization on the response.
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: 201, body: { props } }` on success.
19
+ */
20
+ export declare const create: ({ name, tableName, schema }: types.Resource) => (req: CreateRequest, res: MoleculeResponse) => Promise<{
21
+ statusCode: number;
22
+ body: {
23
+ error: string;
24
+ errorKey: string;
25
+ props?: undefined;
26
+ };
27
+ } | {
28
+ statusCode: number;
29
+ body: {
30
+ props: import("@molecule/api-resource/types").Props & Record<string, unknown>;
31
+ error?: undefined;
32
+ errorKey?: undefined;
33
+ };
34
+ } | {
35
+ statusCode: number;
36
+ body: {
37
+ props: (import("@molecule/api-resource/types").Props & Record<string, unknown>) | {
38
+ username?: string | undefined;
39
+ name?: string | undefined;
40
+ email?: string | null | undefined;
41
+ id: string;
42
+ };
43
+ accessToken: string | null;
44
+ user: (import("@molecule/api-resource/types").Props & Record<string, unknown>) | {
45
+ username?: string | undefined;
46
+ name?: string | undefined;
47
+ email?: string | null | undefined;
48
+ id: string;
49
+ };
50
+ };
51
+ }>;
52
+ //# sourceMappingURL=create.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../src/handlers/create.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AAI/E,OAAO,KAAK,KAAK,KAAK,MAAM,aAAa,CAAA;AAMzC,mFAAmF;AACnF,MAAM,WAAW,aAAc,SAAQ,eAAe;IACpD,IAAI,EAAE,KAAK,CAAC,WAAW,GAAG;QACxB,QAAQ,CAAC,EAAE,MAAM,CAAA;QACjB,UAAU,CAAC,EAAE,MAAM,CAAA;KACpB,CAAA;CACF;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,MAAM,GAAI,6BAA6B,KAAK,CAAC,QAAQ,MAOlD,KAAK,aAAa,EAAE,KAAK,gBAAgB;;;;;aAAf,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;EA4M1C,CAAA"}