3fc5c9ff7a
- Datenbank auf utf8mb4_unicode_ci migriert (migrate_to_utf8mb4.sh) - beos: Spalte 'role' (kommagetrennte Rollen: guide, admin, key, deleted) - BEO-Auswahl im Formular filtert nur noch role='guide' - logbuch_objekte: ObjektName-Spalte entfernt, stattdessen JOIN auf objekte - lib/db.ts: charset utf8mb4 in Connection-Pool - Session und Auth um role-Feld erweitert - compose.yml: phpMyAdmin mit Traefik unter /myadmin - compose.yml: MySQL auf 127.0.0.1:3336 für SSH-Tunnel (lokale Entwicklung) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
64 lines
1.9 KiB
TypeScript
64 lines
1.9 KiB
TypeScript
import { cookies } from 'next/headers';
|
|
import { SignJWT, jwtVerify } from 'jose';
|
|
|
|
const SESSION_COOKIE_NAME = 'logbuch_session';
|
|
const SESSION_DURATION = 60 * 60 * 1000;
|
|
|
|
const secretKey = process.env.AUTH_SECRET || 'logbuch-secret-change-in-production';
|
|
const key = new TextEncoder().encode(secretKey);
|
|
|
|
export interface SessionData {
|
|
kuerzel: string;
|
|
beoId: number;
|
|
beoName: string;
|
|
mustChangePassword: boolean;
|
|
isAuthenticated: boolean;
|
|
expiresAt: number;
|
|
role: string | null;
|
|
}
|
|
|
|
async function encrypt(payload: SessionData): Promise<string> {
|
|
return new SignJWT(payload as unknown as Record<string, unknown>)
|
|
.setProtectedHeader({ alg: 'HS256' })
|
|
.setIssuedAt()
|
|
.setExpirationTime(new Date(payload.expiresAt))
|
|
.sign(key);
|
|
}
|
|
|
|
async function decrypt(token: string): Promise<SessionData | null> {
|
|
try {
|
|
const { payload } = await jwtVerify(token, key, { algorithms: ['HS256'] });
|
|
return payload as unknown as SessionData;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export async function createSession(data: Omit<SessionData, 'expiresAt'>): Promise<void> {
|
|
const expiresAt = Date.now() + SESSION_DURATION;
|
|
const session: SessionData = { ...data, expiresAt };
|
|
const encrypted = await encrypt(session);
|
|
const cookieStore = await cookies();
|
|
cookieStore.set(SESSION_COOKIE_NAME, encrypted, {
|
|
httpOnly: true,
|
|
secure: process.env.NODE_ENV === 'production',
|
|
expires: expiresAt,
|
|
sameSite: 'lax',
|
|
path: '/',
|
|
});
|
|
}
|
|
|
|
export async function getSession(): Promise<SessionData | null> {
|
|
const cookieStore = await cookies();
|
|
const cookie = cookieStore.get(SESSION_COOKIE_NAME);
|
|
if (!cookie?.value) return null;
|
|
const session = await decrypt(cookie.value);
|
|
if (!session || session.expiresAt < Date.now()) return null;
|
|
return session;
|
|
}
|
|
|
|
export async function deleteSession(): Promise<void> {
|
|
const cookieStore = await cookies();
|
|
cookieStore.delete(SESSION_COOKIE_NAME);
|
|
}
|