import { NextResponse } from 'next/server' import { cookies } from 'next/headers' import { execFileSync } from 'child_process' import { verifySharedJwt } from '@/lib/jwt' import { isLldapAdmin } from '@/lib/ldap' import { queryLldap, execLldap, esc } from '@/lib/lldap-db' function checkAdmin() { return async () => { const cookieStore = await cookies() const token = cookieStore.get('tlyq_session')?.value if (!token) return false const session = verifySharedJwt(token) return session ? isLldapAdmin(session.username) : false } } function siteSQL(dbPath: string, sql: string): void { try { execFileSync('sqlite3', [dbPath], { input: sql, timeout: 3000 }) } catch {} } function nowStr(): string { const d = new Date() return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')} ${String(d.getHours()).padStart(2,'0')}:${String(d.getMinutes()).padStart(2,'0')}:${String(d.getSeconds()).padStart(2,'0')}` } // GET — 列出 LLDAP 中所有用户 export async function GET() { const isAdmin = await checkAdmin()() if (!isAdmin) return NextResponse.json({ error: 'Forbidden' }, { status: 403 }) try { const out = queryLldap(`SELECT user_id, email, display_name, creation_date FROM users ORDER BY creation_date DESC`) const users = out.split('\n').filter(Boolean).map(line => { const [user_id, email, display_name, creation_date] = line.split('|') return { username: user_id, email, displayName: display_name || user_id, createdAt: creation_date } }) return NextResponse.json({ users }) } catch { return NextResponse.json({ error: '查询失败' }, { status: 500 }) } } // DELETE — 删除用户(LLDAP + 各站点) export async function DELETE(request: Request) { const isAdmin = await checkAdmin()() if (!isAdmin) return NextResponse.json({ error: 'Forbidden' }, { status: 403 }) try { const { username } = await request.json() if (!username) return NextResponse.json({ error: '用户名不能为空' }, { status: 400 }) if (username === 'admin' || username === 'localadmin') { return NextResponse.json({ error: '不能删除系统保留用户' }, { status: 400 }) } const safeUser = esc(username) execLldap(`DELETE FROM users WHERE user_id='${safeUser}'`) const results: Record = {} for (const [site, dbPath] of Object.entries({ assets: process.env.ASSETS_DB_PATH || '/data/other-sites/assets/assets.db', issue: process.env.ISSUE_DB_PATH || '/data/other-sites/issue/issue.db', })) { try { siteSQL(dbPath, `DELETE FROM users WHERE username='${safeUser}'`); results[site] = true } catch { results[site] = false } } return NextResponse.json({ success: true, deleted: results }) } catch { return NextResponse.json({ error: '删除失败' }, { status: 500 }) } } // PATCH — 修改用户信息 export async function PATCH(request: Request) { const isAdmin = await checkAdmin()() if (!isAdmin) return NextResponse.json({ error: 'Forbidden' }, { status: 403 }) try { const { username, email, displayName } = await request.json() if (!username) return NextResponse.json({ error: '用户名不能为空' }, { status: 400 }) if (email === undefined && displayName === undefined) { return NextResponse.json({ error: '至少需要 email 或 displayName' }, { status: 400 }) } if (email !== undefined && email !== '' && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) { return NextResponse.json({ error: '邮箱格式不合法' }, { status: 400 }) } const safeUser = esc(username) let lldapSets: string[] = [], siteSets: string[] = [] if (email !== undefined) { const safeEmail = esc(email || '') lldapSets.push(`email = '${safeEmail}'`, `lowercase_email = LOWER('${safeEmail}')`) siteSets.push(`email = '${safeEmail}'`) } if (displayName !== undefined) { const safeName = esc(displayName) lldapSets.push(`display_name = '${safeName}'`) siteSets.push(`display_name = '${safeName}'`) } lldapSets.push(`modified_date = '${nowStr()}'`) siteSets.push(`updated_at = datetime('now', '+8 hours')`) execLldap(`UPDATE users SET ${lldapSets.join(', ')} WHERE user_id = '${safeUser}'`) const siteSql = `UPDATE users SET ${siteSets.join(', ')} WHERE username = '${safeUser}'` for (const dbPath of [process.env.ASSETS_DB_PATH || '/data/other-sites/assets/assets.db', process.env.ISSUE_DB_PATH || '/data/other-sites/issue/issue.db']) { siteSQL(dbPath, siteSql) } return NextResponse.json({ success: true, username, email, displayName }) } catch { return NextResponse.json({ error: '修改失败' }, { status: 500 }) } }