@technomoron/api-server-base 2.0.0-beta.2 → 2.0.0-beta.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/README.txt +81 -28
  2. package/dist/cjs/api-module.cjs +9 -0
  3. package/dist/cjs/api-module.d.ts +7 -4
  4. package/dist/cjs/api-server-base.cjs +607 -99
  5. package/dist/cjs/api-server-base.d.ts +80 -23
  6. package/dist/cjs/auth-api/auth-module.d.ts +23 -3
  7. package/dist/cjs/auth-api/auth-module.js +320 -124
  8. package/dist/cjs/auth-api/compat-auth-storage.d.ts +7 -5
  9. package/dist/cjs/auth-api/compat-auth-storage.js +15 -3
  10. package/dist/cjs/auth-api/mem-auth-store.d.ts +5 -3
  11. package/dist/cjs/auth-api/mem-auth-store.js +14 -28
  12. package/dist/cjs/auth-api/module.d.ts +1 -1
  13. package/dist/cjs/auth-api/sql-auth-store.d.ts +16 -4
  14. package/dist/cjs/auth-api/sql-auth-store.js +43 -30
  15. package/dist/cjs/auth-api/storage.d.ts +6 -4
  16. package/dist/cjs/auth-api/storage.js +15 -5
  17. package/dist/cjs/auth-api/types.d.ts +7 -2
  18. package/dist/cjs/auth-api/user-id.d.ts +5 -0
  19. package/dist/cjs/auth-api/user-id.js +38 -0
  20. package/dist/cjs/auth-cookie-options.d.ts +11 -0
  21. package/dist/cjs/auth-cookie-options.js +66 -0
  22. package/dist/cjs/index.cjs +4 -14
  23. package/dist/cjs/index.d.ts +4 -9
  24. package/dist/cjs/oauth/memory.d.ts +6 -0
  25. package/dist/cjs/oauth/memory.js +44 -11
  26. package/dist/cjs/oauth/models.d.ts +7 -2
  27. package/dist/cjs/oauth/models.js +10 -21
  28. package/dist/cjs/oauth/sequelize.d.ts +10 -48
  29. package/dist/cjs/oauth/sequelize.js +44 -99
  30. package/dist/cjs/oauth/types.d.ts +1 -0
  31. package/dist/cjs/passkey/base.d.ts +2 -0
  32. package/dist/cjs/passkey/config.d.ts +2 -0
  33. package/dist/cjs/passkey/config.js +26 -0
  34. package/dist/cjs/passkey/memory.d.ts +8 -0
  35. package/dist/cjs/passkey/memory.js +57 -16
  36. package/dist/cjs/passkey/models.d.ts +13 -4
  37. package/dist/cjs/passkey/models.js +41 -14
  38. package/dist/cjs/passkey/sequelize.d.ts +13 -25
  39. package/dist/cjs/passkey/sequelize.js +68 -153
  40. package/dist/cjs/passkey/service.d.ts +6 -2
  41. package/dist/cjs/passkey/service.js +205 -27
  42. package/dist/cjs/passkey/types.d.ts +18 -9
  43. package/dist/cjs/sequelize-utils.d.ts +8 -0
  44. package/dist/cjs/sequelize-utils.js +57 -0
  45. package/dist/cjs/token/base.d.ts +2 -1
  46. package/dist/cjs/token/base.js +3 -1
  47. package/dist/cjs/token/memory.d.ts +10 -0
  48. package/dist/cjs/token/memory.js +122 -32
  49. package/dist/cjs/token/sequelize.d.ts +4 -4
  50. package/dist/cjs/token/sequelize.js +67 -85
  51. package/dist/cjs/token/types.d.ts +8 -1
  52. package/dist/cjs/user/base.d.ts +1 -0
  53. package/dist/cjs/user/base.js +11 -4
  54. package/dist/cjs/user/memory.d.ts +2 -0
  55. package/dist/cjs/user/memory.js +9 -10
  56. package/dist/cjs/user/sequelize.d.ts +7 -2
  57. package/dist/cjs/user/sequelize.js +19 -32
  58. package/dist/esm/api-module.d.ts +7 -4
  59. package/dist/esm/api-module.js +9 -0
  60. package/dist/esm/api-server-base.d.ts +80 -23
  61. package/dist/esm/api-server-base.js +608 -100
  62. package/dist/esm/auth-api/auth-module.d.ts +23 -3
  63. package/dist/esm/auth-api/auth-module.js +321 -125
  64. package/dist/esm/auth-api/compat-auth-storage.d.ts +7 -5
  65. package/dist/esm/auth-api/compat-auth-storage.js +13 -1
  66. package/dist/esm/auth-api/mem-auth-store.d.ts +5 -3
  67. package/dist/esm/auth-api/mem-auth-store.js +14 -28
  68. package/dist/esm/auth-api/module.d.ts +1 -1
  69. package/dist/esm/auth-api/sql-auth-store.d.ts +16 -4
  70. package/dist/esm/auth-api/sql-auth-store.js +43 -30
  71. package/dist/esm/auth-api/storage.d.ts +6 -4
  72. package/dist/esm/auth-api/storage.js +13 -3
  73. package/dist/esm/auth-api/types.d.ts +7 -2
  74. package/dist/esm/auth-api/user-id.d.ts +5 -0
  75. package/dist/esm/auth-api/user-id.js +32 -0
  76. package/dist/esm/auth-cookie-options.d.ts +11 -0
  77. package/dist/esm/auth-cookie-options.js +63 -0
  78. package/dist/esm/index.d.ts +4 -9
  79. package/dist/esm/index.js +2 -7
  80. package/dist/esm/oauth/memory.d.ts +6 -0
  81. package/dist/esm/oauth/memory.js +44 -11
  82. package/dist/esm/oauth/models.d.ts +7 -2
  83. package/dist/esm/oauth/models.js +6 -19
  84. package/dist/esm/oauth/sequelize.d.ts +10 -48
  85. package/dist/esm/oauth/sequelize.js +32 -87
  86. package/dist/esm/oauth/types.d.ts +1 -0
  87. package/dist/esm/passkey/base.d.ts +2 -0
  88. package/dist/esm/passkey/config.d.ts +2 -0
  89. package/dist/esm/passkey/config.js +23 -0
  90. package/dist/esm/passkey/memory.d.ts +8 -0
  91. package/dist/esm/passkey/memory.js +57 -16
  92. package/dist/esm/passkey/models.d.ts +13 -4
  93. package/dist/esm/passkey/models.js +39 -12
  94. package/dist/esm/passkey/sequelize.d.ts +13 -25
  95. package/dist/esm/passkey/sequelize.js +69 -154
  96. package/dist/esm/passkey/service.d.ts +6 -2
  97. package/dist/esm/passkey/service.js +173 -28
  98. package/dist/esm/passkey/types.d.ts +18 -9
  99. package/dist/esm/sequelize-utils.d.ts +8 -0
  100. package/dist/esm/sequelize-utils.js +48 -0
  101. package/dist/esm/token/base.d.ts +2 -1
  102. package/dist/esm/token/base.js +3 -1
  103. package/dist/esm/token/memory.d.ts +10 -0
  104. package/dist/esm/token/memory.js +122 -32
  105. package/dist/esm/token/sequelize.d.ts +4 -4
  106. package/dist/esm/token/sequelize.js +67 -85
  107. package/dist/esm/token/types.d.ts +8 -1
  108. package/dist/esm/user/base.d.ts +1 -0
  109. package/dist/esm/user/base.js +11 -4
  110. package/dist/esm/user/memory.d.ts +2 -0
  111. package/dist/esm/user/memory.js +9 -10
  112. package/dist/esm/user/sequelize.d.ts +7 -2
  113. package/dist/esm/user/sequelize.js +19 -32
  114. package/docs/swagger/openapi.json +1876 -0
  115. package/package.json +84 -34
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, BaseAuthStorage } from '@technomoron/api-server-base';
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 BaseAuthStorage<DemoUser, Omit<DemoUser, 'password'>> {
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
- apiPort: 3101,
84
- apiHost: '127.0.0.1',
85
- accessSecret: 'replace-me'
86
- })
87
- .authStorage(yourStorageAdapter)
88
- .api(new UserModule())
89
- .start();
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 '.somewhere-over-the-rainbow.com') Domain applied to auth cookies.
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 * 1000) Refresh token lifetime in milliseconds.
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, setting currReq and logging when debug is enabled.
125
- 3. authenticate enforces the ApiRoute auth type: `none`, `maybe`, `yes`, `strict`, or `apikey`. Bearer JWTs and the `dat` cookie are accepted for `yes`/`strict`, while API key tokens prefixed with `apikey-` always delegate to `getApiKey`. 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.
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
@@ -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 ?? '';
@@ -1,12 +1,13 @@
1
1
  import type { ApiRequest } from './api-server-base.js';
2
- export type ApiHandler = (apiReq: ApiRequest) => Promise<[number] | [number, any] | [number, any, string]>;
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
- server: T;
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;