@technomoron/api-server-base 2.0.0-beta.21 → 2.0.0-beta.23
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/dist/cjs/common/types.cjs +10 -0
- package/dist/cjs/common/types.d.ts +137 -0
- package/dist/cjs/{api-module.cjs → server/src/api-module.cjs} +8 -0
- package/dist/{esm → cjs/server/src}/api-module.d.ts +15 -0
- package/dist/cjs/{api-server-base.cjs → server/src/api-server-base.cjs} +669 -627
- package/dist/{esm → cjs/server/src}/api-server-base.d.ts +105 -78
- package/dist/cjs/{auth-api/auth-module.js → server/src/auth-api/auth-module.cjs} +96 -76
- package/dist/cjs/{auth-api → server/src/auth-api}/auth-module.d.ts +1 -1
- package/dist/cjs/{auth-api/compat-auth-storage.js → server/src/auth-api/compat-auth-storage.cjs} +4 -4
- package/dist/cjs/{auth-api/mem-auth-store.js → server/src/auth-api/mem-auth-store.cjs} +7 -7
- package/dist/cjs/{auth-api/module.js → server/src/auth-api/module.cjs} +1 -1
- package/dist/cjs/server/src/auth-api/schemas.cjs +171 -0
- package/dist/cjs/server/src/auth-api/schemas.d.ts +21 -0
- package/dist/cjs/{auth-api/sql-auth-store.js → server/src/auth-api/sql-auth-store.cjs} +8 -8
- package/dist/cjs/{auth-api/user-id.js → server/src/auth-api/user-id.cjs} +12 -3
- package/dist/{esm → cjs/server/src}/auth-cookie-options.d.ts +5 -3
- package/dist/cjs/server/src/base/client-info.cjs +285 -0
- package/dist/cjs/server/src/base/client-info.d.ts +27 -0
- package/dist/cjs/server/src/base/error-utils.cjs +50 -0
- package/dist/cjs/server/src/base/error-utils.d.ts +16 -0
- package/dist/cjs/server/src/base/request-utils.cjs +27 -0
- package/dist/cjs/server/src/base/request-utils.d.ts +8 -0
- package/dist/cjs/{index.cjs → server/src/index.cjs} +24 -15
- package/dist/{esm → cjs/server/src}/index.d.ts +7 -0
- package/dist/cjs/server/src/limiter/auth-rate-limiter.cjs +35 -0
- package/dist/cjs/server/src/limiter/auth-rate-limiter.d.ts +12 -0
- package/dist/cjs/server/src/limiter/fixed-window.cjs +41 -0
- package/dist/cjs/server/src/limiter/fixed-window.d.ts +11 -0
- package/dist/cjs/{oauth/base.js → server/src/oauth/base.cjs} +1 -0
- package/dist/cjs/{oauth → server/src/oauth}/base.d.ts +8 -1
- package/dist/cjs/{oauth/memory.js → server/src/oauth/memory.cjs} +7 -4
- package/dist/{esm → cjs/server/src}/oauth/memory.d.ts +1 -1
- package/dist/cjs/{oauth/models.js → server/src/oauth/models.cjs} +2 -2
- package/dist/cjs/{oauth/sequelize.js → server/src/oauth/sequelize.cjs} +11 -7
- package/dist/{esm → cjs/server/src}/oauth/sequelize.d.ts +1 -1
- package/dist/cjs/{passkey/base.js → server/src/passkey/base.cjs} +1 -0
- package/dist/{esm → cjs/server/src}/passkey/base.d.ts +11 -0
- package/dist/cjs/{passkey/memory.js → server/src/passkey/memory.cjs} +2 -2
- package/dist/cjs/{passkey/models.js → server/src/passkey/models.cjs} +1 -1
- package/dist/cjs/{passkey/sequelize.js → server/src/passkey/sequelize.cjs} +3 -3
- package/dist/cjs/{passkey/service.js → server/src/passkey/service.cjs} +17 -3
- package/dist/{esm → cjs/server/src}/passkey/service.d.ts +1 -1
- package/dist/cjs/{sequelize-utils.js → server/src/sequelize-utils.cjs} +4 -5
- package/dist/cjs/{token/base.js → server/src/token/base.cjs} +4 -0
- package/dist/{esm → cjs/server/src}/token/base.d.ts +7 -0
- package/dist/cjs/{token/memory.js → server/src/token/memory.cjs} +15 -20
- package/dist/cjs/{token/sequelize.js → server/src/token/sequelize.cjs} +25 -11
- package/dist/cjs/server/src/upload/memory.cjs +92 -0
- package/dist/cjs/server/src/upload/memory.d.ts +17 -0
- package/dist/cjs/server/src/upload/tus-module.cjs +270 -0
- package/dist/cjs/server/src/upload/tus-module.d.ts +38 -0
- package/dist/cjs/server/src/upload/types.d.ts +8 -0
- package/dist/cjs/{user/base.js → server/src/user/base.cjs} +1 -0
- package/dist/cjs/{user → server/src/user}/base.d.ts +9 -0
- package/dist/cjs/{user/memory.js → server/src/user/memory.cjs} +29 -7
- package/dist/cjs/{user/sequelize.js → server/src/user/sequelize.cjs} +33 -8
- package/dist/cjs/server/src/user/types.cjs +2 -0
- package/dist/esm/common/types.d.ts +137 -0
- package/dist/esm/common/types.js +9 -0
- package/dist/{cjs → esm/server/src}/api-module.d.ts +15 -0
- package/dist/esm/{api-module.js → server/src/api-module.js} +8 -0
- package/dist/{cjs → esm/server/src}/api-server-base.d.ts +105 -78
- package/dist/esm/{api-server-base.js → server/src/api-server-base.js} +658 -616
- package/dist/esm/{auth-api → server/src/auth-api}/auth-module.d.ts +1 -1
- package/dist/esm/{auth-api → server/src/auth-api}/auth-module.js +92 -72
- package/dist/esm/{auth-api → server/src/auth-api}/compat-auth-storage.js +3 -3
- package/dist/esm/server/src/auth-api/schemas.d.ts +21 -0
- package/dist/esm/server/src/auth-api/schemas.js +168 -0
- package/dist/esm/{auth-api → server/src/auth-api}/user-id.js +12 -3
- package/dist/{cjs → esm/server/src}/auth-cookie-options.d.ts +5 -3
- package/dist/esm/server/src/base/client-info.d.ts +27 -0
- package/dist/esm/server/src/base/client-info.js +282 -0
- package/dist/esm/server/src/base/error-utils.d.ts +16 -0
- package/dist/esm/server/src/base/error-utils.js +44 -0
- package/dist/esm/server/src/base/request-utils.d.ts +8 -0
- package/dist/esm/server/src/base/request-utils.js +23 -0
- package/dist/{cjs → esm/server/src}/index.d.ts +7 -0
- package/dist/esm/{index.js → server/src/index.js} +4 -0
- package/dist/esm/server/src/limiter/auth-rate-limiter.d.ts +12 -0
- package/dist/esm/server/src/limiter/auth-rate-limiter.js +32 -0
- package/dist/esm/server/src/limiter/fixed-window.d.ts +11 -0
- package/dist/esm/server/src/limiter/fixed-window.js +37 -0
- package/dist/esm/{oauth → server/src/oauth}/base.d.ts +8 -1
- package/dist/esm/server/src/oauth/base.js +3 -0
- package/dist/{cjs → esm/server/src}/oauth/memory.d.ts +1 -1
- package/dist/esm/{oauth → server/src/oauth}/memory.js +5 -2
- package/dist/{cjs → esm/server/src}/oauth/sequelize.d.ts +1 -1
- package/dist/esm/{oauth → server/src/oauth}/sequelize.js +6 -2
- package/dist/{cjs → esm/server/src}/passkey/base.d.ts +11 -0
- package/dist/esm/server/src/passkey/base.js +3 -0
- package/dist/{cjs → esm/server/src}/passkey/service.d.ts +1 -1
- package/dist/esm/{passkey → server/src/passkey}/service.js +17 -3
- package/dist/esm/{sequelize-utils.js → server/src/sequelize-utils.js} +4 -5
- package/dist/{cjs → esm/server/src}/token/base.d.ts +7 -0
- package/dist/esm/{token → server/src/token}/base.js +4 -0
- package/dist/esm/{token → server/src/token}/memory.js +14 -19
- package/dist/esm/{token → server/src/token}/sequelize.js +22 -8
- package/dist/esm/server/src/upload/memory.d.ts +17 -0
- package/dist/esm/server/src/upload/memory.js +86 -0
- package/dist/esm/server/src/upload/tus-module.d.ts +38 -0
- package/dist/esm/server/src/upload/tus-module.js +266 -0
- package/dist/esm/server/src/upload/types.d.ts +8 -0
- package/dist/esm/{user → server/src/user}/base.d.ts +9 -0
- package/dist/esm/{user → server/src/user}/base.js +1 -0
- package/dist/esm/{user → server/src/user}/memory.js +27 -5
- package/dist/esm/{user → server/src/user}/sequelize.js +30 -5
- package/dist/esm/server/src/user/types.js +1 -0
- package/docs/swagger/openapi.json +411 -125
- package/package.json +129 -134
- package/README.txt +0 -213
- package/dist/esm/oauth/base.js +0 -2
- package/dist/esm/passkey/base.js +0 -2
- /package/dist/cjs/{auth-api → server/src/auth-api}/compat-auth-storage.d.ts +0 -0
- /package/dist/cjs/{auth-api → server/src/auth-api}/mem-auth-store.d.ts +0 -0
- /package/dist/cjs/{auth-api → server/src/auth-api}/module.d.ts +0 -0
- /package/dist/cjs/{auth-api → server/src/auth-api}/sql-auth-store.d.ts +0 -0
- /package/dist/cjs/{auth-api/storage.js → server/src/auth-api/storage.cjs} +0 -0
- /package/dist/cjs/{auth-api → server/src/auth-api}/storage.d.ts +0 -0
- /package/dist/cjs/{auth-api/types.js → server/src/auth-api/types.cjs} +0 -0
- /package/dist/cjs/{auth-api → server/src/auth-api}/types.d.ts +0 -0
- /package/dist/cjs/{auth-api → server/src/auth-api}/user-id.d.ts +0 -0
- /package/dist/cjs/{auth-cookie-options.js → server/src/auth-cookie-options.cjs} +0 -0
- /package/dist/cjs/{oauth → server/src/oauth}/models.d.ts +0 -0
- /package/dist/cjs/{oauth/types.js → server/src/oauth/types.cjs} +0 -0
- /package/dist/cjs/{oauth → server/src/oauth}/types.d.ts +0 -0
- /package/dist/cjs/{passkey/config.js → server/src/passkey/config.cjs} +0 -0
- /package/dist/cjs/{passkey → server/src/passkey}/config.d.ts +0 -0
- /package/dist/cjs/{passkey → server/src/passkey}/memory.d.ts +0 -0
- /package/dist/cjs/{passkey → server/src/passkey}/models.d.ts +0 -0
- /package/dist/cjs/{passkey → server/src/passkey}/sequelize.d.ts +0 -0
- /package/dist/cjs/{passkey/types.js → server/src/passkey/types.cjs} +0 -0
- /package/dist/cjs/{passkey → server/src/passkey}/types.d.ts +0 -0
- /package/dist/cjs/{sequelize-utils.d.ts → server/src/sequelize-utils.d.ts} +0 -0
- /package/dist/cjs/{token → server/src/token}/memory.d.ts +0 -0
- /package/dist/cjs/{token → server/src/token}/sequelize.d.ts +0 -0
- /package/dist/cjs/{token/types.js → server/src/token/types.cjs} +0 -0
- /package/dist/cjs/{token → server/src/token}/types.d.ts +0 -0
- /package/dist/cjs/{user/types.js → server/src/upload/types.cjs} +0 -0
- /package/dist/cjs/{user → server/src/user}/memory.d.ts +0 -0
- /package/dist/cjs/{user → server/src/user}/sequelize.d.ts +0 -0
- /package/dist/cjs/{user → server/src/user}/types.d.ts +0 -0
- /package/dist/esm/{auth-api → server/src/auth-api}/compat-auth-storage.d.ts +0 -0
- /package/dist/esm/{auth-api → server/src/auth-api}/mem-auth-store.d.ts +0 -0
- /package/dist/esm/{auth-api → server/src/auth-api}/mem-auth-store.js +0 -0
- /package/dist/esm/{auth-api → server/src/auth-api}/module.d.ts +0 -0
- /package/dist/esm/{auth-api → server/src/auth-api}/module.js +0 -0
- /package/dist/esm/{auth-api → server/src/auth-api}/sql-auth-store.d.ts +0 -0
- /package/dist/esm/{auth-api → server/src/auth-api}/sql-auth-store.js +0 -0
- /package/dist/esm/{auth-api → server/src/auth-api}/storage.d.ts +0 -0
- /package/dist/esm/{auth-api → server/src/auth-api}/storage.js +0 -0
- /package/dist/esm/{auth-api → server/src/auth-api}/types.d.ts +0 -0
- /package/dist/esm/{auth-api → server/src/auth-api}/types.js +0 -0
- /package/dist/esm/{auth-api → server/src/auth-api}/user-id.d.ts +0 -0
- /package/dist/esm/{auth-cookie-options.js → server/src/auth-cookie-options.js} +0 -0
- /package/dist/esm/{oauth → server/src/oauth}/models.d.ts +0 -0
- /package/dist/esm/{oauth → server/src/oauth}/models.js +0 -0
- /package/dist/esm/{oauth → server/src/oauth}/types.d.ts +0 -0
- /package/dist/esm/{oauth → server/src/oauth}/types.js +0 -0
- /package/dist/esm/{passkey → server/src/passkey}/config.d.ts +0 -0
- /package/dist/esm/{passkey → server/src/passkey}/config.js +0 -0
- /package/dist/esm/{passkey → server/src/passkey}/memory.d.ts +0 -0
- /package/dist/esm/{passkey → server/src/passkey}/memory.js +0 -0
- /package/dist/esm/{passkey → server/src/passkey}/models.d.ts +0 -0
- /package/dist/esm/{passkey → server/src/passkey}/models.js +0 -0
- /package/dist/esm/{passkey → server/src/passkey}/sequelize.d.ts +0 -0
- /package/dist/esm/{passkey → server/src/passkey}/sequelize.js +0 -0
- /package/dist/esm/{passkey → server/src/passkey}/types.d.ts +0 -0
- /package/dist/esm/{passkey → server/src/passkey}/types.js +0 -0
- /package/dist/esm/{sequelize-utils.d.ts → server/src/sequelize-utils.d.ts} +0 -0
- /package/dist/esm/{token → server/src/token}/memory.d.ts +0 -0
- /package/dist/esm/{token → server/src/token}/sequelize.d.ts +0 -0
- /package/dist/esm/{token → server/src/token}/types.d.ts +0 -0
- /package/dist/esm/{token → server/src/token}/types.js +0 -0
- /package/dist/esm/{user → server/src/upload}/types.js +0 -0
- /package/dist/esm/{user → server/src/user}/memory.d.ts +0 -0
- /package/dist/esm/{user → server/src/user}/sequelize.d.ts +0 -0
- /package/dist/esm/{user → server/src/user}/types.d.ts +0 -0
|
@@ -2,12 +2,17 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const node_crypto_1 = require("node:crypto");
|
|
4
4
|
const helpers_1 = require("@simplewebauthn/server/helpers");
|
|
5
|
-
const api_server_base_js_1 = require("../api-server-base.
|
|
6
|
-
const auth_cookie_options_js_1 = require("../auth-cookie-options.
|
|
7
|
-
const module_js_1 = require("./module.
|
|
8
|
-
const
|
|
5
|
+
const api_server_base_js_1 = require("../api-server-base.cjs");
|
|
6
|
+
const auth_cookie_options_js_1 = require("../auth-cookie-options.cjs");
|
|
7
|
+
const module_js_1 = require("./module.cjs");
|
|
8
|
+
const schemas_js_1 = require("./schemas.cjs");
|
|
9
|
+
const storage_js_1 = require("./storage.cjs");
|
|
9
10
|
function isAuthIdentifier(value) {
|
|
10
|
-
|
|
11
|
+
if (typeof value === 'string')
|
|
12
|
+
return value.length > 0;
|
|
13
|
+
if (typeof value === 'number')
|
|
14
|
+
return Number.isFinite(value);
|
|
15
|
+
return false;
|
|
11
16
|
}
|
|
12
17
|
function toStringOrNull(value) {
|
|
13
18
|
if (typeof value === 'string') {
|
|
@@ -44,7 +49,7 @@ class AuthModule extends module_js_1.BaseAuthModule {
|
|
|
44
49
|
this.defaultDomain = options.defaultDomain;
|
|
45
50
|
this.canImpersonateHook = options.canImpersonate;
|
|
46
51
|
this.rateLimitHook = options.rateLimit;
|
|
47
|
-
this.allowInsecurePkcePlain = options.allowInsecurePkcePlain ??
|
|
52
|
+
this.allowInsecurePkcePlain = options.allowInsecurePkcePlain ?? false;
|
|
48
53
|
}
|
|
49
54
|
get storage() {
|
|
50
55
|
return this.server.getAuthStorage();
|
|
@@ -60,16 +65,17 @@ class AuthModule extends module_js_1.BaseAuthModule {
|
|
|
60
65
|
targetUser,
|
|
61
66
|
effectiveUserId
|
|
62
67
|
});
|
|
63
|
-
if (allowed)
|
|
68
|
+
if (allowed === true)
|
|
64
69
|
return true;
|
|
65
|
-
|
|
70
|
+
if (allowed === false)
|
|
71
|
+
return false;
|
|
66
72
|
}
|
|
67
73
|
const storageWithHook = this.storage;
|
|
68
74
|
if (typeof storageWithHook.canImpersonate === 'function') {
|
|
69
75
|
const allowed = await storageWithHook.canImpersonate({ realUserId, effectiveUserId });
|
|
70
76
|
return !!allowed;
|
|
71
77
|
}
|
|
72
|
-
return realUserId === effectiveUserId;
|
|
78
|
+
return String(realUserId) === String(effectiveUserId);
|
|
73
79
|
}
|
|
74
80
|
async ensureImpersonationAllowed(apiReq, realUser, targetUser) {
|
|
75
81
|
const permitted = await this.canImpersonate(apiReq, realUser, targetUser);
|
|
@@ -257,6 +263,9 @@ class AuthModule extends module_js_1.BaseAuthModule {
|
|
|
257
263
|
const accessMaxAge = Math.max(1, conf.accessExpiry) * 1000;
|
|
258
264
|
const refreshSeconds = Math.max(1, preferences.refreshTtlSeconds ?? conf.refreshExpiry);
|
|
259
265
|
const refreshMaxAge = refreshSeconds * 1000;
|
|
266
|
+
// When sessionCookie is true we omit maxAge so the browser deletes the
|
|
267
|
+
// cookie on close. The server-side JWT still has its own expiry, which
|
|
268
|
+
// limits exposure if the browser crashes without running its cleanup.
|
|
260
269
|
const accessOptions = sessionCookie ? options : { ...options, maxAge: accessMaxAge };
|
|
261
270
|
const refreshOptions = sessionCookie ? options : { ...options, maxAge: refreshMaxAge };
|
|
262
271
|
if (tokens.accessToken) {
|
|
@@ -346,19 +355,10 @@ class AuthModule extends module_js_1.BaseAuthModule {
|
|
|
346
355
|
}
|
|
347
356
|
parseLoginBody(apiReq) {
|
|
348
357
|
const body = (apiReq.req.body ?? {});
|
|
349
|
-
|
|
350
|
-
const
|
|
358
|
+
// login and password are guaranteed present and non-empty by JSON Schema
|
|
359
|
+
const login = body.login;
|
|
360
|
+
const password = body.password;
|
|
351
361
|
const sessionPrefs = this.resolveSessionPreferences(body.keepSession);
|
|
352
|
-
if (!login || !password) {
|
|
353
|
-
const errors = {};
|
|
354
|
-
if (!login) {
|
|
355
|
-
errors.login = 'Login is required';
|
|
356
|
-
}
|
|
357
|
-
if (!password) {
|
|
358
|
-
errors.password = 'Password is required';
|
|
359
|
-
}
|
|
360
|
-
throw new api_server_base_js_1.ApiError({ code: 400, message: 'Missing credentials', errors });
|
|
361
|
-
}
|
|
362
362
|
return {
|
|
363
363
|
login,
|
|
364
364
|
password,
|
|
@@ -453,7 +453,9 @@ class AuthModule extends module_js_1.BaseAuthModule {
|
|
|
453
453
|
const { login, password, ...metadata } = this.parseLoginBody(apiReq);
|
|
454
454
|
const user = await this.storage.getUser(login);
|
|
455
455
|
const hash = user ? this.storage.getUserPasswordHash(user) : '';
|
|
456
|
-
|
|
456
|
+
// Reject users with no password hash (e.g. OAuth/passkey-only accounts) before
|
|
457
|
+
// calling verifyPassword, since bcrypt behaviour on an empty hash is undefined.
|
|
458
|
+
const verified = user && hash ? await this.storage.verifyPassword(password, hash) : false;
|
|
457
459
|
if (!user || !verified) {
|
|
458
460
|
throw new api_server_base_js_1.ApiError({
|
|
459
461
|
code: 400,
|
|
@@ -484,6 +486,13 @@ class AuthModule extends module_js_1.BaseAuthModule {
|
|
|
484
486
|
message: verify.error ?? 'Unable to verify refresh token'
|
|
485
487
|
});
|
|
486
488
|
}
|
|
489
|
+
// Delete the token immediately after verification to narrow the TOCTOU window.
|
|
490
|
+
// This must happen before the slower getUserOrThrow call.
|
|
491
|
+
const deleted = await this.storage.deleteToken({ refreshToken: providedToken });
|
|
492
|
+
if (deleted === 0) {
|
|
493
|
+
// Another concurrent request already consumed this refresh token.
|
|
494
|
+
throw new api_server_base_js_1.ApiError({ code: 401, message: 'Invalid refresh token' });
|
|
495
|
+
}
|
|
487
496
|
const user = await this.getUserOrThrow(stored.userId ?? verify.data.uid, 'User not found');
|
|
488
497
|
const metadata = {
|
|
489
498
|
domain: stored.domain,
|
|
@@ -499,7 +508,6 @@ class AuthModule extends module_js_1.BaseAuthModule {
|
|
|
499
508
|
refreshTtlSeconds: sessionPrefs.refreshTtlSeconds ?? stored.refreshTtlSeconds,
|
|
500
509
|
sessionCookie: sessionPrefs.sessionCookie ?? stored.sessionCookie
|
|
501
510
|
};
|
|
502
|
-
await this.storage.deleteToken({ refreshToken: providedToken });
|
|
503
511
|
const pair = await this.issueTokens(apiReq, user, metadata);
|
|
504
512
|
const publicUser = this.storage.filterUser(user);
|
|
505
513
|
return [200, { ...pair, user: publicUser }];
|
|
@@ -539,8 +547,7 @@ class AuthModule extends module_js_1.BaseAuthModule {
|
|
|
539
547
|
apiReq.req.cookies[conf.accessCookie].trim().length > 0);
|
|
540
548
|
const shouldRefresh = Boolean(body.refresh) || !hasAccessToken;
|
|
541
549
|
if (shouldRefresh) {
|
|
542
|
-
|
|
543
|
-
if (typeof updateToken !== 'function' || !this.storageImplements('updateToken')) {
|
|
550
|
+
if (typeof this.storage.updateToken !== 'function' || !this.storageImplements('updateToken')) {
|
|
544
551
|
throw new api_server_base_js_1.ApiError({ code: 501, message: 'Token update storage is not configured' });
|
|
545
552
|
}
|
|
546
553
|
// Sign a new access token without embedding stored token secrets into the JWT payload.
|
|
@@ -564,7 +571,7 @@ class AuthModule extends module_js_1.BaseAuthModule {
|
|
|
564
571
|
if (!access.success || !access.token) {
|
|
565
572
|
throw new api_server_base_js_1.ApiError({ code: 500, message: access.error ?? 'Unable to sign access token' });
|
|
566
573
|
}
|
|
567
|
-
const updated = await
|
|
574
|
+
const updated = await this.storage.updateToken({
|
|
568
575
|
refreshToken,
|
|
569
576
|
accessToken: access.token,
|
|
570
577
|
lastSeenAt: new Date()
|
|
@@ -610,12 +617,10 @@ class AuthModule extends module_js_1.BaseAuthModule {
|
|
|
610
617
|
throw new api_server_base_js_1.ApiError({ code: 501, message: 'Passkey support is not configured' });
|
|
611
618
|
}
|
|
612
619
|
const body = (apiReq.req.body ?? {});
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
throw new api_server_base_js_1.ApiError({ code: 400, message: 'Passkey action must be "register" or "authenticate"' });
|
|
616
|
-
}
|
|
620
|
+
// action is guaranteed to be 'register' | 'authenticate' by JSON Schema
|
|
621
|
+
const action = body.action;
|
|
617
622
|
const params = {
|
|
618
|
-
action,
|
|
623
|
+
action: action,
|
|
619
624
|
login: toStringOrNull(body.login) ?? undefined,
|
|
620
625
|
userId: isAuthIdentifier(body.userId) ? body.userId : undefined
|
|
621
626
|
};
|
|
@@ -628,11 +633,9 @@ class AuthModule extends module_js_1.BaseAuthModule {
|
|
|
628
633
|
}
|
|
629
634
|
const body = (apiReq.req.body ?? {});
|
|
630
635
|
const sessionPrefs = this.resolveSessionPreferences(body.keepSession);
|
|
631
|
-
|
|
636
|
+
// expectedChallenge (string) and response (object) are guaranteed by JSON Schema
|
|
637
|
+
const expectedChallenge = body.expectedChallenge;
|
|
632
638
|
const response = body.response;
|
|
633
|
-
if (!expectedChallenge || typeof response !== 'object' || response === null) {
|
|
634
|
-
throw new api_server_base_js_1.ApiError({ code: 400, message: 'Malformed passkey verification payload' });
|
|
635
|
-
}
|
|
636
639
|
const rawMetadata = {
|
|
637
640
|
domain: toStringOrNull(body.domain) ?? undefined,
|
|
638
641
|
fingerprint: toStringOrNull(body.fingerprint) ?? undefined,
|
|
@@ -742,6 +745,15 @@ class AuthModule extends module_js_1.BaseAuthModule {
|
|
|
742
745
|
}
|
|
743
746
|
async deleteImpersonation(apiReq) {
|
|
744
747
|
this.assertAuthReady();
|
|
748
|
+
if (!apiReq.isImpersonating()) {
|
|
749
|
+
throw new api_server_base_js_1.ApiError({ code: 400, message: 'Not currently impersonating' });
|
|
750
|
+
}
|
|
751
|
+
// Revoke the active impersonation refresh token before issuing new real-user tokens
|
|
752
|
+
// so that a captured impersonation token cannot be reused after impersonation ends.
|
|
753
|
+
const impersonationRefreshToken = this.extractRefreshToken(apiReq, {});
|
|
754
|
+
if (impersonationRefreshToken) {
|
|
755
|
+
await this.storage.deleteToken({ refreshToken: impersonationRefreshToken });
|
|
756
|
+
}
|
|
745
757
|
const actor = await this.resolveActorContext(apiReq);
|
|
746
758
|
const query = (apiReq.req.query ?? {});
|
|
747
759
|
const metadata = this.buildImpersonationMetadata(query);
|
|
@@ -778,9 +790,7 @@ class AuthModule extends module_js_1.BaseAuthModule {
|
|
|
778
790
|
? apiReq.req.body.extras
|
|
779
791
|
: undefined
|
|
780
792
|
};
|
|
781
|
-
|
|
782
|
-
throw new api_server_base_js_1.ApiError({ code: 400, message: 'OAuth provider is required' });
|
|
783
|
-
}
|
|
793
|
+
// provider is guaranteed present and non-empty by params schema
|
|
784
794
|
const result = await this.server.initiateOAuth(params);
|
|
785
795
|
return [200, result];
|
|
786
796
|
}
|
|
@@ -793,9 +803,6 @@ class AuthModule extends module_js_1.BaseAuthModule {
|
|
|
793
803
|
query: apiReq.req.query,
|
|
794
804
|
body: (apiReq.req.body ?? {})
|
|
795
805
|
};
|
|
796
|
-
if (!params.provider) {
|
|
797
|
-
throw new api_server_base_js_1.ApiError({ code: 400, message: 'OAuth provider is required' });
|
|
798
|
-
}
|
|
799
806
|
const result = await this.server.completeOAuth(params);
|
|
800
807
|
if (result.tokens?.accessToken && result.tokens.refreshToken) {
|
|
801
808
|
this.setJwtCookies(apiReq, {
|
|
@@ -809,20 +816,16 @@ class AuthModule extends module_js_1.BaseAuthModule {
|
|
|
809
816
|
if (typeof this.storage.getClient !== 'function' || typeof this.storage.createAuthCode !== 'function') {
|
|
810
817
|
throw new api_server_base_js_1.ApiError({ code: 501, message: 'OAuth authorization storage is not configured' });
|
|
811
818
|
}
|
|
819
|
+
await this.applyRateLimit(apiReq, 'oauth-authorize');
|
|
812
820
|
const body = (apiReq.req.body ?? {});
|
|
813
|
-
|
|
814
|
-
const
|
|
821
|
+
// clientId and redirectUri are guaranteed present and non-empty by JSON Schema
|
|
822
|
+
const clientId = body.clientId;
|
|
823
|
+
const redirectUri = body.redirectUri;
|
|
815
824
|
const scope = toScopeArray(body.scope) ?? [];
|
|
816
825
|
const state = toStringOrNull(body.state) ?? undefined;
|
|
817
826
|
const codeChallenge = toStringOrNull(body.codeChallenge) ?? undefined;
|
|
818
827
|
const codeChallengeMethod = toStringOrNull(body.codeChallengeMethod) ?? undefined;
|
|
819
828
|
const resolvedCodeChallengeMethod = this.resolvePkceChallengeMethod(codeChallengeMethod);
|
|
820
|
-
if (!clientId) {
|
|
821
|
-
throw new api_server_base_js_1.ApiError({ code: 400, message: 'clientId is required' });
|
|
822
|
-
}
|
|
823
|
-
if (!redirectUri) {
|
|
824
|
-
throw new api_server_base_js_1.ApiError({ code: 400, message: 'redirectUri is required' });
|
|
825
|
-
}
|
|
826
829
|
const client = await this.storage.getClient(clientId);
|
|
827
830
|
if (!client) {
|
|
828
831
|
throw new api_server_base_js_1.ApiError({ code: 400, message: 'Unknown client_id' });
|
|
@@ -852,10 +855,8 @@ class AuthModule extends module_js_1.BaseAuthModule {
|
|
|
852
855
|
throw new api_server_base_js_1.ApiError({ code: 501, message: 'OAuth token storage is not configured' });
|
|
853
856
|
}
|
|
854
857
|
const body = (apiReq.req.body ?? {});
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
throw new api_server_base_js_1.ApiError({ code: 400, message: 'grant_type is required' });
|
|
858
|
-
}
|
|
858
|
+
// grant_type is guaranteed to be 'authorization_code' | 'refresh_token' by JSON Schema
|
|
859
|
+
const grantType = body.grant_type;
|
|
859
860
|
const { client, clientSecretProvided } = await this.resolveClientAuthentication(apiReq, body);
|
|
860
861
|
switch (grantType) {
|
|
861
862
|
case 'authorization_code':
|
|
@@ -909,7 +910,7 @@ class AuthModule extends module_js_1.BaseAuthModule {
|
|
|
909
910
|
}
|
|
910
911
|
}
|
|
911
912
|
}
|
|
912
|
-
else if (!clientSecretProvided && (client.hasSecret ??
|
|
913
|
+
else if (!clientSecretProvided && (client.hasSecret ?? false)) {
|
|
913
914
|
throw new api_server_base_js_1.ApiError({ code: 400, message: 'Client authentication required when no PKCE challenge present' });
|
|
914
915
|
}
|
|
915
916
|
const user = await this.getUserOrThrow(record.userId, 'User not found');
|
|
@@ -943,8 +944,12 @@ class AuthModule extends module_js_1.BaseAuthModule {
|
|
|
943
944
|
if (stored.clientId && stored.clientId !== client.clientId) {
|
|
944
945
|
throw new api_server_base_js_1.ApiError({ code: 400, message: 'Refresh token issued to another client' });
|
|
945
946
|
}
|
|
947
|
+
// Delete the token immediately after verification to narrow the TOCTOU window.
|
|
948
|
+
const deleted = await this.storage.deleteToken({ refreshToken });
|
|
949
|
+
if (deleted === 0) {
|
|
950
|
+
throw new api_server_base_js_1.ApiError({ code: 401, message: 'Invalid refresh token' });
|
|
951
|
+
}
|
|
946
952
|
const user = await this.getUserOrThrow(stored.userId ?? verify.data.uid, 'User not found');
|
|
947
|
-
await this.storage.deleteToken({ refreshToken });
|
|
948
953
|
const tokens = await this.issueTokens(apiReq, user, {
|
|
949
954
|
clientId: client.clientId,
|
|
950
955
|
scope: stored.scope,
|
|
@@ -1016,7 +1021,7 @@ class AuthModule extends module_js_1.BaseAuthModule {
|
|
|
1016
1021
|
if (!client) {
|
|
1017
1022
|
throw new api_server_base_js_1.ApiError({ code: 400, message: 'Unknown client_id' });
|
|
1018
1023
|
}
|
|
1019
|
-
const requiresSecret = client.hasSecret ??
|
|
1024
|
+
const requiresSecret = client.hasSecret ?? false;
|
|
1020
1025
|
if (requiresSecret) {
|
|
1021
1026
|
if (!secretProvided) {
|
|
1022
1027
|
throw new api_server_base_js_1.ApiError({ code: 400, message: 'Client authentication is required' });
|
|
@@ -1034,7 +1039,7 @@ class AuthModule extends module_js_1.BaseAuthModule {
|
|
|
1034
1039
|
}
|
|
1035
1040
|
assertRedirectUriAllowed(client, redirectUri) {
|
|
1036
1041
|
if (client.redirectUris.length === 0) {
|
|
1037
|
-
|
|
1042
|
+
throw new api_server_base_js_1.ApiError({ code: 400, message: 'Client has no registered redirect URIs' });
|
|
1038
1043
|
}
|
|
1039
1044
|
if (!client.redirectUris.includes(redirectUri)) {
|
|
1040
1045
|
throw new api_server_base_js_1.ApiError({ code: 400, message: 'redirect_uri not registered for client' });
|
|
@@ -1043,9 +1048,13 @@ class AuthModule extends module_js_1.BaseAuthModule {
|
|
|
1043
1048
|
async resolveUserForOAuth(apiReq, body) {
|
|
1044
1049
|
const refreshToken = this.extractRefreshToken(apiReq, body);
|
|
1045
1050
|
if (refreshToken) {
|
|
1051
|
+
const verify = this.server.jwtVerify(refreshToken, this.server.config.refreshSecret);
|
|
1052
|
+
if (!verify.success || !verify.data) {
|
|
1053
|
+
throw new api_server_base_js_1.ApiError({ code: 401, message: 'Invalid or expired refresh token' });
|
|
1054
|
+
}
|
|
1046
1055
|
const stored = await this.storage.getToken({ refreshToken });
|
|
1047
1056
|
if (stored) {
|
|
1048
|
-
return this.getUserOrThrow(stored.userId, 'User not found for authorization');
|
|
1057
|
+
return this.getUserOrThrow(stored.userId ?? verify.data.uid, 'User not found for authorization');
|
|
1049
1058
|
}
|
|
1050
1059
|
}
|
|
1051
1060
|
const login = toStringOrNull(body.login);
|
|
@@ -1053,7 +1062,7 @@ class AuthModule extends module_js_1.BaseAuthModule {
|
|
|
1053
1062
|
if (login && password) {
|
|
1054
1063
|
const user = await this.storage.getUser(login);
|
|
1055
1064
|
const hash = user ? this.storage.getUserPasswordHash(user) : '';
|
|
1056
|
-
const verified = user ? await this.storage.verifyPassword(password, hash) : false;
|
|
1065
|
+
const verified = user && hash ? await this.storage.verifyPassword(password, hash) : false;
|
|
1057
1066
|
if (!user || !verified) {
|
|
1058
1067
|
throw new api_server_base_js_1.ApiError({ code: 400, message: 'Invalid credentials' });
|
|
1059
1068
|
}
|
|
@@ -1069,8 +1078,7 @@ class AuthModule extends module_js_1.BaseAuthModule {
|
|
|
1069
1078
|
if (storageHints.adapter?.passkeyService || storageHints.adapter?.passkeyStore) {
|
|
1070
1079
|
return true;
|
|
1071
1080
|
}
|
|
1072
|
-
|
|
1073
|
-
return !!serverHints.passkeyServiceAdapter;
|
|
1081
|
+
return false;
|
|
1074
1082
|
}
|
|
1075
1083
|
hasOAuthStore() {
|
|
1076
1084
|
const storageHints = this.storage;
|
|
@@ -1080,8 +1088,7 @@ class AuthModule extends module_js_1.BaseAuthModule {
|
|
|
1080
1088
|
if (storageHints.adapter?.oauthStore) {
|
|
1081
1089
|
return true;
|
|
1082
1090
|
}
|
|
1083
|
-
|
|
1084
|
-
return !!serverHints.oauthStoreAdapter;
|
|
1091
|
+
return false;
|
|
1085
1092
|
}
|
|
1086
1093
|
storageImplements(key) {
|
|
1087
1094
|
const candidate = this.storage[key];
|
|
@@ -1131,32 +1138,38 @@ class AuthModule extends module_js_1.BaseAuthModule {
|
|
|
1131
1138
|
method: 'post',
|
|
1132
1139
|
path: '/v1/login',
|
|
1133
1140
|
handler: (req) => this.postLogin(req),
|
|
1134
|
-
auth: { type: 'none', req: 'any' }
|
|
1141
|
+
auth: { type: 'none', req: 'any' },
|
|
1142
|
+
schema: { body: schemas_js_1.loginBodySchema }
|
|
1135
1143
|
}, {
|
|
1136
1144
|
method: 'post',
|
|
1137
1145
|
path: '/v1/refresh',
|
|
1138
1146
|
handler: (req) => this.postRefresh(req),
|
|
1139
|
-
auth: { type: 'none', req: 'any' }
|
|
1147
|
+
auth: { type: 'none', req: 'any' },
|
|
1148
|
+
schema: { body: schemas_js_1.refreshBodySchema }
|
|
1140
1149
|
}, {
|
|
1141
1150
|
method: 'post',
|
|
1142
1151
|
path: '/v1/logout',
|
|
1143
1152
|
handler: (req) => this.postLogout(req),
|
|
1144
|
-
auth: { type: 'maybe', req: 'any' }
|
|
1153
|
+
auth: { type: 'maybe', req: 'any' },
|
|
1154
|
+
schema: { body: schemas_js_1.logoutBodySchema }
|
|
1145
1155
|
}, {
|
|
1146
1156
|
method: 'post',
|
|
1147
1157
|
path: '/v1/whoami',
|
|
1148
1158
|
handler: (req) => this.postWhoAmI(req),
|
|
1149
|
-
auth: { type: 'maybe', req: 'any' }
|
|
1159
|
+
auth: { type: 'maybe', req: 'any' },
|
|
1160
|
+
schema: { body: schemas_js_1.whoamiBodySchema }
|
|
1150
1161
|
}, {
|
|
1151
1162
|
method: 'post',
|
|
1152
1163
|
path: '/v1/impersonations',
|
|
1153
1164
|
handler: (req) => this.postImpersonation(req),
|
|
1154
|
-
auth: { type: 'strict', req: 'any' }
|
|
1165
|
+
auth: { type: 'strict', req: 'any' },
|
|
1166
|
+
schema: { body: schemas_js_1.impersonateBodySchema }
|
|
1155
1167
|
}, {
|
|
1156
1168
|
method: 'delete',
|
|
1157
1169
|
path: '/v1/impersonations',
|
|
1158
1170
|
handler: (req) => this.deleteImpersonation(req),
|
|
1159
|
-
auth: { type: 'strict', req: 'any' }
|
|
1171
|
+
auth: { type: 'strict', req: 'any' },
|
|
1172
|
+
schema: { querystring: schemas_js_1.deleteImpersonationQuerySchema }
|
|
1160
1173
|
});
|
|
1161
1174
|
const passkeysSupported = this.hasPasskeyService() &&
|
|
1162
1175
|
this.storageImplements('createPasskeyChallenge') &&
|
|
@@ -1169,12 +1182,14 @@ class AuthModule extends module_js_1.BaseAuthModule {
|
|
|
1169
1182
|
method: 'post',
|
|
1170
1183
|
path: '/v1/passkeys/challenge',
|
|
1171
1184
|
handler: (req) => this.postPasskeyChallenge(req),
|
|
1172
|
-
auth: { type: 'none', req: 'any' }
|
|
1185
|
+
auth: { type: 'none', req: 'any' },
|
|
1186
|
+
schema: { body: schemas_js_1.passkeyChallengeBodySchema }
|
|
1173
1187
|
}, {
|
|
1174
1188
|
method: 'post',
|
|
1175
1189
|
path: '/v1/passkeys/verify',
|
|
1176
1190
|
handler: (req) => this.postPasskeyVerify(req),
|
|
1177
|
-
auth: { type: 'none', req: 'any' }
|
|
1191
|
+
auth: { type: 'none', req: 'any' },
|
|
1192
|
+
schema: { body: schemas_js_1.passkeyVerifyBodySchema }
|
|
1178
1193
|
});
|
|
1179
1194
|
if (passkeyCredentialsSupported) {
|
|
1180
1195
|
routes.push({
|
|
@@ -1186,7 +1201,8 @@ class AuthModule extends module_js_1.BaseAuthModule {
|
|
|
1186
1201
|
method: 'delete',
|
|
1187
1202
|
path: '/v1/passkeys/:credentialId',
|
|
1188
1203
|
handler: (req) => this.deletePasskey(req),
|
|
1189
|
-
auth: { type: 'strict', req: 'any' }
|
|
1204
|
+
auth: { type: 'strict', req: 'any' },
|
|
1205
|
+
schema: { params: schemas_js_1.passkeyCredentialParamsSchema }
|
|
1190
1206
|
});
|
|
1191
1207
|
}
|
|
1192
1208
|
}
|
|
@@ -1196,12 +1212,14 @@ class AuthModule extends module_js_1.BaseAuthModule {
|
|
|
1196
1212
|
method: 'post',
|
|
1197
1213
|
path: '/v1/oauth2/:provider/start',
|
|
1198
1214
|
handler: (req) => this.postOAuthStart(req),
|
|
1199
|
-
auth: { type: 'none', req: 'any' }
|
|
1215
|
+
auth: { type: 'none', req: 'any' },
|
|
1216
|
+
schema: { body: schemas_js_1.oauthStartBodySchema, params: schemas_js_1.oauthProviderParamsSchema }
|
|
1200
1217
|
}, {
|
|
1201
1218
|
method: 'post',
|
|
1202
1219
|
path: '/v1/oauth2/:provider/callback',
|
|
1203
1220
|
handler: (req) => this.postOAuthCallback(req),
|
|
1204
|
-
auth: { type: 'none', req: 'any' }
|
|
1221
|
+
auth: { type: 'none', req: 'any' },
|
|
1222
|
+
schema: { params: schemas_js_1.oauthProviderParamsSchema }
|
|
1205
1223
|
});
|
|
1206
1224
|
}
|
|
1207
1225
|
const oauthStorageSupported = this.hasOAuthStore() &&
|
|
@@ -1213,12 +1231,14 @@ class AuthModule extends module_js_1.BaseAuthModule {
|
|
|
1213
1231
|
method: 'post',
|
|
1214
1232
|
path: '/v1/oauth2/authorize',
|
|
1215
1233
|
handler: (req) => this.postOAuthAuthorize(req),
|
|
1216
|
-
auth: { type: 'maybe', req: 'any' }
|
|
1234
|
+
auth: { type: 'maybe', req: 'any' },
|
|
1235
|
+
schema: { body: schemas_js_1.oauthAuthorizeBodySchema }
|
|
1217
1236
|
}, {
|
|
1218
1237
|
method: 'post',
|
|
1219
1238
|
path: '/v1/oauth2/token',
|
|
1220
1239
|
handler: (req) => this.postOAuthToken(req),
|
|
1221
|
-
auth: { type: 'none', req: 'any' }
|
|
1240
|
+
auth: { type: 'none', req: 'any' },
|
|
1241
|
+
schema: { body: schemas_js_1.oauthTokenBodySchema }
|
|
1222
1242
|
});
|
|
1223
1243
|
}
|
|
1224
1244
|
return routes;
|
|
@@ -10,7 +10,7 @@ interface CanImpersonateContext<UserEntity> {
|
|
|
10
10
|
targetUser: UserEntity;
|
|
11
11
|
effectiveUserId: AuthIdentifier;
|
|
12
12
|
}
|
|
13
|
-
type AuthRateLimitEndpoint = 'login' | 'passkey-challenge' | 'oauth-token';
|
|
13
|
+
type AuthRateLimitEndpoint = 'login' | 'passkey-challenge' | 'oauth-token' | 'oauth-authorize';
|
|
14
14
|
interface AuthModuleOptions<UserEntity> {
|
|
15
15
|
namespace?: string;
|
|
16
16
|
defaultDomain?: string;
|
package/dist/cjs/{auth-api/compat-auth-storage.js → server/src/auth-api/compat-auth-storage.cjs}
RENAMED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.CompositeAuthAdapter = void 0;
|
|
4
4
|
const node_crypto_1 = require("node:crypto");
|
|
5
|
-
const service_js_1 = require("../passkey/service.
|
|
5
|
+
const service_js_1 = require("../passkey/service.cjs");
|
|
6
6
|
class CompositeAuthAdapter {
|
|
7
7
|
constructor(options) {
|
|
8
8
|
this.userStore = options.userStore;
|
|
@@ -112,8 +112,8 @@ class CompositeAuthAdapter {
|
|
|
112
112
|
if (!this.oauthStore) {
|
|
113
113
|
return null;
|
|
114
114
|
}
|
|
115
|
-
const consumed = await this.oauthStore.consumeAuthCode(code);
|
|
116
|
-
if (!consumed
|
|
115
|
+
const consumed = await this.oauthStore.consumeAuthCode(code, clientId);
|
|
116
|
+
if (!consumed) {
|
|
117
117
|
return null;
|
|
118
118
|
}
|
|
119
119
|
return consumed;
|
|
@@ -122,7 +122,7 @@ class CompositeAuthAdapter {
|
|
|
122
122
|
if (this.canImpersonateFn) {
|
|
123
123
|
return !!(await this.canImpersonateFn(params));
|
|
124
124
|
}
|
|
125
|
-
return params.realUserId === params.effectiveUserId;
|
|
125
|
+
return String(params.realUserId) === String(params.effectiveUserId);
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
128
|
exports.CompositeAuthAdapter = CompositeAuthAdapter;
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.MemAuthStore = void 0;
|
|
4
|
-
const memory_js_1 = require("../oauth/memory.
|
|
5
|
-
const config_js_1 = require("../passkey/config.
|
|
6
|
-
const memory_js_2 = require("../passkey/memory.
|
|
7
|
-
const memory_js_3 = require("../token/memory.
|
|
8
|
-
const memory_js_4 = require("../user/memory.
|
|
9
|
-
const compat_auth_storage_js_1 = require("./compat-auth-storage.
|
|
10
|
-
const user_id_js_1 = require("./user-id.
|
|
4
|
+
const memory_js_1 = require("../oauth/memory.cjs");
|
|
5
|
+
const config_js_1 = require("../passkey/config.cjs");
|
|
6
|
+
const memory_js_2 = require("../passkey/memory.cjs");
|
|
7
|
+
const memory_js_3 = require("../token/memory.cjs");
|
|
8
|
+
const memory_js_4 = require("../user/memory.cjs");
|
|
9
|
+
const compat_auth_storage_js_1 = require("./compat-auth-storage.cjs");
|
|
10
|
+
const user_id_js_1 = require("./user-id.cjs");
|
|
11
11
|
class MemAuthStore {
|
|
12
12
|
constructor(params = {}) {
|
|
13
13
|
this.userStore = new memory_js_4.MemoryUserStore({
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.nullAuthModule = exports.BaseAuthModule = void 0;
|
|
4
|
-
const api_module_js_1 = require("../api-module.
|
|
4
|
+
const api_module_js_1 = require("../api-module.cjs");
|
|
5
5
|
// Handy base that you can extend when wiring a real auth module. Subclasses
|
|
6
6
|
// must supply a namespace via the constructor and implement token issuance.
|
|
7
7
|
class BaseAuthModule extends api_module_js_1.ApiModule {
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* JSON Schema definitions for auth module routes.
|
|
4
|
+
* These are the runtime validation source-of-truth; TypeScript interfaces
|
|
5
|
+
* in auth-module.ts remain for handler-internal typing only.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.oauthTokenBodySchema = exports.oauthAuthorizeBodySchema = exports.oauthStartBodySchema = exports.oauthProviderParamsSchema = exports.deleteImpersonationQuerySchema = exports.impersonateBodySchema = exports.passkeyCredentialParamsSchema = exports.passkeyVerifyBodySchema = exports.passkeyChallengeBodySchema = exports.whoamiBodySchema = exports.logoutBodySchema = exports.refreshBodySchema = exports.loginBodySchema = void 0;
|
|
9
|
+
/* ------------------------------------------------------------------ */
|
|
10
|
+
/* Shared fragments */
|
|
11
|
+
/* ------------------------------------------------------------------ */
|
|
12
|
+
const tokenMetadataProperties = {
|
|
13
|
+
domain: { type: 'string' },
|
|
14
|
+
fingerprint: { type: 'string' },
|
|
15
|
+
label: { type: 'string' },
|
|
16
|
+
browser: { type: 'string' },
|
|
17
|
+
device: { type: 'string' },
|
|
18
|
+
ip: { type: 'string' },
|
|
19
|
+
os: { type: 'string' }
|
|
20
|
+
};
|
|
21
|
+
const keepSessionProperty = {
|
|
22
|
+
keepSession: { type: ['boolean', 'number', 'string'] }
|
|
23
|
+
};
|
|
24
|
+
function authIdentifierProperty(name) {
|
|
25
|
+
return { [name]: { type: ['string', 'number'] } };
|
|
26
|
+
}
|
|
27
|
+
/* ------------------------------------------------------------------ */
|
|
28
|
+
/* Auth route schemas */
|
|
29
|
+
/* ------------------------------------------------------------------ */
|
|
30
|
+
exports.loginBodySchema = {
|
|
31
|
+
type: 'object',
|
|
32
|
+
required: ['login', 'password'],
|
|
33
|
+
properties: {
|
|
34
|
+
login: { type: 'string', minLength: 1 },
|
|
35
|
+
password: { type: 'string', minLength: 1 },
|
|
36
|
+
...tokenMetadataProperties,
|
|
37
|
+
...keepSessionProperty
|
|
38
|
+
},
|
|
39
|
+
additionalProperties: true
|
|
40
|
+
};
|
|
41
|
+
exports.refreshBodySchema = {
|
|
42
|
+
type: 'object',
|
|
43
|
+
properties: {
|
|
44
|
+
refreshToken: { type: 'string' },
|
|
45
|
+
domain: { type: 'string' },
|
|
46
|
+
fingerprint: { type: 'string' },
|
|
47
|
+
label: { type: 'string' },
|
|
48
|
+
...keepSessionProperty
|
|
49
|
+
},
|
|
50
|
+
additionalProperties: false
|
|
51
|
+
};
|
|
52
|
+
exports.logoutBodySchema = {
|
|
53
|
+
type: 'object',
|
|
54
|
+
properties: {
|
|
55
|
+
token: { type: 'string' },
|
|
56
|
+
refreshToken: { type: 'string' }
|
|
57
|
+
},
|
|
58
|
+
additionalProperties: false
|
|
59
|
+
};
|
|
60
|
+
exports.whoamiBodySchema = {
|
|
61
|
+
type: 'object',
|
|
62
|
+
properties: {
|
|
63
|
+
refreshToken: { type: 'string' },
|
|
64
|
+
refresh: { type: 'boolean' }
|
|
65
|
+
},
|
|
66
|
+
additionalProperties: false
|
|
67
|
+
};
|
|
68
|
+
/* ------------------------------------------------------------------ */
|
|
69
|
+
/* Passkey schemas */
|
|
70
|
+
/* ------------------------------------------------------------------ */
|
|
71
|
+
exports.passkeyChallengeBodySchema = {
|
|
72
|
+
type: 'object',
|
|
73
|
+
required: ['action'],
|
|
74
|
+
properties: {
|
|
75
|
+
action: { type: 'string', enum: ['register', 'authenticate'] },
|
|
76
|
+
login: { type: 'string' },
|
|
77
|
+
...authIdentifierProperty('userId')
|
|
78
|
+
},
|
|
79
|
+
additionalProperties: false
|
|
80
|
+
};
|
|
81
|
+
exports.passkeyVerifyBodySchema = {
|
|
82
|
+
type: 'object',
|
|
83
|
+
required: ['expectedChallenge', 'response'],
|
|
84
|
+
properties: {
|
|
85
|
+
expectedChallenge: { type: 'string' },
|
|
86
|
+
response: { type: 'object' },
|
|
87
|
+
login: { type: 'string' },
|
|
88
|
+
...authIdentifierProperty('userId'),
|
|
89
|
+
userAgent: { type: 'string' },
|
|
90
|
+
...tokenMetadataProperties,
|
|
91
|
+
...keepSessionProperty
|
|
92
|
+
},
|
|
93
|
+
additionalProperties: true
|
|
94
|
+
};
|
|
95
|
+
exports.passkeyCredentialParamsSchema = {
|
|
96
|
+
type: 'object',
|
|
97
|
+
required: ['credentialId'],
|
|
98
|
+
properties: {
|
|
99
|
+
credentialId: { type: 'string', minLength: 1 }
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
/* ------------------------------------------------------------------ */
|
|
103
|
+
/* Impersonation schemas */
|
|
104
|
+
/* ------------------------------------------------------------------ */
|
|
105
|
+
exports.impersonateBodySchema = {
|
|
106
|
+
type: 'object',
|
|
107
|
+
properties: {
|
|
108
|
+
...authIdentifierProperty('userId'),
|
|
109
|
+
login: { type: 'string' },
|
|
110
|
+
...tokenMetadataProperties,
|
|
111
|
+
...keepSessionProperty,
|
|
112
|
+
clientId: { type: 'string' },
|
|
113
|
+
scope: {},
|
|
114
|
+
loginType: { type: 'string' }
|
|
115
|
+
},
|
|
116
|
+
additionalProperties: true
|
|
117
|
+
};
|
|
118
|
+
exports.deleteImpersonationQuerySchema = {
|
|
119
|
+
type: 'object',
|
|
120
|
+
additionalProperties: true
|
|
121
|
+
};
|
|
122
|
+
/* ------------------------------------------------------------------ */
|
|
123
|
+
/* OAuth schemas */
|
|
124
|
+
/* ------------------------------------------------------------------ */
|
|
125
|
+
exports.oauthProviderParamsSchema = {
|
|
126
|
+
type: 'object',
|
|
127
|
+
required: ['provider'],
|
|
128
|
+
properties: {
|
|
129
|
+
provider: { type: 'string', minLength: 1 }
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
exports.oauthStartBodySchema = {
|
|
133
|
+
type: 'object',
|
|
134
|
+
properties: {
|
|
135
|
+
redirectUri: { type: 'string' },
|
|
136
|
+
scope: {},
|
|
137
|
+
state: { type: 'string' },
|
|
138
|
+
extras: { type: 'object' }
|
|
139
|
+
},
|
|
140
|
+
additionalProperties: true
|
|
141
|
+
};
|
|
142
|
+
exports.oauthAuthorizeBodySchema = {
|
|
143
|
+
type: 'object',
|
|
144
|
+
required: ['clientId', 'redirectUri'],
|
|
145
|
+
properties: {
|
|
146
|
+
clientId: { type: 'string', minLength: 1 },
|
|
147
|
+
redirectUri: { type: 'string', minLength: 1 },
|
|
148
|
+
scope: {},
|
|
149
|
+
state: { type: 'string' },
|
|
150
|
+
codeChallenge: { type: 'string' },
|
|
151
|
+
codeChallengeMethod: { type: 'string' },
|
|
152
|
+
login: { type: 'string' },
|
|
153
|
+
password: { type: 'string' }
|
|
154
|
+
},
|
|
155
|
+
additionalProperties: false
|
|
156
|
+
};
|
|
157
|
+
exports.oauthTokenBodySchema = {
|
|
158
|
+
type: 'object',
|
|
159
|
+
required: ['grant_type'],
|
|
160
|
+
properties: {
|
|
161
|
+
grant_type: { type: 'string', enum: ['authorization_code', 'refresh_token'] },
|
|
162
|
+
code: { type: 'string' },
|
|
163
|
+
redirect_uri: { type: 'string' },
|
|
164
|
+
code_verifier: { type: 'string' },
|
|
165
|
+
client_id: { type: 'string' },
|
|
166
|
+
client_secret: { type: 'string' },
|
|
167
|
+
refresh_token: { type: 'string' },
|
|
168
|
+
scope: { type: 'string' }
|
|
169
|
+
},
|
|
170
|
+
additionalProperties: false
|
|
171
|
+
};
|