@technomoron/apicore-server 1.0.0-beta.1

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 (171) hide show
  1. package/LICENSE +21 -0
  2. package/dist/cjs/api-module.cjs +34 -0
  3. package/dist/cjs/api-module.d.ts +45 -0
  4. package/dist/cjs/apicore-server.cjs +1561 -0
  5. package/dist/cjs/apicore-server.d.ts +288 -0
  6. package/dist/cjs/auth-api/auth-module.cjs +1248 -0
  7. package/dist/cjs/auth-api/auth-module.d.ts +116 -0
  8. package/dist/cjs/auth-api/compat-auth-storage.cjs +128 -0
  9. package/dist/cjs/auth-api/compat-auth-storage.d.ts +57 -0
  10. package/dist/cjs/auth-api/mem-auth-store.cjs +121 -0
  11. package/dist/cjs/auth-api/mem-auth-store.d.ts +68 -0
  12. package/dist/cjs/auth-api/module.cjs +25 -0
  13. package/dist/cjs/auth-api/module.d.ts +20 -0
  14. package/dist/cjs/auth-api/schemas.cjs +171 -0
  15. package/dist/cjs/auth-api/schemas.d.ts +21 -0
  16. package/dist/cjs/auth-api/sql-auth-store.cjs +179 -0
  17. package/dist/cjs/auth-api/sql-auth-store.d.ts +87 -0
  18. package/dist/cjs/auth-api/storage.cjs +102 -0
  19. package/dist/cjs/auth-api/storage.d.ts +38 -0
  20. package/dist/cjs/auth-api/types.cjs +2 -0
  21. package/dist/cjs/auth-api/types.d.ts +34 -0
  22. package/dist/cjs/auth-api/user-id.cjs +47 -0
  23. package/dist/cjs/auth-api/user-id.d.ts +5 -0
  24. package/dist/cjs/auth-cookie-options.cjs +66 -0
  25. package/dist/cjs/auth-cookie-options.d.ts +13 -0
  26. package/dist/cjs/base/client-info.cjs +285 -0
  27. package/dist/cjs/base/client-info.d.ts +27 -0
  28. package/dist/cjs/base/error-utils.cjs +50 -0
  29. package/dist/cjs/base/error-utils.d.ts +16 -0
  30. package/dist/cjs/base/request-utils.cjs +27 -0
  31. package/dist/cjs/base/request-utils.d.ts +8 -0
  32. package/dist/cjs/index.cjs +51 -0
  33. package/dist/cjs/index.d.ts +34 -0
  34. package/dist/cjs/limiter/auth-rate-limiter.cjs +35 -0
  35. package/dist/cjs/limiter/auth-rate-limiter.d.ts +12 -0
  36. package/dist/cjs/limiter/fixed-window.cjs +41 -0
  37. package/dist/cjs/limiter/fixed-window.d.ts +11 -0
  38. package/dist/cjs/oauth/base.cjs +7 -0
  39. package/dist/cjs/oauth/base.d.ts +17 -0
  40. package/dist/cjs/oauth/memory.cjs +135 -0
  41. package/dist/cjs/oauth/memory.d.ts +22 -0
  42. package/dist/cjs/oauth/models.cjs +47 -0
  43. package/dist/cjs/oauth/models.d.ts +50 -0
  44. package/dist/cjs/oauth/sequelize.cjs +159 -0
  45. package/dist/cjs/oauth/sequelize.d.ts +30 -0
  46. package/dist/cjs/oauth/types.cjs +3 -0
  47. package/dist/cjs/oauth/types.d.ts +51 -0
  48. package/dist/cjs/passkey/base.cjs +7 -0
  49. package/dist/cjs/passkey/base.d.ts +28 -0
  50. package/dist/cjs/passkey/config.cjs +26 -0
  51. package/dist/cjs/passkey/config.d.ts +2 -0
  52. package/dist/cjs/passkey/memory.cjs +123 -0
  53. package/dist/cjs/passkey/memory.d.ts +34 -0
  54. package/dist/cjs/passkey/models.cjs +142 -0
  55. package/dist/cjs/passkey/models.d.ts +34 -0
  56. package/dist/cjs/passkey/sequelize.cjs +126 -0
  57. package/dist/cjs/passkey/sequelize.d.ts +42 -0
  58. package/dist/cjs/passkey/service.cjs +413 -0
  59. package/dist/cjs/passkey/service.d.ts +21 -0
  60. package/dist/cjs/passkey/types.cjs +2 -0
  61. package/dist/cjs/passkey/types.d.ts +84 -0
  62. package/dist/cjs/sequelize-utils.cjs +56 -0
  63. package/dist/cjs/sequelize-utils.d.ts +8 -0
  64. package/dist/cjs/token/base.cjs +120 -0
  65. package/dist/cjs/token/base.d.ts +46 -0
  66. package/dist/cjs/token/memory.cjs +234 -0
  67. package/dist/cjs/token/memory.d.ts +29 -0
  68. package/dist/cjs/token/sequelize.cjs +400 -0
  69. package/dist/cjs/token/sequelize.d.ts +58 -0
  70. package/dist/cjs/token/types.cjs +2 -0
  71. package/dist/cjs/token/types.d.ts +34 -0
  72. package/dist/cjs/upload/memory.cjs +92 -0
  73. package/dist/cjs/upload/memory.d.ts +17 -0
  74. package/dist/cjs/upload/tus-module.cjs +270 -0
  75. package/dist/cjs/upload/tus-module.d.ts +38 -0
  76. package/dist/cjs/upload/types.cjs +2 -0
  77. package/dist/cjs/upload/types.d.ts +28 -0
  78. package/dist/cjs/user/base.cjs +53 -0
  79. package/dist/cjs/user/base.d.ts +36 -0
  80. package/dist/cjs/user/memory.cjs +194 -0
  81. package/dist/cjs/user/memory.d.ts +37 -0
  82. package/dist/cjs/user/sequelize.cjs +194 -0
  83. package/dist/cjs/user/sequelize.d.ts +46 -0
  84. package/dist/cjs/user/types.cjs +2 -0
  85. package/dist/cjs/user/types.d.ts +11 -0
  86. package/dist/esm/api-module.d.ts +45 -0
  87. package/dist/esm/api-module.js +30 -0
  88. package/dist/esm/apicore-server.d.ts +288 -0
  89. package/dist/esm/apicore-server.js +1552 -0
  90. package/dist/esm/auth-api/auth-module.d.ts +116 -0
  91. package/dist/esm/auth-api/auth-module.js +1246 -0
  92. package/dist/esm/auth-api/compat-auth-storage.d.ts +57 -0
  93. package/dist/esm/auth-api/compat-auth-storage.js +124 -0
  94. package/dist/esm/auth-api/mem-auth-store.d.ts +68 -0
  95. package/dist/esm/auth-api/mem-auth-store.js +117 -0
  96. package/dist/esm/auth-api/module.d.ts +20 -0
  97. package/dist/esm/auth-api/module.js +21 -0
  98. package/dist/esm/auth-api/schemas.d.ts +21 -0
  99. package/dist/esm/auth-api/schemas.js +168 -0
  100. package/dist/esm/auth-api/sql-auth-store.d.ts +87 -0
  101. package/dist/esm/auth-api/sql-auth-store.js +175 -0
  102. package/dist/esm/auth-api/storage.d.ts +38 -0
  103. package/dist/esm/auth-api/storage.js +98 -0
  104. package/dist/esm/auth-api/types.d.ts +34 -0
  105. package/dist/esm/auth-api/types.js +1 -0
  106. package/dist/esm/auth-api/user-id.d.ts +5 -0
  107. package/dist/esm/auth-api/user-id.js +41 -0
  108. package/dist/esm/auth-cookie-options.d.ts +13 -0
  109. package/dist/esm/auth-cookie-options.js +63 -0
  110. package/dist/esm/base/client-info.d.ts +27 -0
  111. package/dist/esm/base/client-info.js +282 -0
  112. package/dist/esm/base/error-utils.d.ts +16 -0
  113. package/dist/esm/base/error-utils.js +44 -0
  114. package/dist/esm/base/request-utils.d.ts +8 -0
  115. package/dist/esm/base/request-utils.js +23 -0
  116. package/dist/esm/index.d.ts +34 -0
  117. package/dist/esm/index.js +21 -0
  118. package/dist/esm/limiter/auth-rate-limiter.d.ts +12 -0
  119. package/dist/esm/limiter/auth-rate-limiter.js +32 -0
  120. package/dist/esm/limiter/fixed-window.d.ts +11 -0
  121. package/dist/esm/limiter/fixed-window.js +37 -0
  122. package/dist/esm/oauth/base.d.ts +17 -0
  123. package/dist/esm/oauth/base.js +3 -0
  124. package/dist/esm/oauth/memory.d.ts +22 -0
  125. package/dist/esm/oauth/memory.js +128 -0
  126. package/dist/esm/oauth/models.d.ts +50 -0
  127. package/dist/esm/oauth/models.js +38 -0
  128. package/dist/esm/oauth/sequelize.d.ts +30 -0
  129. package/dist/esm/oauth/sequelize.js +148 -0
  130. package/dist/esm/oauth/types.d.ts +51 -0
  131. package/dist/esm/oauth/types.js +2 -0
  132. package/dist/esm/passkey/base.d.ts +28 -0
  133. package/dist/esm/passkey/base.js +3 -0
  134. package/dist/esm/passkey/config.d.ts +2 -0
  135. package/dist/esm/passkey/config.js +23 -0
  136. package/dist/esm/passkey/memory.d.ts +34 -0
  137. package/dist/esm/passkey/memory.js +119 -0
  138. package/dist/esm/passkey/models.d.ts +34 -0
  139. package/dist/esm/passkey/models.js +135 -0
  140. package/dist/esm/passkey/sequelize.d.ts +42 -0
  141. package/dist/esm/passkey/sequelize.js +122 -0
  142. package/dist/esm/passkey/service.d.ts +21 -0
  143. package/dist/esm/passkey/service.js +376 -0
  144. package/dist/esm/passkey/types.d.ts +84 -0
  145. package/dist/esm/passkey/types.js +1 -0
  146. package/dist/esm/sequelize-utils.d.ts +8 -0
  147. package/dist/esm/sequelize-utils.js +47 -0
  148. package/dist/esm/token/base.d.ts +46 -0
  149. package/dist/esm/token/base.js +113 -0
  150. package/dist/esm/token/memory.d.ts +29 -0
  151. package/dist/esm/token/memory.js +230 -0
  152. package/dist/esm/token/sequelize.d.ts +58 -0
  153. package/dist/esm/token/sequelize.js +396 -0
  154. package/dist/esm/token/types.d.ts +34 -0
  155. package/dist/esm/token/types.js +1 -0
  156. package/dist/esm/upload/memory.d.ts +17 -0
  157. package/dist/esm/upload/memory.js +86 -0
  158. package/dist/esm/upload/tus-module.d.ts +38 -0
  159. package/dist/esm/upload/tus-module.js +266 -0
  160. package/dist/esm/upload/types.d.ts +28 -0
  161. package/dist/esm/upload/types.js +1 -0
  162. package/dist/esm/user/base.d.ts +36 -0
  163. package/dist/esm/user/base.js +46 -0
  164. package/dist/esm/user/memory.d.ts +37 -0
  165. package/dist/esm/user/memory.js +190 -0
  166. package/dist/esm/user/sequelize.d.ts +46 -0
  167. package/dist/esm/user/sequelize.js +188 -0
  168. package/dist/esm/user/types.d.ts +11 -0
  169. package/dist/esm/user/types.js +1 -0
  170. package/docs/swagger/openapi.json +2162 -0
  171. package/package.json +131 -0
@@ -0,0 +1,282 @@
1
+ function normalizeIpAddress(candidate) {
2
+ let value = candidate.trim();
3
+ if (!value) {
4
+ return null;
5
+ }
6
+ value = value.replace(/^"+|"+$/g, '').replace(/^'+|'+$/g, '');
7
+ if (value.startsWith('::ffff:')) {
8
+ value = value.slice(7);
9
+ }
10
+ if (value.startsWith('[') && value.endsWith(']')) {
11
+ value = value.slice(1, -1);
12
+ }
13
+ const firstColon = value.indexOf(':');
14
+ const lastColon = value.lastIndexOf(':');
15
+ if (firstColon !== -1 && firstColon === lastColon) {
16
+ const maybePort = value.slice(lastColon + 1);
17
+ if (/^\d+$/.test(maybePort)) {
18
+ value = value.slice(0, lastColon);
19
+ }
20
+ }
21
+ value = value.trim();
22
+ return value || null;
23
+ }
24
+ function extractForwardedFor(header) {
25
+ if (!header) {
26
+ return [];
27
+ }
28
+ const values = Array.isArray(header) ? header : [header];
29
+ const ips = [];
30
+ for (const entry of values) {
31
+ for (const part of entry.split(',')) {
32
+ const normalized = normalizeIpAddress(part);
33
+ if (normalized) {
34
+ ips.push(normalized);
35
+ }
36
+ }
37
+ }
38
+ return ips;
39
+ }
40
+ function extractForwardedHeader(header) {
41
+ if (!header) {
42
+ return [];
43
+ }
44
+ const values = Array.isArray(header) ? header : [header];
45
+ const ips = [];
46
+ for (const entry of values) {
47
+ for (const part of entry.split(',')) {
48
+ const match = part.match(/for=([^;]+)/i);
49
+ if (match) {
50
+ const normalized = normalizeIpAddress(match[1]);
51
+ if (normalized) {
52
+ ips.push(normalized);
53
+ }
54
+ }
55
+ }
56
+ }
57
+ return ips;
58
+ }
59
+ function detectBrowser(userAgent) {
60
+ const browserMatchers = [
61
+ { label: 'Edge', pattern: /(Edg|Edge|EdgiOS|EdgA)\/([\d.]+)/i, versionGroup: 2 },
62
+ { label: 'Chrome', pattern: /(Chrome|CriOS)\/([\d.]+)/i, versionGroup: 2 },
63
+ { label: 'Firefox', pattern: /(Firefox|FxiOS)\/([\d.]+)/i, versionGroup: 2 },
64
+ { label: 'Safari', pattern: /Version\/([\d.]+).*Safari/i, versionGroup: 1 },
65
+ { label: 'Opera', pattern: /(OPR|Opera)\/([\d.]+)/i, versionGroup: 2 },
66
+ { label: 'Brave', pattern: /Brave\/([\d.]+)/i, versionGroup: 1 },
67
+ { label: 'Vivaldi', pattern: /Vivaldi\/([\d.]+)/i, versionGroup: 1 },
68
+ { label: 'Electron', pattern: /Electron\/([\d.]+)/i, versionGroup: 1 },
69
+ { label: 'Node', pattern: /Node\.js\/([\d.]+)/i, versionGroup: 1 },
70
+ { label: 'IE', pattern: /MSIE ([\d.]+)/i, versionGroup: 1 },
71
+ { label: 'IE', pattern: /Trident\/.*rv:([\d.]+)/i, versionGroup: 1 }
72
+ ];
73
+ for (const matcher of browserMatchers) {
74
+ const m = userAgent.match(matcher.pattern);
75
+ if (m) {
76
+ const version = matcher.versionGroup ? m[matcher.versionGroup] : '';
77
+ return version ? `${matcher.label} ${version}` : matcher.label;
78
+ }
79
+ }
80
+ return '';
81
+ }
82
+ function detectOs(userAgent) {
83
+ const osMatchers = [
84
+ {
85
+ label: 'Windows',
86
+ pattern: /Windows NT ([\d.]+)/i,
87
+ transform: (match) => `Windows ${match[1]}`
88
+ },
89
+ {
90
+ label: 'iOS',
91
+ pattern: /iPhone OS ([\d_]+)/i,
92
+ transform: (match) => `iOS ${match[1].replace(/_/g, '.')}`
93
+ },
94
+ {
95
+ label: 'iPadOS',
96
+ pattern: /iPad; CPU OS ([\d_]+)/i,
97
+ transform: (match) => `iPadOS ${match[1].replace(/_/g, '.')}`
98
+ },
99
+ {
100
+ label: 'macOS',
101
+ pattern: /Mac OS X ([\d_]+)/i,
102
+ transform: (match) => `macOS ${match[1].replace(/_/g, '.')}`
103
+ },
104
+ {
105
+ label: 'Android',
106
+ pattern: /Android ([\d.]+)/i,
107
+ transform: (match) => `Android ${match[1]}`
108
+ },
109
+ {
110
+ label: 'ChromeOS',
111
+ pattern: /CrOS [^ ]+ ([\d.]+)/i,
112
+ transform: (match) => `ChromeOS ${match[1]}`
113
+ },
114
+ { label: 'Linux', pattern: /Linux/i },
115
+ { label: 'Unix', pattern: /X11/i }
116
+ ];
117
+ for (const matcher of osMatchers) {
118
+ const m = userAgent.match(matcher.pattern);
119
+ if (m) {
120
+ return matcher.transform ? matcher.transform(m) : matcher.label;
121
+ }
122
+ }
123
+ return '';
124
+ }
125
+ function detectDevice(userAgent, osLabel) {
126
+ if (/iPhone/i.test(userAgent)) {
127
+ return 'iPhone';
128
+ }
129
+ if (/iPad/i.test(userAgent)) {
130
+ return 'iPad';
131
+ }
132
+ if (/iPod/i.test(userAgent)) {
133
+ return 'iPod';
134
+ }
135
+ if (/Android/i.test(userAgent)) {
136
+ const match = userAgent.match(/;\s*([^;]+)\s+Build/i);
137
+ if (match) {
138
+ return match[1];
139
+ }
140
+ return 'Android Device';
141
+ }
142
+ if (/Macintosh/i.test(userAgent)) {
143
+ return 'Mac';
144
+ }
145
+ if (/Windows/i.test(userAgent)) {
146
+ return 'PC';
147
+ }
148
+ if (/CrOS/i.test(userAgent)) {
149
+ return 'Chromebook';
150
+ }
151
+ return osLabel;
152
+ }
153
+ function parseClientAgent(userAgentHeader) {
154
+ const raw = Array.isArray(userAgentHeader) ? userAgentHeader[0] : userAgentHeader;
155
+ const ua = typeof raw === 'string' ? raw.trim() : '';
156
+ if (!ua) {
157
+ return { ua: '', browser: '', os: '', device: '' };
158
+ }
159
+ const os = detectOs(ua);
160
+ const browser = detectBrowser(ua);
161
+ const device = detectDevice(ua, os);
162
+ return { ua, browser, os, device };
163
+ }
164
+ function isLoopbackAddress(ip) {
165
+ if (ip === '::1' || ip === '0:0:0:0:0:0:0:1') {
166
+ return true;
167
+ }
168
+ if (ip === '0.0.0.0' || ip === '127.0.0.1') {
169
+ return true;
170
+ }
171
+ if (ip.startsWith('127.')) {
172
+ return true;
173
+ }
174
+ return false;
175
+ }
176
+ function collectSocketAddresses(req) {
177
+ const seen = new Set();
178
+ const result = [];
179
+ const pushNormalized = (ip) => {
180
+ if (!ip || seen.has(ip))
181
+ return;
182
+ seen.add(ip);
183
+ result.push(ip);
184
+ };
185
+ if (Array.isArray(req.ips)) {
186
+ for (const ip of req.ips) {
187
+ pushNormalized(normalizeIpAddress(ip));
188
+ }
189
+ }
190
+ if (typeof req.ip === 'string') {
191
+ pushNormalized(normalizeIpAddress(req.ip));
192
+ }
193
+ const socketAddress = req.socket?.remoteAddress;
194
+ if (typeof socketAddress === 'string') {
195
+ pushNormalized(normalizeIpAddress(socketAddress));
196
+ }
197
+ const connectionAddress = req.connection?.remoteAddress;
198
+ if (typeof connectionAddress === 'string') {
199
+ pushNormalized(normalizeIpAddress(connectionAddress));
200
+ }
201
+ return result;
202
+ }
203
+ function collectForwardedIps(req) {
204
+ const seen = new Set();
205
+ const result = [];
206
+ const pushNormalized = (ip) => {
207
+ if (!ip || seen.has(ip))
208
+ return;
209
+ seen.add(ip);
210
+ result.push(ip);
211
+ };
212
+ for (const ip of extractForwardedFor(req.headers['x-forwarded-for'])) {
213
+ pushNormalized(ip);
214
+ }
215
+ for (const ip of extractForwardedHeader(req.headers['forwarded'])) {
216
+ pushNormalized(ip);
217
+ }
218
+ const realIp = req.headers['x-real-ip'];
219
+ if (Array.isArray(realIp)) {
220
+ for (const value of realIp) {
221
+ pushNormalized(normalizeIpAddress(value));
222
+ }
223
+ }
224
+ else if (typeof realIp === 'string') {
225
+ pushNormalized(normalizeIpAddress(realIp));
226
+ }
227
+ return result;
228
+ }
229
+ function collectClientIpChain(req, trustProxy) {
230
+ const socketIps = collectSocketAddresses(req);
231
+ // trustProxy=false: ignore forwarded headers entirely
232
+ if (trustProxy === false) {
233
+ return socketIps;
234
+ }
235
+ const forwardedIps = collectForwardedIps(req);
236
+ // trustProxy=number: only trust that many rightmost forwarded entries
237
+ if (typeof trustProxy === 'number' && trustProxy >= 0) {
238
+ const trusted = trustProxy > 0 ? forwardedIps.slice(-trustProxy) : [];
239
+ const seen = new Set(trusted);
240
+ for (const ip of socketIps) {
241
+ if (!seen.has(ip)) {
242
+ seen.add(ip);
243
+ trusted.push(ip);
244
+ }
245
+ }
246
+ return trusted;
247
+ }
248
+ // trustProxy=true or undefined: trust all (backwards-compatible default)
249
+ const seen = new Set(forwardedIps);
250
+ const result = [...forwardedIps];
251
+ for (const ip of socketIps) {
252
+ if (!seen.has(ip)) {
253
+ seen.add(ip);
254
+ result.push(ip);
255
+ }
256
+ }
257
+ return result;
258
+ }
259
+ function selectClientIp(chain) {
260
+ for (const ip of chain) {
261
+ if (!isLoopbackAddress(ip)) {
262
+ return ip;
263
+ }
264
+ }
265
+ return chain[0] ?? null;
266
+ }
267
+ function buildClientInfo(req, trustProxy) {
268
+ const agent = parseClientAgent(req.headers['user-agent']);
269
+ const ipchain = collectClientIpChain(req, trustProxy);
270
+ const ip = selectClientIp(ipchain);
271
+ return {
272
+ ...agent,
273
+ ip,
274
+ ipchain
275
+ };
276
+ }
277
+ export function ensureClientInfo(apiReq, trustProxy) {
278
+ if (!apiReq.clientInfo) {
279
+ apiReq.clientInfo = buildClientInfo(apiReq.req, trustProxy);
280
+ }
281
+ return apiReq.clientInfo;
282
+ }
@@ -0,0 +1,16 @@
1
+ import type { ApiErrorParams } from '../apicore-server.js';
2
+ export interface ApiErrorLike {
3
+ code: number;
4
+ message: string;
5
+ data?: unknown;
6
+ errors?: unknown;
7
+ }
8
+ export declare function guessExceptionText(error: unknown, defMsg?: string): string;
9
+ export declare function normalizeApiErrorParams({ code, message, data, errors }: ApiErrorParams): {
10
+ code: number;
11
+ message: string;
12
+ data: unknown;
13
+ errors: Record<string, string>;
14
+ };
15
+ export declare function isApiErrorLike(candidate: unknown): candidate is ApiErrorLike;
16
+ export declare function asHttpStatus(error: unknown): number | null;
@@ -0,0 +1,44 @@
1
+ export function guessExceptionText(error, defMsg = 'Unknown Error') {
2
+ const msg = [];
3
+ if (typeof error === 'string' && error.trim() !== '') {
4
+ msg.push(error);
5
+ }
6
+ else if (error && typeof error === 'object') {
7
+ const errorDetails = error;
8
+ if (typeof errorDetails.message === 'string' && errorDetails.message.trim() !== '') {
9
+ msg.push(errorDetails.message);
10
+ }
11
+ if (errorDetails.parent &&
12
+ typeof errorDetails.parent.message === 'string' &&
13
+ errorDetails.parent.message.trim() !== '') {
14
+ msg.push(errorDetails.parent.message);
15
+ }
16
+ }
17
+ return msg.length > 0 ? msg.join(' / ') : defMsg;
18
+ }
19
+ export function normalizeApiErrorParams({ code, message, data, errors }) {
20
+ return {
21
+ code: typeof code === 'number' ? code : 500,
22
+ message: guessExceptionText(message, '[Unknown error (null/undefined)]'),
23
+ data: data !== undefined ? data : null,
24
+ errors: errors !== undefined ? errors : {}
25
+ };
26
+ }
27
+ export function isApiErrorLike(candidate) {
28
+ if (!candidate || typeof candidate !== 'object') {
29
+ return false;
30
+ }
31
+ const maybeError = candidate;
32
+ return typeof maybeError.code === 'number' && typeof maybeError.message === 'string';
33
+ }
34
+ export function asHttpStatus(error) {
35
+ if (!error || typeof error !== 'object') {
36
+ return null;
37
+ }
38
+ const maybe = error;
39
+ const status = typeof maybe.status === 'number' ? maybe.status : maybe.statusCode;
40
+ if (typeof status === 'number' && status >= 400 && status <= 599) {
41
+ return status;
42
+ }
43
+ return null;
44
+ }
@@ -0,0 +1,8 @@
1
+ interface HydratableRequest {
2
+ method?: string;
3
+ query?: unknown;
4
+ body?: unknown;
5
+ }
6
+ export declare function isPlainObject(value: unknown): value is Record<string, unknown>;
7
+ export declare function hydrateGetBody(req: HydratableRequest): void;
8
+ export {};
@@ -0,0 +1,23 @@
1
+ export function isPlainObject(value) {
2
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
3
+ return false;
4
+ }
5
+ const proto = Object.getPrototypeOf(value);
6
+ return proto === Object.prototype || proto === null;
7
+ }
8
+ export function hydrateGetBody(req) {
9
+ if ((req.method ?? '').toUpperCase() !== 'GET') {
10
+ return;
11
+ }
12
+ const query = isPlainObject(req.query) ? req.query : null;
13
+ if (!query || Object.keys(query).length === 0) {
14
+ return;
15
+ }
16
+ const body = isPlainObject(req.body) ? req.body : null;
17
+ if (!body || Object.keys(body).length === 0) {
18
+ req.body = { ...query };
19
+ return;
20
+ }
21
+ // Keep explicit body fields authoritative when both query and body provide the same key.
22
+ req.body = { ...query, ...body };
23
+ }
@@ -0,0 +1,34 @@
1
+ export { default as ApiServer } from './apicore-server.js';
2
+ export { ApiError } from './apicore-server.js';
3
+ export { ApiModule } from './api-module.js';
4
+ export type { ApiErrorParams, ApiHandler, ApiKey, ApiServerConf, ApiRequest, ApiRoute, ApiAuthType, ApiAuthClass, ApiTokenData, ExtendedReq, ExpressApiRequest, ExpressApiLocals } from './apicore-server.js';
5
+ export type { AuthIdentifier } from './auth-api/types.js';
6
+ export type { Token, TokenPair, TokenStatus } from './token/types.js';
7
+ export type { JwtSignResult, JwtVerifyResult, JwtDecodeResult } from './token/base.js';
8
+ export type { OAuthClient, AuthCodeData, AuthCodeRequest } from './oauth/types.js';
9
+ export type { AuthProviderModule } from './auth-api/module.js';
10
+ export { nullAuthAdapter, BaseAuthAdapter } from './auth-api/storage.js';
11
+ export { nullAuthModule, BaseAuthModule } from './auth-api/module.js';
12
+ export { CompositeAuthAdapter } from './auth-api/compat-auth-storage.js';
13
+ export { MemAuthStore } from './auth-api/mem-auth-store.js';
14
+ export { default as AuthModule } from './auth-api/auth-module.js';
15
+ export type { OAuthStartParams, OAuthStartResult, OAuthCallbackParams, OAuthCallbackResult } from './oauth/types.js';
16
+ export type { BcryptHasherOptions, CreateUserInput, UpdateUserInput, PublicUserMapper } from './user/types.js';
17
+ export { UserStore } from './user/base.js';
18
+ export { MemoryUserStore } from './user/memory.js';
19
+ export type { MemoryUserAttributes, MemoryUserStoreOptions } from './user/memory.js';
20
+ export { TokenStore } from './token/base.js';
21
+ export { MemoryTokenStore } from './token/memory.js';
22
+ export { PasskeyService } from './passkey/service.js';
23
+ export { PasskeyStore } from './passkey/base.js';
24
+ export { MemoryPasskeyStore } from './passkey/memory.js';
25
+ export type { PasskeyServiceConfig, PasskeyChallengeRecord, PasskeyUserDescriptor, StoredPasskeyCredential, PasskeyChallenge, PasskeyChallengeParams, PasskeyVerificationParams, PasskeyVerificationResult } from './passkey/types.js';
26
+ export { OAuthStore } from './oauth/base.js';
27
+ export { MemoryOAuthStore } from './oauth/memory.js';
28
+ export { FixedWindowRateLimiter } from './limiter/fixed-window.js';
29
+ export type { RateLimitDecision } from './limiter/fixed-window.js';
30
+ export { createAuthRateLimitHook } from './limiter/auth-rate-limiter.js';
31
+ export type { AuthRateLimitConfig } from './limiter/auth-rate-limiter.js';
32
+ export { TusUploadModule } from './upload/tus-module.js';
33
+ export { MemoryTusUploadStore, TusUploadOffsetError } from './upload/memory.js';
34
+ export type { TusMetadata, TusUploadRecord, TusCreateUploadInput, TusAppendInput, TusUploadStore } from './upload/types.js';
@@ -0,0 +1,21 @@
1
+ export { default as ApiServer } from './apicore-server.js';
2
+ export { ApiError } from './apicore-server.js';
3
+ export { ApiModule } from './api-module.js';
4
+ export { nullAuthAdapter, BaseAuthAdapter } from './auth-api/storage.js';
5
+ export { nullAuthModule, BaseAuthModule } from './auth-api/module.js';
6
+ export { CompositeAuthAdapter } from './auth-api/compat-auth-storage.js';
7
+ export { MemAuthStore } from './auth-api/mem-auth-store.js';
8
+ export { default as AuthModule } from './auth-api/auth-module.js';
9
+ export { UserStore } from './user/base.js';
10
+ export { MemoryUserStore } from './user/memory.js';
11
+ export { TokenStore } from './token/base.js';
12
+ export { MemoryTokenStore } from './token/memory.js';
13
+ export { PasskeyService } from './passkey/service.js';
14
+ export { PasskeyStore } from './passkey/base.js';
15
+ export { MemoryPasskeyStore } from './passkey/memory.js';
16
+ export { OAuthStore } from './oauth/base.js';
17
+ export { MemoryOAuthStore } from './oauth/memory.js';
18
+ export { FixedWindowRateLimiter } from './limiter/fixed-window.js';
19
+ export { createAuthRateLimitHook } from './limiter/auth-rate-limiter.js';
20
+ export { TusUploadModule } from './upload/tus-module.js';
21
+ export { MemoryTusUploadStore, TusUploadOffsetError } from './upload/memory.js';
@@ -0,0 +1,12 @@
1
+ import { type ApiRequest } from '../apicore-server.js';
2
+ import { FixedWindowRateLimiter } from './fixed-window.js';
3
+ type AuthRateLimitEndpoint = 'login' | 'passkey-challenge' | 'oauth-token' | 'oauth-authorize';
4
+ export interface AuthRateLimitConfig {
5
+ windowSec?: number;
6
+ max?: number;
7
+ }
8
+ export declare function createAuthRateLimitHook(config: Partial<Record<AuthRateLimitEndpoint, AuthRateLimitConfig>>, limiter?: FixedWindowRateLimiter): (ctx: {
9
+ apiReq: ApiRequest;
10
+ endpoint: string;
11
+ }) => void;
12
+ export {};
@@ -0,0 +1,32 @@
1
+ import { ApiError } from '../apicore-server.js';
2
+ import { FixedWindowRateLimiter } from './fixed-window.js';
3
+ export function createAuthRateLimitHook(config, limiter) {
4
+ if (!config || Object.keys(config).length === 0) {
5
+ return () => { };
6
+ }
7
+ const instance = limiter ?? new FixedWindowRateLimiter();
8
+ return (ctx) => {
9
+ const endpointConfig = config[ctx.endpoint];
10
+ if (!endpointConfig) {
11
+ return;
12
+ }
13
+ const windowSec = endpointConfig.windowSec ?? 0;
14
+ const max = endpointConfig.max ?? 0;
15
+ if (windowSec <= 0 || max <= 0) {
16
+ return;
17
+ }
18
+ const clientIp = ctx.apiReq.getClientIp() ?? '';
19
+ if (!clientIp) {
20
+ return;
21
+ }
22
+ const key = `${ctx.endpoint}:${clientIp}`;
23
+ const decision = instance.check(key, max, windowSec * 1000);
24
+ if (!decision.allowed) {
25
+ throw new ApiError({
26
+ code: 429,
27
+ message: 'Too many requests; try again later',
28
+ data: { retryAfterSec: decision.retryAfterSec }
29
+ });
30
+ }
31
+ };
32
+ }
@@ -0,0 +1,11 @@
1
+ export type RateLimitDecision = {
2
+ allowed: boolean;
3
+ retryAfterSec: number;
4
+ };
5
+ export declare class FixedWindowRateLimiter {
6
+ private readonly maxKeys;
7
+ private readonly buckets;
8
+ constructor(maxKeys?: number);
9
+ check(key: string, max: number, windowMs: number): RateLimitDecision;
10
+ private prune;
11
+ }
@@ -0,0 +1,37 @@
1
+ export class FixedWindowRateLimiter {
2
+ constructor(maxKeys = 10000) {
3
+ this.maxKeys = maxKeys;
4
+ this.buckets = new Map();
5
+ }
6
+ check(key, max, windowMs) {
7
+ if (!key || max <= 0 || windowMs <= 0) {
8
+ return { allowed: true, retryAfterSec: 0 };
9
+ }
10
+ const now = Date.now();
11
+ const bucket = this.buckets.get(key);
12
+ if (!bucket || now - bucket.windowStartMs >= windowMs) {
13
+ this.buckets.delete(key);
14
+ this.buckets.set(key, { windowStartMs: now, count: 1 });
15
+ this.prune();
16
+ return { allowed: true, retryAfterSec: 0 };
17
+ }
18
+ bucket.count += 1;
19
+ // Refresh insertion order to keep active entries at the end for pruning.
20
+ this.buckets.delete(key);
21
+ this.buckets.set(key, bucket);
22
+ if (bucket.count <= max) {
23
+ return { allowed: true, retryAfterSec: 0 };
24
+ }
25
+ const retryAfterSec = Math.max(1, Math.ceil((bucket.windowStartMs + windowMs - now) / 1000));
26
+ return { allowed: false, retryAfterSec };
27
+ }
28
+ prune() {
29
+ while (this.buckets.size > this.maxKeys) {
30
+ const oldest = this.buckets.keys().next().value;
31
+ if (!oldest) {
32
+ break;
33
+ }
34
+ this.buckets.delete(oldest);
35
+ }
36
+ }
37
+ }
@@ -0,0 +1,17 @@
1
+ import type { AuthCode, OAuthClient } from './types.js';
2
+ /** Base contract for OAuth client/auth-code persistence backends. */
3
+ export declare abstract class OAuthStore {
4
+ /** Fetch an OAuth client by id. */
5
+ abstract getClient(clientId: string): Promise<OAuthClient | null>;
6
+ /** Create an OAuth client. */
7
+ abstract createClient(input: OAuthClient): Promise<OAuthClient>;
8
+ /** Verify an OAuth client secret. */
9
+ abstract verifyClientSecret(clientId: string, secret: string | null): Promise<boolean>;
10
+ /** Persist an authorization code. */
11
+ abstract createAuthCode(code: AuthCode): Promise<void>;
12
+ /** Consume an authorization code once. When clientId is provided, only consume if it matches. */
13
+ abstract consumeAuthCode(code: string, clientId?: string): Promise<AuthCode | null>;
14
+ /** Close underlying resources. */
15
+ abstract close(): Promise<void>;
16
+ }
17
+ export type { OAuthClient, AuthCode } from './types.js';
@@ -0,0 +1,3 @@
1
+ /** Base contract for OAuth client/auth-code persistence backends. */
2
+ export class OAuthStore {
3
+ }
@@ -0,0 +1,22 @@
1
+ import { OAuthStore, type AuthCode, type OAuthClient } from './base.js';
2
+ export interface MemoryOAuthStoreOptions {
3
+ bcryptRounds?: number;
4
+ maxClients?: number;
5
+ maxAuthCodes?: number;
6
+ }
7
+ export declare class MemoryOAuthStore extends OAuthStore {
8
+ private readonly clients;
9
+ private readonly codes;
10
+ private readonly bcryptRounds;
11
+ private readonly maxClients?;
12
+ private readonly maxAuthCodes?;
13
+ constructor(options?: MemoryOAuthStoreOptions);
14
+ getClient(clientId: string): Promise<OAuthClient | null>;
15
+ createClient(input: OAuthClient): Promise<OAuthClient>;
16
+ verifyClientSecret(clientId: string, secret: string | null): Promise<boolean>;
17
+ createAuthCode(code: AuthCode): Promise<void>;
18
+ consumeAuthCode(code: string, clientId?: string): Promise<AuthCode | null>;
19
+ close(): Promise<void>;
20
+ private enforceClientCapacity;
21
+ private enforceCodeCapacity;
22
+ }