123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133 |
- import logging
- import os
- from datetime import datetime
- from core.base import (
- AuthConfig,
- CryptoProvider,
- EmailProvider,
- R2RException,
- TokenData,
- )
- from ..database import PostgresDatabaseProvider
- from .jwt import JwtAuthProvider
- logger = logging.getLogger(__name__)
- class ClerkAuthProvider(JwtAuthProvider):
- """
- ClerkAuthProvider extends JwtAuthProvider to support token verification with Clerk.
- It uses Clerk's SDK to verify the JWT token and extract user information.
- """
- def __init__(
- self,
- config: AuthConfig,
- crypto_provider: CryptoProvider,
- database_provider: PostgresDatabaseProvider,
- email_provider: EmailProvider,
- ):
- super().__init__(
- config=config,
- crypto_provider=crypto_provider,
- database_provider=database_provider,
- email_provider=email_provider,
- )
- try:
- from clerk_backend_api.jwks_helpers.verifytoken import (
- VerifyTokenOptions,
- verify_token,
- )
- self.verify_token = verify_token
- self.VerifyTokenOptions = VerifyTokenOptions
- except ImportError as e:
- raise R2RException(
- status_code=500,
- message="Clerk SDK is not installed. Run `pip install clerk-backend-api`",
- ) from e
- async def decode_token(self, token: str) -> TokenData:
- """
- Decode and verify the JWT token using Clerk's verify_token function.
- Args:
- token: The JWT token to decode
- Returns:
- TokenData: The decoded token data with user information
- Raises:
- R2RException: If the token is invalid or verification fails
- """
- clerk_secret_key = os.getenv("CLERK_SECRET_KEY")
- if not clerk_secret_key:
- raise R2RException(
- status_code=500,
- message="CLERK_SECRET_KEY environment variable is not set",
- )
- try:
- # Configure verification options
- options = self.VerifyTokenOptions(
- secret_key=clerk_secret_key,
- # Optional: specify audience if needed
- # audience="your-audience",
- # Optional: specify authorized parties if needed
- # authorized_parties=["https://your-domain.com"]
- )
- # Verify the token using Clerk's SDK
- payload = self.verify_token(token, options)
- # Check for the expected claims in the token payload
- if not payload.get("sub") or not payload.get("email"):
- raise R2RException(
- status_code=401,
- message="Invalid token: missing required claims",
- )
- # Create user in database if not exists
- try:
- await self.database_provider.users_handler.get_user_by_email(
- payload.get("email")
- )
- # TODO do we want to update user info here based on what's in the token?
- except Exception:
- # user doesn't exist, create in db
- logger.debug(f"Creating new user: {payload.get('email')}")
- try:
- # Construct name from first_name and last_name if available
- first_name = payload.get("first_name", "")
- last_name = payload.get("last_name", "")
- name = payload.get("name")
- # If name not directly provided, try to build it from first and last names
- if not name and (first_name or last_name):
- name = f"{first_name} {last_name}".strip()
- await self.database_provider.users_handler.create_user(
- email=payload.get("email"),
- account_type="external",
- name=name,
- )
- except Exception as e:
- logger.error(f"Error creating user: {e}")
- raise R2RException(
- status_code=500, message="Failed to create user"
- ) from e
- # Return the token data
- return TokenData(
- email=payload.get("email"),
- token_type="bearer",
- exp=datetime.fromtimestamp(payload.get("exp")),
- )
- except Exception as e:
- logger.info(f"Clerk token verification failed: {e}")
- raise R2RException(
- status_code=401, message="Invalid token", detail=str(e)
- ) from e
|