oa-ai/src/app/api/admin/users/route.ts

115 lines
4.7 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 { 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<string, boolean> = {}
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 })
}
}