Initial commit: Lageplan v1.0 - Next.js 15.5, React 19
This commit is contained in:
128
src/lib/auth.ts
Normal file
128
src/lib/auth.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
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
|
||||
}
|
||||
|
||||
export async function createToken(user: UserPayload): Promise<string> {
|
||||
return await new SignJWT({ user })
|
||||
.setProtectedHeader({ alg: 'HS256' })
|
||||
.setIssuedAt()
|
||||
.setExpirationTime('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: 'Benutzer nicht gefunden' }
|
||||
}
|
||||
|
||||
const isValidPassword = await bcrypt.compare(password, user.password)
|
||||
if (!isValidPassword) {
|
||||
return { success: false, error: 'Ungültiges Passwort' }
|
||||
}
|
||||
|
||||
// Check email verification (skip for SERVER_ADMIN and users created before verification was added)
|
||||
if ((user as any).emailVerified === false && (user.role as string) !== 'SERVER_ADMIN') {
|
||||
return { success: false, error: 'Bitte bestätigen Sie zuerst Ihre E-Mail-Adresse. Prüfen Sie Ihren Posteingang.' }
|
||||
}
|
||||
|
||||
// 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,
|
||||
}
|
||||
|
||||
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'
|
||||
}
|
||||
Reference in New Issue
Block a user