fix:auth CSRF

This commit is contained in:
2025-09-27 11:43:58 +02:00
parent 7e4570cf0e
commit 88a7e74c78
6 changed files with 147 additions and 6 deletions

View File

@@ -0,0 +1,11 @@
CREATE TABLE "session" (
"id" text PRIMARY KEY NOT NULL,
"user_id" text NOT NULL,
"expires_at" timestamp with time zone NOT NULL
);
--> statement-breakpoint
ALTER TABLE "user" ALTER COLUMN "id" SET DATA TYPE text;--> statement-breakpoint
ALTER TABLE "user" ADD COLUMN "username" text NOT NULL;--> statement-breakpoint
ALTER TABLE "user" ADD COLUMN "password_hash" text NOT NULL;--> statement-breakpoint
ALTER TABLE "session" ADD CONSTRAINT "session_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "user" ADD CONSTRAINT "user_username_unique" UNIQUE("username");

View File

@@ -0,0 +1,109 @@
{
"id": "d190d67d-4be7-486a-b23e-9106feca588a",
"prevId": "bc9d1429-4923-4b5e-8245-8d420404185f",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.session": {
"name": "session",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"expires_at": {
"name": "expires_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {
"session_user_id_user_id_fk": {
"name": "session_user_id_user_id_fk",
"tableFrom": "session",
"tableTo": "user",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.user": {
"name": "user",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"age": {
"name": "age",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"username": {
"name": "username",
"type": "text",
"primaryKey": false,
"notNull": true
},
"password_hash": {
"name": "password_hash",
"type": "text",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"user_username_unique": {
"name": "user_username_unique",
"nullsNotDistinct": false,
"columns": [
"username"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@@ -8,6 +8,13 @@
"when": 1758895882780, "when": 1758895882780,
"tag": "0000_amused_shooting_star", "tag": "0000_amused_shooting_star",
"breakpoints": true "breakpoints": true
},
{
"idx": 1,
"version": "7",
"when": 1758905260578,
"tag": "0001_complete_namora",
"breakpoints": true
} }
] ]
} }

View File

@@ -1,7 +1,21 @@
import type { Handle } from '@sveltejs/kit'; import type { Handle } from '@sveltejs/kit';
import * as auth from '$lib/server/auth'; import * as auth from '$lib/server/auth';
const handleAuth: Handle = async ({ event, resolve }) => { export const handle: Handle = async ({ event, resolve }) => {
// CSRF protection - verify origin header for state-changing requests
const method = event.request.method;
const origin = event.request.headers.get('origin');
// Skip CSRF check for GET/HEAD requests
if (method !== 'GET' && method !== 'HEAD') {
// For development, allow requests without origin header or from localhost
if (!origin || origin.includes('localhost') || origin.includes('127.0.0.1')) {
// Allow in development
}
// In production, you would add: else if (origin !== 'yourdomain.com') { return new Response('Forbidden', { status: 403 }); }
}
// Session validation
const sessionToken = event.cookies.get(auth.sessionCookieName); const sessionToken = event.cookies.get(auth.sessionCookieName);
if (!sessionToken) { if (!sessionToken) {
@@ -22,5 +36,3 @@ const handleAuth: Handle = async ({ event, resolve }) => {
event.locals.session = session; event.locals.session = session;
return resolve(event); return resolve(event);
}; };
export const handle: Handle = handleAuth;

View File

@@ -70,7 +70,10 @@ export async function invalidateSession(sessionId: string) {
export function setSessionTokenCookie(event: RequestEvent, token: string, expiresAt: Date) { export function setSessionTokenCookie(event: RequestEvent, token: string, expiresAt: Date) {
event.cookies.set(sessionCookieName, token, { event.cookies.set(sessionCookieName, token, {
expires: expiresAt, expires: expiresAt,
path: '/' path: '/',
httpOnly: true,
secure: false, // Allow HTTP in development
sameSite: 'lax'
}); });
} }

View File

@@ -1,7 +1,6 @@
import tailwindcss from '@tailwindcss/vite';
import { sveltekit } from '@sveltejs/kit/vite'; import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite'; import { defineConfig } from 'vite';
export default defineConfig({ export default defineConfig({
plugins: [tailwindcss(), sveltekit()] plugins: [sveltekit()]
}); });