@technomoron/api-server-base 1.0.43 → 1.1.0
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 +8 -3
- package/dist/cjs/api-server-base.cjs +68 -20
- package/dist/cjs/api-server-base.d.ts +29 -24
- package/dist/cjs/auth-module.cjs +26 -0
- package/dist/cjs/auth-module.d.ts +17 -0
- package/dist/cjs/auth-storage.cjs +87 -0
- package/dist/cjs/auth-storage.d.ts +114 -0
- package/dist/cjs/index.cjs +7 -1
- package/dist/cjs/index.d.ts +4 -0
- package/dist/esm/api-server-base.d.ts +29 -24
- package/dist/esm/api-server-base.js +68 -20
- package/dist/esm/auth-module.d.ts +17 -0
- package/dist/esm/auth-module.js +22 -0
- package/dist/esm/auth-storage.d.ts +114 -0
- package/dist/esm/auth-storage.js +83 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.js +2 -0
- package/package.json +1 -1
package/README.txt
CHANGED
|
@@ -62,13 +62,18 @@ class UserModule extends ApiModule<AppServer> {
|
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
const yourStorageAdapter = new YourStorageAdapter();
|
|
66
|
+
|
|
65
67
|
const server = new AppServer({
|
|
66
68
|
apiPort: 3101,
|
|
67
69
|
apiHost: '127.0.0.1',
|
|
68
|
-
accessSecret: 'replace-me'
|
|
69
|
-
})
|
|
70
|
+
accessSecret: 'replace-me'
|
|
71
|
+
})
|
|
72
|
+
.authStorage(yourStorageAdapter)
|
|
73
|
+
.api(new UserModule())
|
|
74
|
+
.start();
|
|
70
75
|
|
|
71
|
-
|
|
76
|
+
Need a dedicated auth module as well? Chain `.authModule(...)` in the same spot.
|
|
72
77
|
|
|
73
78
|
Handlers must return a tuple: [statusCode], [statusCode, data], or [statusCode, data, message]. Throw ApiError for predictable failures.
|
|
74
79
|
|
|
@@ -15,6 +15,8 @@ const cors_1 = __importDefault(require("cors"));
|
|
|
15
15
|
const express_1 = __importDefault(require("express"));
|
|
16
16
|
const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
|
|
17
17
|
const multer_1 = __importDefault(require("multer"));
|
|
18
|
+
const auth_module_js_1 = require("./auth-module.cjs");
|
|
19
|
+
const auth_storage_js_1 = require("./auth-storage.cjs");
|
|
18
20
|
class ApiModule {
|
|
19
21
|
constructor(opts = {}) {
|
|
20
22
|
this.mountpath = '';
|
|
@@ -168,6 +170,8 @@ class ApiServer {
|
|
|
168
170
|
constructor(config = {}) {
|
|
169
171
|
this.currReq = null;
|
|
170
172
|
this.config = fillConfig(config);
|
|
173
|
+
this.storageAdapter = auth_storage_js_1.nullAuthStorage;
|
|
174
|
+
this.moduleAdapter = auth_module_js_1.nullAuthModule;
|
|
171
175
|
this.app = (0, express_1.default)();
|
|
172
176
|
if (config.uploadPath) {
|
|
173
177
|
const upload = (0, multer_1.default)({ dest: config.uploadPath });
|
|
@@ -176,6 +180,32 @@ class ApiServer {
|
|
|
176
180
|
this.middlewares();
|
|
177
181
|
// addSwaggerUi(this.app);
|
|
178
182
|
}
|
|
183
|
+
authStorage(storage) {
|
|
184
|
+
this.storageAdapter = storage;
|
|
185
|
+
return this;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* @deprecated Use {@link ApiServer.authStorage} instead.
|
|
189
|
+
*/
|
|
190
|
+
useAuthStorage(storage) {
|
|
191
|
+
return this.authStorage(storage);
|
|
192
|
+
}
|
|
193
|
+
authModule(module) {
|
|
194
|
+
this.moduleAdapter = module;
|
|
195
|
+
return this;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* @deprecated Use {@link ApiServer.authModule} instead.
|
|
199
|
+
*/
|
|
200
|
+
useAuthModule(module) {
|
|
201
|
+
return this.authModule(module);
|
|
202
|
+
}
|
|
203
|
+
getAuthStorage() {
|
|
204
|
+
return this.storageAdapter;
|
|
205
|
+
}
|
|
206
|
+
getAuthModule() {
|
|
207
|
+
return this.moduleAdapter;
|
|
208
|
+
}
|
|
179
209
|
jwtSign(payload, secret, expiresInSeconds, options) {
|
|
180
210
|
options || (options = {});
|
|
181
211
|
const opts = { ...options, expiresIn: expiresInSeconds };
|
|
@@ -248,36 +278,51 @@ class ApiServer {
|
|
|
248
278
|
return null;
|
|
249
279
|
}
|
|
250
280
|
async getUser(uid) {
|
|
251
|
-
|
|
252
|
-
|
|
281
|
+
return this.storageAdapter.getUser(uid);
|
|
282
|
+
}
|
|
283
|
+
getUserPasswordHash(user) {
|
|
284
|
+
return this.storageAdapter.getUserPasswordHash(user);
|
|
285
|
+
}
|
|
286
|
+
getUserId(user) {
|
|
287
|
+
return this.storageAdapter.getUserId(user);
|
|
253
288
|
}
|
|
254
289
|
async authenticateUser(params) {
|
|
255
|
-
|
|
256
|
-
|
|
290
|
+
if (!params?.login || !params?.password) {
|
|
291
|
+
return false;
|
|
292
|
+
}
|
|
293
|
+
const user = await this.getUser(params.login);
|
|
294
|
+
if (!user) {
|
|
295
|
+
return false;
|
|
296
|
+
}
|
|
297
|
+
const hash = this.storageAdapter.getUserPasswordHash(user);
|
|
298
|
+
return this.verifyPassword(params.password, hash);
|
|
257
299
|
}
|
|
258
|
-
async storeToken(
|
|
259
|
-
|
|
260
|
-
throw new Error('storeToken() not implemented');
|
|
300
|
+
async storeToken(data) {
|
|
301
|
+
await this.storageAdapter.storeToken(data);
|
|
261
302
|
}
|
|
262
|
-
async getToken(
|
|
263
|
-
|
|
264
|
-
throw new Error('getToken() not implemented');
|
|
303
|
+
async getToken(query) {
|
|
304
|
+
return this.storageAdapter.getToken(query);
|
|
265
305
|
}
|
|
266
|
-
async updateToken(
|
|
267
|
-
|
|
268
|
-
|
|
306
|
+
async updateToken(updates) {
|
|
307
|
+
if (typeof this.storageAdapter.updateToken !== 'function') {
|
|
308
|
+
return false;
|
|
309
|
+
}
|
|
310
|
+
return this.storageAdapter.updateToken({
|
|
311
|
+
refreshToken: updates.refreshToken,
|
|
312
|
+
access: updates.accessToken,
|
|
313
|
+
expires: updates.expires,
|
|
314
|
+
clientId: updates.clientId,
|
|
315
|
+
scope: updates.scope
|
|
316
|
+
});
|
|
269
317
|
}
|
|
270
|
-
async deleteToken(
|
|
271
|
-
|
|
272
|
-
throw new Error('deleteToken() not implemented');
|
|
318
|
+
async deleteToken(query) {
|
|
319
|
+
return this.storageAdapter.deleteToken(query);
|
|
273
320
|
}
|
|
274
321
|
async verifyPassword(password, hash) {
|
|
275
|
-
|
|
276
|
-
void hash;
|
|
277
|
-
throw new Error('verifyPassword() not implemented');
|
|
322
|
+
return this.storageAdapter.verifyPassword(password, hash);
|
|
278
323
|
}
|
|
279
324
|
filterUser(fullUser) {
|
|
280
|
-
return fullUser;
|
|
325
|
+
return this.storageAdapter.filterUser(fullUser);
|
|
281
326
|
}
|
|
282
327
|
guessExceptionText(error, defMsg = 'Unkown Error') {
|
|
283
328
|
return guess_exception_text(error, defMsg);
|
|
@@ -495,6 +540,9 @@ class ApiServer {
|
|
|
495
540
|
api(module) {
|
|
496
541
|
const router = express_1.default.Router();
|
|
497
542
|
module.server = this;
|
|
543
|
+
if (module?.moduleType === 'auth') {
|
|
544
|
+
this.authModule(module);
|
|
545
|
+
}
|
|
498
546
|
module.checkConfig();
|
|
499
547
|
const base = this.config.apiBasePath ?? '/api';
|
|
500
548
|
const ns = module.namespace;
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { Application, Request, Response } from 'express';
|
|
8
8
|
import jwt, { JwtPayload, SignOptions, VerifyOptions } from 'jsonwebtoken';
|
|
9
|
+
import type { AuthProviderModule } from './auth-module.js';
|
|
10
|
+
import type { AuthIdentifier, AuthStorage, AuthTokenData, AuthTokenQuery } from './auth-storage.js';
|
|
9
11
|
export type { Application, Request, Response, NextFunction, Router } from 'express';
|
|
10
12
|
export type { Multer } from 'multer';
|
|
11
13
|
export type { JwtPayload, SignOptions, VerifyOptions } from 'jsonwebtoken';
|
|
@@ -104,39 +106,42 @@ export declare class ApiServer {
|
|
|
104
106
|
app: Application;
|
|
105
107
|
currReq: ApiRequest | null;
|
|
106
108
|
readonly config: ApiServerConf;
|
|
109
|
+
private storageAdapter;
|
|
110
|
+
private moduleAdapter;
|
|
107
111
|
constructor(config?: Partial<ApiServerConf>);
|
|
112
|
+
authStorage<UserRow, SafeUser>(storage: AuthStorage<UserRow, SafeUser>): this;
|
|
113
|
+
/**
|
|
114
|
+
* @deprecated Use {@link ApiServer.authStorage} instead.
|
|
115
|
+
*/
|
|
116
|
+
useAuthStorage<UserRow, SafeUser>(storage: AuthStorage<UserRow, SafeUser>): this;
|
|
117
|
+
authModule<UserRow, SafeUser>(module: AuthProviderModule<UserRow, SafeUser>): this;
|
|
118
|
+
/**
|
|
119
|
+
* @deprecated Use {@link ApiServer.authModule} instead.
|
|
120
|
+
*/
|
|
121
|
+
useAuthModule<UserRow, SafeUser>(module: AuthProviderModule<UserRow, SafeUser>): this;
|
|
122
|
+
getAuthStorage(): AuthStorage<any, any>;
|
|
123
|
+
getAuthModule(): AuthProviderModule<any, any>;
|
|
108
124
|
jwtSign(payload: any, secret: string, expiresInSeconds: number, options?: SignOptions): JwtSignResult;
|
|
109
125
|
jwtVerify<T>(token: string, secret: string, options?: VerifyOptions): JwtVerifyResult<T>;
|
|
110
126
|
jwtDecode<T>(token: string, options?: jwt.DecodeOptions): JwtDecodeResult<T>;
|
|
111
127
|
getApiKey<T = ApiKey>(token: string): Promise<T | null>;
|
|
112
|
-
getUser(uid:
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
getToken(params: {
|
|
123
|
-
accessToken?: string;
|
|
124
|
-
refreshToken?: string;
|
|
125
|
-
userId?: unknown;
|
|
126
|
-
}): Promise<unknown>;
|
|
127
|
-
updateToken(params: {
|
|
128
|
+
getUser(uid: AuthIdentifier): Promise<unknown>;
|
|
129
|
+
getUserPasswordHash(user: unknown): string;
|
|
130
|
+
getUserId(user: unknown): AuthIdentifier;
|
|
131
|
+
authenticateUser(params: {
|
|
132
|
+
login: string;
|
|
133
|
+
password: string;
|
|
134
|
+
}): Promise<boolean>;
|
|
135
|
+
storeToken(data: AuthTokenData): Promise<void>;
|
|
136
|
+
getToken(query: AuthTokenQuery): Promise<AuthTokenData | null>;
|
|
137
|
+
updateToken(updates: {
|
|
128
138
|
accessToken: string;
|
|
129
139
|
refreshToken: string;
|
|
130
140
|
expires?: Date;
|
|
141
|
+
clientId?: string;
|
|
142
|
+
scope?: string[];
|
|
131
143
|
}): Promise<boolean>;
|
|
132
|
-
deleteToken(
|
|
133
|
-
refreshToken?: string;
|
|
134
|
-
accessToken?: string;
|
|
135
|
-
userId?: unknown;
|
|
136
|
-
domain?: string;
|
|
137
|
-
fingerprint?: string;
|
|
138
|
-
label?: string;
|
|
139
|
-
}): Promise<number>;
|
|
144
|
+
deleteToken(query: AuthTokenQuery): Promise<number>;
|
|
140
145
|
verifyPassword(password: string, hash: string): Promise<boolean>;
|
|
141
146
|
filterUser<T = any, U = any>(fullUser: T): U;
|
|
142
147
|
guessExceptionText(error: any, defMsg?: string): string;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.nullAuthModule = exports.BaseAuthModule = void 0;
|
|
4
|
+
// Handy base that you can extend when wiring a real auth module. Subclasses
|
|
5
|
+
// must provide their namespace. Methods throw by default so unimplemented
|
|
6
|
+
// hooks fail loudly.
|
|
7
|
+
class BaseAuthModule {
|
|
8
|
+
constructor() {
|
|
9
|
+
this.moduleType = 'auth';
|
|
10
|
+
}
|
|
11
|
+
// Override to mint tokens for the provided user and request context
|
|
12
|
+
async issueTokens(apiReq, user, metadata) {
|
|
13
|
+
void apiReq;
|
|
14
|
+
void user;
|
|
15
|
+
void metadata;
|
|
16
|
+
throw new Error('Auth module not configured');
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
exports.BaseAuthModule = BaseAuthModule;
|
|
20
|
+
class NullAuthModule extends BaseAuthModule {
|
|
21
|
+
constructor() {
|
|
22
|
+
super(...arguments);
|
|
23
|
+
this.namespace = '__null__';
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
exports.nullAuthModule = new NullAuthModule();
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ApiRequest } from './api-server-base.js';
|
|
2
|
+
import type { AuthTokenMetadata, AuthTokenPair } from './auth-storage.js';
|
|
3
|
+
export interface AuthProviderModule<UserRow, SafeUser> {
|
|
4
|
+
readonly moduleType: 'auth';
|
|
5
|
+
readonly namespace: string;
|
|
6
|
+
issueTokens(apiReq: ApiRequest, user: UserRow, metadata?: AuthTokenMetadata & {
|
|
7
|
+
expires?: Date;
|
|
8
|
+
}): Promise<AuthTokenPair>;
|
|
9
|
+
}
|
|
10
|
+
export declare abstract class BaseAuthModule implements AuthProviderModule<unknown, unknown> {
|
|
11
|
+
readonly moduleType: "auth";
|
|
12
|
+
abstract readonly namespace: string;
|
|
13
|
+
issueTokens(apiReq: ApiRequest, user: unknown, metadata?: AuthTokenMetadata & {
|
|
14
|
+
expires?: Date;
|
|
15
|
+
}): Promise<AuthTokenPair>;
|
|
16
|
+
}
|
|
17
|
+
export declare const nullAuthModule: AuthProviderModule<unknown, unknown>;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Numeric database id or lookup string such as username/email.
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.nullAuthStorage = exports.BaseAuthStorage = void 0;
|
|
5
|
+
// Handy base you can extend when wiring a real storage adapter. Every method
|
|
6
|
+
// throws by default so unimplemented hooks fail loudly.
|
|
7
|
+
class BaseAuthStorage {
|
|
8
|
+
// Override to load a user record by identifier
|
|
9
|
+
async getUser(identifier) {
|
|
10
|
+
void identifier;
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
// Override to return the stored password hash for the user
|
|
14
|
+
getUserPasswordHash(user) {
|
|
15
|
+
void user;
|
|
16
|
+
throw new Error('Auth storage not configured');
|
|
17
|
+
}
|
|
18
|
+
// Override to expose the canonical user identifier
|
|
19
|
+
getUserId(user) {
|
|
20
|
+
void user;
|
|
21
|
+
throw new Error('Auth storage not configured');
|
|
22
|
+
}
|
|
23
|
+
// Override to strip sensitive fields from the user record
|
|
24
|
+
filterUser(user) {
|
|
25
|
+
return user;
|
|
26
|
+
}
|
|
27
|
+
// Override to validate a raw password against the stored hash
|
|
28
|
+
async verifyPassword(password, hash) {
|
|
29
|
+
void password;
|
|
30
|
+
void hash;
|
|
31
|
+
throw new Error('Auth storage not configured');
|
|
32
|
+
}
|
|
33
|
+
// Override to persist newly issued tokens
|
|
34
|
+
async storeToken(data) {
|
|
35
|
+
void data;
|
|
36
|
+
throw new Error('Auth storage not configured');
|
|
37
|
+
}
|
|
38
|
+
// Override to look up a stored token by query
|
|
39
|
+
async getToken(query) {
|
|
40
|
+
void query;
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
// Override to remove stored tokens that match the query
|
|
44
|
+
async deleteToken(query) {
|
|
45
|
+
void query;
|
|
46
|
+
return 0;
|
|
47
|
+
}
|
|
48
|
+
// Override to update metadata for an existing refresh token
|
|
49
|
+
async updateToken(updates) {
|
|
50
|
+
void updates;
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
// Override to create a new passkey challenge record
|
|
54
|
+
async createPasskeyChallenge(params) {
|
|
55
|
+
void params;
|
|
56
|
+
throw new Error('Auth storage not configured');
|
|
57
|
+
}
|
|
58
|
+
// Override to verify an incoming WebAuthn response
|
|
59
|
+
async verifyPasskeyResponse(params) {
|
|
60
|
+
void params;
|
|
61
|
+
throw new Error('Auth storage not configured');
|
|
62
|
+
}
|
|
63
|
+
// Override to fetch an OAuth client by identifier
|
|
64
|
+
async getClient(clientId) {
|
|
65
|
+
void clientId;
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
// Override to compare a provided client secret against storage
|
|
69
|
+
async verifyClientSecret(client, clientSecret) {
|
|
70
|
+
void client;
|
|
71
|
+
void clientSecret;
|
|
72
|
+
throw new Error('Auth storage not configured');
|
|
73
|
+
}
|
|
74
|
+
// Override to create a new authorization code entry
|
|
75
|
+
async createAuthCode(request) {
|
|
76
|
+
void request;
|
|
77
|
+
throw new Error('Auth storage not configured');
|
|
78
|
+
}
|
|
79
|
+
// Override to consume and invalidate an authorization code
|
|
80
|
+
async consumeAuthCode(code, clientId) {
|
|
81
|
+
void code;
|
|
82
|
+
void clientId;
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
exports.BaseAuthStorage = BaseAuthStorage;
|
|
87
|
+
exports.nullAuthStorage = new BaseAuthStorage();
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
export type AuthIdentifier = string | number;
|
|
2
|
+
export interface AuthTokenMetadata {
|
|
3
|
+
clientId?: string;
|
|
4
|
+
domain?: string;
|
|
5
|
+
fingerprint?: string;
|
|
6
|
+
label?: string;
|
|
7
|
+
scope?: string | string[];
|
|
8
|
+
}
|
|
9
|
+
export interface AuthTokenData extends AuthTokenMetadata {
|
|
10
|
+
access: string;
|
|
11
|
+
expires?: Date;
|
|
12
|
+
refresh: string;
|
|
13
|
+
userId: AuthIdentifier;
|
|
14
|
+
}
|
|
15
|
+
export interface AuthTokenQuery extends AuthTokenMetadata {
|
|
16
|
+
accessToken?: string;
|
|
17
|
+
refreshToken?: string;
|
|
18
|
+
userId?: AuthIdentifier;
|
|
19
|
+
}
|
|
20
|
+
export interface AuthTokenPair {
|
|
21
|
+
accessToken: string;
|
|
22
|
+
refreshToken: string;
|
|
23
|
+
}
|
|
24
|
+
export interface AuthTokenPayload extends AuthTokenMetadata {
|
|
25
|
+
exp?: number;
|
|
26
|
+
iat?: number;
|
|
27
|
+
uid: AuthIdentifier;
|
|
28
|
+
}
|
|
29
|
+
export interface PasskeyChallengeParams extends AuthTokenMetadata {
|
|
30
|
+
action: 'register' | 'authenticate';
|
|
31
|
+
login?: string;
|
|
32
|
+
userAgent?: string;
|
|
33
|
+
userId?: AuthIdentifier;
|
|
34
|
+
}
|
|
35
|
+
export interface PasskeyChallenge extends Record<string, unknown> {
|
|
36
|
+
challenge: string;
|
|
37
|
+
expiresAt?: string | number | Date;
|
|
38
|
+
userId?: AuthIdentifier;
|
|
39
|
+
}
|
|
40
|
+
export interface PasskeyVerificationParams extends AuthTokenMetadata {
|
|
41
|
+
expectedChallenge: string;
|
|
42
|
+
login?: string;
|
|
43
|
+
response: Record<string, unknown>;
|
|
44
|
+
userId?: AuthIdentifier;
|
|
45
|
+
}
|
|
46
|
+
export interface PasskeyVerificationResult extends Record<string, unknown> {
|
|
47
|
+
login?: string;
|
|
48
|
+
tokens?: AuthTokenPair;
|
|
49
|
+
userId?: AuthIdentifier;
|
|
50
|
+
verified: boolean;
|
|
51
|
+
}
|
|
52
|
+
export interface OAuthClient {
|
|
53
|
+
clientId: string;
|
|
54
|
+
clientSecret: string;
|
|
55
|
+
firstParty?: boolean;
|
|
56
|
+
metadata?: Record<string, unknown>;
|
|
57
|
+
name?: string;
|
|
58
|
+
redirectUris: string[];
|
|
59
|
+
scope?: string[];
|
|
60
|
+
}
|
|
61
|
+
export interface AuthCodeData {
|
|
62
|
+
code: string;
|
|
63
|
+
clientId: string;
|
|
64
|
+
codeChallenge?: string;
|
|
65
|
+
codeChallengeMethod?: 'plain' | 'S256';
|
|
66
|
+
expiresAt: Date;
|
|
67
|
+
metadata?: Record<string, unknown>;
|
|
68
|
+
redirectUri: string;
|
|
69
|
+
scope: string[];
|
|
70
|
+
userId: AuthIdentifier;
|
|
71
|
+
}
|
|
72
|
+
export type AuthCodeRequest = Omit<AuthCodeData, 'code' | 'expiresAt'> & {
|
|
73
|
+
code?: string;
|
|
74
|
+
expiresInSeconds?: number;
|
|
75
|
+
};
|
|
76
|
+
export interface AuthStorage<UserRow, SafeUser> {
|
|
77
|
+
getUser(identifier: AuthIdentifier): Promise<UserRow | null>;
|
|
78
|
+
getUserPasswordHash(user: UserRow): string;
|
|
79
|
+
getUserId(user: UserRow): AuthIdentifier;
|
|
80
|
+
filterUser(user: UserRow): SafeUser;
|
|
81
|
+
verifyPassword(password: string, hash: string): Promise<boolean>;
|
|
82
|
+
storeToken(data: AuthTokenData): Promise<void>;
|
|
83
|
+
getToken(query: AuthTokenQuery): Promise<AuthTokenData | null>;
|
|
84
|
+
deleteToken(query: AuthTokenQuery): Promise<number>;
|
|
85
|
+
updateToken?(updates: Partial<AuthTokenData> & {
|
|
86
|
+
refreshToken: string;
|
|
87
|
+
}): Promise<boolean>;
|
|
88
|
+
createPasskeyChallenge?(params: PasskeyChallengeParams): Promise<PasskeyChallenge>;
|
|
89
|
+
verifyPasskeyResponse?(params: PasskeyVerificationParams): Promise<PasskeyVerificationResult>;
|
|
90
|
+
getClient?(clientId: string): Promise<OAuthClient | null>;
|
|
91
|
+
verifyClientSecret?(client: OAuthClient, clientSecret: string | null): Promise<boolean>;
|
|
92
|
+
createAuthCode?(request: AuthCodeRequest): Promise<AuthCodeData>;
|
|
93
|
+
consumeAuthCode?(code: string, clientId: string): Promise<AuthCodeData | null>;
|
|
94
|
+
}
|
|
95
|
+
export declare class BaseAuthStorage<UserRow = unknown, SafeUser = unknown> implements AuthStorage<UserRow, SafeUser> {
|
|
96
|
+
getUser(identifier: AuthIdentifier): Promise<UserRow | null>;
|
|
97
|
+
getUserPasswordHash(user: UserRow): string;
|
|
98
|
+
getUserId(user: UserRow): AuthIdentifier;
|
|
99
|
+
filterUser(user: UserRow): SafeUser;
|
|
100
|
+
verifyPassword(password: string, hash: string): Promise<boolean>;
|
|
101
|
+
storeToken(data: AuthTokenData): Promise<void>;
|
|
102
|
+
getToken(query: AuthTokenQuery): Promise<AuthTokenData | null>;
|
|
103
|
+
deleteToken(query: AuthTokenQuery): Promise<number>;
|
|
104
|
+
updateToken(updates: Partial<AuthTokenData> & {
|
|
105
|
+
refreshToken: string;
|
|
106
|
+
}): Promise<boolean>;
|
|
107
|
+
createPasskeyChallenge(params: PasskeyChallengeParams): Promise<PasskeyChallenge>;
|
|
108
|
+
verifyPasskeyResponse(params: PasskeyVerificationParams): Promise<PasskeyVerificationResult>;
|
|
109
|
+
getClient(clientId: string): Promise<OAuthClient | null>;
|
|
110
|
+
verifyClientSecret(client: OAuthClient, clientSecret: string | null): Promise<boolean>;
|
|
111
|
+
createAuthCode(request: AuthCodeRequest): Promise<AuthCodeData>;
|
|
112
|
+
consumeAuthCode(code: string, clientId: string): Promise<AuthCodeData | null>;
|
|
113
|
+
}
|
|
114
|
+
export declare const nullAuthStorage: AuthStorage<unknown, unknown>;
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -3,9 +3,15 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.ApiError = exports.ApiModule = exports.ApiServer = void 0;
|
|
6
|
+
exports.BaseAuthModule = exports.nullAuthModule = exports.BaseAuthStorage = exports.nullAuthStorage = exports.ApiError = exports.ApiModule = exports.ApiServer = void 0;
|
|
7
7
|
var api_server_base_js_1 = require("./api-server-base.cjs");
|
|
8
8
|
Object.defineProperty(exports, "ApiServer", { enumerable: true, get: function () { return __importDefault(api_server_base_js_1).default; } });
|
|
9
9
|
var api_server_base_js_2 = require("./api-server-base.cjs");
|
|
10
10
|
Object.defineProperty(exports, "ApiModule", { enumerable: true, get: function () { return api_server_base_js_2.ApiModule; } });
|
|
11
11
|
Object.defineProperty(exports, "ApiError", { enumerable: true, get: function () { return api_server_base_js_2.ApiError; } });
|
|
12
|
+
var auth_storage_js_1 = require("./auth-storage.cjs");
|
|
13
|
+
Object.defineProperty(exports, "nullAuthStorage", { enumerable: true, get: function () { return auth_storage_js_1.nullAuthStorage; } });
|
|
14
|
+
Object.defineProperty(exports, "BaseAuthStorage", { enumerable: true, get: function () { return auth_storage_js_1.BaseAuthStorage; } });
|
|
15
|
+
var auth_module_js_1 = require("./auth-module.cjs");
|
|
16
|
+
Object.defineProperty(exports, "nullAuthModule", { enumerable: true, get: function () { return auth_module_js_1.nullAuthModule; } });
|
|
17
|
+
Object.defineProperty(exports, "BaseAuthModule", { enumerable: true, get: function () { return auth_module_js_1.BaseAuthModule; } });
|
package/dist/cjs/index.d.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
1
|
export { default as ApiServer } from './api-server-base.js';
|
|
2
2
|
export { ApiModule, ApiError } from './api-server-base.js';
|
|
3
3
|
export type { ApiErrorParams, ApiHandler, ApiKey, ApiServerConf, ApiRequest, ApiRoute, ApiAuthType, ApiAuthClass, ApiTokenData, RequestWithStuff } from './api-server-base.js';
|
|
4
|
+
export type { AuthIdentifier, AuthTokenMetadata, AuthTokenData, AuthTokenQuery, AuthTokenPair, AuthTokenPayload, PasskeyChallengeParams, PasskeyChallenge, PasskeyVerificationParams, PasskeyVerificationResult, OAuthClient, AuthCodeData, AuthCodeRequest, AuthStorage } from './auth-storage.js';
|
|
5
|
+
export type { AuthProviderModule } from './auth-module.js';
|
|
6
|
+
export { nullAuthStorage, BaseAuthStorage } from './auth-storage.js';
|
|
7
|
+
export { nullAuthModule, BaseAuthModule } from './auth-module.js';
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { Application, Request, Response } from 'express';
|
|
8
8
|
import jwt, { JwtPayload, SignOptions, VerifyOptions } from 'jsonwebtoken';
|
|
9
|
+
import type { AuthProviderModule } from './auth-module.js';
|
|
10
|
+
import type { AuthIdentifier, AuthStorage, AuthTokenData, AuthTokenQuery } from './auth-storage.js';
|
|
9
11
|
export type { Application, Request, Response, NextFunction, Router } from 'express';
|
|
10
12
|
export type { Multer } from 'multer';
|
|
11
13
|
export type { JwtPayload, SignOptions, VerifyOptions } from 'jsonwebtoken';
|
|
@@ -104,39 +106,42 @@ export declare class ApiServer {
|
|
|
104
106
|
app: Application;
|
|
105
107
|
currReq: ApiRequest | null;
|
|
106
108
|
readonly config: ApiServerConf;
|
|
109
|
+
private storageAdapter;
|
|
110
|
+
private moduleAdapter;
|
|
107
111
|
constructor(config?: Partial<ApiServerConf>);
|
|
112
|
+
authStorage<UserRow, SafeUser>(storage: AuthStorage<UserRow, SafeUser>): this;
|
|
113
|
+
/**
|
|
114
|
+
* @deprecated Use {@link ApiServer.authStorage} instead.
|
|
115
|
+
*/
|
|
116
|
+
useAuthStorage<UserRow, SafeUser>(storage: AuthStorage<UserRow, SafeUser>): this;
|
|
117
|
+
authModule<UserRow, SafeUser>(module: AuthProviderModule<UserRow, SafeUser>): this;
|
|
118
|
+
/**
|
|
119
|
+
* @deprecated Use {@link ApiServer.authModule} instead.
|
|
120
|
+
*/
|
|
121
|
+
useAuthModule<UserRow, SafeUser>(module: AuthProviderModule<UserRow, SafeUser>): this;
|
|
122
|
+
getAuthStorage(): AuthStorage<any, any>;
|
|
123
|
+
getAuthModule(): AuthProviderModule<any, any>;
|
|
108
124
|
jwtSign(payload: any, secret: string, expiresInSeconds: number, options?: SignOptions): JwtSignResult;
|
|
109
125
|
jwtVerify<T>(token: string, secret: string, options?: VerifyOptions): JwtVerifyResult<T>;
|
|
110
126
|
jwtDecode<T>(token: string, options?: jwt.DecodeOptions): JwtDecodeResult<T>;
|
|
111
127
|
getApiKey<T = ApiKey>(token: string): Promise<T | null>;
|
|
112
|
-
getUser(uid:
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
getToken(params: {
|
|
123
|
-
accessToken?: string;
|
|
124
|
-
refreshToken?: string;
|
|
125
|
-
userId?: unknown;
|
|
126
|
-
}): Promise<unknown>;
|
|
127
|
-
updateToken(params: {
|
|
128
|
+
getUser(uid: AuthIdentifier): Promise<unknown>;
|
|
129
|
+
getUserPasswordHash(user: unknown): string;
|
|
130
|
+
getUserId(user: unknown): AuthIdentifier;
|
|
131
|
+
authenticateUser(params: {
|
|
132
|
+
login: string;
|
|
133
|
+
password: string;
|
|
134
|
+
}): Promise<boolean>;
|
|
135
|
+
storeToken(data: AuthTokenData): Promise<void>;
|
|
136
|
+
getToken(query: AuthTokenQuery): Promise<AuthTokenData | null>;
|
|
137
|
+
updateToken(updates: {
|
|
128
138
|
accessToken: string;
|
|
129
139
|
refreshToken: string;
|
|
130
140
|
expires?: Date;
|
|
141
|
+
clientId?: string;
|
|
142
|
+
scope?: string[];
|
|
131
143
|
}): Promise<boolean>;
|
|
132
|
-
deleteToken(
|
|
133
|
-
refreshToken?: string;
|
|
134
|
-
accessToken?: string;
|
|
135
|
-
userId?: unknown;
|
|
136
|
-
domain?: string;
|
|
137
|
-
fingerprint?: string;
|
|
138
|
-
label?: string;
|
|
139
|
-
}): Promise<number>;
|
|
144
|
+
deleteToken(query: AuthTokenQuery): Promise<number>;
|
|
140
145
|
verifyPassword(password: string, hash: string): Promise<boolean>;
|
|
141
146
|
filterUser<T = any, U = any>(fullUser: T): U;
|
|
142
147
|
guessExceptionText(error: any, defMsg?: string): string;
|
|
@@ -9,6 +9,8 @@ import cors from 'cors';
|
|
|
9
9
|
import express from 'express';
|
|
10
10
|
import jwt from 'jsonwebtoken';
|
|
11
11
|
import multer from 'multer';
|
|
12
|
+
import { nullAuthModule } from './auth-module.js';
|
|
13
|
+
import { nullAuthStorage } from './auth-storage.js';
|
|
12
14
|
export class ApiModule {
|
|
13
15
|
constructor(opts = {}) {
|
|
14
16
|
this.mountpath = '';
|
|
@@ -160,6 +162,8 @@ export class ApiServer {
|
|
|
160
162
|
constructor(config = {}) {
|
|
161
163
|
this.currReq = null;
|
|
162
164
|
this.config = fillConfig(config);
|
|
165
|
+
this.storageAdapter = nullAuthStorage;
|
|
166
|
+
this.moduleAdapter = nullAuthModule;
|
|
163
167
|
this.app = express();
|
|
164
168
|
if (config.uploadPath) {
|
|
165
169
|
const upload = multer({ dest: config.uploadPath });
|
|
@@ -168,6 +172,32 @@ export class ApiServer {
|
|
|
168
172
|
this.middlewares();
|
|
169
173
|
// addSwaggerUi(this.app);
|
|
170
174
|
}
|
|
175
|
+
authStorage(storage) {
|
|
176
|
+
this.storageAdapter = storage;
|
|
177
|
+
return this;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* @deprecated Use {@link ApiServer.authStorage} instead.
|
|
181
|
+
*/
|
|
182
|
+
useAuthStorage(storage) {
|
|
183
|
+
return this.authStorage(storage);
|
|
184
|
+
}
|
|
185
|
+
authModule(module) {
|
|
186
|
+
this.moduleAdapter = module;
|
|
187
|
+
return this;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* @deprecated Use {@link ApiServer.authModule} instead.
|
|
191
|
+
*/
|
|
192
|
+
useAuthModule(module) {
|
|
193
|
+
return this.authModule(module);
|
|
194
|
+
}
|
|
195
|
+
getAuthStorage() {
|
|
196
|
+
return this.storageAdapter;
|
|
197
|
+
}
|
|
198
|
+
getAuthModule() {
|
|
199
|
+
return this.moduleAdapter;
|
|
200
|
+
}
|
|
171
201
|
jwtSign(payload, secret, expiresInSeconds, options) {
|
|
172
202
|
options || (options = {});
|
|
173
203
|
const opts = { ...options, expiresIn: expiresInSeconds };
|
|
@@ -240,36 +270,51 @@ export class ApiServer {
|
|
|
240
270
|
return null;
|
|
241
271
|
}
|
|
242
272
|
async getUser(uid) {
|
|
243
|
-
|
|
244
|
-
|
|
273
|
+
return this.storageAdapter.getUser(uid);
|
|
274
|
+
}
|
|
275
|
+
getUserPasswordHash(user) {
|
|
276
|
+
return this.storageAdapter.getUserPasswordHash(user);
|
|
277
|
+
}
|
|
278
|
+
getUserId(user) {
|
|
279
|
+
return this.storageAdapter.getUserId(user);
|
|
245
280
|
}
|
|
246
281
|
async authenticateUser(params) {
|
|
247
|
-
|
|
248
|
-
|
|
282
|
+
if (!params?.login || !params?.password) {
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
285
|
+
const user = await this.getUser(params.login);
|
|
286
|
+
if (!user) {
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
const hash = this.storageAdapter.getUserPasswordHash(user);
|
|
290
|
+
return this.verifyPassword(params.password, hash);
|
|
249
291
|
}
|
|
250
|
-
async storeToken(
|
|
251
|
-
|
|
252
|
-
throw new Error('storeToken() not implemented');
|
|
292
|
+
async storeToken(data) {
|
|
293
|
+
await this.storageAdapter.storeToken(data);
|
|
253
294
|
}
|
|
254
|
-
async getToken(
|
|
255
|
-
|
|
256
|
-
throw new Error('getToken() not implemented');
|
|
295
|
+
async getToken(query) {
|
|
296
|
+
return this.storageAdapter.getToken(query);
|
|
257
297
|
}
|
|
258
|
-
async updateToken(
|
|
259
|
-
|
|
260
|
-
|
|
298
|
+
async updateToken(updates) {
|
|
299
|
+
if (typeof this.storageAdapter.updateToken !== 'function') {
|
|
300
|
+
return false;
|
|
301
|
+
}
|
|
302
|
+
return this.storageAdapter.updateToken({
|
|
303
|
+
refreshToken: updates.refreshToken,
|
|
304
|
+
access: updates.accessToken,
|
|
305
|
+
expires: updates.expires,
|
|
306
|
+
clientId: updates.clientId,
|
|
307
|
+
scope: updates.scope
|
|
308
|
+
});
|
|
261
309
|
}
|
|
262
|
-
async deleteToken(
|
|
263
|
-
|
|
264
|
-
throw new Error('deleteToken() not implemented');
|
|
310
|
+
async deleteToken(query) {
|
|
311
|
+
return this.storageAdapter.deleteToken(query);
|
|
265
312
|
}
|
|
266
313
|
async verifyPassword(password, hash) {
|
|
267
|
-
|
|
268
|
-
void hash;
|
|
269
|
-
throw new Error('verifyPassword() not implemented');
|
|
314
|
+
return this.storageAdapter.verifyPassword(password, hash);
|
|
270
315
|
}
|
|
271
316
|
filterUser(fullUser) {
|
|
272
|
-
return fullUser;
|
|
317
|
+
return this.storageAdapter.filterUser(fullUser);
|
|
273
318
|
}
|
|
274
319
|
guessExceptionText(error, defMsg = 'Unkown Error') {
|
|
275
320
|
return guess_exception_text(error, defMsg);
|
|
@@ -487,6 +532,9 @@ export class ApiServer {
|
|
|
487
532
|
api(module) {
|
|
488
533
|
const router = express.Router();
|
|
489
534
|
module.server = this;
|
|
535
|
+
if (module?.moduleType === 'auth') {
|
|
536
|
+
this.authModule(module);
|
|
537
|
+
}
|
|
490
538
|
module.checkConfig();
|
|
491
539
|
const base = this.config.apiBasePath ?? '/api';
|
|
492
540
|
const ns = module.namespace;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ApiRequest } from './api-server-base.js';
|
|
2
|
+
import type { AuthTokenMetadata, AuthTokenPair } from './auth-storage.js';
|
|
3
|
+
export interface AuthProviderModule<UserRow, SafeUser> {
|
|
4
|
+
readonly moduleType: 'auth';
|
|
5
|
+
readonly namespace: string;
|
|
6
|
+
issueTokens(apiReq: ApiRequest, user: UserRow, metadata?: AuthTokenMetadata & {
|
|
7
|
+
expires?: Date;
|
|
8
|
+
}): Promise<AuthTokenPair>;
|
|
9
|
+
}
|
|
10
|
+
export declare abstract class BaseAuthModule implements AuthProviderModule<unknown, unknown> {
|
|
11
|
+
readonly moduleType: "auth";
|
|
12
|
+
abstract readonly namespace: string;
|
|
13
|
+
issueTokens(apiReq: ApiRequest, user: unknown, metadata?: AuthTokenMetadata & {
|
|
14
|
+
expires?: Date;
|
|
15
|
+
}): Promise<AuthTokenPair>;
|
|
16
|
+
}
|
|
17
|
+
export declare const nullAuthModule: AuthProviderModule<unknown, unknown>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Handy base that you can extend when wiring a real auth module. Subclasses
|
|
2
|
+
// must provide their namespace. Methods throw by default so unimplemented
|
|
3
|
+
// hooks fail loudly.
|
|
4
|
+
export class BaseAuthModule {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.moduleType = 'auth';
|
|
7
|
+
}
|
|
8
|
+
// Override to mint tokens for the provided user and request context
|
|
9
|
+
async issueTokens(apiReq, user, metadata) {
|
|
10
|
+
void apiReq;
|
|
11
|
+
void user;
|
|
12
|
+
void metadata;
|
|
13
|
+
throw new Error('Auth module not configured');
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
class NullAuthModule extends BaseAuthModule {
|
|
17
|
+
constructor() {
|
|
18
|
+
super(...arguments);
|
|
19
|
+
this.namespace = '__null__';
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export const nullAuthModule = new NullAuthModule();
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
export type AuthIdentifier = string | number;
|
|
2
|
+
export interface AuthTokenMetadata {
|
|
3
|
+
clientId?: string;
|
|
4
|
+
domain?: string;
|
|
5
|
+
fingerprint?: string;
|
|
6
|
+
label?: string;
|
|
7
|
+
scope?: string | string[];
|
|
8
|
+
}
|
|
9
|
+
export interface AuthTokenData extends AuthTokenMetadata {
|
|
10
|
+
access: string;
|
|
11
|
+
expires?: Date;
|
|
12
|
+
refresh: string;
|
|
13
|
+
userId: AuthIdentifier;
|
|
14
|
+
}
|
|
15
|
+
export interface AuthTokenQuery extends AuthTokenMetadata {
|
|
16
|
+
accessToken?: string;
|
|
17
|
+
refreshToken?: string;
|
|
18
|
+
userId?: AuthIdentifier;
|
|
19
|
+
}
|
|
20
|
+
export interface AuthTokenPair {
|
|
21
|
+
accessToken: string;
|
|
22
|
+
refreshToken: string;
|
|
23
|
+
}
|
|
24
|
+
export interface AuthTokenPayload extends AuthTokenMetadata {
|
|
25
|
+
exp?: number;
|
|
26
|
+
iat?: number;
|
|
27
|
+
uid: AuthIdentifier;
|
|
28
|
+
}
|
|
29
|
+
export interface PasskeyChallengeParams extends AuthTokenMetadata {
|
|
30
|
+
action: 'register' | 'authenticate';
|
|
31
|
+
login?: string;
|
|
32
|
+
userAgent?: string;
|
|
33
|
+
userId?: AuthIdentifier;
|
|
34
|
+
}
|
|
35
|
+
export interface PasskeyChallenge extends Record<string, unknown> {
|
|
36
|
+
challenge: string;
|
|
37
|
+
expiresAt?: string | number | Date;
|
|
38
|
+
userId?: AuthIdentifier;
|
|
39
|
+
}
|
|
40
|
+
export interface PasskeyVerificationParams extends AuthTokenMetadata {
|
|
41
|
+
expectedChallenge: string;
|
|
42
|
+
login?: string;
|
|
43
|
+
response: Record<string, unknown>;
|
|
44
|
+
userId?: AuthIdentifier;
|
|
45
|
+
}
|
|
46
|
+
export interface PasskeyVerificationResult extends Record<string, unknown> {
|
|
47
|
+
login?: string;
|
|
48
|
+
tokens?: AuthTokenPair;
|
|
49
|
+
userId?: AuthIdentifier;
|
|
50
|
+
verified: boolean;
|
|
51
|
+
}
|
|
52
|
+
export interface OAuthClient {
|
|
53
|
+
clientId: string;
|
|
54
|
+
clientSecret: string;
|
|
55
|
+
firstParty?: boolean;
|
|
56
|
+
metadata?: Record<string, unknown>;
|
|
57
|
+
name?: string;
|
|
58
|
+
redirectUris: string[];
|
|
59
|
+
scope?: string[];
|
|
60
|
+
}
|
|
61
|
+
export interface AuthCodeData {
|
|
62
|
+
code: string;
|
|
63
|
+
clientId: string;
|
|
64
|
+
codeChallenge?: string;
|
|
65
|
+
codeChallengeMethod?: 'plain' | 'S256';
|
|
66
|
+
expiresAt: Date;
|
|
67
|
+
metadata?: Record<string, unknown>;
|
|
68
|
+
redirectUri: string;
|
|
69
|
+
scope: string[];
|
|
70
|
+
userId: AuthIdentifier;
|
|
71
|
+
}
|
|
72
|
+
export type AuthCodeRequest = Omit<AuthCodeData, 'code' | 'expiresAt'> & {
|
|
73
|
+
code?: string;
|
|
74
|
+
expiresInSeconds?: number;
|
|
75
|
+
};
|
|
76
|
+
export interface AuthStorage<UserRow, SafeUser> {
|
|
77
|
+
getUser(identifier: AuthIdentifier): Promise<UserRow | null>;
|
|
78
|
+
getUserPasswordHash(user: UserRow): string;
|
|
79
|
+
getUserId(user: UserRow): AuthIdentifier;
|
|
80
|
+
filterUser(user: UserRow): SafeUser;
|
|
81
|
+
verifyPassword(password: string, hash: string): Promise<boolean>;
|
|
82
|
+
storeToken(data: AuthTokenData): Promise<void>;
|
|
83
|
+
getToken(query: AuthTokenQuery): Promise<AuthTokenData | null>;
|
|
84
|
+
deleteToken(query: AuthTokenQuery): Promise<number>;
|
|
85
|
+
updateToken?(updates: Partial<AuthTokenData> & {
|
|
86
|
+
refreshToken: string;
|
|
87
|
+
}): Promise<boolean>;
|
|
88
|
+
createPasskeyChallenge?(params: PasskeyChallengeParams): Promise<PasskeyChallenge>;
|
|
89
|
+
verifyPasskeyResponse?(params: PasskeyVerificationParams): Promise<PasskeyVerificationResult>;
|
|
90
|
+
getClient?(clientId: string): Promise<OAuthClient | null>;
|
|
91
|
+
verifyClientSecret?(client: OAuthClient, clientSecret: string | null): Promise<boolean>;
|
|
92
|
+
createAuthCode?(request: AuthCodeRequest): Promise<AuthCodeData>;
|
|
93
|
+
consumeAuthCode?(code: string, clientId: string): Promise<AuthCodeData | null>;
|
|
94
|
+
}
|
|
95
|
+
export declare class BaseAuthStorage<UserRow = unknown, SafeUser = unknown> implements AuthStorage<UserRow, SafeUser> {
|
|
96
|
+
getUser(identifier: AuthIdentifier): Promise<UserRow | null>;
|
|
97
|
+
getUserPasswordHash(user: UserRow): string;
|
|
98
|
+
getUserId(user: UserRow): AuthIdentifier;
|
|
99
|
+
filterUser(user: UserRow): SafeUser;
|
|
100
|
+
verifyPassword(password: string, hash: string): Promise<boolean>;
|
|
101
|
+
storeToken(data: AuthTokenData): Promise<void>;
|
|
102
|
+
getToken(query: AuthTokenQuery): Promise<AuthTokenData | null>;
|
|
103
|
+
deleteToken(query: AuthTokenQuery): Promise<number>;
|
|
104
|
+
updateToken(updates: Partial<AuthTokenData> & {
|
|
105
|
+
refreshToken: string;
|
|
106
|
+
}): Promise<boolean>;
|
|
107
|
+
createPasskeyChallenge(params: PasskeyChallengeParams): Promise<PasskeyChallenge>;
|
|
108
|
+
verifyPasskeyResponse(params: PasskeyVerificationParams): Promise<PasskeyVerificationResult>;
|
|
109
|
+
getClient(clientId: string): Promise<OAuthClient | null>;
|
|
110
|
+
verifyClientSecret(client: OAuthClient, clientSecret: string | null): Promise<boolean>;
|
|
111
|
+
createAuthCode(request: AuthCodeRequest): Promise<AuthCodeData>;
|
|
112
|
+
consumeAuthCode(code: string, clientId: string): Promise<AuthCodeData | null>;
|
|
113
|
+
}
|
|
114
|
+
export declare const nullAuthStorage: AuthStorage<unknown, unknown>;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// Numeric database id or lookup string such as username/email.
|
|
2
|
+
// Handy base you can extend when wiring a real storage adapter. Every method
|
|
3
|
+
// throws by default so unimplemented hooks fail loudly.
|
|
4
|
+
export class BaseAuthStorage {
|
|
5
|
+
// Override to load a user record by identifier
|
|
6
|
+
async getUser(identifier) {
|
|
7
|
+
void identifier;
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
// Override to return the stored password hash for the user
|
|
11
|
+
getUserPasswordHash(user) {
|
|
12
|
+
void user;
|
|
13
|
+
throw new Error('Auth storage not configured');
|
|
14
|
+
}
|
|
15
|
+
// Override to expose the canonical user identifier
|
|
16
|
+
getUserId(user) {
|
|
17
|
+
void user;
|
|
18
|
+
throw new Error('Auth storage not configured');
|
|
19
|
+
}
|
|
20
|
+
// Override to strip sensitive fields from the user record
|
|
21
|
+
filterUser(user) {
|
|
22
|
+
return user;
|
|
23
|
+
}
|
|
24
|
+
// Override to validate a raw password against the stored hash
|
|
25
|
+
async verifyPassword(password, hash) {
|
|
26
|
+
void password;
|
|
27
|
+
void hash;
|
|
28
|
+
throw new Error('Auth storage not configured');
|
|
29
|
+
}
|
|
30
|
+
// Override to persist newly issued tokens
|
|
31
|
+
async storeToken(data) {
|
|
32
|
+
void data;
|
|
33
|
+
throw new Error('Auth storage not configured');
|
|
34
|
+
}
|
|
35
|
+
// Override to look up a stored token by query
|
|
36
|
+
async getToken(query) {
|
|
37
|
+
void query;
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
// Override to remove stored tokens that match the query
|
|
41
|
+
async deleteToken(query) {
|
|
42
|
+
void query;
|
|
43
|
+
return 0;
|
|
44
|
+
}
|
|
45
|
+
// Override to update metadata for an existing refresh token
|
|
46
|
+
async updateToken(updates) {
|
|
47
|
+
void updates;
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
// Override to create a new passkey challenge record
|
|
51
|
+
async createPasskeyChallenge(params) {
|
|
52
|
+
void params;
|
|
53
|
+
throw new Error('Auth storage not configured');
|
|
54
|
+
}
|
|
55
|
+
// Override to verify an incoming WebAuthn response
|
|
56
|
+
async verifyPasskeyResponse(params) {
|
|
57
|
+
void params;
|
|
58
|
+
throw new Error('Auth storage not configured');
|
|
59
|
+
}
|
|
60
|
+
// Override to fetch an OAuth client by identifier
|
|
61
|
+
async getClient(clientId) {
|
|
62
|
+
void clientId;
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
// Override to compare a provided client secret against storage
|
|
66
|
+
async verifyClientSecret(client, clientSecret) {
|
|
67
|
+
void client;
|
|
68
|
+
void clientSecret;
|
|
69
|
+
throw new Error('Auth storage not configured');
|
|
70
|
+
}
|
|
71
|
+
// Override to create a new authorization code entry
|
|
72
|
+
async createAuthCode(request) {
|
|
73
|
+
void request;
|
|
74
|
+
throw new Error('Auth storage not configured');
|
|
75
|
+
}
|
|
76
|
+
// Override to consume and invalidate an authorization code
|
|
77
|
+
async consumeAuthCode(code, clientId) {
|
|
78
|
+
void code;
|
|
79
|
+
void clientId;
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
export const nullAuthStorage = new BaseAuthStorage();
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
1
|
export { default as ApiServer } from './api-server-base.js';
|
|
2
2
|
export { ApiModule, ApiError } from './api-server-base.js';
|
|
3
3
|
export type { ApiErrorParams, ApiHandler, ApiKey, ApiServerConf, ApiRequest, ApiRoute, ApiAuthType, ApiAuthClass, ApiTokenData, RequestWithStuff } from './api-server-base.js';
|
|
4
|
+
export type { AuthIdentifier, AuthTokenMetadata, AuthTokenData, AuthTokenQuery, AuthTokenPair, AuthTokenPayload, PasskeyChallengeParams, PasskeyChallenge, PasskeyVerificationParams, PasskeyVerificationResult, OAuthClient, AuthCodeData, AuthCodeRequest, AuthStorage } from './auth-storage.js';
|
|
5
|
+
export type { AuthProviderModule } from './auth-module.js';
|
|
6
|
+
export { nullAuthStorage, BaseAuthStorage } from './auth-storage.js';
|
|
7
|
+
export { nullAuthModule, BaseAuthModule } from './auth-module.js';
|
package/dist/esm/index.js
CHANGED