mirror of
https://github.com/OpenHands/OpenHands.git
synced 2026-03-22 13:47:19 +08:00
Add signed cookie-based GitHub authentication caching (#4853)
Co-authored-by: openhands <openhands@all-hands.dev>
This commit is contained in:
@@ -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}`,
|
||||
|
||||
@@ -2,10 +2,12 @@ import asyncio
|
||||
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
|
||||
@@ -15,6 +17,7 @@ from openhands.server.data_models.feedback import FeedbackDataModel, store_feedb
|
||||
from openhands.server.github import (
|
||||
GITHUB_CLIENT_ID,
|
||||
GITHUB_CLIENT_SECRET,
|
||||
UserVerifier,
|
||||
authenticate_github_user,
|
||||
)
|
||||
from openhands.storage import get_file_store
|
||||
@@ -60,7 +63,7 @@ from openhands.events.serialization import event_to_dict
|
||||
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
|
||||
|
||||
@@ -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')
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user