From ea1d0023bfee55358ebb1a96f3d06e783a219c0d Mon Sep 17 00:00:00 2001 From: Mohamed Bassem Date: Sun, 1 Jun 2025 20:46:41 +0100 Subject: feat: Add support for public lists (#1511) * WIP: public lists * Drop viewing modes * Add the public endpoint for assets * regen the openapi spec * proper handling for different asset types * Add num bookmarks and a no bookmark banner * Correctly set page title * Add a not-found page * merge the RSS and public list endpoints * Add e2e tests for the public endpoints * Redesign the share list modal * Make NEXTAUTH_SECRET not required * propery render text bookmarks * rebase migration * fix public token tests * Add more tests --- packages/shared/signedTokens.ts | 71 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 packages/shared/signedTokens.ts (limited to 'packages/shared/signedTokens.ts') diff --git a/packages/shared/signedTokens.ts b/packages/shared/signedTokens.ts new file mode 100644 index 00000000..b5e27f3e --- /dev/null +++ b/packages/shared/signedTokens.ts @@ -0,0 +1,71 @@ +import crypto from "node:crypto"; +import { z } from "zod"; + +import serverConfig from "./config"; + +const zTokenPayload = z.object({ + payload: z.unknown(), + expiresAt: z.number(), +}); + +const zSignedTokenPayload = z.object({ + payload: zTokenPayload, + signature: z.string(), +}); + +export type SignedTokenPayload = z.infer; + +export function createSignedToken( + payload: unknown, + expiryEpoch?: number, +): string { + const expiresAt = expiryEpoch ?? Date.now() + 5 * 60 * 1000; // 5 minutes from now + + const toBeSigned: z.infer = { + payload, + expiresAt, + }; + + const payloadString = JSON.stringify(toBeSigned); + const signature = crypto + .createHmac("sha256", serverConfig.signingSecret()) + .update(payloadString) + .digest("hex"); + + const tokenData: z.infer = { + payload: toBeSigned, + signature, + }; + + return Buffer.from(JSON.stringify(tokenData)).toString("base64"); +} + +export function verifySignedToken( + token: string, + schema: z.ZodSchema, +): T | null { + try { + const tokenData = zSignedTokenPayload.parse( + JSON.parse(Buffer.from(token, "base64").toString()), + ); + const { payload, signature } = tokenData; + + // Verify signature + const expectedSignature = crypto + .createHmac("sha256", serverConfig.signingSecret()) + .update(JSON.stringify(payload)) + .digest("hex"); + + if (signature !== expectedSignature) { + return null; + } + // Check expiry + if (Date.now() > payload.expiresAt) { + return null; + } + + return schema.parse(payload.payload); + } catch { + return null; + } +} -- cgit v1.2.3-70-g09d2