implement session via cloudflare kv

This commit is contained in:
2025-02-22 13:23:08 +01:00
parent c0e487b320
commit 1d12aac6ec
3 changed files with 94 additions and 1 deletions

6
app/src/app.d.ts vendored
View File

@@ -1,9 +1,13 @@
import { KVNamespace } from '@cloudflare/workers-types';
// See https://svelte.dev/docs/kit/types#app.d.ts
// for information about these interfaces
declare global {
namespace App {
interface Platform {
env: Env
env: {
GH_MEDIA: R2Bucket;
GH_SESSIONS: KVNamespace;
};
cf: CfProperties
ctx: ExecutionContext
}

View File

@@ -0,0 +1,79 @@
import type { KVNamespace } from "@cloudflare/workers-types";
import { encodeBase32, encodeHexLowerCase } from "@oslojs/encoding";
import { sha256 } from "@oslojs/crypto/sha2";
import type { RequestEvent } from "@sveltejs/kit";
// in seconds
const EXPIRATION_TTL: number = 60 * 60 * 24 * 7;
export async function validateSessionToken(token: string, sessions: KVNamespace): Promise<SessionValidationResult> {
const sessionId = encodeHexLowerCase(sha256(new TextEncoder().encode(token)));
const session: Session | null = await sessions.get(sessionId, { type: "json" });
if (!session) {
return null;
}
if (Date.now() >= session.expiresAt.getTime() - 1000 * 60 * 60 * 24 * 15) {
await sessions.put(sessionId, JSON.stringify(session), { expirationTtl: EXPIRATION_TTL });
}
return session;
}
export async function invalidateSession(sessionId: string, sessions: KVNamespace): Promise<void> {
await sessions.delete(sessionId);
}
export async function invalidateUserSessions(userId: number, sessions: KVNamespace): Promise<void> {
const keys = await sessions.list({ prefix: `${userId}:` });
for (const key of keys.keys) {
await sessions.delete(key.name);
}
}
export function setSessionTokenCookie(event: RequestEvent, token: string, expiresAt: Date): void {
event.cookies.set("session", token, {
httpOnly: true,
path: "/",
secure: import.meta.env.PROD,
sameSite: "lax",
expires: expiresAt
});
}
export function deleteSessionTokenCookie(event: RequestEvent): void {
event.cookies.set("session", "", {
httpOnly: true,
path: "/",
secure: import.meta.env.PROD,
sameSite: "lax",
maxAge: 0
});
}
export function generateSessionToken(): string {
const tokenBytes = new Uint8Array(20);
crypto.getRandomValues(tokenBytes);
const token = encodeBase32(tokenBytes).toLowerCase();
return token;
}
export async function createSession(token: string, userId: number, sessions: KVNamespace): Promise<Session> {
const sessionId = `${userId}:${encodeHexLowerCase(sha256(new TextEncoder().encode(token)))}`;
const session: Session = {
id: sessionId,
userId,
expiresAt: new Date(Date.now() + 1000 * EXPIRATION_TTL)
};
await sessions.put(sessionId, JSON.stringify(session), { expirationTtl: EXPIRATION_TTL });
return session;
}
export interface Session {
id: string;
expiresAt: Date;
userId: number;
}
type SessionValidationResult = Session | null;

View File

@@ -0,0 +1,10 @@
import { redirect } from "@sveltejs/kit";
import type { RequestEvent } from "./$types";
export async function load(event: RequestEvent) {
if (event.locals.session !== null && event.locals.user !== null) {
return redirect(302, "/");
}
return {};
}