fix(miniapp): persist session token for WeChat login
Return sessionToken from login endpoints and store it client-side because wx.request cannot rely on Set-Cookie. Also fix multi-cookie parsing, start on login page, and guard chat rendering fields. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"pages": [
|
"pages": [
|
||||||
"pages/chat/index",
|
|
||||||
"pages/login/index",
|
"pages/login/index",
|
||||||
|
"pages/chat/index",
|
||||||
"pages/sessions/index",
|
"pages/sessions/index",
|
||||||
"pages/space/index",
|
"pages/space/index",
|
||||||
"pages/webview/index"
|
"pages/webview/index"
|
||||||
|
|||||||
@@ -30,10 +30,30 @@ Page({
|
|||||||
},
|
},
|
||||||
|
|
||||||
async onShow() {
|
async onShow() {
|
||||||
|
if (this._authCheckPromise) {
|
||||||
|
await this._authCheckPromise;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._authCheckPromise = this.verifyAccess();
|
||||||
|
try {
|
||||||
|
await this._authCheckPromise;
|
||||||
|
} finally {
|
||||||
|
this._authCheckPromise = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async verifyAccess() {
|
||||||
|
if (this._redirectingToLogin) return;
|
||||||
try {
|
try {
|
||||||
const status = await checkAuth();
|
const status = await checkAuth();
|
||||||
if (!status?.authenticated) {
|
if (!status?.authenticated) {
|
||||||
wx.redirectTo({ url: '/pages/login/index' });
|
this._redirectingToLogin = true;
|
||||||
|
wx.redirectTo({
|
||||||
|
url: '/pages/login/index',
|
||||||
|
complete: () => {
|
||||||
|
this._redirectingToLogin = false;
|
||||||
|
},
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await this.ensureCurrentUser();
|
await this.ensureCurrentUser();
|
||||||
@@ -47,7 +67,14 @@ Page({
|
|||||||
await this.refreshSession(this.data.sessionId);
|
await this.refreshSession(this.data.sessionId);
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
wx.redirectTo({ url: '/pages/login/index' });
|
if (this._redirectingToLogin) return;
|
||||||
|
this._redirectingToLogin = true;
|
||||||
|
wx.redirectTo({
|
||||||
|
url: '/pages/login/index',
|
||||||
|
complete: () => {
|
||||||
|
this._redirectingToLogin = false;
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
<view wx:for="{{messages}}" wx:key="id" id="msg-{{index}}" class="message-row {{item.role}}">
|
<view wx:for="{{messages}}" wx:key="id" id="msg-{{index}}" class="message-row {{item.role}}">
|
||||||
<view class="message {{item.role}}">
|
<view class="message {{item.role}}">
|
||||||
<view class="message-body">
|
<view class="message-body">
|
||||||
<block wx:for="{{item.parts}}" wx:for-item="part" wx:for-index="partIndex" wx:key="partIndex">
|
<block wx:for="{{item.parts || []}}" wx:for-item="part" wx:for-index="partIndex" wx:key="partIndex">
|
||||||
<text wx:if="{{part.type === 'text'}}" class="message-text">{{part.text}}</text>
|
<text wx:if="{{part.type === 'text'}}" class="message-text">{{part.text}}</text>
|
||||||
<text
|
<text
|
||||||
wx:elif="{{part.type === 'link'}}"
|
wx:elif="{{part.type === 'link'}}"
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
>{{part.text}}</text>
|
>{{part.text}}</text>
|
||||||
</block>
|
</block>
|
||||||
</view>
|
</view>
|
||||||
<view wx:if="{{item.pageLinks.length}}" class="page-links">
|
<view wx:if="{{item.pageLinks && item.pageLinks.length}}" class="page-links">
|
||||||
<view
|
<view
|
||||||
wx:for="{{item.pageLinks}}"
|
wx:for="{{item.pageLinks}}"
|
||||||
wx:for-item="page"
|
wx:for-item="page"
|
||||||
|
|||||||
@@ -43,6 +43,10 @@ Page({
|
|||||||
try {
|
try {
|
||||||
const status = await checkAuth();
|
const status = await checkAuth();
|
||||||
if (status?.authenticated) {
|
if (status?.authenticated) {
|
||||||
|
const app = getApp();
|
||||||
|
if (status.user) {
|
||||||
|
app.globalData.user = status.user;
|
||||||
|
}
|
||||||
wx.switchTab({ url: '/pages/chat/index' });
|
wx.switchTab({ url: '/pages/chat/index' });
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
+57
-10
@@ -1,9 +1,36 @@
|
|||||||
const SESSION_KEY = 'tkmind_session';
|
const SESSION_KEY = 'tkmind_session';
|
||||||
const { createUuid } = require('./config');
|
const { createUuid, SESSION_COOKIE_NAME } = require('./config');
|
||||||
|
|
||||||
let apiBaseUrl = '';
|
let apiBaseUrl = '';
|
||||||
let cookie = '';
|
let cookie = '';
|
||||||
|
|
||||||
|
function buildSessionCookie(token) {
|
||||||
|
const value = String(token ?? '').trim();
|
||||||
|
if (!value) return '';
|
||||||
|
return `${SESSION_COOKIE_NAME}=${encodeURIComponent(value)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function applySessionToken(token) {
|
||||||
|
const nextCookie = buildSessionCookie(token);
|
||||||
|
if (!nextCookie) return;
|
||||||
|
cookie = nextCookie;
|
||||||
|
const previous = getStoredSession() || {};
|
||||||
|
saveSession({ ...previous, cookie: nextCookie });
|
||||||
|
}
|
||||||
|
|
||||||
|
function restoreSessionCookie() {
|
||||||
|
try {
|
||||||
|
const session = wx.getStorageSync(SESSION_KEY);
|
||||||
|
if (session?.cookie) {
|
||||||
|
cookie = session.cookie;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Ignore storage read errors during cold start.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
restoreSessionCookie();
|
||||||
|
|
||||||
function setApiBaseUrl(value) {
|
function setApiBaseUrl(value) {
|
||||||
apiBaseUrl = String(value || '').replace(/\/$/, '');
|
apiBaseUrl = String(value || '').replace(/\/$/, '');
|
||||||
}
|
}
|
||||||
@@ -68,14 +95,34 @@ function clearSession() {
|
|||||||
function normalizeSetCookie(headers = {}) {
|
function normalizeSetCookie(headers = {}) {
|
||||||
const raw = headers['Set-Cookie'] || headers['set-cookie'];
|
const raw = headers['Set-Cookie'] || headers['set-cookie'];
|
||||||
if (!raw) return '';
|
if (!raw) return '';
|
||||||
if (Array.isArray(raw)) {
|
const items = Array.isArray(raw) ? raw : [String(raw)];
|
||||||
return raw.map((item) => String(item).split(';')[0]).join('; ');
|
let sessionCookie = '';
|
||||||
|
for (const item of items) {
|
||||||
|
const firstPart = String(item).trim().split(';')[0].trim();
|
||||||
|
const separator = firstPart.indexOf('=');
|
||||||
|
if (separator <= 0) continue;
|
||||||
|
const name = firstPart.slice(0, separator);
|
||||||
|
const value = firstPart.slice(separator + 1);
|
||||||
|
if (name === SESSION_COOKIE_NAME && value) {
|
||||||
|
sessionCookie = `${name}=${value}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sessionCookie;
|
||||||
|
}
|
||||||
|
|
||||||
|
function persistLoginResult(result, loginType) {
|
||||||
|
if (result?.sessionToken) {
|
||||||
|
applySessionToken(result.sessionToken);
|
||||||
|
}
|
||||||
|
saveSession({
|
||||||
|
cookie,
|
||||||
|
user: result?.user || null,
|
||||||
|
loginType,
|
||||||
|
});
|
||||||
|
const app = getApp?.();
|
||||||
|
if (app && result?.user) {
|
||||||
|
app.globalData.user = result.user;
|
||||||
}
|
}
|
||||||
return String(raw)
|
|
||||||
.split(/,(?=\s*[^;,]+=)/)
|
|
||||||
.map((item) => item.trim().split(';')[0])
|
|
||||||
.filter(Boolean)
|
|
||||||
.join('; ');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function request(path, options = {}) {
|
function request(path, options = {}) {
|
||||||
@@ -140,7 +187,7 @@ async function loginWithPassword(username, password) {
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: { username, password }
|
body: { username, password }
|
||||||
});
|
});
|
||||||
saveSession({ cookie, user: result?.user || result || null, loginType: 'password' });
|
persistLoginResult(result, 'password');
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,7 +216,7 @@ async function loginWithWechatCode(loginPath) {
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: { code }
|
body: { code }
|
||||||
});
|
});
|
||||||
saveSession({ cookie, user: result?.user || result || null, loginType: 'wechat' });
|
persistLoginResult(result, 'wechat');
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ function createUuid() {
|
|||||||
const defaults = {
|
const defaults = {
|
||||||
API_BASE_URL: LOCAL_DEV ? 'http://127.0.0.1:8081' : 'https://m.tkmind.cn',
|
API_BASE_URL: LOCAL_DEV ? 'http://127.0.0.1:8081' : 'https://m.tkmind.cn',
|
||||||
MINIAPP_LOGIN_PATH: '/auth/wechat-miniapp/login',
|
MINIAPP_LOGIN_PATH: '/auth/wechat-miniapp/login',
|
||||||
|
SESSION_COOKIE_NAME: 'tkmind_user_session',
|
||||||
SELECTED_SESSION_KEY: 'tkmind_selected_session_id',
|
SELECTED_SESSION_KEY: 'tkmind_selected_session_id',
|
||||||
AGENT_RUN_POLL_MS: 1200,
|
AGENT_RUN_POLL_MS: 1200,
|
||||||
AGENT_RUN_MAX_POLLS: 120,
|
AGENT_RUN_MAX_POLLS: 120,
|
||||||
|
|||||||
+7
-1
@@ -897,7 +897,12 @@ app.post('/auth/login', jsonBody, async (req, res) => {
|
|||||||
return res.status(401).json({ message: result.message });
|
return res.status(401).json({ message: result.message });
|
||||||
}
|
}
|
||||||
setUserLoginCookies(res, req, result.token);
|
setUserLoginCookies(res, req, result.token);
|
||||||
return res.json({ authenticated: true, user: result.user, mode: 'user' });
|
return res.json({
|
||||||
|
authenticated: true,
|
||||||
|
user: result.user,
|
||||||
|
mode: 'user',
|
||||||
|
sessionToken: result.token,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!legacyAuth) {
|
if (!legacyAuth) {
|
||||||
@@ -950,6 +955,7 @@ app.post('/auth/wechat-miniapp/login', jsonBody, async (req, res) => {
|
|||||||
user: result.user,
|
user: result.user,
|
||||||
mode: 'user',
|
mode: 'user',
|
||||||
isNewUser: Boolean(result.isNewUser),
|
isNewUser: Boolean(result.isNewUser),
|
||||||
|
sessionToken: result.token,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const message = err instanceof Error ? err.message : '微信登录失败';
|
const message = err instanceof Error ? err.message : '微信登录失败';
|
||||||
|
|||||||
Reference in New Issue
Block a user