import { Router, Request, Response } from 'express'; import { wechatMpService } from '../services/wechat-mp.service'; import { config } from '../config'; import { logger } from '../utils/logger'; const router = Router(); /** * GET /api/mp/auth-url * 获取公众号网页授权URL * 前端在微信环境中调用此接口,获取授权跳转URL */ router.get('/auth-url', async (req: Request, res: Response) => { try { if (!wechatMpService.isConfigured()) { return res.json({ success: false, error: 'WeChat MP not configured', }); } // 从query获取回调地址,默认使用移动端URL const redirectUri = (req.query.redirect_uri as string) || config.mobileClientUrl; if (!isRedirectAllowed(redirectUri)) { return res.status(400).json({ success: false, error: 'Invalid redirect_uri', }); } // 生成随机state防止CSRF const state = await wechatMpService.createState(); // 使用snsapi_base静默授权(只获取openid) const scope = (req.query.scope as 'snsapi_base' | 'snsapi_userinfo') || 'snsapi_base'; const authUrl = wechatMpService.generateAuthUrl(redirectUri, state, scope); logger.info({ redirectUri, scope }, 'Generated WeChat MP auth URL'); return res.json({ success: true, data: { authUrl, state, }, }); } catch (error) { logger.error({ error }, 'Failed to generate auth URL'); return res.status(500).json({ success: false, error: 'Internal server error', }); } }); /** * POST /api/mp/login * 用code完成登录 * 前端在授权回调后,携带code调用此接口完成登录 */ router.post('/login', async (req: Request, res: Response) => { try { const { code, state } = req.body; if (!code) { return res.status(400).json({ success: false, error: 'Missing code parameter', }); } if (!state) { return res.status(400).json({ success: false, error: 'Missing state parameter', }); } if (!wechatMpService.isConfigured()) { return res.json({ success: false, error: 'WeChat MP not configured', }); } const isValidState = await wechatMpService.validateState(state); if (!isValidState) { return res.status(400).json({ success: false, error: 'Invalid or expired state', }); } const result = await wechatMpService.login(code); if (!result.success) { return res.json({ success: false, error: result.error, }); } return res.json({ success: true, data: result.data, }); } catch (error) { logger.error({ error }, 'Failed to process MP login'); return res.status(500).json({ success: false, error: 'Internal server error', }); } }); /** * GET /api/mp/config * 获取公众号配置状态(不返回敏感信息) */ router.get('/config', (_req: Request, res: Response) => { return res.json({ success: true, data: { configured: wechatMpService.isConfigured(), }, }); }); function isRedirectAllowed(redirectUri: string): boolean { try { const url = new URL(redirectUri); if (!['http:', 'https:'].includes(url.protocol)) return false; const allowlist = config.wechatMp.redirectAllowlist; if (allowlist.length > 0) { return allowlist.includes(url.host); } const fallbackHost = new URL(config.mobileClientUrl).host; return url.host === fallbackHost; } catch { return false; } } export default router;