mirror of
https://gitlab.w-hs.de/an14051/backerup-website.git
synced 2026-07-27 17:35:29 +00:00
feat: implement user authentication and admin management
- Add user management API endpoints for listing and creating users. - Implement login and logout functionality with rate limiting. - Create UI components for login form, logout button, and animated elements. - Add session management with JWT and secure cookie handling. - Seed initial admin user if no users exist. - Introduce utility functions for password hashing and user authentication. - Set up proxy middleware for route protection and security headers. - Create a JSON file for user data storage. - Add links configuration for external resources.
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
import { SignJWT, jwtVerify } from 'jose'
|
||||
import { cookies } from 'next/headers'
|
||||
|
||||
export type Role = 'admin' | 'user'
|
||||
|
||||
export interface SessionPayload {
|
||||
userId: string
|
||||
username: string
|
||||
role: Role
|
||||
}
|
||||
|
||||
export const SESSION_COOKIE = 'backerup_session'
|
||||
const SESSION_DURATION_MS = 7 * 24 * 60 * 60 * 1000 // 7 days
|
||||
|
||||
// Dev fallback — NOT safe for production (use AUTH_SECRET env var)
|
||||
const DEV_FALLBACK_SECRET = 'dev-secret-NOT-for-production-change-me-32chars!!'
|
||||
|
||||
export function getSecretKey(): Uint8Array {
|
||||
const secret = process.env.AUTH_SECRET
|
||||
if (!secret) {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
return new TextEncoder().encode(DEV_FALLBACK_SECRET)
|
||||
}
|
||||
throw new Error('AUTH_SECRET environment variable is required in production')
|
||||
}
|
||||
if (secret.length < 32) {
|
||||
throw new Error('AUTH_SECRET must be at least 32 characters long')
|
||||
}
|
||||
return new TextEncoder().encode(secret)
|
||||
}
|
||||
|
||||
/** Creates and stores a session JWT in an httpOnly cookie. */
|
||||
export async function createSession(payload: SessionPayload): Promise<void> {
|
||||
const expiresAt = new Date(Date.now() + SESSION_DURATION_MS)
|
||||
const token = await new SignJWT({ ...payload })
|
||||
.setProtectedHeader({ alg: 'HS256' })
|
||||
.setIssuedAt()
|
||||
.setExpirationTime(expiresAt)
|
||||
.sign(getSecretKey())
|
||||
|
||||
const cookieStore = await cookies()
|
||||
cookieStore.set(SESSION_COOKIE, token, {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
sameSite: 'lax',
|
||||
expires: expiresAt,
|
||||
path: '/',
|
||||
})
|
||||
}
|
||||
|
||||
/** Reads and validates the current session cookie. Returns null if missing/invalid. */
|
||||
export async function getSession(): Promise<SessionPayload | null> {
|
||||
const cookieStore = await cookies()
|
||||
const token = cookieStore.get(SESSION_COOKIE)?.value
|
||||
if (!token) return null
|
||||
return verifyToken(token)
|
||||
}
|
||||
|
||||
/** Deletes the session cookie. */
|
||||
export async function deleteSession(): Promise<void> {
|
||||
const cookieStore = await cookies()
|
||||
cookieStore.delete(SESSION_COOKIE)
|
||||
}
|
||||
|
||||
/** Verifies a JWT token string. Edge-runtime safe (uses jose). */
|
||||
export async function verifyToken(token: string): Promise<SessionPayload | null> {
|
||||
try {
|
||||
const { payload } = await jwtVerify(token, getSecretKey(), { algorithms: ['HS256'] })
|
||||
return {
|
||||
userId: payload.userId as string,
|
||||
username: payload.username as string,
|
||||
role: payload.role as Role,
|
||||
}
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user