@technomoron/api-server-base 2.0.0-beta.2 → 2.0.0-beta.20
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 +81 -28
- package/dist/cjs/api-module.cjs +9 -0
- package/dist/cjs/api-module.d.ts +7 -4
- package/dist/cjs/api-server-base.cjs +607 -99
- package/dist/cjs/api-server-base.d.ts +80 -23
- package/dist/cjs/auth-api/auth-module.d.ts +23 -3
- package/dist/cjs/auth-api/auth-module.js +320 -124
- package/dist/cjs/auth-api/compat-auth-storage.d.ts +7 -5
- package/dist/cjs/auth-api/compat-auth-storage.js +15 -3
- package/dist/cjs/auth-api/mem-auth-store.d.ts +5 -3
- package/dist/cjs/auth-api/mem-auth-store.js +14 -28
- package/dist/cjs/auth-api/module.d.ts +1 -1
- package/dist/cjs/auth-api/sql-auth-store.d.ts +16 -4
- package/dist/cjs/auth-api/sql-auth-store.js +43 -30
- package/dist/cjs/auth-api/storage.d.ts +6 -4
- package/dist/cjs/auth-api/storage.js +15 -5
- package/dist/cjs/auth-api/types.d.ts +7 -2
- package/dist/cjs/auth-api/user-id.d.ts +5 -0
- package/dist/cjs/auth-api/user-id.js +38 -0
- package/dist/cjs/auth-cookie-options.d.ts +11 -0
- package/dist/cjs/auth-cookie-options.js +66 -0
- package/dist/cjs/index.cjs +4 -14
- package/dist/cjs/index.d.ts +4 -9
- package/dist/cjs/oauth/memory.d.ts +6 -0
- package/dist/cjs/oauth/memory.js +44 -11
- package/dist/cjs/oauth/models.d.ts +7 -2
- package/dist/cjs/oauth/models.js +10 -21
- package/dist/cjs/oauth/sequelize.d.ts +10 -48
- package/dist/cjs/oauth/sequelize.js +44 -99
- package/dist/cjs/oauth/types.d.ts +1 -0
- package/dist/cjs/passkey/base.d.ts +2 -0
- package/dist/cjs/passkey/config.d.ts +2 -0
- package/dist/cjs/passkey/config.js +26 -0
- package/dist/cjs/passkey/memory.d.ts +8 -0
- package/dist/cjs/passkey/memory.js +57 -16
- package/dist/cjs/passkey/models.d.ts +13 -4
- package/dist/cjs/passkey/models.js +41 -14
- package/dist/cjs/passkey/sequelize.d.ts +13 -25
- package/dist/cjs/passkey/sequelize.js +68 -153
- package/dist/cjs/passkey/service.d.ts +6 -2
- package/dist/cjs/passkey/service.js +205 -27
- package/dist/cjs/passkey/types.d.ts +18 -9
- package/dist/cjs/sequelize-utils.d.ts +8 -0
- package/dist/cjs/sequelize-utils.js +57 -0
- package/dist/cjs/token/base.d.ts +2 -1
- package/dist/cjs/token/base.js +3 -1
- package/dist/cjs/token/memory.d.ts +10 -0
- package/dist/cjs/token/memory.js +122 -32
- package/dist/cjs/token/sequelize.d.ts +4 -4
- package/dist/cjs/token/sequelize.js +67 -85
- package/dist/cjs/token/types.d.ts +8 -1
- package/dist/cjs/user/base.d.ts +1 -0
- package/dist/cjs/user/base.js +11 -4
- package/dist/cjs/user/memory.d.ts +2 -0
- package/dist/cjs/user/memory.js +9 -10
- package/dist/cjs/user/sequelize.d.ts +7 -2
- package/dist/cjs/user/sequelize.js +19 -32
- package/dist/esm/api-module.d.ts +7 -4
- package/dist/esm/api-module.js +9 -0
- package/dist/esm/api-server-base.d.ts +80 -23
- package/dist/esm/api-server-base.js +608 -100
- package/dist/esm/auth-api/auth-module.d.ts +23 -3
- package/dist/esm/auth-api/auth-module.js +321 -125
- package/dist/esm/auth-api/compat-auth-storage.d.ts +7 -5
- package/dist/esm/auth-api/compat-auth-storage.js +13 -1
- package/dist/esm/auth-api/mem-auth-store.d.ts +5 -3
- package/dist/esm/auth-api/mem-auth-store.js +14 -28
- package/dist/esm/auth-api/module.d.ts +1 -1
- package/dist/esm/auth-api/sql-auth-store.d.ts +16 -4
- package/dist/esm/auth-api/sql-auth-store.js +43 -30
- package/dist/esm/auth-api/storage.d.ts +6 -4
- package/dist/esm/auth-api/storage.js +13 -3
- package/dist/esm/auth-api/types.d.ts +7 -2
- package/dist/esm/auth-api/user-id.d.ts +5 -0
- package/dist/esm/auth-api/user-id.js +32 -0
- package/dist/esm/auth-cookie-options.d.ts +11 -0
- package/dist/esm/auth-cookie-options.js +63 -0
- package/dist/esm/index.d.ts +4 -9
- package/dist/esm/index.js +2 -7
- package/dist/esm/oauth/memory.d.ts +6 -0
- package/dist/esm/oauth/memory.js +44 -11
- package/dist/esm/oauth/models.d.ts +7 -2
- package/dist/esm/oauth/models.js +6 -19
- package/dist/esm/oauth/sequelize.d.ts +10 -48
- package/dist/esm/oauth/sequelize.js +32 -87
- package/dist/esm/oauth/types.d.ts +1 -0
- package/dist/esm/passkey/base.d.ts +2 -0
- package/dist/esm/passkey/config.d.ts +2 -0
- package/dist/esm/passkey/config.js +23 -0
- package/dist/esm/passkey/memory.d.ts +8 -0
- package/dist/esm/passkey/memory.js +57 -16
- package/dist/esm/passkey/models.d.ts +13 -4
- package/dist/esm/passkey/models.js +39 -12
- package/dist/esm/passkey/sequelize.d.ts +13 -25
- package/dist/esm/passkey/sequelize.js +69 -154
- package/dist/esm/passkey/service.d.ts +6 -2
- package/dist/esm/passkey/service.js +173 -28
- package/dist/esm/passkey/types.d.ts +18 -9
- package/dist/esm/sequelize-utils.d.ts +8 -0
- package/dist/esm/sequelize-utils.js +48 -0
- package/dist/esm/token/base.d.ts +2 -1
- package/dist/esm/token/base.js +3 -1
- package/dist/esm/token/memory.d.ts +10 -0
- package/dist/esm/token/memory.js +122 -32
- package/dist/esm/token/sequelize.d.ts +4 -4
- package/dist/esm/token/sequelize.js +67 -85
- package/dist/esm/token/types.d.ts +8 -1
- package/dist/esm/user/base.d.ts +1 -0
- package/dist/esm/user/base.js +11 -4
- package/dist/esm/user/memory.d.ts +2 -0
- package/dist/esm/user/memory.js +9 -10
- package/dist/esm/user/sequelize.d.ts +7 -2
- package/dist/esm/user/sequelize.js +19 -32
- package/docs/swagger/openapi.json +1876 -0
- package/package.json +81 -32
package/README.txt
CHANGED
|
@@ -25,13 +25,13 @@ pnpm add @technomoron/api-server-base
|
|
|
25
25
|
|
|
26
26
|
All runtime dependencies and `@types/*` packages are bundled with the distribution. The library exports ES modules by default. Consumers that rely on CommonJS can import via the require entry exposed in package.json.
|
|
27
27
|
|
|
28
|
-
Quick Start
|
|
29
|
-
-----------
|
|
30
|
-
import { ApiServer, ApiModule, ApiError,
|
|
28
|
+
Quick Start
|
|
29
|
+
-----------
|
|
30
|
+
import { ApiServer, ApiModule, ApiError, BaseAuthAdapter } from '@technomoron/api-server-base';
|
|
31
31
|
|
|
32
32
|
type DemoUser = { id: string; email: string; password: string };
|
|
33
33
|
|
|
34
|
-
class DemoStorage extends
|
|
34
|
+
class DemoStorage extends BaseAuthAdapter<DemoUser, Omit<DemoUser, 'password'>> {
|
|
35
35
|
private readonly users = new Map<string, DemoUser>([
|
|
36
36
|
['1', { id: '1', email: 'demo@example.com', password: 'secret' }]
|
|
37
37
|
]);
|
|
@@ -79,14 +79,15 @@ class UserModule extends ApiModule<AppServer> {
|
|
|
79
79
|
|
|
80
80
|
const yourStorageAdapter = new DemoStorage();
|
|
81
81
|
|
|
82
|
-
const server = new AppServer({
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
})
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
82
|
+
const server = new AppServer({
|
|
83
|
+
apiPort: 3101,
|
|
84
|
+
apiHost: '127.0.0.1',
|
|
85
|
+
accessSecret: 'replace-me'
|
|
86
|
+
})
|
|
87
|
+
.authStorage(yourStorageAdapter)
|
|
88
|
+
.api(new UserModule())
|
|
89
|
+
.finalize()
|
|
90
|
+
.start();
|
|
90
91
|
|
|
91
92
|
Need a dedicated auth module as well? Chain `.authModule(...)` in the same spot.
|
|
92
93
|
|
|
@@ -102,30 +103,36 @@ apiBasePath (string, default '/api') Prefix applied to every module namespace.
|
|
|
102
103
|
origins (string array, default empty array) CORS allowlist; empty allows all origins.
|
|
103
104
|
uploadPath (string, default empty string) Enables multer.any() when provided.
|
|
104
105
|
uploadMax (number, default 30 * 1024 * 1024) Maximum upload size in bytes.
|
|
106
|
+
staticDirs (record, default empty object) Map of mount path => disk path for serving static files as-is (ex: { '/assets': './public' }).
|
|
105
107
|
accessSecret (string, default empty string) Required for JWT signing and verification.
|
|
106
108
|
refreshSecret (string, default empty string) Used for refresh tokens if you implement them.
|
|
107
|
-
cookieDomain (string, default '
|
|
109
|
+
cookieDomain (string, default '') Domain applied to auth cookies.
|
|
110
|
+
cookiePath (string, default '/') Path applied to auth cookies.
|
|
111
|
+
cookieSameSite ('lax' | 'strict' | 'none', default 'lax') SameSite attribute applied to auth cookies.
|
|
112
|
+
cookieSecure (boolean | 'auto', default 'auto') Secure attribute applied to auth cookies; 'auto' enables Secure only when the request is HTTPS (or forwarded as HTTPS).
|
|
113
|
+
cookieHttpOnly (boolean, default true) HttpOnly attribute applied to auth cookies.
|
|
108
114
|
accessCookie (string, default 'dat') Access token cookie name.
|
|
109
115
|
refreshCookie (string, default 'drt') Refresh token cookie name.
|
|
110
116
|
accessExpiry (number, default 60 * 15) Access token lifetime in seconds.
|
|
111
|
-
refreshExpiry (number, default 30 * 24 * 60 * 60
|
|
117
|
+
refreshExpiry (number, default 30 * 24 * 60 * 60) Refresh token lifetime in seconds.
|
|
112
118
|
sessionRefreshExpiry (number, default 24 * 60 * 60) Session token lifetime in seconds when clients opt out of "remember me" cookies.
|
|
113
119
|
authApi (boolean, default false) Toggle you can use when mounting auth routes.
|
|
114
120
|
devMode (boolean, default false) Custom hook for development only features.
|
|
115
121
|
debug (boolean, default false) When true the server logs inbound requests via dumpRequest.
|
|
116
122
|
hydrateGetBody (boolean, default true) Copy query parameters into `req.body` for GET requests; set false if you prefer untouched bodies.
|
|
117
123
|
validateTokens (boolean, default false) When true, every JWT-authenticated request must match a stored token row (access token + user id) before reaching your handler. API keys remain stateless either way.
|
|
124
|
+
refreshMaybe (boolean, default false) When true, `auth: maybe` routes will try to refresh a missing/expired access token using the refresh cookie; if refresh fails, the request stays anonymous.
|
|
118
125
|
|
|
119
126
|
Tip: If you add new configuration fields in downstream projects, extend ApiServerConf and update fillConfig so defaults stay aligned.
|
|
120
127
|
|
|
121
|
-
Request Lifecycle
|
|
122
|
-
-----------------
|
|
123
|
-
1. Express middlewares (express.json, cookie-parser, optional multer) run before your handler.
|
|
124
|
-
2. ApiServer wraps the route inside handle_request,
|
|
125
|
-
3. authenticate enforces the ApiRoute auth type: `none`, `maybe`, `yes`, `strict`, or `apikey`. Bearer JWTs and the `dat`
|
|
126
|
-
4. authorize runs with the requested auth class (any or admin in the base implementation). Override to connect to your role system.
|
|
127
|
-
5. The handler executes and returns its tuple. Responses are normalized to { code, message, data } JSON.
|
|
128
|
-
6. Errors bubble into the wrapper. ApiError instances respect the provided status codes; other exceptions result in a 500 with text derived from guessExceptionText.
|
|
128
|
+
Request Lifecycle
|
|
129
|
+
-----------------
|
|
130
|
+
1. Express middlewares (express.json, cookie-parser, optional multer) run before your handler.
|
|
131
|
+
2. ApiServer wraps the route inside handle_request, creating an ApiRequest and logging when debug is enabled.
|
|
132
|
+
3. authenticate enforces the ApiRoute auth type: `none`, `maybe`, `yes`, `strict`, or `apikey`. Bearer JWTs and the access cookie (`accessCookie`, default `dat`) are accepted for `yes`/`strict`, while API key tokens prefixed with `apikey-` always delegate to `getApiKey`. When `refreshSecret` is configured and your storage supports refresh lookups (`getToken({ refreshToken })` + `updateToken(...)`), `yes`/`strict` routes will automatically mint a new access token when it is missing or expired (and also recover from "Authorization token is no longer valid" by refreshing). `maybe` routes only do the same when `refreshMaybe: true`. The optional `strict` type (or server-wide `validateTokens` flag) requires the signed JWT to exist in storage; when it does, the persisted row is attached to `apiReq.authToken`. The dedicated `apikey` type simply means “an API key is required”; otherwise API keys are still accepted by `yes`/`strict` routes alongside JWTs, and `apiReq.apiKey` is populated when present.
|
|
133
|
+
4. authorize runs with the requested auth class (any or admin in the base implementation). Override to connect to your role system.
|
|
134
|
+
5. The handler executes and returns its tuple. Responses are normalized to { code, message, data } JSON.
|
|
135
|
+
6. Errors bubble into the wrapper. ApiError instances respect the provided status codes; other exceptions result in a 500 with text derived from guessExceptionText.
|
|
129
136
|
|
|
130
137
|
Client IP Helpers
|
|
131
138
|
-----------------
|
|
@@ -133,12 +140,58 @@ Call `apiReq.getClientInfo()` when you need the entire client fingerprint captur
|
|
|
133
140
|
|
|
134
141
|
Call `apiReq.getClientIp()` to obtain the most likely client address, skipping loopback entries collected from proxy headers. Use `apiReq.getClientIpChain()` when you need the de-duplicated sequence gathered from the standard Forwarded/X-Forwarded-For/X-Real-IP headers as well as Express' `req.ip`/`req.ips` and the underlying socket. Both helpers reuse the cached payload returned by `apiReq.getClientInfo()`.
|
|
135
142
|
|
|
136
|
-
Extending the Base Classes
|
|
137
|
-
--------------------------
|
|
138
|
-
Implement the AuthStorage contract (getUser, verifyPassword, storeToken, updateToken, etc.) to integrate with your persistence layer, then supply it via authStorage().
|
|
139
|
-
Use your storage adapter's filterUser helper to trim sensitive data before returning responses.
|
|
140
|
-
Provide your own authorize method to enforce role based access control using the ApiAuthClass enum.
|
|
141
|
-
Create feature modules by extending ApiModule. Use the optional checkConfig hook to validate prerequisites before routes mount.
|
|
143
|
+
Extending the Base Classes
|
|
144
|
+
--------------------------
|
|
145
|
+
Implement the AuthStorage contract (getUser, verifyPassword, storeToken, updateToken, etc.) to integrate with your persistence layer, then supply it via authStorage().
|
|
146
|
+
Use your storage adapter's filterUser helper to trim sensitive data before returning responses.
|
|
147
|
+
Provide your own authorize method to enforce role based access control using the ApiAuthClass enum.
|
|
148
|
+
Create feature modules by extending ApiModule. Use the optional checkConfig hook to validate prerequisites before routes mount.
|
|
149
|
+
|
|
150
|
+
OAuth Client Secrets
|
|
151
|
+
--------------------
|
|
152
|
+
If your OAuth client records use a client secret, make `getClient(clientId)` return a client with a truthy `clientSecret` (do not return the stored hash/secret itself) and implement `verifyClientSecret(client, clientSecret)` on your storage adapter. If `clientSecret` is truthy but `verifyClientSecret` is not overridden, the `/auth/v1/oauth2/token` endpoint returns 501.
|
|
153
|
+
|
|
154
|
+
Sequelize Table Prefixes
|
|
155
|
+
------------------------
|
|
156
|
+
Sequelize-backed stores accept `tablePrefix` to prepend to the built-in table names (`users`, `jwttokens`, `passkey_credentials`, `passkey_challenges`, `oauth_clients`, `oauth_codes`).
|
|
157
|
+
|
|
158
|
+
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.
|
|
159
|
+
|
|
160
|
+
Example:
|
|
161
|
+
|
|
162
|
+
const store = new SqlAuthStore({
|
|
163
|
+
sequelize,
|
|
164
|
+
tablePrefix: 'myapp_'
|
|
165
|
+
});
|
|
166
|
+
// Creates tables like myapp_users, myapp_jwttokens, myapp_oauth_clients, ...
|
|
167
|
+
|
|
168
|
+
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.
|
|
169
|
+
|
|
170
|
+
Custom Express Endpoints
|
|
171
|
+
------------------------
|
|
172
|
+
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.
|
|
173
|
+
|
|
174
|
+
- `server.useExpress(...)` mounts middleware/routes and keeps the built-in `/api` 404 handler ordered last, so mounts under `apiBasePath` are not intercepted.
|
|
175
|
+
- Protect endpoints by inserting `server.expressAuth({ type, req })` as middleware. It authenticates using the same JWT/cookie/API-key logic as ApiModule routes and then runs `authorize`.
|
|
176
|
+
- On success, `expressAuth` attaches the computed ApiRequest to both `req.apiReq` and `res.locals.apiReq`.
|
|
177
|
+
- If you want the same JSON error envelope for custom endpoints, mount `server.expressErrorHandler()` after your custom routes.
|
|
178
|
+
|
|
179
|
+
Example:
|
|
180
|
+
|
|
181
|
+
server
|
|
182
|
+
.useExpress(
|
|
183
|
+
'/api/custom/optional',
|
|
184
|
+
server.expressAuth({ type: 'maybe', req: 'any' }),
|
|
185
|
+
(req, res) => {
|
|
186
|
+
const apiReq = (req as any).apiReq;
|
|
187
|
+
res.status(200).json({ uid: apiReq.tokenData?.uid ?? null });
|
|
188
|
+
}
|
|
189
|
+
)
|
|
190
|
+
.useExpress(server.expressErrorHandler());
|
|
191
|
+
|
|
192
|
+
Finalize And Start
|
|
193
|
+
------------------
|
|
194
|
+
Call `server.finalize()` after you have mounted all ApiModules and custom Express endpoints. After finalize (or after `start()`), calling `api()` / `useExpress()` will throw.
|
|
142
195
|
|
|
143
196
|
|
|
144
197
|
Tooling and Scripts
|
package/dist/cjs/api-module.cjs
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ApiModule = void 0;
|
|
4
4
|
class ApiModule {
|
|
5
|
+
get server() {
|
|
6
|
+
if (this._server === undefined) {
|
|
7
|
+
throw new Error('ApiModule.server is not set. Mount the module with ApiServer.api(...) before using it.');
|
|
8
|
+
}
|
|
9
|
+
return this._server;
|
|
10
|
+
}
|
|
11
|
+
set server(value) {
|
|
12
|
+
this._server = value;
|
|
13
|
+
}
|
|
5
14
|
constructor(opts = {}) {
|
|
6
15
|
this.mountpath = '';
|
|
7
16
|
this.namespace = opts.namespace ?? this.constructor.defaultNamespace ?? '';
|
package/dist/cjs/api-module.d.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
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 {
|
|
6
7
|
uid: unknown;
|
|
7
8
|
}
|
|
8
9
|
export type ApiRoute = {
|
|
9
|
-
method: 'get' | 'post' | 'put' | 'delete';
|
|
10
|
+
method: 'get' | 'post' | 'put' | 'patch' | 'delete';
|
|
10
11
|
path: string;
|
|
11
12
|
handler: ApiHandler;
|
|
12
13
|
auth: {
|
|
@@ -14,8 +15,10 @@ export type ApiRoute = {
|
|
|
14
15
|
req: ApiAuthClass;
|
|
15
16
|
};
|
|
16
17
|
};
|
|
17
|
-
export declare class ApiModule<T> {
|
|
18
|
-
|
|
18
|
+
export declare class ApiModule<T = unknown> {
|
|
19
|
+
private _server?;
|
|
20
|
+
get server(): T;
|
|
21
|
+
set server(value: T);
|
|
19
22
|
namespace: string;
|
|
20
23
|
mountpath: string;
|
|
21
24
|
static defaultNamespace: string;
|