@waiaas/daemon 2.4.0 → 2.5.0-rc.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.
- package/dist/api/routes/admin.d.ts.map +1 -1
- package/dist/api/routes/admin.js +146 -233
- package/dist/api/routes/admin.js.map +1 -1
- package/dist/api/routes/connect-info.d.ts +2 -0
- package/dist/api/routes/connect-info.d.ts.map +1 -1
- package/dist/api/routes/connect-info.js +31 -19
- package/dist/api/routes/connect-info.js.map +1 -1
- package/dist/api/routes/openapi-schemas.d.ts +24 -0
- package/dist/api/routes/openapi-schemas.d.ts.map +1 -1
- package/dist/api/routes/openapi-schemas.js +10 -0
- package/dist/api/routes/openapi-schemas.js.map +1 -1
- package/dist/api/routes/policies.d.ts +2 -1
- package/dist/api/routes/policies.d.ts.map +1 -1
- package/dist/api/routes/policies.js +24 -10
- package/dist/api/routes/policies.js.map +1 -1
- package/dist/api/routes/sessions.d.ts.map +1 -1
- package/dist/api/routes/sessions.js +3 -0
- package/dist/api/routes/sessions.js.map +1 -1
- package/dist/api/server.d.ts.map +1 -1
- package/dist/api/server.js +47 -2
- package/dist/api/server.js.map +1 -1
- package/dist/infrastructure/database/migrate.d.ts +1 -1
- package/dist/infrastructure/database/migrate.d.ts.map +1 -1
- package/dist/infrastructure/database/migrate.js +14 -3
- package/dist/infrastructure/database/migrate.js.map +1 -1
- package/dist/infrastructure/database/schema.d.ts +17 -0
- package/dist/infrastructure/database/schema.d.ts.map +1 -1
- package/dist/infrastructure/database/schema.js +1 -0
- package/dist/infrastructure/database/schema.js.map +1 -1
- package/dist/lifecycle/daemon.d.ts.map +1 -1
- package/dist/lifecycle/daemon.js +36 -14
- package/dist/lifecycle/daemon.js.map +1 -1
- package/package.json +4 -4
- package/public/admin/assets/index-BLLOYSZp.js +1 -0
- package/public/admin/index.html +1 -1
- package/public/admin/assets/index-i7xhksGh.js +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"admin.d.ts","sourceRoot":"","sources":["../../../src/api/routes/admin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EAAE,WAAW,EAAkB,MAAM,mBAAmB,CAAC;AAEhE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACxE,OAAO,KAAK,EAAE,QAAQ,IAAI,cAAc,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"admin.d.ts","sourceRoot":"","sources":["../../../src/api/routes/admin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EAAE,WAAW,EAAkB,MAAM,mBAAmB,CAAC;AAEhE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACxE,OAAO,KAAK,EAAE,QAAQ,IAAI,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAGjE,OAAO,KAAK,EAAyE,YAAY,EAAE,iBAAiB,EAAgB,MAAM,cAAc,CAAC;AAEzJ,OAAO,KAAK,EAAE,gBAAgB,EAAc,MAAM,gDAAgD,CAAC;AAInG,OAAO,KAAK,KAAK,MAAM,MAAM,yCAAyC,CAAC;AACvE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,6CAA6C,CAAC;AACvF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uCAAuC,CAAC;AAC1E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wCAAwC,CAAC;AAE9E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sCAAsC,CAAC;AAExE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8CAA8C,CAAC;AAChF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,yDAAyD,CAAC;AACtG,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uCAAuC,CAAC;AAC/E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uDAAuD,CAAC;AA+BjG,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,qBAAqB,CAAC,OAAO,MAAM,CAAC,CAAC;IACzC,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IACpC,kBAAkB,EAAE,MAAM,eAAe,CAAC;IAC1C,kBAAkB,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAClE,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;IACtC,eAAe,CAAC,EAAE,MAAM,IAAI,CAAC;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,mBAAmB,CAAC,EAAE,mBAAmB,CAAC;IAC1C,kBAAkB,CAAC,EAAE,YAAY,CAAC,eAAe,CAAC,CAAC;IACnD,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,iBAAiB,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACpD,WAAW,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IACjC,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,WAAW,CAAC,EAAE,YAAY,CAAC;IAC3B,YAAY,CAAC,EAAE;QAAE,yBAAyB,EAAE,OAAO,CAAC;QAAC,wBAAwB,EAAE,MAAM,CAAA;KAAE,CAAC;IACxF,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,sBAAsB,CAAC,EAAE,sBAAsB,CAAC;IAChD,gBAAgB,CAAC,EAAE,iBAAiB,CAAC;IACrC,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,mBAAmB,CAAC,EAAE,mBAAmB,GAAG,IAAI,CAAC;CAClD;AAkhBD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,cAAc,GAAG,WAAW,CA+xC7D"}
|
package/dist/api/routes/admin.js
CHANGED
|
@@ -26,10 +26,8 @@
|
|
|
26
26
|
* @see docs/36-killswitch-evm-freeze.md
|
|
27
27
|
*/
|
|
28
28
|
import { OpenAPIHono, createRoute, z } from '@hono/zod-openapi';
|
|
29
|
-
import { sql, desc, eq, and, count as drizzleCount } from 'drizzle-orm';
|
|
29
|
+
import { sql, desc, eq, and, isNull, gt, count as drizzleCount } from 'drizzle-orm';
|
|
30
30
|
import { createHash } from 'node:crypto';
|
|
31
|
-
import { writeFile, rename, mkdir } from 'node:fs/promises';
|
|
32
|
-
import { join, dirname } from 'node:path';
|
|
33
31
|
import { WAIaaSError, getDefaultNetwork, getNetworksForEnvironment } from '@waiaas/core';
|
|
34
32
|
import { CurrencyCodeSchema, formatRatePreview } from '@waiaas/core';
|
|
35
33
|
import { wallets, sessions, sessionWallets, notificationLogs, policies, transactions } from '../../infrastructure/database/schema.js';
|
|
@@ -37,7 +35,7 @@ import { generateId } from '../../infrastructure/database/id.js';
|
|
|
37
35
|
import { buildConnectInfoPrompt } from './connect-info.js';
|
|
38
36
|
import { getSettingDefinition } from '../../infrastructure/settings/index.js';
|
|
39
37
|
import { resolveRpcUrl } from '../../infrastructure/adapter-pool.js';
|
|
40
|
-
import { AdminStatusResponseSchema, KillSwitchResponseSchema, KillSwitchActivateResponseSchema, KillSwitchEscalateResponseSchema, RecoverResponseSchema, KillSwitchRecoverRequestSchema, ShutdownResponseSchema, RotateSecretResponseSchema, NotificationStatusResponseSchema, NotificationTestRequestSchema, NotificationTestResponseSchema, NotificationLogResponseSchema, SettingsResponseSchema, SettingsUpdateRequestSchema, SettingsUpdateResponseSchema, TestRpcRequestSchema, TestRpcResponseSchema, OracleStatusResponseSchema, AgentPromptRequestSchema, AgentPromptResponseSchema, buildErrorResponses, openApiValidationHook, } from './openapi-schemas.js';
|
|
38
|
+
import { AdminStatusResponseSchema, KillSwitchResponseSchema, KillSwitchActivateResponseSchema, KillSwitchEscalateResponseSchema, RecoverResponseSchema, KillSwitchRecoverRequestSchema, ShutdownResponseSchema, RotateSecretResponseSchema, NotificationStatusResponseSchema, NotificationTestRequestSchema, NotificationTestResponseSchema, NotificationLogResponseSchema, SettingsResponseSchema, SettingsUpdateRequestSchema, SettingsUpdateResponseSchema, TestRpcRequestSchema, TestRpcResponseSchema, OracleStatusResponseSchema, AgentPromptRequestSchema, AgentPromptResponseSchema, SessionReissueResponseSchema, buildErrorResponses, openApiValidationHook, } from './openapi-schemas.js';
|
|
41
39
|
// ---------------------------------------------------------------------------
|
|
42
40
|
// Route definitions
|
|
43
41
|
// ---------------------------------------------------------------------------
|
|
@@ -435,82 +433,6 @@ const adminWalletBalanceRoute = createRoute({
|
|
|
435
433
|
},
|
|
436
434
|
});
|
|
437
435
|
// ---------------------------------------------------------------------------
|
|
438
|
-
// Bulk session/MCP token route definitions
|
|
439
|
-
// ---------------------------------------------------------------------------
|
|
440
|
-
const BulkResultItemSchema = z.object({
|
|
441
|
-
walletId: z.string().uuid(),
|
|
442
|
-
walletName: z.string().nullable(),
|
|
443
|
-
sessionId: z.string().uuid().optional(),
|
|
444
|
-
token: z.string().optional(),
|
|
445
|
-
tokenPath: z.string().optional(),
|
|
446
|
-
error: z.string().optional(),
|
|
447
|
-
});
|
|
448
|
-
const adminBulkSessionsRoute = createRoute({
|
|
449
|
-
method: 'post',
|
|
450
|
-
path: '/admin/sessions/bulk',
|
|
451
|
-
tags: ['Admin'],
|
|
452
|
-
summary: 'Create sessions for multiple wallets at once',
|
|
453
|
-
request: {
|
|
454
|
-
body: {
|
|
455
|
-
content: {
|
|
456
|
-
'application/json': {
|
|
457
|
-
schema: z.object({
|
|
458
|
-
walletIds: z.array(z.string().uuid()).min(1).max(50),
|
|
459
|
-
ttl: z.number().int().min(300).optional(),
|
|
460
|
-
}),
|
|
461
|
-
},
|
|
462
|
-
},
|
|
463
|
-
},
|
|
464
|
-
},
|
|
465
|
-
responses: {
|
|
466
|
-
200: {
|
|
467
|
-
description: 'Bulk session creation results',
|
|
468
|
-
content: {
|
|
469
|
-
'application/json': {
|
|
470
|
-
schema: z.object({
|
|
471
|
-
results: z.array(BulkResultItemSchema),
|
|
472
|
-
created: z.number().int(),
|
|
473
|
-
failed: z.number().int(),
|
|
474
|
-
}),
|
|
475
|
-
},
|
|
476
|
-
},
|
|
477
|
-
},
|
|
478
|
-
},
|
|
479
|
-
});
|
|
480
|
-
const adminBulkMcpTokensRoute = createRoute({
|
|
481
|
-
method: 'post',
|
|
482
|
-
path: '/admin/mcp/tokens/bulk',
|
|
483
|
-
tags: ['Admin'],
|
|
484
|
-
summary: 'Create MCP tokens for multiple wallets at once',
|
|
485
|
-
request: {
|
|
486
|
-
body: {
|
|
487
|
-
content: {
|
|
488
|
-
'application/json': {
|
|
489
|
-
schema: z.object({
|
|
490
|
-
walletIds: z.array(z.string().uuid()).min(1).max(50),
|
|
491
|
-
ttl: z.number().int().min(300).optional(),
|
|
492
|
-
}),
|
|
493
|
-
},
|
|
494
|
-
},
|
|
495
|
-
},
|
|
496
|
-
},
|
|
497
|
-
responses: {
|
|
498
|
-
200: {
|
|
499
|
-
description: 'Bulk MCP token creation results',
|
|
500
|
-
content: {
|
|
501
|
-
'application/json': {
|
|
502
|
-
schema: z.object({
|
|
503
|
-
results: z.array(BulkResultItemSchema),
|
|
504
|
-
created: z.number().int(),
|
|
505
|
-
failed: z.number().int(),
|
|
506
|
-
claudeDesktopConfig: z.record(z.unknown()),
|
|
507
|
-
}),
|
|
508
|
-
},
|
|
509
|
-
},
|
|
510
|
-
},
|
|
511
|
-
},
|
|
512
|
-
});
|
|
513
|
-
// ---------------------------------------------------------------------------
|
|
514
436
|
// Telegram Users route definitions
|
|
515
437
|
// ---------------------------------------------------------------------------
|
|
516
438
|
const TelegramUserSchema = z.object({
|
|
@@ -1393,132 +1315,6 @@ export function adminRoutes(deps) {
|
|
|
1393
1315
|
return c.json({ success: true }, 200);
|
|
1394
1316
|
});
|
|
1395
1317
|
// ---------------------------------------------------------------------------
|
|
1396
|
-
// POST /admin/sessions/bulk — Bulk create sessions
|
|
1397
|
-
// ---------------------------------------------------------------------------
|
|
1398
|
-
router.openapi(adminBulkSessionsRoute, async (c) => {
|
|
1399
|
-
if (!deps.jwtSecretManager || !deps.daemonConfig) {
|
|
1400
|
-
throw new WAIaaSError('ADAPTER_NOT_AVAILABLE', { message: 'JWT signing not available' });
|
|
1401
|
-
}
|
|
1402
|
-
const { walletIds, ttl: reqTtl } = c.req.valid('json');
|
|
1403
|
-
const config = deps.daemonConfig;
|
|
1404
|
-
const nowSec = Math.floor(Date.now() / 1000);
|
|
1405
|
-
const ttl = reqTtl ?? config.security.session_ttl;
|
|
1406
|
-
const expiresAt = nowSec + ttl;
|
|
1407
|
-
const absoluteExpiresAt = nowSec + config.security.session_absolute_lifetime;
|
|
1408
|
-
const results = [];
|
|
1409
|
-
for (const walletId of walletIds) {
|
|
1410
|
-
try {
|
|
1411
|
-
const wallet = deps.db.select().from(wallets).where(eq(wallets.id, walletId)).get();
|
|
1412
|
-
if (!wallet) {
|
|
1413
|
-
results.push({ walletId, walletName: null, error: 'Wallet not found' });
|
|
1414
|
-
continue;
|
|
1415
|
-
}
|
|
1416
|
-
if (wallet.status === 'TERMINATED') {
|
|
1417
|
-
results.push({ walletId, walletName: wallet.name, error: 'Wallet terminated' });
|
|
1418
|
-
continue;
|
|
1419
|
-
}
|
|
1420
|
-
const sessionId = generateId();
|
|
1421
|
-
const jwtPayload = { sub: sessionId, wlt: walletId, iat: nowSec, exp: expiresAt };
|
|
1422
|
-
const token = await deps.jwtSecretManager.signToken(jwtPayload);
|
|
1423
|
-
const tokenHash = createHash('sha256').update(token).digest('hex');
|
|
1424
|
-
deps.db.insert(sessions).values({
|
|
1425
|
-
id: sessionId, tokenHash,
|
|
1426
|
-
expiresAt: new Date(expiresAt * 1000),
|
|
1427
|
-
absoluteExpiresAt: new Date(absoluteExpiresAt * 1000),
|
|
1428
|
-
createdAt: new Date(nowSec * 1000),
|
|
1429
|
-
renewalCount: 0, maxRenewals: config.security.session_max_renewals,
|
|
1430
|
-
constraints: null, source: 'api',
|
|
1431
|
-
}).run();
|
|
1432
|
-
deps.db.insert(sessionWallets).values({
|
|
1433
|
-
sessionId, walletId, isDefault: true, createdAt: new Date(nowSec * 1000),
|
|
1434
|
-
}).run();
|
|
1435
|
-
void deps.notificationService?.notify('SESSION_CREATED', walletId, { sessionId });
|
|
1436
|
-
results.push({ walletId, walletName: wallet.name, sessionId, token });
|
|
1437
|
-
}
|
|
1438
|
-
catch (err) {
|
|
1439
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
1440
|
-
results.push({ walletId, walletName: null, error: msg });
|
|
1441
|
-
}
|
|
1442
|
-
}
|
|
1443
|
-
const created = results.filter((r) => !r.error).length;
|
|
1444
|
-
return c.json({ results, created, failed: results.length - created }, 200);
|
|
1445
|
-
});
|
|
1446
|
-
// ---------------------------------------------------------------------------
|
|
1447
|
-
// POST /admin/mcp/tokens/bulk — Bulk create MCP tokens
|
|
1448
|
-
// ---------------------------------------------------------------------------
|
|
1449
|
-
router.openapi(adminBulkMcpTokensRoute, async (c) => {
|
|
1450
|
-
if (!deps.jwtSecretManager || !deps.daemonConfig || !deps.dataDir) {
|
|
1451
|
-
throw new WAIaaSError('ADAPTER_NOT_AVAILABLE', { message: 'JWT signing not available' });
|
|
1452
|
-
}
|
|
1453
|
-
const { walletIds, ttl: reqTtl } = c.req.valid('json');
|
|
1454
|
-
const config = deps.daemonConfig;
|
|
1455
|
-
const nowSec = Math.floor(Date.now() / 1000);
|
|
1456
|
-
const ttl = reqTtl ?? config.security.session_ttl;
|
|
1457
|
-
const expiresAt = nowSec + ttl;
|
|
1458
|
-
const absoluteExpiresAt = nowSec + config.security.session_absolute_lifetime;
|
|
1459
|
-
const baseUrl = `http://127.0.0.1:${config.daemon.port}`;
|
|
1460
|
-
const results = [];
|
|
1461
|
-
const claudeDesktopConfig = {};
|
|
1462
|
-
const toSlug = (name) => {
|
|
1463
|
-
const slug = name.toLowerCase().replace(/[^a-z0-9-]/g, '-').replace(/-{2,}/g, '-').replace(/^-|-$/g, '');
|
|
1464
|
-
return slug || 'wallet';
|
|
1465
|
-
};
|
|
1466
|
-
for (const walletId of walletIds) {
|
|
1467
|
-
try {
|
|
1468
|
-
const wallet = deps.db.select().from(wallets).where(eq(wallets.id, walletId)).get();
|
|
1469
|
-
if (!wallet) {
|
|
1470
|
-
results.push({ walletId, walletName: null, error: 'Wallet not found' });
|
|
1471
|
-
continue;
|
|
1472
|
-
}
|
|
1473
|
-
if (wallet.status === 'TERMINATED') {
|
|
1474
|
-
results.push({ walletId, walletName: wallet.name, error: 'Wallet terminated' });
|
|
1475
|
-
continue;
|
|
1476
|
-
}
|
|
1477
|
-
const sessionId = generateId();
|
|
1478
|
-
const jwtPayload = { sub: sessionId, wlt: walletId, iat: nowSec, exp: expiresAt };
|
|
1479
|
-
const token = await deps.jwtSecretManager.signToken(jwtPayload);
|
|
1480
|
-
const tokenHash = createHash('sha256').update(token).digest('hex');
|
|
1481
|
-
deps.db.insert(sessions).values({
|
|
1482
|
-
id: sessionId, tokenHash,
|
|
1483
|
-
expiresAt: new Date(expiresAt * 1000),
|
|
1484
|
-
absoluteExpiresAt: new Date(absoluteExpiresAt * 1000),
|
|
1485
|
-
createdAt: new Date(nowSec * 1000),
|
|
1486
|
-
renewalCount: 0, maxRenewals: config.security.session_max_renewals,
|
|
1487
|
-
constraints: null, source: 'mcp',
|
|
1488
|
-
}).run();
|
|
1489
|
-
deps.db.insert(sessionWallets).values({
|
|
1490
|
-
sessionId, walletId, isDefault: true, createdAt: new Date(nowSec * 1000),
|
|
1491
|
-
}).run();
|
|
1492
|
-
// Write token file
|
|
1493
|
-
const tokenPath = join(deps.dataDir, 'mcp-tokens', walletId);
|
|
1494
|
-
const tmpPath = `${tokenPath}.tmp`;
|
|
1495
|
-
await mkdir(dirname(tokenPath), { recursive: true });
|
|
1496
|
-
await writeFile(tmpPath, token, 'utf-8');
|
|
1497
|
-
await rename(tmpPath, tokenPath);
|
|
1498
|
-
// Claude Desktop config snippet
|
|
1499
|
-
const slug = toSlug(wallet.name);
|
|
1500
|
-
claudeDesktopConfig[`waiaas-${slug}`] = {
|
|
1501
|
-
command: 'npx',
|
|
1502
|
-
args: ['@waiaas/mcp'],
|
|
1503
|
-
env: {
|
|
1504
|
-
WAIAAS_DATA_DIR: deps.dataDir,
|
|
1505
|
-
WAIAAS_BASE_URL: baseUrl,
|
|
1506
|
-
WAIAAS_WALLET_ID: walletId,
|
|
1507
|
-
WAIAAS_WALLET_NAME: wallet.name,
|
|
1508
|
-
},
|
|
1509
|
-
};
|
|
1510
|
-
void deps.notificationService?.notify('SESSION_CREATED', walletId, { sessionId });
|
|
1511
|
-
results.push({ walletId, walletName: wallet.name, sessionId, tokenPath });
|
|
1512
|
-
}
|
|
1513
|
-
catch (err) {
|
|
1514
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
1515
|
-
results.push({ walletId, walletName: null, error: msg });
|
|
1516
|
-
}
|
|
1517
|
-
}
|
|
1518
|
-
const created = results.filter((r) => !r.error).length;
|
|
1519
|
-
return c.json({ results, created, failed: results.length - created, claudeDesktopConfig }, 200);
|
|
1520
|
-
});
|
|
1521
|
-
// ---------------------------------------------------------------------------
|
|
1522
1318
|
// POST /admin/agent-prompt — Generate agent connection prompt
|
|
1523
1319
|
// ---------------------------------------------------------------------------
|
|
1524
1320
|
const agentPromptRoute = createRoute({
|
|
@@ -1566,36 +1362,83 @@ export function adminRoutes(deps) {
|
|
|
1566
1362
|
.map((w) => ({ id: w.id, name: w.name, chain: w.chain, environment: w.environment, publicKey: w.publicKey, defaultNetwork: w.defaultNetwork }));
|
|
1567
1363
|
}
|
|
1568
1364
|
if (targetWallets.length === 0) {
|
|
1569
|
-
return c.json({ prompt: '', walletCount: 0, sessionsCreated: 0, expiresAt }, 201);
|
|
1365
|
+
return c.json({ prompt: '', walletCount: 0, sessionsCreated: 0, sessionReused: false, expiresAt }, 201);
|
|
1570
1366
|
}
|
|
1571
|
-
//
|
|
1367
|
+
// Try to reuse an existing valid session covering all target wallets
|
|
1572
1368
|
const defaultWallet = targetWallets[0];
|
|
1573
|
-
const
|
|
1574
|
-
const
|
|
1575
|
-
const
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1369
|
+
const targetWalletIds = targetWallets.map((w) => w.id);
|
|
1370
|
+
const minRemainingTtl = Math.max(Math.floor(ttl * 0.1), 3600); // 10% of TTL or 1 hour
|
|
1371
|
+
const minExpiresAt = new Date((nowSec + minRemainingTtl) * 1000);
|
|
1372
|
+
let sessionId;
|
|
1373
|
+
let sessionReused = false;
|
|
1374
|
+
let sessionsCreated = 1;
|
|
1375
|
+
let actualExpiresAt = expiresAt;
|
|
1376
|
+
// Find active sessions that cover all target wallets with sufficient TTL
|
|
1377
|
+
const candidateSessions = deps.db
|
|
1378
|
+
.select({
|
|
1379
|
+
id: sessions.id,
|
|
1380
|
+
expiresAt: sessions.expiresAt,
|
|
1381
|
+
})
|
|
1382
|
+
.from(sessions)
|
|
1383
|
+
.where(and(isNull(sessions.revokedAt), gt(sessions.expiresAt, minExpiresAt)))
|
|
1384
|
+
.all();
|
|
1385
|
+
let reusableSessionId = null;
|
|
1386
|
+
let reusableExpiresAt = 0;
|
|
1387
|
+
for (const candidate of candidateSessions) {
|
|
1388
|
+
// Count how many of our target wallets are linked to this session
|
|
1389
|
+
const linkedCount = deps.db
|
|
1390
|
+
.select({ cnt: drizzleCount() })
|
|
1391
|
+
.from(sessionWallets)
|
|
1392
|
+
.where(and(eq(sessionWallets.sessionId, candidate.id), sql `${sessionWallets.walletId} IN (${sql.join(targetWalletIds.map((id) => sql `${id}`), sql `, `)})`))
|
|
1393
|
+
.get();
|
|
1394
|
+
if (linkedCount && linkedCount.cnt === targetWalletIds.length) {
|
|
1395
|
+
reusableSessionId = candidate.id;
|
|
1396
|
+
reusableExpiresAt = candidate.expiresAt instanceof Date
|
|
1397
|
+
? Math.floor(candidate.expiresAt.getTime() / 1000)
|
|
1398
|
+
: candidate.expiresAt;
|
|
1399
|
+
break;
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
if (reusableSessionId) {
|
|
1403
|
+
// Reuse existing session — re-sign JWT with existing session ID
|
|
1404
|
+
sessionId = reusableSessionId;
|
|
1405
|
+
sessionReused = true;
|
|
1406
|
+
sessionsCreated = 0;
|
|
1407
|
+
actualExpiresAt = reusableExpiresAt;
|
|
1408
|
+
}
|
|
1409
|
+
else {
|
|
1410
|
+
// Create a new multi-wallet session
|
|
1411
|
+
sessionId = generateId();
|
|
1412
|
+
deps.db.insert(sessions).values({
|
|
1413
|
+
id: sessionId,
|
|
1414
|
+
tokenHash: '',
|
|
1415
|
+
expiresAt: new Date(expiresAt * 1000),
|
|
1416
|
+
absoluteExpiresAt: new Date(absoluteExpiresAt * 1000),
|
|
1595
1417
|
createdAt: new Date(nowSec * 1000),
|
|
1418
|
+
renewalCount: 0,
|
|
1419
|
+
maxRenewals: config.security.session_max_renewals,
|
|
1420
|
+
constraints: null,
|
|
1421
|
+
source: 'api',
|
|
1596
1422
|
}).run();
|
|
1423
|
+
// Insert N rows into session_wallets (first wallet is default)
|
|
1424
|
+
for (let i = 0; i < targetWallets.length; i++) {
|
|
1425
|
+
const w = targetWallets[i];
|
|
1426
|
+
deps.db.insert(sessionWallets).values({
|
|
1427
|
+
sessionId,
|
|
1428
|
+
walletId: w.id,
|
|
1429
|
+
isDefault: i === 0,
|
|
1430
|
+
createdAt: new Date(nowSec * 1000),
|
|
1431
|
+
}).run();
|
|
1432
|
+
}
|
|
1433
|
+
void deps.notificationService?.notify('SESSION_CREATED', defaultWallet.id, { sessionId });
|
|
1434
|
+
}
|
|
1435
|
+
// Sign JWT (new or re-signed for reused session)
|
|
1436
|
+
const jwtPayload = { sub: sessionId, wlt: defaultWallet.id, iat: nowSec, exp: actualExpiresAt };
|
|
1437
|
+
const token = await deps.jwtSecretManager.signToken(jwtPayload);
|
|
1438
|
+
if (!sessionReused) {
|
|
1439
|
+
const tokenHash = createHash('sha256').update(token).digest('hex');
|
|
1440
|
+
deps.db.update(sessions).set({ tokenHash }).where(eq(sessions.id, sessionId)).run();
|
|
1597
1441
|
}
|
|
1598
|
-
void deps.notificationService?.notify('SESSION_CREATED', defaultWallet.id, { sessionId });
|
|
1599
1442
|
// Query per-wallet policies for prompt builder
|
|
1600
1443
|
const promptWallets = targetWallets.map((w) => {
|
|
1601
1444
|
const walletPolicies = deps.db
|
|
@@ -1603,12 +1446,15 @@ export function adminRoutes(deps) {
|
|
|
1603
1446
|
.from(policies)
|
|
1604
1447
|
.where(and(eq(policies.walletId, w.id), eq(policies.enabled, true)))
|
|
1605
1448
|
.all();
|
|
1449
|
+
const networks = getNetworksForEnvironment(w.chain, w.environment);
|
|
1606
1450
|
return {
|
|
1451
|
+
id: w.id,
|
|
1607
1452
|
name: w.name,
|
|
1608
1453
|
chain: w.chain,
|
|
1609
1454
|
environment: w.environment,
|
|
1610
1455
|
address: w.publicKey,
|
|
1611
1456
|
defaultNetwork: w.defaultNetwork,
|
|
1457
|
+
networks: networks.map((n) => n),
|
|
1612
1458
|
policies: walletPolicies,
|
|
1613
1459
|
};
|
|
1614
1460
|
});
|
|
@@ -1653,10 +1499,77 @@ export function adminRoutes(deps) {
|
|
|
1653
1499
|
return c.json({
|
|
1654
1500
|
prompt: fullPrompt,
|
|
1655
1501
|
walletCount: targetWallets.length,
|
|
1656
|
-
sessionsCreated
|
|
1657
|
-
|
|
1502
|
+
sessionsCreated,
|
|
1503
|
+
sessionReused,
|
|
1504
|
+
expiresAt: actualExpiresAt,
|
|
1658
1505
|
}, 201);
|
|
1659
1506
|
});
|
|
1507
|
+
// ---------------------------------------------------------------------------
|
|
1508
|
+
// POST /admin/sessions/:id/reissue — Reissue session token
|
|
1509
|
+
// ---------------------------------------------------------------------------
|
|
1510
|
+
const sessionReissueRoute = createRoute({
|
|
1511
|
+
method: 'post',
|
|
1512
|
+
path: '/admin/sessions/{id}/reissue',
|
|
1513
|
+
tags: ['Admin'],
|
|
1514
|
+
summary: 'Reissue session token (re-sign JWT for existing session)',
|
|
1515
|
+
request: {
|
|
1516
|
+
params: z.object({ id: z.string().uuid() }),
|
|
1517
|
+
},
|
|
1518
|
+
responses: {
|
|
1519
|
+
200: {
|
|
1520
|
+
description: 'Token reissued',
|
|
1521
|
+
content: { 'application/json': { schema: SessionReissueResponseSchema } },
|
|
1522
|
+
},
|
|
1523
|
+
...buildErrorResponses(['SESSION_NOT_FOUND', 'SESSION_REVOKED']),
|
|
1524
|
+
},
|
|
1525
|
+
});
|
|
1526
|
+
router.openapi(sessionReissueRoute, async (c) => {
|
|
1527
|
+
if (!deps.jwtSecretManager) {
|
|
1528
|
+
throw new WAIaaSError('ADAPTER_NOT_AVAILABLE', { message: 'JWT signing not available' });
|
|
1529
|
+
}
|
|
1530
|
+
const { id: sessionId } = c.req.valid('param');
|
|
1531
|
+
const nowSec = Math.floor(Date.now() / 1000);
|
|
1532
|
+
// Find session
|
|
1533
|
+
const session = deps.db
|
|
1534
|
+
.select()
|
|
1535
|
+
.from(sessions)
|
|
1536
|
+
.where(eq(sessions.id, sessionId))
|
|
1537
|
+
.get();
|
|
1538
|
+
if (!session) {
|
|
1539
|
+
throw new WAIaaSError('SESSION_NOT_FOUND');
|
|
1540
|
+
}
|
|
1541
|
+
if (session.revokedAt) {
|
|
1542
|
+
throw new WAIaaSError('SESSION_REVOKED');
|
|
1543
|
+
}
|
|
1544
|
+
const expiresAtSec = session.expiresAt instanceof Date
|
|
1545
|
+
? Math.floor(session.expiresAt.getTime() / 1000)
|
|
1546
|
+
: session.expiresAt;
|
|
1547
|
+
if (expiresAtSec <= nowSec) {
|
|
1548
|
+
throw new WAIaaSError('SESSION_NOT_FOUND', { message: 'Session expired' });
|
|
1549
|
+
}
|
|
1550
|
+
// Get default wallet for JWT
|
|
1551
|
+
const defaultSw = deps.db
|
|
1552
|
+
.select({ walletId: sessionWallets.walletId })
|
|
1553
|
+
.from(sessionWallets)
|
|
1554
|
+
.where(and(eq(sessionWallets.sessionId, sessionId), eq(sessionWallets.isDefault, true)))
|
|
1555
|
+
.get();
|
|
1556
|
+
const defaultWalletId = defaultSw?.walletId ?? '';
|
|
1557
|
+
// Re-sign JWT
|
|
1558
|
+
const jwtPayload = { sub: sessionId, wlt: defaultWalletId, iat: nowSec, exp: expiresAtSec };
|
|
1559
|
+
const token = await deps.jwtSecretManager.signToken(jwtPayload);
|
|
1560
|
+
// Increment token_issued_count
|
|
1561
|
+
const newCount = (session.tokenIssuedCount ?? 1) + 1;
|
|
1562
|
+
deps.db.update(sessions)
|
|
1563
|
+
.set({ tokenIssuedCount: newCount })
|
|
1564
|
+
.where(eq(sessions.id, sessionId))
|
|
1565
|
+
.run();
|
|
1566
|
+
return c.json({
|
|
1567
|
+
token,
|
|
1568
|
+
sessionId,
|
|
1569
|
+
tokenIssuedCount: newCount,
|
|
1570
|
+
expiresAt: expiresAtSec,
|
|
1571
|
+
}, 200);
|
|
1572
|
+
});
|
|
1660
1573
|
return router;
|
|
1661
1574
|
}
|
|
1662
1575
|
//# sourceMappingURL=admin.js.map
|