@essentialai/cogent-server 2.0.0
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/.env.example +68 -0
- package/CHANGELOG.md +16 -0
- package/Caddyfile +8 -0
- package/Dockerfile +46 -0
- package/LICENSE +190 -0
- package/README.md +89 -0
- package/config.json.example +16 -0
- package/dist/__tests__/helpers.d.ts +56 -0
- package/dist/__tests__/helpers.d.ts.map +1 -0
- package/dist/__tests__/helpers.js +138 -0
- package/dist/__tests__/helpers.js.map +1 -0
- package/dist/app.d.ts +38 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/app.js +60 -0
- package/dist/app.js.map +1 -0
- package/dist/config.d.ts +88 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +148 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +102 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware/auth.d.ts +15 -0
- package/dist/middleware/auth.d.ts.map +1 -0
- package/dist/middleware/auth.js +47 -0
- package/dist/middleware/auth.js.map +1 -0
- package/dist/middleware/error-handler.d.ts +14 -0
- package/dist/middleware/error-handler.d.ts.map +1 -0
- package/dist/middleware/error-handler.js +26 -0
- package/dist/middleware/error-handler.js.map +1 -0
- package/dist/middleware/not-found.d.ts +8 -0
- package/dist/middleware/not-found.d.ts.map +1 -0
- package/dist/middleware/not-found.js +12 -0
- package/dist/middleware/not-found.js.map +1 -0
- package/dist/middleware/request-logger.d.ts +17 -0
- package/dist/middleware/request-logger.d.ts.map +1 -0
- package/dist/middleware/request-logger.js +65 -0
- package/dist/middleware/request-logger.js.map +1 -0
- package/dist/middleware/ws-auth.d.ts +21 -0
- package/dist/middleware/ws-auth.d.ts.map +1 -0
- package/dist/middleware/ws-auth.js +59 -0
- package/dist/middleware/ws-auth.js.map +1 -0
- package/dist/routes/health.d.ts +11 -0
- package/dist/routes/health.d.ts.map +1 -0
- package/dist/routes/health.js +34 -0
- package/dist/routes/health.js.map +1 -0
- package/dist/routes/messages.d.ts +19 -0
- package/dist/routes/messages.d.ts.map +1 -0
- package/dist/routes/messages.js +154 -0
- package/dist/routes/messages.js.map +1 -0
- package/dist/routes/peers.d.ts +17 -0
- package/dist/routes/peers.d.ts.map +1 -0
- package/dist/routes/peers.js +169 -0
- package/dist/routes/peers.js.map +1 -0
- package/dist/routes/poll.d.ts +15 -0
- package/dist/routes/poll.d.ts.map +1 -0
- package/dist/routes/poll.js +97 -0
- package/dist/routes/poll.js.map +1 -0
- package/dist/routes/sessions.d.ts +14 -0
- package/dist/routes/sessions.d.ts.map +1 -0
- package/dist/routes/sessions.js +113 -0
- package/dist/routes/sessions.js.map +1 -0
- package/dist/routes/ui.d.ts +21 -0
- package/dist/routes/ui.d.ts.map +1 -0
- package/dist/routes/ui.js +173 -0
- package/dist/routes/ui.js.map +1 -0
- package/dist/routes/validation-hook.d.ts +18 -0
- package/dist/routes/validation-hook.d.ts.map +1 -0
- package/dist/routes/validation-hook.js +24 -0
- package/dist/routes/validation-hook.js.map +1 -0
- package/dist/services/auth-service.d.ts +48 -0
- package/dist/services/auth-service.d.ts.map +1 -0
- package/dist/services/auth-service.js +63 -0
- package/dist/services/auth-service.js.map +1 -0
- package/dist/services/connection-manager.d.ts +108 -0
- package/dist/services/connection-manager.d.ts.map +1 -0
- package/dist/services/connection-manager.js +216 -0
- package/dist/services/connection-manager.js.map +1 -0
- package/dist/services/message-queue.d.ts +56 -0
- package/dist/services/message-queue.d.ts.map +1 -0
- package/dist/services/message-queue.js +164 -0
- package/dist/services/message-queue.js.map +1 -0
- package/dist/services/peer-cleanup.d.ts +39 -0
- package/dist/services/peer-cleanup.d.ts.map +1 -0
- package/dist/services/peer-cleanup.js +96 -0
- package/dist/services/peer-cleanup.js.map +1 -0
- package/dist/services/session-cleanup.d.ts +44 -0
- package/dist/services/session-cleanup.d.ts.map +1 -0
- package/dist/services/session-cleanup.js +100 -0
- package/dist/services/session-cleanup.js.map +1 -0
- package/dist/services/session-store.d.ts +103 -0
- package/dist/services/session-store.d.ts.map +1 -0
- package/dist/services/session-store.js +292 -0
- package/dist/services/session-store.js.map +1 -0
- package/dist/services/stats-service.d.ts +48 -0
- package/dist/services/stats-service.d.ts.map +1 -0
- package/dist/services/stats-service.js +77 -0
- package/dist/services/stats-service.js.map +1 -0
- package/dist/types.d.ts +60 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/ui/components/Footer.d.ts +7 -0
- package/dist/ui/components/Footer.d.ts.map +1 -0
- package/dist/ui/components/Footer.js +17 -0
- package/dist/ui/components/Footer.js.map +1 -0
- package/dist/ui/components/Layout.d.ts +13 -0
- package/dist/ui/components/Layout.d.ts.map +1 -0
- package/dist/ui/components/Layout.js +11 -0
- package/dist/ui/components/Layout.js.map +1 -0
- package/dist/ui/components/NavBar.d.ts +12 -0
- package/dist/ui/components/NavBar.d.ts.map +1 -0
- package/dist/ui/components/NavBar.js +60 -0
- package/dist/ui/components/NavBar.js.map +1 -0
- package/dist/ui/components/StatCard.d.ts +14 -0
- package/dist/ui/components/StatCard.d.ts.map +1 -0
- package/dist/ui/components/StatCard.js +32 -0
- package/dist/ui/components/StatCard.js.map +1 -0
- package/dist/ui/components/Terminal.d.ts +13 -0
- package/dist/ui/components/Terminal.d.ts.map +1 -0
- package/dist/ui/components/Terminal.js +37 -0
- package/dist/ui/components/Terminal.js.map +1 -0
- package/dist/ui/pages/AdminDashboard.d.ts +13 -0
- package/dist/ui/pages/AdminDashboard.d.ts.map +1 -0
- package/dist/ui/pages/AdminDashboard.js +59 -0
- package/dist/ui/pages/AdminDashboard.js.map +1 -0
- package/dist/ui/pages/HowToPage.d.ts +8 -0
- package/dist/ui/pages/HowToPage.d.ts.map +1 -0
- package/dist/ui/pages/HowToPage.js +312 -0
- package/dist/ui/pages/HowToPage.js.map +1 -0
- package/dist/ui/pages/LandingPage.d.ts +13 -0
- package/dist/ui/pages/LandingPage.d.ts.map +1 -0
- package/dist/ui/pages/LandingPage.js +160 -0
- package/dist/ui/pages/LandingPage.js.map +1 -0
- package/dist/ui/pages/MessageLog.d.ts +25 -0
- package/dist/ui/pages/MessageLog.d.ts.map +1 -0
- package/dist/ui/pages/MessageLog.js +146 -0
- package/dist/ui/pages/MessageLog.js.map +1 -0
- package/dist/ui/pages/SessionDetail.d.ts +14 -0
- package/dist/ui/pages/SessionDetail.d.ts.map +1 -0
- package/dist/ui/pages/SessionDetail.js +165 -0
- package/dist/ui/pages/SessionDetail.js.map +1 -0
- package/dist/ui/pages/SessionList.d.ts +22 -0
- package/dist/ui/pages/SessionList.d.ts.map +1 -0
- package/dist/ui/pages/SessionList.js +88 -0
- package/dist/ui/pages/SessionList.js.map +1 -0
- package/dist/ui/styles/theme.d.ts +35 -0
- package/dist/ui/styles/theme.d.ts.map +1 -0
- package/dist/ui/styles/theme.js +65 -0
- package/dist/ui/styles/theme.js.map +1 -0
- package/dist/ws/frames.d.ts +82 -0
- package/dist/ws/frames.d.ts.map +1 -0
- package/dist/ws/frames.js +68 -0
- package/dist/ws/frames.js.map +1 -0
- package/dist/ws/handler.d.ts +26 -0
- package/dist/ws/handler.d.ts.map +1 -0
- package/dist/ws/handler.js +72 -0
- package/dist/ws/handler.js.map +1 -0
- package/dist/ws/heartbeat.d.ts +18 -0
- package/dist/ws/heartbeat.d.ts.map +1 -0
- package/dist/ws/heartbeat.js +39 -0
- package/dist/ws/heartbeat.js.map +1 -0
- package/docker-compose.yml +38 -0
- package/nginx.conf.example +63 -0
- package/package.json +61 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import type { SessionStore } from "../services/session-store.js";
|
|
3
|
+
import type { AuthService } from "../services/auth-service.js";
|
|
4
|
+
import type { ServerEnv } from "../types.js";
|
|
5
|
+
/**
|
|
6
|
+
* Create poll routes sub-router.
|
|
7
|
+
*
|
|
8
|
+
* GET /:sessionId/poll -> Polling fallback for environments without WebSocket
|
|
9
|
+
*
|
|
10
|
+
* Returns new messages and peer lifecycle events since the last poll,
|
|
11
|
+
* filtered for the requesting peer. Updates the peer's lastSeenAt
|
|
12
|
+
* to keep it alive for stale cleanup.
|
|
13
|
+
*/
|
|
14
|
+
export declare function createPollRoutes(sessionStore: SessionStore, authService: AuthService): Hono<ServerEnv>;
|
|
15
|
+
//# sourceMappingURL=poll.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"poll.d.ts","sourceRoot":"","sources":["../../src/routes/poll.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAQ5B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AACjE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAC/D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAI7C;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAC9B,YAAY,EAAE,YAAY,EAC1B,WAAW,EAAE,WAAW,GACvB,IAAI,CAAC,SAAS,CAAC,CA6GjB"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import { zValidator } from "@hono/zod-validator";
|
|
3
|
+
import { BridgeError, ErrorCode, PollQuerySchema, } from "@essentialai/cogent";
|
|
4
|
+
import { createAuthMiddleware } from "../middleware/auth.js";
|
|
5
|
+
import { validationHook } from "./validation-hook.js";
|
|
6
|
+
/**
|
|
7
|
+
* Create poll routes sub-router.
|
|
8
|
+
*
|
|
9
|
+
* GET /:sessionId/poll -> Polling fallback for environments without WebSocket
|
|
10
|
+
*
|
|
11
|
+
* Returns new messages and peer lifecycle events since the last poll,
|
|
12
|
+
* filtered for the requesting peer. Updates the peer's lastSeenAt
|
|
13
|
+
* to keep it alive for stale cleanup.
|
|
14
|
+
*/
|
|
15
|
+
export function createPollRoutes(sessionStore, authService) {
|
|
16
|
+
const poll = new Hono();
|
|
17
|
+
// Auth middleware applied per-route (not wildcard) to avoid intercepting
|
|
18
|
+
// unauthenticated session routes mounted at the same /api/sessions prefix.
|
|
19
|
+
const auth = createAuthMiddleware(sessionStore, authService);
|
|
20
|
+
/**
|
|
21
|
+
* GET /:sessionId/poll - Poll for new messages and peer events.
|
|
22
|
+
*
|
|
23
|
+
* If lastMessageId is provided, returns messages after that ID.
|
|
24
|
+
* If lastMessageId is not found (truncated by cap), returns all messages.
|
|
25
|
+
* If lastMessageId is not provided (first poll), returns all messages.
|
|
26
|
+
*
|
|
27
|
+
* Messages are filtered to those relevant to the polling peer:
|
|
28
|
+
* sent by, addressed to, or broadcast to this peer.
|
|
29
|
+
*
|
|
30
|
+
* Peer events are filtered by timestamp (after the lastMessageId's timestamp).
|
|
31
|
+
* Limited to the last 100 events to prevent unbounded growth.
|
|
32
|
+
*/
|
|
33
|
+
poll.get("/:sessionId/poll", auth, zValidator("query", PollQuerySchema, validationHook), async (c) => {
|
|
34
|
+
const authSessionId = c.get("sessionId");
|
|
35
|
+
const pathSessionId = c.req.param("sessionId");
|
|
36
|
+
// Verify path param matches authenticated session
|
|
37
|
+
if (pathSessionId !== authSessionId) {
|
|
38
|
+
throw new BridgeError(ErrorCode.AUTH_INVALID_TOKEN, "Token does not belong to this session");
|
|
39
|
+
}
|
|
40
|
+
const { lastMessageId, peerId } = c.req.valid("query");
|
|
41
|
+
// Update the polling peer's lastSeenAt to keep them alive for stale cleanup
|
|
42
|
+
await sessionStore.updateSession(authSessionId, (state) => {
|
|
43
|
+
if (state.peers[peerId]) {
|
|
44
|
+
state.peers[peerId].lastSeenAt = new Date().toISOString();
|
|
45
|
+
}
|
|
46
|
+
return state;
|
|
47
|
+
});
|
|
48
|
+
// Fresh read from store after the update
|
|
49
|
+
const session = await sessionStore.getSession(authSessionId);
|
|
50
|
+
if (!session) {
|
|
51
|
+
throw new BridgeError(ErrorCode.SESSION_NOT_FOUND, `Session ${authSessionId} not found`);
|
|
52
|
+
}
|
|
53
|
+
// Find new messages since lastMessageId
|
|
54
|
+
let newMessages;
|
|
55
|
+
let referenceTimestamp = null;
|
|
56
|
+
if (lastMessageId) {
|
|
57
|
+
const idx = session.messages.findIndex((msg) => msg.id === lastMessageId);
|
|
58
|
+
if (idx !== -1) {
|
|
59
|
+
// Found the message -- return everything after it
|
|
60
|
+
referenceTimestamp = session.messages[idx].timestamp;
|
|
61
|
+
newMessages = session.messages.slice(idx + 1);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
// Message not found (may have been truncated by cap) -- return all
|
|
65
|
+
newMessages = session.messages;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
// First poll -- return all messages
|
|
70
|
+
newMessages = session.messages;
|
|
71
|
+
}
|
|
72
|
+
// Filter messages relevant to this peer
|
|
73
|
+
const filteredMessages = newMessages.filter((msg) => msg.toPeerId === peerId ||
|
|
74
|
+
msg.toPeerId === "*" ||
|
|
75
|
+
msg.fromPeerId === peerId);
|
|
76
|
+
// Find peer events since the reference timestamp
|
|
77
|
+
let events;
|
|
78
|
+
if (referenceTimestamp) {
|
|
79
|
+
const refTime = new Date(referenceTimestamp).getTime();
|
|
80
|
+
events = session.peerEvents.filter((evt) => new Date(evt.timestamp).getTime() > refTime);
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
// No reference -- return all events
|
|
84
|
+
events = session.peerEvents;
|
|
85
|
+
}
|
|
86
|
+
// Limit to last 100 events to prevent unbounded growth
|
|
87
|
+
if (events.length > 100) {
|
|
88
|
+
events = events.slice(-100);
|
|
89
|
+
}
|
|
90
|
+
return c.json({
|
|
91
|
+
messages: filteredMessages,
|
|
92
|
+
events,
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
return poll;
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=poll.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"poll.js","sourceRoot":"","sources":["../../src/routes/poll.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import type { SessionStore } from "../services/session-store.js";
|
|
3
|
+
import type { AuthService } from "../services/auth-service.js";
|
|
4
|
+
import type { ServerEnv } from "../types.js";
|
|
5
|
+
/**
|
|
6
|
+
* Create session routes sub-router.
|
|
7
|
+
*
|
|
8
|
+
* POST / -> Create a new session (maps to /api/sessions via app.route)
|
|
9
|
+
* POST /:sessionId/join -> Join an existing session
|
|
10
|
+
*
|
|
11
|
+
* Both endpoints are unauthenticated (they issue tokens, not consume them).
|
|
12
|
+
*/
|
|
13
|
+
export declare function createSessionRoutes(sessionStore: SessionStore, authService: AuthService): Hono<ServerEnv>;
|
|
14
|
+
//# sourceMappingURL=sessions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessions.d.ts","sourceRoot":"","sources":["../../src/routes/sessions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAW5B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AACjE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAC/D,OAAO,KAAK,EAAc,SAAS,EAAE,MAAM,aAAa,CAAC;AAGzD;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,YAAY,EAAE,YAAY,EAC1B,WAAW,EAAE,WAAW,GACvB,IAAI,CAAC,SAAS,CAAC,CA0JjB"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import { zValidator } from "@hono/zod-validator";
|
|
3
|
+
import { BridgeError, ErrorCode, CreateSessionRequestSchema, JoinSessionRequestSchema, generateSessionId, generateSessionLabel, isValidSessionLabel, } from "@essentialai/cogent";
|
|
4
|
+
import { validationHook } from "./validation-hook.js";
|
|
5
|
+
/**
|
|
6
|
+
* Create session routes sub-router.
|
|
7
|
+
*
|
|
8
|
+
* POST / -> Create a new session (maps to /api/sessions via app.route)
|
|
9
|
+
* POST /:sessionId/join -> Join an existing session
|
|
10
|
+
*
|
|
11
|
+
* Both endpoints are unauthenticated (they issue tokens, not consume them).
|
|
12
|
+
*/
|
|
13
|
+
export function createSessionRoutes(sessionStore, authService) {
|
|
14
|
+
const sessions = new Hono();
|
|
15
|
+
/**
|
|
16
|
+
* POST / - Create a new session.
|
|
17
|
+
*
|
|
18
|
+
* Receives { secret, label? }, bcrypt-hashes the secret, generates a bearer
|
|
19
|
+
* token, creates the session on disk, and returns { sessionId, token, createdAt }.
|
|
20
|
+
*/
|
|
21
|
+
sessions.post("/", zValidator("json", CreateSessionRequestSchema, validationHook), async (c) => {
|
|
22
|
+
const { secret, label } = c.req.valid("json");
|
|
23
|
+
// Generate unique session ID
|
|
24
|
+
const sessionId = generateSessionId();
|
|
25
|
+
// Auto-generate label if not provided, with collision retry
|
|
26
|
+
let sessionLabel = label;
|
|
27
|
+
if (!sessionLabel) {
|
|
28
|
+
for (let i = 0; i < 5; i++) {
|
|
29
|
+
const candidate = generateSessionLabel();
|
|
30
|
+
if (!sessionStore.isLabelTaken(candidate)) {
|
|
31
|
+
sessionLabel = candidate;
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (!sessionLabel)
|
|
36
|
+
sessionLabel = generateSessionLabel(); // last resort, collision unlikely
|
|
37
|
+
}
|
|
38
|
+
// Hash secret with bcrypt for secure storage
|
|
39
|
+
const secretHash = await authService.hashSecret(secret);
|
|
40
|
+
// Generate bearer token (returns plaintext + SHA-256 hash)
|
|
41
|
+
const { token, tokenHash } = authService.generateToken();
|
|
42
|
+
// Build token entry for storage
|
|
43
|
+
const tokenEntry = {
|
|
44
|
+
tokenHash,
|
|
45
|
+
createdAt: new Date().toISOString(),
|
|
46
|
+
};
|
|
47
|
+
// Capture creator IP (behind nginx: X-Forwarded-For or X-Real-IP)
|
|
48
|
+
const creatorIp = c.req.header("x-forwarded-for")?.split(",")[0].trim() ||
|
|
49
|
+
c.req.header("x-real-ip") ||
|
|
50
|
+
undefined;
|
|
51
|
+
// Persist the session
|
|
52
|
+
const session = await sessionStore.createSession(sessionId, sessionLabel, secretHash, tokenEntry, creatorIp);
|
|
53
|
+
return c.json({
|
|
54
|
+
sessionId,
|
|
55
|
+
token,
|
|
56
|
+
label: sessionLabel,
|
|
57
|
+
createdAt: session.createdAt,
|
|
58
|
+
}, 201);
|
|
59
|
+
});
|
|
60
|
+
/**
|
|
61
|
+
* GET /resolve/:label - Resolve a session label to its UUID.
|
|
62
|
+
*
|
|
63
|
+
* Unauthenticated -- a sessionId alone grants no access (token required).
|
|
64
|
+
* Returns { sessionId, label } or 400/404.
|
|
65
|
+
*/
|
|
66
|
+
sessions.get("/resolve/:label", async (c) => {
|
|
67
|
+
const label = c.req.param("label");
|
|
68
|
+
if (!isValidSessionLabel(label)) {
|
|
69
|
+
return c.json({ error: "Invalid label format. Must be 3-32 lowercase alphanumeric characters with internal hyphens." }, 400);
|
|
70
|
+
}
|
|
71
|
+
const sessionId = sessionStore.getSessionIdByLabel(label);
|
|
72
|
+
if (!sessionId) {
|
|
73
|
+
throw new BridgeError(ErrorCode.SESSION_NOT_FOUND, `No session with label "${label}"`, "Check the label or use the full session ID");
|
|
74
|
+
}
|
|
75
|
+
return c.json({ sessionId, label });
|
|
76
|
+
});
|
|
77
|
+
/**
|
|
78
|
+
* POST /:sessionId/join - Join an existing session.
|
|
79
|
+
*
|
|
80
|
+
* Receives { secret }, verifies against bcrypt hash, generates a new bearer
|
|
81
|
+
* token, and returns { sessionId, token, peerCount }.
|
|
82
|
+
*/
|
|
83
|
+
sessions.post("/:sessionId/join", zValidator("json", JoinSessionRequestSchema, validationHook), async (c) => {
|
|
84
|
+
const sessionId = c.req.param("sessionId");
|
|
85
|
+
const { secret } = c.req.valid("json");
|
|
86
|
+
// Look up the session
|
|
87
|
+
const session = await sessionStore.getSession(sessionId);
|
|
88
|
+
if (!session) {
|
|
89
|
+
throw new BridgeError(ErrorCode.SESSION_NOT_FOUND, `Session ${sessionId} not found`, "Check the session ID or create a new session");
|
|
90
|
+
}
|
|
91
|
+
// Verify the provided secret against the stored bcrypt hash
|
|
92
|
+
const isValid = await authService.verifySecret(secret, session.secretHash);
|
|
93
|
+
if (!isValid) {
|
|
94
|
+
throw new BridgeError(ErrorCode.AUTH_INVALID_TOKEN, "Invalid session secret", "Verify you are using the correct shared secret for this session");
|
|
95
|
+
}
|
|
96
|
+
// Generate a new bearer token for this joining client
|
|
97
|
+
const { token, tokenHash } = authService.generateToken();
|
|
98
|
+
const tokenEntry = {
|
|
99
|
+
tokenHash,
|
|
100
|
+
createdAt: new Date().toISOString(),
|
|
101
|
+
};
|
|
102
|
+
// Add the token to the session
|
|
103
|
+
await sessionStore.addToken(sessionId, tokenEntry);
|
|
104
|
+
return c.json({
|
|
105
|
+
sessionId,
|
|
106
|
+
token,
|
|
107
|
+
label: session.label ?? "",
|
|
108
|
+
peerCount: Object.keys(session.peers).length,
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
return sessions;
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=sessions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessions.js","sourceRoot":"","sources":["../../src/routes/sessions.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import type { ServerEnv } from "../types.js";
|
|
3
|
+
import type { StatsService } from "../services/stats-service.js";
|
|
4
|
+
import type { SessionStore } from "../services/session-store.js";
|
|
5
|
+
import type { ConnectionManager } from "../services/connection-manager.js";
|
|
6
|
+
/** Dependencies for UI route handlers. */
|
|
7
|
+
export interface UiRouteDeps {
|
|
8
|
+
statsService: StatsService;
|
|
9
|
+
adminPassword: string | null;
|
|
10
|
+
sessionStore: SessionStore;
|
|
11
|
+
connectionManager: ConnectionManager;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Create UI route handlers for the public landing page, SSE stats stream,
|
|
15
|
+
* and password-protected admin dashboard with session/peer CRUD and message log.
|
|
16
|
+
*
|
|
17
|
+
* Admin routes are only mounted when adminPassword is set (non-null).
|
|
18
|
+
* When disabled, /admin/* returns 404 via Hono's default handler.
|
|
19
|
+
*/
|
|
20
|
+
export declare function createUiRoutes(deps: UiRouteDeps): Hono<ServerEnv>;
|
|
21
|
+
//# sourceMappingURL=ui.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../../src/routes/ui.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAG5B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AACjE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AACjE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AAU3E,0CAA0C;AAC1C,MAAM,WAAW,WAAW;IAC1B,YAAY,EAAE,YAAY,CAAC;IAC3B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,YAAY,EAAE,YAAY,CAAC;IAC3B,iBAAiB,EAAE,iBAAiB,CAAC;CACtC;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI,CAAC,SAAS,CAAC,CAkMjE"}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { jsx as _jsx } from "hono/jsx/jsx-runtime";
|
|
2
|
+
import { Hono } from "hono";
|
|
3
|
+
import { streamSSE } from "hono/streaming";
|
|
4
|
+
import { basicAuth } from "hono/basic-auth";
|
|
5
|
+
import { LandingPage } from "../ui/pages/LandingPage.js";
|
|
6
|
+
import { HowToPage } from "../ui/pages/HowToPage.js";
|
|
7
|
+
import { Layout } from "../ui/components/Layout.js";
|
|
8
|
+
import { AdminDashboard } from "../ui/pages/AdminDashboard.js";
|
|
9
|
+
import { SessionList } from "../ui/pages/SessionList.js";
|
|
10
|
+
import { SessionDetail } from "../ui/pages/SessionDetail.js";
|
|
11
|
+
import { MessageLog } from "../ui/pages/MessageLog.js";
|
|
12
|
+
/**
|
|
13
|
+
* Create UI route handlers for the public landing page, SSE stats stream,
|
|
14
|
+
* and password-protected admin dashboard with session/peer CRUD and message log.
|
|
15
|
+
*
|
|
16
|
+
* Admin routes are only mounted when adminPassword is set (non-null).
|
|
17
|
+
* When disabled, /admin/* returns 404 via Hono's default handler.
|
|
18
|
+
*/
|
|
19
|
+
export function createUiRoutes(deps) {
|
|
20
|
+
const { statsService, adminPassword, sessionStore, connectionManager } = deps;
|
|
21
|
+
const ui = new Hono();
|
|
22
|
+
// GET / -- Landing page with server-rendered stats
|
|
23
|
+
ui.get("/", (c) => {
|
|
24
|
+
const stats = statsService.getStats();
|
|
25
|
+
return c.html(_jsx(LandingPage, { stats: stats }));
|
|
26
|
+
});
|
|
27
|
+
// GET /how-to -- Comprehensive how-to guide
|
|
28
|
+
ui.get("/how-to", (c) => {
|
|
29
|
+
return c.html(_jsx(HowToPage, {}));
|
|
30
|
+
});
|
|
31
|
+
// GET /ui/stats/stream -- SSE endpoint pushing stats every 5 seconds
|
|
32
|
+
ui.get("/ui/stats/stream", (c) => {
|
|
33
|
+
return streamSSE(c, async (stream) => {
|
|
34
|
+
while (true) {
|
|
35
|
+
const stats = statsService.getStats();
|
|
36
|
+
await stream.writeSSE({
|
|
37
|
+
data: JSON.stringify(stats),
|
|
38
|
+
event: "stats",
|
|
39
|
+
id: String(Date.now()),
|
|
40
|
+
});
|
|
41
|
+
await stream.sleep(5000);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
// --- Admin routes (only when password is configured) ---
|
|
46
|
+
if (adminPassword) {
|
|
47
|
+
const admin = new Hono();
|
|
48
|
+
// Basic Auth middleware for all admin routes
|
|
49
|
+
admin.use("*", basicAuth({
|
|
50
|
+
verifyUser: (username, password) => {
|
|
51
|
+
return username === "admin" && password === adminPassword;
|
|
52
|
+
},
|
|
53
|
+
realm: "COGENT Admin",
|
|
54
|
+
}));
|
|
55
|
+
// GET /admin/ or /admin -- Dashboard overview
|
|
56
|
+
admin.get("/", async (_c) => {
|
|
57
|
+
const stats = statsService.getStats();
|
|
58
|
+
return _c.html(_jsx(AdminDashboard, { stats: stats }));
|
|
59
|
+
});
|
|
60
|
+
// GET /admin/sessions -- Session list
|
|
61
|
+
admin.get("/sessions", async (c) => {
|
|
62
|
+
const sessionIds = await sessionStore.listSessions();
|
|
63
|
+
const sessions = [];
|
|
64
|
+
for (const id of sessionIds) {
|
|
65
|
+
const session = await sessionStore.getSession(id);
|
|
66
|
+
if (session) {
|
|
67
|
+
sessions.push({
|
|
68
|
+
sessionId: id,
|
|
69
|
+
label: session.label,
|
|
70
|
+
createdAt: session.createdAt,
|
|
71
|
+
creatorIp: session.creatorIp,
|
|
72
|
+
peerCount: Object.keys(session.peers).length,
|
|
73
|
+
messageCount: session.messages.length,
|
|
74
|
+
connectedPeerIds: connectionManager.getConnectedPeerIds(id),
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return c.html(_jsx(SessionList, { sessions: sessions }));
|
|
79
|
+
});
|
|
80
|
+
// GET /admin/sessions/:id -- Session detail
|
|
81
|
+
admin.get("/sessions/:id", async (c) => {
|
|
82
|
+
const sessionId = c.req.param("id");
|
|
83
|
+
const session = await sessionStore.getSession(sessionId);
|
|
84
|
+
if (!session) {
|
|
85
|
+
return c.html(_jsx(Layout, { title: "Not Found", children: _jsx("p", { children: "Session not found" }) }), 404);
|
|
86
|
+
}
|
|
87
|
+
const connectedPeerIds = connectionManager.getConnectedPeerIds(sessionId);
|
|
88
|
+
return c.html(_jsx(SessionDetail, { session: session, connectedPeerIds: connectedPeerIds }));
|
|
89
|
+
});
|
|
90
|
+
// POST /admin/sessions/:id/delete -- Delete session
|
|
91
|
+
admin.post("/sessions/:id/delete", async (c) => {
|
|
92
|
+
const sessionId = c.req.param("id");
|
|
93
|
+
// Close all WS connections for this session before deleting
|
|
94
|
+
const connectedPeers = connectionManager.getConnectedPeerIds(sessionId);
|
|
95
|
+
for (const peerId of connectedPeers) {
|
|
96
|
+
const conns = connectionManager.getConnections(sessionId, peerId);
|
|
97
|
+
for (const conn of conns) {
|
|
98
|
+
try {
|
|
99
|
+
conn.ws.close(1000, "Session deleted by admin");
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
// Ignore errors from already-closed sockets
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
await sessionStore.deleteSession(sessionId);
|
|
107
|
+
return c.redirect("/admin/sessions");
|
|
108
|
+
});
|
|
109
|
+
// POST /admin/sessions/:id/peers/:peerId/delete -- Delete peer from session
|
|
110
|
+
admin.post("/sessions/:id/peers/:peerId/delete", async (c) => {
|
|
111
|
+
const sessionId = c.req.param("id");
|
|
112
|
+
const peerId = c.req.param("peerId");
|
|
113
|
+
// Close WS connections for this peer
|
|
114
|
+
const conns = connectionManager.getConnections(sessionId, peerId);
|
|
115
|
+
for (const conn of conns) {
|
|
116
|
+
try {
|
|
117
|
+
conn.ws.close(1000, "Peer removed by admin");
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
// Ignore errors from already-closed sockets
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// Remove peer from session state
|
|
124
|
+
await sessionStore.updateSession(sessionId, (state) => {
|
|
125
|
+
const { [peerId]: _, ...remainingPeers } = state.peers;
|
|
126
|
+
return { ...state, peers: remainingPeers };
|
|
127
|
+
});
|
|
128
|
+
return c.redirect("/admin/sessions/" + sessionId);
|
|
129
|
+
});
|
|
130
|
+
// GET /admin/messages -- Message log with filtering
|
|
131
|
+
admin.get("/messages", async (c) => {
|
|
132
|
+
const filterSessionId = c.req.query("sessionId") || undefined;
|
|
133
|
+
const filterPeerId = c.req.query("peerId") || undefined;
|
|
134
|
+
const filterQuery = c.req.query("q") || undefined;
|
|
135
|
+
const sessionIds = filterSessionId
|
|
136
|
+
? [filterSessionId]
|
|
137
|
+
: await sessionStore.listSessions();
|
|
138
|
+
const allMessages = [];
|
|
139
|
+
for (const id of sessionIds) {
|
|
140
|
+
const session = await sessionStore.getSession(id);
|
|
141
|
+
if (session) {
|
|
142
|
+
for (const msg of session.messages) {
|
|
143
|
+
allMessages.push({ ...msg, sessionId: id });
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
// Apply filters
|
|
148
|
+
let filtered = allMessages;
|
|
149
|
+
if (filterPeerId) {
|
|
150
|
+
filtered = filtered.filter((m) => m.fromPeerId === filterPeerId || m.toPeerId === filterPeerId);
|
|
151
|
+
}
|
|
152
|
+
if (filterQuery) {
|
|
153
|
+
const q = filterQuery.toLowerCase();
|
|
154
|
+
filtered = filtered.filter((m) => m.message.toLowerCase().includes(q));
|
|
155
|
+
}
|
|
156
|
+
// Sort by timestamp descending (newest first), take last 200
|
|
157
|
+
filtered.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
|
|
158
|
+
filtered = filtered.slice(0, 200);
|
|
159
|
+
const allSessionIds = await sessionStore.listSessions();
|
|
160
|
+
return c.html(_jsx(MessageLog, { messages: filtered, filters: {
|
|
161
|
+
sessionId: filterSessionId,
|
|
162
|
+
peerId: filterPeerId,
|
|
163
|
+
query: filterQuery,
|
|
164
|
+
}, sessions: allSessionIds }));
|
|
165
|
+
});
|
|
166
|
+
// Mount admin sub-router
|
|
167
|
+
ui.route("/admin", admin);
|
|
168
|
+
// Hono sub-routers don't match trailing slash on root — redirect /admin/ → /admin
|
|
169
|
+
ui.get("/admin/", (c) => c.redirect("/admin"));
|
|
170
|
+
}
|
|
171
|
+
return ui;
|
|
172
|
+
}
|
|
173
|
+
//# sourceMappingURL=ui.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ui.js","sourceRoot":"","sources":["../../src/routes/ui.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Context, Env } from "hono";
|
|
2
|
+
/**
|
|
3
|
+
* Shared validation hook for @hono/zod-validator.
|
|
4
|
+
*
|
|
5
|
+
* Converts Zod validation failures into structured BridgeError JSON
|
|
6
|
+
* responses with error code INVALID_INPUT and HTTP 400. This ensures
|
|
7
|
+
* all validation errors across routes use a consistent format.
|
|
8
|
+
*
|
|
9
|
+
* Reusable by all route files that use zValidator.
|
|
10
|
+
*
|
|
11
|
+
* Note: The hook uses the base Env type (not ServerEnv) because
|
|
12
|
+
* @hono/zod-validator's Hook type narrows Env generically.
|
|
13
|
+
*/
|
|
14
|
+
export declare function validationHook(result: {
|
|
15
|
+
success: boolean;
|
|
16
|
+
error?: unknown;
|
|
17
|
+
}, c: Context<Env>): Response | void;
|
|
18
|
+
//# sourceMappingURL=validation-hook.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation-hook.d.ts","sourceRoot":"","sources":["../../src/routes/validation-hook.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,MAAM,CAAC;AAEzC;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,EAC7C,CAAC,EAAE,OAAO,CAAC,GAAG,CAAC,GACd,QAAQ,GAAG,IAAI,CAejB"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { BridgeError, ErrorCode } from "@essentialai/cogent";
|
|
2
|
+
/**
|
|
3
|
+
* Shared validation hook for @hono/zod-validator.
|
|
4
|
+
*
|
|
5
|
+
* Converts Zod validation failures into structured BridgeError JSON
|
|
6
|
+
* responses with error code INVALID_INPUT and HTTP 400. This ensures
|
|
7
|
+
* all validation errors across routes use a consistent format.
|
|
8
|
+
*
|
|
9
|
+
* Reusable by all route files that use zValidator.
|
|
10
|
+
*
|
|
11
|
+
* Note: The hook uses the base Env type (not ServerEnv) because
|
|
12
|
+
* @hono/zod-validator's Hook type narrows Env generically.
|
|
13
|
+
*/
|
|
14
|
+
export function validationHook(result, c) {
|
|
15
|
+
if (!result.success) {
|
|
16
|
+
const zodError = result.error;
|
|
17
|
+
const message = zodError instanceof Error
|
|
18
|
+
? zodError.message
|
|
19
|
+
: "Invalid request body";
|
|
20
|
+
const err = new BridgeError(ErrorCode.INVALID_INPUT, `Validation failed: ${message}`, "Check request body matches the expected schema");
|
|
21
|
+
return c.json(err.toJSON(), 400);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=validation-hook.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation-hook.js","sourceRoot":"","sources":["../../src/routes/validation-hook.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAG7D;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,cAAc,CAC5B,MAA6C,EAC7C,CAAe;IAEf,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,CAAC;QAC9B,MAAM,OAAO,GACX,QAAQ,YAAY,KAAK;YACvB,CAAC,CAAC,QAAQ,CAAC,OAAO;YAClB,CAAC,CAAC,sBAAsB,CAAC;QAE7B,MAAM,GAAG,GAAG,IAAI,WAAW,CACzB,SAAS,CAAC,aAAa,EACvB,sBAAsB,OAAO,EAAE,EAC/B,gDAAgD,CACjD,CAAC;QACF,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,EAAE,GAAG,CAAC,CAAC;IACnC,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authentication service for the Cogent server.
|
|
3
|
+
*
|
|
4
|
+
* Responsibilities:
|
|
5
|
+
* - bcrypt hashing of session secrets (one-time, during create/join)
|
|
6
|
+
* - Random bearer token generation with SHA-256 hashing for storage
|
|
7
|
+
* - Timing-safe comparison for token verification (every request)
|
|
8
|
+
*
|
|
9
|
+
* bcrypt is used for secrets (slow, salted -- appropriate for passwords).
|
|
10
|
+
* SHA-256 is used for tokens (fast -- tokens are high-entropy random values).
|
|
11
|
+
* Timing-safe comparison prevents timing attacks on token verification.
|
|
12
|
+
*/
|
|
13
|
+
export declare class AuthService {
|
|
14
|
+
private readonly bcryptRounds;
|
|
15
|
+
constructor(bcryptRounds: number);
|
|
16
|
+
/**
|
|
17
|
+
* Hash a plaintext secret using bcrypt.
|
|
18
|
+
* Used during session creation and join operations.
|
|
19
|
+
*/
|
|
20
|
+
hashSecret(plainSecret: string): Promise<string>;
|
|
21
|
+
/**
|
|
22
|
+
* Verify a plaintext secret against a bcrypt hash.
|
|
23
|
+
* Used during session join to validate the provided secret.
|
|
24
|
+
*/
|
|
25
|
+
verifySecret(plainSecret: string, hash: string): Promise<boolean>;
|
|
26
|
+
/**
|
|
27
|
+
* Generate a cryptographically random bearer token.
|
|
28
|
+
* Returns both the plaintext token (to send to client) and its
|
|
29
|
+
* SHA-256 hash (to store on disk for lookup).
|
|
30
|
+
*/
|
|
31
|
+
generateToken(): {
|
|
32
|
+
token: string;
|
|
33
|
+
tokenHash: string;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Compute the SHA-256 hash of a token string.
|
|
37
|
+
* Used by auth middleware to look up tokens in the session store index.
|
|
38
|
+
*/
|
|
39
|
+
hashToken(token: string): string;
|
|
40
|
+
/**
|
|
41
|
+
* Timing-safe string comparison.
|
|
42
|
+
* Hashes both strings with SHA-256 to normalize length, then uses
|
|
43
|
+
* crypto.timingSafeEqual on the resulting buffers. This prevents
|
|
44
|
+
* timing attacks regardless of input string lengths.
|
|
45
|
+
*/
|
|
46
|
+
timingSafeCompare(a: string, b: string): boolean;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=auth-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-service.d.ts","sourceRoot":"","sources":["../../src/services/auth-service.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;GAWG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;gBAE1B,YAAY,EAAE,MAAM;IAIhC;;;OAGG;IACG,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAItD;;;OAGG;IACG,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAIvE;;;;OAIG;IACH,aAAa,IAAI;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE;IAMrD;;;OAGG;IACH,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAIhC;;;;;OAKG;IACH,iBAAiB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO;CAKjD"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import bcrypt from "bcryptjs";
|
|
3
|
+
/**
|
|
4
|
+
* Authentication service for the Cogent server.
|
|
5
|
+
*
|
|
6
|
+
* Responsibilities:
|
|
7
|
+
* - bcrypt hashing of session secrets (one-time, during create/join)
|
|
8
|
+
* - Random bearer token generation with SHA-256 hashing for storage
|
|
9
|
+
* - Timing-safe comparison for token verification (every request)
|
|
10
|
+
*
|
|
11
|
+
* bcrypt is used for secrets (slow, salted -- appropriate for passwords).
|
|
12
|
+
* SHA-256 is used for tokens (fast -- tokens are high-entropy random values).
|
|
13
|
+
* Timing-safe comparison prevents timing attacks on token verification.
|
|
14
|
+
*/
|
|
15
|
+
export class AuthService {
|
|
16
|
+
bcryptRounds;
|
|
17
|
+
constructor(bcryptRounds) {
|
|
18
|
+
this.bcryptRounds = bcryptRounds;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Hash a plaintext secret using bcrypt.
|
|
22
|
+
* Used during session creation and join operations.
|
|
23
|
+
*/
|
|
24
|
+
async hashSecret(plainSecret) {
|
|
25
|
+
return bcrypt.hash(plainSecret, this.bcryptRounds);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Verify a plaintext secret against a bcrypt hash.
|
|
29
|
+
* Used during session join to validate the provided secret.
|
|
30
|
+
*/
|
|
31
|
+
async verifySecret(plainSecret, hash) {
|
|
32
|
+
return bcrypt.compare(plainSecret, hash);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Generate a cryptographically random bearer token.
|
|
36
|
+
* Returns both the plaintext token (to send to client) and its
|
|
37
|
+
* SHA-256 hash (to store on disk for lookup).
|
|
38
|
+
*/
|
|
39
|
+
generateToken() {
|
|
40
|
+
const token = crypto.randomBytes(32).toString("hex");
|
|
41
|
+
const tokenHash = this.hashToken(token);
|
|
42
|
+
return { token, tokenHash };
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Compute the SHA-256 hash of a token string.
|
|
46
|
+
* Used by auth middleware to look up tokens in the session store index.
|
|
47
|
+
*/
|
|
48
|
+
hashToken(token) {
|
|
49
|
+
return crypto.createHash("sha256").update(token).digest("hex");
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Timing-safe string comparison.
|
|
53
|
+
* Hashes both strings with SHA-256 to normalize length, then uses
|
|
54
|
+
* crypto.timingSafeEqual on the resulting buffers. This prevents
|
|
55
|
+
* timing attacks regardless of input string lengths.
|
|
56
|
+
*/
|
|
57
|
+
timingSafeCompare(a, b) {
|
|
58
|
+
const hashA = crypto.createHash("sha256").update(a).digest();
|
|
59
|
+
const hashB = crypto.createHash("sha256").update(b).digest();
|
|
60
|
+
return crypto.timingSafeEqual(hashA, hashB);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=auth-service.js.map
|