fix(miniapp): 修复登录态错配导致的 403(userId 与 userToken 原子写入/清除)

- completeLogin 统一接收 sessionToken:有 token 写入、无 token(体验账号回退)清除旧 token,杜绝旧 token 属主与新 userId 不一致

- 微信手机号登录把 token 交给 completeLogin 统一落库

- normalizeUserId 移除历史 101→2006 映射;有 token 但本地 userId 缺失时传 0,由服务端按 token 识别

- 新增 login-identity-consistency.spec.ts(106 套件/706 用例全绿)
这个提交包含在:
xcbyy
2026-10-21 10:59:22 +08:00
父节点 f56bbe76d7
当前提交 7a1e32688c
共有 3 个文件被更改,包括 46 次插入 和 7 次删除

查看文件

@@ -564,8 +564,14 @@ function normalizeUserId(uid) {
} catch (_) {}
}
const n = Number(target);
if (n === 101) return 2006;
return (!isNaN(n) && n > 0) ? n : 1001;
const valid = !isNaN(n) && n > 0;
// 已登录(存在会话 token)时身份以服务端 token 为准:若本地 userId 缺失则传 0,
// 避免"旧 token 属主 + 声明的 userId"不一致被服务端判定为以他人身份操作(403)。
try {
const token = uni.getStorageSync('userToken');
if (token) return valid ? n : 0;
} catch (_) {}
return valid ? n : 1001;
}
/**

查看文件

@@ -156,7 +156,7 @@ onShow(() => {
loadProtocolConfig();
});
const completeLogin = (userId, customData = {}) => {
const completeLogin = (userId, customData = {}, sessionToken = '') => {
if (customData.status === '已封禁' || customData.status === '封禁') {
uni.showModal({
title: '账号已封禁',
@@ -172,6 +172,13 @@ const completeLogin = (userId, customData = {}) => {
uni.hideLoading();
uni.removeStorageSync('user_has_logged_out');
uni.setStorageSync('userId', userId);
// userId 与 token 必须保持一致:无 token 的体验登录要清掉旧 token,
// 否则会出现"旧 token 属主 + 新 userId"→ 服务端判定以他人身份操作(403)
if (sessionToken) {
uni.setStorageSync('userToken', sessionToken);
} else {
uni.removeStorageSync('userToken');
}
uni.setStorageSync('userInfo', {
id: userId,
nickname: customData.nickname || '微信用户',
@@ -251,10 +258,7 @@ const handleGetPhoneNumber = async (e) => {
if (res && (res.user || res.userId)) {
const userObj = res.user || { id: res.userId, phone: res.phone };
const sessionToken = res.userToken || res.token || '';
if (sessionToken) {
uni.setStorageSync('userToken', sessionToken);
}
completeLogin(userObj.id, userObj);
completeLogin(userObj.id, userObj, sessionToken);
return;
}
} catch (err) {

查看文件

@@ -0,0 +1,29 @@
// 已读取AGENTS.md
import * as fs from 'fs';
import * as path from 'path';
/**
* 第九十批:登录态一致性(userId 与 userToken 不得错配)。
* 背景:体验账号回退登录只写 userId=1001 却保留旧 token(属主 2010),
* 服务端按 token 身份校验 → 判定"以他人身份操作" → 收藏/通知/优惠券/举报全部 403。
*/
describe('第九十批登录态一致性闸门', () => {
const root = path.resolve(__dirname, '../../');
const api = fs.readFileSync(path.join(root, 'pwuniapp/common/api.js'), 'utf-8');
const login = fs.readFileSync(path.join(root, 'pwuniapp/pages/auth/login.vue'), 'utf-8');
it('completeLogin 原子写入 userId 与 token(无 token 时清除旧 token)', () => {
expect(login).toContain('const completeLogin = (userId, customData = {}, sessionToken = \'\')');
expect(login).toContain('uni.removeStorageSync(\'userToken\')');
expect(login).toContain('uni.setStorageSync(\'userToken\', sessionToken)');
});
it('微信登录把 token 交给 completeLogin 统一落库', () => {
expect(login).toContain('completeLogin(userObj.id, userObj, sessionToken)');
});
it('normalizeUserId 不再做 101→2006 映射;有 token 时缺失 userId 传 0', () => {
expect(api).not.toContain('if (n === 101) return 2006;');
expect(api).toContain('if (token) return valid ? n : 0;');
});
});