mirror of
https://github.com/OpenHands/OpenHands.git
synced 2025-12-25 21:36:52 +08:00
143 lines
5.0 KiB
Python
143 lines
5.0 KiB
Python
import re
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, Request, status
|
|
from fastapi.responses import JSONResponse, RedirectResponse
|
|
from pydantic import BaseModel, field_validator
|
|
from server.auth.constants import KEYCLOAK_CLIENT_ID
|
|
from server.auth.keycloak_manager import get_keycloak_admin
|
|
from server.auth.saas_user_auth import SaasUserAuth
|
|
from server.routes.auth import set_response_cookie
|
|
|
|
from openhands.core.logger import openhands_logger as logger
|
|
from openhands.server.user_auth import get_user_id
|
|
from openhands.server.user_auth.user_auth import get_user_auth
|
|
|
|
# Email validation regex pattern
|
|
EMAIL_REGEX = re.compile(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$')
|
|
|
|
api_router = APIRouter(prefix='/api/email')
|
|
|
|
|
|
class EmailUpdate(BaseModel):
|
|
email: str
|
|
|
|
@field_validator('email')
|
|
def validate_email(cls, v):
|
|
if not EMAIL_REGEX.match(v):
|
|
raise ValueError('Invalid email format')
|
|
return v
|
|
|
|
|
|
@api_router.post('')
|
|
async def update_email(
|
|
email_data: EmailUpdate, request: Request, user_id: str = Depends(get_user_id)
|
|
):
|
|
# Email validation is now handled by the Pydantic model
|
|
# If we get here, the email has already passed validation
|
|
|
|
try:
|
|
keycloak_admin = get_keycloak_admin()
|
|
user = keycloak_admin.get_user(user_id)
|
|
email = email_data.email
|
|
|
|
# Additional validation check just to be safe
|
|
if not EMAIL_REGEX.match(email):
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST, detail='Invalid email format'
|
|
)
|
|
|
|
await keycloak_admin.a_update_user(
|
|
user_id=user_id,
|
|
payload={
|
|
'email': email,
|
|
'emailVerified': False,
|
|
'enabled': user['enabled'], # Retain existing values
|
|
'username': user['username'], # Required field
|
|
},
|
|
)
|
|
|
|
user_auth: SaasUserAuth = await get_user_auth(request)
|
|
await user_auth.refresh() # refresh so access token has updated email
|
|
user_auth.email = email
|
|
user_auth.email_verified = False
|
|
response = JSONResponse(
|
|
status_code=status.HTTP_200_OK, content={'message': 'Email changed'}
|
|
)
|
|
|
|
# need to set auth cookie to the new tokens
|
|
set_response_cookie(
|
|
request=request,
|
|
response=response,
|
|
keycloak_access_token=user_auth.access_token.get_secret_value(),
|
|
keycloak_refresh_token=user_auth.refresh_token.get_secret_value(),
|
|
secure=False if request.url.hostname == 'localhost' else True,
|
|
accepted_tos=user_auth.accepted_tos,
|
|
)
|
|
|
|
await verify_email(request=request, user_id=user_id)
|
|
|
|
logger.info(f'Updating email address for {user_id} to {email}')
|
|
return response
|
|
|
|
except ValueError as e:
|
|
# Handle validation errors from Pydantic
|
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
|
except Exception as e:
|
|
logger.exception(f'Error updating email: {str(e)}')
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail='An error occurred while updating the email',
|
|
)
|
|
|
|
|
|
@api_router.put('/verify')
|
|
async def resend_email_verification(
|
|
request: Request, user_id: str = Depends(get_user_id)
|
|
):
|
|
await verify_email(request=request, user_id=user_id)
|
|
|
|
logger.info(f'Resending verification email for {user_id}')
|
|
return JSONResponse(
|
|
status_code=status.HTTP_200_OK,
|
|
content={'message': 'Email verification message sent'},
|
|
)
|
|
|
|
|
|
@api_router.get('/verified')
|
|
async def verified_email(request: Request):
|
|
user_auth: SaasUserAuth = await get_user_auth(request)
|
|
await user_auth.refresh() # refresh so access token has updated email
|
|
user_auth.email_verified = True
|
|
scheme = 'http' if request.url.hostname == 'localhost' else 'https'
|
|
redirect_uri = f'{scheme}://{request.url.netloc}/settings/user'
|
|
response = RedirectResponse(redirect_uri, status_code=302)
|
|
|
|
# need to set auth cookie to the new tokens
|
|
set_response_cookie(
|
|
request=request,
|
|
response=response,
|
|
keycloak_access_token=user_auth.access_token.get_secret_value(),
|
|
keycloak_refresh_token=user_auth.refresh_token.get_secret_value(),
|
|
secure=False if request.url.hostname == 'localhost' else True,
|
|
accepted_tos=user_auth.accepted_tos,
|
|
)
|
|
|
|
logger.info(f'Email {user_auth.email} verified.')
|
|
return response
|
|
|
|
|
|
async def verify_email(request: Request, user_id: str, is_auth_flow: bool = False):
|
|
keycloak_admin = get_keycloak_admin()
|
|
scheme = 'http' if request.url.hostname == 'localhost' else 'https'
|
|
redirect_uri = (
|
|
f'{scheme}://{request.url.netloc}?email_verified=true'
|
|
if is_auth_flow
|
|
else f'{scheme}://{request.url.netloc}/api/email/verified'
|
|
)
|
|
logger.info(f'Redirect URI: {redirect_uri}')
|
|
await keycloak_admin.a_send_verify_email(
|
|
user_id=user_id,
|
|
redirect_uri=redirect_uri,
|
|
client_id=KEYCLOAK_CLIENT_ID,
|
|
)
|