feat(auth): add opaque bearer sessions

This commit is contained in:
caoxiaozhu
2026-07-13 14:45:36 +08:00
parent 661990b27b
commit 653eda0596
59 changed files with 1408 additions and 408 deletions

View File

@@ -17,8 +17,9 @@ const adminSecretDir = path.join(rootDir, 'server', '.secrets')
const adminSecretFile = path.join(adminSecretDir, 'admin.json')
const adminScryptOptions = { N: 16384, r: 8, p: 1 }
const adminScryptKeyLength = 64
let backendStartPromise = null
let backendStartState = createBackendStartState()
let backendStartPromise = null
let backendStartState = createBackendStartState()
let backendStartAuthorized = false
function createBackendStartState() {
return {
@@ -400,11 +401,21 @@ function buildClientEnvUpdates(payload, apiBaseUrl) {
}
}
function normalizeState(env) {
const adminConfigured = Boolean(readAdminSecret())
return {
initialized: String(env.SETUP_COMPLETED || '').toLowerCase() === 'true' && adminConfigured,
export function isSetupCompletedState(env, adminConfigured) {
return String(env.SETUP_COMPLETED || '').toLowerCase() === 'true' && adminConfigured
}
function isSetupCompleted() {
return isSetupCompletedState(readEnvState(), Boolean(readAdminSecret()))
}
export function normalizeState(env, options = {}) {
const adminConfigured = options.adminConfigured ?? Boolean(readAdminSecret())
const initialized = isSetupCompletedState(env, adminConfigured)
const redactInfrastructure = options.redactInfrastructure ?? initialized
return {
initialized,
company: {
name: env.COMPANY_NAME || '',
code: env.COMPANY_CODE || '',
@@ -423,18 +434,26 @@ function normalizeState(env) {
},
database: {
driver: 'postgresql',
host: env.POSTGRES_HOST || '127.0.0.1',
host: redactInfrastructure ? '' : env.POSTGRES_HOST || '127.0.0.1',
port: Number(env.POSTGRES_PORT || 5432),
name: env.POSTGRES_DB || 'x_financial',
username: env.POSTGRES_USER || 'postgres',
username: redactInfrastructure ? '' : env.POSTGRES_USER || 'postgres',
password_configured: Boolean(env.POSTGRES_PASSWORD)
},
redis: {
enabled: Boolean(env.REDIS_URL),
url: env.REDIS_URL || ''
url: redactInfrastructure ? '' : env.REDIS_URL || ''
}
}
}
}
}
function rejectCompletedSetup(res) {
if (!isSetupCompleted()) {
return false
}
sendJson(res, 403, { detail: '系统已完成初始化,本地初始化桥已锁定。' })
return true
}
async function readJsonBody(req) {
const chunks = []
@@ -827,9 +846,12 @@ function localSetupPlugin() {
server.watcher.unwatch(path.join(rootDir, 'server', 'storage'))
server.watcher.unwatch(path.join(rootDir, 'test-results'))
server.middlewares.use('/__setup/auth/login', async (req, res) => {
try {
if (req.method !== 'POST') {
server.middlewares.use('/__setup/auth/login', async (req, res) => {
try {
if (rejectCompletedSetup(res)) {
return
}
if (req.method !== 'POST') {
sendJson(res, 405, { detail: 'Method not allowed' })
return
}
@@ -864,9 +886,12 @@ function localSetupPlugin() {
}
})
server.middlewares.use('/__setup/bootstrap/runtime', async (req, res) => {
try {
if (req.method !== 'PUT') {
server.middlewares.use('/__setup/bootstrap/runtime', async (req, res) => {
try {
if (rejectCompletedSetup(res)) {
return
}
if (req.method !== 'PUT') {
sendJson(res, 405, { detail: 'Method not allowed' })
return
}
@@ -895,9 +920,12 @@ function localSetupPlugin() {
}
})
server.middlewares.use('/__setup/bootstrap/database', async (req, res) => {
try {
if (req.method !== 'PUT') {
server.middlewares.use('/__setup/bootstrap/database', async (req, res) => {
try {
if (rejectCompletedSetup(res)) {
return
}
if (req.method !== 'PUT') {
sendJson(res, 405, { detail: 'Method not allowed' })
return
}
@@ -928,21 +956,29 @@ function localSetupPlugin() {
server.middlewares.use('/__setup/bootstrap/backend', async (req, res) => {
try {
if (req.method === 'GET') {
const logFile = path.join(rootDir, 'server', 'logs', 'bootstrap-backend.log')
backendStartState.logTail = readBackendLogTail(logFile)
sendJson(res, 200, cloneBackendStartState())
return
if (req.method === 'GET') {
const logFile = path.join(rootDir, 'server', 'logs', 'bootstrap-backend.log')
backendStartState.logTail = isSetupCompleted() ? '' : readBackendLogTail(logFile)
sendJson(res, 200, cloneBackendStartState())
return
}
if (req.method !== 'POST') {
sendJson(res, 405, { detail: 'Method not allowed' })
return
}
try {
const result = await startBackendAndWait()
sendJson(res, 200, result)
if (req.method !== 'POST') {
sendJson(res, 405, { detail: 'Method not allowed' })
return
}
if (isSetupCompleted() && !backendStartAuthorized) {
sendJson(res, 403, { detail: '系统已完成初始化,后端启动桥已锁定。' })
return
}
try {
const result = await startBackendAndWait()
if (result.completed) {
backendStartAuthorized = false
}
sendJson(res, 200, result)
} catch (error) {
sendJson(res, 500, {
ok: false,
@@ -963,12 +999,16 @@ function localSetupPlugin() {
return
}
if (req.method !== 'POST') {
sendJson(res, 405, { detail: 'Method not allowed' })
return
}
const currentEnv = readEnvState()
if (req.method !== 'POST') {
sendJson(res, 405, { detail: 'Method not allowed' })
return
}
if (rejectCompletedSetup(res)) {
return
}
const currentEnv = readEnvState()
const payload = resolveRuntimePayload(await readJsonBody(req), currentEnv)
const validationError = validateSetupPayload(payload)
@@ -991,7 +1031,7 @@ function localSetupPlugin() {
const apiBaseUrl = buildApiBaseUrl(payload, currentEnv)
updateEnvFile({
updateEnvFile({
SETUP_COMPLETED: 'true',
COMPANY_NAME: String(payload.company_name || '').trim(),
COMPANY_CODE: String(payload.company_code || '').trim(),
@@ -1009,10 +1049,11 @@ function localSetupPlugin() {
REDIS_URL: String(payload.redis_url || '').trim(),
CORS_ORIGINS: buildCorsOrigins(payload),
VITE_API_BASE_URL: apiBaseUrl,
...buildClientEnvUpdates(payload, apiBaseUrl)
})
sendJson(res, 201, normalizeState(readEnvState()))
...buildClientEnvUpdates(payload, apiBaseUrl)
})
backendStartAuthorized = true
sendJson(res, 201, normalizeState(readEnvState()))
} catch (error) {
sendJson(res, 500, {
detail: error instanceof Error ? error.message : '初始化写入失败。'