@molecule/api-bonds-default-express 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.
@@ -0,0 +1,103 @@
1
+ /**
2
+ * `@molecule/api-bonds-default-express` — default API bond wirings and
3
+ * shared route/handler plumbing for Express-based apps.
4
+ *
5
+ * Two halves:
6
+ *
7
+ * 1. **`setup<Name>()` bond wirings** (40+): one function per default
8
+ * provider (`setupConfigEnv`, `setupDatabasePostgresql`,
9
+ * `setupJwtJsonwebtoken`, `setupEmailsMailgun`, `setupUploadsS3`,
10
+ * `setupRealtimeSocketio`, `setupAiAnthropic`, …) so per-app
11
+ * `api/src/bonds/<name>.ts` files are 1-line re-exports and
12
+ * `bonds/index.ts` just calls them in order.
13
+ * 2. **Shared Express plumbing**: `createBillingRouter` (the fleet's
14
+ * Stripe billing endpoints), the `mountDefaultUserAuthRoutes` /
15
+ * `mountDefaultDeviceRoutes` / other `mountDefault*Routes` helpers,
16
+ * handler guards (`requireAuth`, `requireUser`, `requireOwnership`,
17
+ * `getUserId`, `validationError`, `internalError`), zod param schemas,
18
+ * `trackAuthEvent`, and a `createMigrator` re-export.
19
+ *
20
+ * @example
21
+ * ```typescript
22
+ * // api/src/bonds/index.ts — wire defaults at startup, then validate:
23
+ * import { validateBonds } from '@molecule/api-bond'
24
+ * import {
25
+ * setupConfigEnv,
26
+ * setupDatabasePostgresql,
27
+ * setupEmailsMailgun,
28
+ * setupJwtJsonwebtoken,
29
+ * setupSecretsEnv,
30
+ * } from '@molecule/api-bonds-default-express'
31
+ *
32
+ * async function setupBonds(): Promise<void> {
33
+ * setupConfigEnv()
34
+ * setupSecretsEnv()
35
+ * setupDatabasePostgresql()
36
+ * setupJwtJsonwebtoken()
37
+ * setupEmailsMailgun()
38
+ * validateBonds()
39
+ * }
40
+ * ```
41
+ *
42
+ * @example
43
+ * ```typescript
44
+ * // api/src/routes/billing.ts — the fleet-standard billing endpoints
45
+ * // (POST /checkout, POST /cancel, GET /status, GET /tiers), mounted by
46
+ * // the app router at /billing (the real file default-exports the router):
47
+ * import { createBillingRouter } from '@molecule/api-bonds-default-express'
48
+ *
49
+ * // Your app owns these (typically in api/src/tiers.ts):
50
+ * interface AppLimits { seats: number }
51
+ * const getPricingTiers = () => [] // your tiers, each with a stripePriceId + limits
52
+ * const appPlanKeys = { free: 'free', pro: 'pro' }
53
+ *
54
+ * const billingRouter = createBillingRouter<AppLimits>({
55
+ * getPricingTiers,
56
+ * planKeys: appPlanKeys,
57
+ * })
58
+ * ```
59
+ *
60
+ * @module
61
+ * @remarks
62
+ * - **Development falls back to zero-credential providers; production never
63
+ * does.** When `NODE_ENV !== 'production'` and a provider's required env
64
+ * is missing, the setup wires the capture/local sibling instead and logs
65
+ * the swap: mailgun→emails-capture (`MAILGUN_API_KEY`/`MAILGUN_DOMAIN`),
66
+ * uploads-s3→uploads-filesystem (`AWS_*`), search-meilisearch→
67
+ * search-postgres (`MEILISEARCH_URL`), web-push→push-capture
68
+ * (`VAPID_*`), geolocation-mapbox→nominatim (`MAPBOX_ACCESS_TOKEN`),
69
+ * cache-redis→cache-memory (`REDIS_URL`). In production the credentialed
70
+ * provider is wired regardless — missing env surfaces as loud,
71
+ * actionable 503s and boot-report entries, never a silent provider swap.
72
+ * So "emails don't arrive in dev" usually means they were CAPTURED (read
73
+ * them via the activity/capture tooling), not lost.
74
+ * - **Realtime setups (`setupRealtimeSocketio`, `setupRealtimeWs`,
75
+ * `setupRealtimeSse`) all defer-attach.** Each dynamic-imports its
76
+ * provider's `createProvider({ deferAttach: true })`, calls
77
+ * `setProvider()`, then `registerServerCreatedHook((server) =>
78
+ * provider.attachHttpServer?.(server))` from
79
+ * `@molecule/api-server-default-express` — so the realtime transport
80
+ * shares the API's HTTP server/port once it exists, instead of a
81
+ * standalone port a containerized sandbox / proxied deploy may not
82
+ * expose. Add new realtime bonds by mirroring this pattern exactly.
83
+ * - `createBillingRouter` registers the app's Stripe plan catalogue with
84
+ * `@molecule/api-resource-payment` at construction AND re-registers per
85
+ * checkout (price-id env vars may resolve after startup); webhook
86
+ * handling stays with `@molecule/api-resource-user`'s
87
+ * `handlePaymentNotification`. A paid price whose `planKeys` entry is
88
+ * missing is skipped WITH a warning — that plan could never be granted.
89
+ * - Only wire the setups whose packages your app actually installed —
90
+ * each one imports its provider package (several lazily via dynamic
91
+ * import), so calling a setup for an uninstalled bond fails at that
92
+ * import.
93
+ */
94
+ export * from './billing.js';
95
+ export * from './browser-guard.js';
96
+ export * from './handlers.js';
97
+ export * from './middleware.js';
98
+ export * from './migrate.js';
99
+ export * from './resources.js';
100
+ export * from './routes.js';
101
+ export * from './schemas.js';
102
+ export * from './setup.js';
103
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4FG;AAEH,cAAc,cAAc,CAAA;AAC5B,cAAc,oBAAoB,CAAA;AAClC,cAAc,eAAe,CAAA;AAC7B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,cAAc,CAAA;AAC5B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,aAAa,CAAA;AAC3B,cAAc,cAAc,CAAA;AAC5B,cAAc,YAAY,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,102 @@
1
+ /**
2
+ * `@molecule/api-bonds-default-express` — default API bond wirings and
3
+ * shared route/handler plumbing for Express-based apps.
4
+ *
5
+ * Two halves:
6
+ *
7
+ * 1. **`setup<Name>()` bond wirings** (40+): one function per default
8
+ * provider (`setupConfigEnv`, `setupDatabasePostgresql`,
9
+ * `setupJwtJsonwebtoken`, `setupEmailsMailgun`, `setupUploadsS3`,
10
+ * `setupRealtimeSocketio`, `setupAiAnthropic`, …) so per-app
11
+ * `api/src/bonds/<name>.ts` files are 1-line re-exports and
12
+ * `bonds/index.ts` just calls them in order.
13
+ * 2. **Shared Express plumbing**: `createBillingRouter` (the fleet's
14
+ * Stripe billing endpoints), the `mountDefaultUserAuthRoutes` /
15
+ * `mountDefaultDeviceRoutes` / other `mountDefault*Routes` helpers,
16
+ * handler guards (`requireAuth`, `requireUser`, `requireOwnership`,
17
+ * `getUserId`, `validationError`, `internalError`), zod param schemas,
18
+ * `trackAuthEvent`, and a `createMigrator` re-export.
19
+ *
20
+ * @example
21
+ * ```typescript
22
+ * // api/src/bonds/index.ts — wire defaults at startup, then validate:
23
+ * import { validateBonds } from '@molecule/api-bond'
24
+ * import {
25
+ * setupConfigEnv,
26
+ * setupDatabasePostgresql,
27
+ * setupEmailsMailgun,
28
+ * setupJwtJsonwebtoken,
29
+ * setupSecretsEnv,
30
+ * } from '@molecule/api-bonds-default-express'
31
+ *
32
+ * async function setupBonds(): Promise<void> {
33
+ * setupConfigEnv()
34
+ * setupSecretsEnv()
35
+ * setupDatabasePostgresql()
36
+ * setupJwtJsonwebtoken()
37
+ * setupEmailsMailgun()
38
+ * validateBonds()
39
+ * }
40
+ * ```
41
+ *
42
+ * @example
43
+ * ```typescript
44
+ * // api/src/routes/billing.ts — the fleet-standard billing endpoints
45
+ * // (POST /checkout, POST /cancel, GET /status, GET /tiers), mounted by
46
+ * // the app router at /billing (the real file default-exports the router):
47
+ * import { createBillingRouter } from '@molecule/api-bonds-default-express'
48
+ *
49
+ * // Your app owns these (typically in api/src/tiers.ts):
50
+ * interface AppLimits { seats: number }
51
+ * const getPricingTiers = () => [] // your tiers, each with a stripePriceId + limits
52
+ * const appPlanKeys = { free: 'free', pro: 'pro' }
53
+ *
54
+ * const billingRouter = createBillingRouter<AppLimits>({
55
+ * getPricingTiers,
56
+ * planKeys: appPlanKeys,
57
+ * })
58
+ * ```
59
+ *
60
+ * @module
61
+ * @remarks
62
+ * - **Development falls back to zero-credential providers; production never
63
+ * does.** When `NODE_ENV !== 'production'` and a provider's required env
64
+ * is missing, the setup wires the capture/local sibling instead and logs
65
+ * the swap: mailgun→emails-capture (`MAILGUN_API_KEY`/`MAILGUN_DOMAIN`),
66
+ * uploads-s3→uploads-filesystem (`AWS_*`), search-meilisearch→
67
+ * search-postgres (`MEILISEARCH_URL`), web-push→push-capture
68
+ * (`VAPID_*`), geolocation-mapbox→nominatim (`MAPBOX_ACCESS_TOKEN`),
69
+ * cache-redis→cache-memory (`REDIS_URL`). In production the credentialed
70
+ * provider is wired regardless — missing env surfaces as loud,
71
+ * actionable 503s and boot-report entries, never a silent provider swap.
72
+ * So "emails don't arrive in dev" usually means they were CAPTURED (read
73
+ * them via the activity/capture tooling), not lost.
74
+ * - **Realtime setups (`setupRealtimeSocketio`, `setupRealtimeWs`,
75
+ * `setupRealtimeSse`) all defer-attach.** Each dynamic-imports its
76
+ * provider's `createProvider({ deferAttach: true })`, calls
77
+ * `setProvider()`, then `registerServerCreatedHook((server) =>
78
+ * provider.attachHttpServer?.(server))` from
79
+ * `@molecule/api-server-default-express` — so the realtime transport
80
+ * shares the API's HTTP server/port once it exists, instead of a
81
+ * standalone port a containerized sandbox / proxied deploy may not
82
+ * expose. Add new realtime bonds by mirroring this pattern exactly.
83
+ * - `createBillingRouter` registers the app's Stripe plan catalogue with
84
+ * `@molecule/api-resource-payment` at construction AND re-registers per
85
+ * checkout (price-id env vars may resolve after startup); webhook
86
+ * handling stays with `@molecule/api-resource-user`'s
87
+ * `handlePaymentNotification`. A paid price whose `planKeys` entry is
88
+ * missing is skipped WITH a warning — that plan could never be granted.
89
+ * - Only wire the setups whose packages your app actually installed —
90
+ * each one imports its provider package (several lazily via dynamic
91
+ * import), so calling a setup for an uninstalled bond fails at that
92
+ * import.
93
+ */
94
+ export * from './billing.js';
95
+ export * from './browser-guard.js';
96
+ export * from './handlers.js';
97
+ export * from './middleware.js';
98
+ export * from './migrate.js';
99
+ export * from './resources.js';
100
+ export * from './routes.js';
101
+ export * from './schemas.js';
102
+ export * from './setup.js';
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Default Express middleware helpers used by the molecule fleet's
3
+ * `api/src/middleware/<name>.ts` files.
4
+ *
5
+ * @module
6
+ */
7
+ import type { RequestHandler } from 'express';
8
+ /**
9
+ * Emits an analytics event AND a log entry for an auth-related mutation
10
+ * (signup, login, password reset, plan change, etc.). Logs at info on
11
+ * success and warn on auth failure (4xx) so security signal is captured.
12
+ *
13
+ * Replaces the per-app `api/src/middleware/auth-analytics.ts` shipped
14
+ * by 10 fleet apps.
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * import { trackAuthEvent } from '@molecule/api-bonds-default-express'
19
+ * router.post('/users/log-in', trackAuthEvent('user.login'), User.logIn)
20
+ * ```
21
+ */
22
+ export declare function trackAuthEvent(eventName: string): RequestHandler;
23
+ //# sourceMappingURL=middleware.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAyB,cAAc,EAAY,MAAM,SAAS,CAAA;AAK9E;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,cAAc,CAqBhE"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Default Express middleware helpers used by the molecule fleet's
3
+ * `api/src/middleware/<name>.ts` files.
4
+ *
5
+ * @module
6
+ */
7
+ import { track } from '@molecule/api-analytics';
8
+ import { logger } from '@molecule/api-logger';
9
+ /**
10
+ * Emits an analytics event AND a log entry for an auth-related mutation
11
+ * (signup, login, password reset, plan change, etc.). Logs at info on
12
+ * success and warn on auth failure (4xx) so security signal is captured.
13
+ *
14
+ * Replaces the per-app `api/src/middleware/auth-analytics.ts` shipped
15
+ * by 10 fleet apps.
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * import { trackAuthEvent } from '@molecule/api-bonds-default-express'
20
+ * router.post('/users/log-in', trackAuthEvent('user.login'), User.logIn)
21
+ * ```
22
+ */
23
+ export function trackAuthEvent(eventName) {
24
+ return (req, res, next) => {
25
+ res.on('finish', () => {
26
+ const id = typeof req.params.id === 'string' ? req.params.id : req.params.id?.[0];
27
+ const ok = res.statusCode >= 200 && res.statusCode < 400;
28
+ if (ok) {
29
+ void track({
30
+ name: eventName,
31
+ properties: { method: req.method, userId: id },
32
+ });
33
+ logger.info(`[auth] ${eventName} method=${req.method} userId=${id ?? '-'} status=${res.statusCode}`);
34
+ }
35
+ else if (res.statusCode >= 400 && res.statusCode < 500) {
36
+ logger.warn(`[auth] ${eventName} failed method=${req.method} userId=${id ?? '-'} status=${res.statusCode}`);
37
+ }
38
+ });
39
+ next();
40
+ };
41
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Postgres migration runner factory — re-exported from the PostgreSQL
3
+ * bond.
4
+ *
5
+ * The `createMigrator` implementation lives in
6
+ * `@molecule/api-database-postgresql` because it is Postgres-specific
7
+ * bootstrapping plumbing (`CREATE DATABASE`, the `pg_database` catalog
8
+ * probe, raw `pg.Client` connections). This bond-setup package must not
9
+ * import the concrete `pg` driver directly — it reaches Postgres only
10
+ * through the `@molecule/api-database-postgresql` bond it already
11
+ * peer-depends on. App `scripts/migrate.ts` files keep importing
12
+ * `createMigrator` from here unchanged.
13
+ *
14
+ * @module
15
+ */
16
+ export { createMigrator } from '@molecule/api-database-postgresql';
17
+ //# sourceMappingURL=migrate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrate.d.ts","sourceRoot":"","sources":["../src/migrate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAA"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Postgres migration runner factory — re-exported from the PostgreSQL
3
+ * bond.
4
+ *
5
+ * The `createMigrator` implementation lives in
6
+ * `@molecule/api-database-postgresql` because it is Postgres-specific
7
+ * bootstrapping plumbing (`CREATE DATABASE`, the `pg_database` catalog
8
+ * probe, raw `pg.Client` connections). This bond-setup package must not
9
+ * import the concrete `pg` driver directly — it reaches Postgres only
10
+ * through the `@molecule/api-database-postgresql` bond it already
11
+ * peer-depends on. App `scripts/migrate.ts` files keep importing
12
+ * `createMigrator` from here unchanged.
13
+ *
14
+ * @module
15
+ */
16
+ export { createMigrator } from '@molecule/api-database-postgresql';
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Pre-wired default request handler maps for the user + device
3
+ * resources. Apps that don't need to customize their resource
4
+ * wiring can re-export these directly from
5
+ * `App/Resource/<Name>/index.ts`, eliminating the byte-identical
6
+ * `createRequestHandlerMap(createRequestHandler)` calls that every
7
+ * flagship app shipped.
8
+ *
9
+ * @module
10
+ */
11
+ import { deviceService } from '@molecule/api-resource-device';
12
+ import { authorization } from '@molecule/api-resource-user';
13
+ /** Pre-wired request handler map for `@molecule/api-resource-user`. */
14
+ export declare const userRequestHandlerMap: import("@molecule/api-resource-user").UserRequestHandlerMap;
15
+ /** Pre-wired request handler map for `@molecule/api-resource-device`. */
16
+ export declare const deviceRequestHandlerMap: import("@molecule/api-resource-device").DeviceRequestHandlerMap;
17
+ export { deviceService, authorization as userAuthorization };
18
+ //# sourceMappingURL=resources.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resources.d.ts","sourceRoot":"","sources":["../src/resources.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,EAEL,aAAa,EACd,MAAM,+BAA+B,CAAA;AACtC,OAAO,EACL,aAAa,EAEd,MAAM,6BAA6B,CAAA;AAEpC,uEAAuE;AACvE,eAAO,MAAM,qBAAqB,6DAAoD,CAAA;AAEtF,yEAAyE;AACzE,eAAO,MAAM,uBAAuB,iEAAsD,CAAA;AAE1F,OAAO,EAAE,aAAa,EAAE,aAAa,IAAI,iBAAiB,EAAE,CAAA"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Pre-wired default request handler maps for the user + device
3
+ * resources. Apps that don't need to customize their resource
4
+ * wiring can re-export these directly from
5
+ * `App/Resource/<Name>/index.ts`, eliminating the byte-identical
6
+ * `createRequestHandlerMap(createRequestHandler)` calls that every
7
+ * flagship app shipped.
8
+ *
9
+ * @module
10
+ */
11
+ import { createRequestHandler } from '@molecule/api-resource';
12
+ import { createRequestHandlerMap as createDeviceRequestHandlerMap, deviceService, } from '@molecule/api-resource-device';
13
+ import { authorization, createRequestHandlerMap as createUserRequestHandlerMap, } from '@molecule/api-resource-user';
14
+ /** Pre-wired request handler map for `@molecule/api-resource-user`. */
15
+ export const userRequestHandlerMap = createUserRequestHandlerMap(createRequestHandler);
16
+ /** Pre-wired request handler map for `@molecule/api-resource-device`. */
17
+ export const deviceRequestHandlerMap = createDeviceRequestHandlerMap(createRequestHandler);
18
+ export { deviceService, authorization as userAuthorization };
@@ -0,0 +1,124 @@
1
+ import type { Router } from 'express';
2
+ import type { DeviceRequestHandlerMap } from '@molecule/api-resource-device';
3
+ import type { UserRequestHandlerMap } from '@molecule/api-resource-user';
4
+ /**
5
+ * Composable route-mount helpers for the molecule fleet's default
6
+ * `api/src/App/router.ts`. Apps assemble their router by calling the
7
+ * mount* functions they need — `mountDefaultDeviceRoutes`,
8
+ * `mountDefaultUserAuthRoutes`, etc. — instead of writing 80 lines of
9
+ * `router.METHOD(path, ...handlers)` calls.
10
+ *
11
+ * Each helper takes the router + the corresponding resource handler
12
+ * map (e.g. `mountDefaultDeviceRoutes(router, Device)`). Helpers are
13
+ * additive and can be combined freely.
14
+ *
15
+ * @module
16
+ */
17
+ type DeviceMap = DeviceRequestHandlerMap;
18
+ type UserMap = UserRequestHandlerMap;
19
+ /**
20
+ * Mounts the standard device routes:
21
+ *
22
+ * - `GET /devices/push/public-key` (public — the VAPID public key browsers
23
+ * need for `pushManager.subscribe({ applicationServerKey })`; bond-gated
24
+ * 404/503 when no push provider is bonded/configured)
25
+ * - `GET /devices` (auth+query)
26
+ * - `GET /devices/:id` (authUser+read)
27
+ * - `PATCH /devices/:id` (authUser+update)
28
+ * - `DELETE /devices/:id` (authUser+del)
29
+ */
30
+ export declare function mountDefaultDeviceRoutes(router: Router, device: DeviceMap): void;
31
+ /**
32
+ * Mounts the public auth endpoints:
33
+ *
34
+ * - `POST /users` (create)
35
+ * - `POST /users/log-in` (rateLimitAuth + logIn)
36
+ * - `POST /users/forgot-password` (rateLimitAuth + forgotPassword)
37
+ *
38
+ * The credential-bearing routes are fronted by `user.rateLimitAuth` — the
39
+ * default IP+account brute-force throttle from `@molecule/api-resource-user` —
40
+ * so generated apps are not left with unthrottled password / TOTP-via-login
41
+ * guessing. The limiter degrades open (logs a warning) when no rate-limit
42
+ * provider is bonded, so apps that opt out still boot.
43
+ */
44
+ export declare function mountDefaultUserAuthRoutes(router: Router, user: UserMap): void;
45
+ /**
46
+ * Optional OAuth routes — BOTH halves of the flow:
47
+ *
48
+ * - `GET /users/oauth/:provider` (rateLimitAuth + oauthAuthorize) —
49
+ * initiation: sets the CSRF `oauth_state` + PKCE `oauth_verifier` httpOnly
50
+ * cookies and 302-redirects to the bonded provider's authorization URL.
51
+ * Without this half the state cookie `logInOAuth` validates is never set,
52
+ * so every callback fails 403 (this is exactly how the generated-app fleet
53
+ * shipped an exchange endpoint with no way to start the dance). The GET
54
+ * carries the same `rateLimitAuth` throttle as the POST: it has no body, so
55
+ * only the generous per-IP bucket applies — an abuse ceiling on cookie-mint/
56
+ * redirect flooding that a legitimate login (one GET + one POST) never
57
+ * approaches. A trip is a 429 JSON on a top-level navigation, which is
58
+ * acceptable for that ceiling.
59
+ * - `POST /users/log-in/oauth` (rateLimitAuth + logInOAuth) — callback
60
+ * exchange: verifies state + code with the bonded provider and logs the
61
+ * user in.
62
+ *
63
+ * Only mount when the app wires an oauth bond. Handlers check the bond
64
+ * registry at request time, so an unbonded provider yields a clean 404.
65
+ */
66
+ export declare function mountDefaultUserOAuthLoginRoute(router: Router, user: UserMap): void;
67
+ /**
68
+ * Optional reset-password route: `POST /users/reset-password` (rateLimitAuth +
69
+ * resetPassword). Only mount when the app uses the pkg's resetPassword handler
70
+ * rather than a custom local handler.
71
+ */
72
+ export declare function mountDefaultUserResetPasswordRoute(router: Router, user: UserMap): void;
73
+ /**
74
+ * Mounts the authed-self user CRUD routes:
75
+ *
76
+ * - `GET /users/me` (auth+readSelf) — session restore; MUST precede `/users/:id`
77
+ * - `GET /users/:id` (authSelf+read)
78
+ * - `PATCH /users/:id` (authSelf+update)
79
+ * - `DELETE /users/:id` (authSelf+del)
80
+ */
81
+ export declare function mountDefaultUserCrudRoutes(router: Router, user: UserMap): void;
82
+ /**
83
+ * Mounts password + 2FA security routes:
84
+ *
85
+ * - `PATCH /users/:id/password` (authSelf+updatePassword)
86
+ * - `POST /users/:id/verify-two-factor` (authSelf + rateLimitTwoFactor + verifyTwoFactor)
87
+ *
88
+ * The 2FA verification route carries a stricter limiter (`user.rateLimitTwoFactor`)
89
+ * that temp-locks the second factor per account after consecutive misses.
90
+ */
91
+ export declare function mountDefaultUserSecurityRoutes(router: Router, user: UserMap): void;
92
+ /**
93
+ * Mounts plan/billing routes:
94
+ *
95
+ * - `PATCH /users/:id/plan` (authSelf+updatePlan)
96
+ * - `POST /users/payment-notification/:provider` (requireWebhookAuthenticity+handlePaymentNotification)
97
+ *
98
+ * The notification route is public (providers POST to it), so it is gated by
99
+ * `requireWebhookAuthenticity`: signature-verifying webhook providers (Stripe)
100
+ * pass through, while unsigned server-to-server providers (Apple/Google) require
101
+ * a shared secret — the endpoint is not open by default.
102
+ */
103
+ export declare function mountDefaultUserBillingRoutes(router: Router, user: UserMap): void;
104
+ /**
105
+ * Optional payment-verification routes for apps that support
106
+ * client-driven payment confirmation (Apple/Google receipt verify).
107
+ *
108
+ * Both verbs require `authSelf` ([M3-1]): the handler mutates and returns the
109
+ * `:id` user, so an unauthenticated / cross-user call must not reach it. The
110
+ * permissive global `verifyMiddleware()` never blocks, so per-route `authSelf`
111
+ * is the gate. `authSelf` does NOT break the Stripe Checkout `success_url`
112
+ * callback — that is a top-level browser navigation which carries the
113
+ * `sameSite:'lax'` session cookie — and in-handler customer/checkout-session
114
+ * binding remains as defense-in-depth. This mirrors the hardened declarative
115
+ * route table (`resources/user/src/routes.ts`) and molecule-dev's live router;
116
+ * the fix had not been propagated to this mounter, which the generated-app
117
+ * fleet uses.
118
+ *
119
+ * - `GET /users/:id/verify-payment/:provider` (authSelf+verifyPayment)
120
+ * - `POST /users/:id/verify-payment/:provider` (authSelf+verifyPayment)
121
+ */
122
+ export declare function mountDefaultUserVerifyPaymentRoutes(router: Router, user: UserMap): void;
123
+ export {};
124
+ //# sourceMappingURL=routes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA;AAErC,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,+BAA+B,CAAA;AAC5E,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,6BAA6B,CAAA;AAExE;;;;;;;;;;;;GAYG;AAEH,KAAK,SAAS,GAAG,uBAAuB,CAAA;AAExC,KAAK,OAAO,GAAG,qBAAqB,CAAA;AAEpC;;;;;;;;;;GAUG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,GAAG,IAAI,CAWhF;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,IAAI,CAU9E;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,+BAA+B,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,IAAI,CAMnF;AAED;;;;GAIG;AACH,wBAAgB,kCAAkC,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,IAAI,CAGtF;AAED;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,IAAI,CAW9E;AAED;;;;;;;;GAQG;AACH,wBAAgB,8BAA8B,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,IAAI,CAWlF;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,IAAI,CAQjF;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,mCAAmC,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,IAAI,CAIvF"}
package/dist/routes.js ADDED
@@ -0,0 +1,166 @@
1
+ /**
2
+ * Mounts the standard device routes:
3
+ *
4
+ * - `GET /devices/push/public-key` (public — the VAPID public key browsers
5
+ * need for `pushManager.subscribe({ applicationServerKey })`; bond-gated
6
+ * 404/503 when no push provider is bonded/configured)
7
+ * - `GET /devices` (auth+query)
8
+ * - `GET /devices/:id` (authUser+read)
9
+ * - `PATCH /devices/:id` (authUser+update)
10
+ * - `DELETE /devices/:id` (authUser+del)
11
+ */
12
+ export function mountDefaultDeviceRoutes(router, device) {
13
+ // Mounted before /devices/:id so the literal "push" segment can never be
14
+ // captured as an :id. Guarded like user.readSelf/oauthAuthorize so older
15
+ // handler maps (and test fixtures) without the handler still mount.
16
+ if (device.pushPublicKey) {
17
+ router.get('/devices/push/public-key', device.pushPublicKey);
18
+ }
19
+ router.get('/devices', device.auth, device.query);
20
+ router.get('/devices/:id', device.authUser, device.read);
21
+ router.patch('/devices/:id', device.authUser, device.update);
22
+ router.delete('/devices/:id', device.authUser, device.del);
23
+ }
24
+ /**
25
+ * Mounts the public auth endpoints:
26
+ *
27
+ * - `POST /users` (create)
28
+ * - `POST /users/log-in` (rateLimitAuth + logIn)
29
+ * - `POST /users/forgot-password` (rateLimitAuth + forgotPassword)
30
+ *
31
+ * The credential-bearing routes are fronted by `user.rateLimitAuth` — the
32
+ * default IP+account brute-force throttle from `@molecule/api-resource-user` —
33
+ * so generated apps are not left with unthrottled password / TOTP-via-login
34
+ * guessing. The limiter degrades open (logs a warning) when no rate-limit
35
+ * provider is bonded, so apps that opt out still boot.
36
+ */
37
+ export function mountDefaultUserAuthRoutes(router, user) {
38
+ router.post('/users', user.create);
39
+ router.post('/users/log-in', user.rateLimitAuth, user.logIn);
40
+ // Logout — cookie-authed device revocation + credential-cookie clearing.
41
+ // [M1-1] required so logout clears the httpOnly cookie (else the cookie-based
42
+ // session restore re-logs-in on the next load).
43
+ if (user.logout) {
44
+ router.post('/users/logout', user.auth, user.logout);
45
+ }
46
+ router.post('/users/forgot-password', user.rateLimitAuth, user.forgotPassword);
47
+ }
48
+ /**
49
+ * Optional OAuth routes — BOTH halves of the flow:
50
+ *
51
+ * - `GET /users/oauth/:provider` (rateLimitAuth + oauthAuthorize) —
52
+ * initiation: sets the CSRF `oauth_state` + PKCE `oauth_verifier` httpOnly
53
+ * cookies and 302-redirects to the bonded provider's authorization URL.
54
+ * Without this half the state cookie `logInOAuth` validates is never set,
55
+ * so every callback fails 403 (this is exactly how the generated-app fleet
56
+ * shipped an exchange endpoint with no way to start the dance). The GET
57
+ * carries the same `rateLimitAuth` throttle as the POST: it has no body, so
58
+ * only the generous per-IP bucket applies — an abuse ceiling on cookie-mint/
59
+ * redirect flooding that a legitimate login (one GET + one POST) never
60
+ * approaches. A trip is a 429 JSON on a top-level navigation, which is
61
+ * acceptable for that ceiling.
62
+ * - `POST /users/log-in/oauth` (rateLimitAuth + logInOAuth) — callback
63
+ * exchange: verifies state + code with the bonded provider and logs the
64
+ * user in.
65
+ *
66
+ * Only mount when the app wires an oauth bond. Handlers check the bond
67
+ * registry at request time, so an unbonded provider yields a clean 404.
68
+ */
69
+ export function mountDefaultUserOAuthLoginRoute(router, user) {
70
+ if (user.oauthAuthorize) {
71
+ router.get('/users/oauth/:provider', user.rateLimitAuth, user.oauthAuthorize);
72
+ }
73
+ if (!user.logInOAuth)
74
+ return;
75
+ router.post('/users/log-in/oauth', user.rateLimitAuth, user.logInOAuth);
76
+ }
77
+ /**
78
+ * Optional reset-password route: `POST /users/reset-password` (rateLimitAuth +
79
+ * resetPassword). Only mount when the app uses the pkg's resetPassword handler
80
+ * rather than a custom local handler.
81
+ */
82
+ export function mountDefaultUserResetPasswordRoute(router, user) {
83
+ if (!user.resetPassword)
84
+ return;
85
+ router.post('/users/reset-password', user.rateLimitAuth, user.resetPassword);
86
+ }
87
+ /**
88
+ * Mounts the authed-self user CRUD routes:
89
+ *
90
+ * - `GET /users/me` (auth+readSelf) — session restore; MUST precede `/users/:id`
91
+ * - `GET /users/:id` (authSelf+read)
92
+ * - `PATCH /users/:id` (authSelf+update)
93
+ * - `DELETE /users/:id` (authSelf+del)
94
+ */
95
+ export function mountDefaultUserCrudRoutes(router, user) {
96
+ // Current user — cookie-authed session restore. Registered before `/users/:id`
97
+ // so Express doesn't capture `me` as an `:id`. [M1-1] the app's auth client
98
+ // calls this on init to restore the session from the httpOnly cookie after a
99
+ // full page load (the in-memory bearer token does not survive a reload).
100
+ if (user.readSelf) {
101
+ router.get('/users/me', user.auth, user.readSelf);
102
+ }
103
+ router.get('/users/:id', user.authSelf, user.read);
104
+ router.patch('/users/:id', user.authSelf, user.update);
105
+ router.delete('/users/:id', user.authSelf, user.del);
106
+ }
107
+ /**
108
+ * Mounts password + 2FA security routes:
109
+ *
110
+ * - `PATCH /users/:id/password` (authSelf+updatePassword)
111
+ * - `POST /users/:id/verify-two-factor` (authSelf + rateLimitTwoFactor + verifyTwoFactor)
112
+ *
113
+ * The 2FA verification route carries a stricter limiter (`user.rateLimitTwoFactor`)
114
+ * that temp-locks the second factor per account after consecutive misses.
115
+ */
116
+ export function mountDefaultUserSecurityRoutes(router, user) {
117
+ router.patch('/users/:id/password', user.authSelf, user.updatePassword);
118
+ // POST alias for clients that use the auth-client `changePassword` flow,
119
+ // which historically dispatches POST. Same handler; both verbs accepted.
120
+ router.post('/users/:id/password', user.authSelf, user.updatePassword);
121
+ router.post('/users/:id/verify-two-factor', user.authSelf, user.rateLimitTwoFactor, user.verifyTwoFactor);
122
+ }
123
+ /**
124
+ * Mounts plan/billing routes:
125
+ *
126
+ * - `PATCH /users/:id/plan` (authSelf+updatePlan)
127
+ * - `POST /users/payment-notification/:provider` (requireWebhookAuthenticity+handlePaymentNotification)
128
+ *
129
+ * The notification route is public (providers POST to it), so it is gated by
130
+ * `requireWebhookAuthenticity`: signature-verifying webhook providers (Stripe)
131
+ * pass through, while unsigned server-to-server providers (Apple/Google) require
132
+ * a shared secret — the endpoint is not open by default.
133
+ */
134
+ export function mountDefaultUserBillingRoutes(router, user) {
135
+ router.patch('/users/:id/plan', user.authSelf, user.updatePlan);
136
+ if (user.handlePaymentNotification) {
137
+ const notificationHandlers = user.requireWebhookAuthenticity
138
+ ? [user.requireWebhookAuthenticity, user.handlePaymentNotification]
139
+ : [user.handlePaymentNotification];
140
+ router.post('/users/payment-notification/:provider', ...notificationHandlers);
141
+ }
142
+ }
143
+ /**
144
+ * Optional payment-verification routes for apps that support
145
+ * client-driven payment confirmation (Apple/Google receipt verify).
146
+ *
147
+ * Both verbs require `authSelf` ([M3-1]): the handler mutates and returns the
148
+ * `:id` user, so an unauthenticated / cross-user call must not reach it. The
149
+ * permissive global `verifyMiddleware()` never blocks, so per-route `authSelf`
150
+ * is the gate. `authSelf` does NOT break the Stripe Checkout `success_url`
151
+ * callback — that is a top-level browser navigation which carries the
152
+ * `sameSite:'lax'` session cookie — and in-handler customer/checkout-session
153
+ * binding remains as defense-in-depth. This mirrors the hardened declarative
154
+ * route table (`resources/user/src/routes.ts`) and molecule-dev's live router;
155
+ * the fix had not been propagated to this mounter, which the generated-app
156
+ * fleet uses.
157
+ *
158
+ * - `GET /users/:id/verify-payment/:provider` (authSelf+verifyPayment)
159
+ * - `POST /users/:id/verify-payment/:provider` (authSelf+verifyPayment)
160
+ */
161
+ export function mountDefaultUserVerifyPaymentRoutes(router, user) {
162
+ if (!user.verifyPayment)
163
+ return;
164
+ router.get('/users/:id/verify-payment/:provider', user.authSelf, user.verifyPayment);
165
+ router.post('/users/:id/verify-payment/:provider', user.authSelf, user.verifyPayment);
166
+ }