Files
memind/wechat-miniapp.mjs
john 04ef1c9105 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>
2026-07-11 12:41:37 +08:00

64 lines
1.9 KiB
JavaScript

import { fetch } from 'undici';
const JSCODE2SESSION_URL = 'https://api.weixin.qq.com/sns/jscode2session';
export function loadWechatMiniappConfig(env = process.env) {
const appId =
env.H5_WECHAT_MINIAPP_APP_ID?.trim() ??
env.H5_WECHAT_APP_ID?.trim() ??
'';
const appSecret =
env.H5_WECHAT_MINIAPP_APP_SECRET?.trim() ??
env.H5_WECHAT_APP_SECRET?.trim() ??
'';
return {
enabled: Boolean(appId && appSecret),
appId,
appSecret,
};
}
export async function exchangeMiniProgramCode(
{ appId, appSecret, code, fetchImpl = fetch } = {},
) {
const jsCode = String(code ?? '').trim();
if (!appId || !appSecret) {
throw Object.assign(new Error('小程序登录未配置 AppID/AppSecret'), {
code: 'wechat_miniapp_not_configured',
});
}
if (!jsCode) {
throw Object.assign(new Error('缺少微信登录 code'), { code: 'invalid_code' });
}
const url = new URL(JSCODE2SESSION_URL);
url.searchParams.set('appid', appId);
url.searchParams.set('secret', appSecret);
url.searchParams.set('js_code', jsCode);
url.searchParams.set('grant_type', 'authorization_code');
const response = await fetchImpl(url);
const payload = await response.json().catch(() => ({}));
if (!response.ok) {
throw Object.assign(new Error('微信登录服务暂不可用'), {
code: 'wechat_miniapp_http_failed',
status: response.status,
});
}
if (payload.errcode) {
throw Object.assign(new Error(payload.errmsg || '微信 code 无效或已过期'), {
code: 'wechat_miniapp_code_failed',
errcode: payload.errcode,
});
}
const openid = String(payload.openid ?? '').trim();
if (!openid) {
throw Object.assign(new Error('微信未返回 openid'), { code: 'wechat_miniapp_openid_missing' });
}
return {
openid,
sessionKey: payload.session_key ?? null,
unionid: payload.unionid ? String(payload.unionid).trim() : null,
};
}