fix: address code review feedback for LANGDRIVE implementation

- Normalize module paths in deploy permission checks (trailing slash)
- Add devId format validation in PATCH /dev/:devId/status
- Fix onboarding resume logic (allow resuming incomplete sessions)
- Switch audit logging from sync to async file I/O
- Add hasModuleAccess helper for consistent module permission checks

Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com>
Agent-Logs-Url: https://github.com/qinfendebingshuo/guanghulab/sessions/a260ce18-279a-4490-af90-a90a7b1f46cd
This commit is contained in:
copilot-swe-agent[bot] 2026-03-25 08:27:00 +00:00
parent 38d8afef55
commit 5e8bb969a4
3 changed files with 37 additions and 9 deletions

View File

@ -45,14 +45,17 @@ function auditLog(req, res, next) {
logEntry.statusCode = res.statusCode;
// 异步写入日志,不阻塞响应
try {
fs.mkdirSync(AUDIT_LOG_DIR, { recursive: true });
fs.mkdir(AUDIT_LOG_DIR, { recursive: true }, function(mkdirErr) {
if (mkdirErr) {
console.error('审计日志目录创建失败:', mkdirErr.message);
return;
}
var today = new Date().toISOString().split('T')[0];
var logFile = path.join(AUDIT_LOG_DIR, 'audit-' + today + '.jsonl');
fs.appendFileSync(logFile, JSON.stringify(logEntry) + '\n');
} catch (e) {
console.error('审计日志写入失败:', e.message);
}
fs.appendFile(logFile, JSON.stringify(logEntry) + '\n', function(writeErr) {
if (writeErr) console.error('审计日志写入失败:', writeErr.message);
});
});
return originalSend.call(this, body);
};

View File

@ -62,6 +62,25 @@ function rateLimit(req, res, next) {
router.use(rateLimit);
// 模块路径归一化(确保带末尾斜杠)
function normalizeModule(mod) {
if (!mod) return '';
mod = mod.trim();
if (mod !== '*' && !mod.endsWith('/')) mod += '/';
return mod;
}
// 检查开发者是否拥有指定模块
function hasModuleAccess(userModules, module) {
if (!userModules || !module) return false;
if (userModules.includes('*')) return true;
var normalized = normalizeModule(module);
for (var i = 0; i < userModules.length; i++) {
if (normalizeModule(userModules[i]) === normalized) return true;
}
return false;
}
// 获取实际数据库 ID沙箱环境使用沙箱 DB
function getDbId(req, configKey) {
if (req.sandbox && req.sandboxDbIds && req.sandboxDbIds[configKey]) {
@ -251,7 +270,7 @@ router.post('/deploy/preview',
}
// 检查开发者是否拥有该模块
if (!req.user.modules.includes('*') && !req.user.modules.includes(module)) {
if (!hasModuleAccess(req.user.modules, module)) {
return res.status(403).json({
error: true,
code: 'MODULE_DENIED',
@ -290,7 +309,7 @@ router.post('/deploy/production',
}
// 检查模块权限
if (!req.user.modules.includes('*') && !req.user.modules.includes(module)) {
if (!hasModuleAccess(req.user.modules, module)) {
return res.status(403).json({
error: true,
code: 'MODULE_DENIED',
@ -432,6 +451,11 @@ router.patch('/dev/:devId/status',
var devId = req.params.devId;
var status = req.body.status;
// 验证 devId 格式
if (!/^DEV-\d{3}$/.test(devId) && !/^TCS-\d{4}$/.test(devId)) {
return res.status(400).json({ error: true, code: 'INVALID_DEV_ID', message: '无效的开发者编号' });
}
// 开发者只能更新自己的状态(管理者除外)
if (req.user.devId !== devId && !req.user.permissions.includes('dev:update_all')) {
return res.status(403).json({

View File

@ -25,7 +25,8 @@
});
if (res.ok) {
var data = await res.json();
return !data.started && !data.completed && data.permissionLevel === 0;
// Show onboarding if not completed (handles both new and resumed sessions)
return !data.completed && data.permissionLevel === 0;
}
} catch (e) {
console.debug('[Onboarding] check failed:', e.message);