@technomoron/api-server-base 2.0.0-beta.15 → 2.0.0-beta.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.txt +17 -0
- package/dist/cjs/api-server-base.cjs +17 -0
- package/dist/cjs/api-server-base.d.ts +2 -0
- package/dist/cjs/auth-api/sql-auth-store.d.ts +11 -1
- package/dist/cjs/auth-api/sql-auth-store.js +35 -3
- package/dist/cjs/oauth/models.d.ts +6 -2
- package/dist/cjs/oauth/models.js +17 -6
- package/dist/cjs/oauth/sequelize.d.ts +13 -4
- package/dist/cjs/oauth/sequelize.js +27 -8
- package/dist/cjs/passkey/models.d.ts +6 -2
- package/dist/cjs/passkey/models.js +15 -4
- package/dist/cjs/passkey/sequelize.d.ts +7 -2
- package/dist/cjs/passkey/sequelize.js +22 -6
- package/dist/cjs/token/sequelize.d.ts +4 -1
- package/dist/cjs/token/sequelize.js +26 -7
- package/dist/cjs/user/sequelize.d.ts +7 -2
- package/dist/cjs/user/sequelize.js +18 -5
- package/dist/esm/api-server-base.d.ts +2 -0
- package/dist/esm/api-server-base.js +17 -0
- package/dist/esm/auth-api/sql-auth-store.d.ts +11 -1
- package/dist/esm/auth-api/sql-auth-store.js +35 -3
- package/dist/esm/oauth/models.d.ts +6 -2
- package/dist/esm/oauth/models.js +17 -6
- package/dist/esm/oauth/sequelize.d.ts +13 -4
- package/dist/esm/oauth/sequelize.js +27 -8
- package/dist/esm/passkey/models.d.ts +6 -2
- package/dist/esm/passkey/models.js +15 -4
- package/dist/esm/passkey/sequelize.d.ts +7 -2
- package/dist/esm/passkey/sequelize.js +22 -6
- package/dist/esm/token/sequelize.d.ts +4 -1
- package/dist/esm/token/sequelize.js +26 -7
- package/dist/esm/user/sequelize.d.ts +7 -2
- package/dist/esm/user/sequelize.js +18 -5
- package/docs/swagger/openapi.json +1 -1
- package/package.json +6 -6
package/README.txt
CHANGED
|
@@ -102,6 +102,7 @@ apiBasePath (string, default '/api') Prefix applied to every module namespace.
|
|
|
102
102
|
origins (string array, default empty array) CORS allowlist; empty allows all origins.
|
|
103
103
|
uploadPath (string, default empty string) Enables multer.any() when provided.
|
|
104
104
|
uploadMax (number, default 30 * 1024 * 1024) Maximum upload size in bytes.
|
|
105
|
+
staticDirs (record, default empty object) Map of mount path => disk path for serving static files as-is (ex: { '/assets': './public' }).
|
|
105
106
|
accessSecret (string, default empty string) Required for JWT signing and verification.
|
|
106
107
|
refreshSecret (string, default empty string) Used for refresh tokens if you implement them.
|
|
107
108
|
cookieDomain (string, default '.somewhere-over-the-rainbow.com') Domain applied to auth cookies.
|
|
@@ -141,6 +142,22 @@ Use your storage adapter's filterUser helper to trim sensitive data before retur
|
|
|
141
142
|
Provide your own authorize method to enforce role based access control using the ApiAuthClass enum.
|
|
142
143
|
Create feature modules by extending ApiModule. Use the optional checkConfig hook to validate prerequisites before routes mount.
|
|
143
144
|
|
|
145
|
+
Sequelize Table Prefixes
|
|
146
|
+
------------------------
|
|
147
|
+
Sequelize-backed stores accept `tablePrefix` to prepend to the built-in table names (`users`, `jwttokens`, `passkey_credentials`, `passkey_challenges`, `oauth_clients`, `oauth_codes`).
|
|
148
|
+
|
|
149
|
+
SqlAuthStore supports both a global prefix (`tablePrefix`) and per-module overrides (`tablePrefixes.user|token|passkey|oauth`). When present, `tokenStoreOptions.tablePrefix` and `oauthStoreOptions.tablePrefix` take precedence.
|
|
150
|
+
|
|
151
|
+
Example:
|
|
152
|
+
|
|
153
|
+
const store = new SqlAuthStore({
|
|
154
|
+
sequelize,
|
|
155
|
+
tablePrefix: 'myapp_'
|
|
156
|
+
});
|
|
157
|
+
// Creates tables like myapp_users, myapp_jwttokens, myapp_oauth_clients, ...
|
|
158
|
+
|
|
159
|
+
If you need a different base name (for example `myapp_tokens` instead of `myapp_jwttokens`), pass a custom model or model factory to the store and set the `tableName` yourself.
|
|
160
|
+
|
|
144
161
|
Custom Express Endpoints
|
|
145
162
|
------------------------
|
|
146
163
|
ApiModule routes run inside the tuple wrapper (always responding with a standardized JSON envelope). For endpoints that need raw Express control (streaming, webhooks, tus uploads, etc.), mount your own handlers directly.
|
|
@@ -343,6 +343,7 @@ function fillConfig(config) {
|
|
|
343
343
|
apiHost: config.apiHost ?? 'localhost',
|
|
344
344
|
uploadPath: config.uploadPath ?? '',
|
|
345
345
|
uploadMax: config.uploadMax ?? 30 * 1024 * 1024,
|
|
346
|
+
staticDirs: config.staticDirs,
|
|
346
347
|
origins: config.origins ?? [],
|
|
347
348
|
debug: config.debug ?? false,
|
|
348
349
|
apiBasePath: config.apiBasePath ?? '/api',
|
|
@@ -399,6 +400,7 @@ class ApiServer {
|
|
|
399
400
|
this.app.use(upload.any());
|
|
400
401
|
}
|
|
401
402
|
this.middlewares();
|
|
403
|
+
this.installStaticDirs();
|
|
402
404
|
this.installPingHandler();
|
|
403
405
|
this.installSwaggerHandler();
|
|
404
406
|
// addSwaggerUi(this.app);
|
|
@@ -656,6 +658,21 @@ class ApiServer {
|
|
|
656
658
|
};
|
|
657
659
|
this.app.use((0, cors_1.default)(corsOptions));
|
|
658
660
|
}
|
|
661
|
+
installStaticDirs() {
|
|
662
|
+
const staticDirs = this.config.staticDirs;
|
|
663
|
+
if (!staticDirs || !isPlainObject(staticDirs)) {
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
for (const [mountRaw, dirRaw] of Object.entries(staticDirs)) {
|
|
667
|
+
const mount = typeof mountRaw === 'string' ? mountRaw.trim() : '';
|
|
668
|
+
const dir = typeof dirRaw === 'string' ? dirRaw.trim() : '';
|
|
669
|
+
if (!mount || !dir) {
|
|
670
|
+
continue;
|
|
671
|
+
}
|
|
672
|
+
const resolvedMount = mount.startsWith('/') ? mount : `/${mount}`;
|
|
673
|
+
this.app.use(resolvedMount, express_1.default.static(dir));
|
|
674
|
+
}
|
|
675
|
+
}
|
|
659
676
|
installPingHandler() {
|
|
660
677
|
const path = `${this.apiBasePath}/v1/ping`;
|
|
661
678
|
this.app.get(path, (_req, res) => {
|
|
@@ -92,6 +92,7 @@ export interface ApiServerConf {
|
|
|
92
92
|
apiHost: string;
|
|
93
93
|
uploadPath: string;
|
|
94
94
|
uploadMax: number;
|
|
95
|
+
staticDirs?: Record<string, string>;
|
|
95
96
|
origins: string[];
|
|
96
97
|
debug: boolean;
|
|
97
98
|
apiBasePath: string;
|
|
@@ -193,6 +194,7 @@ export declare class ApiServer {
|
|
|
193
194
|
guessExceptionText(error: unknown, defMsg?: string): string;
|
|
194
195
|
protected authorize(apiReq: ApiRequest, requiredClass: ApiAuthClass): Promise<void>;
|
|
195
196
|
private middlewares;
|
|
197
|
+
private installStaticDirs;
|
|
196
198
|
private installPingHandler;
|
|
197
199
|
private loadSwaggerSpec;
|
|
198
200
|
private installSwaggerHandler;
|
|
@@ -11,13 +11,23 @@ import type { Token } from '../token/types.js';
|
|
|
11
11
|
interface PasskeyOptions extends Partial<PasskeyServiceConfig> {
|
|
12
12
|
enabled?: boolean;
|
|
13
13
|
}
|
|
14
|
+
export interface SqlAuthStoreTablePrefixes {
|
|
15
|
+
user?: string;
|
|
16
|
+
token?: string;
|
|
17
|
+
passkey?: string;
|
|
18
|
+
oauth?: string;
|
|
19
|
+
}
|
|
14
20
|
export interface SqlAuthStoreParams<UserAttributes extends AuthUserAttributes = AuthUserAttributes, PublicUserShape extends Omit<UserAttributes, 'password'> = Omit<UserAttributes, 'password'>> {
|
|
15
21
|
sequelize: Sequelize;
|
|
16
22
|
syncOptions?: SyncOptions;
|
|
17
23
|
bcryptRounds?: number;
|
|
18
24
|
passwordPepper?: string;
|
|
25
|
+
tablePrefix?: string;
|
|
26
|
+
tablePrefixes?: SqlAuthStoreTablePrefixes;
|
|
19
27
|
userModel?: GenericUserModelStatic;
|
|
20
|
-
userModelFactory?: (sequelize: Sequelize
|
|
28
|
+
userModelFactory?: (sequelize: Sequelize, options?: {
|
|
29
|
+
tablePrefix?: string;
|
|
30
|
+
}) => GenericUserModelStatic;
|
|
21
31
|
userRecordMapper?: (model: GenericUserModel) => UserAttributes;
|
|
22
32
|
publicUserMapper?: (user: UserAttributes) => PublicUserShape;
|
|
23
33
|
passkeyUserMapper?: (user: UserAttributes) => PasskeyUserDescriptor;
|
|
@@ -13,6 +13,22 @@ const DEFAULT_PASSKEY_CONFIG = {
|
|
|
13
13
|
timeoutMs: 5 * 60 * 1000,
|
|
14
14
|
userVerification: 'preferred'
|
|
15
15
|
};
|
|
16
|
+
function normalizeTablePrefix(prefix) {
|
|
17
|
+
if (!prefix) {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
const trimmed = prefix.trim();
|
|
21
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
22
|
+
}
|
|
23
|
+
function resolveTablePrefix(...prefixes) {
|
|
24
|
+
for (const prefix of prefixes) {
|
|
25
|
+
const normalized = normalizeTablePrefix(prefix);
|
|
26
|
+
if (normalized) {
|
|
27
|
+
return normalized;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
16
32
|
function isOriginString(origin) {
|
|
17
33
|
return typeof origin === 'string' && origin.trim().length > 0;
|
|
18
34
|
}
|
|
@@ -36,6 +52,8 @@ class SqlAuthStore {
|
|
|
36
52
|
}
|
|
37
53
|
this.sequelize = params.sequelize;
|
|
38
54
|
this.syncOptions = params.syncOptions;
|
|
55
|
+
const moduleTablePrefixes = params.tablePrefixes ?? {};
|
|
56
|
+
const userTablePrefix = resolveTablePrefix(moduleTablePrefixes.user, params.tablePrefix);
|
|
39
57
|
this.userStore = new sequelize_js_4.SequelizeUserStore({
|
|
40
58
|
sequelize: this.sequelize,
|
|
41
59
|
userModel: params.userModel,
|
|
@@ -43,18 +61,28 @@ class SqlAuthStore {
|
|
|
43
61
|
recordMapper: params.userRecordMapper,
|
|
44
62
|
toPublic: params.publicUserMapper,
|
|
45
63
|
bcryptRounds: params.bcryptRounds,
|
|
46
|
-
bcryptPepper: params.passwordPepper
|
|
64
|
+
bcryptPepper: params.passwordPepper,
|
|
65
|
+
tablePrefix: userTablePrefix
|
|
47
66
|
});
|
|
67
|
+
const tokenTablePrefix = resolveTablePrefix(params.tokenStoreOptions?.tablePrefix, moduleTablePrefixes.token, params.tablePrefix);
|
|
48
68
|
this.tokenStore =
|
|
49
|
-
params.tokenStore ??
|
|
69
|
+
params.tokenStore ??
|
|
70
|
+
new sequelize_js_3.SequelizeTokenStore({
|
|
71
|
+
sequelize: this.sequelize,
|
|
72
|
+
...params.tokenStoreOptions,
|
|
73
|
+
tablePrefix: tokenTablePrefix
|
|
74
|
+
});
|
|
75
|
+
const oauthTablePrefix = resolveTablePrefix(params.oauthStoreOptions?.tablePrefix, moduleTablePrefixes.oauth, params.tablePrefix);
|
|
50
76
|
this.oauthStore = new sequelize_js_1.SequelizeOAuthStore({
|
|
51
77
|
sequelize: this.sequelize,
|
|
52
78
|
...params.oauthStoreOptions,
|
|
79
|
+
tablePrefix: oauthTablePrefix,
|
|
53
80
|
bcryptRounds: params.bcryptRounds
|
|
54
81
|
});
|
|
55
82
|
let passkeyStore;
|
|
56
83
|
let passkeyConfig;
|
|
57
84
|
if (params.passkeys !== false) {
|
|
85
|
+
const passkeyTablePrefix = resolveTablePrefix(moduleTablePrefixes.passkey, params.tablePrefix);
|
|
58
86
|
passkeyConfig = normalizePasskeyConfig(params.passkeys ?? {});
|
|
59
87
|
const resolveUser = async (lookup) => {
|
|
60
88
|
const found = await this.userStore.findUser(lookup.userId ?? lookup.login ?? '');
|
|
@@ -69,7 +97,11 @@ class SqlAuthStore {
|
|
|
69
97
|
}));
|
|
70
98
|
return mapper(found);
|
|
71
99
|
};
|
|
72
|
-
passkeyStore = new sequelize_js_2.SequelizePasskeyStore({
|
|
100
|
+
passkeyStore = new sequelize_js_2.SequelizePasskeyStore({
|
|
101
|
+
sequelize: this.sequelize,
|
|
102
|
+
resolveUser,
|
|
103
|
+
tablePrefix: passkeyTablePrefix
|
|
104
|
+
});
|
|
73
105
|
this.passkeyStore = passkeyStore;
|
|
74
106
|
}
|
|
75
107
|
this.adapter = new compat_auth_storage_js_1.CompositeAuthAdapter({
|
|
@@ -18,7 +18,9 @@ export declare class OAuthClientModel extends Model<OAuthClientAttributes, OAuth
|
|
|
18
18
|
metadata: string | null;
|
|
19
19
|
first_party: boolean;
|
|
20
20
|
}
|
|
21
|
-
export declare function initOAuthClientModel(sequelize: Sequelize
|
|
21
|
+
export declare function initOAuthClientModel(sequelize: Sequelize, options?: {
|
|
22
|
+
tablePrefix?: string;
|
|
23
|
+
}): typeof OAuthClientModel;
|
|
22
24
|
export interface OAuthCodeAttributes {
|
|
23
25
|
code: string;
|
|
24
26
|
client_id: string;
|
|
@@ -42,4 +44,6 @@ export declare class OAuthCodeModel extends Model<OAuthCodeAttributes, OAuthCode
|
|
|
42
44
|
expires: Date;
|
|
43
45
|
metadata: string | null;
|
|
44
46
|
}
|
|
45
|
-
export declare function initOAuthCodeModel(sequelize: Sequelize
|
|
47
|
+
export declare function initOAuthCodeModel(sequelize: Sequelize, options?: {
|
|
48
|
+
tablePrefix?: string;
|
|
49
|
+
}): typeof OAuthCodeModel;
|
package/dist/cjs/oauth/models.js
CHANGED
|
@@ -5,11 +5,22 @@ exports.initOAuthClientModel = initOAuthClientModel;
|
|
|
5
5
|
exports.initOAuthCodeModel = initOAuthCodeModel;
|
|
6
6
|
const sequelize_1 = require("sequelize");
|
|
7
7
|
const DIALECTS_SUPPORTING_UNSIGNED = new Set(['mysql', 'mariadb']);
|
|
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
|
+
}
|
|
8
19
|
function integerIdType(sequelize) {
|
|
9
20
|
return DIALECTS_SUPPORTING_UNSIGNED.has(sequelize.getDialect()) ? sequelize_1.DataTypes.INTEGER.UNSIGNED : sequelize_1.DataTypes.INTEGER;
|
|
10
21
|
}
|
|
11
|
-
function tableOptions(sequelize, tableName, extra) {
|
|
12
|
-
const opts = { sequelize, tableName };
|
|
22
|
+
function tableOptions(sequelize, tableName, tablePrefix, extra) {
|
|
23
|
+
const opts = { sequelize, tableName: applyTablePrefix(tablePrefix, tableName) };
|
|
13
24
|
if (extra) {
|
|
14
25
|
Object.assign(opts, extra);
|
|
15
26
|
}
|
|
@@ -22,7 +33,7 @@ function tableOptions(sequelize, tableName, extra) {
|
|
|
22
33
|
class OAuthClientModel extends sequelize_1.Model {
|
|
23
34
|
}
|
|
24
35
|
exports.OAuthClientModel = OAuthClientModel;
|
|
25
|
-
function initOAuthClientModel(sequelize) {
|
|
36
|
+
function initOAuthClientModel(sequelize, options = {}) {
|
|
26
37
|
OAuthClientModel.init({
|
|
27
38
|
client_id: { type: sequelize_1.DataTypes.STRING(128), allowNull: false, primaryKey: true },
|
|
28
39
|
client_secret: { type: sequelize_1.DataTypes.STRING(255), allowNull: false, defaultValue: '' },
|
|
@@ -32,14 +43,14 @@ function initOAuthClientModel(sequelize) {
|
|
|
32
43
|
metadata: { type: sequelize_1.DataTypes.TEXT, allowNull: true, defaultValue: null },
|
|
33
44
|
first_party: { type: sequelize_1.DataTypes.BOOLEAN, allowNull: false, defaultValue: false }
|
|
34
45
|
}, {
|
|
35
|
-
...tableOptions(sequelize, 'oauth_clients', { timestamps: false })
|
|
46
|
+
...tableOptions(sequelize, 'oauth_clients', options.tablePrefix, { timestamps: false })
|
|
36
47
|
});
|
|
37
48
|
return OAuthClientModel;
|
|
38
49
|
}
|
|
39
50
|
class OAuthCodeModel extends sequelize_1.Model {
|
|
40
51
|
}
|
|
41
52
|
exports.OAuthCodeModel = OAuthCodeModel;
|
|
42
|
-
function initOAuthCodeModel(sequelize) {
|
|
53
|
+
function initOAuthCodeModel(sequelize, options = {}) {
|
|
43
54
|
const idType = integerIdType(sequelize);
|
|
44
55
|
OAuthCodeModel.init({
|
|
45
56
|
code: { type: sequelize_1.DataTypes.STRING(128), allowNull: false, primaryKey: true },
|
|
@@ -52,7 +63,7 @@ function initOAuthCodeModel(sequelize) {
|
|
|
52
63
|
expires: { type: sequelize_1.DataTypes.DATE, allowNull: false },
|
|
53
64
|
metadata: { type: sequelize_1.DataTypes.TEXT, allowNull: true, defaultValue: null }
|
|
54
65
|
}, {
|
|
55
|
-
...tableOptions(sequelize, 'oauth_codes', { timestamps: false })
|
|
66
|
+
...tableOptions(sequelize, 'oauth_codes', options.tablePrefix, { timestamps: false })
|
|
56
67
|
});
|
|
57
68
|
return OAuthCodeModel;
|
|
58
69
|
}
|
|
@@ -19,7 +19,9 @@ export declare class OAuthClientModel extends Model<OAuthClientAttributes, OAuth
|
|
|
19
19
|
metadata: string | null;
|
|
20
20
|
first_party: boolean;
|
|
21
21
|
}
|
|
22
|
-
export declare function initOAuthClientModel(sequelize: Sequelize
|
|
22
|
+
export declare function initOAuthClientModel(sequelize: Sequelize, options?: {
|
|
23
|
+
tablePrefix?: string;
|
|
24
|
+
}): typeof OAuthClientModel;
|
|
23
25
|
export interface OAuthCodeAttributes {
|
|
24
26
|
code: string;
|
|
25
27
|
client_id: string;
|
|
@@ -43,13 +45,20 @@ export declare class OAuthCodeModel extends Model<OAuthCodeAttributes, OAuthCode
|
|
|
43
45
|
expires: Date;
|
|
44
46
|
metadata: string | null;
|
|
45
47
|
}
|
|
46
|
-
export declare function initOAuthCodeModel(sequelize: Sequelize
|
|
48
|
+
export declare function initOAuthCodeModel(sequelize: Sequelize, options?: {
|
|
49
|
+
tablePrefix?: string;
|
|
50
|
+
}): typeof OAuthCodeModel;
|
|
47
51
|
export interface SequelizeOAuthStoreOptions {
|
|
48
52
|
sequelize: Sequelize;
|
|
53
|
+
tablePrefix?: string;
|
|
49
54
|
clientModel?: typeof OAuthClientModel;
|
|
50
55
|
codeModel?: typeof OAuthCodeModel;
|
|
51
|
-
clientModelFactory?: (sequelize: Sequelize
|
|
52
|
-
|
|
56
|
+
clientModelFactory?: (sequelize: Sequelize, options?: {
|
|
57
|
+
tablePrefix?: string;
|
|
58
|
+
}) => typeof OAuthClientModel;
|
|
59
|
+
codeModelFactory?: (sequelize: Sequelize, options?: {
|
|
60
|
+
tablePrefix?: string;
|
|
61
|
+
}) => typeof OAuthCodeModel;
|
|
53
62
|
bcryptRounds?: number;
|
|
54
63
|
}
|
|
55
64
|
export declare class SequelizeOAuthStore extends OAuthStore {
|
|
@@ -10,11 +10,22 @@ const bcryptjs_1 = __importDefault(require("bcryptjs"));
|
|
|
10
10
|
const sequelize_1 = require("sequelize");
|
|
11
11
|
const base_js_1 = require("./base.js");
|
|
12
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
|
+
}
|
|
13
24
|
function integerIdType(sequelize) {
|
|
14
25
|
return DIALECTS_SUPPORTING_UNSIGNED.has(sequelize.getDialect()) ? sequelize_1.DataTypes.INTEGER.UNSIGNED : sequelize_1.DataTypes.INTEGER;
|
|
15
26
|
}
|
|
16
|
-
function tableOptions(sequelize, tableName, extra) {
|
|
17
|
-
const opts = { sequelize, tableName };
|
|
27
|
+
function tableOptions(sequelize, tableName, tablePrefix, extra) {
|
|
28
|
+
const opts = { sequelize, tableName: applyTablePrefix(tablePrefix, tableName) };
|
|
18
29
|
if (extra) {
|
|
19
30
|
Object.assign(opts, extra);
|
|
20
31
|
}
|
|
@@ -27,7 +38,7 @@ function tableOptions(sequelize, tableName, extra) {
|
|
|
27
38
|
class OAuthClientModel extends sequelize_1.Model {
|
|
28
39
|
}
|
|
29
40
|
exports.OAuthClientModel = OAuthClientModel;
|
|
30
|
-
function initOAuthClientModel(sequelize) {
|
|
41
|
+
function initOAuthClientModel(sequelize, options = {}) {
|
|
31
42
|
OAuthClientModel.init({
|
|
32
43
|
client_id: { type: sequelize_1.DataTypes.STRING(128), allowNull: false, primaryKey: true },
|
|
33
44
|
client_secret: { type: sequelize_1.DataTypes.STRING(255), allowNull: false, defaultValue: '' },
|
|
@@ -36,13 +47,13 @@ function initOAuthClientModel(sequelize) {
|
|
|
36
47
|
scope: { type: sequelize_1.DataTypes.TEXT, allowNull: false, defaultValue: '[]' },
|
|
37
48
|
metadata: { type: sequelize_1.DataTypes.TEXT, allowNull: true, defaultValue: null },
|
|
38
49
|
first_party: { type: sequelize_1.DataTypes.BOOLEAN, allowNull: false, defaultValue: false }
|
|
39
|
-
}, tableOptions(sequelize, 'oauth_clients', { timestamps: false }));
|
|
50
|
+
}, tableOptions(sequelize, 'oauth_clients', options.tablePrefix, { timestamps: false }));
|
|
40
51
|
return OAuthClientModel;
|
|
41
52
|
}
|
|
42
53
|
class OAuthCodeModel extends sequelize_1.Model {
|
|
43
54
|
}
|
|
44
55
|
exports.OAuthCodeModel = OAuthCodeModel;
|
|
45
|
-
function initOAuthCodeModel(sequelize) {
|
|
56
|
+
function initOAuthCodeModel(sequelize, options = {}) {
|
|
46
57
|
const idType = integerIdType(sequelize);
|
|
47
58
|
OAuthCodeModel.init({
|
|
48
59
|
code: { type: sequelize_1.DataTypes.STRING(128), allowNull: false, primaryKey: true },
|
|
@@ -54,7 +65,7 @@ function initOAuthCodeModel(sequelize) {
|
|
|
54
65
|
code_challenge_method: { type: sequelize_1.DataTypes.STRING(10), allowNull: true, defaultValue: null },
|
|
55
66
|
expires: { type: sequelize_1.DataTypes.DATE, allowNull: false },
|
|
56
67
|
metadata: { type: sequelize_1.DataTypes.TEXT, allowNull: true, defaultValue: null }
|
|
57
|
-
}, tableOptions(sequelize, 'oauth_codes', { timestamps: false }));
|
|
68
|
+
}, tableOptions(sequelize, 'oauth_codes', options.tablePrefix, { timestamps: false }));
|
|
58
69
|
return OAuthCodeModel;
|
|
59
70
|
}
|
|
60
71
|
function encodeStringArray(values) {
|
|
@@ -114,8 +125,16 @@ class SequelizeOAuthStore extends base_js_1.OAuthStore {
|
|
|
114
125
|
if (!options?.sequelize) {
|
|
115
126
|
throw new Error('SequelizeOAuthStore requires an initialised Sequelize instance');
|
|
116
127
|
}
|
|
117
|
-
this.clients =
|
|
118
|
-
|
|
128
|
+
this.clients =
|
|
129
|
+
options.clientModel ??
|
|
130
|
+
(options.clientModelFactory ?? initOAuthClientModel)(options.sequelize, {
|
|
131
|
+
tablePrefix: options.tablePrefix
|
|
132
|
+
});
|
|
133
|
+
this.codes =
|
|
134
|
+
options.codeModel ??
|
|
135
|
+
(options.codeModelFactory ?? initOAuthCodeModel)(options.sequelize, {
|
|
136
|
+
tablePrefix: options.tablePrefix
|
|
137
|
+
});
|
|
119
138
|
this.bcryptRounds = options.bcryptRounds ?? 12;
|
|
120
139
|
}
|
|
121
140
|
async getClient(clientId) {
|
|
@@ -26,5 +26,9 @@ export declare class PasskeyChallengeModel extends Model<InferAttributes<Passkey
|
|
|
26
26
|
createdAt?: Date;
|
|
27
27
|
updatedAt?: Date;
|
|
28
28
|
}
|
|
29
|
-
export declare function initPasskeyCredentialModel(sequelize: Sequelize
|
|
30
|
-
|
|
29
|
+
export declare function initPasskeyCredentialModel(sequelize: Sequelize, options?: {
|
|
30
|
+
tablePrefix?: string;
|
|
31
|
+
}): ModelStatic<PasskeyCredentialModel>;
|
|
32
|
+
export declare function initPasskeyChallengeModel(sequelize: Sequelize, options?: {
|
|
33
|
+
tablePrefix?: string;
|
|
34
|
+
}): ModelStatic<PasskeyChallengeModel>;
|
|
@@ -5,6 +5,17 @@ exports.initPasskeyCredentialModel = initPasskeyCredentialModel;
|
|
|
5
5
|
exports.initPasskeyChallengeModel = initPasskeyChallengeModel;
|
|
6
6
|
const sequelize_1 = require("sequelize");
|
|
7
7
|
const DIALECTS_SUPPORTING_UNSIGNED = new Set(['mysql', 'mariadb']);
|
|
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
|
+
}
|
|
8
19
|
function integerIdType(sequelize) {
|
|
9
20
|
return DIALECTS_SUPPORTING_UNSIGNED.has(sequelize.getDialect()) ? sequelize_1.DataTypes.INTEGER.UNSIGNED : sequelize_1.DataTypes.INTEGER;
|
|
10
21
|
}
|
|
@@ -14,7 +25,7 @@ exports.PasskeyCredentialModel = PasskeyCredentialModel;
|
|
|
14
25
|
class PasskeyChallengeModel extends sequelize_1.Model {
|
|
15
26
|
}
|
|
16
27
|
exports.PasskeyChallengeModel = PasskeyChallengeModel;
|
|
17
|
-
function initPasskeyCredentialModel(sequelize) {
|
|
28
|
+
function initPasskeyCredentialModel(sequelize, options = {}) {
|
|
18
29
|
const idType = integerIdType(sequelize);
|
|
19
30
|
return PasskeyCredentialModel.init({
|
|
20
31
|
credentialId: {
|
|
@@ -104,12 +115,12 @@ function initPasskeyCredentialModel(sequelize) {
|
|
|
104
115
|
}
|
|
105
116
|
}, {
|
|
106
117
|
sequelize,
|
|
107
|
-
tableName: 'passkey_credentials',
|
|
118
|
+
tableName: applyTablePrefix(options.tablePrefix, 'passkey_credentials'),
|
|
108
119
|
timestamps: true,
|
|
109
120
|
underscored: true
|
|
110
121
|
});
|
|
111
122
|
}
|
|
112
|
-
function initPasskeyChallengeModel(sequelize) {
|
|
123
|
+
function initPasskeyChallengeModel(sequelize, options = {}) {
|
|
113
124
|
const idType = integerIdType(sequelize);
|
|
114
125
|
return PasskeyChallengeModel.init({
|
|
115
126
|
challenge: {
|
|
@@ -137,7 +148,7 @@ function initPasskeyChallengeModel(sequelize) {
|
|
|
137
148
|
}
|
|
138
149
|
}, {
|
|
139
150
|
sequelize,
|
|
140
|
-
tableName: 'passkey_challenges',
|
|
151
|
+
tableName: applyTablePrefix(options.tablePrefix, 'passkey_challenges'),
|
|
141
152
|
timestamps: true,
|
|
142
153
|
underscored: true,
|
|
143
154
|
indexes: [{ fields: ['expires_at'] }, { fields: ['user_id'] }]
|
|
@@ -31,10 +31,15 @@ declare class PasskeyChallengeModel extends Model<InferAttributes<PasskeyChallen
|
|
|
31
31
|
}
|
|
32
32
|
export interface SequelizePasskeyStoreOptions {
|
|
33
33
|
sequelize: Sequelize;
|
|
34
|
+
tablePrefix?: string;
|
|
34
35
|
credentialModel?: ModelStatic<PasskeyCredentialModel>;
|
|
35
36
|
challengeModel?: ModelStatic<PasskeyChallengeModel>;
|
|
36
|
-
credentialModelFactory?: (sequelize: Sequelize
|
|
37
|
-
|
|
37
|
+
credentialModelFactory?: (sequelize: Sequelize, options?: {
|
|
38
|
+
tablePrefix?: string;
|
|
39
|
+
}) => ModelStatic<PasskeyCredentialModel>;
|
|
40
|
+
challengeModelFactory?: (sequelize: Sequelize, options?: {
|
|
41
|
+
tablePrefix?: string;
|
|
42
|
+
}) => ModelStatic<PasskeyChallengeModel>;
|
|
38
43
|
resolveUser: (params: {
|
|
39
44
|
userId?: AuthIdentifier;
|
|
40
45
|
login?: string;
|
|
@@ -4,6 +4,17 @@ exports.SequelizePasskeyStore = void 0;
|
|
|
4
4
|
const sequelize_1 = require("sequelize");
|
|
5
5
|
const base_js_1 = require("./base.js");
|
|
6
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
|
+
}
|
|
7
18
|
function integerIdType(sequelize) {
|
|
8
19
|
return DIALECTS_SUPPORTING_UNSIGNED.has(sequelize.getDialect()) ? sequelize_1.DataTypes.INTEGER.UNSIGNED : sequelize_1.DataTypes.INTEGER;
|
|
9
20
|
}
|
|
@@ -23,7 +34,7 @@ class PasskeyCredentialModel extends sequelize_1.Model {
|
|
|
23
34
|
}
|
|
24
35
|
class PasskeyChallengeModel extends sequelize_1.Model {
|
|
25
36
|
}
|
|
26
|
-
function initPasskeyCredentialModel(sequelize) {
|
|
37
|
+
function initPasskeyCredentialModel(sequelize, options = {}) {
|
|
27
38
|
const idType = integerIdType(sequelize);
|
|
28
39
|
return PasskeyCredentialModel.init({
|
|
29
40
|
credentialId: {
|
|
@@ -113,12 +124,12 @@ function initPasskeyCredentialModel(sequelize) {
|
|
|
113
124
|
}
|
|
114
125
|
}, {
|
|
115
126
|
sequelize,
|
|
116
|
-
tableName: 'passkey_credentials',
|
|
127
|
+
tableName: applyTablePrefix(options.tablePrefix, 'passkey_credentials'),
|
|
117
128
|
timestamps: true,
|
|
118
129
|
underscored: true
|
|
119
130
|
});
|
|
120
131
|
}
|
|
121
|
-
function initPasskeyChallengeModel(sequelize) {
|
|
132
|
+
function initPasskeyChallengeModel(sequelize, options = {}) {
|
|
122
133
|
const idType = integerIdType(sequelize);
|
|
123
134
|
return PasskeyChallengeModel.init({
|
|
124
135
|
challenge: {
|
|
@@ -146,7 +157,7 @@ function initPasskeyChallengeModel(sequelize) {
|
|
|
146
157
|
}
|
|
147
158
|
}, {
|
|
148
159
|
sequelize,
|
|
149
|
-
tableName: 'passkey_challenges',
|
|
160
|
+
tableName: applyTablePrefix(options.tablePrefix, 'passkey_challenges'),
|
|
150
161
|
timestamps: true,
|
|
151
162
|
underscored: true,
|
|
152
163
|
indexes: [{ fields: ['expires_at'] }, { fields: ['user_id'] }]
|
|
@@ -161,9 +172,14 @@ class SequelizePasskeyStore extends base_js_1.PasskeyStore {
|
|
|
161
172
|
this.resolveUserFn = options.resolveUser;
|
|
162
173
|
this.credentials =
|
|
163
174
|
options.credentialModel ??
|
|
164
|
-
(options.credentialModelFactory ?? initPasskeyCredentialModel)(options.sequelize
|
|
175
|
+
(options.credentialModelFactory ?? initPasskeyCredentialModel)(options.sequelize, {
|
|
176
|
+
tablePrefix: options.tablePrefix
|
|
177
|
+
});
|
|
165
178
|
this.challenges =
|
|
166
|
-
options.challengeModel ??
|
|
179
|
+
options.challengeModel ??
|
|
180
|
+
(options.challengeModelFactory ?? initPasskeyChallengeModel)(options.sequelize, {
|
|
181
|
+
tablePrefix: options.tablePrefix
|
|
182
|
+
});
|
|
167
183
|
}
|
|
168
184
|
async resolveUser(params) {
|
|
169
185
|
return this.resolveUserFn(params);
|
|
@@ -27,8 +27,11 @@ export type TokenAttributes = InferAttributes<TokenModel>;
|
|
|
27
27
|
export type TokenCreationAttributes = InferCreationAttributes<TokenModel>;
|
|
28
28
|
export interface SequelizeTokenStoreOptions {
|
|
29
29
|
sequelize: Sequelize;
|
|
30
|
+
tablePrefix?: string;
|
|
30
31
|
tokenModel?: ModelStatic<TokenModel>;
|
|
31
|
-
tokenModelFactory?: (sequelize: Sequelize
|
|
32
|
+
tokenModelFactory?: (sequelize: Sequelize, options?: {
|
|
33
|
+
tablePrefix?: string;
|
|
34
|
+
}) => ModelStatic<TokenModel>;
|
|
32
35
|
}
|
|
33
36
|
export declare class SequelizeTokenStore extends TokenStore {
|
|
34
37
|
readonly Tokens: ModelStatic<TokenModel>;
|
|
@@ -4,12 +4,23 @@ exports.SequelizeTokenStore = void 0;
|
|
|
4
4
|
const sequelize_1 = require("sequelize");
|
|
5
5
|
const base_js_1 = require("./base.js");
|
|
6
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
|
+
}
|
|
7
18
|
class TokenModel extends sequelize_1.Model {
|
|
8
19
|
}
|
|
9
|
-
function tokenTableOptions(sequelize) {
|
|
20
|
+
function tokenTableOptions(sequelize, tablePrefix) {
|
|
10
21
|
const opts = {
|
|
11
22
|
sequelize,
|
|
12
|
-
tableName: 'jwttokens',
|
|
23
|
+
tableName: applyTablePrefix(tablePrefix, 'jwttokens'),
|
|
13
24
|
timestamps: false
|
|
14
25
|
};
|
|
15
26
|
if (DIALECTS_SUPPORTING_UNSIGNED.has(sequelize.getDialect())) {
|
|
@@ -18,7 +29,11 @@ function tokenTableOptions(sequelize) {
|
|
|
18
29
|
}
|
|
19
30
|
return opts;
|
|
20
31
|
}
|
|
21
|
-
function initTokenModel(sequelize) {
|
|
32
|
+
function initTokenModel(sequelize, options = {}) {
|
|
33
|
+
const tableName = applyTablePrefix(options.tablePrefix, 'jwttokens');
|
|
34
|
+
const usePrefixedIndexNames = tableName !== 'jwttokens';
|
|
35
|
+
const accessIndexName = usePrefixedIndexNames ? `${tableName}_access_unique` : 'jwt_access_unique';
|
|
36
|
+
const refreshIndexName = usePrefixedIndexNames ? `${tableName}_refresh_unique` : 'jwt_refresh_unique';
|
|
22
37
|
TokenModel.init({
|
|
23
38
|
token_id: {
|
|
24
39
|
type: DIALECTS_SUPPORTING_UNSIGNED.has(sequelize.getDialect())
|
|
@@ -120,10 +135,10 @@ function initTokenModel(sequelize) {
|
|
|
120
135
|
defaultValue: '[]'
|
|
121
136
|
}
|
|
122
137
|
}, {
|
|
123
|
-
...tokenTableOptions(sequelize),
|
|
138
|
+
...tokenTableOptions(sequelize, options.tablePrefix),
|
|
124
139
|
indexes: [
|
|
125
|
-
{ name:
|
|
126
|
-
{ name:
|
|
140
|
+
{ name: accessIndexName, unique: true, fields: ['access'] },
|
|
141
|
+
{ name: refreshIndexName, unique: true, fields: ['refresh'] }
|
|
127
142
|
]
|
|
128
143
|
});
|
|
129
144
|
return TokenModel;
|
|
@@ -134,7 +149,11 @@ class SequelizeTokenStore extends base_js_1.TokenStore {
|
|
|
134
149
|
if (!options?.sequelize) {
|
|
135
150
|
throw new Error('SequelizeTokenStore requires an initialised Sequelize instance');
|
|
136
151
|
}
|
|
137
|
-
this.Tokens =
|
|
152
|
+
this.Tokens =
|
|
153
|
+
options.tokenModel ??
|
|
154
|
+
(options.tokenModelFactory ?? initTokenModel)(options.sequelize, {
|
|
155
|
+
tablePrefix: options.tablePrefix
|
|
156
|
+
});
|
|
138
157
|
}
|
|
139
158
|
async save(record) {
|
|
140
159
|
const normalized = this.normalizeToken(record);
|
|
@@ -10,15 +10,20 @@ export declare class AuthUserModel extends Model<InferAttributes<AuthUserModel>,
|
|
|
10
10
|
}
|
|
11
11
|
export type AuthUserAttributes = InferAttributes<AuthUserModel>;
|
|
12
12
|
export type AuthUserCreationAttributes = InferCreationAttributes<AuthUserModel>;
|
|
13
|
-
export declare function initAuthUserModel(sequelize: Sequelize
|
|
13
|
+
export declare function initAuthUserModel(sequelize: Sequelize, options?: {
|
|
14
|
+
tablePrefix?: string;
|
|
15
|
+
}): typeof AuthUserModel;
|
|
14
16
|
export type GenericUserModel = Model<Record<string, unknown>, Record<string, unknown>>;
|
|
15
17
|
export type GenericUserModelStatic = ModelStatic<GenericUserModel>;
|
|
16
18
|
export interface SequelizeUserStoreOptions<UserAttributes extends AuthUserAttributes = AuthUserAttributes, PublicUserShape extends Omit<UserAttributes, 'password'> = Omit<UserAttributes, 'password'>> {
|
|
17
19
|
bcryptRounds?: number;
|
|
18
20
|
bcryptPepper?: string;
|
|
19
21
|
sequelize: Sequelize;
|
|
22
|
+
tablePrefix?: string;
|
|
20
23
|
userModel?: GenericUserModelStatic;
|
|
21
|
-
userModelFactory?: (sequelize: Sequelize
|
|
24
|
+
userModelFactory?: (sequelize: Sequelize, options?: {
|
|
25
|
+
tablePrefix?: string;
|
|
26
|
+
}) => GenericUserModelStatic;
|
|
22
27
|
recordMapper?: (model: GenericUserModel) => UserAttributes;
|
|
23
28
|
toPublic?: PublicUserMapper<UserAttributes, PublicUserShape>;
|
|
24
29
|
}
|