Simplify session and reset token formats; removes the token utility functions entirely.

This commit is contained in:
2026-01-23 20:53:03 +00:00
parent ae4c982a30
commit 2ec6314264
15 changed files with 112 additions and 130 deletions
+15 -18
View File
@@ -15,10 +15,8 @@ from paskia.util import (
passphrase,
permutil,
querysafe,
tokens,
useragent,
)
from paskia.util.tokens import encode_session_key, session_key
app = FastAPI()
@@ -217,7 +215,9 @@ async def admin_add_org_permission(
ctx = await authz.verify(
auth, ["auth:admin"], host=request.headers.get("host"), match=permutil.has_all
)
db.add_permission_to_organization(str(org_uuid), permission_id, actor=str(ctx.user.uuid))
db.add_permission_to_organization(
str(org_uuid), permission_id, actor=str(ctx.user.uuid)
)
return {"status": "ok"}
@@ -241,7 +241,9 @@ async def admin_remove_org_permission(
"This would lock you out of admin access."
)
db.remove_permission_from_organization(str(org_uuid), permission_id, actor=str(ctx.user.uuid))
db.remove_permission_from_organization(
str(org_uuid), permission_id, actor=str(ctx.user.uuid)
)
return {"status": "ok"}
@@ -543,7 +545,7 @@ async def admin_create_user_registration_link(
expiry = reset_expires()
db.create_reset_token(
user_uuid=user_uuid,
key=tokens.reset_key(token),
passphrase=token,
expiry=expiry,
token_type=token_type,
actor=str(ctx.user.uuid),
@@ -640,13 +642,13 @@ async def admin_get_user_detail(
# Get sessions for the user
normalized_request_host = hostutil.normalize_host(request.headers.get("host"))
session_records = db.list_sessions_for_user(user_uuid)
current_session_key = session_key(auth)
current_session_key = auth
sessions_payload: list[dict] = []
for entry in session_records:
renewed = entry.expiry - EXPIRES
sessions_payload.append(
{
"id": encode_session_key(entry.key),
"id": entry.key,
"credential_uuid": str(entry.credential_uuid),
"host": entry.host,
"ip": entry.ip,
@@ -787,21 +789,14 @@ async def admin_delete_user_session(
status_code=403, detail="Insufficient permissions", mode="forbidden"
)
try:
target_key = tokens.decode_session_key(session_id)
except ValueError as exc:
raise HTTPException(
status_code=400, detail="Invalid session identifier"
) from exc
target_session = db.get_session(target_key)
target_session = db.get_session(session_id)
if not target_session or target_session.user_uuid != user_uuid:
raise HTTPException(status_code=404, detail="Session not found")
db.delete_session(target_key, actor=str(ctx.user.uuid))
db.delete_session(session_id, actor=str(ctx.user.uuid))
# Check if admin terminated their own session
current_terminated = target_key == session_key(auth)
current_terminated = session_id == auth
return {"status": "ok", "current_session_terminated": current_terminated}
@@ -1047,7 +1042,9 @@ async def admin_rename_permission(
_check_admin_lockout(str(perm.uuid), domain_value, request.headers.get("host"))
# All current backends support rename_permission
db.rename_permission(old_scope, new_scope, display_name, domain_value, actor=str(ctx.user.uuid))
db.rename_permission(
old_scope, new_scope, display_name, domain_value, actor=str(ctx.user.uuid)
)
return {"status": "ok"}
+1 -2
View File
@@ -24,7 +24,6 @@ from paskia.fastapi import authz, session, user
from paskia.fastapi.session import AUTH_COOKIE, AUTH_COOKIE_NAME
from paskia.globals import passkey as global_passkey
from paskia.util import frontend, hostutil, htmlutil, passphrase, userinfo
from paskia.util.tokens import session_key
bearer_auth = HTTPBearer(auto_error=True)
@@ -293,7 +292,7 @@ async def api_logout(request: Request, response: Response, auth=AUTH_COOKIE):
except ValueError:
return {"message": "Already logged out"}
with suppress(Exception):
db.delete_session(session_key(auth), actor=str(s.user_uuid))
db.delete_session(auth, actor=str(s.user_uuid))
session.clear_session_cookie(response)
return {"message": "Logged out successfully"}
+5 -9
View File
@@ -342,25 +342,23 @@ async def websocket_remote_auth_permit(ws: WebSocket):
if request.action == "register":
# For registration, create a reset token for device addition
from paskia.authsession import expires
from paskia.util import hostutil, tokens
from paskia.util import hostutil
token_str = passphrase.generate()
expiry = expires()
db.create_reset_token(
user_uuid=stored_cred.user_uuid,
key=tokens.reset_key(token_str),
passphrase=token_str,
expiry=expiry,
token_type="device addition",
actor=str(stored_cred.user_uuid),
)
reset_token = token_str
# Also create a session so the device is logged in
session_token = passphrase.generate()
normalized_host = hostutil.normalize_host(request.host)
db.login(
session_token = db.login(
user_uuid=stored_cred.user_uuid,
credential=stored_cred,
session_key=tokens.session_key(session_token),
host=normalized_host,
ip=request.ip,
user_agent=request.user_agent,
@@ -369,14 +367,12 @@ async def websocket_remote_auth_permit(ws: WebSocket):
else:
# Default login action
from paskia.authsession import expires
from paskia.util import hostutil, tokens
from paskia.util import hostutil
session_token = passphrase.generate()
normalized_host = hostutil.normalize_host(request.host)
db.login(
session_token = db.login(
user_uuid=stored_cred.user_uuid,
credential=stored_cred,
session_key=tokens.session_key(session_token),
host=normalized_host,
ip=request.ip,
user_agent=request.user_agent,
+1 -2
View File
@@ -18,7 +18,6 @@ from uuid import UUID
from paskia import authsession as _authsession
from paskia import db as _db
from paskia.util import hostutil, passphrase
from paskia.util import tokens as _tokens
async def _resolve_targets(query: str | None):
@@ -65,7 +64,7 @@ async def _create_reset(user, role_name: str):
token = passphrase.generate()
expiry = _authsession.reset_expires()
_db.create_reset_token(
key=_tokens.reset_key(token),
passphrase=token,
user_uuid=user.uuid,
expiry=expiry,
token_type="manual reset",
+5 -13
View File
@@ -18,8 +18,7 @@ from paskia.authsession import (
)
from paskia.fastapi import authz, session
from paskia.fastapi.session import AUTH_COOKIE
from paskia.util import hostutil, passphrase, tokens
from paskia.util.tokens import decode_session_key, session_key
from paskia.util import hostutil, passphrase
app = FastAPI()
@@ -92,19 +91,12 @@ async def api_delete_session(
status_code=401, detail="Session expired", mode="login"
) from exc
try:
target_key = decode_session_key(session_id)
except ValueError as exc:
raise HTTPException(
status_code=400, detail="Invalid session identifier"
) from exc
target_session = db.get_session(target_key)
target_session = db.get_session(session_id)
if not target_session or target_session.user_uuid != current_session.user_uuid:
raise HTTPException(status_code=404, detail="Session not found")
db.delete_session(target_key, actor=str(current_session.user_uuid))
current_terminated = target_key == session_key(auth)
db.delete_session(session_id, actor=str(current_session.user_uuid))
current_terminated = session_id == auth
if current_terminated:
session.clear_session_cookie(response) # explicit because 200
return {"status": "ok", "current_session_terminated": current_terminated}
@@ -146,7 +138,7 @@ async def api_create_link(
expiry = expires()
db.create_reset_token(
user_uuid=s.user_uuid,
key=tokens.reset_key(token),
passphrase=token,
expiry=expiry,
token_type="device addition",
actor=str(s.user_uuid),
+2 -7
View File
@@ -9,7 +9,6 @@ from paskia.fastapi.session import AUTH_COOKIE, infodict
from paskia.fastapi.wsutil import validate_origin, websocket_error_handler
from paskia.globals import passkey
from paskia.util import hostutil, passphrase
from paskia.util.tokens import create_token, session_key
# Create a FastAPI subapp for WebSocket endpoints
app = FastAPI()
@@ -78,13 +77,11 @@ async def websocket_register_add(
credential = await register_chat(ws, user_uuid, user_name, origin, challenge_ids)
# Create a new session and store everything in database
token = create_token()
metadata = infodict(ws, "authenticated")
db.create_credential_session( # type: ignore[attr-defined]
token = db.create_credential_session( # type: ignore[attr-defined]
user_uuid=user_uuid,
credential=credential,
reset_key=(s.key if reset is not None else None),
session_key=session_key(token),
display_name=user_name,
host=host,
ip=metadata.get("ip"),
@@ -145,7 +142,6 @@ async def websocket_authenticate(ws: WebSocket, auth=AUTH_COOKIE):
# Create session and update user/credential in a single transaction
assert stored_cred.uuid is not None
metadata = infodict(ws, "auth")
token = create_token()
normalized_host = hostutil.normalize_host(host)
if not normalized_host:
raise ValueError("Host required for session creation")
@@ -154,10 +150,9 @@ async def websocket_authenticate(ws: WebSocket, auth=AUTH_COOKIE):
if not (hostname == rp_id or hostname.endswith(f".{rp_id}")):
raise ValueError(f"Host must be the same as or a subdomain of {rp_id}")
db.login(
token = db.login(
user_uuid=stored_cred.user_uuid,
credential=stored_cred,
session_key=session_key(token),
host=normalized_host,
ip=metadata.get("ip") or "",
user_agent=metadata.get("user_agent") or "",