Skip to content
File

Blob: src/worker/services/inbox/session-store.ts

typescript83 lines
1import { nanoid } from "nanoid";
2import type { SessionRecord } from "@/shared/contracts";
3import { decodeWebSocketTicket } from "@/worker/security";
4import { WEBSOCKET_TICKET_TTL_MS } from "./shared";
5 
6function getSessionTokenKey(token: string) {
7 return `token:${token}`;
8}
9 
10function getInboxTokenLookupKey(address: string) {
11 return `inbox-token:${address}`;
12}
13 
14async function persistSessionToken(env: Env, token: string, session: SessionRecord, expirationTtl: number) {
15 await env.SESSIONS.put(getSessionTokenKey(token), JSON.stringify(session), {
16 expirationTtl,
17 });
18 
19 if (session.type === "user") {
20 await env.SESSIONS.put(getInboxTokenLookupKey(session.address), token, {
21 expirationTtl,
22 });
23 }
24}
25 
26export async function refreshSessionToken(env: Env, token: string, session: SessionRecord, expiresAt: Date) {
27 const ttlMs = expiresAt.getTime() - Date.now();
28 const expirationTtl = Math.max(60, Math.ceil(ttlMs / 1000));
29 
30 await persistSessionToken(env, token, session, expirationTtl);
31}
32 
33export async function createSessionToken(env: Env, session: SessionRecord, ttlMs: number) {
34 const token = `tok_${nanoid(32)}`;
35 const expirationTtl = Math.max(60, Math.ceil(ttlMs / 1000));
36 
37 await persistSessionToken(env, token, session, expirationTtl);
38 
39 return token;
40}
41 
42export async function revokeInboxSessionToken(env: Env, address: string) {
43 const lookupKey = getInboxTokenLookupKey(address);
44 const token = await env.SESSIONS.get(lookupKey);
45 
46 await Promise.all([
47 env.SESSIONS.delete(lookupKey),
48 ...(token ? [env.SESSIONS.delete(getSessionTokenKey(token))] : []),
49 ]);
50}
51 
52export async function revokeAdminSessionToken(env: Env, token: string) {
53 if (!token) {
54 return;
55 }
56 await env.SESSIONS.delete(getSessionTokenKey(token));
57}
58 
59export async function createWebSocketTicket(env: Env, address: string, session: SessionRecord) {
60 const ticket = `wst_${nanoid(24)}`;
61 
62 await env.SESSIONS.put(`ws-ticket:${ticket}`, JSON.stringify({ address, session }), {
63 expirationTtl: Math.max(30, Math.ceil(WEBSOCKET_TICKET_TTL_MS / 1000)),
64 });
65 
66 return ticket;
67}
68 
69export async function consumeWebSocketTicket(env: Env, ticket: string | null | undefined) {
70 if (!ticket) {
71 return null;
72 }
73 
74 const key = `ws-ticket:${ticket}`;
75 const raw = await env.SESSIONS.get(key);
76 if (!raw) {
77 return null;
78 }
79 
80 await env.SESSIONS.delete(key);
81 return decodeWebSocketTicket(raw);
82}