feat(auth): add opaque bearer sessions
This commit is contained in:
@@ -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 : '初始化写入失败。'
|
||||
|
||||
Reference in New Issue
Block a user