Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
223429d51c |
+2
-2
@@ -14,12 +14,12 @@ For integrating Paskia with your app frontend, see [integration](Integration.md)
|
||||
|
||||
### Public JSON API: /auth/api/*
|
||||
|
||||
| Method | Path | Used for | Responses |
|
||||
| Method | Path | Used for | Expected responses |
|
||||
|---:|---|---|---|
|
||||
| GET | /auth/api/settings | Paskia configuration: RP info, base paths, session cookie name | 200 |
|
||||
| GET | /auth/api/user-info | Full user profile: info, credentials, sessions, permissions | 200/401 |
|
||||
| POST | /auth/api/logout | Terminate session and delete session cookie on the current host | 200 |
|
||||
| POST | [/auth/api/validate](api/validate.md) | Validate and renew the session cookie; query [perm](api/perm.md), [max_age](api/max-age.md) | 200/401/403 |
|
||||
| POST | [/auth/api/validate](api/validate.md) | Validate and renew the session cookie; query [perm](api/perm.md), [max_age](api/max-age.md), [renew](api/validate.md#query-parameters) | 200/401/403 |
|
||||
| GET | [/auth/api/forward](api/forward.md) | Forward-auth with reverse proxies; see [proxy guides](proxy/index.md), query [perm](api/perm.md), [max_age](api/max-age.md) | 204/401/403 empty, json or html|
|
||||
|
||||
### User JSON API: /auth/api/user/*
|
||||
|
||||
+1
-1
@@ -122,7 +122,7 @@ This is useful for:
|
||||
- Background jobs that need to verify a stored session
|
||||
- Check extra permissions, get user context or renew session
|
||||
|
||||
Your backend can validate sessions directly by calling Paskia's validate endpoint [`/auth/api/validate`](api/validate.md). It generally expects client headers proxied as is, while on the URL you can specify exact requirements.
|
||||
Your backend can validate sessions directly by calling Paskia's validate endpoint [`/auth/api/validate`](api/validate.md). It generally expects client headers proxied as is, while on the URL you can specify exact requirements. To verify a session without extending its lifetime or updating its IP / user-agent, pass `renew=0`.
|
||||
|
||||
Usually it is sufficient to simply forward the headers the client sent, assuming your proxy already preserved `Host` and set `X-Forwarded-For` (otherwise set them here with original host and IP). `User-Agent` should also be forwarded if available, omitted if not: do not let your backend HTTP client add its own header.
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ See also the [API overview](../API.md) and the [integration guide](../Integratio
|
||||
|-----------|-------------|
|
||||
| perm | Required permissions. See the [perm argument](perm.md). |
|
||||
| max_age | Require recent passkey use. See the [max_age argument](max-age.md). |
|
||||
| renew | Pass `renew=0` to skip renewal: does no session updates, auth check only. |
|
||||
|
||||
## Request headers
|
||||
|
||||
@@ -19,7 +20,7 @@ See also the [API overview](../API.md) and the [integration guide](../Integratio
|
||||
|---|---|---|
|
||||
| Host | Forwarded directly from the client | Verifying the session's bound host |
|
||||
| Cookie | Forwarded directly or just cookie `__Host-paskia` | Session ID |
|
||||
| X-Forwarded-For | Real client IP | Recorded in session data instead of the backend/proxy IP; requires FORWARDED_ALLOW_IPS to trust the immediate peer |
|
||||
| X-Forwarded-For | Real client IP | Recorded in logs and session data instead of the backend/proxy IP; requires FORWARDED_ALLOW_IPS to trust the immediate peer |
|
||||
| User-Agent | Forward the original client UA if available; do not let your backend client add its own default | Recorded in session data only when the header is present; omitting it preserves the existing value |
|
||||
|
||||
See [integration documentation for backend validate requests](../Integration.md) for more detailed instructions, in particular for forwarding of client-provided headers.
|
||||
|
||||
@@ -103,9 +103,10 @@ async def validate_token(
|
||||
response: Response,
|
||||
perm: list[str] = Query([]),
|
||||
max_age: str | None = Query(None),
|
||||
renew: bool = Query(True),
|
||||
auth=AUTH_COOKIE,
|
||||
):
|
||||
"""Validate session and return context. Refreshes session expiry."""
|
||||
"""Validate session and return context. Refreshes session expiry by default."""
|
||||
perm_groups = _parse_perm(perm)
|
||||
try:
|
||||
ctx = await authz.verify(
|
||||
@@ -118,7 +119,7 @@ async def validate_token(
|
||||
# Global handler will clear cookie if 401
|
||||
raise
|
||||
renewed = False
|
||||
if auth:
|
||||
if auth and renew:
|
||||
consumed = datetime.now(UTC) - ctx.session.validated
|
||||
if not timedelta(0) < consumed < _REFRESH_INTERVAL:
|
||||
db.update_session(
|
||||
@@ -128,16 +129,18 @@ async def validate_token(
|
||||
validated=datetime.now(UTC),
|
||||
ctx=ctx,
|
||||
)
|
||||
session.set_session_cookie(response, auth)
|
||||
renewed = True
|
||||
_set_log_extra(request, ctx.session.key)
|
||||
return MsgspecResponse(
|
||||
resp = MsgspecResponse(
|
||||
ApiValidateResponse(
|
||||
valid=True,
|
||||
renewed=renewed,
|
||||
ctx=userinfo.build_session_context(ctx),
|
||||
)
|
||||
)
|
||||
if renewed:
|
||||
session.set_session_cookie(resp, auth)
|
||||
return resp
|
||||
|
||||
|
||||
@app.get("/check")
|
||||
|
||||
+53
-1
@@ -18,11 +18,13 @@ from uuid import UUID
|
||||
import httpx
|
||||
import pytest
|
||||
|
||||
from paskia import authcode
|
||||
from paskia import authcode, db
|
||||
from paskia.authsession import EXPIRES
|
||||
from paskia.db import delete_session
|
||||
from paskia.db.structs import Client
|
||||
from paskia.fastapi.api import _REFRESH_INTERVAL
|
||||
from paskia.util import avatar, hostutil, oidjwt, permutil
|
||||
from paskia.util.crypto import hash_secret
|
||||
from paskia.util.passphrase import generate
|
||||
from tests.conftest import auth_headers, create_test_image_bytes, create_test_session
|
||||
|
||||
@@ -924,3 +926,53 @@ class TestValidateWithMaxAge:
|
||||
# This exercises the max_age path - but isn't defined in validate
|
||||
# Actually validate doesn't have max_age - this tests that unknown params are ignored
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
class TestValidateRenewParameter:
|
||||
"""Tests for the renew query parameter on /auth/api/validate."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_validate_renew_false_skips_renewal(
|
||||
self, client: httpx.AsyncClient, session_token: str, test_db
|
||||
):
|
||||
"""renew=0 should skip session renewal and leave metadata untouched."""
|
||||
key = hash_secret("cookie", session_token)
|
||||
old_validated = datetime.now(UTC) - _REFRESH_INTERVAL - timedelta(minutes=1)
|
||||
db.update_session(key, validated=old_validated)
|
||||
original_ua = test_db.sessions[key].user_agent
|
||||
|
||||
response = await client.post(
|
||||
"/auth/api/validate?renew=0",
|
||||
headers={
|
||||
**auth_headers(session_token),
|
||||
"Host": "localhost:4401",
|
||||
"User-Agent": "different-ua",
|
||||
},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["valid"] is True
|
||||
assert data["renewed"] is False
|
||||
assert "set-cookie" not in response.headers
|
||||
assert test_db.sessions[key].validated == old_validated
|
||||
assert test_db.sessions[key].user_agent == original_ua
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_validate_renew_true_renews_old_session(
|
||||
self, client: httpx.AsyncClient, session_token: str, test_db
|
||||
):
|
||||
"""Explicit renew=1 should renew an aged session and return Set-Cookie."""
|
||||
key = hash_secret("cookie", session_token)
|
||||
old_validated = datetime.now(UTC) - _REFRESH_INTERVAL - timedelta(minutes=1)
|
||||
db.update_session(key, validated=old_validated)
|
||||
|
||||
response = await client.post(
|
||||
"/auth/api/validate?renew=1",
|
||||
headers={**auth_headers(session_token), "Host": "localhost:4401"},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["valid"] is True
|
||||
assert data["renewed"] is True
|
||||
assert "set-cookie" in response.headers
|
||||
assert test_db.sessions[key].validated > old_validated
|
||||
|
||||
Reference in New Issue
Block a user