fix(cloud): scope QR login requests to workspace (#2414)

Co-authored-by: Chan <dadachann@users.noreply.github.com>
This commit is contained in:
Hyu
2026-08-11 16:57:16 +08:00
committed by GitHub
parent 90f3d880e5
commit 20710df9cb
10 changed files with 134 additions and 10 deletions
@@ -15,6 +15,7 @@ import {
XCircle,
} from 'lucide-react';
import QRCode from 'qrcode';
import { getActiveWorkspaceUuid } from '@/app/infra/http/workspaceContext';
export type QrLoginPlatform =
| 'feishu'
@@ -55,12 +56,12 @@ const PLATFORM_CONFIGS: Record<QrLoginPlatform, PlatformConfig> = {
},
weixin: {
titleKey: 'weixin.scanLogin',
connectingKey: 'feishu.connecting',
connectingKey: 'weixin.connecting',
scanQRCodeKey: 'weixin.scanQRCode',
waitingKey: 'feishu.waitingForScan',
waitingKey: 'weixin.waitingForScan',
successKey: 'weixin.loginSuccess',
failedKey: 'weixin.loginFailed',
retryKey: 'feishu.retry',
retryKey: 'weixin.retry',
apiBase: '/api/v1/platform/adapters/weixin/login',
extractSuccess: (data) => ({
token: data.token,
@@ -146,6 +147,8 @@ export default function QrCodeLoginDialog({
const checkExpiredRef = useRef<ReturnType<typeof setInterval> | null>(null);
const abortRef = useRef<AbortController | null>(null);
const sessionIdRef = useRef<string | null>(null);
const sessionWorkspaceUuidRef = useRef<string | null>(null);
const sessionApiBaseRef = useRef('');
const baseUrlRef = useRef('');
const cleanedRef = useRef(false);
@@ -180,18 +183,23 @@ export default function QrCodeLoginDialog({
}
if (sessionIdRef.current) {
const token = localStorage.getItem('token');
const baseUrl =
import.meta.env.VITE_API_BASE_URL || window.location.origin;
const workspaceUuid = sessionWorkspaceUuidRef.current;
fetch(
`${baseUrl}${platformConfigRef.current.apiBase}/${sessionIdRef.current}`,
`${baseUrlRef.current}${sessionApiBaseRef.current}/${sessionIdRef.current}`,
{
method: 'DELETE',
headers: { Authorization: `Bearer ${token}` },
headers: {
Authorization: `Bearer ${token}`,
...(workspaceUuid ? { 'X-Workspace-Id': workspaceUuid } : {}),
},
keepalive: true,
},
).catch(() => {});
sessionIdRef.current = null;
}
sessionWorkspaceUuidRef.current = null;
sessionApiBaseRef.current = '';
baseUrlRef.current = '';
}, []);
const startLogin = useCallback(async () => {
@@ -204,6 +212,7 @@ export default function QrCodeLoginDialog({
setSuccessMeta('');
const token = localStorage.getItem('token');
const workspaceUuid = getActiveWorkspaceUuid();
const baseUrl = import.meta.env.VITE_API_BASE_URL || window.location.origin;
baseUrlRef.current = baseUrl;
const cfg = platformConfigRef.current;
@@ -214,7 +223,10 @@ export default function QrCodeLoginDialog({
const res = await fetch(`${baseUrl}${cfg.apiBase}`, {
method: 'POST',
headers: { Authorization: `Bearer ${token}` },
headers: {
Authorization: `Bearer ${token}`,
...(workspaceUuid ? { 'X-Workspace-Id': workspaceUuid } : {}),
},
signal: controller.signal,
});
@@ -225,6 +237,8 @@ export default function QrCodeLoginDialog({
const { session_id, qr_data_url, qr_url, expire_at } = json.data;
sessionIdRef.current = session_id;
sessionWorkspaceUuidRef.current = workspaceUuid;
sessionApiBaseRef.current = cfg.apiBase;
if (qr_data_url) {
setQrDataUrl(qr_data_url);
@@ -270,11 +284,19 @@ export default function QrCodeLoginDialog({
`${baseUrlRef.current}${cfg.apiBase}/${sessionIdRef.current}`,
{
method: 'DELETE',
headers: { Authorization: `Bearer ${token}` },
headers: {
Authorization: `Bearer ${token}`,
...(workspaceUuid
? { 'X-Workspace-Id': workspaceUuid }
: {}),
},
keepalive: true,
},
).catch(() => {});
sessionIdRef.current = null;
sessionWorkspaceUuidRef.current = null;
sessionApiBaseRef.current = '';
baseUrlRef.current = '';
}
setState('expired');
}
@@ -286,7 +308,12 @@ export default function QrCodeLoginDialog({
try {
const pollRes = await fetch(
`${baseUrl}${cfg.apiBase}/status/${session_id}`,
{ headers: { Authorization: `Bearer ${token}` } },
{
headers: {
Authorization: `Bearer ${token}`,
...(workspaceUuid ? { 'X-Workspace-Id': workspaceUuid } : {}),
},
},
);
if (!pollRes.ok) return;
+3
View File
@@ -1921,6 +1921,9 @@ const enUS = {
'Scan the QR code below with WeChat to authorize and automatically fill in the token',
loginSuccess: 'Login successful! Token has been filled in',
loginFailed: 'Login failed',
connecting: 'Connecting to WeChat service...',
waitingForScan: 'Waiting for scan',
retry: 'Retry',
},
dingtalk: {
createApp: 'One-Click Create DingTalk App',
+3
View File
@@ -1747,6 +1747,9 @@ const esES = {
loginSuccess:
'¡Inicio de sesión correcto! El token se ha rellenado automáticamente',
loginFailed: 'Error al iniciar sesión',
connecting: 'Conectando con el servicio de WeChat...',
waitingForScan: 'Esperando escaneo',
retry: 'Reintentar',
},
dingtalk: {
createApp: 'Crear aplicación de DingTalk con un clic',
+3
View File
@@ -1837,6 +1837,9 @@ const jaJP = {
scanQRCode: '以下のQRコードをWeChatでスキャンし、トークンを自動入力',
loginSuccess: 'ログイン成功!トークンが自動入力されました',
loginFailed: 'ログイン失敗',
connecting: 'WeChatサービスに接続中...',
waitingForScan: 'スキャン待ち',
retry: '再試行',
},
dingtalk: {
createApp: 'ワンクリックでDingTalkアプリ作成',
+3
View File
@@ -1717,6 +1717,9 @@ const ruRU = {
'Отсканируйте QR-код ниже в WeChat, чтобы авторизоваться и автоматически заполнить токен',
loginSuccess: 'Вход выполнен успешно! Токен заполнен автоматически',
loginFailed: 'Не удалось выполнить вход',
connecting: 'Подключение к сервису WeChat...',
waitingForScan: 'Ожидание сканирования',
retry: 'Повторить',
},
dingtalk: {
createApp: 'Создать приложение DingTalk в один клик',
+3
View File
@@ -1680,6 +1680,9 @@ const thTH = {
'สแกนคิวอาร์โค้ดด้านล่างด้วย WeChat เพื่ออนุญาตและกรอกโทเคนอัตโนมัติ',
loginSuccess: 'เข้าสู่ระบบสำเร็จ และกรอกโทเคนอัตโนมัติแล้ว',
loginFailed: 'เข้าสู่ระบบไม่สำเร็จ',
connecting: 'กำลังเชื่อมต่อบริการ WeChat...',
waitingForScan: 'กำลังรอการสแกน',
retry: 'ลองอีกครั้ง',
},
dingtalk: {
createApp: 'สร้างแอป DingTalk ด้วยคลิกเดียว',
+3
View File
@@ -1708,6 +1708,9 @@ const viVN = {
'Quét mã QR bên dưới bằng WeChat để ủy quyền và tự động điền token',
loginSuccess: 'Đăng nhập thành công! Token đã được điền tự động',
loginFailed: 'Đăng nhập thất bại',
connecting: 'Đang kết nối tới dịch vụ WeChat...',
waitingForScan: 'Đang chờ quét mã',
retry: 'Thử lại',
},
dingtalk: {
createApp: 'Tạo ứng dụng DingTalk chỉ với một lần nhấp',
+3
View File
@@ -1834,6 +1834,9 @@ const zhHans = {
scanQRCode: '请使用微信扫描以下二维码,授权后将自动登录并填写令牌',
loginSuccess: '登录成功!令牌已自动填入',
loginFailed: '登录失败',
connecting: '正在连接微信服务...',
waitingForScan: '等待扫码中',
retry: '重试',
},
dingtalk: {
createApp: '一键创建钉钉应用',
+3
View File
@@ -1657,6 +1657,9 @@ const zhHant = {
scanQRCode: '請使用微信掃描以下 QR Code,授權後將自動登入並填寫令牌',
loginSuccess: '登入成功!令牌已自動填入',
loginFailed: '登入失敗',
connecting: '正在連接微信服務...',
waitingForScan: '等待掃碼中',
retry: '重試',
},
dingtalk: {
createApp: '一鍵建立釘釘應用',