From 087b24388ca1590d5188dc755e44bba87e0681ee Mon Sep 17 00:00:00 2001 From: Leo Vasanko Date: Tue, 9 Dec 2025 21:57:33 +0000 Subject: [PATCH] Fix regressions with the remote-auth preventing it from working. Minor usability and style improvements. Changed /auth/api/ws/pair name to permit, to go with other parts of the software. --- frontend/src/components/RemoteAuthPermit.vue | 25 +++++++++++++------- paskia/fastapi/remote.py | 6 ++--- paskia/fastapi/ws.py | 5 +++- paskia/globals.py | 5 ++++ 4 files changed, 28 insertions(+), 13 deletions(-) diff --git a/frontend/src/components/RemoteAuthPermit.vue b/frontend/src/components/RemoteAuthPermit.vue index 6758f48..c6b7e84 100644 --- a/frontend/src/components/RemoteAuthPermit.vue +++ b/frontend/src/components/RemoteAuthPermit.vue @@ -75,8 +75,6 @@ - -

{{ error }}

@@ -346,7 +344,7 @@ async function ensureConnection() { wsConnecting = true try { const authHost = settings.value?.auth_host - const wsPath = '/auth/ws/remote-auth/pair' + const wsPath = '/auth/ws/remote-auth/permit' const wsUrl = authHost && location.host !== authHost ? `//${authHost}${wsPath}` : wsPath ws = await aWebSocket(wsUrl) const msg = await ws.receive_json() @@ -365,7 +363,7 @@ async function ensureConnection() { // Defer cursor position update to after browser processes the key function deferUpdateCursor(event) { // Handle Tab/Space for autocomplete immediately - if (event.key === 'Tab' || event.key === ' ') { + if (event.key === 'Tab' || event.key === ' ' || event.key === 'Escape') { handleKeydown(event) return } @@ -527,7 +525,7 @@ async function lookupDeviceInfo() { const res = await ws.receive_json() updateChallenge(res.pow) if (typeof res.status === 'number' && res.status >= 400) { - error.value = res.detail || 'Request failed' + showMessage(res.detail || 'Request failed', 'error') serverError.value = true deviceInfo.value = null lastLookedUpCode = null @@ -544,14 +542,14 @@ async function lookupDeviceInfo() { lastLookedUpCode = currentCode nextTick(() => { submitBtnRef.value?.focus() }) } else { - error.value = 'Unexpected response from server' + showMessage('Unexpected response from server', 'error') serverError.value = true deviceInfo.value = null lastLookedUpCode = null } } catch (err) { console.error('Lookup error:', err) - error.value = err.message || 'Lookup failed' + showMessage(err.message || 'Lookup failed', 'error') serverError.value = true deviceInfo.value = null lastLookedUpCode = null @@ -563,6 +561,12 @@ async function lookupDeviceInfo() { } function handleKeydown(event) { + if (event.key === 'Escape') { + code.value = '' + handleInput() + event.preventDefault() + return + } if (event.key === 'Tab') { if (autocompleteHint.value) { const applied = applyAutocomplete() @@ -708,12 +712,15 @@ defineExpose({ reset, deny, code, handleInput, loading, error }) } .slot-machine.has-error { - border-color: var(--color-error, #ef4444); + /* Error background only shown when focused */ +} + +.input-wrapper.focused.has-error .slot-machine { background: var(--color-error-bg, rgba(239, 68, 68, 0.05)); } .slot-machine.is-complete { - border-color: var(--color-success, #10b981); + /* Success state - no special styling */ } .slot-reel { diff --git a/paskia/fastapi/remote.py b/paskia/fastapi/remote.py index e29d81e..f3f5285 100644 --- a/paskia/fastapi/remote.py +++ b/paskia/fastapi/remote.py @@ -38,7 +38,7 @@ async def websocket_remote_auth_request(ws: WebSocket): 1. Client connects 2. Server sends HARD PoW challenge, client solves and responds 3. Server creates a 3-word pairing code and sends it with expiry - 4. Server waits for another device to authenticate via /remote-auth/pair + 4. Server waits for another device to authenticate via /remote-auth/permit 5. When auth completes, server sends session_token to this client 6. Client can then use the session token to set a cookie 7. Connection times out after 5 minutes with explicit timeout message @@ -250,9 +250,9 @@ async def websocket_remote_auth_request(ws: WebSocket): remoteauth.instance.decrement_connections() -@app.websocket("/pair") +@app.websocket("/permit") @websocket_error_handler -async def websocket_remote_auth_pair(ws: WebSocket): +async def websocket_remote_auth_permit(ws: WebSocket): """Complete a remote authentication request using a 3-word pairing code. This endpoint is called from the user's profile on the authenticating device. diff --git a/paskia/fastapi/ws.py b/paskia/fastapi/ws.py index 0745cc6..fe7f24c 100644 --- a/paskia/fastapi/ws.py +++ b/paskia/fastapi/ws.py @@ -3,7 +3,7 @@ from uuid import UUID from fastapi import FastAPI, WebSocket from paskia.authsession import create_session, get_reset, get_session -from paskia.fastapi import authz +from paskia.fastapi import authz, remote from paskia.fastapi.session import AUTH_COOKIE, infodict from paskia.fastapi.wsutil import validate_origin, websocket_error_handler from paskia.globals import db, passkey @@ -13,6 +13,9 @@ from paskia.util.tokens import create_token, session_key # Create a FastAPI subapp for WebSocket endpoints app = FastAPI() +# Mount the remote auth WebSocket endpoints +app.mount("/remote-auth", remote.app) + async def register_chat( ws: WebSocket, diff --git a/paskia/globals.py b/paskia/globals.py index ba918ba..d843653 100644 --- a/paskia/globals.py +++ b/paskia/globals.py @@ -39,6 +39,8 @@ async def init( In FastAPI lifespan we call with bootstrap=False to avoid duplicate bootstrapping since the CLI performs it once before servers start. """ + from . import remoteauth + # Initialize passkey instance with provided parameters passkey.instance = Passkey( rp_id=rp_id, @@ -54,6 +56,9 @@ async def init( await sql.init() + # Initialize remote auth manager + await remoteauth.init() + if bootstrap: # Bootstrap system if needed from .bootstrap import bootstrap_if_needed