import crypto from 'crypto' import bcrypt from 'bcryptjs' import db from './db' const JWT_SECRET = process.env.JWT_SECRET || 'default-secret-change-me' interface SessionPayload { userId: number; username: string; role: string } function base64url(str: string): string { return Buffer.from(str).toString('base64url') } function base64urlDecode(str: string): string { return Buffer.from(str, 'base64url').toString() } export function signJwt(payload: SessionPayload, expiresIn: number = 86400): string { const header = { alg: 'HS256', typ: 'JWT' } const now = Math.floor(Date.now() / 1000) const body = { ...payload, iat: now, exp: now + expiresIn } const segments = [base64url(JSON.stringify(header)), base64url(JSON.stringify(body))] const signingInput = segments.join('.') segments.push(crypto.createHmac('sha256', JWT_SECRET).update(signingInput).digest('base64url')) return segments.join('.') } export function verifyJwt(token: string): SessionPayload | null { try { const parts = token.split('.') if (parts.length !== 3) return null const signingInput = parts.slice(0, 2).join('.') const expectedSig = crypto.createHmac('sha256', JWT_SECRET).update(signingInput).digest('base64url') if (parts[2] !== expectedSig) return null const payload = JSON.parse(base64urlDecode(parts[1])) if (payload.exp && payload.exp < Math.floor(Date.now() / 1000)) return null return { userId: payload.userId, username: payload.username, role: payload.role } } catch { return null } } export function hashPassword(pw: string): string { return bcrypt.hashSync(pw, 10) } export function verifyPassword(pw: string, hash: string): boolean { return bcrypt.compareSync(pw, hash) } export function hashApiKey(key: string): string { return crypto.createHash('sha256').update(key).digest('hex') } export function generateApiKey(): string { return `ak_${crypto.randomBytes(32).toString('hex')}` } export function verifySession(token: string): SessionPayload | null { return verifyJwt(token) } // 统一获取当前会话:优先 tlyq_session(共享 JWT),回退 session_assets(本地 JWT) import { cookies } from 'next/headers' import { verifySharedJwt } from '@/lib/jwt' import { ldapUserExists } from '@/lib/ldap' export async function getSession(): Promise { const cookieStore = await cookies() // 1. tlyq_session(共享 JWT,LDAP 用户) const sharedToken = cookieStore.get('tlyq_session')?.value if (sharedToken) { const sharedPayload = verifySharedJwt(sharedToken) if (sharedPayload) { // Q1: 检查 LLDAP 中用户是否仍存在(已删除则强制退出) if (!(await ldapUserExists(sharedPayload.username))) { cookieStore.set('tlyq_session', '', { maxAge: 0, path: '/' }) cookieStore.set('session_assets', '', { maxAge: 0, path: '/' }) return null } const row = db.prepare( 'SELECT id, username, role FROM users WHERE username = ? AND is_active = 1' ).get(sharedPayload.username) as { id: number; username: string; role: string } | undefined if (row) { db.prepare("UPDATE users SET last_login_at = datetime('now', '+8 hours'), last_active_at = datetime('now', '+8 hours') WHERE id = ?").run(row.id) return { userId: row.id, username: row.username, role: row.role } } // SSO 免登录:LLDAP 验证通过但本地无记录 → 自动创建(viewer 角色) db.prepare( "INSERT OR IGNORE INTO users (username, display_name, role, is_active, created_at, updated_at) VALUES (?, ?, 'viewer', 1, datetime('now', '+8 hours'), datetime('now', '+8 hours'))" ).run(sharedPayload.username, sharedPayload.displayName) const newRow = db.prepare( 'SELECT id, username, role FROM users WHERE username = ? AND is_active = 1' ).get(sharedPayload.username) as { id: number; username: string; role: string } | undefined if (newRow) { return { userId: newRow.id, username: newRow.username, role: newRow.role } } } } // 2. session_assets(本地 JWT,admin 账号或紧急绕过) const token = cookieStore.get('session_assets')?.value if (token) { const payload = verifyJwt(token) if (payload) { db.prepare("UPDATE users SET last_login_at = datetime('now', '+8 hours'), last_active_at = datetime('now', '+8 hours') WHERE id = ?").run(payload.userId) return payload } } return null } export function verifyApiKey(key: string): { id: number; name: string; permissions: string[] } | null { if (!key.startsWith('ak_')) return null const keyHash = hashApiKey(key) const row = db.prepare('SELECT id, name, permissions FROM api_keys WHERE key_hash = ? AND is_active = 1') .get(keyHash) as { id: number; name: string; permissions: string } | undefined if (!row) return null if (row.expires_at && new Date(row.expires_at) < new Date()) return null db.prepare("UPDATE api_keys SET last_used_at = datetime('now', '+8 hours') WHERE id = ?").run(row.id) return { id: row.id, name: row.name, permissions: JSON.parse(row.permissions) } }