@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.
- package/LICENSE +115 -0
- package/dist/authorization.d.ts +107 -0
- package/dist/authorization.d.ts.map +1 -0
- package/dist/authorization.js +375 -0
- package/dist/authorization.js.map +1 -0
- package/dist/authorizers/auth.d.ts +8 -0
- package/dist/authorizers/auth.d.ts.map +1 -0
- package/dist/authorizers/auth.js +19 -0
- package/dist/authorizers/auth.js.map +1 -0
- package/dist/authorizers/authSelf.d.ts +14 -0
- package/dist/authorizers/authSelf.d.ts.map +1 -0
- package/dist/authorizers/authSelf.js +31 -0
- package/dist/authorizers/authSelf.js.map +1 -0
- package/dist/authorizers/index.d.ts +5 -0
- package/dist/authorizers/index.d.ts.map +1 -0
- package/dist/authorizers/index.js +5 -0
- package/dist/authorizers/index.js.map +1 -0
- package/dist/authorizers/rateLimit.d.ts +84 -0
- package/dist/authorizers/rateLimit.d.ts.map +1 -0
- package/dist/authorizers/rateLimit.js +144 -0
- package/dist/authorizers/rateLimit.js.map +1 -0
- package/dist/authorizers/requireWebhookAuthenticity.d.ts +23 -0
- package/dist/authorizers/requireWebhookAuthenticity.d.ts.map +1 -0
- package/dist/authorizers/requireWebhookAuthenticity.js +76 -0
- package/dist/authorizers/requireWebhookAuthenticity.js.map +1 -0
- package/dist/browser-guard.d.ts +2 -0
- package/dist/browser-guard.d.ts.map +1 -0
- package/dist/browser-guard.js +19 -0
- package/dist/browser-guard.js.map +1 -0
- package/dist/handlers/create.d.ts +52 -0
- package/dist/handlers/create.d.ts.map +1 -0
- package/dist/handlers/create.js +208 -0
- package/dist/handlers/create.js.map +1 -0
- package/dist/handlers/del.d.ts +30 -0
- package/dist/handlers/del.d.ts.map +1 -0
- package/dist/handlers/del.js +55 -0
- package/dist/handlers/del.js.map +1 -0
- package/dist/handlers/forgotPassword.d.ts +34 -0
- package/dist/handlers/forgotPassword.d.ts.map +1 -0
- package/dist/handlers/forgotPassword.js +119 -0
- package/dist/handlers/forgotPassword.js.map +1 -0
- package/dist/handlers/index.d.ts +16 -0
- package/dist/handlers/index.d.ts.map +1 -0
- package/dist/handlers/index.js +16 -0
- package/dist/handlers/index.js.map +1 -0
- package/dist/handlers/logIn.d.ts +91 -0
- package/dist/handlers/logIn.d.ts.map +1 -0
- package/dist/handlers/logIn.js +223 -0
- package/dist/handlers/logIn.js.map +1 -0
- package/dist/handlers/logInOAuth.d.ts +66 -0
- package/dist/handlers/logInOAuth.d.ts.map +1 -0
- package/dist/handlers/logInOAuth.js +462 -0
- package/dist/handlers/logInOAuth.js.map +1 -0
- package/dist/handlers/logout.d.ts +22 -0
- package/dist/handlers/logout.d.ts.map +1 -0
- package/dist/handlers/logout.js +57 -0
- package/dist/handlers/logout.js.map +1 -0
- package/dist/handlers/oauthAuthorize.d.ts +44 -0
- package/dist/handlers/oauthAuthorize.d.ts.map +1 -0
- package/dist/handlers/oauthAuthorize.js +141 -0
- package/dist/handlers/oauthAuthorize.js.map +1 -0
- package/dist/handlers/payments/handlePaymentNotification.d.ts +28 -0
- package/dist/handlers/payments/handlePaymentNotification.d.ts.map +1 -0
- package/dist/handlers/payments/handlePaymentNotification.js +217 -0
- package/dist/handlers/payments/handlePaymentNotification.js.map +1 -0
- package/dist/handlers/payments/registry.d.ts +13 -0
- package/dist/handlers/payments/registry.d.ts.map +1 -0
- package/dist/handlers/payments/registry.js +13 -0
- package/dist/handlers/payments/registry.js.map +1 -0
- package/dist/handlers/payments/verifyPayment.d.ts +37 -0
- package/dist/handlers/payments/verifyPayment.d.ts.map +1 -0
- package/dist/handlers/payments/verifyPayment.js +383 -0
- package/dist/handlers/payments/verifyPayment.js.map +1 -0
- package/dist/handlers/read.d.ts +17 -0
- package/dist/handlers/read.d.ts.map +1 -0
- package/dist/handlers/read.js +60 -0
- package/dist/handlers/read.js.map +1 -0
- package/dist/handlers/readSelf.d.ts +23 -0
- package/dist/handlers/readSelf.d.ts.map +1 -0
- package/dist/handlers/readSelf.js +77 -0
- package/dist/handlers/readSelf.js.map +1 -0
- package/dist/handlers/resetPassword.d.ts +40 -0
- package/dist/handlers/resetPassword.d.ts.map +1 -0
- package/dist/handlers/resetPassword.js +147 -0
- package/dist/handlers/resetPassword.js.map +1 -0
- package/dist/handlers/update.d.ts +28 -0
- package/dist/handlers/update.d.ts.map +1 -0
- package/dist/handlers/update.js +250 -0
- package/dist/handlers/update.js.map +1 -0
- package/dist/handlers/updatePassword.d.ts +35 -0
- package/dist/handlers/updatePassword.d.ts.map +1 -0
- package/dist/handlers/updatePassword.js +113 -0
- package/dist/handlers/updatePassword.js.map +1 -0
- package/dist/handlers/updatePlan.d.ts +41 -0
- package/dist/handlers/updatePlan.d.ts.map +1 -0
- package/dist/handlers/updatePlan.js +242 -0
- package/dist/handlers/updatePlan.js.map +1 -0
- package/dist/handlers/verifyTwoFactor.d.ts +46 -0
- package/dist/handlers/verifyTwoFactor.d.ts.map +1 -0
- package/dist/handlers/verifyTwoFactor.js +162 -0
- package/dist/handlers/verifyTwoFactor.js.map +1 -0
- package/dist/i18n.d.ts +10 -0
- package/dist/i18n.d.ts.map +1 -0
- package/dist/i18n.js +25 -0
- package/dist/i18n.js.map +1 -0
- package/dist/index.d.ts +88 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +88 -0
- package/dist/index.js.map +1 -0
- package/dist/requestHandlerMap.d.ts +44 -0
- package/dist/requestHandlerMap.d.ts.map +1 -0
- package/dist/requestHandlerMap.js +68 -0
- package/dist/requestHandlerMap.js.map +1 -0
- package/dist/resource.d.ts +22 -0
- package/dist/resource.d.ts.map +1 -0
- package/dist/resource.js +23 -0
- package/dist/resource.js.map +1 -0
- package/dist/routes.d.ts +44 -0
- package/dist/routes.d.ts.map +1 -0
- package/dist/routes.js +121 -0
- package/dist/routes.js.map +1 -0
- package/dist/schema.d.ts +193 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +229 -0
- package/dist/schema.js.map +1 -0
- package/dist/secrets.d.ts +16 -0
- package/dist/secrets.d.ts.map +1 -0
- package/dist/secrets.js +28 -0
- package/dist/secrets.js.map +1 -0
- package/dist/types.d.ts +14 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +9 -0
- package/dist/types.js.map +1 -0
- package/dist/utilities/getPlan.d.ts +19 -0
- package/dist/utilities/getPlan.d.ts.map +1 -0
- package/dist/utilities/getPlan.js +26 -0
- package/dist/utilities/getPlan.js.map +1 -0
- package/dist/utilities/hashResetToken.d.ts +16 -0
- package/dist/utilities/hashResetToken.d.ts.map +1 -0
- package/dist/utilities/hashResetToken.js +19 -0
- package/dist/utilities/hashResetToken.js.map +1 -0
- package/dist/utilities/index.d.ts +5 -0
- package/dist/utilities/index.d.ts.map +1 -0
- package/dist/utilities/index.js +5 -0
- package/dist/utilities/index.js.map +1 -0
- package/dist/utilities/invalidateEntitlements.d.ts +24 -0
- package/dist/utilities/invalidateEntitlements.d.ts.map +1 -0
- package/dist/utilities/invalidateEntitlements.js +41 -0
- package/dist/utilities/invalidateEntitlements.js.map +1 -0
- package/dist/utilities/normalizeEmail.d.ts +22 -0
- package/dist/utilities/normalizeEmail.d.ts.map +1 -0
- package/dist/utilities/normalizeEmail.js +28 -0
- package/dist/utilities/normalizeEmail.js.map +1 -0
- package/dist/utilities/notify.d.ts +21 -0
- package/dist/utilities/notify.d.ts.map +1 -0
- package/dist/utilities/notify.js +39 -0
- package/dist/utilities/notify.js.map +1 -0
- package/dist/utilities/stripSensitiveUserColumns.d.ts +25 -0
- package/dist/utilities/stripSensitiveUserColumns.d.ts.map +1 -0
- package/dist/utilities/stripSensitiveUserColumns.js +43 -0
- package/dist/utilities/stripSensitiveUserColumns.js.map +1 -0
- package/package.json +94 -0
- package/setup/users.sql +22 -0
- 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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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"}
|