Restrict remote auth to the existing granting user's credentials.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user