diff --git a/src/api/auth.ts b/src/api/auth.ts index a9b0552..4cf23a5 100644 --- a/src/api/auth.ts +++ b/src/api/auth.ts @@ -138,7 +138,7 @@ export const authApi = { provider: string, ): Promise<{ authorize_url: string; state: string }> => { const response = await apiClient.get<{ authorize_url: string; state: string }>( - `/cabinet/auth/oauth/${provider}/authorize`, + `/cabinet/auth/oauth/${encodeURIComponent(provider)}/authorize`, ); return response.data; }, @@ -146,7 +146,7 @@ export const authApi = { // OAuth: callback (exchange code for tokens) oauthCallback: async (provider: string, code: string, state: string): Promise => { const response = await apiClient.post( - `/cabinet/auth/oauth/${provider}/callback`, + `/cabinet/auth/oauth/${encodeURIComponent(provider)}/callback`, { code, state }, ); return response.data; diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx index 1cc840f..e3c71bf 100644 --- a/src/pages/Login.tsx +++ b/src/pages/Login.tsx @@ -109,6 +109,17 @@ export default function Login() { setOauthLoading(provider); try { const { authorize_url, state } = await authApi.getOAuthAuthorizeUrl(provider); + + // Validate redirect URL — only allow HTTPS to prevent open redirect + try { + const parsed = new URL(authorize_url); + if (parsed.protocol !== 'https:') { + throw new Error('Invalid OAuth redirect URL'); + } + } catch { + throw new Error('Invalid OAuth redirect URL'); + } + saveOAuthState(state, provider); window.location.href = authorize_url; } catch { @@ -122,7 +133,7 @@ export default function Login() { // If email auth is disabled but user came with ref param, redirect to bot useEffect(() => { if (referralCode && emailAuthConfig?.enabled === false && botUsername) { - window.location.href = `https://t.me/${botUsername}?start=${referralCode}`; + window.location.href = `https://t.me/${botUsername}?start=${encodeURIComponent(referralCode)}`; } }, [referralCode, emailAuthConfig, botUsername]); @@ -165,7 +176,8 @@ export default function Login() { const error = err as { response?: { status?: number; data?: { detail?: string } } }; const status = error.response?.status; const detail = error.response?.data?.detail; - console.warn(`Telegram auth attempt ${attempt + 1} failed:`, status, detail); + if (import.meta.env.DEV) + console.warn(`Telegram auth attempt ${attempt + 1} failed:`, status, detail); if (status === 401 && attempt < MAX_RETRIES) { await new Promise((r) => setTimeout(r, 1500)); @@ -200,7 +212,7 @@ export default function Login() { const error = err as { response?: { status?: number; data?: { detail?: string } } }; const status = error.response?.status; const detail = error.response?.data?.detail; - console.warn('Telegram auth retry failed:', status, detail); + if (import.meta.env.DEV) console.warn('Telegram auth retry failed:', status, detail); setError( detail || t('auth.telegramRetryFailed', 'Authorization failed. Close the app and try again.'),