import { sessionCookie } from '../auth.mjs'; import { isDatabaseConfigured } from '../db.mjs'; import { exchangeMiniProgramCode, loadWechatMiniappConfig, } from '../wechat-miniapp.mjs'; export function attachPortalCoreAuthRoutes({ app, jsonBody, userAuthReady = Promise.resolve(), getUserAuth = () => null, getLegacyAuth = () => null, userToken, legacySessionToken, setUserLoginCookies, isSecureRequest, resolveSkillRuntimeForClient = async () => null, resolveAgentCodeRunForClient = async () => null, getPlazaSeo = () => null, plazaClientIp = (req) => req.ip, logger = console, isDatabaseConfiguredFn = isDatabaseConfigured, sessionCookieFn = sessionCookie, loadWechatMiniappConfigFn = loadWechatMiniappConfig, exchangeMiniProgramCodeFn = exchangeMiniProgramCode, } = {}) { if ( !app || typeof jsonBody !== 'function' || typeof userToken !== 'function' || typeof legacySessionToken !== 'function' || typeof setUserLoginCookies !== 'function' || typeof isSecureRequest !== 'function' ) { throw new Error( 'attachPortalCoreAuthRoutes requires route dependencies', ); } app.get('/auth/status', async (req, res) => { await userAuthReady; const userAuth = getUserAuth(); if (userAuth) { try { const me = await userAuth.getMe(userToken(req)); if (!me) { return res.json({ authenticated: false, mode: 'user', }); } const row = await userAuth.getUserById(me.id); const capabilityState = await userAuth.resolveUserCapabilities(row); const skillRuntime = await resolveSkillRuntimeForClient(); const agentCodeRun = await resolveAgentCodeRunForClient(me.id); return res.json({ authenticated: true, user: me, mode: 'user', capabilities: capabilityState.capabilities, grantedSkills: capabilityState.grantedSkills ?? [], unrestricted: capabilityState.unrestricted, skillRuntime, agentCodeRun, }); } catch (error) { logger.error( '[Auth] status failed:', error instanceof Error ? error.message : error, ); return res.status(503).json({ authenticated: false, mode: 'unavailable', message: '用户认证服务不可用,请稍后重试', }); } } if (isDatabaseConfiguredFn()) { return res.status(503).json({ authenticated: false, mode: 'unavailable', message: '用户认证服务不可用,请稍后重试', }); } const legacyAuth = getLegacyAuth(); if (legacyAuth) { return res.json({ authenticated: legacyAuth.verify( legacySessionToken(req), ), mode: 'legacy', }); } return res.json({ authenticated: false, mode: 'none', }); }); app.post( '/auth/login', jsonBody, async (req, res) => { await userAuthReady; const secure = isSecureRequest(req); const userAuth = getUserAuth(); if (userAuth) { const { username, password } = req.body ?? {}; if (!username || !password) { return res.status(400).json({ message: '用户名和密码不能为空', }); } const result = await userAuth.login({ username, password, ip: req.ip, }); if (!result.ok) { if (result.retryAfterMs > 0) { res.set( 'Retry-After', String( Math.ceil(result.retryAfterMs / 1000), ), ); return res.status(429).json({ message: result.message, }); } return res.status(401).json({ message: result.message, }); } setUserLoginCookies( res, req, result.token, ); return res.json({ authenticated: true, user: result.user, mode: 'user', sessionToken: result.token, }); } const legacyAuth = getLegacyAuth(); if (!legacyAuth) { return res.status(503).json({ message: '未配置用户数据库或访问密码', }); } const password = typeof req.body?.password === 'string' ? req.body.password : ''; const result = legacyAuth.login(password, req.ip); if (!result.ok) { if (result.retryAfterMs > 0) { res.set( 'Retry-After', String( Math.ceil(result.retryAfterMs / 1000), ), ); return res.status(429).json({ message: '尝试次数过多,请稍后再试', }); } return res.status(401).json({ message: '密码错误,请重试', }); } res.set( 'Set-Cookie', sessionCookieFn(result.token, secure), ); return res.json({ authenticated: true, mode: 'legacy', }); }, ); app.post( '/auth/wechat-miniapp/login', jsonBody, async (req, res) => { await userAuthReady; const userAuth = getUserAuth(); if (!userAuth) { return res.status(503).json({ message: '未启用用户系统', }); } const miniappConfig = loadWechatMiniappConfigFn(); if (!miniappConfig.enabled) { return res.status(503).json({ message: '小程序登录未配置,请联系管理员', }); } const code = typeof req.body?.code === 'string' ? req.body.code.trim() : ''; if (!code) { return res.status(400).json({ message: '缺少微信登录 code', }); } try { const session = await exchangeMiniProgramCodeFn({ appId: miniappConfig.appId, appSecret: miniappConfig.appSecret, code, }); const result = await userAuth.loginByWechatMiniProgram({ appId: miniappConfig.appId, openid: session.openid, unionid: session.unionid, }); if (!result.ok) { return res.status(401).json({ message: result.message || '微信登录失败', }); } setUserLoginCookies( res, req, result.token, ); return res.json({ authenticated: true, user: result.user, mode: 'user', isNewUser: Boolean(result.isNewUser), sessionToken: result.token, }); } catch (error) { const message = error instanceof Error ? error.message : '微信登录失败'; const status = error?.code === 'wechat_miniapp_code_failed' ? 401 : 400; return res.status(status).json({ message }); } }, ); app.post( '/auth/register', jsonBody, async (req, res) => { await userAuthReady; const userAuth = getUserAuth(); if (!userAuth) { return res.status(503).json({ message: '未启用用户注册', }); } const { username, password, displayName, email, } = req.body ?? {}; const result = await userAuth.register({ username, password, displayName, email, }); if (!result.ok) { const status = result.message.includes( '已存在', ) ? 409 : 400; return res.status(status).json({ message: result.message, }); } const plazaSeo = getPlazaSeo(); if (plazaSeo && req.body?.utm_source) { void plazaSeo .recordAttribution( { event_type: 'signup', utm_source: req.body.utm_source, utm_medium: req.body.utm_medium, utm_campaign: req.body.utm_campaign, ref_id: req.body.ref ?? req.body.ref_id, user_id: result.user?.id ?? null, }, plazaClientIp(req), ) .catch(() => {}); } return res.json({ ok: true, user: result.user, }); }, ); app.post( '/auth/reset-password', jsonBody, async (req, res) => { await userAuthReady; const userAuth = getUserAuth(); if (!userAuth) { return res.status(503).json({ message: '未启用用户系统', }); } const { username, email, password } = req.body ?? {}; const result = await userAuth.resetPassword({ username, email, password, }); if (!result.ok) { return res.status(400).json({ message: result.message, }); } return res.json({ ok: true }); }, ); }