diff --git a/backend/constants.py b/backend/constants.py index e1086c9..bfddb72 100644 --- a/backend/constants.py +++ b/backend/constants.py @@ -1,9 +1,11 @@ import os - DATABASE_URL = "sqlite:///./data/database.db" JWT_ALGORITHM = "HS256" -ACCESS_TOKEN_EXPIRE_HOURS = 24 +# Token inactivity expiration - token expires if not used for this duration +TOKEN_INACTIVITY_EXPIRE_HOURS = 30 * 24 # 30 days of inactivity +# Maximum token lifetime (safety net) - tokens expire after this regardless of usage +MAX_TOKEN_LIFETIME_HOURS = 365 * 24 # 1 year maximum OWNER_USERNAME = "denis0001-dev" JWT_SECRET_KEY = os.getenv("JWT_SECRET") diff --git a/backend/dependencies.py b/backend/dependencies.py index c19adee..6ebb55b 100644 --- a/backend/dependencies.py +++ b/backend/dependencies.py @@ -1,4 +1,4 @@ -from datetime import datetime +from datetime import datetime, timedelta from fastapi import Depends, HTTPException, Request, status from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer from sqlalchemy.orm import Session @@ -66,7 +66,20 @@ def get_current_user( headers={"WWW-Authenticate": "Bearer"}, ) - # Touch last_seen on valid session + # Check if session has been inactive for too long (sliding expiration) + from constants import TOKEN_INACTIVITY_EXPIRE_HOURS + inactivity_threshold = datetime.now() - timedelta(hours=TOKEN_INACTIVITY_EXPIRE_HOURS) + if device_session.last_seen < inactivity_threshold: + # Session expired due to inactivity - revoke it + device_session.revoked = True + db.commit() + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Session expired due to inactivity", + headers={"WWW-Authenticate": "Bearer"}, + ) + + # Touch last_seen on valid session (sliding expiration - extends token life) device_session.last_seen = datetime.now() db.commit() diff --git a/backend/utils.py b/backend/utils.py index 660b475..ac2cd5a 100644 --- a/backend/utils.py +++ b/backend/utils.py @@ -4,16 +4,17 @@ import jwt from typing import Optional, Any import bcrypt -from constants import ACCESS_TOKEN_EXPIRE_HOURS, JWT_SECRET_KEY, JWT_ALGORITHM +from constants import MAX_TOKEN_LIFETIME_HOURS, JWT_SECRET_KEY, JWT_ALGORITHM # JWT Helper Functions def create_token(user_id: int, username: str, session_id: str) -> str: - expire = datetime.now() + timedelta(hours=ACCESS_TOKEN_EXPIRE_HOURS) + # Set a long expiration as safety net (actual expiration based on inactivity) + expire = datetime.now() + timedelta(hours=MAX_TOKEN_LIFETIME_HOURS) payload = { "user_id": user_id, "username": username, "session_id": session_id, - "exp": expire + "exp": int(expire.timestamp()) # JWT exp must be Unix timestamp (int) } return jwt.encode(payload, JWT_SECRET_KEY, algorithm=JWT_ALGORITHM)