@technomoron/apicore-server 1.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/cjs/api-module.cjs +34 -0
- package/dist/cjs/api-module.d.ts +45 -0
- package/dist/cjs/apicore-server.cjs +1561 -0
- package/dist/cjs/apicore-server.d.ts +288 -0
- package/dist/cjs/auth-api/auth-module.cjs +1248 -0
- package/dist/cjs/auth-api/auth-module.d.ts +116 -0
- package/dist/cjs/auth-api/compat-auth-storage.cjs +128 -0
- package/dist/cjs/auth-api/compat-auth-storage.d.ts +57 -0
- package/dist/cjs/auth-api/mem-auth-store.cjs +121 -0
- package/dist/cjs/auth-api/mem-auth-store.d.ts +68 -0
- package/dist/cjs/auth-api/module.cjs +25 -0
- package/dist/cjs/auth-api/module.d.ts +20 -0
- package/dist/cjs/auth-api/schemas.cjs +171 -0
- package/dist/cjs/auth-api/schemas.d.ts +21 -0
- package/dist/cjs/auth-api/sql-auth-store.cjs +179 -0
- package/dist/cjs/auth-api/sql-auth-store.d.ts +87 -0
- package/dist/cjs/auth-api/storage.cjs +102 -0
- package/dist/cjs/auth-api/storage.d.ts +38 -0
- package/dist/cjs/auth-api/types.cjs +2 -0
- package/dist/cjs/auth-api/types.d.ts +34 -0
- package/dist/cjs/auth-api/user-id.cjs +47 -0
- package/dist/cjs/auth-api/user-id.d.ts +5 -0
- package/dist/cjs/auth-cookie-options.cjs +66 -0
- package/dist/cjs/auth-cookie-options.d.ts +13 -0
- package/dist/cjs/base/client-info.cjs +285 -0
- package/dist/cjs/base/client-info.d.ts +27 -0
- package/dist/cjs/base/error-utils.cjs +50 -0
- package/dist/cjs/base/error-utils.d.ts +16 -0
- package/dist/cjs/base/request-utils.cjs +27 -0
- package/dist/cjs/base/request-utils.d.ts +8 -0
- package/dist/cjs/index.cjs +51 -0
- package/dist/cjs/index.d.ts +34 -0
- package/dist/cjs/limiter/auth-rate-limiter.cjs +35 -0
- package/dist/cjs/limiter/auth-rate-limiter.d.ts +12 -0
- package/dist/cjs/limiter/fixed-window.cjs +41 -0
- package/dist/cjs/limiter/fixed-window.d.ts +11 -0
- package/dist/cjs/oauth/base.cjs +7 -0
- package/dist/cjs/oauth/base.d.ts +17 -0
- package/dist/cjs/oauth/memory.cjs +135 -0
- package/dist/cjs/oauth/memory.d.ts +22 -0
- package/dist/cjs/oauth/models.cjs +47 -0
- package/dist/cjs/oauth/models.d.ts +50 -0
- package/dist/cjs/oauth/sequelize.cjs +159 -0
- package/dist/cjs/oauth/sequelize.d.ts +30 -0
- package/dist/cjs/oauth/types.cjs +3 -0
- package/dist/cjs/oauth/types.d.ts +51 -0
- package/dist/cjs/passkey/base.cjs +7 -0
- package/dist/cjs/passkey/base.d.ts +28 -0
- package/dist/cjs/passkey/config.cjs +26 -0
- package/dist/cjs/passkey/config.d.ts +2 -0
- package/dist/cjs/passkey/memory.cjs +123 -0
- package/dist/cjs/passkey/memory.d.ts +34 -0
- package/dist/cjs/passkey/models.cjs +142 -0
- package/dist/cjs/passkey/models.d.ts +34 -0
- package/dist/cjs/passkey/sequelize.cjs +126 -0
- package/dist/cjs/passkey/sequelize.d.ts +42 -0
- package/dist/cjs/passkey/service.cjs +413 -0
- package/dist/cjs/passkey/service.d.ts +21 -0
- package/dist/cjs/passkey/types.cjs +2 -0
- package/dist/cjs/passkey/types.d.ts +84 -0
- package/dist/cjs/sequelize-utils.cjs +56 -0
- package/dist/cjs/sequelize-utils.d.ts +8 -0
- package/dist/cjs/token/base.cjs +120 -0
- package/dist/cjs/token/base.d.ts +46 -0
- package/dist/cjs/token/memory.cjs +234 -0
- package/dist/cjs/token/memory.d.ts +29 -0
- package/dist/cjs/token/sequelize.cjs +400 -0
- package/dist/cjs/token/sequelize.d.ts +58 -0
- package/dist/cjs/token/types.cjs +2 -0
- package/dist/cjs/token/types.d.ts +34 -0
- package/dist/cjs/upload/memory.cjs +92 -0
- package/dist/cjs/upload/memory.d.ts +17 -0
- package/dist/cjs/upload/tus-module.cjs +270 -0
- package/dist/cjs/upload/tus-module.d.ts +38 -0
- package/dist/cjs/upload/types.cjs +2 -0
- package/dist/cjs/upload/types.d.ts +28 -0
- package/dist/cjs/user/base.cjs +53 -0
- package/dist/cjs/user/base.d.ts +36 -0
- package/dist/cjs/user/memory.cjs +194 -0
- package/dist/cjs/user/memory.d.ts +37 -0
- package/dist/cjs/user/sequelize.cjs +194 -0
- package/dist/cjs/user/sequelize.d.ts +46 -0
- package/dist/cjs/user/types.cjs +2 -0
- package/dist/cjs/user/types.d.ts +11 -0
- package/dist/esm/api-module.d.ts +45 -0
- package/dist/esm/api-module.js +30 -0
- package/dist/esm/apicore-server.d.ts +288 -0
- package/dist/esm/apicore-server.js +1552 -0
- package/dist/esm/auth-api/auth-module.d.ts +116 -0
- package/dist/esm/auth-api/auth-module.js +1246 -0
- package/dist/esm/auth-api/compat-auth-storage.d.ts +57 -0
- package/dist/esm/auth-api/compat-auth-storage.js +124 -0
- package/dist/esm/auth-api/mem-auth-store.d.ts +68 -0
- package/dist/esm/auth-api/mem-auth-store.js +117 -0
- package/dist/esm/auth-api/module.d.ts +20 -0
- package/dist/esm/auth-api/module.js +21 -0
- package/dist/esm/auth-api/schemas.d.ts +21 -0
- package/dist/esm/auth-api/schemas.js +168 -0
- package/dist/esm/auth-api/sql-auth-store.d.ts +87 -0
- package/dist/esm/auth-api/sql-auth-store.js +175 -0
- package/dist/esm/auth-api/storage.d.ts +38 -0
- package/dist/esm/auth-api/storage.js +98 -0
- package/dist/esm/auth-api/types.d.ts +34 -0
- package/dist/esm/auth-api/types.js +1 -0
- package/dist/esm/auth-api/user-id.d.ts +5 -0
- package/dist/esm/auth-api/user-id.js +41 -0
- package/dist/esm/auth-cookie-options.d.ts +13 -0
- package/dist/esm/auth-cookie-options.js +63 -0
- package/dist/esm/base/client-info.d.ts +27 -0
- package/dist/esm/base/client-info.js +282 -0
- package/dist/esm/base/error-utils.d.ts +16 -0
- package/dist/esm/base/error-utils.js +44 -0
- package/dist/esm/base/request-utils.d.ts +8 -0
- package/dist/esm/base/request-utils.js +23 -0
- package/dist/esm/index.d.ts +34 -0
- package/dist/esm/index.js +21 -0
- package/dist/esm/limiter/auth-rate-limiter.d.ts +12 -0
- package/dist/esm/limiter/auth-rate-limiter.js +32 -0
- package/dist/esm/limiter/fixed-window.d.ts +11 -0
- package/dist/esm/limiter/fixed-window.js +37 -0
- package/dist/esm/oauth/base.d.ts +17 -0
- package/dist/esm/oauth/base.js +3 -0
- package/dist/esm/oauth/memory.d.ts +22 -0
- package/dist/esm/oauth/memory.js +128 -0
- package/dist/esm/oauth/models.d.ts +50 -0
- package/dist/esm/oauth/models.js +38 -0
- package/dist/esm/oauth/sequelize.d.ts +30 -0
- package/dist/esm/oauth/sequelize.js +148 -0
- package/dist/esm/oauth/types.d.ts +51 -0
- package/dist/esm/oauth/types.js +2 -0
- package/dist/esm/passkey/base.d.ts +28 -0
- package/dist/esm/passkey/base.js +3 -0
- package/dist/esm/passkey/config.d.ts +2 -0
- package/dist/esm/passkey/config.js +23 -0
- package/dist/esm/passkey/memory.d.ts +34 -0
- package/dist/esm/passkey/memory.js +119 -0
- package/dist/esm/passkey/models.d.ts +34 -0
- package/dist/esm/passkey/models.js +135 -0
- package/dist/esm/passkey/sequelize.d.ts +42 -0
- package/dist/esm/passkey/sequelize.js +122 -0
- package/dist/esm/passkey/service.d.ts +21 -0
- package/dist/esm/passkey/service.js +376 -0
- package/dist/esm/passkey/types.d.ts +84 -0
- package/dist/esm/passkey/types.js +1 -0
- package/dist/esm/sequelize-utils.d.ts +8 -0
- package/dist/esm/sequelize-utils.js +47 -0
- package/dist/esm/token/base.d.ts +46 -0
- package/dist/esm/token/base.js +113 -0
- package/dist/esm/token/memory.d.ts +29 -0
- package/dist/esm/token/memory.js +230 -0
- package/dist/esm/token/sequelize.d.ts +58 -0
- package/dist/esm/token/sequelize.js +396 -0
- package/dist/esm/token/types.d.ts +34 -0
- package/dist/esm/token/types.js +1 -0
- package/dist/esm/upload/memory.d.ts +17 -0
- package/dist/esm/upload/memory.js +86 -0
- package/dist/esm/upload/tus-module.d.ts +38 -0
- package/dist/esm/upload/tus-module.js +266 -0
- package/dist/esm/upload/types.d.ts +28 -0
- package/dist/esm/upload/types.js +1 -0
- package/dist/esm/user/base.d.ts +36 -0
- package/dist/esm/user/base.js +46 -0
- package/dist/esm/user/memory.d.ts +37 -0
- package/dist/esm/user/memory.js +190 -0
- package/dist/esm/user/sequelize.d.ts +46 -0
- package/dist/esm/user/sequelize.js +188 -0
- package/dist/esm/user/types.d.ts +11 -0
- package/dist/esm/user/types.js +1 -0
- package/docs/swagger/openapi.json +2162 -0
- package/package.json +131 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.TokenStore = void 0;
|
|
7
|
+
const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
|
|
8
|
+
function normalizeScope(scope) {
|
|
9
|
+
if (!scope) {
|
|
10
|
+
return undefined;
|
|
11
|
+
}
|
|
12
|
+
if (Array.isArray(scope)) {
|
|
13
|
+
return scope.filter((entry) => typeof entry === 'string' && entry.length > 0);
|
|
14
|
+
}
|
|
15
|
+
return scope
|
|
16
|
+
.split(/\s+/)
|
|
17
|
+
.map((entry) => entry.trim())
|
|
18
|
+
.filter((entry) => entry.length > 0);
|
|
19
|
+
}
|
|
20
|
+
function normalizeRefreshTtlSeconds(value) {
|
|
21
|
+
if (typeof value === 'number' && Number.isFinite(value) && value > 0) {
|
|
22
|
+
return Math.floor(value);
|
|
23
|
+
}
|
|
24
|
+
if (typeof value === 'string') {
|
|
25
|
+
const parsed = Number(value);
|
|
26
|
+
if (Number.isFinite(parsed) && parsed > 0) {
|
|
27
|
+
return Math.floor(parsed);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
function normalizeTokenInternal(input) {
|
|
33
|
+
if (!input.refreshToken) {
|
|
34
|
+
throw new Error('refreshToken is required');
|
|
35
|
+
}
|
|
36
|
+
if (!input.accessToken) {
|
|
37
|
+
throw new Error('accessToken is required');
|
|
38
|
+
}
|
|
39
|
+
if (input.userId === undefined || input.userId === null) {
|
|
40
|
+
throw new Error('userId is required');
|
|
41
|
+
}
|
|
42
|
+
const userId = String(input.userId);
|
|
43
|
+
const ruid = input.ruid === undefined || input.ruid === null ? undefined : String(input.ruid);
|
|
44
|
+
const expires = input.expires ? new Date(input.expires) : new Date(Date.now() + 30 * 24 * 60 * 60 * 1000);
|
|
45
|
+
if (Number.isNaN(expires.getTime())) {
|
|
46
|
+
throw new Error(`Invalid token expiry value: ${String(input.expires)}`);
|
|
47
|
+
}
|
|
48
|
+
const issuedAt = input.issuedAt ? new Date(input.issuedAt) : new Date();
|
|
49
|
+
const lastSeenAt = input.lastSeenAt ? new Date(input.lastSeenAt) : issuedAt;
|
|
50
|
+
const scope = normalizeScope(input.scope);
|
|
51
|
+
const refreshTtlSeconds = normalizeRefreshTtlSeconds(input.refreshTtlSeconds);
|
|
52
|
+
const status = input.status === 'revoked' ? 'revoked' : expires.getTime() < Date.now() ? 'expired' : 'active';
|
|
53
|
+
const sessionCookie = typeof input.sessionCookie === 'boolean' ? input.sessionCookie : false;
|
|
54
|
+
return {
|
|
55
|
+
accessToken: input.accessToken,
|
|
56
|
+
refreshToken: input.refreshToken,
|
|
57
|
+
userId,
|
|
58
|
+
clientId: typeof input.clientId === 'string' && input.clientId.length > 0 ? input.clientId : undefined,
|
|
59
|
+
domain: typeof input.domain === 'string' ? input.domain : '',
|
|
60
|
+
fingerprint: typeof input.fingerprint === 'string' ? input.fingerprint : '',
|
|
61
|
+
label: typeof input.label === 'string' ? input.label : '',
|
|
62
|
+
browser: typeof input.browser === 'string' ? input.browser : '',
|
|
63
|
+
device: typeof input.device === 'string' ? input.device : '',
|
|
64
|
+
ip: typeof input.ip === 'string' ? input.ip : '',
|
|
65
|
+
os: typeof input.os === 'string' ? input.os : '',
|
|
66
|
+
loginType: typeof input.loginType === 'string' && input.loginType.length > 0 ? input.loginType : undefined,
|
|
67
|
+
scope,
|
|
68
|
+
refreshTtlSeconds,
|
|
69
|
+
expires,
|
|
70
|
+
issuedAt,
|
|
71
|
+
lastSeenAt,
|
|
72
|
+
status,
|
|
73
|
+
ruid,
|
|
74
|
+
sessionCookie
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/** Base contract for token/session persistence backends plus shared JWT helpers. */
|
|
78
|
+
class TokenStore {
|
|
79
|
+
// Instance helpers
|
|
80
|
+
normalizeToken(token) {
|
|
81
|
+
return normalizeTokenInternal(token);
|
|
82
|
+
}
|
|
83
|
+
// JWT helpers live on TokenStore so every adapter automatically exposes
|
|
84
|
+
// signing/verification without additional wiring.
|
|
85
|
+
jwtSign(payload, secret, expiresInSeconds, options) {
|
|
86
|
+
const opts = { ...(options ?? {}), expiresIn: expiresInSeconds };
|
|
87
|
+
try {
|
|
88
|
+
const token = jsonwebtoken_1.default.sign(payload, secret, opts);
|
|
89
|
+
return { success: true, token };
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
jwtVerify(token, secret, options) {
|
|
96
|
+
try {
|
|
97
|
+
const data = jsonwebtoken_1.default.verify(token, secret, options ?? {});
|
|
98
|
+
return { success: true, data };
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
if (error instanceof jsonwebtoken_1.default.TokenExpiredError) {
|
|
102
|
+
return { success: false, expired: true, error: 'Token expired' };
|
|
103
|
+
}
|
|
104
|
+
return { success: false, expired: false, error: error instanceof Error ? error.message : String(error) };
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
jwtDecode(token, options) {
|
|
108
|
+
try {
|
|
109
|
+
const data = jsonwebtoken_1.default.decode(token, options ?? {});
|
|
110
|
+
if (data === null) {
|
|
111
|
+
return { success: false, error: 'Invalid token format' };
|
|
112
|
+
}
|
|
113
|
+
return { success: true, data };
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
exports.TokenStore = TokenStore;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { Token } from './types.js';
|
|
2
|
+
import type { DecodeOptions, SignOptions, VerifyOptions } from 'jsonwebtoken';
|
|
3
|
+
export interface JwtSignResult {
|
|
4
|
+
success: boolean;
|
|
5
|
+
token?: string;
|
|
6
|
+
error?: string;
|
|
7
|
+
}
|
|
8
|
+
export type JwtSignPayload = string | Buffer | Record<string, unknown>;
|
|
9
|
+
export interface JwtVerifyResult<T> {
|
|
10
|
+
success: boolean;
|
|
11
|
+
data?: T;
|
|
12
|
+
expired?: boolean;
|
|
13
|
+
error?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface JwtDecodeResult<T> {
|
|
16
|
+
success: boolean;
|
|
17
|
+
data?: T;
|
|
18
|
+
error?: string;
|
|
19
|
+
}
|
|
20
|
+
/** Base contract for token/session persistence backends plus shared JWT helpers. */
|
|
21
|
+
export declare abstract class TokenStore {
|
|
22
|
+
/** Create/persist a token record. */
|
|
23
|
+
abstract save(record: Token): Promise<void>;
|
|
24
|
+
/** Read a token record by partial query. */
|
|
25
|
+
abstract get(query: Partial<Token>, opts?: {
|
|
26
|
+
includeExpired?: boolean;
|
|
27
|
+
}): Promise<Token | null>;
|
|
28
|
+
/** Delete token records matching a partial query. */
|
|
29
|
+
abstract delete(query: Partial<Token>): Promise<number>;
|
|
30
|
+
/** Update a token identified by refresh token. */
|
|
31
|
+
abstract update(update: Partial<Token> & {
|
|
32
|
+
refreshToken: string;
|
|
33
|
+
}): Promise<boolean>;
|
|
34
|
+
/** List tokens for a specific user. */
|
|
35
|
+
abstract list(userId: string | number, opts?: {
|
|
36
|
+
limit?: number;
|
|
37
|
+
offset?: number;
|
|
38
|
+
includeExpired?: boolean;
|
|
39
|
+
}): Promise<Token[]>;
|
|
40
|
+
/** Close underlying resources. */
|
|
41
|
+
abstract close(): Promise<void>;
|
|
42
|
+
normalizeToken(token: Partial<Token>): Token;
|
|
43
|
+
jwtSign(payload: JwtSignPayload, secret: string, expiresInSeconds: number, options?: SignOptions): JwtSignResult;
|
|
44
|
+
jwtVerify<T>(token: string, secret: string, options?: VerifyOptions): JwtVerifyResult<T>;
|
|
45
|
+
jwtDecode<T>(token: string, options?: DecodeOptions): JwtDecodeResult<T>;
|
|
46
|
+
}
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MemoryTokenStore = void 0;
|
|
4
|
+
const base_js_1 = require("./base.cjs");
|
|
5
|
+
function comparableUserId(value) {
|
|
6
|
+
if (value === undefined) {
|
|
7
|
+
return undefined;
|
|
8
|
+
}
|
|
9
|
+
return String(value);
|
|
10
|
+
}
|
|
11
|
+
function cloneToken(record) {
|
|
12
|
+
return {
|
|
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
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function matchesQuery(record, query, includeExpired) {
|
|
21
|
+
if (query.refreshToken !== undefined && record.refreshToken !== query.refreshToken) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
if (query.accessToken !== undefined && record.accessToken !== query.accessToken) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
if (query.userId !== undefined && comparableUserId(record.userId) !== comparableUserId(query.userId)) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
if (query.clientId !== undefined && record.clientId !== query.clientId) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
if (query.domain !== undefined && (record.domain ?? '') !== (query.domain ?? '')) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
if (query.fingerprint !== undefined && (record.fingerprint ?? '') !== (query.fingerprint ?? '')) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
if (query.loginType !== undefined && record.loginType !== (query.loginType ?? undefined)) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
if (query.label !== undefined && record.label !== query.label) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
if (!includeExpired) {
|
|
46
|
+
const expiresTime = record.expires ? record.expires.getTime() : Infinity;
|
|
47
|
+
if (expiresTime < Date.now()) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
class MemoryTokenStore extends base_js_1.TokenStore {
|
|
54
|
+
constructor(options = {}) {
|
|
55
|
+
super();
|
|
56
|
+
this.tokens = new Map();
|
|
57
|
+
this.tokensByUser = new Map();
|
|
58
|
+
this.maxTokens =
|
|
59
|
+
typeof options.maxTokens === 'number' && Number.isFinite(options.maxTokens) && options.maxTokens > 0
|
|
60
|
+
? Math.floor(options.maxTokens)
|
|
61
|
+
: undefined;
|
|
62
|
+
}
|
|
63
|
+
indexToken(token) {
|
|
64
|
+
const userId = comparableUserId(token.userId);
|
|
65
|
+
if (!userId) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
let userTokens = this.tokensByUser.get(userId);
|
|
69
|
+
if (!userTokens) {
|
|
70
|
+
userTokens = new Set();
|
|
71
|
+
this.tokensByUser.set(userId, userTokens);
|
|
72
|
+
}
|
|
73
|
+
userTokens.add(token.refreshToken);
|
|
74
|
+
}
|
|
75
|
+
unindexToken(token) {
|
|
76
|
+
const userId = comparableUserId(token.userId);
|
|
77
|
+
if (!userId) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const userTokens = this.tokensByUser.get(userId);
|
|
81
|
+
if (!userTokens) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
userTokens.delete(token.refreshToken);
|
|
85
|
+
if (userTokens.size === 0) {
|
|
86
|
+
this.tokensByUser.delete(userId);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
removeByRefreshToken(refreshToken) {
|
|
90
|
+
const existing = this.tokens.get(refreshToken);
|
|
91
|
+
if (!existing) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
this.unindexToken(existing);
|
|
95
|
+
this.tokens.delete(refreshToken);
|
|
96
|
+
}
|
|
97
|
+
async save(record) {
|
|
98
|
+
const stored = this.normalizeToken(record);
|
|
99
|
+
const normalizedUserId = comparableUserId(stored.userId);
|
|
100
|
+
if (!normalizedUserId) {
|
|
101
|
+
throw new Error('userId is required');
|
|
102
|
+
}
|
|
103
|
+
const domainProvided = record.domain !== undefined;
|
|
104
|
+
const fingerprintProvided = record.fingerprint !== undefined;
|
|
105
|
+
const userRefreshTokens = [...(this.tokensByUser.get(normalizedUserId) ?? [])];
|
|
106
|
+
for (const refreshToken of userRefreshTokens) {
|
|
107
|
+
const existing = this.tokens.get(refreshToken);
|
|
108
|
+
if (!existing) {
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
if (comparableUserId(existing.userId) !== normalizedUserId) {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (record.clientId && existing.clientId !== record.clientId) {
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (domainProvided && existing.domain !== stored.domain) {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (fingerprintProvided && existing.fingerprint !== stored.fingerprint) {
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
this.removeByRefreshToken(existing.refreshToken);
|
|
124
|
+
}
|
|
125
|
+
this.removeByRefreshToken(stored.refreshToken);
|
|
126
|
+
this.tokens.set(stored.refreshToken, stored);
|
|
127
|
+
this.indexToken(stored);
|
|
128
|
+
this.enforceCapacity();
|
|
129
|
+
}
|
|
130
|
+
async get(query, opts) {
|
|
131
|
+
if (!query.refreshToken && !query.accessToken && query.userId === undefined) {
|
|
132
|
+
throw new Error('At least one token lookup field must be provided');
|
|
133
|
+
}
|
|
134
|
+
const includeExpired = opts?.includeExpired ?? false;
|
|
135
|
+
if (query.refreshToken) {
|
|
136
|
+
const record = this.tokens.get(query.refreshToken);
|
|
137
|
+
return record && matchesQuery(record, query, includeExpired) ? cloneToken(record) : null;
|
|
138
|
+
}
|
|
139
|
+
for (const token of this.tokens.values()) {
|
|
140
|
+
if (matchesQuery(token, query, includeExpired)) {
|
|
141
|
+
return cloneToken(token);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
async delete(query) {
|
|
147
|
+
if (!query.refreshToken && !query.accessToken && query.userId === undefined && !query.clientId) {
|
|
148
|
+
return 0;
|
|
149
|
+
}
|
|
150
|
+
let removed = 0;
|
|
151
|
+
const refreshTokens = [...this.tokens.keys()];
|
|
152
|
+
for (const refreshToken of refreshTokens) {
|
|
153
|
+
const token = this.tokens.get(refreshToken);
|
|
154
|
+
if (token && matchesQuery(token, query, true)) {
|
|
155
|
+
this.removeByRefreshToken(refreshToken);
|
|
156
|
+
removed += 1;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return removed;
|
|
160
|
+
}
|
|
161
|
+
async update(params) {
|
|
162
|
+
const token = this.tokens.get(params.refreshToken);
|
|
163
|
+
if (!token) {
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
if (params.clientId && token.clientId !== params.clientId) {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
const merged = { ...token };
|
|
170
|
+
const maybeAssign = (key) => {
|
|
171
|
+
const value = params[key];
|
|
172
|
+
if (value !== undefined && value !== null) {
|
|
173
|
+
merged[key] = value;
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
maybeAssign('accessToken');
|
|
177
|
+
maybeAssign('expires');
|
|
178
|
+
maybeAssign('issuedAt');
|
|
179
|
+
maybeAssign('lastSeenAt');
|
|
180
|
+
maybeAssign('scope');
|
|
181
|
+
maybeAssign('label');
|
|
182
|
+
maybeAssign('domain');
|
|
183
|
+
maybeAssign('fingerprint');
|
|
184
|
+
maybeAssign('browser');
|
|
185
|
+
maybeAssign('device');
|
|
186
|
+
maybeAssign('ip');
|
|
187
|
+
maybeAssign('os');
|
|
188
|
+
maybeAssign('refreshTtlSeconds');
|
|
189
|
+
maybeAssign('loginType');
|
|
190
|
+
maybeAssign('sessionCookie');
|
|
191
|
+
const normalized = this.normalizeToken(merged);
|
|
192
|
+
const previousUserId = token.userId;
|
|
193
|
+
const previousRefreshToken = token.refreshToken;
|
|
194
|
+
const userChanged = comparableUserId(previousUserId) !== comparableUserId(normalized.userId);
|
|
195
|
+
Object.assign(token, normalized);
|
|
196
|
+
if (userChanged || previousRefreshToken !== token.refreshToken) {
|
|
197
|
+
this.unindexToken({ ...token, userId: previousUserId, refreshToken: previousRefreshToken });
|
|
198
|
+
this.indexToken(token);
|
|
199
|
+
if (previousRefreshToken !== token.refreshToken) {
|
|
200
|
+
this.tokens.delete(previousRefreshToken);
|
|
201
|
+
this.tokens.set(token.refreshToken, token);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return true;
|
|
205
|
+
}
|
|
206
|
+
async list(userId, opts = {}) {
|
|
207
|
+
const includeExpired = opts.includeExpired ?? false;
|
|
208
|
+
const normalizedUserId = comparableUserId(userId);
|
|
209
|
+
const userRefreshTokens = normalizedUserId ? [...(this.tokensByUser.get(normalizedUserId) ?? [])] : [];
|
|
210
|
+
const filtered = userRefreshTokens
|
|
211
|
+
.map((refreshToken) => this.tokens.get(refreshToken))
|
|
212
|
+
.filter((token) => Boolean(token))
|
|
213
|
+
.filter((token) => matchesQuery(token, { userId: normalizedUserId }, includeExpired));
|
|
214
|
+
const offset = opts.offset ?? 0;
|
|
215
|
+
const limit = opts.limit ?? filtered.length;
|
|
216
|
+
return filtered.slice(offset, offset + limit).map(cloneToken);
|
|
217
|
+
}
|
|
218
|
+
async close() {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
enforceCapacity() {
|
|
222
|
+
if (!this.maxTokens) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
while (this.tokens.size > this.maxTokens) {
|
|
226
|
+
const oldestRefresh = this.tokens.keys().next().value;
|
|
227
|
+
if (!oldestRefresh) {
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
this.removeByRefreshToken(oldestRefresh);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
exports.MemoryTokenStore = MemoryTokenStore;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { TokenStore } from './base.js';
|
|
2
|
+
import type { Token } from './types.js';
|
|
3
|
+
export interface MemoryTokenStoreOptions {
|
|
4
|
+
maxTokens?: number;
|
|
5
|
+
}
|
|
6
|
+
export declare class MemoryTokenStore extends TokenStore {
|
|
7
|
+
private readonly tokens;
|
|
8
|
+
private readonly tokensByUser;
|
|
9
|
+
private readonly maxTokens?;
|
|
10
|
+
constructor(options?: MemoryTokenStoreOptions);
|
|
11
|
+
private indexToken;
|
|
12
|
+
private unindexToken;
|
|
13
|
+
private removeByRefreshToken;
|
|
14
|
+
save(record: Token): Promise<void>;
|
|
15
|
+
get(query: Partial<Token>, opts?: {
|
|
16
|
+
includeExpired?: boolean;
|
|
17
|
+
}): Promise<Token | null>;
|
|
18
|
+
delete(query: Partial<Token>): Promise<number>;
|
|
19
|
+
update(params: Partial<Token> & {
|
|
20
|
+
refreshToken: string;
|
|
21
|
+
}): Promise<boolean>;
|
|
22
|
+
list(userId: string | number, opts?: {
|
|
23
|
+
limit?: number;
|
|
24
|
+
offset?: number;
|
|
25
|
+
includeExpired?: boolean;
|
|
26
|
+
}): Promise<Token[]>;
|
|
27
|
+
close(): Promise<void>;
|
|
28
|
+
private enforceCapacity;
|
|
29
|
+
}
|