@technomoron/api-server-base 2.0.0-beta.15 → 2.0.0-beta.16
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 +16 -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/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 +1 -1
package/README.txt
CHANGED
|
@@ -141,6 +141,22 @@ Use your storage adapter's filterUser helper to trim sensitive data before retur
|
|
|
141
141
|
Provide your own authorize method to enforce role based access control using the ApiAuthClass enum.
|
|
142
142
|
Create feature modules by extending ApiModule. Use the optional checkConfig hook to validate prerequisites before routes mount.
|
|
143
143
|
|
|
144
|
+
Sequelize Table Prefixes
|
|
145
|
+
------------------------
|
|
146
|
+
Sequelize-backed stores accept `tablePrefix` to prepend to the built-in table names (`users`, `jwttokens`, `passkey_credentials`, `passkey_challenges`, `oauth_clients`, `oauth_codes`).
|
|
147
|
+
|
|
148
|
+
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.
|
|
149
|
+
|
|
150
|
+
Example:
|
|
151
|
+
|
|
152
|
+
const store = new SqlAuthStore({
|
|
153
|
+
sequelize,
|
|
154
|
+
tablePrefix: 'myapp_'
|
|
155
|
+
});
|
|
156
|
+
// Creates tables like myapp_users, myapp_jwttokens, myapp_oauth_clients, ...
|
|
157
|
+
|
|
158
|
+
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.
|
|
159
|
+
|
|
144
160
|
Custom Express Endpoints
|
|
145
161
|
------------------------
|
|
146
162
|
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.
|
|
@@ -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
|
}
|
|
@@ -5,13 +5,24 @@ exports.initAuthUserModel = initAuthUserModel;
|
|
|
5
5
|
const sequelize_1 = require("sequelize");
|
|
6
6
|
const base_js_1 = require("./base.js");
|
|
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 userTableOptions(sequelize) {
|
|
22
|
+
function userTableOptions(sequelize, tablePrefix) {
|
|
12
23
|
const opts = {
|
|
13
24
|
sequelize,
|
|
14
|
-
tableName: 'users',
|
|
25
|
+
tableName: applyTablePrefix(tablePrefix, 'users'),
|
|
15
26
|
timestamps: false
|
|
16
27
|
};
|
|
17
28
|
if (DIALECTS_SUPPORTING_UNSIGNED.has(sequelize.getDialect())) {
|
|
@@ -23,7 +34,7 @@ function userTableOptions(sequelize) {
|
|
|
23
34
|
class AuthUserModel extends sequelize_1.Model {
|
|
24
35
|
}
|
|
25
36
|
exports.AuthUserModel = AuthUserModel;
|
|
26
|
-
function initAuthUserModel(sequelize) {
|
|
37
|
+
function initAuthUserModel(sequelize, options = {}) {
|
|
27
38
|
const idType = integerIdType(sequelize);
|
|
28
39
|
AuthUserModel.init({
|
|
29
40
|
user_id: {
|
|
@@ -47,7 +58,7 @@ function initAuthUserModel(sequelize) {
|
|
|
47
58
|
allowNull: false
|
|
48
59
|
}
|
|
49
60
|
}, {
|
|
50
|
-
...userTableOptions(sequelize)
|
|
61
|
+
...userTableOptions(sequelize, options.tablePrefix)
|
|
51
62
|
});
|
|
52
63
|
return AuthUserModel;
|
|
53
64
|
}
|
|
@@ -63,7 +74,9 @@ class SequelizeUserStore extends base_js_1.UserStore {
|
|
|
63
74
|
}
|
|
64
75
|
this.Users = options.userModel
|
|
65
76
|
? options.userModel
|
|
66
|
-
: (options.userModelFactory ?? initAuthUserModel)(options.sequelize
|
|
77
|
+
: (options.userModelFactory ?? initAuthUserModel)(options.sequelize, {
|
|
78
|
+
tablePrefix: options.tablePrefix
|
|
79
|
+
});
|
|
67
80
|
this.recordMapper =
|
|
68
81
|
options.recordMapper ??
|
|
69
82
|
((model) => SequelizeUserStore.mapModelToUser(model));
|
|
@@ -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;
|
|
@@ -10,6 +10,22 @@ const DEFAULT_PASSKEY_CONFIG = {
|
|
|
10
10
|
timeoutMs: 5 * 60 * 1000,
|
|
11
11
|
userVerification: 'preferred'
|
|
12
12
|
};
|
|
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 resolveTablePrefix(...prefixes) {
|
|
21
|
+
for (const prefix of prefixes) {
|
|
22
|
+
const normalized = normalizeTablePrefix(prefix);
|
|
23
|
+
if (normalized) {
|
|
24
|
+
return normalized;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
13
29
|
function isOriginString(origin) {
|
|
14
30
|
return typeof origin === 'string' && origin.trim().length > 0;
|
|
15
31
|
}
|
|
@@ -33,6 +49,8 @@ export class SqlAuthStore {
|
|
|
33
49
|
}
|
|
34
50
|
this.sequelize = params.sequelize;
|
|
35
51
|
this.syncOptions = params.syncOptions;
|
|
52
|
+
const moduleTablePrefixes = params.tablePrefixes ?? {};
|
|
53
|
+
const userTablePrefix = resolveTablePrefix(moduleTablePrefixes.user, params.tablePrefix);
|
|
36
54
|
this.userStore = new SequelizeUserStore({
|
|
37
55
|
sequelize: this.sequelize,
|
|
38
56
|
userModel: params.userModel,
|
|
@@ -40,18 +58,28 @@ export class SqlAuthStore {
|
|
|
40
58
|
recordMapper: params.userRecordMapper,
|
|
41
59
|
toPublic: params.publicUserMapper,
|
|
42
60
|
bcryptRounds: params.bcryptRounds,
|
|
43
|
-
bcryptPepper: params.passwordPepper
|
|
61
|
+
bcryptPepper: params.passwordPepper,
|
|
62
|
+
tablePrefix: userTablePrefix
|
|
44
63
|
});
|
|
64
|
+
const tokenTablePrefix = resolveTablePrefix(params.tokenStoreOptions?.tablePrefix, moduleTablePrefixes.token, params.tablePrefix);
|
|
45
65
|
this.tokenStore =
|
|
46
|
-
params.tokenStore ??
|
|
66
|
+
params.tokenStore ??
|
|
67
|
+
new SequelizeTokenStore({
|
|
68
|
+
sequelize: this.sequelize,
|
|
69
|
+
...params.tokenStoreOptions,
|
|
70
|
+
tablePrefix: tokenTablePrefix
|
|
71
|
+
});
|
|
72
|
+
const oauthTablePrefix = resolveTablePrefix(params.oauthStoreOptions?.tablePrefix, moduleTablePrefixes.oauth, params.tablePrefix);
|
|
47
73
|
this.oauthStore = new SequelizeOAuthStore({
|
|
48
74
|
sequelize: this.sequelize,
|
|
49
75
|
...params.oauthStoreOptions,
|
|
76
|
+
tablePrefix: oauthTablePrefix,
|
|
50
77
|
bcryptRounds: params.bcryptRounds
|
|
51
78
|
});
|
|
52
79
|
let passkeyStore;
|
|
53
80
|
let passkeyConfig;
|
|
54
81
|
if (params.passkeys !== false) {
|
|
82
|
+
const passkeyTablePrefix = resolveTablePrefix(moduleTablePrefixes.passkey, params.tablePrefix);
|
|
55
83
|
passkeyConfig = normalizePasskeyConfig(params.passkeys ?? {});
|
|
56
84
|
const resolveUser = async (lookup) => {
|
|
57
85
|
const found = await this.userStore.findUser(lookup.userId ?? lookup.login ?? '');
|
|
@@ -66,7 +94,11 @@ export class SqlAuthStore {
|
|
|
66
94
|
}));
|
|
67
95
|
return mapper(found);
|
|
68
96
|
};
|
|
69
|
-
passkeyStore = new SequelizePasskeyStore({
|
|
97
|
+
passkeyStore = new SequelizePasskeyStore({
|
|
98
|
+
sequelize: this.sequelize,
|
|
99
|
+
resolveUser,
|
|
100
|
+
tablePrefix: passkeyTablePrefix
|
|
101
|
+
});
|
|
70
102
|
this.passkeyStore = passkeyStore;
|
|
71
103
|
}
|
|
72
104
|
this.adapter = new 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/esm/oauth/models.js
CHANGED
|
@@ -1,10 +1,21 @@
|
|
|
1
1
|
import { DataTypes, Model } from 'sequelize';
|
|
2
2
|
const DIALECTS_SUPPORTING_UNSIGNED = new Set(['mysql', 'mariadb']);
|
|
3
|
+
function normalizeTablePrefix(prefix) {
|
|
4
|
+
if (!prefix) {
|
|
5
|
+
return undefined;
|
|
6
|
+
}
|
|
7
|
+
const trimmed = prefix.trim();
|
|
8
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
9
|
+
}
|
|
10
|
+
function applyTablePrefix(prefix, tableName) {
|
|
11
|
+
const normalized = normalizeTablePrefix(prefix);
|
|
12
|
+
return normalized ? `${normalized}${tableName}` : tableName;
|
|
13
|
+
}
|
|
3
14
|
function integerIdType(sequelize) {
|
|
4
15
|
return DIALECTS_SUPPORTING_UNSIGNED.has(sequelize.getDialect()) ? DataTypes.INTEGER.UNSIGNED : DataTypes.INTEGER;
|
|
5
16
|
}
|
|
6
|
-
function tableOptions(sequelize, tableName, extra) {
|
|
7
|
-
const opts = { sequelize, tableName };
|
|
17
|
+
function tableOptions(sequelize, tableName, tablePrefix, extra) {
|
|
18
|
+
const opts = { sequelize, tableName: applyTablePrefix(tablePrefix, tableName) };
|
|
8
19
|
if (extra) {
|
|
9
20
|
Object.assign(opts, extra);
|
|
10
21
|
}
|
|
@@ -16,7 +27,7 @@ function tableOptions(sequelize, tableName, extra) {
|
|
|
16
27
|
}
|
|
17
28
|
export class OAuthClientModel extends Model {
|
|
18
29
|
}
|
|
19
|
-
export function initOAuthClientModel(sequelize) {
|
|
30
|
+
export function initOAuthClientModel(sequelize, options = {}) {
|
|
20
31
|
OAuthClientModel.init({
|
|
21
32
|
client_id: { type: DataTypes.STRING(128), allowNull: false, primaryKey: true },
|
|
22
33
|
client_secret: { type: DataTypes.STRING(255), allowNull: false, defaultValue: '' },
|
|
@@ -26,13 +37,13 @@ export function initOAuthClientModel(sequelize) {
|
|
|
26
37
|
metadata: { type: DataTypes.TEXT, allowNull: true, defaultValue: null },
|
|
27
38
|
first_party: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: false }
|
|
28
39
|
}, {
|
|
29
|
-
...tableOptions(sequelize, 'oauth_clients', { timestamps: false })
|
|
40
|
+
...tableOptions(sequelize, 'oauth_clients', options.tablePrefix, { timestamps: false })
|
|
30
41
|
});
|
|
31
42
|
return OAuthClientModel;
|
|
32
43
|
}
|
|
33
44
|
export class OAuthCodeModel extends Model {
|
|
34
45
|
}
|
|
35
|
-
export function initOAuthCodeModel(sequelize) {
|
|
46
|
+
export function initOAuthCodeModel(sequelize, options = {}) {
|
|
36
47
|
const idType = integerIdType(sequelize);
|
|
37
48
|
OAuthCodeModel.init({
|
|
38
49
|
code: { type: DataTypes.STRING(128), allowNull: false, primaryKey: true },
|
|
@@ -45,7 +56,7 @@ export function initOAuthCodeModel(sequelize) {
|
|
|
45
56
|
expires: { type: DataTypes.DATE, allowNull: false },
|
|
46
57
|
metadata: { type: DataTypes.TEXT, allowNull: true, defaultValue: null }
|
|
47
58
|
}, {
|
|
48
|
-
...tableOptions(sequelize, 'oauth_codes', { timestamps: false })
|
|
59
|
+
...tableOptions(sequelize, 'oauth_codes', options.tablePrefix, { timestamps: false })
|
|
49
60
|
});
|
|
50
61
|
return OAuthCodeModel;
|
|
51
62
|
}
|
|
@@ -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 {
|
|
@@ -2,11 +2,22 @@ import bcrypt from 'bcryptjs';
|
|
|
2
2
|
import { DataTypes, Model } from 'sequelize';
|
|
3
3
|
import { OAuthStore } from './base.js';
|
|
4
4
|
const DIALECTS_SUPPORTING_UNSIGNED = new Set(['mysql', 'mariadb']);
|
|
5
|
+
function normalizeTablePrefix(prefix) {
|
|
6
|
+
if (!prefix) {
|
|
7
|
+
return undefined;
|
|
8
|
+
}
|
|
9
|
+
const trimmed = prefix.trim();
|
|
10
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
11
|
+
}
|
|
12
|
+
function applyTablePrefix(prefix, tableName) {
|
|
13
|
+
const normalized = normalizeTablePrefix(prefix);
|
|
14
|
+
return normalized ? `${normalized}${tableName}` : tableName;
|
|
15
|
+
}
|
|
5
16
|
function integerIdType(sequelize) {
|
|
6
17
|
return DIALECTS_SUPPORTING_UNSIGNED.has(sequelize.getDialect()) ? DataTypes.INTEGER.UNSIGNED : DataTypes.INTEGER;
|
|
7
18
|
}
|
|
8
|
-
function tableOptions(sequelize, tableName, extra) {
|
|
9
|
-
const opts = { sequelize, tableName };
|
|
19
|
+
function tableOptions(sequelize, tableName, tablePrefix, extra) {
|
|
20
|
+
const opts = { sequelize, tableName: applyTablePrefix(tablePrefix, tableName) };
|
|
10
21
|
if (extra) {
|
|
11
22
|
Object.assign(opts, extra);
|
|
12
23
|
}
|
|
@@ -18,7 +29,7 @@ function tableOptions(sequelize, tableName, extra) {
|
|
|
18
29
|
}
|
|
19
30
|
export class OAuthClientModel extends Model {
|
|
20
31
|
}
|
|
21
|
-
export function initOAuthClientModel(sequelize) {
|
|
32
|
+
export function initOAuthClientModel(sequelize, options = {}) {
|
|
22
33
|
OAuthClientModel.init({
|
|
23
34
|
client_id: { type: DataTypes.STRING(128), allowNull: false, primaryKey: true },
|
|
24
35
|
client_secret: { type: DataTypes.STRING(255), allowNull: false, defaultValue: '' },
|
|
@@ -27,12 +38,12 @@ export function initOAuthClientModel(sequelize) {
|
|
|
27
38
|
scope: { type: DataTypes.TEXT, allowNull: false, defaultValue: '[]' },
|
|
28
39
|
metadata: { type: DataTypes.TEXT, allowNull: true, defaultValue: null },
|
|
29
40
|
first_party: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: false }
|
|
30
|
-
}, tableOptions(sequelize, 'oauth_clients', { timestamps: false }));
|
|
41
|
+
}, tableOptions(sequelize, 'oauth_clients', options.tablePrefix, { timestamps: false }));
|
|
31
42
|
return OAuthClientModel;
|
|
32
43
|
}
|
|
33
44
|
export class OAuthCodeModel extends Model {
|
|
34
45
|
}
|
|
35
|
-
export function initOAuthCodeModel(sequelize) {
|
|
46
|
+
export function initOAuthCodeModel(sequelize, options = {}) {
|
|
36
47
|
const idType = integerIdType(sequelize);
|
|
37
48
|
OAuthCodeModel.init({
|
|
38
49
|
code: { type: DataTypes.STRING(128), allowNull: false, primaryKey: true },
|
|
@@ -44,7 +55,7 @@ export function initOAuthCodeModel(sequelize) {
|
|
|
44
55
|
code_challenge_method: { type: DataTypes.STRING(10), allowNull: true, defaultValue: null },
|
|
45
56
|
expires: { type: DataTypes.DATE, allowNull: false },
|
|
46
57
|
metadata: { type: DataTypes.TEXT, allowNull: true, defaultValue: null }
|
|
47
|
-
}, tableOptions(sequelize, 'oauth_codes', { timestamps: false }));
|
|
58
|
+
}, tableOptions(sequelize, 'oauth_codes', options.tablePrefix, { timestamps: false }));
|
|
48
59
|
return OAuthCodeModel;
|
|
49
60
|
}
|
|
50
61
|
function encodeStringArray(values) {
|
|
@@ -104,8 +115,16 @@ export class SequelizeOAuthStore extends OAuthStore {
|
|
|
104
115
|
if (!options?.sequelize) {
|
|
105
116
|
throw new Error('SequelizeOAuthStore requires an initialised Sequelize instance');
|
|
106
117
|
}
|
|
107
|
-
this.clients =
|
|
108
|
-
|
|
118
|
+
this.clients =
|
|
119
|
+
options.clientModel ??
|
|
120
|
+
(options.clientModelFactory ?? initOAuthClientModel)(options.sequelize, {
|
|
121
|
+
tablePrefix: options.tablePrefix
|
|
122
|
+
});
|
|
123
|
+
this.codes =
|
|
124
|
+
options.codeModel ??
|
|
125
|
+
(options.codeModelFactory ?? initOAuthCodeModel)(options.sequelize, {
|
|
126
|
+
tablePrefix: options.tablePrefix
|
|
127
|
+
});
|
|
109
128
|
this.bcryptRounds = options.bcryptRounds ?? 12;
|
|
110
129
|
}
|
|
111
130
|
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>;
|
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
import { DataTypes, Model } from 'sequelize';
|
|
2
2
|
const DIALECTS_SUPPORTING_UNSIGNED = new Set(['mysql', 'mariadb']);
|
|
3
|
+
function normalizeTablePrefix(prefix) {
|
|
4
|
+
if (!prefix) {
|
|
5
|
+
return undefined;
|
|
6
|
+
}
|
|
7
|
+
const trimmed = prefix.trim();
|
|
8
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
9
|
+
}
|
|
10
|
+
function applyTablePrefix(prefix, tableName) {
|
|
11
|
+
const normalized = normalizeTablePrefix(prefix);
|
|
12
|
+
return normalized ? `${normalized}${tableName}` : tableName;
|
|
13
|
+
}
|
|
3
14
|
function integerIdType(sequelize) {
|
|
4
15
|
return DIALECTS_SUPPORTING_UNSIGNED.has(sequelize.getDialect()) ? DataTypes.INTEGER.UNSIGNED : DataTypes.INTEGER;
|
|
5
16
|
}
|
|
@@ -7,7 +18,7 @@ export class PasskeyCredentialModel extends Model {
|
|
|
7
18
|
}
|
|
8
19
|
export class PasskeyChallengeModel extends Model {
|
|
9
20
|
}
|
|
10
|
-
export function initPasskeyCredentialModel(sequelize) {
|
|
21
|
+
export function initPasskeyCredentialModel(sequelize, options = {}) {
|
|
11
22
|
const idType = integerIdType(sequelize);
|
|
12
23
|
return PasskeyCredentialModel.init({
|
|
13
24
|
credentialId: {
|
|
@@ -97,12 +108,12 @@ export function initPasskeyCredentialModel(sequelize) {
|
|
|
97
108
|
}
|
|
98
109
|
}, {
|
|
99
110
|
sequelize,
|
|
100
|
-
tableName: 'passkey_credentials',
|
|
111
|
+
tableName: applyTablePrefix(options.tablePrefix, 'passkey_credentials'),
|
|
101
112
|
timestamps: true,
|
|
102
113
|
underscored: true
|
|
103
114
|
});
|
|
104
115
|
}
|
|
105
|
-
export function initPasskeyChallengeModel(sequelize) {
|
|
116
|
+
export function initPasskeyChallengeModel(sequelize, options = {}) {
|
|
106
117
|
const idType = integerIdType(sequelize);
|
|
107
118
|
return PasskeyChallengeModel.init({
|
|
108
119
|
challenge: {
|
|
@@ -130,7 +141,7 @@ export function initPasskeyChallengeModel(sequelize) {
|
|
|
130
141
|
}
|
|
131
142
|
}, {
|
|
132
143
|
sequelize,
|
|
133
|
-
tableName: 'passkey_challenges',
|
|
144
|
+
tableName: applyTablePrefix(options.tablePrefix, 'passkey_challenges'),
|
|
134
145
|
timestamps: true,
|
|
135
146
|
underscored: true,
|
|
136
147
|
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;
|
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
import { DataTypes, Model, Op } from 'sequelize';
|
|
2
2
|
import { PasskeyStore } from './base.js';
|
|
3
3
|
const DIALECTS_SUPPORTING_UNSIGNED = new Set(['mysql', 'mariadb']);
|
|
4
|
+
function normalizeTablePrefix(prefix) {
|
|
5
|
+
if (!prefix) {
|
|
6
|
+
return undefined;
|
|
7
|
+
}
|
|
8
|
+
const trimmed = prefix.trim();
|
|
9
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
10
|
+
}
|
|
11
|
+
function applyTablePrefix(prefix, tableName) {
|
|
12
|
+
const normalized = normalizeTablePrefix(prefix);
|
|
13
|
+
return normalized ? `${normalized}${tableName}` : tableName;
|
|
14
|
+
}
|
|
4
15
|
function integerIdType(sequelize) {
|
|
5
16
|
return DIALECTS_SUPPORTING_UNSIGNED.has(sequelize.getDialect()) ? DataTypes.INTEGER.UNSIGNED : DataTypes.INTEGER;
|
|
6
17
|
}
|
|
@@ -20,7 +31,7 @@ class PasskeyCredentialModel extends Model {
|
|
|
20
31
|
}
|
|
21
32
|
class PasskeyChallengeModel extends Model {
|
|
22
33
|
}
|
|
23
|
-
function initPasskeyCredentialModel(sequelize) {
|
|
34
|
+
function initPasskeyCredentialModel(sequelize, options = {}) {
|
|
24
35
|
const idType = integerIdType(sequelize);
|
|
25
36
|
return PasskeyCredentialModel.init({
|
|
26
37
|
credentialId: {
|
|
@@ -110,12 +121,12 @@ function initPasskeyCredentialModel(sequelize) {
|
|
|
110
121
|
}
|
|
111
122
|
}, {
|
|
112
123
|
sequelize,
|
|
113
|
-
tableName: 'passkey_credentials',
|
|
124
|
+
tableName: applyTablePrefix(options.tablePrefix, 'passkey_credentials'),
|
|
114
125
|
timestamps: true,
|
|
115
126
|
underscored: true
|
|
116
127
|
});
|
|
117
128
|
}
|
|
118
|
-
function initPasskeyChallengeModel(sequelize) {
|
|
129
|
+
function initPasskeyChallengeModel(sequelize, options = {}) {
|
|
119
130
|
const idType = integerIdType(sequelize);
|
|
120
131
|
return PasskeyChallengeModel.init({
|
|
121
132
|
challenge: {
|
|
@@ -143,7 +154,7 @@ function initPasskeyChallengeModel(sequelize) {
|
|
|
143
154
|
}
|
|
144
155
|
}, {
|
|
145
156
|
sequelize,
|
|
146
|
-
tableName: 'passkey_challenges',
|
|
157
|
+
tableName: applyTablePrefix(options.tablePrefix, 'passkey_challenges'),
|
|
147
158
|
timestamps: true,
|
|
148
159
|
underscored: true,
|
|
149
160
|
indexes: [{ fields: ['expires_at'] }, { fields: ['user_id'] }]
|
|
@@ -158,9 +169,14 @@ export class SequelizePasskeyStore extends PasskeyStore {
|
|
|
158
169
|
this.resolveUserFn = options.resolveUser;
|
|
159
170
|
this.credentials =
|
|
160
171
|
options.credentialModel ??
|
|
161
|
-
(options.credentialModelFactory ?? initPasskeyCredentialModel)(options.sequelize
|
|
172
|
+
(options.credentialModelFactory ?? initPasskeyCredentialModel)(options.sequelize, {
|
|
173
|
+
tablePrefix: options.tablePrefix
|
|
174
|
+
});
|
|
162
175
|
this.challenges =
|
|
163
|
-
options.challengeModel ??
|
|
176
|
+
options.challengeModel ??
|
|
177
|
+
(options.challengeModelFactory ?? initPasskeyChallengeModel)(options.sequelize, {
|
|
178
|
+
tablePrefix: options.tablePrefix
|
|
179
|
+
});
|
|
164
180
|
}
|
|
165
181
|
async resolveUser(params) {
|
|
166
182
|
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>;
|
|
@@ -1,12 +1,23 @@
|
|
|
1
1
|
import { DataTypes, Model, Op } from 'sequelize';
|
|
2
2
|
import { TokenStore } from './base.js';
|
|
3
3
|
const DIALECTS_SUPPORTING_UNSIGNED = new Set(['mysql', 'mariadb']);
|
|
4
|
+
function normalizeTablePrefix(prefix) {
|
|
5
|
+
if (!prefix) {
|
|
6
|
+
return undefined;
|
|
7
|
+
}
|
|
8
|
+
const trimmed = prefix.trim();
|
|
9
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
10
|
+
}
|
|
11
|
+
function applyTablePrefix(prefix, tableName) {
|
|
12
|
+
const normalized = normalizeTablePrefix(prefix);
|
|
13
|
+
return normalized ? `${normalized}${tableName}` : tableName;
|
|
14
|
+
}
|
|
4
15
|
class TokenModel extends Model {
|
|
5
16
|
}
|
|
6
|
-
function tokenTableOptions(sequelize) {
|
|
17
|
+
function tokenTableOptions(sequelize, tablePrefix) {
|
|
7
18
|
const opts = {
|
|
8
19
|
sequelize,
|
|
9
|
-
tableName: 'jwttokens',
|
|
20
|
+
tableName: applyTablePrefix(tablePrefix, 'jwttokens'),
|
|
10
21
|
timestamps: false
|
|
11
22
|
};
|
|
12
23
|
if (DIALECTS_SUPPORTING_UNSIGNED.has(sequelize.getDialect())) {
|
|
@@ -15,7 +26,11 @@ function tokenTableOptions(sequelize) {
|
|
|
15
26
|
}
|
|
16
27
|
return opts;
|
|
17
28
|
}
|
|
18
|
-
function initTokenModel(sequelize) {
|
|
29
|
+
function initTokenModel(sequelize, options = {}) {
|
|
30
|
+
const tableName = applyTablePrefix(options.tablePrefix, 'jwttokens');
|
|
31
|
+
const usePrefixedIndexNames = tableName !== 'jwttokens';
|
|
32
|
+
const accessIndexName = usePrefixedIndexNames ? `${tableName}_access_unique` : 'jwt_access_unique';
|
|
33
|
+
const refreshIndexName = usePrefixedIndexNames ? `${tableName}_refresh_unique` : 'jwt_refresh_unique';
|
|
19
34
|
TokenModel.init({
|
|
20
35
|
token_id: {
|
|
21
36
|
type: DIALECTS_SUPPORTING_UNSIGNED.has(sequelize.getDialect())
|
|
@@ -117,10 +132,10 @@ function initTokenModel(sequelize) {
|
|
|
117
132
|
defaultValue: '[]'
|
|
118
133
|
}
|
|
119
134
|
}, {
|
|
120
|
-
...tokenTableOptions(sequelize),
|
|
135
|
+
...tokenTableOptions(sequelize, options.tablePrefix),
|
|
121
136
|
indexes: [
|
|
122
|
-
{ name:
|
|
123
|
-
{ name:
|
|
137
|
+
{ name: accessIndexName, unique: true, fields: ['access'] },
|
|
138
|
+
{ name: refreshIndexName, unique: true, fields: ['refresh'] }
|
|
124
139
|
]
|
|
125
140
|
});
|
|
126
141
|
return TokenModel;
|
|
@@ -131,7 +146,11 @@ export class SequelizeTokenStore extends TokenStore {
|
|
|
131
146
|
if (!options?.sequelize) {
|
|
132
147
|
throw new Error('SequelizeTokenStore requires an initialised Sequelize instance');
|
|
133
148
|
}
|
|
134
|
-
this.Tokens =
|
|
149
|
+
this.Tokens =
|
|
150
|
+
options.tokenModel ??
|
|
151
|
+
(options.tokenModelFactory ?? initTokenModel)(options.sequelize, {
|
|
152
|
+
tablePrefix: options.tablePrefix
|
|
153
|
+
});
|
|
135
154
|
}
|
|
136
155
|
async save(record) {
|
|
137
156
|
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
|
}
|
|
@@ -1,13 +1,24 @@
|
|
|
1
1
|
import { DataTypes, Model, Op } from 'sequelize';
|
|
2
2
|
import { UserStore } from './base.js';
|
|
3
3
|
const DIALECTS_SUPPORTING_UNSIGNED = new Set(['mysql', 'mariadb']);
|
|
4
|
+
function normalizeTablePrefix(prefix) {
|
|
5
|
+
if (!prefix) {
|
|
6
|
+
return undefined;
|
|
7
|
+
}
|
|
8
|
+
const trimmed = prefix.trim();
|
|
9
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
10
|
+
}
|
|
11
|
+
function applyTablePrefix(prefix, tableName) {
|
|
12
|
+
const normalized = normalizeTablePrefix(prefix);
|
|
13
|
+
return normalized ? `${normalized}${tableName}` : tableName;
|
|
14
|
+
}
|
|
4
15
|
function integerIdType(sequelize) {
|
|
5
16
|
return DIALECTS_SUPPORTING_UNSIGNED.has(sequelize.getDialect()) ? DataTypes.INTEGER.UNSIGNED : DataTypes.INTEGER;
|
|
6
17
|
}
|
|
7
|
-
function userTableOptions(sequelize) {
|
|
18
|
+
function userTableOptions(sequelize, tablePrefix) {
|
|
8
19
|
const opts = {
|
|
9
20
|
sequelize,
|
|
10
|
-
tableName: 'users',
|
|
21
|
+
tableName: applyTablePrefix(tablePrefix, 'users'),
|
|
11
22
|
timestamps: false
|
|
12
23
|
};
|
|
13
24
|
if (DIALECTS_SUPPORTING_UNSIGNED.has(sequelize.getDialect())) {
|
|
@@ -18,7 +29,7 @@ function userTableOptions(sequelize) {
|
|
|
18
29
|
}
|
|
19
30
|
export class AuthUserModel extends Model {
|
|
20
31
|
}
|
|
21
|
-
export function initAuthUserModel(sequelize) {
|
|
32
|
+
export function initAuthUserModel(sequelize, options = {}) {
|
|
22
33
|
const idType = integerIdType(sequelize);
|
|
23
34
|
AuthUserModel.init({
|
|
24
35
|
user_id: {
|
|
@@ -42,7 +53,7 @@ export function initAuthUserModel(sequelize) {
|
|
|
42
53
|
allowNull: false
|
|
43
54
|
}
|
|
44
55
|
}, {
|
|
45
|
-
...userTableOptions(sequelize)
|
|
56
|
+
...userTableOptions(sequelize, options.tablePrefix)
|
|
46
57
|
});
|
|
47
58
|
return AuthUserModel;
|
|
48
59
|
}
|
|
@@ -58,7 +69,9 @@ export class SequelizeUserStore extends UserStore {
|
|
|
58
69
|
}
|
|
59
70
|
this.Users = options.userModel
|
|
60
71
|
? options.userModel
|
|
61
|
-
: (options.userModelFactory ?? initAuthUserModel)(options.sequelize
|
|
72
|
+
: (options.userModelFactory ?? initAuthUserModel)(options.sequelize, {
|
|
73
|
+
tablePrefix: options.tablePrefix
|
|
74
|
+
});
|
|
62
75
|
this.recordMapper =
|
|
63
76
|
options.recordMapper ??
|
|
64
77
|
((model) => SequelizeUserStore.mapModelToUser(model));
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"openapi": "3.1.0",
|
|
3
3
|
"info": {
|
|
4
4
|
"title": "API Server Base",
|
|
5
|
-
"version": "
|
|
5
|
+
"version": "2.0.0-beta.16",
|
|
6
6
|
"description": "OpenAPI reference for ApiServer base endpoints and optional modules. Auth, passkey, and oauth modules are optional and require the corresponding module to be enabled in the ApiServer config. Base endpoints are always available."
|
|
7
7
|
},
|
|
8
8
|
"servers": [
|