Expand the calculator with a multi-step wizard flow, admin dashboard with quote tracking, login/auth system, distance API integration, and history page. Add new UI components (dialog, progress, select, slider, switch), update pricing logic, and improve the overall design with new assets. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
116 lines
2.9 KiB
TypeScript
116 lines
2.9 KiB
TypeScript
import bcrypt from "bcrypt"
|
|
import { cookies } from "next/headers"
|
|
import {
|
|
createSession,
|
|
deleteSession,
|
|
getSession,
|
|
getUserByEmail,
|
|
getUserById,
|
|
createUser as dbCreateUser,
|
|
cleanExpiredSessions,
|
|
type User,
|
|
} from "./db"
|
|
|
|
const SALT_ROUNDS = 10
|
|
const SESSION_COOKIE_NAME = "session"
|
|
const SESSION_DURATION_DAYS = 7
|
|
|
|
export async function hashPassword(password: string): Promise<string> {
|
|
return bcrypt.hash(password, SALT_ROUNDS)
|
|
}
|
|
|
|
export async function verifyPassword(password: string, hash: string): Promise<boolean> {
|
|
return bcrypt.compare(password, hash)
|
|
}
|
|
|
|
function generateSessionId(): string {
|
|
const bytes = new Uint8Array(32)
|
|
crypto.getRandomValues(bytes)
|
|
return Array.from(bytes)
|
|
.map((b) => b.toString(16).padStart(2, "0"))
|
|
.join("")
|
|
}
|
|
|
|
export async function login(
|
|
email: string,
|
|
password: string
|
|
): Promise<{ success: true; user: User } | { success: false; error: string }> {
|
|
const user = getUserByEmail(email)
|
|
|
|
if (!user) {
|
|
return { success: false, error: "Forkert email eller adgangskode" }
|
|
}
|
|
|
|
const validPassword = await verifyPassword(password, user.passwordHash)
|
|
if (!validPassword) {
|
|
return { success: false, error: "Forkert email eller adgangskode" }
|
|
}
|
|
|
|
// Clean up old sessions periodically
|
|
cleanExpiredSessions()
|
|
|
|
// Create new session
|
|
const sessionId = generateSessionId()
|
|
const expiresAt = new Date()
|
|
expiresAt.setDate(expiresAt.getDate() + SESSION_DURATION_DAYS)
|
|
|
|
createSession(sessionId, user.id, expiresAt)
|
|
|
|
// Set cookie
|
|
const cookieStore = await cookies()
|
|
cookieStore.set(SESSION_COOKIE_NAME, sessionId, {
|
|
httpOnly: true,
|
|
secure: process.env.NODE_ENV === "production",
|
|
sameSite: "lax",
|
|
expires: expiresAt,
|
|
path: "/",
|
|
})
|
|
|
|
return {
|
|
success: true,
|
|
user: { id: user.id, email: user.email, name: user.name, createdAt: user.createdAt },
|
|
}
|
|
}
|
|
|
|
export async function logout(): Promise<void> {
|
|
const cookieStore = await cookies()
|
|
const sessionId = cookieStore.get(SESSION_COOKIE_NAME)?.value
|
|
|
|
if (sessionId) {
|
|
deleteSession(sessionId)
|
|
cookieStore.delete(SESSION_COOKIE_NAME)
|
|
}
|
|
}
|
|
|
|
export async function getCurrentUser(): Promise<User | null> {
|
|
const cookieStore = await cookies()
|
|
const sessionId = cookieStore.get(SESSION_COOKIE_NAME)?.value
|
|
|
|
if (!sessionId) return null
|
|
|
|
const session = getSession(sessionId)
|
|
if (!session) return null
|
|
|
|
// Check if session is expired
|
|
if (session.expiresAt < new Date()) {
|
|
deleteSession(sessionId)
|
|
return null
|
|
}
|
|
|
|
return getUserById(session.userId)
|
|
}
|
|
|
|
export async function isAuthenticated(): Promise<boolean> {
|
|
const user = await getCurrentUser()
|
|
return user !== null
|
|
}
|
|
|
|
// Helper to create users (run from CLI or seed script)
|
|
export async function createUserWithPassword(
|
|
email: string,
|
|
password: string,
|
|
name: string
|
|
): Promise<User> {
|
|
const passwordHash = await hashPassword(password)
|
|
return dbCreateUser(email, passwordHash, name)
|
|
}
|