import { isWechatUserAgent, loadWechatOAuthConfig, } from '../wechat-oauth.mjs'; import { validateWechatShareSignatureUrl, } from '../wechat-share.mjs'; export function attachPortalWechatAuthRoutes({ app, jsonBody, userAuthReady = Promise.resolve(), getUserAuth = () => null, getWechatOAuthService = () => null, getWechatMpService = () => null, wechatMpConfig, userToken, setUserLoginCookies, isSecureRequest, getPlazaSeo = () => null, plazaClientIp = (req) => req.ip, logger = console, isWechatUserAgentFn = isWechatUserAgent, loadWechatOAuthConfigFn = loadWechatOAuthConfig, validateWechatShareSignatureUrlFn = validateWechatShareSignatureUrl, } = {}) { if ( !app || typeof jsonBody !== 'function' || !wechatMpConfig || typeof userToken !== 'function' || typeof setUserLoginCookies !== 'function' || typeof isSecureRequest !== 'function' ) { throw new Error( 'attachPortalWechatAuthRoutes requires route dependencies', ); } app.get('/auth/wechat/config', async (req, res) => { await userAuthReady; const wechatOAuthService = getWechatOAuthService(); if (!wechatOAuthService?.enabled) { return res.json({ enabled: false, inWechat: isWechatUserAgentFn( req.get('user-agent') || '', ), scanEnabled: false, }); } return res.json( wechatOAuthService.publicConfig(req), ); }); app.get( '/auth/wechat/js-sdk-signature', async (req, res) => { await userAuthReady; const userAuth = getUserAuth(); const wechatMpService = getWechatMpService(); if (!userAuth || !wechatMpService?.enabled) { return res.status(503).json({ message: '微信 JS-SDK 未启用', }); } const me = await userAuth.getMe(userToken(req)); if (!me) { return res.status(401).json({ message: '未登录', }); } const pageUrl = String( req.query?.url ?? '', ).split('#')[0]; if (!pageUrl) { return res.status(400).json({ message: '缺少 url', }); } try { const normalizedUrl = validateWechatShareSignatureUrlFn(pageUrl, { publicBaseUrl: wechatMpConfig.publicBaseUrl, requestHost: req.get('x-forwarded-host') || req.get('host') || '', }); const payload = await wechatMpService.createJsSdkSignature( normalizedUrl, ); return res.json(payload); } catch (error) { const message = error instanceof Error ? error.message : '微信 JS-SDK 签名失败'; logger.warn( 'WeChat JS-SDK signature failed:', message, ); return res.status(502).json({ message }); } }, ); app.get( '/auth/wechat/public-js-sdk-signature', async (req, res) => { await userAuthReady; const wechatMpService = getWechatMpService(); if (!wechatMpService?.enabled) { return res.status(503).json({ message: '微信 JS-SDK 未启用', }); } const pageUrl = String( req.query?.url ?? '', ).split('#')[0]; if (!pageUrl) { return res.status(400).json({ message: '缺少 url', }); } try { const normalizedUrl = validateWechatShareSignatureUrlFn(pageUrl, { publicBaseUrl: wechatMpConfig.publicBaseUrl, requestHost: req.get('x-forwarded-host') || req.get('host') || '', }); const payload = await wechatMpService.createJsSdkSignature( normalizedUrl, ); return res.json(payload); } catch (error) { const message = error instanceof Error ? error.message : '微信 JS-SDK 签名失败'; logger.warn( 'WeChat public JS-SDK signature failed:', message, ); return res.status(502).json({ message }); } }, ); app.get('/auth/wechat/status', async (req, res) => { await userAuthReady; const userAuth = getUserAuth(); const wechatOAuthService = getWechatOAuthService(); if (!userAuth || !wechatOAuthService?.enabled) { return res.json({ enabled: false, bound: false, }); } const me = await userAuth.getMe(userToken(req)); if (!me) { return res.status(401).json({ message: '未登录', }); } const config = loadWechatOAuthConfigFn(); const status = await userAuth.getWechatBindingStatus( me.id, config.appId, ); return res.json({ enabled: true, ...status, }); }); if (wechatMpConfig.enabled) { app.get( '/auth/wechat/agent-route', async (req, res) => { await userAuthReady; const userAuth = getUserAuth(); const wechatMpService = getWechatMpService(); if ( !userAuth || !wechatMpService?.enabled ) { return res.status(503).json({ message: '公众号 Agent 未启用', }); } const me = await userAuth.getMe( userToken(req), ); if (!me) { return res.status(401).json({ message: '未登录', }); } return res.json( await wechatMpService.getRouteStatusForUser( me.id, ), ); }, ); app.post( '/auth/wechat/agent-route/reset', async (req, res) => { await userAuthReady; const userAuth = getUserAuth(); const wechatMpService = getWechatMpService(); if ( !userAuth || !wechatMpService?.enabled ) { return res.status(503).json({ message: '公众号 Agent 未启用', }); } const me = await userAuth.getMe( userToken(req), ); if (!me) { return res.status(401).json({ message: '未登录', }); } try { const result = await wechatMpService.recreateRouteForUser( me.id, ); if (!result.ok) { return res.status(400).json({ message: result.message, }); } return res.json(result); } catch (error) { return res.status(500).json({ message: error instanceof Error ? error.message : '重建公众号 Agent 路由失败', }); } }, ); } app.get( '/auth/wechat/pending/:token', async (req, res) => { await userAuthReady; const userAuth = getUserAuth(); if (!userAuth) { return res.status(503).json({ message: '未启用用户系统', }); } const pending = await userAuth.getWechatPendingBind( req.params.token, ); if (!pending) { return res.status(404).json({ message: '绑定会话已过期,请重新微信登录', }); } return res.json({ nickname: pending.nickname, avatarUrl: pending.avatar_url, returnTo: pending.return_to || '/', }); }, ); app.post( '/auth/wechat/register', jsonBody, async (req, res) => { await userAuthReady; const secure = isSecureRequest(req); const userAuth = getUserAuth(); if (!userAuth) { return res.status(503).json({ message: '未启用用户系统', }); } const pendingToken = typeof req.body?.pendingToken === 'string' ? req.body.pendingToken : ''; if (!pendingToken) { return res.status(400).json({ message: '缺少绑定会话', }); } const result = await userAuth.completeWechatRegister({ pendingToken, }); if (!result.ok) { return res.status(400).json({ message: result.message, }); } const plazaSeo = getPlazaSeo(); if (result.isNewUser && plazaSeo) { void plazaSeo .recordAttribution( { event_type: 'signup', utm_source: result.utmSource || 'wechat', utm_medium: result.utmMedium, utm_campaign: result.utmCampaign, user_id: result.user?.id ?? null, }, plazaClientIp(req), ) .catch(() => {}); } void secure; setUserLoginCookies( res, req, result.token, ); return res.json({ authenticated: true, user: result.user, returnTo: result.returnTo || '/', }); }, ); app.post( '/auth/wechat/bind', jsonBody, async (req, res) => { await userAuthReady; const secure = isSecureRequest(req); const userAuth = getUserAuth(); if (!userAuth) { return res.status(503).json({ message: '未启用用户系统', }); } const pendingToken = typeof req.body?.pendingToken === 'string' ? req.body.pendingToken : ''; const username = typeof req.body?.username === 'string' ? req.body.username : ''; const password = typeof req.body?.password === 'string' ? req.body.password : ''; if (!pendingToken) { return res.status(400).json({ message: '缺少绑定会话', }); } if (!username || !password) { return res.status(400).json({ message: '用户名和密码不能为空', }); } const result = await userAuth.completeWechatBindAccount({ pendingToken, username, password, ip: req.ip, }); if (!result.ok) { const status = result.retryAfterMs > 0 ? 429 : 401; if (result.retryAfterMs > 0) { res.set( 'Retry-After', String( Math.ceil(result.retryAfterMs / 1000), ), ); } return res.status(status).json({ message: result.message, }); } void secure; setUserLoginCookies( res, req, result.token, ); return res.json({ authenticated: true, user: result.user, bound: true, returnTo: result.returnTo || '/', }); }, ); app.post( '/auth/wechat/scan/start', async (req, res) => { await userAuthReady; const wechatOAuthService = getWechatOAuthService(); if (!wechatOAuthService?.enabled) { return res.status(503).json({ message: '微信登录未启用', }); } try { const payload = await wechatOAuthService.startScanLogin( req, ); return res.json(payload); } catch (error) { return res.status(503).json({ message: error instanceof Error ? error.message : '微信扫码登录不可用', }); } }, ); app.get( '/auth/wechat/scan/poll', async (req, res) => { await userAuthReady; const wechatOAuthService = getWechatOAuthService(); if (!wechatOAuthService?.enabled) { return res.status(503).json({ message: '微信登录未启用', }); } const state = typeof req.query?.state === 'string' ? req.query.state : ''; if (!state) { return res.status(400).json({ message: '缺少扫码状态', }); } const result = await wechatOAuthService.pollScanLogin( state, ); if ( result.status === 'complete' && result.token ) { setUserLoginCookies( res, req, result.token, ); } return res.json(result); }, ); app.get( '/auth/wechat/authorize', async (req, res) => { await userAuthReady; const wechatOAuthService = getWechatOAuthService(); if (!wechatOAuthService?.enabled) { return res.status(503).json({ message: '微信登录未启用', }); } try { const userAuth = getUserAuth(); let bindUserId = null; const intent = typeof req.query?.intent === 'string' ? req.query.intent .trim() .toLowerCase() : 'login'; if (intent === 'bind' && userAuth) { const me = await userAuth.getMe( userToken(req), ); if (!me) { return res.status(401).json({ message: '请先登录后再绑定微信', }); } bindUserId = me.id; } const redirectUrl = await wechatOAuthService.buildAuthorizeRedirect( req, { bindUserId }, ); return res.redirect(302, redirectUrl); } catch (error) { logger.error( 'WeChat authorize failed:', error, ); return res.status(500).json({ message: error instanceof Error ? error.message : '微信授权失败', }); } }, ); app.get( '/auth/wechat/callback', async (req, res) => { await userAuthReady; const secure = isSecureRequest(req); const wechatOAuthService = getWechatOAuthService(); const userAuth = getUserAuth(); if ( !wechatOAuthService?.enabled || !userAuth ) { return res.redirect( 302, '/?wechat_error=unavailable', ); } try { const result = await wechatOAuthService.handleCallback({ code: typeof req.query?.code === 'string' ? req.query.code : '', state: typeof req.query?.state === 'string' ? req.query.state : '', ip: req.ip, }); if (result.action === 'binding_gate') { const params = new URLSearchParams(); params.set( 'wechat_pending', result.pendingToken, ); if ( result.returnTo && result.returnTo !== '/' ) { params.set( 'return_to', result.returnTo, ); } return res.redirect( 302, `/?${params.toString()}`, ); } if (result.action === 'poll_error') { return res.redirect( 302, `/?wechat_error=${encodeURIComponent( result.message || '微信登录失败', )}`, ); } const plazaSeo = getPlazaSeo(); if (result.isNewUser && plazaSeo) { void plazaSeo .recordAttribution( { event_type: 'signup', utm_source: result.utmSource || 'wechat', utm_medium: result.utmMedium, utm_campaign: result.utmCampaign, user_id: result.user?.id ?? null, }, plazaClientIp(req), ) .catch(() => {}); } if ( result.authMode === 'open' || result.authMode === 'scan' ) { return res.send( '微信登录

扫码登录成功,请返回电脑继续操作。

', ); } void secure; setUserLoginCookies( res, req, result.token, ); return res.redirect( 302, result.returnTo || '/', ); } catch (error) { logger.error( 'WeChat callback failed:', error, ); const message = encodeURIComponent( error instanceof Error ? error.message : '微信登录失败', ); return res.redirect( 302, `/?wechat_error=${message}`, ); } }, ); }