Skip to content

Commit 9a45e49

Browse files
Thomas StrombergThomas Stromberg
authored andcommitted
improve oauth2 exchange security
1 parent 699fd99 commit 9a45e49

File tree

6 files changed

+319
-97
lines changed

6 files changed

+319
-97
lines changed

assets/app.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -837,6 +837,21 @@ const App = (() => {
837837
return;
838838
}
839839

840+
// Handle OAuth callback with auth code
841+
console.log('[App.init] Checking for OAuth auth code...');
842+
const authCodeExchanged = await Auth.handleAuthCodeCallback();
843+
844+
// Re-check for authentication token (it might have been set after module load or auth code exchange)
845+
state.accessToken = Auth.getStoredToken();
846+
console.log('[App.init] Checked for access token:', state.accessToken ? 'found' : 'not found');
847+
848+
// If we just exchanged an auth code successfully, reload to start with fresh state
849+
if (authCodeExchanged) {
850+
console.log('[App.init] Auth code exchanged successfully, reloading page...');
851+
window.location.reload();
852+
return;
853+
}
854+
840855
// Check for authentication
841856
if (!state.accessToken) {
842857
updateSearchInputVisibility();

assets/auth.js

Lines changed: 72 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
11
// Authentication Module for Ready To Review
22
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+
315
export const Auth = (() => {
416
"use strict";
517
console.log('[Auth Module] Initializing...');
@@ -36,39 +48,38 @@ export const Auth = (() => {
3648
}
3749

3850
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;
4254

43-
// Check cookie for PAT
55+
// Check for PAT (user-entered token, stored in non-HttpOnly cookie)
4456
const cookieToken = getCookie(CONFIG.COOKIE_KEY);
4557
if (cookieToken) return cookieToken;
4658

47-
// Fall back to localStorage (for OAuth - legacy)
48-
return localStorage.getItem(CONFIG.STORAGE_KEY);
59+
return null;
4960
};
5061

5162
const storeToken = (token, useCookie = false) => {
52-
// For PAT, store in a non-HttpOnly cookie
5363
if (useCookie) {
64+
// For PAT, store in cookie
5465
setCookie(CONFIG.COOKIE_KEY, token, 365); // 1 year
5566
} 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);
5969
}
6070
};
6171

6272
const clearToken = () => {
73+
// Clear localStorage
6374
localStorage.removeItem(CONFIG.STORAGE_KEY);
75+
// Clear PAT cookie
6476
deleteCookie(CONFIG.COOKIE_KEY);
65-
// Clear domain-wide cookies
66-
deleteCookie('access_token');
67-
deleteCookie('username');
6877
};
6978

7079
const initiateOAuthLogin = () => {
7180
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');
7283

7384
// Simply redirect to the backend OAuth endpoint
7485
// The backend will handle state generation and cookie management
@@ -179,8 +190,53 @@ export const Auth = (() => {
179190
}
180191
};
181192

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+
};
184240

185241
const handleAuthError = () => {
186242
clearToken();
@@ -374,6 +430,7 @@ export const Auth = (() => {
374430
storeToken,
375431
clearToken,
376432
initiateOAuthLogin,
433+
handleAuthCodeCallback,
377434
showGitHubAppModal,
378435
closeGitHubAppModal,
379436
proceedWithOAuth,

assets/user.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ export const User = (() => {
110110

111111
try {
112112
const response = await fetch(
113-
"https://turn.ready-to-review.dev/v1/validate",
113+
"https://turn.github.codegroove.app/v1/validate",
114114
{
115115
method: "POST",
116116
headers,

index.html

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,16 @@
88
content="A modern dashboard for managing GitHub pull requests"
99
/>
1010
<title>Ready To Review - GitHub PR Dashboard</title>
11-
<link rel="stylesheet" href="/assets/styles.css" />
11+
<link rel="stylesheet" href="/assets/styles.css?v=BUILD_TIMESTAMP" />
1212
<link rel="icon" href="/favicon.ico" />
1313
<link rel="preconnect" href="https://api.github.com" />
1414
<link rel="dns-prefetch" href="https://api.github.com" />
1515
<link rel="preconnect" href="https://avatars.githubusercontent.com" />
1616
<link rel="dns-prefetch" href="https://avatars.githubusercontent.com" />
17-
<link rel="modulepreload" href="/assets/app.js" />
18-
<link rel="modulepreload" href="/assets/user.js" />
19-
<link rel="modulepreload" href="/assets/utils.js" />
20-
<link rel="modulepreload" href="/assets/workspace.js" />
17+
<link rel="modulepreload" href="/assets/app.js?v=BUILD_TIMESTAMP" />
18+
<link rel="modulepreload" href="/assets/user.js?v=BUILD_TIMESTAMP" />
19+
<link rel="modulepreload" href="/assets/utils.js?v=BUILD_TIMESTAMP" />
20+
<link rel="modulepreload" href="/assets/workspace.js?v=BUILD_TIMESTAMP" />
2121
</head>
2222
<body>
2323
<div id="app">
@@ -1286,7 +1286,7 @@ <h1 class="changelog-title" id="changelogTitleText">
12861286
</footer>
12871287
</div>
12881288

1289-
<script src="/assets/demo-data.js"></script>
1290-
<script type="module" src="/assets/app.js"></script>
1289+
<script src="/assets/demo-data.js?v=BUILD_TIMESTAMP"></script>
1290+
<script type="module" src="/assets/app.js?v=BUILD_TIMESTAMP"></script>
12911291
</body>
12921292
</html>

0 commit comments

Comments
 (0)