From 9485cbd20105881dacb02fbf9a727f9906f60725 Mon Sep 17 00:00:00 2001 From: Leo Vasanko Date: Sun, 7 Dec 2025 00:37:40 +0000 Subject: [PATCH] Drafting autocomplete for the words. Don't check for malformed words --- frontend/src/components/PairingCodeEntry.vue | 178 ++++++++++++++++--- paskia/fastapi/api.py | 14 +- paskia/fastapi/mainapp.py | 8 +- paskia/remoteauth.py | 2 +- 4 files changed, 163 insertions(+), 39 deletions(-) diff --git a/frontend/src/components/PairingCodeEntry.vue b/frontend/src/components/PairingCodeEntry.vue index 0ffb3b4..24ba9ca 100644 --- a/frontend/src/components/PairingCodeEntry.vue +++ b/frontend/src/components/PairingCodeEntry.vue @@ -6,7 +6,7 @@
-
+
+ + {{ autocompleteHintDisplay }}
- +
@@ -49,7 +53,7 @@ {{ loading ? 'Authenticating…' : 'Authenticate to Log In Device' }}
- +

{{ error }}

@@ -61,11 +65,12 @@ @@ -243,15 +353,16 @@ defineExpose({ reset }) gap: 0.5rem; } -.input-group { +.input-wrapper { + position: relative; display: flex; - gap: 0.5rem; } .pairing-input { flex: 1; padding: 0.625rem 0.75rem; font-size: 1rem; + font-family: inherit; border: 1px solid var(--color-border); border-radius: var(--radius-sm, 4px); background: var(--color-surface); @@ -274,6 +385,19 @@ defineExpose({ reset }) opacity: 0.6; } +.autocomplete-hint { + position: absolute; + left: 0; + top: 0; + padding: 0.625rem 0.75rem; + font-size: 1rem; + font-family: inherit; + color: var(--color-text-muted); + opacity: 0.5; + pointer-events: none; + white-space: pre; +} + .lookup-status { display: flex; align-items: center; diff --git a/paskia/fastapi/api.py b/paskia/fastapi/api.py index be0be80..e97a7c3 100644 --- a/paskia/fastapi/api.py +++ b/paskia/fastapi/api.py @@ -209,10 +209,10 @@ async def get_settings(): @app.get("/token-info") async def api_token_info(token: str): """Get information about a token (remote auth or reset token). - + This endpoint allows the frontend to determine what type of token it is dealing with and get relevant information for display. - + Returns: - type: "remote_auth" or "reset" - For remote_auth: host, user_agent_pretty (requesting device info) @@ -220,7 +220,7 @@ async def api_token_info(token: str): """ if not passphrase.is_well_formed(token): raise HTTPException(status_code=404, detail="Invalid token") - + # Check if this is a remote auth token if remoteauth.instance is not None: request = await remoteauth.instance.get_request(token) @@ -232,7 +232,7 @@ async def api_token_info(token: str): "user_agent_pretty": useragent.compact_user_agent(request.user_agent), "ip": request.ip, } - + # Check if this is a reset token try: reset_token = await get_reset(token) @@ -249,17 +249,17 @@ async def api_token_info(token: str): @app.get("/remote-auth-info") async def api_remote_auth_info(code: str): """Get information about a remote auth request by pairing code (first 3 words). - + This is used for real-time lookup as the user types the pairing code. Returns info about the requesting device without initiating authentication. """ if remoteauth.instance is None: raise HTTPException(status_code=404, detail="Remote auth not available") - + request = await remoteauth.instance.get_request_by_pairing_code(code) if request is None: raise HTTPException(status_code=404, detail="Invalid or expired code") - + return { "host": request.host, "user_agent": request.user_agent, diff --git a/paskia/fastapi/mainapp.py b/paskia/fastapi/mainapp.py index d61e5cb..9aff44e 100644 --- a/paskia/fastapi/mainapp.py +++ b/paskia/fastapi/mainapp.py @@ -127,21 +127,21 @@ async def examples_page(): @app.get("/auth/{token}") async def token_link(token: str): """Serve the appropriate app based on token type. - + This endpoint handles both: - Remote auth tokens (cross-device login): serve restricted app - Reset tokens (password reset / device addition): serve reset app - + The frontend will detect the type by calling /auth/api/token-info. """ if not passphrase.is_well_formed(token): raise HTTPException(status_code=404) - + # Check if this is a remote auth token first (they're in-memory, fast lookup) if remoteauth.instance is not None: request = await remoteauth.instance.get_request(token) if request is not None: return Response(*await frontend.read("/auth/restricted/index.html")) - + # Otherwise, serve the reset app (it will validate the token via API) return Response(*await frontend.read("/int/reset/index.html")) diff --git a/paskia/remoteauth.py b/paskia/remoteauth.py index f0fbe7f..6de685e 100644 --- a/paskia/remoteauth.py +++ b/paskia/remoteauth.py @@ -55,7 +55,7 @@ class RemoteAuthRequest: def _generate_pairing_code() -> str: """Generate a short, easy-to-communicate pairing code using words. - + DEPRECATED: Now we use the first 3 words of the main token instead. """ return passphrase.generate(n=PAIRING_CODE_WORDS)