feat: add /api/internal/roles endpoint for OA integration + middleware publicPaths
This commit is contained in:
parent
8886e12b15
commit
b851fbbda8
|
|
@ -21,6 +21,10 @@ services:
|
|||
- OIDC_CLIENT_ID=${OIDC_CLIENT_ID:-oa-oidc}
|
||||
- OIDC_CLIENT_SECRET=${OIDC_CLIENT_SECRET}
|
||||
- OIDC_REDIRECT_URI=${OIDC_REDIRECT_URI:-https://oa.tlyq.ai/api/auth/callback}
|
||||
- ASSETS_INTERNAL_URL=http://assets-ai:3000
|
||||
- ISSUE_INTERNAL_URL=http://issue-ai:3000
|
||||
- MONITOR_INTERNAL_URL=http://monitor-ai:3000
|
||||
- MONITOR_DB_PATH=/data/other-sites/monitor/monitor.db
|
||||
volumes:
|
||||
- ./.next:/app/.next
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
|
|
@ -28,6 +32,7 @@ services:
|
|||
- /root/docker/ldap-ai/data/lldap:/data/other-sites/lldap
|
||||
- /var/lib/docker/volumes/assets-ai_assets-data/_data:/data/other-sites/assets
|
||||
- /var/lib/docker/volumes/issue-ai_issue-data/_data:/data/other-sites/issue
|
||||
- /root/docker/monitor-ai/data:/data/other-sites/monitor
|
||||
networks:
|
||||
- webnet
|
||||
healthcheck:
|
||||
|
|
|
|||
|
|
@ -55,17 +55,21 @@ export async function POST(request: Request) {
|
|||
return NextResponse.json({ error: '仅管理员可创建用户' }, { status: 403 })
|
||||
}
|
||||
|
||||
const { username, displayName, assetsRole, issueRole, email } = await request.json()
|
||||
const { username, displayName, assetsRole, issueRole, monitorRole, email } = await request.json()
|
||||
if (!username) return NextResponse.json({ error: '用户名不能为空' }, { status: 400 })
|
||||
if (!/^[a-z][a-z0-9_.@-]*$/i.test(username)) return NextResponse.json({ error: '用户名格式不合法' }, { status: 400 })
|
||||
|
||||
const password = generatePassword()
|
||||
const [assetsRoles, issueRoles] = await Promise.all([
|
||||
fetchRoles('http://localhost:6177'), fetchRoles('http://localhost:6176'),
|
||||
const ASSETS_URL = process.env.ASSETS_INTERNAL_URL || 'http://localhost:6177'
|
||||
const ISSUE_URL = process.env.ISSUE_INTERNAL_URL || 'http://localhost:6176'
|
||||
const MONITOR_URL = process.env.MONITOR_INTERNAL_URL || 'http://localhost:6181'
|
||||
const [assetsRoles, issueRoles, monitorRoles] = await Promise.all([
|
||||
fetchRoles(ASSETS_URL), fetchRoles(ISSUE_URL), fetchRoles(MONITOR_URL),
|
||||
])
|
||||
|
||||
const ar = (assetsRole && assetsRoles.includes(assetsRole)) ? assetsRole : 'viewer'
|
||||
const ir = (issueRole && issueRoles.includes(issueRole)) ? issueRole : 'viewer'
|
||||
const mr = (monitorRole && monitorRoles.includes(monitorRole)) ? monitorRole : 'viewer'
|
||||
|
||||
const safeName = esc(displayName || username)
|
||||
const safeUser = esc(username)
|
||||
|
|
@ -81,17 +85,20 @@ export async function POST(request: Request) {
|
|||
lldapChangePassword(username, password)
|
||||
|
||||
// 3. 自动登录各站点触发用户同步
|
||||
const [assetsOk, issueOk] = await Promise.all([
|
||||
syncToSite('http://localhost:6177', username, password),
|
||||
syncToSite('http://localhost:6176', username, password),
|
||||
const [assetsOk, issueOk, monitorOk] = await Promise.all([
|
||||
syncToSite(ASSETS_URL, username, password),
|
||||
syncToSite(ISSUE_URL, username, password),
|
||||
syncToSite(MONITOR_URL, username, password),
|
||||
])
|
||||
|
||||
// 4. 直接更新各站点角色
|
||||
const assetsDb = process.env.ASSETS_DB_PATH || '/data/other-sites/assets/assets.db'
|
||||
const issueDb = process.env.ISSUE_DB_PATH || '/data/other-sites/issue/issue.db'
|
||||
const roleResults = { assets: false, issue: false }
|
||||
const monitorDb = process.env.MONITOR_DB_PATH || '/data/other-sites/monitor/monitor.db'
|
||||
const roleResults = { assets: false, issue: false, monitor: false }
|
||||
if (assetsOk) try { execFileSync('sqlite3', [assetsDb], { input: `UPDATE users SET role = '${ar}', updated_at = datetime('now', '+8 hours') WHERE username = '${safeUser}';`, timeout: 3000 }); roleResults.assets = true } catch {}
|
||||
if (issueOk) try { execFileSync('sqlite3', [issueDb], { input: `UPDATE users SET role = '${ir}', updated_at = datetime('now', '+8 hours') WHERE username = '${safeUser}';`, timeout: 3000 }); roleResults.issue = true } catch {}
|
||||
if (monitorOk) try { execFileSync('sqlite3', [monitorDb], { input: `UPDATE users SET role = '${mr}', updated_at = datetime('now', '+8 hours') WHERE username = '${safeUser}';`, timeout: 3000 }); roleResults.monitor = true } catch {}
|
||||
|
||||
// 5. 如果提供了邮箱,发送密码设置链接
|
||||
let emailSent = false
|
||||
|
|
@ -106,8 +113,8 @@ export async function POST(request: Request) {
|
|||
return NextResponse.json({
|
||||
success: true,
|
||||
password: emailSent ? undefined : password,
|
||||
synced: { assets: assetsOk, issue: issueOk },
|
||||
roles: { assets: ar, issue: ir, applied: roleResults },
|
||||
synced: { assets: assetsOk, issue: issueOk, monitor: monitorOk },
|
||||
roles: { assets: ar, issue: ir, monitor: mr, applied: roleResults },
|
||||
emailSent,
|
||||
message: emailSent ? `用户已创建,密码设置链接已发送至 ${email}` : '用户已创建并同步至所有站点',
|
||||
})
|
||||
|
|
|
|||
|
|
@ -5,14 +5,14 @@ import { isLldapAdmin } from '@/lib/ldap'
|
|||
|
||||
const INTERNAL_KEY = 'oa-internal-key-tlyq-2026'
|
||||
|
||||
async function fetchRoles(url: string): Promise<{ name: string; display_name: string }[]> {
|
||||
async function fetchRoles(siteUrl: string): Promise<string[]> {
|
||||
try {
|
||||
const res = await fetch(`${url}/api/internal/roles`, {
|
||||
const res = await fetch(`${siteUrl}/api/internal/roles`, {
|
||||
headers: { 'x-internal-key': INTERNAL_KEY },
|
||||
signal: AbortSignal.timeout(5000),
|
||||
})
|
||||
const data = await res.json()
|
||||
return data.roles || []
|
||||
return (data.roles || []).map((r: { name: string }) => r.name)
|
||||
} catch { return [] }
|
||||
}
|
||||
|
||||
|
|
@ -23,10 +23,13 @@ export async function GET() {
|
|||
const session = verifySharedJwt(token)
|
||||
if (!session || !(await isLldapAdmin(session.username))) return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
|
||||
|
||||
const [assetsRoles, issueRoles] = await Promise.all([
|
||||
fetchRoles('http://localhost:6177'),
|
||||
fetchRoles('http://localhost:6176'),
|
||||
const A_URL = process.env.ASSETS_INTERNAL_URL || 'http://localhost:6177'
|
||||
const I_URL = process.env.ISSUE_INTERNAL_URL || 'http://localhost:6176'
|
||||
const M_URL = process.env.MONITOR_INTERNAL_URL || 'http://localhost:6181'
|
||||
|
||||
const [assetsRoles, issueRoles, monitorRoles] = await Promise.all([
|
||||
fetchRoles(A_URL), fetchRoles(I_URL), fetchRoles(M_URL),
|
||||
])
|
||||
|
||||
return NextResponse.json({ assets: assetsRoles, issue: issueRoles })
|
||||
return NextResponse.json({ assets: assetsRoles, issue: issueRoles, monitor: monitorRoles })
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,22 +38,39 @@ async function checkAdmin() {
|
|||
return session ? isLldapAdmin(session.username) : false
|
||||
}
|
||||
|
||||
const A_URL = process.env.ASSETS_INTERNAL_URL || 'http://localhost:6177'
|
||||
const I_URL = process.env.ISSUE_INTERNAL_URL || 'http://localhost:6176'
|
||||
const M_URL = process.env.MONITOR_INTERNAL_URL || 'http://localhost:6181'
|
||||
const A_DB = process.env.ASSETS_DB_PATH || '/data/other-sites/assets/assets.db'
|
||||
const I_DB = process.env.ISSUE_DB_PATH || '/data/other-sites/issue/issue.db'
|
||||
const M_DB = process.env.MONITOR_DB_PATH || '/data/other-sites/monitor/monitor.db'
|
||||
|
||||
function siteDb(site: string): string {
|
||||
if (site === 'assets') return A_DB
|
||||
if (site === 'issue') return I_DB
|
||||
return M_DB
|
||||
}
|
||||
function siteUrl(site: string): string {
|
||||
if (site === 'assets') return A_URL
|
||||
if (site === 'issue') return I_URL
|
||||
return M_URL
|
||||
}
|
||||
|
||||
// GET — 列出各站点用户及其角色
|
||||
export async function GET() {
|
||||
if (!(await checkAdmin())) return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
|
||||
|
||||
try {
|
||||
const [assetsRoles, issueRoles] = await Promise.all([
|
||||
fetchRoles('http://localhost:6177'),
|
||||
fetchRoles('http://localhost:6176'),
|
||||
const [assetsRoles, issueRoles, monitorRoles] = await Promise.all([
|
||||
fetchRoles(A_URL), fetchRoles(I_URL), fetchRoles(M_URL),
|
||||
])
|
||||
|
||||
const [assetsUsers, issueUsers] = await Promise.all([
|
||||
getSiteUsers(process.env.ASSETS_DB_PATH || '/data/other-sites/assets/assets.db', assetsRoles),
|
||||
getSiteUsers(process.env.ISSUE_DB_PATH || '/data/other-sites/issue/issue.db', issueRoles),
|
||||
const [assetsUsers, issueUsers, monitorUsers] = await Promise.all([
|
||||
getSiteUsers(A_DB, assetsRoles),
|
||||
getSiteUsers(I_DB, issueRoles),
|
||||
getSiteUsers(M_DB, monitorRoles),
|
||||
])
|
||||
|
||||
// 直连 LLDAP SQLite 获取邮箱
|
||||
let emails: Record<string, string> = {}
|
||||
try {
|
||||
const out = queryLldap(`SELECT user_id, email FROM users`)
|
||||
|
|
@ -63,7 +80,11 @@ export async function GET() {
|
|||
})
|
||||
} catch {}
|
||||
|
||||
return NextResponse.json({ assetsRoles, issueRoles, users: { assets: assetsUsers, issue: issueUsers }, emails })
|
||||
return NextResponse.json({
|
||||
assetsRoles, issueRoles, monitorRoles,
|
||||
users: { assets: assetsUsers, issue: issueUsers, monitor: monitorUsers },
|
||||
emails,
|
||||
})
|
||||
} catch {
|
||||
return NextResponse.json({ error: '查询失败' }, { status: 500 })
|
||||
}
|
||||
|
|
@ -78,14 +99,10 @@ export async function PUT(request: Request) {
|
|||
if (!username || !site || !role) return NextResponse.json({ error: '参数不完整' }, { status: 400 })
|
||||
if (username === 'admin' || username === 'localadmin') return NextResponse.json({ error: '不能修改系统保留用户角色' }, { status: 400 })
|
||||
|
||||
const dbPath = site === 'assets'
|
||||
? (process.env.ASSETS_DB_PATH || '/data/other-sites/assets/assets.db')
|
||||
: (process.env.ISSUE_DB_PATH || '/data/other-sites/issue/issue.db')
|
||||
|
||||
const roles = await fetchRoles(`http://localhost:${site === 'assets' ? 6177 : 6176}`)
|
||||
const roles = await fetchRoles(siteUrl(site))
|
||||
if (!roles.includes(role)) return NextResponse.json({ error: '无效的角色' }, { status: 400 })
|
||||
|
||||
execFileSync('sqlite3', [dbPath], {
|
||||
execFileSync('sqlite3', [siteDb(site)], {
|
||||
input: `UPDATE users SET role='${role}', updated_at=datetime('now', '+8 hours') WHERE username='${username}';`,
|
||||
timeout: 3000,
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in New Issue