feat: 飞书单点登录和通知功能
This commit is contained in:
69
index.js
69
index.js
@@ -6,9 +6,12 @@ const config = require('./src/config/env');
|
||||
const { getConfiguredRepos } = require('./src/config/mappings');
|
||||
const { handleIssueEvent } = require('./src/logic/syncManager');
|
||||
const { handleJiraHook } = require('./src/logic/jiraSyncManager');
|
||||
const { notify, inferEventType } = require('./src/logic/larkNotifier');
|
||||
const editorRoutes = require('./src/routes/control');
|
||||
const logger = require('./src/utils/logger');
|
||||
|
||||
const { larkAuthMiddleware, loginHandler, callbackHandler } = require('./src/middleware/larkAuth');
|
||||
|
||||
const app = new Hono();
|
||||
|
||||
const requestCounts = new Map();
|
||||
@@ -47,29 +50,25 @@ setInterval(() => {
|
||||
}
|
||||
}, 5 * 60 * 1000);
|
||||
|
||||
//内网访问控制中间件:保护管理界面,只允许dotenv配置的域名访问
|
||||
const internalOnlyMiddleware = async (c, next) => {
|
||||
const pathname = new URL(c.req.url).pathname;
|
||||
//鉴权路由
|
||||
app.get('/login', loginHandler);
|
||||
app.get('/oauth/callback', callbackHandler);
|
||||
|
||||
if (pathname.startsWith('/hooks/')) {
|
||||
return await next();
|
||||
}
|
||||
// 全局鉴权中间件 (飞书 OAuth)
|
||||
app.use('*', larkAuthMiddleware);
|
||||
|
||||
const host = (c.req.header('host') || '').split(':')[0];
|
||||
const allowedHosts = config.app.dashboardAllowedHosts;
|
||||
// 获取当前用户信息
|
||||
const { meHandler, adminMiddleware } = require('./src/middleware/larkAuth');
|
||||
app.get('/api/me', meHandler);
|
||||
|
||||
if (!allowedHosts.some(allowed => host === allowed || host.endsWith('.' + allowed))) {
|
||||
logger.security(`Blocked access from unauthorized host: ${host}`, {
|
||||
path: pathname,
|
||||
ip: c.req.header('x-forwarded-for') || c.req.header('x-real-ip') || 'unknown'
|
||||
});
|
||||
return c.text('Forbidden - Access denied from this domain', 403);
|
||||
}
|
||||
|
||||
await next();
|
||||
};
|
||||
|
||||
app.use('*', internalOnlyMiddleware);
|
||||
// 敏感接口保护 (仅管理员可访问)
|
||||
// 注意:Hono 的中间件匹配顺序很重要,specific routes should be handled or protected explicitly
|
||||
app.use('/api/env', adminMiddleware);
|
||||
app.use('/api/restart', adminMiddleware);
|
||||
app.use('/api/logs', adminMiddleware);
|
||||
app.use('/api/logs/clear', adminMiddleware);
|
||||
app.use('/api/history', adminMiddleware);
|
||||
app.use('/api/status', adminMiddleware);
|
||||
|
||||
//Gitea webhook处理入口
|
||||
app.post('/hooks/gitea', rateLimiter, async (c) => {
|
||||
@@ -100,16 +99,31 @@ app.post('/hooks/gitea', rateLimiter, async (c) => {
|
||||
//解析JSON
|
||||
const body = JSON.parse(rawBody);
|
||||
|
||||
//Payload结构验证
|
||||
if (!body || !body.issue || !body.repository) {
|
||||
logger.warn('Invalid payload structure', { ip });
|
||||
return c.json({ error: 'Invalid payload structure' }, 400);
|
||||
//触发飞书通知(所有事件类型)
|
||||
const eventType = inferEventType(event, body);
|
||||
if (eventType) {
|
||||
notify(eventType, body).catch(err =>
|
||||
logger.error('[LarkNotifier] Async notify error:', err.message)
|
||||
);
|
||||
}
|
||||
|
||||
//Issue 事件处理(原有逻辑)
|
||||
if (event === 'issues' || event === 'issue_comment') {
|
||||
//异步处理,不阻塞webhook返回
|
||||
handleIssueEvent(body).catch(err => logger.error('Async handler error', err.message));
|
||||
} else {
|
||||
if (body.issue && body.repository) {
|
||||
handleIssueEvent(body).catch(err => logger.error('Async handler error', err.message));
|
||||
} else {
|
||||
logger.warn('Invalid payload structure for issue event', { ip });
|
||||
}
|
||||
}
|
||||
//PR 事件 - 仅记录日志(未来可扩展)
|
||||
else if (event === 'pull_request') {
|
||||
logger.info(`[PR Event] ${body.action} - ${body.pull_request?.title || 'Unknown'}`, { ip });
|
||||
}
|
||||
//Release 事件 - 仅记录日志(未来可扩展)
|
||||
else if (event === 'release') {
|
||||
logger.info(`[Release Event] ${body.action} - ${body.release?.name || 'Unknown'}`, { ip });
|
||||
}
|
||||
else {
|
||||
logger.info(`Ignored event type: ${event}`, { ip });
|
||||
}
|
||||
|
||||
@@ -146,6 +160,7 @@ const configuredRepos = getConfiguredRepos();
|
||||
//控制台首页
|
||||
app.get('/', (c) => c.redirect('/dashboard'));
|
||||
app.get('/dashboard', serveStatic({ path: './public/dashboard.html' }));
|
||||
app.get('/error.html', serveStatic({ path: './public/error.html' }));
|
||||
|
||||
app.route('/api', editorRoutes);
|
||||
app.route('/editor/api', editorRoutes);
|
||||
|
||||
Reference in New Issue
Block a user