@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
|
@@ -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));
|
|
@@ -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;
|
|
@@ -335,6 +335,7 @@ function fillConfig(config) {
|
|
|
335
335
|
apiHost: config.apiHost ?? 'localhost',
|
|
336
336
|
uploadPath: config.uploadPath ?? '',
|
|
337
337
|
uploadMax: config.uploadMax ?? 30 * 1024 * 1024,
|
|
338
|
+
staticDirs: config.staticDirs,
|
|
338
339
|
origins: config.origins ?? [],
|
|
339
340
|
debug: config.debug ?? false,
|
|
340
341
|
apiBasePath: config.apiBasePath ?? '/api',
|
|
@@ -391,6 +392,7 @@ export class ApiServer {
|
|
|
391
392
|
this.app.use(upload.any());
|
|
392
393
|
}
|
|
393
394
|
this.middlewares();
|
|
395
|
+
this.installStaticDirs();
|
|
394
396
|
this.installPingHandler();
|
|
395
397
|
this.installSwaggerHandler();
|
|
396
398
|
// addSwaggerUi(this.app);
|
|
@@ -648,6 +650,21 @@ export class ApiServer {
|
|
|
648
650
|
};
|
|
649
651
|
this.app.use(cors(corsOptions));
|
|
650
652
|
}
|
|
653
|
+
installStaticDirs() {
|
|
654
|
+
const staticDirs = this.config.staticDirs;
|
|
655
|
+
if (!staticDirs || !isPlainObject(staticDirs)) {
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
for (const [mountRaw, dirRaw] of Object.entries(staticDirs)) {
|
|
659
|
+
const mount = typeof mountRaw === 'string' ? mountRaw.trim() : '';
|
|
660
|
+
const dir = typeof dirRaw === 'string' ? dirRaw.trim() : '';
|
|
661
|
+
if (!mount || !dir) {
|
|
662
|
+
continue;
|
|
663
|
+
}
|
|
664
|
+
const resolvedMount = mount.startsWith('/') ? mount : `/${mount}`;
|
|
665
|
+
this.app.use(resolvedMount, express.static(dir));
|
|
666
|
+
}
|
|
667
|
+
}
|
|
651
668
|
installPingHandler() {
|
|
652
669
|
const path = `${this.apiBasePath}/v1/ping`;
|
|
653
670
|
this.app.get(path, (_req, res) => {
|
|
@@ -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
|
}
|