♻️(backend) encapsulate token generation in a service
Encapsulate token generation logic for authenticating to the external API in a well-scoped service. This service can later be reused in other parts of the codebase, especially for providing tokens required by calendar integrations. Commit was cherry picked from #897
This commit is contained in:
committed by
aleb_the_flash
parent
259b739160
commit
7cab46dc29
106
src/backend/core/services/jwt_token.py
Normal file
106
src/backend/core/services/jwt_token.py
Normal file
@@ -0,0 +1,106 @@
|
||||
"""JWT token service."""
|
||||
|
||||
# pylint: disable=R0913,R0917
|
||||
# ruff: noqa: PLR0913
|
||||
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from typing import Optional
|
||||
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
|
||||
import jwt
|
||||
|
||||
|
||||
class JwtTokenService:
|
||||
"""Generic JWT token service with configurable settings."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
secret_key: str,
|
||||
algorithm: str,
|
||||
issuer: str,
|
||||
audience: str,
|
||||
expiration_seconds: int,
|
||||
token_type: str,
|
||||
):
|
||||
"""
|
||||
Initialize the token service with custom settings.
|
||||
|
||||
Args:
|
||||
secret_key: Secret key for JWT encoding/decoding
|
||||
algorithm: JWT algorithm
|
||||
issuer: Token issuer identifier
|
||||
audience: Token audience identifier
|
||||
expiration_seconds: Token expiration time in seconds
|
||||
token_type: Token type
|
||||
|
||||
Raises:
|
||||
ImproperlyConfigured: If secret_key is None or empty
|
||||
"""
|
||||
if not secret_key:
|
||||
raise ImproperlyConfigured("Secret key is required.")
|
||||
if not algorithm:
|
||||
raise ImproperlyConfigured("Algorithm is required.")
|
||||
if not token_type:
|
||||
raise ImproperlyConfigured("Token's type is required.")
|
||||
if expiration_seconds is None:
|
||||
raise ImproperlyConfigured("Expiration's seconds is required.")
|
||||
|
||||
self._key = secret_key
|
||||
self._algorithm = algorithm
|
||||
self._issuer = issuer
|
||||
self._audience = audience
|
||||
self._expiration_seconds = expiration_seconds
|
||||
self._token_type = token_type
|
||||
|
||||
def generate_jwt(
|
||||
self, user, scope: str, extra_payload: Optional[dict] = None
|
||||
) -> dict:
|
||||
"""
|
||||
Generate an access token for the given user.
|
||||
|
||||
Note: any extra_payload variables named iat, exp, or user_id will
|
||||
be overwritten by this service
|
||||
|
||||
Args:
|
||||
user: User instance for whom to generate the token
|
||||
scope: Space-separated scope string
|
||||
|
||||
Returns:
|
||||
Dictionary containing access_token, token_type, expires_in, and scope optionally
|
||||
"""
|
||||
now = datetime.now(timezone.utc)
|
||||
|
||||
payload = extra_payload.copy() if extra_payload else {}
|
||||
|
||||
payload.update(
|
||||
{
|
||||
"iat": now,
|
||||
"exp": now + timedelta(seconds=self._expiration_seconds),
|
||||
"user_id": str(user.id),
|
||||
}
|
||||
)
|
||||
|
||||
if self._issuer:
|
||||
payload["iss"] = self._issuer
|
||||
if self._audience:
|
||||
payload["aud"] = self._audience
|
||||
if scope:
|
||||
payload["scope"] = scope
|
||||
|
||||
token = jwt.encode(
|
||||
payload,
|
||||
self._key,
|
||||
algorithm=self._algorithm,
|
||||
)
|
||||
|
||||
response = {
|
||||
"access_token": token,
|
||||
"token_type": self._token_type,
|
||||
"expires_in": self._expiration_seconds,
|
||||
}
|
||||
|
||||
if scope:
|
||||
response["scope"] = scope
|
||||
|
||||
return response
|
||||
Reference in New Issue
Block a user