fix: harden OAuth login flow — open redirect, path traversal, info leak

- Validate authorize_url is HTTPS before redirect (open redirect prevention)
- encodeURIComponent on provider name in API URL paths (path traversal prevention)
- encodeURIComponent on referralCode in t.me deep link
- Gate console.warn behind import.meta.env.DEV (no info leak in prod)
This commit is contained in:
Fringg
2026-02-11 02:58:04 +03:00
parent f74e316161
commit a744b41910
2 changed files with 17 additions and 5 deletions

View File

@@ -138,7 +138,7 @@ export const authApi = {
provider: string, provider: string,
): Promise<{ authorize_url: string; state: string }> => { ): Promise<{ authorize_url: string; state: string }> => {
const response = await apiClient.get<{ 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; return response.data;
}, },
@@ -146,7 +146,7 @@ export const authApi = {
// OAuth: callback (exchange code for tokens) // OAuth: callback (exchange code for tokens)
oauthCallback: async (provider: string, code: string, state: string): Promise<AuthResponse> => { oauthCallback: async (provider: string, code: string, state: string): Promise<AuthResponse> => {
const response = await apiClient.post<AuthResponse>( const response = await apiClient.post<AuthResponse>(
`/cabinet/auth/oauth/${provider}/callback`, `/cabinet/auth/oauth/${encodeURIComponent(provider)}/callback`,
{ code, state }, { code, state },
); );
return response.data; return response.data;

View File

@@ -109,6 +109,17 @@ export default function Login() {
setOauthLoading(provider); setOauthLoading(provider);
try { try {
const { authorize_url, state } = await authApi.getOAuthAuthorizeUrl(provider); 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); saveOAuthState(state, provider);
window.location.href = authorize_url; window.location.href = authorize_url;
} catch { } catch {
@@ -122,7 +133,7 @@ export default function Login() {
// If email auth is disabled but user came with ref param, redirect to bot // If email auth is disabled but user came with ref param, redirect to bot
useEffect(() => { useEffect(() => {
if (referralCode && emailAuthConfig?.enabled === false && botUsername) { 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]); }, [referralCode, emailAuthConfig, botUsername]);
@@ -165,6 +176,7 @@ export default function Login() {
const error = err as { response?: { status?: number; data?: { detail?: string } } }; const error = err as { response?: { status?: number; data?: { detail?: string } } };
const status = error.response?.status; const status = error.response?.status;
const detail = error.response?.data?.detail; const detail = error.response?.data?.detail;
if (import.meta.env.DEV)
console.warn(`Telegram auth attempt ${attempt + 1} failed:`, status, detail); console.warn(`Telegram auth attempt ${attempt + 1} failed:`, status, detail);
if (status === 401 && attempt < MAX_RETRIES) { if (status === 401 && attempt < MAX_RETRIES) {
@@ -200,7 +212,7 @@ export default function Login() {
const error = err as { response?: { status?: number; data?: { detail?: string } } }; const error = err as { response?: { status?: number; data?: { detail?: string } } };
const status = error.response?.status; const status = error.response?.status;
const detail = error.response?.data?.detail; 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( setError(
detail || detail ||
t('auth.telegramRetryFailed', 'Authorization failed. Close the app and try again.'), t('auth.telegramRetryFailed', 'Authorization failed. Close the app and try again.'),