feat(auth): add WeChat mini program login endpoint
Add jscode2session exchange, CSRF bypass for servicewechat.com, and openid-based auto register/login for the mini program client. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+54
@@ -172,6 +172,7 @@ import {
|
||||
WECHAT_NOTIFY_SUCCESS_V2,
|
||||
} from './wechat-pay.mjs';
|
||||
import { createWechatOAuthService, isWechatUserAgent, loadWechatOAuthConfig } from './wechat-oauth.mjs';
|
||||
import { exchangeMiniProgramCode, loadWechatMiniappConfig } from './wechat-miniapp.mjs';
|
||||
import { loadWechatMpConfig } from './wechat-mp-config.mjs';
|
||||
import { loadWechatMpModule } from './wechat-mp-loader.mjs';
|
||||
import { validateWechatShareSignatureUrl } from './wechat-share.mjs';
|
||||
@@ -262,6 +263,15 @@ app.use((req, res, next) => {
|
||||
next();
|
||||
});
|
||||
|
||||
function isWechatMiniProgramSource(value) {
|
||||
if (!value) return false;
|
||||
try {
|
||||
return new URL(value).hostname.endsWith('servicewechat.com');
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function csrfOriginCheck(req, res, next) {
|
||||
if (['GET', 'HEAD', 'OPTIONS'].includes(req.method)) return next();
|
||||
const host = req.get('host');
|
||||
@@ -269,6 +279,9 @@ function csrfOriginCheck(req, res, next) {
|
||||
const referer = req.get('referer');
|
||||
if (!origin && !referer) return next();
|
||||
const requestHostname = (host ?? '').split(':')[0];
|
||||
if ([origin, referer].some(isWechatMiniProgramSource)) {
|
||||
return next();
|
||||
}
|
||||
const allowed = [origin, referer].some((value) => {
|
||||
if (!value) return false;
|
||||
try {
|
||||
@@ -904,6 +917,47 @@ app.post('/auth/login', jsonBody, async (req, res) => {
|
||||
return res.json({ authenticated: true, mode: 'legacy' });
|
||||
});
|
||||
|
||||
app.post('/auth/wechat-miniapp/login', jsonBody, async (req, res) => {
|
||||
await userAuthReady;
|
||||
if (!userAuth) {
|
||||
return res.status(503).json({ message: '未启用用户系统' });
|
||||
}
|
||||
const miniappConfig = loadWechatMiniappConfig();
|
||||
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 exchangeMiniProgramCode({
|
||||
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),
|
||||
});
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : '微信登录失败';
|
||||
const status = err?.code === 'wechat_miniapp_code_failed' ? 401 : 400;
|
||||
return res.status(status).json({ message });
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/auth/register', jsonBody, async (req, res) => {
|
||||
await userAuthReady;
|
||||
if (!userAuth) {
|
||||
|
||||
Reference in New Issue
Block a user