Extending session login time

This commit is contained in:
Ian Roddis committed 2026-08-10 12:09:51 -03:00
1 parent 14aa2ac5d6
commit a9c4c16d4f
6 files changed
+35 -10

No files matched your search

+3
View File
@@ -25,6 +25,9 @@ AUTO_CREATE_TABLES=true
COOKIE_NAME=access_token
# Set true when serving over HTTPS.
COOKIE_SECURE=false
# How long a browser login lasts before being bounced back to /login.
# Independent of ACCESS_TOKEN_EXPIRE_MINUTES, which governs JSON API tokens.
SESSION_TIMEOUT_HOURS=24
# --- LDAP (optional; direct-bind) ---
# When enabled, the login page and /api/auth/ldap-login accept a directory
+2 -1
View File
@@ -149,8 +149,9 @@ Every setting is read from an environment variable first, then `.env`
|----------|---------|-------|
| `SECRET_KEY` | dev placeholder | **set a strong value in production** |
| `DATABASE_URL` | `sqlite+aiosqlite:///./app.db` | use `postgresql+asyncpg://...` for Postgres |
| `ACCESS_TOKEN_EXPIRE_MINUTES` | `15` | |
| `ACCESS_TOKEN_EXPIRE_MINUTES` | `15` | JSON API bearer token lifetime |
| `REFRESH_TOKEN_EXPIRE_DAYS` | `7` | |
| `SESSION_TIMEOUT_HOURS` | `24` | browser login (HTMX UI cookie) lifetime |
| `AUTO_CREATE_TABLES` | `true` | create tables on startup (dev); use Alembic in prod |
| `COOKIE_SECURE` | `false` | set `true` behind HTTPS |
| `FIRST_SUPERUSER_EMAIL` / `_PASSWORD` | admin@example.com / changethis | used by seed |
+5 -1
View File
@@ -33,9 +33,13 @@ class Settings(BaseSettings):
database_url: str = "sqlite+aiosqlite:///./app.db"
auto_create_tables: bool = True
# Web / cookies (the HTMX UI stores the access token in an httpOnly cookie)
# Web / cookies (the HTMX UI stores the access token in an httpOnly cookie).
# session_timeout_hours governs how long a browser login lasts before the
# user is bounced back to /login; it's independent of the JSON API's
# short-lived access_token_expire_minutes.
cookie_name: str = "access_token"
cookie_secure: bool = False
session_timeout_hours: int = 24
# LDAP (direct-bind). When enabled, the login page offers an LDAP option.
# At login the user's DN is built from ``ldap_bind_dn_template`` (with
+4 -2
View File
@@ -47,10 +47,12 @@ def _create_token(
return token, payload
def create_access_token(subject: str | int) -> str:
def create_access_token(
subject: str | int, expires_delta: timedelta | None = None
) -> str:
token, _ = _create_token(
subject,
timedelta(minutes=settings.access_token_expire_minutes),
expires_delta or timedelta(minutes=settings.access_token_expire_minutes),
ACCESS_TOKEN_TYPE,
)
return token
+12 -3
View File
@@ -1,5 +1,7 @@
"""Token issuance / rotation shared by the JSON API and the HTMX UI."""
from datetime import timedelta
import jwt
from sqlalchemy.ext.asyncio import AsyncSession
@@ -13,9 +15,16 @@ from app.crud import token as token_crud
from app.models.user import User
async def issue_token_pair(db: AsyncSession, user: User) -> tuple[str, str]:
"""Create an access + refresh token and persist the refresh token for revocation."""
access = create_access_token(user.id)
async def issue_token_pair(
db: AsyncSession, user: User, access_expires_delta: timedelta | None = None
) -> tuple[str, str]:
"""Create an access + refresh token and persist the refresh token for revocation.
``access_expires_delta`` overrides the access token's default lifetime
(``settings.access_token_expire_minutes``) — used by the web login flow to
issue a longer-lived session cookie without affecting API bearer tokens.
"""
access = create_access_token(user.id, access_expires_delta)
refresh, payload = create_refresh_token(user.id)
await token_crud.store(
db,
+9 -3
View File
@@ -1,3 +1,5 @@
from datetime import timedelta
from fastapi import APIRouter, Depends, Form, HTTPException, Query, Request, status
from fastapi.responses import HTMLResponse, RedirectResponse, Response
from sqlalchemy.ext.asyncio import AsyncSession
@@ -36,7 +38,7 @@ def _set_auth_cookie(response: Response, access_token: str) -> None:
httponly=True,
secure=settings.cookie_secure,
samesite="lax",
max_age=settings.access_token_expire_minutes * 60,
max_age=settings.session_timeout_hours * 3600,
path="/",
)
@@ -79,7 +81,9 @@ async def login_submit(
},
status_code=status.HTTP_401_UNAUTHORIZED,
)
access, _ = await issue_token_pair(db, user)
access, _ = await issue_token_pair(
db, user, timedelta(hours=settings.session_timeout_hours)
)
response = RedirectResponse(
url=_safe_next(next_url), status_code=status.HTTP_303_SEE_OTHER
)
@@ -110,7 +114,9 @@ async def login_ldap_submit(
},
status_code=status.HTTP_401_UNAUTHORIZED,
)
access, _ = await issue_token_pair(db, user)
access, _ = await issue_token_pair(
db, user, timedelta(hours=settings.session_timeout_hours)
)
response = RedirectResponse(
url=_safe_next(next_url), status_code=status.HTTP_303_SEE_OTHER
)