@olane/o-server 0.8.3 → 0.8.5
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.md +136 -37
- package/dist/src/o-server.d.ts.map +1 -1
- package/dist/src/o-server.js +86 -20
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ HTTP server entrypoint for Olane OS nodes. Exposes a node's `use` functionality
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
|
|
10
|
+
pnpm install @olane/o-server
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
## Quick Start
|
|
@@ -219,11 +219,19 @@ const server = oServer({
|
|
|
219
219
|
credentials: true
|
|
220
220
|
},
|
|
221
221
|
|
|
222
|
-
// Optional:
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
222
|
+
// Optional: JWT authentication (recommended)
|
|
223
|
+
jwtAuth: {
|
|
224
|
+
method: 'secret',
|
|
225
|
+
secret: 'your-jwt-secret',
|
|
226
|
+
issuer: 'https://auth.example.com',
|
|
227
|
+
audience: 'https://api.example.com'
|
|
226
228
|
},
|
|
229
|
+
|
|
230
|
+
// DEPRECATED: Use jwtAuth instead. Will be removed in a future version.
|
|
231
|
+
// authenticate: async (req) => {
|
|
232
|
+
// const token = req.headers.authorization;
|
|
233
|
+
// return validateToken(token);
|
|
234
|
+
// },
|
|
227
235
|
|
|
228
236
|
// Optional: Enable debug logging (default: false)
|
|
229
237
|
debug: true
|
|
@@ -232,28 +240,71 @@ const server = oServer({
|
|
|
232
240
|
await server.start();
|
|
233
241
|
```
|
|
234
242
|
|
|
235
|
-
### Authentication
|
|
243
|
+
### JWT Authentication (Recommended)
|
|
244
|
+
|
|
245
|
+
Protect your endpoints with built-in JWT verification. All routes except `/health` require a valid token when `jwtAuth` is configured.
|
|
246
|
+
|
|
247
|
+
**RS256 with public key:**
|
|
248
|
+
```typescript
|
|
249
|
+
const server = oServer({
|
|
250
|
+
node: myNode,
|
|
251
|
+
port: 3000,
|
|
252
|
+
jwtAuth: {
|
|
253
|
+
method: 'publicKey',
|
|
254
|
+
publicKeyPath: '/path/to/public-key.pem',
|
|
255
|
+
issuer: 'https://auth.example.com',
|
|
256
|
+
audience: 'https://api.example.com',
|
|
257
|
+
algorithms: ['RS256']
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
**HS256 with shared secret:**
|
|
263
|
+
```typescript
|
|
264
|
+
const server = oServer({
|
|
265
|
+
node: myNode,
|
|
266
|
+
port: 3000,
|
|
267
|
+
jwtAuth: {
|
|
268
|
+
method: 'secret',
|
|
269
|
+
secret: process.env.JWT_SECRET!,
|
|
270
|
+
clockTolerance: 5 // seconds of tolerance for exp/nbf
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
**`jwtAuth` configuration options:**
|
|
276
|
+
|
|
277
|
+
| Option | Type | Required | Description |
|
|
278
|
+
|--------|------|----------|-------------|
|
|
279
|
+
| `method` | `'publicKey' \| 'secret'` | Yes | Verification method |
|
|
280
|
+
| `publicKeyPath` | `string` | If `method='publicKey'` | Path to PEM public key file |
|
|
281
|
+
| `secret` | `string` | If `method='secret'` | Shared secret for HS256 |
|
|
282
|
+
| `issuer` | `string` | No | Expected `iss` claim |
|
|
283
|
+
| `audience` | `string` | No | Expected `aud` claim |
|
|
284
|
+
| `algorithms` | `Algorithm[]` | No | Allowed algorithms (defaults based on method) |
|
|
285
|
+
| `clockTolerance` | `number` | No | Seconds of clock tolerance (default: 0) |
|
|
236
286
|
|
|
237
|
-
|
|
287
|
+
### Legacy Authentication (Deprecated)
|
|
288
|
+
|
|
289
|
+
> **Deprecated**: The `authenticate` option is deprecated and will be removed in a future version. Migrate to `jwtAuth` instead.
|
|
238
290
|
|
|
239
291
|
```typescript
|
|
292
|
+
// @deprecated - use jwtAuth instead
|
|
240
293
|
const server = oServer({
|
|
241
294
|
node: myNode,
|
|
242
295
|
port: 3000,
|
|
296
|
+
// @deprecated - use jwtAuth instead
|
|
243
297
|
authenticate: async (req) => {
|
|
244
|
-
// Validate JWT token
|
|
245
298
|
const token = req.headers.authorization?.split(' ')[1];
|
|
246
|
-
|
|
299
|
+
|
|
247
300
|
if (!token) {
|
|
248
301
|
throw new Error('No token provided');
|
|
249
302
|
}
|
|
250
|
-
|
|
303
|
+
|
|
251
304
|
const user = await verifyJWT(token);
|
|
252
305
|
return { userId: user.id, roles: user.roles };
|
|
253
306
|
}
|
|
254
307
|
});
|
|
255
|
-
|
|
256
|
-
// Now all requests require valid authentication
|
|
257
308
|
```
|
|
258
309
|
|
|
259
310
|
### CORS Configuration
|
|
@@ -323,6 +374,8 @@ await server.start();
|
|
|
323
374
|
|
|
324
375
|
```typescript
|
|
325
376
|
// frontend/src/api.ts
|
|
377
|
+
// Note: HTTP responses from o-server use the flat { success, data, error } format.
|
|
378
|
+
// This is different from internal node.use() calls which return response.result.success.
|
|
326
379
|
export async function analyzeRevenue(startDate: string, endDate: string) {
|
|
327
380
|
const response = await fetch('http://localhost:3000/api/v1/use', {
|
|
328
381
|
method: 'POST',
|
|
@@ -333,8 +386,13 @@ export async function analyzeRevenue(startDate: string, endDate: string) {
|
|
|
333
386
|
params: { startDate, endDate }
|
|
334
387
|
})
|
|
335
388
|
});
|
|
336
|
-
|
|
389
|
+
|
|
337
390
|
const result = await response.json();
|
|
391
|
+
|
|
392
|
+
if (!result.success) {
|
|
393
|
+
throw new Error(result.error?.message || 'Request failed');
|
|
394
|
+
}
|
|
395
|
+
|
|
338
396
|
return result.data;
|
|
339
397
|
}
|
|
340
398
|
```
|
|
@@ -349,11 +407,10 @@ import { oServer } from '@olane/o-server';
|
|
|
349
407
|
const server = oServer({
|
|
350
408
|
node: myNode,
|
|
351
409
|
port: 8080,
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
return { userId: decodedToken.uid };
|
|
410
|
+
jwtAuth: {
|
|
411
|
+
method: 'secret',
|
|
412
|
+
secret: process.env.JWT_SECRET!,
|
|
413
|
+
issuer: 'https://your-firebase-project.firebaseapp.com'
|
|
357
414
|
}
|
|
358
415
|
});
|
|
359
416
|
|
|
@@ -373,17 +430,22 @@ const server = oServer({ node: myNode, port: 3000 });
|
|
|
373
430
|
// Stripe webhook
|
|
374
431
|
server.app.post('/webhooks/stripe', async (req, res) => {
|
|
375
432
|
const event = req.body;
|
|
376
|
-
|
|
377
|
-
// Process via your node
|
|
378
|
-
await myNode.use(
|
|
433
|
+
|
|
434
|
+
// Process via your node (internal call uses response.result pattern)
|
|
435
|
+
const response = await myNode.use(
|
|
379
436
|
new oAddress('o://payments/processor'),
|
|
380
437
|
{
|
|
381
438
|
method: 'handle_payment',
|
|
382
439
|
params: { event }
|
|
383
440
|
}
|
|
384
441
|
);
|
|
385
|
-
|
|
386
|
-
|
|
442
|
+
|
|
443
|
+
if (!response.result.success) {
|
|
444
|
+
res.status(500).json({ error: response.result.error });
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
res.json({ received: true, data: response.result.data });
|
|
387
449
|
});
|
|
388
450
|
|
|
389
451
|
await server.start();
|
|
@@ -420,18 +482,20 @@ await server.start();
|
|
|
420
482
|
// -d '{"address": "o://any/node/in/network", "method": "...", "params": {...}}'
|
|
421
483
|
```
|
|
422
484
|
|
|
423
|
-
##
|
|
485
|
+
## Response Structure
|
|
424
486
|
|
|
425
|
-
|
|
487
|
+
### HTTP Response (from o-server)
|
|
426
488
|
|
|
427
|
-
|
|
428
|
-
|
|
489
|
+
`o-server` flattens the internal node response into a simple HTTP-friendly format:
|
|
490
|
+
|
|
491
|
+
```json
|
|
492
|
+
// Success response (HTTP)
|
|
429
493
|
{
|
|
430
494
|
"success": true,
|
|
431
495
|
"data": { ... }
|
|
432
496
|
}
|
|
433
497
|
|
|
434
|
-
// Error response
|
|
498
|
+
// Error response (HTTP)
|
|
435
499
|
{
|
|
436
500
|
"success": false,
|
|
437
501
|
"error": {
|
|
@@ -442,6 +506,38 @@ await server.start();
|
|
|
442
506
|
}
|
|
443
507
|
```
|
|
444
508
|
|
|
509
|
+
### Internal Node Response (from node.use())
|
|
510
|
+
|
|
511
|
+
Within the Olane node ecosystem, `node.use()` returns a JSON-RPC wrapped response. When calling nodes programmatically (not via HTTP), always access data through the `result` property:
|
|
512
|
+
|
|
513
|
+
```typescript
|
|
514
|
+
const response = await node.use(address, { method: 'my_method', params: {} });
|
|
515
|
+
|
|
516
|
+
// Access the response correctly:
|
|
517
|
+
if (response.result.success) {
|
|
518
|
+
const data = response.result.data; // Your method's return value
|
|
519
|
+
} else {
|
|
520
|
+
const error = response.result.error; // Error message string
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// Full internal response shape:
|
|
524
|
+
// {
|
|
525
|
+
// jsonrpc: "2.0",
|
|
526
|
+
// id: "request-id",
|
|
527
|
+
// result: {
|
|
528
|
+
// success: boolean,
|
|
529
|
+
// data: any, // Present on success
|
|
530
|
+
// error?: string // Present on failure
|
|
531
|
+
// }
|
|
532
|
+
// }
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
> **Important**: The `o-server` translates the internal `response.result.success` / `response.result.data` / `response.result.error` structure into the flat HTTP `{ success, data }` / `{ success, error }` format. When writing code that runs inside the node ecosystem (not behind o-server), always use `response.result.success`, `response.result.data`, and `response.result.error`.
|
|
536
|
+
|
|
537
|
+
## Error Handling
|
|
538
|
+
|
|
539
|
+
`o-server` provides consistent error responses:
|
|
540
|
+
|
|
445
541
|
**Common error codes:**
|
|
446
542
|
- `INVALID_PARAMS` - Missing or invalid request parameters
|
|
447
543
|
- `NODE_NOT_FOUND` - Target node address not found
|
|
@@ -540,8 +636,9 @@ const config: ServerConfig = {
|
|
|
540
636
|
cors: {
|
|
541
637
|
origin: 'https://example.com'
|
|
542
638
|
},
|
|
543
|
-
|
|
544
|
-
|
|
639
|
+
jwtAuth: {
|
|
640
|
+
method: 'secret',
|
|
641
|
+
secret: process.env.JWT_SECRET!,
|
|
545
642
|
},
|
|
546
643
|
debug: true
|
|
547
644
|
};
|
|
@@ -568,13 +665,15 @@ LOG_LEVEL=info
|
|
|
568
665
|
# Dockerfile
|
|
569
666
|
FROM node:20-alpine
|
|
570
667
|
|
|
668
|
+
RUN corepack enable && corepack prepare pnpm@latest --activate
|
|
669
|
+
|
|
571
670
|
WORKDIR /app
|
|
572
671
|
|
|
573
|
-
COPY package
|
|
574
|
-
RUN
|
|
672
|
+
COPY package.json pnpm-lock.yaml ./
|
|
673
|
+
RUN pnpm install --frozen-lockfile --prod
|
|
575
674
|
|
|
576
675
|
COPY . .
|
|
577
|
-
RUN
|
|
676
|
+
RUN pnpm run build
|
|
578
677
|
|
|
579
678
|
EXPOSE 8080
|
|
580
679
|
|
|
@@ -686,13 +785,13 @@ Complete examples:
|
|
|
686
785
|
const server = oServer({ node: myNode, port: 3000 });
|
|
687
786
|
await server.start();
|
|
688
787
|
|
|
689
|
-
// With authentication
|
|
788
|
+
// With JWT authentication
|
|
690
789
|
const server = oServer({
|
|
691
790
|
node: myNode,
|
|
692
791
|
port: 3000,
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
792
|
+
jwtAuth: {
|
|
793
|
+
method: 'secret',
|
|
794
|
+
secret: process.env.JWT_SECRET!,
|
|
696
795
|
}
|
|
697
796
|
});
|
|
698
797
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"o-server.d.ts","sourceRoot":"","sources":["../../src/o-server.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,YAAY,EACZ,cAAc,EACf,MAAM,yCAAyC,CAAC;AAiBjD,wBAAgB,OAAO,CAAC,MAAM,EAAE,YAAY,GAAG,cAAc,
|
|
1
|
+
{"version":3,"file":"o-server.d.ts","sourceRoot":"","sources":["../../src/o-server.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,YAAY,EACZ,cAAc,EACf,MAAM,yCAAyC,CAAC;AAiBjD,wBAAgB,OAAO,CAAC,MAAM,EAAE,YAAY,GAAG,cAAc,CAoW5D"}
|
package/dist/src/o-server.js
CHANGED
|
@@ -4,13 +4,57 @@ import { errorHandler } from './middleware/error-handler.js';
|
|
|
4
4
|
import { authMiddleware } from './middleware/auth.js';
|
|
5
5
|
import { createJwtMiddleware } from './middleware/jwt-auth.js';
|
|
6
6
|
import { ServerLogger } from './utils/logger.js';
|
|
7
|
-
import { oAddress } from '@olane/o-core';
|
|
7
|
+
import { oAddress, oRequestContext } from '@olane/o-core';
|
|
8
8
|
import { validateAddress, validateMethod, sanitizeParams, validateRequest, useRequestSchema, streamRequestSchema, } from './validation/index.js';
|
|
9
9
|
export function oServer(config) {
|
|
10
10
|
const { node, port = 3000, basePath = '/api/v1', cors: corsConfig, authenticate, jwtAuth, debug = false, } = config;
|
|
11
11
|
const app = express();
|
|
12
12
|
const logger = new ServerLogger(debug);
|
|
13
13
|
let server = null;
|
|
14
|
+
/**
|
|
15
|
+
* Build auth context from a JWT-verified Express request.
|
|
16
|
+
* Returns undefined if no JWT is present (e.g. JWT auth is disabled).
|
|
17
|
+
*/
|
|
18
|
+
function buildAuthFromRequest(req) {
|
|
19
|
+
if (!req.jwt)
|
|
20
|
+
return undefined;
|
|
21
|
+
// Extract raw token from Authorization header
|
|
22
|
+
const authHeader = req.headers.authorization;
|
|
23
|
+
const token = authHeader?.startsWith('Bearer ')
|
|
24
|
+
? authHeader.slice(7)
|
|
25
|
+
: '';
|
|
26
|
+
return {
|
|
27
|
+
token,
|
|
28
|
+
claims: req.jwt,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Remove any client-injected _auth from params (anti-spoofing).
|
|
33
|
+
* Server-verified auth is injected separately.
|
|
34
|
+
*/
|
|
35
|
+
function stripClientAuth(params) {
|
|
36
|
+
if (!params || typeof params !== 'object')
|
|
37
|
+
return params;
|
|
38
|
+
const { _auth, ...rest } = params;
|
|
39
|
+
return rest;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Deep-strip _auth from response data before sending to HTTP client.
|
|
43
|
+
* Prevents leaking tokens in response payloads.
|
|
44
|
+
*/
|
|
45
|
+
function stripAuthFromResponse(obj) {
|
|
46
|
+
if (obj === null || obj === undefined || typeof obj !== 'object')
|
|
47
|
+
return obj;
|
|
48
|
+
if (Array.isArray(obj))
|
|
49
|
+
return obj.map(stripAuthFromResponse);
|
|
50
|
+
const result = {};
|
|
51
|
+
for (const key of Object.keys(obj)) {
|
|
52
|
+
if (key === '_auth')
|
|
53
|
+
continue;
|
|
54
|
+
result[key] = stripAuthFromResponse(obj[key]);
|
|
55
|
+
}
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
14
58
|
// Middleware
|
|
15
59
|
app.use(express.json());
|
|
16
60
|
if (corsConfig) {
|
|
@@ -53,18 +97,25 @@ export function oServer(config) {
|
|
|
53
97
|
validateAddress(addressStr);
|
|
54
98
|
// Validate method
|
|
55
99
|
validateMethod(method);
|
|
100
|
+
// Strip client-injected _auth before sanitization (anti-spoofing)
|
|
101
|
+
const strippedParams = stripClientAuth(params);
|
|
56
102
|
// Sanitize params
|
|
57
|
-
const sanitizedParams = sanitizeParams(
|
|
103
|
+
const sanitizedParams = sanitizeParams(strippedParams);
|
|
104
|
+
// Build server-verified auth from JWT
|
|
105
|
+
const auth = buildAuthFromRequest(req);
|
|
106
|
+
// Inject _auth into params if JWT is present
|
|
107
|
+
const finalParams = auth
|
|
108
|
+
? { ...sanitizedParams, _auth: auth }
|
|
109
|
+
: sanitizedParams;
|
|
58
110
|
logger.debugLog(`Calling use with address ${addressStr}, method: ${method}`);
|
|
59
111
|
const address = new oAddress(addressStr);
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
params:
|
|
63
|
-
id
|
|
64
|
-
});
|
|
112
|
+
// Wrap in request context for AsyncLocalStorage propagation
|
|
113
|
+
const result = await (auth
|
|
114
|
+
? oRequestContext.run({ auth }, () => node.use(address, { method, params: finalParams, id }))
|
|
115
|
+
: node.use(address, { method, params: finalParams, id }));
|
|
65
116
|
const response = {
|
|
66
117
|
success: true,
|
|
67
|
-
data: result.result,
|
|
118
|
+
data: stripAuthFromResponse(result.result),
|
|
68
119
|
};
|
|
69
120
|
res.json(response);
|
|
70
121
|
}
|
|
@@ -84,17 +135,25 @@ export function oServer(config) {
|
|
|
84
135
|
validateAddress(addressStr);
|
|
85
136
|
// Validate method
|
|
86
137
|
validateMethod(method);
|
|
138
|
+
// Strip client-injected _auth before sanitization (anti-spoofing)
|
|
139
|
+
const strippedParams = stripClientAuth(params);
|
|
87
140
|
// Sanitize params
|
|
88
|
-
const sanitizedParams = sanitizeParams(
|
|
141
|
+
const sanitizedParams = sanitizeParams(strippedParams);
|
|
142
|
+
// Build server-verified auth from JWT
|
|
143
|
+
const auth = buildAuthFromRequest(req);
|
|
144
|
+
// Inject _auth into params if JWT is present
|
|
145
|
+
const finalParams = auth
|
|
146
|
+
? { ...sanitizedParams, _auth: auth }
|
|
147
|
+
: sanitizedParams;
|
|
89
148
|
logger.debugLog(`Calling method ${method} on ${addressParam} with params:`, sanitizedParams);
|
|
90
149
|
const address = new oAddress(addressStr);
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
params:
|
|
94
|
-
|
|
150
|
+
// Wrap in request context for AsyncLocalStorage propagation
|
|
151
|
+
const result = await (auth
|
|
152
|
+
? oRequestContext.run({ auth }, () => node.use(address, { method, params: finalParams }))
|
|
153
|
+
: node.use(address, { method, params: finalParams }));
|
|
95
154
|
const response = {
|
|
96
155
|
success: true,
|
|
97
|
-
data: result.result,
|
|
156
|
+
data: stripAuthFromResponse(result.result),
|
|
98
157
|
};
|
|
99
158
|
res.json(response);
|
|
100
159
|
}
|
|
@@ -112,8 +171,16 @@ export function oServer(config) {
|
|
|
112
171
|
validateAddress(addressStr);
|
|
113
172
|
// Validate method
|
|
114
173
|
validateMethod(method);
|
|
174
|
+
// Strip client-injected _auth before sanitization (anti-spoofing)
|
|
175
|
+
const strippedParams = stripClientAuth(params);
|
|
115
176
|
// Sanitize params
|
|
116
|
-
const sanitizedParams = sanitizeParams(
|
|
177
|
+
const sanitizedParams = sanitizeParams(strippedParams);
|
|
178
|
+
// Build server-verified auth from JWT
|
|
179
|
+
const auth = buildAuthFromRequest(req);
|
|
180
|
+
// Inject _auth into params if JWT is present
|
|
181
|
+
const finalParams = auth
|
|
182
|
+
? { ...sanitizedParams, _auth: auth }
|
|
183
|
+
: sanitizedParams;
|
|
117
184
|
logger.debugLog(`Streaming use call to ${addressStr}, method: ${method}`);
|
|
118
185
|
// Set headers for Server-Sent Events
|
|
119
186
|
res.setHeader('Content-Type', 'text/event-stream');
|
|
@@ -123,13 +190,12 @@ export function oServer(config) {
|
|
|
123
190
|
try {
|
|
124
191
|
// TODO: Implement actual streaming support when available
|
|
125
192
|
// For now, execute and return result
|
|
126
|
-
const result = await
|
|
127
|
-
method,
|
|
128
|
-
params:
|
|
129
|
-
});
|
|
193
|
+
const result = await (auth
|
|
194
|
+
? oRequestContext.run({ auth }, () => node.use(address, { method, params: finalParams }))
|
|
195
|
+
: node.use(address, { method, params: finalParams }));
|
|
130
196
|
res.write(`data: ${JSON.stringify({
|
|
131
197
|
type: 'complete',
|
|
132
|
-
result: result.result,
|
|
198
|
+
result: stripAuthFromResponse(result.result),
|
|
133
199
|
})}\n\n`);
|
|
134
200
|
res.end();
|
|
135
201
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@olane/o-server",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/src/index.js",
|
|
6
6
|
"types": "dist/src/index.d.ts",
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
"typescript": "5.4.5"
|
|
62
62
|
},
|
|
63
63
|
"dependencies": {
|
|
64
|
-
"@olane/o-core": "0.8.
|
|
64
|
+
"@olane/o-core": "0.8.5",
|
|
65
65
|
"cors": "^2.8.5",
|
|
66
66
|
"debug": "^4.4.1",
|
|
67
67
|
"dotenv": "^16.5.0",
|
|
@@ -69,5 +69,5 @@
|
|
|
69
69
|
"jsonwebtoken": "^9.0.3",
|
|
70
70
|
"zod": "^3.25.76"
|
|
71
71
|
},
|
|
72
|
-
"gitHead": "
|
|
72
|
+
"gitHead": "e88f1e55dcc92d9a410d28200e4220697116f82f"
|
|
73
73
|
}
|