94 lines
4.3 KiB
TypeScript
94 lines
4.3 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server'
|
||
import { createToken } from '@/lib/auth'
|
||
import { initDatabase } from '@/lib/db-schema'
|
||
import { signSharedJwt, sharedCookieConfig } from '@/lib/jwt-shared'
|
||
import { ldapAuth } from '@/lib/ldap'
|
||
import { getDb } from '@/lib/db'
|
||
import bcrypt from 'bcryptjs'
|
||
|
||
export async function POST(request: NextRequest) {
|
||
try {
|
||
initDatabase()
|
||
const { username, password } = await request.json()
|
||
if (!username || !password) return NextResponse.json({ error: '请输入用户名和密码' }, { status: 400 })
|
||
|
||
let userId: number
|
||
let role: string
|
||
let displayName: string
|
||
const db = getDb()
|
||
|
||
// 1. localadmin:纯本地 BCrypt,不依赖 LLDAP
|
||
if (username === 'localadmin') {
|
||
const localUser = db.prepare(
|
||
'SELECT * FROM users WHERE username = ? AND is_active = 1'
|
||
).get(username) as { id: number; username: string; display_name: string; role: string; password_hash: string } | undefined
|
||
if (!localUser || !bcrypt.compareSync(password, localUser.password_hash)) {
|
||
return NextResponse.json({ error: '用户名或密码错误' }, { status: 401 })
|
||
}
|
||
userId = localUser.id
|
||
role = localUser.role
|
||
displayName = localUser.display_name || username
|
||
} else {
|
||
// 2. 其他用户:LLDAP 优先
|
||
const ldapResult = await ldapAuth(username, password)
|
||
|
||
if (ldapResult.success) {
|
||
// LDAP 认证成功 → 更新本地密码缓存 + 自动创建用户
|
||
displayName = ldapResult.displayName || username
|
||
const pwHash = bcrypt.hashSync(password, 10)
|
||
const existing = db.prepare(
|
||
'SELECT id, role FROM users WHERE username = ? AND is_active = 1'
|
||
).get(username) as { id: number; role: string } | undefined
|
||
|
||
if (existing) {
|
||
db.prepare('UPDATE users SET password_hash = ?, display_name = ? WHERE id = ?')
|
||
.run(pwHash, displayName, existing.id)
|
||
userId = existing.id
|
||
role = existing.role
|
||
} else {
|
||
db.prepare(
|
||
"INSERT INTO users (username, password_hash, display_name, role, is_active, created_at, updated_at) VALUES (?, ?, ?, 'viewer', 1, datetime('now', '+8 hours'), datetime('now', '+8 hours'))"
|
||
).run(username, pwHash, displayName)
|
||
const created = db.prepare(
|
||
'SELECT id, role FROM users WHERE username = ?'
|
||
).get(username) as { id: number; role: string } | undefined
|
||
if (!created) return NextResponse.json({ error: '用户创建失败' }, { status: 500 })
|
||
userId = created.id
|
||
role = created.role
|
||
}
|
||
} else if (ldapResult.unreachable) {
|
||
// LLDAP 不可达 → 回退本地密码缓存
|
||
const localUser = db.prepare(
|
||
'SELECT * FROM users WHERE username = ? AND is_active = 1'
|
||
).get(username) as { id: number; username: string; display_name: string; role: string; password_hash: string } | undefined
|
||
if (!localUser || !bcrypt.compareSync(password, localUser.password_hash)) {
|
||
return NextResponse.json({ error: '认证服务不可用,且本地密码不匹配' }, { status: 401 })
|
||
}
|
||
userId = localUser.id
|
||
role = localUser.role
|
||
displayName = localUser.display_name || username
|
||
} else {
|
||
return NextResponse.json({ error: '用户名或密码错误' }, { status: 401 })
|
||
}
|
||
}
|
||
|
||
// 3. 更新最后登录时间和活跃时间
|
||
db.prepare("UPDATE users SET last_login_at = datetime('now', '+8 hours'), last_active_at = datetime('now', '+8 hours') WHERE id = ?").run(userId)
|
||
|
||
// 4. 签发两个 cookie
|
||
const localToken = await createToken({ id: userId, username, display_name: displayName, role })
|
||
const sharedToken = signSharedJwt({ username, displayName })
|
||
const sharedCfg = sharedCookieConfig()
|
||
|
||
const response = NextResponse.json({
|
||
user: { id: userId, username, display_name: displayName, role },
|
||
})
|
||
response.cookies.set('session_issue', localToken, {
|
||
httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'lax',
|
||
maxAge: 7 * 24 * 60 * 60, path: '/',
|
||
})
|
||
response.cookies.set(sharedCfg.name, sharedToken, sharedCfg)
|
||
return response
|
||
} catch (e) { console.error('Login error:', e); return NextResponse.json({ error: '登录失败' }, { status: 500 }) }
|
||
}
|