clerk.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. import logging
  2. import os
  3. from datetime import datetime
  4. from core.base import (
  5. AuthConfig,
  6. CryptoProvider,
  7. EmailProvider,
  8. R2RException,
  9. TokenData,
  10. )
  11. from ..database import PostgresDatabaseProvider
  12. from .jwt import JwtAuthProvider
  13. logger = logging.getLogger(__name__)
  14. class ClerkAuthProvider(JwtAuthProvider):
  15. """
  16. ClerkAuthProvider extends JwtAuthProvider to support token verification with Clerk.
  17. It uses Clerk's SDK to verify the JWT token and extract user information.
  18. """
  19. def __init__(
  20. self,
  21. config: AuthConfig,
  22. crypto_provider: CryptoProvider,
  23. database_provider: PostgresDatabaseProvider,
  24. email_provider: EmailProvider,
  25. ):
  26. super().__init__(
  27. config=config,
  28. crypto_provider=crypto_provider,
  29. database_provider=database_provider,
  30. email_provider=email_provider,
  31. )
  32. try:
  33. from clerk_backend_api.jwks_helpers.verifytoken import (
  34. VerifyTokenOptions,
  35. verify_token,
  36. )
  37. self.verify_token = verify_token
  38. self.VerifyTokenOptions = VerifyTokenOptions
  39. except ImportError as e:
  40. raise R2RException(
  41. status_code=500,
  42. message="Clerk SDK is not installed. Run `pip install clerk-backend-api`",
  43. ) from e
  44. async def decode_token(self, token: str) -> TokenData:
  45. """
  46. Decode and verify the JWT token using Clerk's verify_token function.
  47. Args:
  48. token: The JWT token to decode
  49. Returns:
  50. TokenData: The decoded token data with user information
  51. Raises:
  52. R2RException: If the token is invalid or verification fails
  53. """
  54. clerk_secret_key = os.getenv("CLERK_SECRET_KEY")
  55. if not clerk_secret_key:
  56. raise R2RException(
  57. status_code=500,
  58. message="CLERK_SECRET_KEY environment variable is not set",
  59. )
  60. try:
  61. # Configure verification options
  62. options = self.VerifyTokenOptions(
  63. secret_key=clerk_secret_key,
  64. # Optional: specify audience if needed
  65. # audience="your-audience",
  66. # Optional: specify authorized parties if needed
  67. # authorized_parties=["https://your-domain.com"]
  68. )
  69. # Verify the token using Clerk's SDK
  70. payload = self.verify_token(token, options)
  71. # Check for the expected claims in the token payload
  72. if not payload.get("sub") or not payload.get("email"):
  73. raise R2RException(
  74. status_code=401,
  75. message="Invalid token: missing required claims",
  76. )
  77. # Create user in database if not exists
  78. try:
  79. await self.database_provider.users_handler.get_user_by_email(
  80. payload.get("email")
  81. )
  82. # TODO do we want to update user info here based on what's in the token?
  83. except Exception:
  84. # user doesn't exist, create in db
  85. logger.debug(f"Creating new user: {payload.get('email')}")
  86. try:
  87. # Construct name from first_name and last_name if available
  88. first_name = payload.get("first_name", "")
  89. last_name = payload.get("last_name", "")
  90. name = payload.get("name")
  91. # If name not directly provided, try to build it from first and last names
  92. if not name and (first_name or last_name):
  93. name = f"{first_name} {last_name}".strip()
  94. await self.database_provider.users_handler.create_user(
  95. email=payload.get("email"),
  96. account_type="external",
  97. name=name,
  98. )
  99. except Exception as e:
  100. logger.error(f"Error creating user: {e}")
  101. raise R2RException(
  102. status_code=500, message="Failed to create user"
  103. ) from e
  104. # Return the token data
  105. return TokenData(
  106. email=payload.get("email"),
  107. token_type="bearer",
  108. exp=datetime.fromtimestamp(payload.get("exp")),
  109. )
  110. except Exception as e:
  111. logger.info(f"Clerk token verification failed: {e}")
  112. raise R2RException(
  113. status_code=401, message="Invalid token", detail=str(e)
  114. ) from e