@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 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) => 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;
@@ -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 ?? new sequelize_js_3.SequelizeTokenStore({ sequelize: this.sequelize, ...params.tokenStoreOptions });
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({ sequelize: this.sequelize, resolveUser });
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): 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;
@@ -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): 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 {
@@ -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 = options.clientModel ?? (options.clientModelFactory ?? initOAuthClientModel)(options.sequelize);
118
- this.codes = options.codeModel ?? (options.codeModelFactory ?? initOAuthCodeModel)(options.sequelize);
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): 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>;
@@ -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) => 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;
@@ -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 ?? (options.challengeModelFactory ?? initPasskeyChallengeModel)(options.sequelize);
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) => 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>;
@@ -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: 'jwt_access_unique', unique: true, fields: ['access'] },
126
- { name: 'jwt_refresh_unique', unique: true, fields: ['refresh'] }
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 = options.tokenModel ?? (options.tokenModelFactory ?? initTokenModel)(options.sequelize);
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): 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
  }
@@ -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) => 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
  }
@@ -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": "1.0.0",
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": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@technomoron/api-server-base",
3
- "version": "2.0.0-beta.15",
3
+ "version": "2.0.0-beta.16",
4
4
  "description": "Api Server Skeleton / Base Class",
5
5
  "type": "module",
6
6
  "main": "./dist/cjs/index.cjs",