aboutsummaryrefslogtreecommitdiffstats
path: root/lib/sockets.ts
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-08-07 22:58:52 -0700
committerAdam Soltys <[email protected]>2026-08-07 23:29:24 -0700
commit01784219f83f68c7d03eb19c35f05eeaedc74487 (patch)
treec96a432094da1a655e98bbdf3f90fcf8e1052d61 /lib/sockets.ts
parentbc5b34f053cf35c11a68e57a9c43c987331452a7 (diff)
downloadcoinos-server-01784219f83f68c7d03eb19c35f05eeaedc74487.tar.gz
coinos-server-01784219f83f68c7d03eb19c35f05eeaedc74487.zip
sockets: verify JWT signatures on the websocket auth path (S1)
The websocket auth gate used jwt.decode(), which parses the payload but never checks the signature — anyone could forge {id: <victim uid>} (alg:none or any key), register their socket under the victim's uid, and receive every emit for that account. Switch both track() and the "login" gate to jwt.verify(token, config.jwt), rejecting forged/altered tokens. Also project the full-record socket emits through pick(user, whitelist) so a "user" event can never carry the bcrypt hash, TOTP secret, or PIN — matching the shape /login and /me already return. Co-Authored-By: Claude Opus 4.8 <[email protected]>
Diffstat (limited to 'lib/sockets.ts')
-rw-r--r--lib/sockets.ts16
1 files changed, 14 insertions, 2 deletions
diff --git a/lib/sockets.ts b/lib/sockets.ts
index 028551ed..58673986 100644
--- a/lib/sockets.ts
+++ b/lib/sockets.ts
@@ -1,9 +1,21 @@
+import config from "$config";
import store from "$lib/store";
import jwt from "jsonwebtoken";
import { v4 } from "uuid";
import { err, warn } from "$lib/logging";
import { fail, getUser } from "$lib/utils";
+// Verify the token's signature — never trust jwt.decode() here. A decode-only
+// gate let anyone forge {id: <victim uid>} (alg:none or any key) and register
+// their socket under the victim's uid, receiving every emit for that account.
+const verifyToken = (token) => {
+ try {
+ return jwt.verify(token, config.jwt);
+ } catch (e) {
+ return null;
+ }
+};
+
const code = 1000;
const all = {};
const subscriptions = [];
@@ -44,7 +56,7 @@ export const broadcast = (type, data) => {
const track = async (ws, token) => {
const { id } = ws;
- const { id: uid } = jwt.decode(token);
+ const { id: uid } = verifyToken(token) || {};
if (!uid) fail("Invalid JWT token");
const user = await getUser(uid);
@@ -97,7 +109,7 @@ Bun.serve({
case "login":
try {
- if (!data || data === "null" || !jwt.decode(data))
+ if (!data || data === "null" || !verifyToken(data))
return ws.close(code, `bad token ${data}`);
await track(ws, data);