auth.py 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. import time
  2. import warnings
  3. import requests
  4. from openhands.server.github_utils import (
  5. GITHUB_CLIENT_ID,
  6. GITHUB_CLIENT_SECRET,
  7. authenticate_github_user,
  8. )
  9. with warnings.catch_warnings():
  10. warnings.simplefilter('ignore')
  11. from fastapi import (
  12. APIRouter,
  13. Request,
  14. status,
  15. )
  16. from fastapi.responses import JSONResponse
  17. from pydantic import BaseModel
  18. from openhands.core.logger import openhands_logger as logger
  19. from openhands.server.auth import sign_token
  20. from openhands.server.shared import config
  21. app = APIRouter(prefix='/api')
  22. class AuthCode(BaseModel):
  23. code: str
  24. @app.post('/github/callback')
  25. def github_callback(auth_code: AuthCode):
  26. # Prepare data for the token exchange request
  27. data = {
  28. 'client_id': GITHUB_CLIENT_ID,
  29. 'client_secret': GITHUB_CLIENT_SECRET,
  30. 'code': auth_code.code,
  31. }
  32. logger.debug('Exchanging code for GitHub token')
  33. headers = {'Accept': 'application/json'}
  34. response = requests.post(
  35. 'https://github.com/login/oauth/access_token', data=data, headers=headers
  36. )
  37. if response.status_code != 200:
  38. logger.error(f'Failed to exchange code for token: {response.text}')
  39. return JSONResponse(
  40. status_code=status.HTTP_400_BAD_REQUEST,
  41. content={'error': 'Failed to exchange code for token'},
  42. )
  43. token_response = response.json()
  44. if 'access_token' not in token_response:
  45. return JSONResponse(
  46. status_code=status.HTTP_400_BAD_REQUEST,
  47. content={'error': 'No access token in response'},
  48. )
  49. return JSONResponse(
  50. status_code=status.HTTP_200_OK,
  51. content={'access_token': token_response['access_token']},
  52. )
  53. @app.post('/authenticate')
  54. async def authenticate(request: Request):
  55. token = request.headers.get('X-GitHub-Token')
  56. if not await authenticate_github_user(token):
  57. return JSONResponse(
  58. status_code=status.HTTP_401_UNAUTHORIZED,
  59. content={'error': 'Not authorized via GitHub waitlist'},
  60. )
  61. # Create a signed JWT token with 1-hour expiration
  62. cookie_data = {
  63. 'github_token': token,
  64. 'exp': int(time.time()) + 3600, # 1 hour expiration
  65. }
  66. signed_token = sign_token(cookie_data, config.jwt_secret)
  67. response = JSONResponse(
  68. status_code=status.HTTP_200_OK, content={'message': 'User authenticated'}
  69. )
  70. # Set secure cookie with signed token
  71. response.set_cookie(
  72. key='github_auth',
  73. value=signed_token,
  74. max_age=3600, # 1 hour in seconds
  75. httponly=True,
  76. secure=True,
  77. samesite='strict',
  78. )
  79. return response