Make the /auth/api/validate endpoint renew sessions if needed.
This commit is contained in:
		| @@ -1,5 +1,6 @@ | |||||||
| import logging | import logging | ||||||
| from contextlib import suppress | from contextlib import suppress | ||||||
|  | from datetime import datetime, timedelta | ||||||
| from uuid import UUID | from uuid import UUID | ||||||
|  |  | ||||||
| from fastapi import ( | from fastapi import ( | ||||||
| @@ -18,7 +19,14 @@ from fastapi.security import HTTPBearer | |||||||
| from passkey.util import frontend | from passkey.util import frontend | ||||||
|  |  | ||||||
| from .. import aaguid | from .. import aaguid | ||||||
| from ..authsession import delete_credential, expires, get_reset, get_session | from ..authsession import ( | ||||||
|  |     EXPIRES, | ||||||
|  |     delete_credential, | ||||||
|  |     expires, | ||||||
|  |     get_reset, | ||||||
|  |     get_session, | ||||||
|  |     refresh_session_token, | ||||||
|  | ) | ||||||
| from ..globals import db | from ..globals import db | ||||||
| from ..globals import passkey as global_passkey | from ..globals import passkey as global_passkey | ||||||
| from ..util import passphrase, permutil, tokens | from ..util import passphrase, permutil, tokens | ||||||
| @@ -29,6 +37,11 @@ bearer_auth = HTTPBearer(auto_error=True) | |||||||
|  |  | ||||||
| app = FastAPI() | app = FastAPI() | ||||||
|  |  | ||||||
|  | # Refresh only if at least this much of the session lifetime has been *consumed*. | ||||||
|  | # Consumption is derived from (now + EXPIRES) - current_expires. | ||||||
|  | # This guarantees a minimum spacing between DB writes even with frequent /validate calls. | ||||||
|  | _REFRESH_INTERVAL = timedelta(minutes=5) | ||||||
|  |  | ||||||
|  |  | ||||||
| @app.exception_handler(ValueError) | @app.exception_handler(ValueError) | ||||||
| async def value_error_handler(_request: Request, exc: ValueError): | async def value_error_handler(_request: Request, exc: ValueError): | ||||||
| @@ -42,9 +55,32 @@ async def general_exception_handler(_request: Request, exc: Exception): | |||||||
|  |  | ||||||
|  |  | ||||||
| @app.post("/validate") | @app.post("/validate") | ||||||
| async def validate_token(perm: list[str] = Query([]), auth=Cookie(None)): | async def validate_token( | ||||||
|  |     response: Response, perm: list[str] = Query([]), auth=Cookie(None) | ||||||
|  | ): | ||||||
|  |     """Validate the current session and extend its expiry. | ||||||
|  |  | ||||||
|  |     Always refreshes the session (sliding expiration) and re-sets the cookie with a | ||||||
|  |     renewed max-age. This keeps active users logged in without needing a separate | ||||||
|  |     refresh endpoint. | ||||||
|  |     """ | ||||||
|     ctx = await authz.verify(auth, perm) |     ctx = await authz.verify(auth, perm) | ||||||
|     return {"valid": True, "user_uuid": str(ctx.session.user_uuid)} |     renewed = False | ||||||
|  |     if auth: | ||||||
|  |         consumed = EXPIRES - (ctx.session.expires - datetime.now()) | ||||||
|  |         if not timedelta(0) < consumed < _REFRESH_INTERVAL: | ||||||
|  |             try: | ||||||
|  |                 await refresh_session_token(auth) | ||||||
|  |                 session.set_session_cookie(response, auth) | ||||||
|  |                 renewed = True | ||||||
|  |             except ValueError: | ||||||
|  |                 # Session disappeared, e.g. due to concurrent logout | ||||||
|  |                 raise HTTPException(status_code=401, detail="Session expired") | ||||||
|  |     return { | ||||||
|  |         "valid": True, | ||||||
|  |         "user_uuid": str(ctx.session.user_uuid), | ||||||
|  |         "renewed": renewed, | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
| @app.get("/forward") | @app.get("/forward") | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Leo Vasanko
					Leo Vasanko