@awesome-lang-auth/node 1.10.1 → 1.10.3
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/README.detailed.md +21 -5
- package/dist/interfaces/session-store.interface.d.ts +5 -0
- package/dist/interfaces/session-store.interface.d.ts.map +1 -1
- package/dist/models/auth-config.model.d.ts +16 -1
- package/dist/models/auth-config.model.d.ts.map +1 -1
- package/dist/models/session.model.d.ts +2 -0
- package/dist/models/session.model.d.ts.map +1 -1
- package/dist/router/admin.router.d.ts.map +1 -1
- package/dist/router/admin.router.js +47 -23
- package/dist/router/admin.router.js.map +1 -1
- package/dist/router/auth.router.d.ts.map +1 -1
- package/dist/router/auth.router.js +215 -23
- package/dist/router/auth.router.js.map +1 -1
- package/dist/router/openapi.d.ts.map +1 -1
- package/dist/router/openapi.js +15 -3
- package/dist/router/openapi.js.map +1 -1
- package/dist/services/token.service.d.ts.map +1 -1
- package/dist/services/token.service.js +9 -2
- package/dist/services/token.service.js.map +1 -1
- package/package.json +1 -1
package/README.detailed.md
CHANGED
|
@@ -110,6 +110,7 @@ export class MyUserStore implements IUserStore {
|
|
|
110
110
|
|
|
111
111
|
// ---- Required: token field updates -----------------------------------------------
|
|
112
112
|
|
|
113
|
+
/** Single-device fallback when no ISessionStore is configured. With a sessionStore, tokens are per-session and not written here. */
|
|
113
114
|
async updateRefreshToken(userId: string, token: string | null, expiry: Date | null): Promise<void> { /* ... */ }
|
|
114
115
|
async updateResetToken(userId: string, token: string | null, expiry: Date | null): Promise<void> { /* ... */ }
|
|
115
116
|
async updatePassword(userId: string, hashedPassword: string): Promise<void> { /* ... */ }
|
|
@@ -430,7 +431,7 @@ If they are on **completely different domains**:
|
|
|
430
431
|
import { AuthConfig } from '@awesome-lang-auth/node';
|
|
431
432
|
|
|
432
433
|
const config: AuthConfig = {
|
|
433
|
-
// Required
|
|
434
|
+
// Required (refreshTokenSecret must differ from accessTokenSecret when a sessionStore is configured)
|
|
434
435
|
accessTokenSecret: process.env.ACCESS_TOKEN_SECRET!,
|
|
435
436
|
refreshTokenSecret: process.env.REFRESH_TOKEN_SECRET!,
|
|
436
437
|
|
|
@@ -728,8 +729,18 @@ app.use('/auth', auth.router({
|
|
|
728
729
|
When `findOrCreateUser` throws an `AuthError` with code `'OAUTH_ACCOUNT_CONFLICT'`, the built-in OAuth callback automatically redirects to:
|
|
729
730
|
|
|
730
731
|
```
|
|
731
|
-
{siteUrl}/
|
|
732
|
+
{siteUrl}{apiPrefix}/ui/account-conflict?provider=google&code=OAUTH_ACCOUNT_CONFLICT&email=user%40example.com
|
|
732
733
|
```
|
|
734
|
+
(or `{siteUrl}{apiPrefix}/account-conflict?...` when `ui.enabled` is false; `apiPrefix` defaults to `/auth`).
|
|
735
|
+
|
|
736
|
+
Note that `return_path` is ignored on account conflict redirects to prevent path pollution. If `allowedOrigins` is empty, any origin from state passes (same as `resolveOAuthRedirect`). (Note: a separate follow-up issue tracks the 2FA redirect path `${redirectTo}/auth/2fa?tempToken=...`).
|
|
737
|
+
|
|
738
|
+
#### OAuth State Nonce CSRF Protection
|
|
739
|
+
|
|
740
|
+
To protect against Login CSRF attacks where an attacker tricks a victim's browser into completing an OAuth callback with the attacker's authorization code:
|
|
741
|
+
1. `GET /auth/oauth/<provider>` generates a secure random nonce, encodes it in the `state` parameter, and sets an `HttpOnly`, `SameSite=Lax` cookie (`oauth_nonce_<provider>` / `oauth_state`, valid for 10 minutes) scoped to the strategy callback path.
|
|
742
|
+
2. `GET /auth/oauth/<provider>/callback` strictly validates that the cookie is present and that its nonce matches the nonce in the `state` query parameter using constant-time comparison (`crypto.timingSafeEqual`).
|
|
743
|
+
3. If the cookie is missing, `state` is missing, or the nonces do not match, the callback rejects the request immediately with `400 {"error":"Invalid OAuth state","code":"INVALID_OAUTH_STATE"}` and never invokes `handleCallback`.
|
|
733
744
|
|
|
734
745
|
When you also attach `{ email, providerAccountId }` to the thrown `AuthError`’s `data` field **and** provide a `pendingLinkStore` in `RouterOptions`, the library stashes the conflicting provider details automatically so the front-end can drive the full conflict-resolution flow without any custom server routes:
|
|
735
746
|
|
|
@@ -777,7 +788,7 @@ app.use('/auth', createAuthRouter(userStore, config, {
|
|
|
777
788
|
**End-to-end unauthenticated conflict-linking flow:**
|
|
778
789
|
|
|
779
790
|
1. User tries to sign in with Google; `findOrCreateUser` detects the email already belongs to an existing account and throws `AuthError('...', 'OAUTH_ACCOUNT_CONFLICT', 409, { email, providerAccountId })`.
|
|
780
|
-
2. Library calls `pendingLinkStore.stash(email, 'google', providerAccountId)` and redirects the browser to `{siteUrl}/
|
|
791
|
+
2. Library calls `pendingLinkStore.stash(email, 'google', providerAccountId)` and redirects the browser to `{siteUrl}{apiPrefix}/ui/account-conflict?provider=google&code=OAUTH_ACCOUNT_CONFLICT&email=user%40example.com` (or `{siteUrl}{apiPrefix}/account-conflict?...` if UI is disabled).
|
|
781
792
|
3. Frontend prompts the user to verify ownership (e.g. sends a magic link / password check). Once verified, the front-end has a `linkToken` from `POST /auth/link-request`.
|
|
782
793
|
4. Frontend calls `POST /auth/link-verify` with `{ token, loginAfterLinking: true }`. The library retrieves the stashed `providerAccountId`, links the account, clears the stash, and returns a full session.
|
|
783
794
|
|
|
@@ -1812,7 +1823,7 @@ The `X-Auth-Strategy: bearer` header is respected by all token-issuing endpoints
|
|
|
1812
1823
|
|
|
1813
1824
|
### Logout (bearer)
|
|
1814
1825
|
|
|
1815
|
-
`POST /auth/logout` ends the session named by the `Authorization: Bearer` access token and/or by a `refreshToken` in the JSON body (as for `/auth/refresh`):
|
|
1826
|
+
`POST /auth/logout` ends the session named by the `Authorization: Bearer` access token and/or by a `refreshToken` in the JSON body (as for `/auth/refresh`): with a `sessionStore`, each session refreshes independently and logout revokes the specific session while its session exists in the store without invalidating other devices. Without a `sessionStore`, single-device fallback applies where a refresh token in the body counts only while it is the user's current one. Send both when you have them; an expired access token alone cannot identify the session.
|
|
1816
1827
|
|
|
1817
1828
|
```typescript
|
|
1818
1829
|
await fetch('/auth/logout', {
|
|
@@ -2953,6 +2964,11 @@ Unauthenticated requests get `401 { "error": "Unauthorized" }`, whatever their `
|
|
|
2953
2964
|
|
|
2954
2965
|
> **Security note:** By configuring an `accessPolicy` (e.g., `'first-user'`, `'is-admin-flag'`) and `jwtSecret`, the Admin UI requests a session. When an unauthenticated browser opens the panel (`GET <apiPrefix>/admin/`), it is redirected to `${loginPath}?redirect=<URL-encoded admin path>` if `loginPath` is set; otherwise the panel shows its own sign-in form. For further security in production, mount the admin router behind a VPN or IP allow-list.
|
|
2955
2966
|
|
|
2967
|
+
> **Input robustness:**
|
|
2968
|
+
> - Query parameters `limit`, `offset`, and `filter` in admin list endpoints accept a single string value. If a parameter is repeated (e.g. `?filter=a&filter=b`), the first value is used. Invalid or non-numeric values fall back to defaults (`offset: 0`, `limit: 20` or endpoint default).
|
|
2969
|
+
> - Cookies with malformed percent-encoding (`URIError`) are safely kept raw and do not cause 500 errors.
|
|
2970
|
+
> - Endpoints expecting a JSON body safely handle missing bodies or non-JSON payloads (`400` with descriptive error instead of `500`).
|
|
2971
|
+
|
|
2956
2972
|
## RouterOptions
|
|
2957
2973
|
|
|
2958
2974
|
All options passed to `auth.router(options)` (or `createAuthRouter(store, config, options)`):
|
|
@@ -3143,7 +3159,7 @@ buildTokenPayload: async (user) => ({
|
|
|
3143
3159
|
|
|
3144
3160
|
You can control the performance/security trade-off via the `session.checkOn` option (with an `ISessionStore` passed to the router):
|
|
3145
3161
|
|
|
3146
|
-
- `none`: Purely stateless.
|
|
3162
|
+
- `none`: Purely stateless for access-token verification. Note that `POST /refresh` always validates session existence in the store even when `checkOn: 'none'`; a deleted or revoked session can no longer refresh (`401 SESSION_REVOKED`).
|
|
3147
3163
|
- `refresh` (default): Validates the session only when a new Access Token is requested. Fast, and ensures that once a session is revoked, the user cannot get new tokens.
|
|
3148
3164
|
- `allcalls`: Validates the session ID on **every single request** via middleware: the auth router's own protected routes (`/me`, `/sessions`, `/change-password`, ...) and `auth.middleware()` when it has a store (`auth.middleware({ sessionStore })`, or a call made after `auth.router({ sessionStore })`). A revoked session gets `401 SESSION_REVOKED` on the next call. Highest security, handles instant "kill-switch" revocation. Recommended with high-performance stores (Redis/In-Memory).
|
|
3149
3165
|
|
|
@@ -88,5 +88,10 @@ export interface ISessionStore {
|
|
|
88
88
|
* @returns The number of sessions that were deleted.
|
|
89
89
|
*/
|
|
90
90
|
deleteExpiredSessions?(): Promise<number>;
|
|
91
|
+
/**
|
|
92
|
+
* Optional: update the stored refresh token hash for an existing session.
|
|
93
|
+
* Called during token refresh and session rotation.
|
|
94
|
+
*/
|
|
95
|
+
updateSessionRefreshTokenHash?(sessionHandle: string, refreshTokenHash: string): Promise<void>;
|
|
91
96
|
}
|
|
92
97
|
//# sourceMappingURL=session-store.interface.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-store.interface.d.ts","sourceRoot":"","sources":["../../src/interfaces/session-store.interface.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAEtD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,MAAM,WAAW,aAAa;IAC5B;;OAEG;IACH,aAAa,CACX,IAAI,EAAE,IAAI,CAAC,WAAW,EAAE,eAAe,CAAC,GACvC,OAAO,CAAC,WAAW,CAAC,CAAC;IAExB;;;OAGG;IACH,UAAU,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IAE/D;;;;OAIG;IACH,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;IAE9E;;;OAGG;IACH,uBAAuB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE9D;;;;;;OAMG;IACH,aAAa,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEpD;;;OAGG;IACH,wBAAwB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE3E;;;;;;OAMG;IACH,cAAc,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;IAEvE;;;;;;OAMG;IACH,qBAAqB,CAAC,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"session-store.interface.d.ts","sourceRoot":"","sources":["../../src/interfaces/session-store.interface.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAEtD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,MAAM,WAAW,aAAa;IAC5B;;OAEG;IACH,aAAa,CACX,IAAI,EAAE,IAAI,CAAC,WAAW,EAAE,eAAe,CAAC,GACvC,OAAO,CAAC,WAAW,CAAC,CAAC;IAExB;;;OAGG;IACH,UAAU,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IAE/D;;;;OAIG;IACH,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;IAE9E;;;OAGG;IACH,uBAAuB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE9D;;;;;;OAMG;IACH,aAAa,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEpD;;;OAGG;IACH,wBAAwB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE3E;;;;;;OAMG;IACH,cAAc,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;IAEvE;;;;;;OAMG;IACH,qBAAqB,CAAC,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAE1C;;;OAGG;IACH,6BAA6B,CAAC,CAAC,aAAa,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAChG"}
|
|
@@ -363,11 +363,26 @@ export interface AuthConfig {
|
|
|
363
363
|
*
|
|
364
364
|
* - `allcalls`: Verify the session on every single request. Maximum security, requires a low-latency store (Redis).
|
|
365
365
|
* - `refresh`: Verify the session only during token refresh. High performance with good security.
|
|
366
|
-
* - `none`:
|
|
366
|
+
* - `none`: Access tokens are verified statelessly without session lookup. Note: `POST /refresh` always validates session existence in the store even under `'none'`; a revoked or rotated session can never refresh (`401 SESSION_REVOKED`).
|
|
367
367
|
*
|
|
368
368
|
* @default 'refresh'
|
|
369
369
|
*/
|
|
370
370
|
checkOn?: 'allcalls' | 'refresh' | 'none';
|
|
371
|
+
/**
|
|
372
|
+
* If `true`, a user can only have one active session at a time.
|
|
373
|
+
* Logging in from a new device/client automatically revokes all previous active sessions
|
|
374
|
+
* for this user in the `sessionStore` (`revokeAllSessionsForUser`), disconnecting previous devices.
|
|
375
|
+
*
|
|
376
|
+
* If `false` (default), multiple concurrent sessions across different devices
|
|
377
|
+
* are allowed simultaneously. Each device maintains its own session and refresh token.
|
|
378
|
+
*
|
|
379
|
+
* @default false
|
|
380
|
+
*/
|
|
381
|
+
singleSessionPerUser?: boolean;
|
|
382
|
+
/**
|
|
383
|
+
* Alias for `singleSessionPerUser`.
|
|
384
|
+
*/
|
|
385
|
+
singleSession?: boolean;
|
|
371
386
|
};
|
|
372
387
|
/**
|
|
373
388
|
* Optional template store for custom email and UI translations.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-config.model.d.ts","sourceRoot":"","sources":["../../src/models/auth-config.model.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"auth-config.model.d.ts","sourceRoot":"","sources":["../../src/models/auth-config.model.ts"],"names":[],"mappings":"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"}
|
|
@@ -24,5 +24,7 @@ export interface SessionInfo {
|
|
|
24
24
|
ipAddress?: string;
|
|
25
25
|
/** Arbitrary session-level data (e.g. device name, geo-location, etc.). */
|
|
26
26
|
data?: Record<string, unknown>;
|
|
27
|
+
/** SHA-256 hash of the active refresh token for this session (multi-device support). */
|
|
28
|
+
refreshTokenHash?: string;
|
|
27
29
|
}
|
|
28
30
|
//# sourceMappingURL=session.model.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session.model.d.ts","sourceRoot":"","sources":["../../src/models/session.model.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,WAAW,WAAW;IAC1B,8EAA8E;IAC9E,aAAa,EAAE,MAAM,CAAC;IACtB,4CAA4C;IAC5C,MAAM,EAAE,MAAM,CAAC;IACf,qDAAqD;IACrD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oCAAoC;IACpC,SAAS,EAAE,IAAI,CAAC;IAChB,gCAAgC;IAChC,SAAS,EAAE,IAAI,CAAC;IAChB,8EAA8E;IAC9E,YAAY,CAAC,EAAE,IAAI,CAAC;IACpB,kEAAkE;IAClE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2CAA2C;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2EAA2E;IAC3E,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"session.model.d.ts","sourceRoot":"","sources":["../../src/models/session.model.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,WAAW,WAAW;IAC1B,8EAA8E;IAC9E,aAAa,EAAE,MAAM,CAAC;IACtB,4CAA4C;IAC5C,MAAM,EAAE,MAAM,CAAC;IACf,qDAAqD;IACrD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oCAAoC;IACpC,SAAS,EAAE,IAAI,CAAC;IAChB,gCAAgC;IAChC,SAAS,EAAE,IAAI,CAAC;IAChB,8EAA8E;IAC9E,YAAY,CAAC,EAAE,IAAI,CAAC;IACpB,kEAAkE;IAClE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2CAA2C;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2EAA2E;IAC3E,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,wFAAwF;IACxF,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"admin.router.d.ts","sourceRoot":"","sources":["../../src/router/admin.router.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAqB,cAAc,EAAuB,MAAM,SAAS,CAAC;AAMzF,OAAO,EAAE,UAAU,EAAE,MAAM,oCAAoC,CAAC;AAChE,OAAO,EAAE,aAAa,EAAE,MAAM,uCAAuC,CAAC;AACtE,OAAO,EAAE,sBAAsB,EAAE,MAAM,iDAAiD,CAAC;AACzF,OAAO,EAAE,YAAY,EAAE,MAAM,sCAAsC,CAAC;AACpE,OAAO,EAAE,kBAAkB,EAAE,MAAM,6CAA6C,CAAC;AACjF,OAAO,EAAE,cAAc,EAAgB,MAAM,wCAAwC,CAAC;AACtF,OAAO,EAAE,oBAAoB,EAAE,MAAM,+CAA+C,CAAC;AACrF,OAAO,EAAE,YAAY,EAAE,MAAM,uCAAuC,CAAC;AACrE,OAAO,EAAE,aAAa,EAAE,MAAM,uCAAuC,CAAC;AACtE,OAAO,EAAE,cAAc,EAAE,MAAM,wCAAwC,CAAC;AAKxE,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"admin.router.d.ts","sourceRoot":"","sources":["../../src/router/admin.router.ts"],"names":[],"mappings":"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"}
|
|
@@ -16,6 +16,23 @@ const webhook_action_1 = require("../tools/webhook-action");
|
|
|
16
16
|
const openapi_1 = require("./openapi");
|
|
17
17
|
const auth_event_names_1 = require("../events/auth-event-names");
|
|
18
18
|
const router_events_1 = require("./router-events");
|
|
19
|
+
/*
|
|
20
|
+
* Admin login lives here (typically /auth/admin/login when mounted via
|
|
21
|
+
* AuthConfigurator.buildAllRouters). End-user login lives in ui.router.ts at
|
|
22
|
+
* /auth/ui/login. Keep these two audiences distinct in code and docs.
|
|
23
|
+
*/
|
|
24
|
+
function toSingleString(val, fallback = '') {
|
|
25
|
+
if (typeof val === 'string')
|
|
26
|
+
return val;
|
|
27
|
+
if (Array.isArray(val) && val.length > 0 && typeof val[0] === 'string')
|
|
28
|
+
return val[0];
|
|
29
|
+
return fallback;
|
|
30
|
+
}
|
|
31
|
+
function toSafeInt(val, fallback) {
|
|
32
|
+
const str = toSingleString(val);
|
|
33
|
+
const parsed = parseInt(str, 10);
|
|
34
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallback;
|
|
35
|
+
}
|
|
19
36
|
/**
|
|
20
37
|
* Id of the admin the `accessPolicy` guard authorized for this request (the
|
|
21
38
|
* actor of a privilege change). Only the policy guard sets it, in
|
|
@@ -134,8 +151,15 @@ function buildPolicyGuard(policy, userStore, jwtSecret, rbacStore, loginPath, co
|
|
|
134
151
|
const cookieHeader = req.headers.cookie ?? '';
|
|
135
152
|
cookies = cookieHeader.split(';').reduce((acc, part) => {
|
|
136
153
|
const [key, ...val] = part.trim().split('=');
|
|
137
|
-
if (key)
|
|
138
|
-
|
|
154
|
+
if (key) {
|
|
155
|
+
const rawVal = val.join('=');
|
|
156
|
+
try {
|
|
157
|
+
acc[key.trim()] = decodeURIComponent(rawVal);
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
acc[key.trim()] = rawVal;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
139
163
|
return acc;
|
|
140
164
|
}, {});
|
|
141
165
|
}
|
|
@@ -371,7 +395,7 @@ function createAdminRouter(userStore, options) {
|
|
|
371
395
|
// ── Local Login Handler (Self-contained Auth) ──────────────────────────
|
|
372
396
|
if (sessionBased && secret) {
|
|
373
397
|
router.post('/login', async (req, res) => {
|
|
374
|
-
const { email, password } = req.body;
|
|
398
|
+
const { email, password } = (req.body ?? {});
|
|
375
399
|
if (!password) {
|
|
376
400
|
res.status(400).json({ error: 'Password required' });
|
|
377
401
|
return;
|
|
@@ -587,9 +611,9 @@ function createAdminRouter(userStore, options) {
|
|
|
587
611
|
// GET /admin/api/users?limit=&offset=
|
|
588
612
|
router.get('/api/users', guard, async (req, res) => {
|
|
589
613
|
try {
|
|
590
|
-
const limit = Math.min(
|
|
591
|
-
const offset =
|
|
592
|
-
const filter = (req.query['filter']
|
|
614
|
+
const limit = Math.min(Math.max(toSafeInt(req.query['limit'], 20), 1), 100);
|
|
615
|
+
const offset = Math.max(toSafeInt(req.query['offset'], 0), 0);
|
|
616
|
+
const filter = toSingleString(req.query['filter']).toLowerCase().trim();
|
|
593
617
|
if (!userStore.listUsers) {
|
|
594
618
|
res.status(501).json({ error: 'IUserStore.listUsers is not implemented', users: [], total: 0 });
|
|
595
619
|
return;
|
|
@@ -662,7 +686,7 @@ function createAdminRouter(userStore, options) {
|
|
|
662
686
|
// POST /admin/api/2fa-policy — bulk set / clear the require-2FA flag on all users
|
|
663
687
|
router.post('/api/2fa-policy', guard, async (req, res) => {
|
|
664
688
|
try {
|
|
665
|
-
const { required } = req.body;
|
|
689
|
+
const { required } = (req.body ?? {});
|
|
666
690
|
if (typeof required !== 'boolean') {
|
|
667
691
|
res.status(400).json({ error: '"required" must be a boolean' });
|
|
668
692
|
return;
|
|
@@ -762,7 +786,7 @@ function createAdminRouter(userStore, options) {
|
|
|
762
786
|
return;
|
|
763
787
|
}
|
|
764
788
|
try {
|
|
765
|
-
const { role, tenantId } = req.body;
|
|
789
|
+
const { role, tenantId } = (req.body ?? {});
|
|
766
790
|
if (!role) {
|
|
767
791
|
res.status(400).json({ error: 'role is required' });
|
|
768
792
|
return;
|
|
@@ -1024,9 +1048,9 @@ function createAdminRouter(userStore, options) {
|
|
|
1024
1048
|
return;
|
|
1025
1049
|
}
|
|
1026
1050
|
try {
|
|
1027
|
-
const limit = Math.min(
|
|
1028
|
-
const offset =
|
|
1029
|
-
const filter = (req.query['filter']
|
|
1051
|
+
const limit = Math.min(Math.max(toSafeInt(req.query['limit'], 20), 1), 100);
|
|
1052
|
+
const offset = Math.max(toSafeInt(req.query['offset'], 0), 0);
|
|
1053
|
+
const filter = toSingleString(req.query['filter']).toLowerCase().trim();
|
|
1030
1054
|
if (!options.sessionStore.getAllSessions) {
|
|
1031
1055
|
res.status(501).json({ error: 'ISessionStore.getAllSessions is not implemented', sessions: [], total: 0 });
|
|
1032
1056
|
return;
|
|
@@ -1092,7 +1116,7 @@ function createAdminRouter(userStore, options) {
|
|
|
1092
1116
|
return;
|
|
1093
1117
|
}
|
|
1094
1118
|
try {
|
|
1095
|
-
const { name, permissions } = req.body;
|
|
1119
|
+
const { name, permissions } = (req.body ?? {});
|
|
1096
1120
|
if (!name) {
|
|
1097
1121
|
res.status(400).json({ error: 'name is required' });
|
|
1098
1122
|
return;
|
|
@@ -1140,7 +1164,7 @@ function createAdminRouter(userStore, options) {
|
|
|
1140
1164
|
return;
|
|
1141
1165
|
}
|
|
1142
1166
|
try {
|
|
1143
|
-
const { name, isActive } = req.body;
|
|
1167
|
+
const { name, isActive } = (req.body ?? {});
|
|
1144
1168
|
if (!name) {
|
|
1145
1169
|
res.status(400).json({ error: 'name is required' });
|
|
1146
1170
|
return;
|
|
@@ -1188,7 +1212,7 @@ function createAdminRouter(userStore, options) {
|
|
|
1188
1212
|
return;
|
|
1189
1213
|
}
|
|
1190
1214
|
try {
|
|
1191
|
-
const { userId } = req.body;
|
|
1215
|
+
const { userId } = (req.body ?? {});
|
|
1192
1216
|
if (!userId) {
|
|
1193
1217
|
res.status(400).json({ error: 'userId is required' });
|
|
1194
1218
|
return;
|
|
@@ -1222,9 +1246,9 @@ function createAdminRouter(userStore, options) {
|
|
|
1222
1246
|
return;
|
|
1223
1247
|
}
|
|
1224
1248
|
try {
|
|
1225
|
-
const limit = Math.min(
|
|
1226
|
-
const offset =
|
|
1227
|
-
const filter = (req.query['filter']
|
|
1249
|
+
const limit = Math.min(Math.max(toSafeInt(req.query['limit'], 20), 1), 100);
|
|
1250
|
+
const offset = Math.max(toSafeInt(req.query['offset'], 0), 0);
|
|
1251
|
+
const filter = toSingleString(req.query['filter']).toLowerCase().trim();
|
|
1228
1252
|
if (!options.apiKeyStore.listAll) {
|
|
1229
1253
|
res.status(501).json({ error: 'IApiKeyStore.listAll is not implemented', keys: [], total: 0 });
|
|
1230
1254
|
return;
|
|
@@ -1265,7 +1289,7 @@ function createAdminRouter(userStore, options) {
|
|
|
1265
1289
|
return;
|
|
1266
1290
|
}
|
|
1267
1291
|
try {
|
|
1268
|
-
const { name, serviceId, scopes, allowedIps, expiresAt } = req.body;
|
|
1292
|
+
const { name, serviceId, scopes, allowedIps, expiresAt } = (req.body ?? {});
|
|
1269
1293
|
if (!name) {
|
|
1270
1294
|
res.status(400).json({ error: 'name is required' });
|
|
1271
1295
|
return;
|
|
@@ -1339,8 +1363,8 @@ function createAdminRouter(userStore, options) {
|
|
|
1339
1363
|
return;
|
|
1340
1364
|
}
|
|
1341
1365
|
try {
|
|
1342
|
-
const limit = Math.min(
|
|
1343
|
-
const offset =
|
|
1366
|
+
const limit = Math.min(Math.max(toSafeInt(req.query['limit'], 20), 1), 100);
|
|
1367
|
+
const offset = Math.max(toSafeInt(req.query['offset'], 0), 0);
|
|
1344
1368
|
if (!options.webhookStore.listAll) {
|
|
1345
1369
|
res.status(501).json({ error: 'IWebhookStore.listAll is not implemented', webhooks: [], total: 0 });
|
|
1346
1370
|
return;
|
|
@@ -1369,7 +1393,7 @@ function createAdminRouter(userStore, options) {
|
|
|
1369
1393
|
return;
|
|
1370
1394
|
}
|
|
1371
1395
|
try {
|
|
1372
|
-
const { url, events, secret, tenantId, isActive, maxRetries, retryDelayMs } = req.body;
|
|
1396
|
+
const { url, events, secret, tenantId, isActive, maxRetries, retryDelayMs } = (req.body ?? {});
|
|
1373
1397
|
if (!url) {
|
|
1374
1398
|
res.status(400).json({ error: 'url is required' });
|
|
1375
1399
|
return;
|
|
@@ -1440,7 +1464,7 @@ function createAdminRouter(userStore, options) {
|
|
|
1440
1464
|
// POST /admin/api/templates/mail — create or update a mail template
|
|
1441
1465
|
router.post('/api/templates/mail', guard, async (req, res) => {
|
|
1442
1466
|
try {
|
|
1443
|
-
const { id, baseHtml, baseText, translations } = req.body;
|
|
1467
|
+
const { id, baseHtml, baseText, translations } = (req.body ?? {});
|
|
1444
1468
|
if (!id) {
|
|
1445
1469
|
res.status(400).json({ error: 'id is required' });
|
|
1446
1470
|
return;
|
|
@@ -1465,7 +1489,7 @@ function createAdminRouter(userStore, options) {
|
|
|
1465
1489
|
// POST /admin/api/templates/ui — update UI translations for a page
|
|
1466
1490
|
router.post('/api/templates/ui', guard, async (req, res) => {
|
|
1467
1491
|
try {
|
|
1468
|
-
const { page, translations } = req.body;
|
|
1492
|
+
const { page, translations } = (req.body ?? {});
|
|
1469
1493
|
if (!page || !translations) {
|
|
1470
1494
|
res.status(400).json({ error: 'page and translations are required' });
|
|
1471
1495
|
return;
|