"""
Cryptographic utilities for Zintlr MCP Server.
Replicates the token decryption logic from Next.js proxy (_auth.handler.ts):
- verify_and_decrypt_jwt: Decrypts JWT tokens using the CIPHER secret
"""
import jwt
from typing import Any
from app.config import settings
def verify_and_decrypt_jwt(token: str, secret: str | None = None) -> Any | None:
"""
Verify and decrypt a JWT token.
This replicates the Next.js function:
```typescript
export function verify_and_decrypt_jwt(token: string, secret: string) {
try {
return jwt.verify(token, secret);
} catch {
return null;
}
}
```
Args:
token: The JWT token to decrypt
secret: The secret key (defaults to CIPHER_SECRET from settings)
Returns:
The decoded payload if valid, None otherwise
"""
if not token:
return None
secret = secret or settings.cipher_secret
if not secret:
raise ValueError("CIPHER_SECRET is not configured")
try:
# PyJWT's decode verifies and decrypts in one step
# The Next.js jwt.verify does the same thing
decoded = jwt.decode(token, secret, algorithms=["HS256"])
return decoded
except jwt.ExpiredSignatureError:
return None
except jwt.InvalidTokenError:
return None
except Exception:
return None
def decrypt_user_tokens(encrypted_key: str, encrypted_access_token: str) -> tuple[Any, Any]:
"""
Decrypt both key and access_token from user's cookies.
Args:
encrypted_key: The encrypted 'key' cookie value
encrypted_access_token: The encrypted 'access_token' cookie value
Returns:
Tuple of (decrypted_key, decrypted_access_token)
Raises:
ValueError: If decryption fails for either token
"""
decrypted_key = verify_and_decrypt_jwt(encrypted_key)
decrypted_access_token = verify_and_decrypt_jwt(encrypted_access_token)
if decrypted_key is None:
raise ValueError("Failed to decrypt key token")
if decrypted_access_token is None:
raise ValueError("Failed to decrypt access_token")
return decrypted_key, decrypted_access_token