1import { getStoredSession } from './sessionState.js'; 2import { trackOAuthStartIntentThenContinue } from '../marketing/googleAds.js'; 3import { trackInstallSquareCtaThenContinue } from '../marketing/googleAds.js'; 4const SQUARE_OAUTH_START_ENDPOINT = '/api/business/oauth/square/start'; 5/** 6 * Valide un chemin de redirection pour prévenir les open redirects et XSS 7 * SÃCURITÃ: Seuls les chemins relatifs commençant par / sont autorisés 8 */ 9function validateRedirectPath(redirectTo) { 10 if (!redirectTo || typeof redirectTo !== 'string') { 11 return null; 12 } 13 const trimmed = redirectTo.trim(); 14 // SÃCURITÃ: Rejeter les URLs absolues, javascript:, data:, etc. 15 if (!trimmed.startsWith('/') || 16 trimmed.startsWith('//') || 17 trimmed.includes('://') || 18 trimmed.includes('javascript:') || 19 trimmed.includes('data:') || 20 trimmed.includes('<') || 21 trimmed.includes('>')) { 22 console.warn('[Square OAuth] Chemin de redirection invalide rejeté:', trimmed); 23 return null; 24 } 25 return trimmed; 26} 27/** 28 * Construit l'URL de démarrage OAuth Square (avec query params). 29 */ 30function buildSquareOAuthStartUrl(options) { 31 const params = new URLSearchParams(); 32 const safeRedirectTo = validateRedirectPath(options.redirectTo); 33 if (safeRedirectTo) { 34 params.set('redirectTo', safeRedirectTo); 35 } 36 if (options.source && ['login', 'register', 'dashboard', 'home', 'landing'].includes(options.source)) { 37 params.set('source', options.source); 38 } 39 const query = params.toString(); 40 const targetUrl = query 41 ? `${SQUARE_OAUTH_START_ENDPOINT}?${query}` 42 : SQUARE_OAUTH_START_ENDPOINT; 43 if (!targetUrl.startsWith('/') || targetUrl.startsWith('//')) { 44 throw new Error('URL de redirection invalide'); 45 } 46 return targetUrl; 47} 48/** 49 * Récupère le token de session depuis localStorage (session Supabase). 50 * La session est stockée en localStorage ; une simple navigation vers /start nâenvoie pas 51 * le token (le serveur ne reçoit que les cookies), donc le serveur ne sait pas que lâutilisateur 52 * est connecté et ne peut pas vérifier « Square déjà connecté » â on envoie toujours vers Square. 53 */ 54function getSessionTokenFromStorage() { 55 return getStoredSession()?.access_token ?? null; 56} 57/** 58 * Démarre le flux OAuth Square 59 * SÃCURITÃ: Valide tous les paramètres avant de construire l'URL 60 * 61 * - Si une session existe en localStorage : on appelle /start avec Authorization pour que le serveur 62 * sache qui on est ; il peut alors skip Square si déjà connecté, ou renvoyer redirectUrl en JSON. 63 * On navigue vers cette URL (dashboard ou Square). Pas de 302 suivie par fetch (Safari). 64 * - Sinon : navigation directe vers /start (le serveur renverra 302 vers Square). 65 */ 66export function startSquareOAuth(options = {}) { 67 const targetUrl = buildSquareOAuthStartUrl(options); 68 const token = getSessionTokenFromStorage(); 69 trackInstallSquareCtaThenContinue(() => { 70 trackOAuthStartIntentThenContinue('square', () => { 71 if (token) { 72 fetch(targetUrl, { 73 method: 'GET', 74 headers: { Authorization: `Bearer ${token}` }, 75 credentials: 'include', 76 }) 77 .then((res) => res.json()) 78 .then((data) => { 79 if (typeof data?.redirectUrl === 'string') { 80 window.location.href = data.redirectUrl; 81 } 82 else { 83 window.location.href = targetUrl; 84 } 85 }) 86 .catch(() => { 87 window.location.href = targetUrl; 88 }); 89 return; 90 } 91 window.location.href = targetUrl; 92 }); 93 }); 94}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.