|
1 | 1 | // Authentication Module for Ready To Review |
2 | 2 | console.log('[Auth Module] Loading...'); |
| 3 | + |
| 4 | +// Log URL parameters on page load for debugging OAuth flow |
| 5 | +const urlParams = new URLSearchParams(window.location.search); |
| 6 | +if (urlParams.has('code') || urlParams.has('state') || urlParams.has('error')) { |
| 7 | + console.log('[Auth] OAuth callback detected!'); |
| 8 | + console.log('[Auth] URL:', window.location.href); |
| 9 | + console.log('[Auth] Code:', urlParams.get('code') ? 'present (length=' + urlParams.get('code').length + ')' : 'missing'); |
| 10 | + console.log('[Auth] State:', urlParams.get('state') ? 'present' : 'missing'); |
| 11 | + console.log('[Auth] Error:', urlParams.get('error')); |
| 12 | + console.log('[Auth] Error description:', urlParams.get('error_description')); |
| 13 | +} |
| 14 | + |
3 | 15 | export const Auth = (() => { |
4 | 16 | "use strict"; |
5 | 17 | console.log('[Auth Module] Initializing...'); |
@@ -36,39 +48,38 @@ export const Auth = (() => { |
36 | 48 | } |
37 | 49 |
|
38 | 50 | const getStoredToken = () => { |
39 | | - // Check for domain-wide access_token cookie (set by server after OAuth) |
40 | | - const accessToken = getCookie('access_token'); |
41 | | - if (accessToken) return accessToken; |
| 51 | + // Check localStorage for OAuth token |
| 52 | + const localToken = localStorage.getItem(CONFIG.STORAGE_KEY); |
| 53 | + if (localToken) return localToken; |
42 | 54 |
|
43 | | - // Check cookie for PAT |
| 55 | + // Check for PAT (user-entered token, stored in non-HttpOnly cookie) |
44 | 56 | const cookieToken = getCookie(CONFIG.COOKIE_KEY); |
45 | 57 | if (cookieToken) return cookieToken; |
46 | 58 |
|
47 | | - // Fall back to localStorage (for OAuth - legacy) |
48 | | - return localStorage.getItem(CONFIG.STORAGE_KEY); |
| 59 | + return null; |
49 | 60 | }; |
50 | 61 |
|
51 | 62 | const storeToken = (token, useCookie = false) => { |
52 | | - // For PAT, store in a non-HttpOnly cookie |
53 | 63 | if (useCookie) { |
| 64 | + // For PAT, store in cookie |
54 | 65 | setCookie(CONFIG.COOKIE_KEY, token, 365); // 1 year |
55 | 66 | } else { |
56 | | - // For OAuth, token is now set by server as HttpOnly cookie |
57 | | - // We don't store it in localStorage anymore |
58 | | - // This function is kept for backward compatibility |
| 67 | + // For OAuth, store in localStorage |
| 68 | + localStorage.setItem(CONFIG.STORAGE_KEY, token); |
59 | 69 | } |
60 | 70 | }; |
61 | 71 |
|
62 | 72 | const clearToken = () => { |
| 73 | + // Clear localStorage |
63 | 74 | localStorage.removeItem(CONFIG.STORAGE_KEY); |
| 75 | + // Clear PAT cookie |
64 | 76 | deleteCookie(CONFIG.COOKIE_KEY); |
65 | | - // Clear domain-wide cookies |
66 | | - deleteCookie('access_token'); |
67 | | - deleteCookie('username'); |
68 | 77 | }; |
69 | 78 |
|
70 | 79 | const initiateOAuthLogin = () => { |
71 | 80 | console.log('[Auth.initiateOAuthLogin] Starting OAuth flow...'); |
| 81 | + console.log('[Auth.initiateOAuthLogin] Current URL:', window.location.href); |
| 82 | + console.log('[Auth.initiateOAuthLogin] Redirecting to:', window.location.origin + '/oauth/login'); |
72 | 83 |
|
73 | 84 | // Simply redirect to the backend OAuth endpoint |
74 | 85 | // The backend will handle state generation and cookie management |
@@ -179,8 +190,53 @@ export const Auth = (() => { |
179 | 190 | } |
180 | 191 | }; |
181 | 192 |
|
182 | | - // OAuth callback is now fully handled by the backend |
183 | | - // The backend sets domain-wide cookies and redirects to the user's workspace |
| 193 | + // Handle OAuth callback with auth code |
| 194 | + const handleAuthCodeCallback = async () => { |
| 195 | + // Auth code is in fragment (hash) not query parameter for security |
| 196 | + // Fragments are not sent to server in Referer headers |
| 197 | + const fragment = window.location.hash.substring(1); // Remove leading # |
| 198 | + const fragmentParams = new URLSearchParams(fragment); |
| 199 | + const authCode = fragmentParams.get('auth_code'); |
| 200 | + |
| 201 | + if (!authCode) { |
| 202 | + return false; // No auth code, nothing to do |
| 203 | + } |
| 204 | + |
| 205 | + console.log('[Auth] Found auth_code in fragment, exchanging for token...'); |
| 206 | + |
| 207 | + try { |
| 208 | + // Exchange auth code for token |
| 209 | + const response = await fetch('/oauth/exchange', { |
| 210 | + method: 'POST', |
| 211 | + headers: { |
| 212 | + 'Content-Type': 'application/json', |
| 213 | + }, |
| 214 | + body: JSON.stringify({ auth_code: authCode }), |
| 215 | + }); |
| 216 | + |
| 217 | + if (!response.ok) { |
| 218 | + console.error('[Auth] Failed to exchange auth code:', response.status); |
| 219 | + return false; |
| 220 | + } |
| 221 | + |
| 222 | + const data = await response.json(); |
| 223 | + |
| 224 | + // Store token in localStorage |
| 225 | + storeToken(data.token); |
| 226 | + |
| 227 | + console.log('[Auth] Successfully exchanged auth code for token, user:', data.username); |
| 228 | + |
| 229 | + // Remove auth_code from URL fragment |
| 230 | + const url = new URL(window.location); |
| 231 | + url.hash = ''; // Clear the fragment |
| 232 | + window.history.replaceState({}, '', url); |
| 233 | + |
| 234 | + return true; |
| 235 | + } catch (error) { |
| 236 | + console.error('[Auth] Error exchanging auth code:', error); |
| 237 | + return false; |
| 238 | + } |
| 239 | + }; |
184 | 240 |
|
185 | 241 | const handleAuthError = () => { |
186 | 242 | clearToken(); |
@@ -374,6 +430,7 @@ export const Auth = (() => { |
374 | 430 | storeToken, |
375 | 431 | clearToken, |
376 | 432 | initiateOAuthLogin, |
| 433 | + handleAuthCodeCallback, |
377 | 434 | showGitHubAppModal, |
378 | 435 | closeGitHubAppModal, |
379 | 436 | proceedWithOAuth, |
|
0 commit comments