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:
@@ -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
7
core/exp.py
Normal 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()
|
||||||
@@ -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:
|
||||||
return jwt.decode(token, cfg.public_key, "RS256")
|
try:
|
||||||
|
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)
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user