feat(auth): implement JWT TTL and expiration

added access_token_ttl setting
fixed public and private key mismatch while decoding jwt
decode_jwt now returns None on expired tokens instead of raising exc
generate_pair converts sub to string and uses dynamic expiration
This commit is contained in:
2026-07-24 12:59:41 +07:00
parent eb35f2acad
commit 9187fbb970
4 changed files with 18 additions and 7 deletions

View File

@@ -16,6 +16,8 @@ class Settings(BaseSettings):
private_key_fp: str = Field() private_key_fp: str = Field()
public_key_fp: str = Field() public_key_fp: str = Field()
access_token_ttl: int = Field(description="Access token TTL (minutes)")
@computed_field @computed_field
@property @property
def db_url(self) -> str: def db_url(self) -> str:

7
core/exp.py Normal file
View File

@@ -0,0 +1,7 @@
from datetime import UTC, datetime, timedelta
from config import cfg
def get_exp(minutes: int = cfg.access_token_ttl) -> float:
return (datetime.now(UTC) + timedelta(minutes=minutes)).timestamp()

View File

@@ -35,12 +35,15 @@ def generate_jwt(payload: dict[str, Any]) -> str:
return jwt.encode(payload, cfg.private_key, "RS256") return jwt.encode(payload, cfg.private_key, "RS256")
def decode_jwt(token: str) -> dict[str, Any]: def decode_jwt(token: str) -> dict[str, Any] | None:
try:
return jwt.decode(token, cfg.public_key, "RS256") return jwt.decode(token, cfg.public_key, "RS256")
except jwt.ExpiredSignatureError:
return
def generate_pair(user_id: int, iss: ProvidersType) -> KeyPair: def generate_pair(user_id: int, iss: ProvidersType) -> KeyPair:
payload = JWTPayload(sub=user_id, iss=iss) payload = JWTPayload(sub=str(user_id), iss=iss)
access_token = generate_jwt(payload.model_dump()) access_token = generate_jwt(payload.model_dump())
refresh_token = secrets.token_urlsafe(32) refresh_token = secrets.token_urlsafe(32)

View File

@@ -1,11 +1,10 @@
from datetime import UTC, datetime
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from core.exp import get_exp
from schemas.providers import ProvidersType from schemas.providers import ProvidersType
class JWTPayload(BaseModel): class JWTPayload(BaseModel):
sub: int = Field(description="User ID") sub: str = Field(description="User ID")
iss: ProvidersType = Field(description="Issuer") iss: ProvidersType = Field(description="Issuer")
exp: float = Field(default_factory=lambda: datetime.now(UTC).timestamp()) exp: float = Field(default_factory=get_exp)