podcast manager
1import { jwtVerify } from "@panva/jose";
2import { combineSignals, timeoutSignal } from "@repo/common/aborts.ts";
3import { jwtDecodedSchema } from "@repo/common/crypto-sig.ts";
4import { AuthError, normalizeError } from "@repo/common/errors.ts";
5import { takeSocket } from "@repo/common/socket.ts";
6import { StrictMap } from "@repo/common/strict-map.ts";
7import { preauthAuthnMessageSchema } from "@repo/schema/proto.ts";
8import { parseIdentId, parseRealmId } from "@repo/schema/state.ts";
9
10import { Realm, realmMap, Session } from "./state.ts";
11
12export async function preauthHandler(
13 ws: WebSocket,
14 signal: AbortSignal,
15): Promise<Session & { realm: Realm }> {
16 const timeout = timeoutSignal(3000);
17 const combinedSignal = combineSignals(signal, timeout.signal);
18
19 // wait for preauth.authn message;it will be signed by the identity,
20 // which we already have if the realm exists, and we add to a new realm if being created
21 try {
22 const data = await takeSocket(ws, combinedSignal);
23 const jwt = jwtDecodedSchema.parse(data);
24
25 const msg = await preauthAuthnMessageSchema.parseAsync(jwt);
26 const realmid = parseRealmId(jwt.aud);
27 const identid = parseIdentId(jwt.iss);
28
29 // make sure we have a realm
30 // if a new one is being created, the current identity to it
31 const realm = realmMap.ensure(realmid, () => ({
32 id: realmid,
33 sockets: new StrictMap(),
34 identities: new StrictMap([[identid, msg.pubkey]]),
35 }));
36
37 try {
38 // we've looked up the realm
39 // pubkey should match what we've got stored, and the signature should be valid
40 const pubkey = realm.identities.require(identid);
41 await jwtVerify(data, pubkey);
42
43 // the signature is good
44 return { realm, realmid, identid, pubkey };
45 } catch (e) {
46 throw new AuthError("jwt verification failed", normalizeError(e));
47 }
48 } finally {
49 timeout.cleanup();
50 }
51}