Skip to content

Commit

Permalink
Add signed cookie-based GitHub authentication caching (#4853)
Browse files Browse the repository at this point in the history
Co-authored-by: openhands <[email protected]>
  • Loading branch information
rbren and openhands-agent authored Nov 8, 2024
1 parent 4ce3b90 commit e1383af
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 7 deletions.
10 changes: 10 additions & 0 deletions frontend/src/services/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,16 @@ export async function request(
} catch (e) {
onFail(`Error fetching ${url}`);
}
if (response?.status === 401) {
await request(
"/api/authenticate",
{
method: "POST",
},
true,
);
return request(url, options, disableToast, returnResponse, maxRetries - 1);
}
if (response?.status && response?.status >= 400) {
onFail(
`${response.status} error while fetching ${url}: ${response?.statusText}`,
Expand Down
43 changes: 36 additions & 7 deletions openhands/server/listen.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
import os
import re
import tempfile
import time
import uuid
import warnings
from contextlib import asynccontextmanager

import jwt
import requests
from pathspec import PathSpec
from pathspec.patterns import GitWildMatchPattern
Expand All @@ -15,6 +17,7 @@
from openhands.server.github import (
GITHUB_CLIENT_ID,
GITHUB_CLIENT_SECRET,
UserVerifier,
authenticate_github_user,
)
from openhands.storage import get_file_store
Expand Down Expand Up @@ -60,7 +63,7 @@
from openhands.events.stream import AsyncEventStreamWrapper
from openhands.llm import bedrock
from openhands.runtime.base import Runtime
from openhands.server.auth import get_sid_from_token, sign_token
from openhands.server.auth.auth import get_sid_from_token, sign_token
from openhands.server.middleware import LocalhostCORSMiddleware, NoCacheMiddleware
from openhands.server.session import SessionManager

Expand Down Expand Up @@ -204,12 +207,22 @@ async def attach_session(request: Request, call_next):
response = await call_next(request)
return response

github_token = request.headers.get('X-GitHub-Token')
if not await authenticate_github_user(github_token):
return JSONResponse(
status_code=status.HTTP_401_UNAUTHORIZED,
content={'error': 'Not authenticated'},
)
user_verifier = UserVerifier()
if user_verifier.is_active():
signed_token = request.cookies.get('github_auth')
if not signed_token:
return JSONResponse(
status_code=status.HTTP_401_UNAUTHORIZED,
content={'error': 'Not authenticated'},
)
try:
jwt.decode(signed_token, config.jwt_secret, algorithms=['HS256'])
except Exception as e:
logger.warning(f'Invalid token: {e}')
return JSONResponse(
status_code=status.HTTP_401_UNAUTHORIZED,
content={'error': 'Invalid token'},
)

if not request.headers.get('Authorization'):
logger.warning('Missing Authorization header')
Expand Down Expand Up @@ -864,10 +877,26 @@ async def authenticate(request: Request):
content={'error': 'Not authorized via GitHub waitlist'},
)

# Create a signed JWT token with 1-hour expiration
cookie_data = {
'github_token': token,
'exp': int(time.time()) + 3600, # 1 hour expiration
}
signed_token = sign_token(cookie_data, config.jwt_secret)

response = JSONResponse(
status_code=status.HTTP_200_OK, content={'message': 'User authenticated'}
)

# Set secure cookie with signed token
response.set_cookie(
key='github_auth',
value=signed_token,
max_age=3600, # 1 hour in seconds
httponly=True,
secure=True,
samesite='strict',
)
return response


Expand Down

0 comments on commit e1383af

Please sign in to comment.