@technomoron/api-server-base 2.0.0-beta.14 → 2.0.0-beta.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.txt +16 -0
- package/dist/cjs/api-module.d.ts +3 -2
- package/dist/cjs/api-server-base.cjs +67 -18
- package/dist/cjs/api-server-base.d.ts +17 -15
- package/dist/cjs/auth-api/auth-module.js +10 -10
- package/dist/cjs/auth-api/module.d.ts +1 -1
- 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/passkey/service.js +11 -11
- package/dist/cjs/token/base.d.ts +2 -1
- 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-module.d.ts +3 -2
- package/dist/esm/api-server-base.d.ts +17 -15
- package/dist/esm/api-server-base.js +67 -18
- package/dist/esm/auth-api/auth-module.js +10 -10
- package/dist/esm/auth-api/module.d.ts +1 -1
- 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/passkey/service.js +11 -11
- package/dist/esm/token/base.d.ts +2 -1
- 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 +11 -10
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.
|
package/dist/cjs/api-module.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ApiRequest } from './api-server-base.js';
|
|
2
|
-
export type
|
|
2
|
+
export type ApiHandlerResult<Data = unknown> = [number] | [number, Data] | [number, Data, string];
|
|
3
|
+
export type ApiHandler<Data = unknown> = (apiReq: ApiRequest) => Promise<ApiHandlerResult<Data>>;
|
|
3
4
|
export type ApiAuthType = 'none' | 'maybe' | 'yes' | 'strict' | 'apikey';
|
|
4
5
|
export type ApiAuthClass = 'any' | 'admin';
|
|
5
6
|
export interface ApiKey {
|
|
@@ -14,7 +15,7 @@ export type ApiRoute = {
|
|
|
14
15
|
req: ApiAuthClass;
|
|
15
16
|
};
|
|
16
17
|
};
|
|
17
|
-
export declare class ApiModule<T> {
|
|
18
|
+
export declare class ApiModule<T = unknown> {
|
|
18
19
|
server: T;
|
|
19
20
|
namespace: string;
|
|
20
21
|
mountpath: string;
|
|
@@ -49,11 +49,14 @@ function guess_exception_text(error, defMsg = 'Unknown Error') {
|
|
|
49
49
|
msg.push(error);
|
|
50
50
|
}
|
|
51
51
|
else if (error && typeof error === 'object') {
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
const errorDetails = error;
|
|
53
|
+
if (typeof errorDetails.message === 'string' && errorDetails.message.trim() !== '') {
|
|
54
|
+
msg.push(errorDetails.message);
|
|
54
55
|
}
|
|
55
|
-
if (
|
|
56
|
-
|
|
56
|
+
if (errorDetails.parent &&
|
|
57
|
+
typeof errorDetails.parent.message === 'string' &&
|
|
58
|
+
errorDetails.parent.message.trim() !== '') {
|
|
59
|
+
msg.push(errorDetails.parent.message);
|
|
57
60
|
}
|
|
58
61
|
}
|
|
59
62
|
return msg.length > 0 ? msg.join(' / ') : defMsg;
|
|
@@ -367,6 +370,7 @@ function fillConfig(config) {
|
|
|
367
370
|
class ApiServer {
|
|
368
371
|
constructor(config = {}) {
|
|
369
372
|
this.currReq = null;
|
|
373
|
+
this.serverAuthAdapter = null;
|
|
370
374
|
this.apiNotFoundHandler = null;
|
|
371
375
|
this.tokenStoreAdapter = null;
|
|
372
376
|
this.userStoreAdapter = null;
|
|
@@ -387,7 +391,7 @@ class ApiServer {
|
|
|
387
391
|
this.passkeyServiceAdapter = passkeyService ?? null;
|
|
388
392
|
this.oauthStoreAdapter = oauthStore ?? null;
|
|
389
393
|
this.canImpersonateAdapter = canImpersonate ?? null;
|
|
390
|
-
this.storageAdapter = this;
|
|
394
|
+
this.storageAdapter = this.getServerAuthAdapter();
|
|
391
395
|
}
|
|
392
396
|
this.app = (0, express_1.default)();
|
|
393
397
|
if (config.uploadPath) {
|
|
@@ -428,9 +432,9 @@ class ApiServer {
|
|
|
428
432
|
}
|
|
429
433
|
setTokenStore(store) {
|
|
430
434
|
this.tokenStoreAdapter = store;
|
|
431
|
-
// If using direct stores, expose
|
|
435
|
+
// If using direct stores, expose the server-backed auth adapter.
|
|
432
436
|
if (this.userStoreAdapter) {
|
|
433
|
-
this.storageAdapter = this;
|
|
437
|
+
this.storageAdapter = this.getServerAuthAdapter();
|
|
434
438
|
}
|
|
435
439
|
return this;
|
|
436
440
|
}
|
|
@@ -467,6 +471,33 @@ class ApiServer {
|
|
|
467
471
|
}
|
|
468
472
|
return this.oauthStoreAdapter;
|
|
469
473
|
}
|
|
474
|
+
getServerAuthAdapter() {
|
|
475
|
+
if (this.serverAuthAdapter) {
|
|
476
|
+
return this.serverAuthAdapter;
|
|
477
|
+
}
|
|
478
|
+
const server = this;
|
|
479
|
+
this.serverAuthAdapter = {
|
|
480
|
+
getUser: (identifier) => server.getUser(identifier),
|
|
481
|
+
getUserPasswordHash: (user) => server.getUserPasswordHash(user),
|
|
482
|
+
getUserId: (user) => server.getUserId(user),
|
|
483
|
+
filterUser: (user) => server.filterUser(user),
|
|
484
|
+
verifyPassword: (password, hash) => server.verifyPassword(password, hash),
|
|
485
|
+
storeToken: (data) => server.storeToken(data),
|
|
486
|
+
getToken: (query, opts) => server.getToken(query, opts),
|
|
487
|
+
deleteToken: (query) => server.deleteToken(query),
|
|
488
|
+
updateToken: (updates) => server.updateToken(updates),
|
|
489
|
+
createPasskeyChallenge: (params) => server.createPasskeyChallenge(params),
|
|
490
|
+
verifyPasskeyResponse: (params) => server.verifyPasskeyResponse(params),
|
|
491
|
+
listUserCredentials: (userId) => server.listUserCredentials(userId),
|
|
492
|
+
deletePasskeyCredential: (credentialId) => server.deletePasskeyCredential(credentialId),
|
|
493
|
+
getClient: (clientId) => server.getClient(clientId),
|
|
494
|
+
verifyClientSecret: (client, clientSecret) => server.verifyClientSecret(client, clientSecret),
|
|
495
|
+
createAuthCode: (request) => server.createAuthCode(request),
|
|
496
|
+
consumeAuthCode: (code, clientId) => server.consumeAuthCode(code, clientId),
|
|
497
|
+
canImpersonate: (params) => server.canImpersonate(params)
|
|
498
|
+
};
|
|
499
|
+
return this.serverAuthAdapter;
|
|
500
|
+
}
|
|
470
501
|
// AuthAdapter-compatible helpers (used by AuthModule)
|
|
471
502
|
async getUser(identifier) {
|
|
472
503
|
return this.userStoreAdapter ? this.userStoreAdapter.findUser(identifier) : null;
|
|
@@ -487,8 +518,9 @@ class ApiServer {
|
|
|
487
518
|
if (this.tokenStoreAdapter) {
|
|
488
519
|
return this.tokenStoreAdapter.save(data);
|
|
489
520
|
}
|
|
490
|
-
|
|
491
|
-
|
|
521
|
+
const storage = this.storageAdapter;
|
|
522
|
+
if (typeof storage.storeToken === 'function') {
|
|
523
|
+
return storage.storeToken(data);
|
|
492
524
|
}
|
|
493
525
|
throw new Error('Token store is not configured');
|
|
494
526
|
}
|
|
@@ -501,8 +533,9 @@ class ApiServer {
|
|
|
501
533
|
if (this.tokenStoreAdapter) {
|
|
502
534
|
return this.tokenStoreAdapter.get(normalized, opts);
|
|
503
535
|
}
|
|
504
|
-
|
|
505
|
-
|
|
536
|
+
const storage = this.storageAdapter;
|
|
537
|
+
if (typeof storage.getToken === 'function') {
|
|
538
|
+
return storage.getToken(normalized, opts);
|
|
506
539
|
}
|
|
507
540
|
return null;
|
|
508
541
|
}
|
|
@@ -515,8 +548,9 @@ class ApiServer {
|
|
|
515
548
|
if (this.tokenStoreAdapter) {
|
|
516
549
|
return this.tokenStoreAdapter.delete(normalized);
|
|
517
550
|
}
|
|
518
|
-
|
|
519
|
-
|
|
551
|
+
const storage = this.storageAdapter;
|
|
552
|
+
if (typeof storage.deleteToken === 'function') {
|
|
553
|
+
return storage.deleteToken(normalized);
|
|
520
554
|
}
|
|
521
555
|
return 0;
|
|
522
556
|
}
|
|
@@ -589,8 +623,9 @@ class ApiServer {
|
|
|
589
623
|
if (this.tokenStoreAdapter) {
|
|
590
624
|
return this.tokenStoreAdapter.update(updates);
|
|
591
625
|
}
|
|
592
|
-
|
|
593
|
-
|
|
626
|
+
const storage = this.storageAdapter;
|
|
627
|
+
if (typeof storage.updateToken === 'function') {
|
|
628
|
+
return storage.updateToken(updates);
|
|
594
629
|
}
|
|
595
630
|
return false;
|
|
596
631
|
}
|
|
@@ -723,7 +758,7 @@ class ApiServer {
|
|
|
723
758
|
if (!this.apiNotFoundHandler) {
|
|
724
759
|
return;
|
|
725
760
|
}
|
|
726
|
-
const stack = this.app
|
|
761
|
+
const stack = this.app._router?.stack;
|
|
727
762
|
if (!stack) {
|
|
728
763
|
return;
|
|
729
764
|
}
|
|
@@ -1216,7 +1251,8 @@ class ApiServer {
|
|
|
1216
1251
|
api(module) {
|
|
1217
1252
|
const router = express_1.default.Router();
|
|
1218
1253
|
module.server = this;
|
|
1219
|
-
|
|
1254
|
+
const moduleType = module.moduleType;
|
|
1255
|
+
if (moduleType === 'auth') {
|
|
1220
1256
|
this.authModule(module);
|
|
1221
1257
|
}
|
|
1222
1258
|
module.checkConfig();
|
|
@@ -1226,7 +1262,20 @@ class ApiServer {
|
|
|
1226
1262
|
module.mountpath = mountPath;
|
|
1227
1263
|
module.defineRoutes().forEach((r) => {
|
|
1228
1264
|
const handler = this.handle_request(r.handler, r.auth);
|
|
1229
|
-
|
|
1265
|
+
switch (r.method) {
|
|
1266
|
+
case 'get':
|
|
1267
|
+
router.get(r.path, handler);
|
|
1268
|
+
break;
|
|
1269
|
+
case 'post':
|
|
1270
|
+
router.post(r.path, handler);
|
|
1271
|
+
break;
|
|
1272
|
+
case 'put':
|
|
1273
|
+
router.put(r.path, handler);
|
|
1274
|
+
break;
|
|
1275
|
+
case 'delete':
|
|
1276
|
+
router.delete(r.path, handler);
|
|
1277
|
+
break;
|
|
1278
|
+
}
|
|
1230
1279
|
if (this.config.debug) {
|
|
1231
1280
|
console.log(`Adding ${mountPath}${r.path} (${r.method.toUpperCase()})`);
|
|
1232
1281
|
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { Application, Request, Response, type ErrorRequestHandler, type RequestHandler } from 'express';
|
|
8
8
|
import { ApiModule } from './api-module.js';
|
|
9
|
-
import { TokenStore, type JwtDecodeResult, type JwtSignResult, type JwtVerifyResult } from './token/base.js';
|
|
9
|
+
import { TokenStore, type JwtDecodeResult, type JwtSignPayload, type JwtSignResult, type JwtVerifyResult } from './token/base.js';
|
|
10
10
|
import type { ApiAuthClass, ApiAuthType, ApiKey } from './api-module.js';
|
|
11
11
|
import type { AuthProviderModule } from './auth-api/module.js';
|
|
12
12
|
import type { AuthAdapter, AuthIdentifier } from './auth-api/types.js';
|
|
@@ -32,7 +32,7 @@ export interface ApiTokenData extends JwtPayload, Partial<Token> {
|
|
|
32
32
|
exp?: number;
|
|
33
33
|
}
|
|
34
34
|
export interface ApiRequest {
|
|
35
|
-
server:
|
|
35
|
+
server: ApiServer;
|
|
36
36
|
req: ExtendedReq;
|
|
37
37
|
res: Response;
|
|
38
38
|
tokenData?: ApiTokenData | null;
|
|
@@ -64,7 +64,7 @@ export interface ClientInfo extends ClientAgentProfile {
|
|
|
64
64
|
ipchain: string[];
|
|
65
65
|
}
|
|
66
66
|
export interface ApiServerAuthStores {
|
|
67
|
-
userStore: UserStore<
|
|
67
|
+
userStore: UserStore<unknown, unknown>;
|
|
68
68
|
tokenStore: TokenStore;
|
|
69
69
|
passkeyService?: PasskeyService;
|
|
70
70
|
oauthStore?: OAuthStore;
|
|
@@ -77,13 +77,13 @@ export { ApiModule } from './api-module.js';
|
|
|
77
77
|
export type { ApiHandler, ApiAuthType, ApiAuthClass, ApiRoute, ApiKey } from './api-module.js';
|
|
78
78
|
export interface ApiErrorParams {
|
|
79
79
|
code?: number;
|
|
80
|
-
message?:
|
|
81
|
-
data?:
|
|
80
|
+
message?: unknown;
|
|
81
|
+
data?: unknown;
|
|
82
82
|
errors?: Record<string, string>;
|
|
83
83
|
}
|
|
84
84
|
export declare class ApiError extends Error {
|
|
85
85
|
code: number;
|
|
86
|
-
data:
|
|
86
|
+
data: unknown;
|
|
87
87
|
errors: Record<string, string>;
|
|
88
88
|
constructor({ code, message, data, errors }: ApiErrorParams);
|
|
89
89
|
}
|
|
@@ -123,6 +123,7 @@ export declare class ApiServer {
|
|
|
123
123
|
private readonly apiBasePath;
|
|
124
124
|
private storageAdapter;
|
|
125
125
|
private moduleAdapter;
|
|
126
|
+
private serverAuthAdapter;
|
|
126
127
|
private apiNotFoundHandler;
|
|
127
128
|
private tokenStoreAdapter;
|
|
128
129
|
private userStoreAdapter;
|
|
@@ -141,8 +142,8 @@ export declare class ApiServer {
|
|
|
141
142
|
* @deprecated Use {@link ApiServer.authModule} instead.
|
|
142
143
|
*/
|
|
143
144
|
useAuthModule<UserRow>(module: AuthProviderModule<UserRow>): this;
|
|
144
|
-
getAuthStorage(): AuthAdapter<
|
|
145
|
-
getAuthModule(): AuthProviderModule<
|
|
145
|
+
getAuthStorage<UserRow = unknown, SafeUser = unknown>(): AuthAdapter<UserRow, SafeUser>;
|
|
146
|
+
getAuthModule<UserRow = unknown>(): AuthProviderModule<UserRow>;
|
|
146
147
|
setTokenStore(store: TokenStore): this;
|
|
147
148
|
getTokenStore(): TokenStore | null;
|
|
148
149
|
private ensureUserStore;
|
|
@@ -151,10 +152,11 @@ export declare class ApiServer {
|
|
|
151
152
|
listUserCredentials(userId: AuthIdentifier): Promise<StoredPasskeyCredential[]>;
|
|
152
153
|
deletePasskeyCredential(credentialId: Buffer | string): Promise<boolean>;
|
|
153
154
|
private ensureOAuthStore;
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
155
|
+
private getServerAuthAdapter;
|
|
156
|
+
getUser(identifier: AuthIdentifier): Promise<unknown | null>;
|
|
157
|
+
getUserPasswordHash(user: unknown): string;
|
|
158
|
+
getUserId(user: unknown): AuthIdentifier;
|
|
159
|
+
filterUser(user: unknown): unknown;
|
|
158
160
|
verifyPassword(password: string, hash: string): Promise<boolean>;
|
|
159
161
|
storeToken(data: Token): Promise<void>;
|
|
160
162
|
getToken(query: Partial<Token> & {
|
|
@@ -177,7 +179,7 @@ export declare class ApiServer {
|
|
|
177
179
|
realUserId: AuthIdentifier;
|
|
178
180
|
effectiveUserId: AuthIdentifier;
|
|
179
181
|
}): Promise<boolean>;
|
|
180
|
-
jwtSign(payload:
|
|
182
|
+
jwtSign(payload: JwtSignPayload, secret: string, expiresInSeconds: number, options?: SignOptions): JwtSignResult;
|
|
181
183
|
jwtVerify<T>(token: string, secret: string, options?: VerifyOptions): JwtVerifyResult<T>;
|
|
182
184
|
jwtDecode<T>(token: string, options?: import('jsonwebtoken').DecodeOptions): JwtDecodeResult<T>;
|
|
183
185
|
getApiKey<T = ApiKey>(token: string): Promise<T | null>;
|
|
@@ -188,7 +190,7 @@ export declare class ApiServer {
|
|
|
188
190
|
updateToken(updates: Partial<Token> & {
|
|
189
191
|
refreshToken: string;
|
|
190
192
|
}): Promise<boolean>;
|
|
191
|
-
guessExceptionText(error:
|
|
193
|
+
guessExceptionText(error: unknown, defMsg?: string): string;
|
|
192
194
|
protected authorize(apiReq: ApiRequest, requiredClass: ApiAuthClass): Promise<void>;
|
|
193
195
|
private middlewares;
|
|
194
196
|
private installPingHandler;
|
|
@@ -220,7 +222,7 @@ export declare class ApiServer {
|
|
|
220
222
|
}): RequestHandler;
|
|
221
223
|
expressErrorHandler(): ErrorRequestHandler;
|
|
222
224
|
private handle_request;
|
|
223
|
-
api<T extends ApiModule<
|
|
225
|
+
api<T extends ApiModule<unknown>>(module: T): this;
|
|
224
226
|
dumpRequest(apiReq: ApiRequest): void;
|
|
225
227
|
private formatDebugValue;
|
|
226
228
|
dumpResponse(apiReq: ApiRequest, payload: unknown, status: number): void;
|
|
@@ -1052,26 +1052,26 @@ class AuthModule extends module_js_1.BaseAuthModule {
|
|
|
1052
1052
|
throw new api_server_base_js_1.ApiError({ code: 401, message: 'Authorization requires user authentication' });
|
|
1053
1053
|
}
|
|
1054
1054
|
hasPasskeyService() {
|
|
1055
|
-
const
|
|
1056
|
-
if (
|
|
1055
|
+
const storageHints = this.storage;
|
|
1056
|
+
if (storageHints.passkeyService || storageHints.passkeyStore) {
|
|
1057
1057
|
return true;
|
|
1058
1058
|
}
|
|
1059
|
-
if (
|
|
1059
|
+
if (storageHints.adapter?.passkeyService || storageHints.adapter?.passkeyStore) {
|
|
1060
1060
|
return true;
|
|
1061
1061
|
}
|
|
1062
|
-
const
|
|
1063
|
-
return !!
|
|
1062
|
+
const serverHints = this.server;
|
|
1063
|
+
return !!serverHints.passkeyServiceAdapter;
|
|
1064
1064
|
}
|
|
1065
1065
|
hasOAuthStore() {
|
|
1066
|
-
const
|
|
1067
|
-
if (
|
|
1066
|
+
const storageHints = this.storage;
|
|
1067
|
+
if (storageHints.oauthStore) {
|
|
1068
1068
|
return true;
|
|
1069
1069
|
}
|
|
1070
|
-
if (
|
|
1070
|
+
if (storageHints.adapter?.oauthStore) {
|
|
1071
1071
|
return true;
|
|
1072
1072
|
}
|
|
1073
|
-
const
|
|
1074
|
-
return !!
|
|
1073
|
+
const serverHints = this.server;
|
|
1074
|
+
return !!serverHints.oauthStoreAdapter;
|
|
1075
1075
|
}
|
|
1076
1076
|
storageImplements(key) {
|
|
1077
1077
|
const candidate = this.storage[key];
|
|
@@ -8,7 +8,7 @@ export interface AuthProviderModule<UserRow = unknown> {
|
|
|
8
8
|
expires?: Date;
|
|
9
9
|
}): Promise<TokenPair>;
|
|
10
10
|
}
|
|
11
|
-
export declare abstract class BaseAuthModule<UserRow = unknown> extends ApiModule
|
|
11
|
+
export declare abstract class BaseAuthModule<UserRow = unknown> extends ApiModule implements AuthProviderModule<UserRow> {
|
|
12
12
|
readonly moduleType: "auth";
|
|
13
13
|
protected constructor(opts: {
|
|
14
14
|
namespace: string;
|
|
@@ -11,13 +11,23 @@ import type { Token } from '../token/types.js';
|
|
|
11
11
|
interface PasskeyOptions extends Partial<PasskeyServiceConfig> {
|
|
12
12
|
enabled?: boolean;
|
|
13
13
|
}
|
|
14
|
+
export interface SqlAuthStoreTablePrefixes {
|
|
15
|
+
user?: string;
|
|
16
|
+
token?: string;
|
|
17
|
+
passkey?: string;
|
|
18
|
+
oauth?: string;
|
|
19
|
+
}
|
|
14
20
|
export interface SqlAuthStoreParams<UserAttributes extends AuthUserAttributes = AuthUserAttributes, PublicUserShape extends Omit<UserAttributes, 'password'> = Omit<UserAttributes, 'password'>> {
|
|
15
21
|
sequelize: Sequelize;
|
|
16
22
|
syncOptions?: SyncOptions;
|
|
17
23
|
bcryptRounds?: number;
|
|
18
24
|
passwordPepper?: string;
|
|
25
|
+
tablePrefix?: string;
|
|
26
|
+
tablePrefixes?: SqlAuthStoreTablePrefixes;
|
|
19
27
|
userModel?: GenericUserModelStatic;
|
|
20
|
-
userModelFactory?: (sequelize: Sequelize
|
|
28
|
+
userModelFactory?: (sequelize: Sequelize, options?: {
|
|
29
|
+
tablePrefix?: string;
|
|
30
|
+
}) => GenericUserModelStatic;
|
|
21
31
|
userRecordMapper?: (model: GenericUserModel) => UserAttributes;
|
|
22
32
|
publicUserMapper?: (user: UserAttributes) => PublicUserShape;
|
|
23
33
|
passkeyUserMapper?: (user: UserAttributes) => PasskeyUserDescriptor;
|
|
@@ -13,6 +13,22 @@ const DEFAULT_PASSKEY_CONFIG = {
|
|
|
13
13
|
timeoutMs: 5 * 60 * 1000,
|
|
14
14
|
userVerification: 'preferred'
|
|
15
15
|
};
|
|
16
|
+
function normalizeTablePrefix(prefix) {
|
|
17
|
+
if (!prefix) {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
const trimmed = prefix.trim();
|
|
21
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
22
|
+
}
|
|
23
|
+
function resolveTablePrefix(...prefixes) {
|
|
24
|
+
for (const prefix of prefixes) {
|
|
25
|
+
const normalized = normalizeTablePrefix(prefix);
|
|
26
|
+
if (normalized) {
|
|
27
|
+
return normalized;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
16
32
|
function isOriginString(origin) {
|
|
17
33
|
return typeof origin === 'string' && origin.trim().length > 0;
|
|
18
34
|
}
|
|
@@ -36,6 +52,8 @@ class SqlAuthStore {
|
|
|
36
52
|
}
|
|
37
53
|
this.sequelize = params.sequelize;
|
|
38
54
|
this.syncOptions = params.syncOptions;
|
|
55
|
+
const moduleTablePrefixes = params.tablePrefixes ?? {};
|
|
56
|
+
const userTablePrefix = resolveTablePrefix(moduleTablePrefixes.user, params.tablePrefix);
|
|
39
57
|
this.userStore = new sequelize_js_4.SequelizeUserStore({
|
|
40
58
|
sequelize: this.sequelize,
|
|
41
59
|
userModel: params.userModel,
|
|
@@ -43,18 +61,28 @@ class SqlAuthStore {
|
|
|
43
61
|
recordMapper: params.userRecordMapper,
|
|
44
62
|
toPublic: params.publicUserMapper,
|
|
45
63
|
bcryptRounds: params.bcryptRounds,
|
|
46
|
-
bcryptPepper: params.passwordPepper
|
|
64
|
+
bcryptPepper: params.passwordPepper,
|
|
65
|
+
tablePrefix: userTablePrefix
|
|
47
66
|
});
|
|
67
|
+
const tokenTablePrefix = resolveTablePrefix(params.tokenStoreOptions?.tablePrefix, moduleTablePrefixes.token, params.tablePrefix);
|
|
48
68
|
this.tokenStore =
|
|
49
|
-
params.tokenStore ??
|
|
69
|
+
params.tokenStore ??
|
|
70
|
+
new sequelize_js_3.SequelizeTokenStore({
|
|
71
|
+
sequelize: this.sequelize,
|
|
72
|
+
...params.tokenStoreOptions,
|
|
73
|
+
tablePrefix: tokenTablePrefix
|
|
74
|
+
});
|
|
75
|
+
const oauthTablePrefix = resolveTablePrefix(params.oauthStoreOptions?.tablePrefix, moduleTablePrefixes.oauth, params.tablePrefix);
|
|
50
76
|
this.oauthStore = new sequelize_js_1.SequelizeOAuthStore({
|
|
51
77
|
sequelize: this.sequelize,
|
|
52
78
|
...params.oauthStoreOptions,
|
|
79
|
+
tablePrefix: oauthTablePrefix,
|
|
53
80
|
bcryptRounds: params.bcryptRounds
|
|
54
81
|
});
|
|
55
82
|
let passkeyStore;
|
|
56
83
|
let passkeyConfig;
|
|
57
84
|
if (params.passkeys !== false) {
|
|
85
|
+
const passkeyTablePrefix = resolveTablePrefix(moduleTablePrefixes.passkey, params.tablePrefix);
|
|
58
86
|
passkeyConfig = normalizePasskeyConfig(params.passkeys ?? {});
|
|
59
87
|
const resolveUser = async (lookup) => {
|
|
60
88
|
const found = await this.userStore.findUser(lookup.userId ?? lookup.login ?? '');
|
|
@@ -69,7 +97,11 @@ class SqlAuthStore {
|
|
|
69
97
|
}));
|
|
70
98
|
return mapper(found);
|
|
71
99
|
};
|
|
72
|
-
passkeyStore = new sequelize_js_2.SequelizePasskeyStore({
|
|
100
|
+
passkeyStore = new sequelize_js_2.SequelizePasskeyStore({
|
|
101
|
+
sequelize: this.sequelize,
|
|
102
|
+
resolveUser,
|
|
103
|
+
tablePrefix: passkeyTablePrefix
|
|
104
|
+
});
|
|
73
105
|
this.passkeyStore = passkeyStore;
|
|
74
106
|
}
|
|
75
107
|
this.adapter = new compat_auth_storage_js_1.CompositeAuthAdapter({
|
|
@@ -18,7 +18,9 @@ export declare class OAuthClientModel extends Model<OAuthClientAttributes, OAuth
|
|
|
18
18
|
metadata: string | null;
|
|
19
19
|
first_party: boolean;
|
|
20
20
|
}
|
|
21
|
-
export declare function initOAuthClientModel(sequelize: Sequelize
|
|
21
|
+
export declare function initOAuthClientModel(sequelize: Sequelize, options?: {
|
|
22
|
+
tablePrefix?: string;
|
|
23
|
+
}): typeof OAuthClientModel;
|
|
22
24
|
export interface OAuthCodeAttributes {
|
|
23
25
|
code: string;
|
|
24
26
|
client_id: string;
|
|
@@ -42,4 +44,6 @@ export declare class OAuthCodeModel extends Model<OAuthCodeAttributes, OAuthCode
|
|
|
42
44
|
expires: Date;
|
|
43
45
|
metadata: string | null;
|
|
44
46
|
}
|
|
45
|
-
export declare function initOAuthCodeModel(sequelize: Sequelize
|
|
47
|
+
export declare function initOAuthCodeModel(sequelize: Sequelize, options?: {
|
|
48
|
+
tablePrefix?: string;
|
|
49
|
+
}): typeof OAuthCodeModel;
|
package/dist/cjs/oauth/models.js
CHANGED
|
@@ -5,11 +5,22 @@ exports.initOAuthClientModel = initOAuthClientModel;
|
|
|
5
5
|
exports.initOAuthCodeModel = initOAuthCodeModel;
|
|
6
6
|
const sequelize_1 = require("sequelize");
|
|
7
7
|
const DIALECTS_SUPPORTING_UNSIGNED = new Set(['mysql', 'mariadb']);
|
|
8
|
+
function normalizeTablePrefix(prefix) {
|
|
9
|
+
if (!prefix) {
|
|
10
|
+
return undefined;
|
|
11
|
+
}
|
|
12
|
+
const trimmed = prefix.trim();
|
|
13
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
14
|
+
}
|
|
15
|
+
function applyTablePrefix(prefix, tableName) {
|
|
16
|
+
const normalized = normalizeTablePrefix(prefix);
|
|
17
|
+
return normalized ? `${normalized}${tableName}` : tableName;
|
|
18
|
+
}
|
|
8
19
|
function integerIdType(sequelize) {
|
|
9
20
|
return DIALECTS_SUPPORTING_UNSIGNED.has(sequelize.getDialect()) ? sequelize_1.DataTypes.INTEGER.UNSIGNED : sequelize_1.DataTypes.INTEGER;
|
|
10
21
|
}
|
|
11
|
-
function tableOptions(sequelize, tableName, extra) {
|
|
12
|
-
const opts = { sequelize, tableName };
|
|
22
|
+
function tableOptions(sequelize, tableName, tablePrefix, extra) {
|
|
23
|
+
const opts = { sequelize, tableName: applyTablePrefix(tablePrefix, tableName) };
|
|
13
24
|
if (extra) {
|
|
14
25
|
Object.assign(opts, extra);
|
|
15
26
|
}
|
|
@@ -22,7 +33,7 @@ function tableOptions(sequelize, tableName, extra) {
|
|
|
22
33
|
class OAuthClientModel extends sequelize_1.Model {
|
|
23
34
|
}
|
|
24
35
|
exports.OAuthClientModel = OAuthClientModel;
|
|
25
|
-
function initOAuthClientModel(sequelize) {
|
|
36
|
+
function initOAuthClientModel(sequelize, options = {}) {
|
|
26
37
|
OAuthClientModel.init({
|
|
27
38
|
client_id: { type: sequelize_1.DataTypes.STRING(128), allowNull: false, primaryKey: true },
|
|
28
39
|
client_secret: { type: sequelize_1.DataTypes.STRING(255), allowNull: false, defaultValue: '' },
|
|
@@ -32,14 +43,14 @@ function initOAuthClientModel(sequelize) {
|
|
|
32
43
|
metadata: { type: sequelize_1.DataTypes.TEXT, allowNull: true, defaultValue: null },
|
|
33
44
|
first_party: { type: sequelize_1.DataTypes.BOOLEAN, allowNull: false, defaultValue: false }
|
|
34
45
|
}, {
|
|
35
|
-
...tableOptions(sequelize, 'oauth_clients', { timestamps: false })
|
|
46
|
+
...tableOptions(sequelize, 'oauth_clients', options.tablePrefix, { timestamps: false })
|
|
36
47
|
});
|
|
37
48
|
return OAuthClientModel;
|
|
38
49
|
}
|
|
39
50
|
class OAuthCodeModel extends sequelize_1.Model {
|
|
40
51
|
}
|
|
41
52
|
exports.OAuthCodeModel = OAuthCodeModel;
|
|
42
|
-
function initOAuthCodeModel(sequelize) {
|
|
53
|
+
function initOAuthCodeModel(sequelize, options = {}) {
|
|
43
54
|
const idType = integerIdType(sequelize);
|
|
44
55
|
OAuthCodeModel.init({
|
|
45
56
|
code: { type: sequelize_1.DataTypes.STRING(128), allowNull: false, primaryKey: true },
|
|
@@ -52,7 +63,7 @@ function initOAuthCodeModel(sequelize) {
|
|
|
52
63
|
expires: { type: sequelize_1.DataTypes.DATE, allowNull: false },
|
|
53
64
|
metadata: { type: sequelize_1.DataTypes.TEXT, allowNull: true, defaultValue: null }
|
|
54
65
|
}, {
|
|
55
|
-
...tableOptions(sequelize, 'oauth_codes', { timestamps: false })
|
|
66
|
+
...tableOptions(sequelize, 'oauth_codes', options.tablePrefix, { timestamps: false })
|
|
56
67
|
});
|
|
57
68
|
return OAuthCodeModel;
|
|
58
69
|
}
|
|
@@ -19,7 +19,9 @@ export declare class OAuthClientModel extends Model<OAuthClientAttributes, OAuth
|
|
|
19
19
|
metadata: string | null;
|
|
20
20
|
first_party: boolean;
|
|
21
21
|
}
|
|
22
|
-
export declare function initOAuthClientModel(sequelize: Sequelize
|
|
22
|
+
export declare function initOAuthClientModel(sequelize: Sequelize, options?: {
|
|
23
|
+
tablePrefix?: string;
|
|
24
|
+
}): typeof OAuthClientModel;
|
|
23
25
|
export interface OAuthCodeAttributes {
|
|
24
26
|
code: string;
|
|
25
27
|
client_id: string;
|
|
@@ -43,13 +45,20 @@ export declare class OAuthCodeModel extends Model<OAuthCodeAttributes, OAuthCode
|
|
|
43
45
|
expires: Date;
|
|
44
46
|
metadata: string | null;
|
|
45
47
|
}
|
|
46
|
-
export declare function initOAuthCodeModel(sequelize: Sequelize
|
|
48
|
+
export declare function initOAuthCodeModel(sequelize: Sequelize, options?: {
|
|
49
|
+
tablePrefix?: string;
|
|
50
|
+
}): typeof OAuthCodeModel;
|
|
47
51
|
export interface SequelizeOAuthStoreOptions {
|
|
48
52
|
sequelize: Sequelize;
|
|
53
|
+
tablePrefix?: string;
|
|
49
54
|
clientModel?: typeof OAuthClientModel;
|
|
50
55
|
codeModel?: typeof OAuthCodeModel;
|
|
51
|
-
clientModelFactory?: (sequelize: Sequelize
|
|
52
|
-
|
|
56
|
+
clientModelFactory?: (sequelize: Sequelize, options?: {
|
|
57
|
+
tablePrefix?: string;
|
|
58
|
+
}) => typeof OAuthClientModel;
|
|
59
|
+
codeModelFactory?: (sequelize: Sequelize, options?: {
|
|
60
|
+
tablePrefix?: string;
|
|
61
|
+
}) => typeof OAuthCodeModel;
|
|
53
62
|
bcryptRounds?: number;
|
|
54
63
|
}
|
|
55
64
|
export declare class SequelizeOAuthStore extends OAuthStore {
|
|
@@ -10,11 +10,22 @@ const bcryptjs_1 = __importDefault(require("bcryptjs"));
|
|
|
10
10
|
const sequelize_1 = require("sequelize");
|
|
11
11
|
const base_js_1 = require("./base.js");
|
|
12
12
|
const DIALECTS_SUPPORTING_UNSIGNED = new Set(['mysql', 'mariadb']);
|
|
13
|
+
function normalizeTablePrefix(prefix) {
|
|
14
|
+
if (!prefix) {
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
const trimmed = prefix.trim();
|
|
18
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
19
|
+
}
|
|
20
|
+
function applyTablePrefix(prefix, tableName) {
|
|
21
|
+
const normalized = normalizeTablePrefix(prefix);
|
|
22
|
+
return normalized ? `${normalized}${tableName}` : tableName;
|
|
23
|
+
}
|
|
13
24
|
function integerIdType(sequelize) {
|
|
14
25
|
return DIALECTS_SUPPORTING_UNSIGNED.has(sequelize.getDialect()) ? sequelize_1.DataTypes.INTEGER.UNSIGNED : sequelize_1.DataTypes.INTEGER;
|
|
15
26
|
}
|
|
16
|
-
function tableOptions(sequelize, tableName, extra) {
|
|
17
|
-
const opts = { sequelize, tableName };
|
|
27
|
+
function tableOptions(sequelize, tableName, tablePrefix, extra) {
|
|
28
|
+
const opts = { sequelize, tableName: applyTablePrefix(tablePrefix, tableName) };
|
|
18
29
|
if (extra) {
|
|
19
30
|
Object.assign(opts, extra);
|
|
20
31
|
}
|
|
@@ -27,7 +38,7 @@ function tableOptions(sequelize, tableName, extra) {
|
|
|
27
38
|
class OAuthClientModel extends sequelize_1.Model {
|
|
28
39
|
}
|
|
29
40
|
exports.OAuthClientModel = OAuthClientModel;
|
|
30
|
-
function initOAuthClientModel(sequelize) {
|
|
41
|
+
function initOAuthClientModel(sequelize, options = {}) {
|
|
31
42
|
OAuthClientModel.init({
|
|
32
43
|
client_id: { type: sequelize_1.DataTypes.STRING(128), allowNull: false, primaryKey: true },
|
|
33
44
|
client_secret: { type: sequelize_1.DataTypes.STRING(255), allowNull: false, defaultValue: '' },
|
|
@@ -36,13 +47,13 @@ function initOAuthClientModel(sequelize) {
|
|
|
36
47
|
scope: { type: sequelize_1.DataTypes.TEXT, allowNull: false, defaultValue: '[]' },
|
|
37
48
|
metadata: { type: sequelize_1.DataTypes.TEXT, allowNull: true, defaultValue: null },
|
|
38
49
|
first_party: { type: sequelize_1.DataTypes.BOOLEAN, allowNull: false, defaultValue: false }
|
|
39
|
-
}, tableOptions(sequelize, 'oauth_clients', { timestamps: false }));
|
|
50
|
+
}, tableOptions(sequelize, 'oauth_clients', options.tablePrefix, { timestamps: false }));
|
|
40
51
|
return OAuthClientModel;
|
|
41
52
|
}
|
|
42
53
|
class OAuthCodeModel extends sequelize_1.Model {
|
|
43
54
|
}
|
|
44
55
|
exports.OAuthCodeModel = OAuthCodeModel;
|
|
45
|
-
function initOAuthCodeModel(sequelize) {
|
|
56
|
+
function initOAuthCodeModel(sequelize, options = {}) {
|
|
46
57
|
const idType = integerIdType(sequelize);
|
|
47
58
|
OAuthCodeModel.init({
|
|
48
59
|
code: { type: sequelize_1.DataTypes.STRING(128), allowNull: false, primaryKey: true },
|
|
@@ -54,7 +65,7 @@ function initOAuthCodeModel(sequelize) {
|
|
|
54
65
|
code_challenge_method: { type: sequelize_1.DataTypes.STRING(10), allowNull: true, defaultValue: null },
|
|
55
66
|
expires: { type: sequelize_1.DataTypes.DATE, allowNull: false },
|
|
56
67
|
metadata: { type: sequelize_1.DataTypes.TEXT, allowNull: true, defaultValue: null }
|
|
57
|
-
}, tableOptions(sequelize, 'oauth_codes', { timestamps: false }));
|
|
68
|
+
}, tableOptions(sequelize, 'oauth_codes', options.tablePrefix, { timestamps: false }));
|
|
58
69
|
return OAuthCodeModel;
|
|
59
70
|
}
|
|
60
71
|
function encodeStringArray(values) {
|
|
@@ -114,8 +125,16 @@ class SequelizeOAuthStore extends base_js_1.OAuthStore {
|
|
|
114
125
|
if (!options?.sequelize) {
|
|
115
126
|
throw new Error('SequelizeOAuthStore requires an initialised Sequelize instance');
|
|
116
127
|
}
|
|
117
|
-
this.clients =
|
|
118
|
-
|
|
128
|
+
this.clients =
|
|
129
|
+
options.clientModel ??
|
|
130
|
+
(options.clientModelFactory ?? initOAuthClientModel)(options.sequelize, {
|
|
131
|
+
tablePrefix: options.tablePrefix
|
|
132
|
+
});
|
|
133
|
+
this.codes =
|
|
134
|
+
options.codeModel ??
|
|
135
|
+
(options.codeModelFactory ?? initOAuthCodeModel)(options.sequelize, {
|
|
136
|
+
tablePrefix: options.tablePrefix
|
|
137
|
+
});
|
|
119
138
|
this.bcryptRounds = options.bcryptRounds ?? 12;
|
|
120
139
|
}
|
|
121
140
|
async getClient(clientId) {
|