Restrict remote auth to the existing granting user's credentials.

This commit is contained in:
Leo Vasanko
2026-01-29 20:24:26 +00:00
parent 09c241aa2c
commit ac85379779
3 changed files with 14 additions and 7 deletions
+3 -3
View File
@@ -17,7 +17,7 @@ from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from paskia import db, remoteauth
from paskia.authsession import expires
from paskia.fastapi.session import infodict
from paskia.fastapi.session import AUTH_COOKIE, infodict
from paskia.fastapi.wschat import authenticate_and_login
from paskia.fastapi.wsutil import validate_origin, websocket_error_handler
from paskia.util import passphrase, pow, useragent
@@ -252,7 +252,7 @@ async def websocket_remote_auth_request(ws: WebSocket):
@app.websocket("/permit")
@websocket_error_handler
async def websocket_remote_auth_permit(ws: WebSocket):
async def websocket_remote_auth_permit(ws: WebSocket, auth=AUTH_COOKIE):
"""Complete a remote authentication request using a 3-word pairing code.
This endpoint is called from the user's profile on the authenticating device.
@@ -310,7 +310,7 @@ async def websocket_remote_auth_permit(ws: WebSocket):
# Handle authenticate request (no PoW needed - already validated during lookup)
if msg.get("authenticate") and request is not None:
ctx = await authenticate_and_login(ws)
ctx = await authenticate_and_login(ws, auth)
session_token = ctx.session.key
reset_token = None
+1 -3
View File
@@ -89,14 +89,12 @@ async def websocket_authenticate(ws: WebSocket, auth=AUTH_COOKIE):
# If there's an existing session, restrict to that user's credentials (reauth)
session_user_uuid = None
credential_ids = None
if auth:
existing_ctx = db.data().session_ctx(auth, host)
if existing_ctx:
session_user_uuid = existing_ctx.user.uuid
credential_ids = db.get_user_credential_ids(session_user_uuid) or None
ctx = await authenticate_and_login(ws, credential_ids)
ctx = await authenticate_and_login(ws, auth)
# If reauth mode, verify the credential belongs to the session's user
if session_user_uuid and ctx.user.uuid != session_user_uuid:
+10 -1
View File
@@ -68,10 +68,12 @@ async def authenticate_chat(
async def authenticate_and_login(
ws: WebSocket,
credential_ids: list[bytes] | None = None,
auth: str | None = None,
) -> SessionContext:
"""Run WebAuthn authentication flow, create session, and return the session context.
If auth is provided, restrict authentication to credentials of that session's user.
Returns:
SessionContext for the authenticated session
"""
@@ -86,6 +88,13 @@ async def authenticate_and_login(
raise ValueError(f"Host must be the same as or a subdomain of {rp_id}")
metadata = infodict(ws, "auth")
# Get credential IDs if restricting to a user's credentials
credential_ids = None
if auth:
existing_ctx = db.data().session_ctx(auth, host)
if existing_ctx:
credential_ids = db.get_user_credential_ids(existing_ctx.user.uuid) or None
cred, new_sign_count = await authenticate_chat(ws, credential_ids)
# Create session and update user/credential