aboutsummaryrefslogtreecommitdiffstats
path: root/packages/shared/signedTokens.ts
diff options
context:
space:
mode:
authorMohamed Bassem <me@mbassem.com>2025-06-01 20:46:41 +0100
committerGitHub <noreply@github.com>2025-06-01 20:46:41 +0100
commitea1d0023bfee55358ebb1a96f3d06e783a219c0d (patch)
tree5bddd451728cb7dd377574a9ea1ea591bca069c4 /packages/shared/signedTokens.ts
parent3afe1e21df6dcc0483e74e0db02d9d82af32ecea (diff)
downloadkarakeep-ea1d0023bfee55358ebb1a96f3d06e783a219c0d.tar.zst
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
Diffstat (limited to 'packages/shared/signedTokens.ts')
-rw-r--r--packages/shared/signedTokens.ts71
1 files changed, 71 insertions, 0 deletions
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<typeof zSignedTokenPayload>;
+
+export function createSignedToken(
+ payload: unknown,
+ expiryEpoch?: number,
+): string {
+ const expiresAt = expiryEpoch ?? Date.now() + 5 * 60 * 1000; // 5 minutes from now
+
+ const toBeSigned: z.infer<typeof zTokenPayload> = {
+ payload,
+ expiresAt,
+ };
+
+ const payloadString = JSON.stringify(toBeSigned);
+ const signature = crypto
+ .createHmac("sha256", serverConfig.signingSecret())
+ .update(payloadString)
+ .digest("hex");
+
+ const tokenData: z.infer<typeof zSignedTokenPayload> = {
+ payload: toBeSigned,
+ signature,
+ };
+
+ return Buffer.from(JSON.stringify(tokenData)).toString("base64");
+}
+
+export function verifySignedToken<T>(
+ token: string,
+ schema: z.ZodSchema<T>,
+): 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;
+ }
+}