assets-ai/src/lib/auth.ts

101 lines
5.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<SessionPayload | null> {
const cookieStore = await cookies()
// 1. tlyq_session共享 JWTLDAP 用户)
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本地 JWTadmin 账号或紧急绕过)
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) }
}