@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.
Files changed (35) hide show
  1. package/README.txt +17 -0
  2. package/dist/cjs/api-server-base.cjs +17 -0
  3. package/dist/cjs/api-server-base.d.ts +2 -0
  4. package/dist/cjs/auth-api/sql-auth-store.d.ts +11 -1
  5. package/dist/cjs/auth-api/sql-auth-store.js +35 -3
  6. package/dist/cjs/oauth/models.d.ts +6 -2
  7. package/dist/cjs/oauth/models.js +17 -6
  8. package/dist/cjs/oauth/sequelize.d.ts +13 -4
  9. package/dist/cjs/oauth/sequelize.js +27 -8
  10. package/dist/cjs/passkey/models.d.ts +6 -2
  11. package/dist/cjs/passkey/models.js +15 -4
  12. package/dist/cjs/passkey/sequelize.d.ts +7 -2
  13. package/dist/cjs/passkey/sequelize.js +22 -6
  14. package/dist/cjs/token/sequelize.d.ts +4 -1
  15. package/dist/cjs/token/sequelize.js +26 -7
  16. package/dist/cjs/user/sequelize.d.ts +7 -2
  17. package/dist/cjs/user/sequelize.js +18 -5
  18. package/dist/esm/api-server-base.d.ts +2 -0
  19. package/dist/esm/api-server-base.js +17 -0
  20. package/dist/esm/auth-api/sql-auth-store.d.ts +11 -1
  21. package/dist/esm/auth-api/sql-auth-store.js +35 -3
  22. package/dist/esm/oauth/models.d.ts +6 -2
  23. package/dist/esm/oauth/models.js +17 -6
  24. package/dist/esm/oauth/sequelize.d.ts +13 -4
  25. package/dist/esm/oauth/sequelize.js +27 -8
  26. package/dist/esm/passkey/models.d.ts +6 -2
  27. package/dist/esm/passkey/models.js +15 -4
  28. package/dist/esm/passkey/sequelize.d.ts +7 -2
  29. package/dist/esm/passkey/sequelize.js +22 -6
  30. package/dist/esm/token/sequelize.d.ts +4 -1
  31. package/dist/esm/token/sequelize.js +26 -7
  32. package/dist/esm/user/sequelize.d.ts +7 -2
  33. package/dist/esm/user/sequelize.js +18 -5
  34. package/docs/swagger/openapi.json +1 -1
  35. 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) => GenericUserModelStatic;
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 ?? new SequelizeTokenStore({ sequelize: this.sequelize, ...params.tokenStoreOptions });
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({ sequelize: this.sequelize, resolveUser });
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): typeof OAuthClientModel;
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): typeof OAuthCodeModel;
47
+ export declare function initOAuthCodeModel(sequelize: Sequelize, options?: {
48
+ tablePrefix?: string;
49
+ }): typeof OAuthCodeModel;
@@ -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): typeof OAuthClientModel;
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): typeof OAuthCodeModel;
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) => typeof OAuthClientModel;
52
- codeModelFactory?: (sequelize: Sequelize) => typeof OAuthCodeModel;
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 = options.clientModel ?? (options.clientModelFactory ?? initOAuthClientModel)(options.sequelize);
108
- this.codes = options.codeModel ?? (options.codeModelFactory ?? initOAuthCodeModel)(options.sequelize);
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): ModelStatic<PasskeyCredentialModel>;
30
- export declare function initPasskeyChallengeModel(sequelize: Sequelize): ModelStatic<PasskeyChallengeModel>;
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) => ModelStatic<PasskeyCredentialModel>;
37
- challengeModelFactory?: (sequelize: Sequelize) => ModelStatic<PasskeyChallengeModel>;
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 ?? (options.challengeModelFactory ?? initPasskeyChallengeModel)(options.sequelize);
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) => ModelStatic<TokenModel>;
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: 'jwt_access_unique', unique: true, fields: ['access'] },
123
- { name: 'jwt_refresh_unique', unique: true, fields: ['refresh'] }
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 = options.tokenModel ?? (options.tokenModelFactory ?? initTokenModel)(options.sequelize);
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): typeof AuthUserModel;
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) => GenericUserModelStatic;
24
+ userModelFactory?: (sequelize: Sequelize, options?: {
25
+ tablePrefix?: string;
26
+ }) => GenericUserModelStatic;
22
27
  recordMapper?: (model: GenericUserModel) => UserAttributes;
23
28
  toPublic?: PublicUserMapper<UserAttributes, PublicUserShape>;
24
29
  }