diff options
Diffstat (limited to 'lib/sockets.ts')
| -rw-r--r-- | lib/sockets.ts | 27 |
1 files changed, 25 insertions, 2 deletions
diff --git a/lib/sockets.ts b/lib/sockets.ts index 028551ed..610d38fd 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 = []; @@ -23,6 +35,9 @@ export const emit = (uid, type, data) => { if (!store.sockets[uid]) return; for (const id in store.sockets[uid]) { const ws = store.sockets[uid][id]; + // Read-only sockets (POS terminals logged in with a "-ro" token) only + // get payment notifications, mirroring the HTTP read-only whitelist. + if (ws.readonly && type !== "payment") continue; ws.send(JSON.stringify({ type, data })); } }; @@ -44,9 +59,16 @@ export const broadcast = (type, data) => { const track = async (ws, token) => { const { id } = ws; - const { id: uid } = jwt.decode(token); + let { id: uid } = verifyToken(token) || {}; if (!uid) fail("Invalid JWT token"); + + // Read-only tokens (see users.ro) carry "<uid>-ro". The HTTP auth layer + // strips the suffix for whitelisted routes; do the same here so a POS + // device can log in and receive payment events, but nothing else. + const readonly = uid.endsWith("-ro"); + if (readonly) uid = uid.slice(0, -3); + const user = await getUser(uid); if (!user) fail(`User not found ${uid}`); @@ -61,6 +83,7 @@ const track = async (ws, token) => { store.sockets[uid][id] = ws; users[id] = uid; ws.user = user; + ws.readonly = readonly; }; Bun.serve({ @@ -97,7 +120,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); |
