Files
Lageplan/src/lib/auth.ts

129 lines
3.6 KiB
TypeScript

import { SignJWT, jwtVerify } from 'jose'
import { cookies } from 'next/headers'
import { prisma } from './db'
import bcrypt from 'bcryptjs'
const secretValue = process.env.NEXTAUTH_SECRET
if (!secretValue || secretValue.length < 32) {
console.warn('[AUTH] WARNING: NEXTAUTH_SECRET is missing or too short (<32 chars). Set a strong secret in production!')
}
const JWT_SECRET = new TextEncoder().encode(
secretValue || 'dev-only-fallback-do-not-use-in-production-' + Date.now()
)
export interface UserPayload {
id: string
email: string
name: string
role: 'SERVER_ADMIN' | 'TENANT_ADMIN' | 'OPERATOR' | 'VIEWER'
tenantId?: string
tenantSlug?: string
emailVerified?: boolean
}
export async function createToken(user: UserPayload, rememberMe = false): Promise<string> {
return await new SignJWT({ user })
.setProtectedHeader({ alg: 'HS256' })
.setIssuedAt()
.setExpirationTime(rememberMe ? '30d' : '24h')
.sign(JWT_SECRET)
}
export async function verifyToken(token: string): Promise<UserPayload | null> {
try {
const { payload } = await jwtVerify(token, JWT_SECRET)
return payload.user as UserPayload
} catch {
return null
}
}
export async function getSession(): Promise<UserPayload | null> {
const cookieStore = await cookies()
const token = cookieStore.get('auth-token')?.value
if (!token) return null
return await verifyToken(token)
}
export async function login(
email: string,
password: string
): Promise<{ success: boolean; user?: UserPayload; error?: string }> {
const user = await (prisma.user.findUnique({
where: { email },
select: {
id: true,
email: true,
name: true,
password: true,
role: true,
emailVerified: true,
},
}) as any)
if (!user) {
return { success: false, error: 'E-Mail oder Passwort falsch' }
}
const isValidPassword = await bcrypt.compare(password, user.password)
if (!isValidPassword) {
return { success: false, error: 'E-Mail oder Passwort falsch' }
}
// Track email verification status (allow login regardless)
const emailVerified = (user as any).emailVerified !== false
// Get first tenant membership for non-server-admins
let tenantId: string | undefined
let tenantSlug: string | undefined
if ((user.role as string) !== 'SERVER_ADMIN') {
const membership = await (prisma as any).tenantMembership.findFirst({
where: { userId: user.id },
include: { tenant: true },
orderBy: { createdAt: 'asc' },
})
if (membership) {
// Check if tenant is active
if (!membership.tenant.isActive) {
return { success: false, error: 'Ihr Mandant wurde gesperrt. Bitte kontaktieren Sie den Administrator.' }
}
tenantId = membership.tenantId
tenantSlug = membership.tenant.slug
}
}
const userPayload: UserPayload = {
id: user.id,
email: user.email,
name: user.name,
role: (user.role === 'ADMIN' ? 'SERVER_ADMIN' : user.role) as UserPayload['role'],
tenantId,
tenantSlug,
emailVerified,
}
return { success: true, user: userPayload }
}
export async function hashPassword(password: string): Promise<string> {
return await bcrypt.hash(password, 12)
}
export function canEdit(role: string): boolean {
return role === 'SERVER_ADMIN' || role === 'TENANT_ADMIN' || role === 'OPERATOR'
}
export function isAdmin(role: string): boolean {
return role === 'SERVER_ADMIN' || role === 'TENANT_ADMIN'
}
export function isServerAdmin(role: string): boolean {
return role === 'SERVER_ADMIN'
}
export function isTenantAdmin(role: string): boolean {
return role === 'TENANT_ADMIN'
}