@technomoron/api-server-base 2.0.0-beta.18 → 2.0.0-beta.19
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/api-module.cjs +9 -0
- package/dist/cjs/api-module.d.ts +4 -2
- package/dist/cjs/api-server-base.cjs +59 -37
- package/dist/cjs/api-server-base.d.ts +5 -0
- package/dist/cjs/auth-api/auth-module.d.ts +12 -1
- package/dist/cjs/auth-api/auth-module.js +42 -34
- package/dist/cjs/auth-api/mem-auth-store.js +2 -24
- package/dist/cjs/auth-api/sql-auth-store.js +4 -32
- package/dist/cjs/auth-api/user-id.d.ts +4 -0
- package/dist/cjs/auth-api/user-id.js +31 -0
- package/dist/cjs/auth-cookie-options.d.ts +11 -0
- package/dist/cjs/auth-cookie-options.js +57 -0
- package/dist/cjs/oauth/memory.js +2 -9
- package/dist/cjs/oauth/models.js +4 -15
- package/dist/cjs/oauth/sequelize.js +6 -22
- package/dist/cjs/passkey/config.d.ts +2 -0
- package/dist/cjs/passkey/config.js +26 -0
- package/dist/cjs/passkey/memory.js +2 -9
- package/dist/cjs/passkey/models.js +4 -15
- package/dist/cjs/passkey/sequelize.js +6 -22
- package/dist/cjs/sequelize-utils.d.ts +3 -0
- package/dist/cjs/sequelize-utils.js +17 -0
- package/dist/cjs/token/memory.d.ts +4 -0
- package/dist/cjs/token/memory.js +90 -25
- package/dist/cjs/token/sequelize.js +16 -22
- package/dist/cjs/token/types.d.ts +7 -0
- package/dist/cjs/user/memory.js +2 -9
- package/dist/cjs/user/sequelize.js +6 -22
- package/dist/esm/api-module.d.ts +4 -2
- package/dist/esm/api-module.js +9 -0
- package/dist/esm/api-server-base.d.ts +5 -0
- package/dist/esm/api-server-base.js +59 -37
- package/dist/esm/auth-api/auth-module.d.ts +12 -1
- package/dist/esm/auth-api/auth-module.js +42 -34
- package/dist/esm/auth-api/mem-auth-store.js +1 -23
- package/dist/esm/auth-api/sql-auth-store.js +2 -30
- package/dist/esm/auth-api/user-id.d.ts +4 -0
- package/dist/esm/auth-api/user-id.js +26 -0
- package/dist/esm/auth-cookie-options.d.ts +11 -0
- package/dist/esm/auth-cookie-options.js +54 -0
- package/dist/esm/oauth/memory.js +2 -9
- package/dist/esm/oauth/models.js +1 -12
- package/dist/esm/oauth/sequelize.js +3 -19
- package/dist/esm/passkey/config.d.ts +2 -0
- package/dist/esm/passkey/config.js +23 -0
- package/dist/esm/passkey/memory.js +2 -9
- package/dist/esm/passkey/models.js +1 -12
- package/dist/esm/passkey/sequelize.js +3 -19
- package/dist/esm/sequelize-utils.d.ts +3 -0
- package/dist/esm/sequelize-utils.js +12 -0
- package/dist/esm/token/memory.d.ts +4 -0
- package/dist/esm/token/memory.js +90 -25
- package/dist/esm/token/sequelize.js +12 -18
- package/dist/esm/token/types.d.ts +7 -0
- package/dist/esm/user/memory.js +2 -9
- package/dist/esm/user/sequelize.js +3 -19
- package/docs/swagger/openapi.json +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildAuthCookieOptions = buildAuthCookieOptions;
|
|
4
|
+
function firstHeaderValue(value) {
|
|
5
|
+
if (typeof value === 'string') {
|
|
6
|
+
return value;
|
|
7
|
+
}
|
|
8
|
+
if (Array.isArray(value)) {
|
|
9
|
+
return value[0] ?? '';
|
|
10
|
+
}
|
|
11
|
+
return '';
|
|
12
|
+
}
|
|
13
|
+
function resolveOriginHostname(origin) {
|
|
14
|
+
try {
|
|
15
|
+
const url = new URL(origin);
|
|
16
|
+
const hostname = url.hostname.trim().toLowerCase();
|
|
17
|
+
return hostname.length > 0 ? hostname : null;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function isLocalhostOrigin(origin) {
|
|
24
|
+
const hostname = resolveOriginHostname(origin);
|
|
25
|
+
if (!hostname) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
return hostname === 'localhost' || hostname.endsWith('.localhost');
|
|
29
|
+
}
|
|
30
|
+
function buildAuthCookieOptions(config, req) {
|
|
31
|
+
const forwardedProto = firstHeaderValue(req.headers['x-forwarded-proto']).split(',')[0].trim().toLowerCase();
|
|
32
|
+
const isHttps = forwardedProto === 'https' || req.protocol === 'https';
|
|
33
|
+
const origin = firstHeaderValue(req.headers.origin ?? req.headers.referer);
|
|
34
|
+
const secure = config.cookieSecure === true ? true : config.cookieSecure === false ? false : /* auto */ Boolean(isHttps);
|
|
35
|
+
let sameSite = config.cookieSameSite ?? 'lax';
|
|
36
|
+
if (sameSite !== 'lax' && sameSite !== 'strict' && sameSite !== 'none') {
|
|
37
|
+
sameSite = 'lax';
|
|
38
|
+
}
|
|
39
|
+
let resolvedSecure = secure;
|
|
40
|
+
if (sameSite === 'none' && resolvedSecure !== true) {
|
|
41
|
+
// Modern browsers reject SameSite=None cookies unless Secure is set.
|
|
42
|
+
resolvedSecure = true;
|
|
43
|
+
}
|
|
44
|
+
const options = {
|
|
45
|
+
httpOnly: config.cookieHttpOnly ?? true,
|
|
46
|
+
secure: resolvedSecure,
|
|
47
|
+
sameSite,
|
|
48
|
+
domain: config.cookieDomain || undefined,
|
|
49
|
+
path: config.cookiePath || '/',
|
|
50
|
+
maxAge: undefined
|
|
51
|
+
};
|
|
52
|
+
if (config.devMode && isLocalhostOrigin(origin)) {
|
|
53
|
+
// Domain cookies do not work on localhost; avoid breaking local development when cookieDomain is set.
|
|
54
|
+
options.domain = undefined;
|
|
55
|
+
}
|
|
56
|
+
return options;
|
|
57
|
+
}
|
package/dist/cjs/oauth/memory.js
CHANGED
|
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.MemoryOAuthStore = void 0;
|
|
7
7
|
const bcryptjs_1 = __importDefault(require("bcryptjs"));
|
|
8
|
+
const user_id_js_1 = require("../auth-api/user-id.js");
|
|
8
9
|
const base_js_1 = require("./base.js");
|
|
9
10
|
function cloneClient(client) {
|
|
10
11
|
if (!client) {
|
|
@@ -29,15 +30,7 @@ function cloneCode(code) {
|
|
|
29
30
|
metadata: code.metadata ? { ...code.metadata } : undefined
|
|
30
31
|
};
|
|
31
32
|
}
|
|
32
|
-
|
|
33
|
-
if (typeof identifier === 'number' && Number.isFinite(identifier)) {
|
|
34
|
-
return identifier;
|
|
35
|
-
}
|
|
36
|
-
if (typeof identifier === 'string' && /^\d+$/.test(identifier)) {
|
|
37
|
-
return Number(identifier);
|
|
38
|
-
}
|
|
39
|
-
throw new Error(`Unable to normalise user identifier: ${identifier}`);
|
|
40
|
-
}
|
|
33
|
+
const normalizeUserId = user_id_js_1.normalizeNumericUserId;
|
|
41
34
|
class MemoryOAuthStore extends base_js_1.OAuthStore {
|
|
42
35
|
constructor(options = {}) {
|
|
43
36
|
super();
|
package/dist/cjs/oauth/models.js
CHANGED
|
@@ -4,27 +4,16 @@ exports.OAuthCodeModel = exports.OAuthClientModel = void 0;
|
|
|
4
4
|
exports.initOAuthClientModel = initOAuthClientModel;
|
|
5
5
|
exports.initOAuthCodeModel = initOAuthCodeModel;
|
|
6
6
|
const sequelize_1 = require("sequelize");
|
|
7
|
-
const
|
|
8
|
-
function normalizeTablePrefix(prefix) {
|
|
9
|
-
if (!prefix) {
|
|
10
|
-
return undefined;
|
|
11
|
-
}
|
|
12
|
-
const trimmed = prefix.trim();
|
|
13
|
-
return trimmed.length > 0 ? trimmed : undefined;
|
|
14
|
-
}
|
|
15
|
-
function applyTablePrefix(prefix, tableName) {
|
|
16
|
-
const normalized = normalizeTablePrefix(prefix);
|
|
17
|
-
return normalized ? `${normalized}${tableName}` : tableName;
|
|
18
|
-
}
|
|
7
|
+
const sequelize_utils_js_1 = require("../sequelize-utils.js");
|
|
19
8
|
function integerIdType(sequelize) {
|
|
20
|
-
return DIALECTS_SUPPORTING_UNSIGNED.has(sequelize.getDialect()) ? sequelize_1.DataTypes.INTEGER.UNSIGNED : sequelize_1.DataTypes.INTEGER;
|
|
9
|
+
return sequelize_utils_js_1.DIALECTS_SUPPORTING_UNSIGNED.has(sequelize.getDialect()) ? sequelize_1.DataTypes.INTEGER.UNSIGNED : sequelize_1.DataTypes.INTEGER;
|
|
21
10
|
}
|
|
22
11
|
function tableOptions(sequelize, tableName, tablePrefix, extra) {
|
|
23
|
-
const opts = { sequelize, tableName: applyTablePrefix(tablePrefix, tableName) };
|
|
12
|
+
const opts = { sequelize, tableName: (0, sequelize_utils_js_1.applyTablePrefix)(tablePrefix, tableName) };
|
|
24
13
|
if (extra) {
|
|
25
14
|
Object.assign(opts, extra);
|
|
26
15
|
}
|
|
27
|
-
if (DIALECTS_SUPPORTING_UNSIGNED.has(sequelize.getDialect())) {
|
|
16
|
+
if (sequelize_utils_js_1.DIALECTS_SUPPORTING_UNSIGNED.has(sequelize.getDialect())) {
|
|
28
17
|
opts.charset = 'utf8mb4';
|
|
29
18
|
opts.collate = 'utf8mb4_unicode_ci';
|
|
30
19
|
}
|
|
@@ -8,28 +8,18 @@ exports.initOAuthClientModel = initOAuthClientModel;
|
|
|
8
8
|
exports.initOAuthCodeModel = initOAuthCodeModel;
|
|
9
9
|
const bcryptjs_1 = __importDefault(require("bcryptjs"));
|
|
10
10
|
const sequelize_1 = require("sequelize");
|
|
11
|
+
const user_id_js_1 = require("../auth-api/user-id.js");
|
|
12
|
+
const sequelize_utils_js_1 = require("../sequelize-utils.js");
|
|
11
13
|
const base_js_1 = require("./base.js");
|
|
12
|
-
const DIALECTS_SUPPORTING_UNSIGNED = new Set(['mysql', 'mariadb']);
|
|
13
|
-
function normalizeTablePrefix(prefix) {
|
|
14
|
-
if (!prefix) {
|
|
15
|
-
return undefined;
|
|
16
|
-
}
|
|
17
|
-
const trimmed = prefix.trim();
|
|
18
|
-
return trimmed.length > 0 ? trimmed : undefined;
|
|
19
|
-
}
|
|
20
|
-
function applyTablePrefix(prefix, tableName) {
|
|
21
|
-
const normalized = normalizeTablePrefix(prefix);
|
|
22
|
-
return normalized ? `${normalized}${tableName}` : tableName;
|
|
23
|
-
}
|
|
24
14
|
function integerIdType(sequelize) {
|
|
25
|
-
return DIALECTS_SUPPORTING_UNSIGNED.has(sequelize.getDialect()) ? sequelize_1.DataTypes.INTEGER.UNSIGNED : sequelize_1.DataTypes.INTEGER;
|
|
15
|
+
return sequelize_utils_js_1.DIALECTS_SUPPORTING_UNSIGNED.has(sequelize.getDialect()) ? sequelize_1.DataTypes.INTEGER.UNSIGNED : sequelize_1.DataTypes.INTEGER;
|
|
26
16
|
}
|
|
27
17
|
function tableOptions(sequelize, tableName, tablePrefix, extra) {
|
|
28
|
-
const opts = { sequelize, tableName: applyTablePrefix(tablePrefix, tableName) };
|
|
18
|
+
const opts = { sequelize, tableName: (0, sequelize_utils_js_1.applyTablePrefix)(tablePrefix, tableName) };
|
|
29
19
|
if (extra) {
|
|
30
20
|
Object.assign(opts, extra);
|
|
31
21
|
}
|
|
32
|
-
if (DIALECTS_SUPPORTING_UNSIGNED.has(sequelize.getDialect())) {
|
|
22
|
+
if (sequelize_utils_js_1.DIALECTS_SUPPORTING_UNSIGNED.has(sequelize.getDialect())) {
|
|
33
23
|
opts.charset = 'utf8mb4';
|
|
34
24
|
opts.collate = 'utf8mb4_unicode_ci';
|
|
35
25
|
}
|
|
@@ -111,13 +101,7 @@ function parseMetadata(raw) {
|
|
|
111
101
|
return undefined;
|
|
112
102
|
}
|
|
113
103
|
function normalizeUserId(identifier) {
|
|
114
|
-
|
|
115
|
-
return identifier;
|
|
116
|
-
}
|
|
117
|
-
if (typeof identifier === 'string' && /^\d+$/.test(identifier)) {
|
|
118
|
-
return Number(identifier);
|
|
119
|
-
}
|
|
120
|
-
throw new Error(`Unable to normalise user identifier: ${identifier}`);
|
|
104
|
+
return (0, user_id_js_1.normalizeNumericUserId)(identifier);
|
|
121
105
|
}
|
|
122
106
|
class SequelizeOAuthStore extends base_js_1.OAuthStore {
|
|
123
107
|
constructor(options) {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.normalizePasskeyConfig = normalizePasskeyConfig;
|
|
4
|
+
const DEFAULT_PASSKEY_CONFIG = {
|
|
5
|
+
rpId: 'localhost',
|
|
6
|
+
rpName: 'API Server',
|
|
7
|
+
origins: ['http://localhost:5173'],
|
|
8
|
+
timeoutMs: 5 * 60 * 1000,
|
|
9
|
+
userVerification: 'preferred'
|
|
10
|
+
};
|
|
11
|
+
function isOriginString(origin) {
|
|
12
|
+
return typeof origin === 'string' && origin.trim().length > 0;
|
|
13
|
+
}
|
|
14
|
+
function normalizePasskeyConfig(config = {}) {
|
|
15
|
+
const candidateOrigins = Array.isArray(config.origins) && config.origins.length > 0 ? config.origins.filter(isOriginString) : null;
|
|
16
|
+
return {
|
|
17
|
+
rpId: config.rpId?.trim() || DEFAULT_PASSKEY_CONFIG.rpId,
|
|
18
|
+
rpName: config.rpName?.trim() || DEFAULT_PASSKEY_CONFIG.rpName,
|
|
19
|
+
origins: candidateOrigins ? candidateOrigins.map((origin) => origin.trim()) : DEFAULT_PASSKEY_CONFIG.origins,
|
|
20
|
+
timeoutMs: typeof config.timeoutMs === 'number' && config.timeoutMs > 0
|
|
21
|
+
? config.timeoutMs
|
|
22
|
+
: DEFAULT_PASSKEY_CONFIG.timeoutMs,
|
|
23
|
+
userVerification: config.userVerification ?? DEFAULT_PASSKEY_CONFIG.userVerification,
|
|
24
|
+
debug: Boolean(config.debug)
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -1,19 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.MemoryPasskeyStore = void 0;
|
|
4
|
+
const user_id_js_1 = require("../auth-api/user-id.js");
|
|
4
5
|
const base_js_1 = require("./base.js");
|
|
5
6
|
function encodeCredentialId(value) {
|
|
6
7
|
return Buffer.isBuffer(value) ? value.toString('base64') : value;
|
|
7
8
|
}
|
|
8
|
-
|
|
9
|
-
if (typeof identifier === 'number' && Number.isFinite(identifier)) {
|
|
10
|
-
return identifier;
|
|
11
|
-
}
|
|
12
|
-
if (typeof identifier === 'string' && /^\d+$/.test(identifier)) {
|
|
13
|
-
return Number(identifier);
|
|
14
|
-
}
|
|
15
|
-
return identifier;
|
|
16
|
-
}
|
|
9
|
+
const normalizeUserId = user_id_js_1.normalizeComparableUserId;
|
|
17
10
|
function cloneCredential(record) {
|
|
18
11
|
return {
|
|
19
12
|
...record,
|
|
@@ -4,20 +4,9 @@ exports.PasskeyChallengeModel = exports.PasskeyCredentialModel = void 0;
|
|
|
4
4
|
exports.initPasskeyCredentialModel = initPasskeyCredentialModel;
|
|
5
5
|
exports.initPasskeyChallengeModel = initPasskeyChallengeModel;
|
|
6
6
|
const sequelize_1 = require("sequelize");
|
|
7
|
-
const
|
|
8
|
-
function normalizeTablePrefix(prefix) {
|
|
9
|
-
if (!prefix) {
|
|
10
|
-
return undefined;
|
|
11
|
-
}
|
|
12
|
-
const trimmed = prefix.trim();
|
|
13
|
-
return trimmed.length > 0 ? trimmed : undefined;
|
|
14
|
-
}
|
|
15
|
-
function applyTablePrefix(prefix, tableName) {
|
|
16
|
-
const normalized = normalizeTablePrefix(prefix);
|
|
17
|
-
return normalized ? `${normalized}${tableName}` : tableName;
|
|
18
|
-
}
|
|
7
|
+
const sequelize_utils_js_1 = require("../sequelize-utils.js");
|
|
19
8
|
function integerIdType(sequelize) {
|
|
20
|
-
return DIALECTS_SUPPORTING_UNSIGNED.has(sequelize.getDialect()) ? sequelize_1.DataTypes.INTEGER.UNSIGNED : sequelize_1.DataTypes.INTEGER;
|
|
9
|
+
return sequelize_utils_js_1.DIALECTS_SUPPORTING_UNSIGNED.has(sequelize.getDialect()) ? sequelize_1.DataTypes.INTEGER.UNSIGNED : sequelize_1.DataTypes.INTEGER;
|
|
21
10
|
}
|
|
22
11
|
class PasskeyCredentialModel extends sequelize_1.Model {
|
|
23
12
|
}
|
|
@@ -115,7 +104,7 @@ function initPasskeyCredentialModel(sequelize, options = {}) {
|
|
|
115
104
|
}
|
|
116
105
|
}, {
|
|
117
106
|
sequelize,
|
|
118
|
-
tableName: applyTablePrefix(options.tablePrefix, 'passkey_credentials'),
|
|
107
|
+
tableName: (0, sequelize_utils_js_1.applyTablePrefix)(options.tablePrefix, 'passkey_credentials'),
|
|
119
108
|
timestamps: true,
|
|
120
109
|
underscored: true
|
|
121
110
|
});
|
|
@@ -148,7 +137,7 @@ function initPasskeyChallengeModel(sequelize, options = {}) {
|
|
|
148
137
|
}
|
|
149
138
|
}, {
|
|
150
139
|
sequelize,
|
|
151
|
-
tableName: applyTablePrefix(options.tablePrefix, 'passkey_challenges'),
|
|
140
|
+
tableName: (0, sequelize_utils_js_1.applyTablePrefix)(options.tablePrefix, 'passkey_challenges'),
|
|
152
141
|
timestamps: true,
|
|
153
142
|
underscored: true,
|
|
154
143
|
indexes: [{ fields: ['expires_at'] }, { fields: ['user_id'] }]
|
|
@@ -2,33 +2,17 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.SequelizePasskeyStore = void 0;
|
|
4
4
|
const sequelize_1 = require("sequelize");
|
|
5
|
+
const user_id_js_1 = require("../auth-api/user-id.js");
|
|
6
|
+
const sequelize_utils_js_1 = require("../sequelize-utils.js");
|
|
5
7
|
const base_js_1 = require("./base.js");
|
|
6
|
-
const DIALECTS_SUPPORTING_UNSIGNED = new Set(['mysql', 'mariadb']);
|
|
7
|
-
function normalizeTablePrefix(prefix) {
|
|
8
|
-
if (!prefix) {
|
|
9
|
-
return undefined;
|
|
10
|
-
}
|
|
11
|
-
const trimmed = prefix.trim();
|
|
12
|
-
return trimmed.length > 0 ? trimmed : undefined;
|
|
13
|
-
}
|
|
14
|
-
function applyTablePrefix(prefix, tableName) {
|
|
15
|
-
const normalized = normalizeTablePrefix(prefix);
|
|
16
|
-
return normalized ? `${normalized}${tableName}` : tableName;
|
|
17
|
-
}
|
|
18
8
|
function integerIdType(sequelize) {
|
|
19
|
-
return DIALECTS_SUPPORTING_UNSIGNED.has(sequelize.getDialect()) ? sequelize_1.DataTypes.INTEGER.UNSIGNED : sequelize_1.DataTypes.INTEGER;
|
|
9
|
+
return sequelize_utils_js_1.DIALECTS_SUPPORTING_UNSIGNED.has(sequelize.getDialect()) ? sequelize_1.DataTypes.INTEGER.UNSIGNED : sequelize_1.DataTypes.INTEGER;
|
|
20
10
|
}
|
|
21
11
|
function encodeCredentialId(value) {
|
|
22
12
|
return Buffer.isBuffer(value) ? value.toString('base64') : value;
|
|
23
13
|
}
|
|
24
14
|
function normalizeUserId(identifier) {
|
|
25
|
-
|
|
26
|
-
return identifier;
|
|
27
|
-
}
|
|
28
|
-
if (typeof identifier === 'string' && /^\d+$/.test(identifier)) {
|
|
29
|
-
return Number(identifier);
|
|
30
|
-
}
|
|
31
|
-
throw new Error(`Unable to normalise user identifier: ${identifier}`);
|
|
15
|
+
return (0, user_id_js_1.normalizeNumericUserId)(identifier);
|
|
32
16
|
}
|
|
33
17
|
class PasskeyCredentialModel extends sequelize_1.Model {
|
|
34
18
|
}
|
|
@@ -124,7 +108,7 @@ function initPasskeyCredentialModel(sequelize, options = {}) {
|
|
|
124
108
|
}
|
|
125
109
|
}, {
|
|
126
110
|
sequelize,
|
|
127
|
-
tableName: applyTablePrefix(options.tablePrefix, 'passkey_credentials'),
|
|
111
|
+
tableName: (0, sequelize_utils_js_1.applyTablePrefix)(options.tablePrefix, 'passkey_credentials'),
|
|
128
112
|
timestamps: true,
|
|
129
113
|
underscored: true
|
|
130
114
|
});
|
|
@@ -157,7 +141,7 @@ function initPasskeyChallengeModel(sequelize, options = {}) {
|
|
|
157
141
|
}
|
|
158
142
|
}, {
|
|
159
143
|
sequelize,
|
|
160
|
-
tableName: applyTablePrefix(options.tablePrefix, 'passkey_challenges'),
|
|
144
|
+
tableName: (0, sequelize_utils_js_1.applyTablePrefix)(options.tablePrefix, 'passkey_challenges'),
|
|
161
145
|
timestamps: true,
|
|
162
146
|
underscored: true,
|
|
163
147
|
indexes: [{ fields: ['expires_at'] }, { fields: ['user_id'] }]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DIALECTS_SUPPORTING_UNSIGNED = void 0;
|
|
4
|
+
exports.normalizeTablePrefix = normalizeTablePrefix;
|
|
5
|
+
exports.applyTablePrefix = applyTablePrefix;
|
|
6
|
+
exports.DIALECTS_SUPPORTING_UNSIGNED = new Set(['mysql', 'mariadb']);
|
|
7
|
+
function normalizeTablePrefix(prefix) {
|
|
8
|
+
if (!prefix) {
|
|
9
|
+
return undefined;
|
|
10
|
+
}
|
|
11
|
+
const trimmed = prefix.trim();
|
|
12
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
13
|
+
}
|
|
14
|
+
function applyTablePrefix(prefix, tableName) {
|
|
15
|
+
const normalized = normalizeTablePrefix(prefix);
|
|
16
|
+
return normalized ? `${normalized}${tableName}` : tableName;
|
|
17
|
+
}
|
|
@@ -2,6 +2,10 @@ import { TokenStore } from './base.js';
|
|
|
2
2
|
import type { Token } from './types.js';
|
|
3
3
|
export declare class MemoryTokenStore extends TokenStore {
|
|
4
4
|
private readonly tokens;
|
|
5
|
+
private readonly tokensByUser;
|
|
6
|
+
private indexToken;
|
|
7
|
+
private unindexToken;
|
|
8
|
+
private removeByRefreshToken;
|
|
5
9
|
save(record: Token): Promise<void>;
|
|
6
10
|
get(query: Partial<Token>, opts?: {
|
|
7
11
|
includeExpired?: boolean;
|
package/dist/cjs/token/memory.js
CHANGED
|
@@ -9,12 +9,12 @@ function comparableUserId(value) {
|
|
|
9
9
|
return String(value);
|
|
10
10
|
}
|
|
11
11
|
function cloneToken(record) {
|
|
12
|
-
// this.normalizeToken is not available in static context; caller passes through instance.
|
|
13
|
-
// cloning handled via store instance methods.
|
|
14
|
-
const normalized = record;
|
|
15
12
|
return {
|
|
16
|
-
...
|
|
17
|
-
scope:
|
|
13
|
+
...record,
|
|
14
|
+
scope: record.scope ? [...record.scope] : undefined,
|
|
15
|
+
expires: record.expires ? new Date(record.expires) : undefined,
|
|
16
|
+
issuedAt: record.issuedAt ? new Date(record.issuedAt) : undefined,
|
|
17
|
+
lastSeenAt: record.lastSeenAt ? new Date(record.lastSeenAt) : undefined
|
|
18
18
|
};
|
|
19
19
|
}
|
|
20
20
|
function matchesQuery(record, query, includeExpired) {
|
|
@@ -50,15 +50,57 @@ function matchesQuery(record, query, includeExpired) {
|
|
|
50
50
|
class MemoryTokenStore extends base_js_1.TokenStore {
|
|
51
51
|
constructor() {
|
|
52
52
|
super(...arguments);
|
|
53
|
-
this.tokens =
|
|
53
|
+
this.tokens = new Map();
|
|
54
|
+
this.tokensByUser = new Map();
|
|
55
|
+
}
|
|
56
|
+
indexToken(token) {
|
|
57
|
+
const userId = comparableUserId(token.userId);
|
|
58
|
+
if (!userId) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
let userTokens = this.tokensByUser.get(userId);
|
|
62
|
+
if (!userTokens) {
|
|
63
|
+
userTokens = new Set();
|
|
64
|
+
this.tokensByUser.set(userId, userTokens);
|
|
65
|
+
}
|
|
66
|
+
userTokens.add(token.refreshToken);
|
|
67
|
+
}
|
|
68
|
+
unindexToken(token) {
|
|
69
|
+
const userId = comparableUserId(token.userId);
|
|
70
|
+
if (!userId) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const userTokens = this.tokensByUser.get(userId);
|
|
74
|
+
if (!userTokens) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
userTokens.delete(token.refreshToken);
|
|
78
|
+
if (userTokens.size === 0) {
|
|
79
|
+
this.tokensByUser.delete(userId);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
removeByRefreshToken(refreshToken) {
|
|
83
|
+
const existing = this.tokens.get(refreshToken);
|
|
84
|
+
if (!existing) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
this.unindexToken(existing);
|
|
88
|
+
this.tokens.delete(refreshToken);
|
|
54
89
|
}
|
|
55
90
|
async save(record) {
|
|
56
91
|
const stored = this.normalizeToken(record);
|
|
57
92
|
const normalizedUserId = comparableUserId(stored.userId);
|
|
93
|
+
if (!normalizedUserId) {
|
|
94
|
+
throw new Error('userId is required');
|
|
95
|
+
}
|
|
58
96
|
const domainProvided = record.domain !== undefined;
|
|
59
97
|
const fingerprintProvided = record.fingerprint !== undefined;
|
|
60
|
-
|
|
61
|
-
|
|
98
|
+
const userRefreshTokens = [...(this.tokensByUser.get(normalizedUserId) ?? [])];
|
|
99
|
+
for (const refreshToken of userRefreshTokens) {
|
|
100
|
+
const existing = this.tokens.get(refreshToken);
|
|
101
|
+
if (!existing) {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
62
104
|
if (comparableUserId(existing.userId) !== normalizedUserId) {
|
|
63
105
|
continue;
|
|
64
106
|
}
|
|
@@ -71,44 +113,51 @@ class MemoryTokenStore extends base_js_1.TokenStore {
|
|
|
71
113
|
if (fingerprintProvided && existing.fingerprint !== stored.fingerprint) {
|
|
72
114
|
continue;
|
|
73
115
|
}
|
|
74
|
-
this.
|
|
116
|
+
this.removeByRefreshToken(existing.refreshToken);
|
|
75
117
|
}
|
|
76
|
-
this.
|
|
118
|
+
this.removeByRefreshToken(stored.refreshToken);
|
|
119
|
+
this.tokens.set(stored.refreshToken, stored);
|
|
120
|
+
this.indexToken(stored);
|
|
77
121
|
}
|
|
78
122
|
async get(query, opts) {
|
|
79
123
|
if (!query.refreshToken && !query.accessToken && query.userId === undefined) {
|
|
80
124
|
throw new Error('At least one token lookup field must be provided');
|
|
81
125
|
}
|
|
82
126
|
const includeExpired = opts?.includeExpired ?? false;
|
|
83
|
-
|
|
84
|
-
|
|
127
|
+
if (query.refreshToken) {
|
|
128
|
+
const record = this.tokens.get(query.refreshToken);
|
|
129
|
+
return record && matchesQuery(record, query, includeExpired) ? cloneToken(record) : null;
|
|
130
|
+
}
|
|
131
|
+
for (const token of this.tokens.values()) {
|
|
132
|
+
if (matchesQuery(token, query, includeExpired)) {
|
|
133
|
+
return cloneToken(token);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return null;
|
|
85
137
|
}
|
|
86
138
|
async delete(query) {
|
|
87
139
|
if (!query.refreshToken && !query.accessToken && query.userId === undefined && !query.clientId) {
|
|
88
140
|
return 0;
|
|
89
141
|
}
|
|
90
142
|
let removed = 0;
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
143
|
+
const refreshTokens = [...this.tokens.keys()];
|
|
144
|
+
for (const refreshToken of refreshTokens) {
|
|
145
|
+
const token = this.tokens.get(refreshToken);
|
|
146
|
+
if (token && matchesQuery(token, query, true)) {
|
|
147
|
+
this.removeByRefreshToken(refreshToken);
|
|
94
148
|
removed += 1;
|
|
95
149
|
}
|
|
96
150
|
}
|
|
97
151
|
return removed;
|
|
98
152
|
}
|
|
99
153
|
async update(params) {
|
|
100
|
-
const token = this.tokens.
|
|
101
|
-
if (record.refreshToken !== params.refreshToken) {
|
|
102
|
-
return false;
|
|
103
|
-
}
|
|
104
|
-
if (params.clientId && record.clientId !== params.clientId) {
|
|
105
|
-
return false;
|
|
106
|
-
}
|
|
107
|
-
return true;
|
|
108
|
-
});
|
|
154
|
+
const token = this.tokens.get(params.refreshToken);
|
|
109
155
|
if (!token) {
|
|
110
156
|
return false;
|
|
111
157
|
}
|
|
158
|
+
if (params.clientId && token.clientId !== params.clientId) {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
112
161
|
const merged = { ...token };
|
|
113
162
|
const maybeAssign = (key) => {
|
|
114
163
|
const value = params[key];
|
|
@@ -132,12 +181,28 @@ class MemoryTokenStore extends base_js_1.TokenStore {
|
|
|
132
181
|
maybeAssign('lastSeenAt');
|
|
133
182
|
maybeAssign('sessionCookie');
|
|
134
183
|
const normalized = this.normalizeToken(merged);
|
|
184
|
+
const previousUserId = token.userId;
|
|
185
|
+
const previousRefreshToken = token.refreshToken;
|
|
186
|
+
const userChanged = comparableUserId(previousUserId) !== comparableUserId(normalized.userId);
|
|
135
187
|
Object.assign(token, normalized);
|
|
188
|
+
if (userChanged || previousRefreshToken !== token.refreshToken) {
|
|
189
|
+
this.unindexToken({ ...token, userId: previousUserId, refreshToken: previousRefreshToken });
|
|
190
|
+
this.indexToken(token);
|
|
191
|
+
if (previousRefreshToken !== token.refreshToken) {
|
|
192
|
+
this.tokens.delete(previousRefreshToken);
|
|
193
|
+
this.tokens.set(token.refreshToken, token);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
136
196
|
return true;
|
|
137
197
|
}
|
|
138
198
|
async list(userId, opts = {}) {
|
|
139
199
|
const includeExpired = opts.includeExpired ?? false;
|
|
140
|
-
const
|
|
200
|
+
const normalizedUserId = comparableUserId(userId);
|
|
201
|
+
const userRefreshTokens = normalizedUserId ? [...(this.tokensByUser.get(normalizedUserId) ?? [])] : [];
|
|
202
|
+
const filtered = userRefreshTokens
|
|
203
|
+
.map((refreshToken) => this.tokens.get(refreshToken))
|
|
204
|
+
.filter((token) => Boolean(token))
|
|
205
|
+
.filter((token) => matchesQuery(token, { userId: normalizedUserId }, includeExpired));
|
|
141
206
|
const offset = opts.offset ?? 0;
|
|
142
207
|
const limit = opts.limit ?? filtered.length;
|
|
143
208
|
return filtered.slice(offset, offset + limit).map(cloneToken);
|
|
@@ -2,41 +2,31 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.SequelizeTokenStore = void 0;
|
|
4
4
|
const sequelize_1 = require("sequelize");
|
|
5
|
+
const user_id_js_1 = require("../auth-api/user-id.js");
|
|
6
|
+
const sequelize_utils_js_1 = require("../sequelize-utils.js");
|
|
5
7
|
const base_js_1 = require("./base.js");
|
|
6
|
-
const DIALECTS_SUPPORTING_UNSIGNED = new Set(['mysql', 'mariadb']);
|
|
7
|
-
function normalizeTablePrefix(prefix) {
|
|
8
|
-
if (!prefix) {
|
|
9
|
-
return undefined;
|
|
10
|
-
}
|
|
11
|
-
const trimmed = prefix.trim();
|
|
12
|
-
return trimmed.length > 0 ? trimmed : undefined;
|
|
13
|
-
}
|
|
14
|
-
function applyTablePrefix(prefix, tableName) {
|
|
15
|
-
const normalized = normalizeTablePrefix(prefix);
|
|
16
|
-
return normalized ? `${normalized}${tableName}` : tableName;
|
|
17
|
-
}
|
|
18
8
|
class TokenModel extends sequelize_1.Model {
|
|
19
9
|
}
|
|
20
10
|
function tokenTableOptions(sequelize, tablePrefix) {
|
|
21
11
|
const opts = {
|
|
22
12
|
sequelize,
|
|
23
|
-
tableName: applyTablePrefix(tablePrefix, 'jwttokens'),
|
|
13
|
+
tableName: (0, sequelize_utils_js_1.applyTablePrefix)(tablePrefix, 'jwttokens'),
|
|
24
14
|
timestamps: false
|
|
25
15
|
};
|
|
26
|
-
if (DIALECTS_SUPPORTING_UNSIGNED.has(sequelize.getDialect())) {
|
|
16
|
+
if (sequelize_utils_js_1.DIALECTS_SUPPORTING_UNSIGNED.has(sequelize.getDialect())) {
|
|
27
17
|
opts.charset = 'utf8mb4';
|
|
28
18
|
opts.collate = 'utf8mb4_unicode_ci';
|
|
29
19
|
}
|
|
30
20
|
return opts;
|
|
31
21
|
}
|
|
32
22
|
function initTokenModel(sequelize, options = {}) {
|
|
33
|
-
const tableName = applyTablePrefix(options.tablePrefix, 'jwttokens');
|
|
23
|
+
const tableName = (0, sequelize_utils_js_1.applyTablePrefix)(options.tablePrefix, 'jwttokens');
|
|
34
24
|
const usePrefixedIndexNames = tableName !== 'jwttokens';
|
|
35
25
|
const accessIndexName = usePrefixedIndexNames ? `${tableName}_access_unique` : 'jwt_access_unique';
|
|
36
26
|
const refreshIndexName = usePrefixedIndexNames ? `${tableName}_refresh_unique` : 'jwt_refresh_unique';
|
|
37
27
|
TokenModel.init({
|
|
38
28
|
token_id: {
|
|
39
|
-
type: DIALECTS_SUPPORTING_UNSIGNED.has(sequelize.getDialect())
|
|
29
|
+
type: sequelize_utils_js_1.DIALECTS_SUPPORTING_UNSIGNED.has(sequelize.getDialect())
|
|
40
30
|
? sequelize_1.DataTypes.INTEGER.UNSIGNED
|
|
41
31
|
: sequelize_1.DataTypes.INTEGER,
|
|
42
32
|
autoIncrement: true,
|
|
@@ -67,11 +57,11 @@ function initTokenModel(sequelize, options = {}) {
|
|
|
67
57
|
defaultValue: sequelize_1.DataTypes.NOW
|
|
68
58
|
},
|
|
69
59
|
access: {
|
|
70
|
-
type: sequelize_1.DataTypes.STRING(
|
|
60
|
+
type: sequelize_1.DataTypes.STRING(768),
|
|
71
61
|
allowNull: false
|
|
72
62
|
},
|
|
73
63
|
refresh: {
|
|
74
|
-
type: sequelize_1.DataTypes.STRING(
|
|
64
|
+
type: sequelize_1.DataTypes.STRING(768),
|
|
75
65
|
allowNull: false
|
|
76
66
|
},
|
|
77
67
|
domain: {
|
|
@@ -184,6 +174,13 @@ class SequelizeTokenStore extends base_js_1.TokenStore {
|
|
|
184
174
|
removalWhere.client_id = normalized.clientId;
|
|
185
175
|
}
|
|
186
176
|
await this.Tokens.destroy({ where: removalWhere });
|
|
177
|
+
// Access/refresh columns are unique. Remove stale collisions before insert to avoid
|
|
178
|
+
// transient uniqueness failures during retries/rotation edge-cases.
|
|
179
|
+
await this.Tokens.destroy({
|
|
180
|
+
where: {
|
|
181
|
+
[sequelize_1.Op.or]: [{ access: normalized.accessToken ?? '' }, { refresh: normalized.refreshToken }]
|
|
182
|
+
}
|
|
183
|
+
});
|
|
187
184
|
await this.Tokens.create({
|
|
188
185
|
user_id: resolvedUserId,
|
|
189
186
|
real_user_id: resolvedRealUserId,
|
|
@@ -343,10 +340,7 @@ class SequelizeTokenStore extends base_js_1.TokenStore {
|
|
|
343
340
|
return;
|
|
344
341
|
}
|
|
345
342
|
normalizeUserId(identifier) {
|
|
346
|
-
|
|
347
|
-
throw new Error(`Unable to normalise user identifier: ${identifier}`);
|
|
348
|
-
}
|
|
349
|
-
return String(identifier);
|
|
343
|
+
return (0, user_id_js_1.normalizeStringUserId)(identifier);
|
|
350
344
|
}
|
|
351
345
|
resolveRealUserId(ruid) {
|
|
352
346
|
if (ruid === undefined || ruid === null) {
|
|
@@ -9,7 +9,14 @@ export interface Token {
|
|
|
9
9
|
status?: TokenStatus;
|
|
10
10
|
ruid?: string;
|
|
11
11
|
clientId?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Optional session partition key. Token stores may use `domain` and `fingerprint`
|
|
14
|
+
* to replace previous sessions that match the same bucket.
|
|
15
|
+
*/
|
|
12
16
|
domain?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Optional device/session fingerprint used together with `domain` for session bucketing.
|
|
19
|
+
*/
|
|
13
20
|
fingerprint?: string;
|
|
14
21
|
label?: string;
|
|
15
22
|
browser?: string;
|
package/dist/cjs/user/memory.js
CHANGED
|
@@ -1,19 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.MemoryUserStore = void 0;
|
|
4
|
+
const user_id_js_1 = require("../auth-api/user-id.js");
|
|
4
5
|
const base_js_1 = require("./base.js");
|
|
5
6
|
function cloneUser(user) {
|
|
6
7
|
return { ...user };
|
|
7
8
|
}
|
|
8
|
-
|
|
9
|
-
if (typeof identifier === 'number' && Number.isFinite(identifier)) {
|
|
10
|
-
return identifier;
|
|
11
|
-
}
|
|
12
|
-
if (typeof identifier === 'string' && /^\d+$/.test(identifier)) {
|
|
13
|
-
return Number(identifier);
|
|
14
|
-
}
|
|
15
|
-
throw new Error(`Unable to normalise user identifier: ${identifier}`);
|
|
16
|
-
}
|
|
9
|
+
const normalizeUserId = user_id_js_1.normalizeNumericUserId;
|
|
17
10
|
class MemoryUserStore extends base_js_1.UserStore {
|
|
18
11
|
constructor(options = {}) {
|
|
19
12
|
super({
|