Skip to content

feat: Add PIN-Based Authentication for LAN Clients#139

Closed
Muneerali199 wants to merge 1 commit into
AOSSIE-Org:mainfrom
Muneerali199:feature/pin-auth-lan
Closed

feat: Add PIN-Based Authentication for LAN Clients#139
Muneerali199 wants to merge 1 commit into
AOSSIE-Org:mainfrom
Muneerali199:feature/pin-auth-lan

Conversation

@Muneerali199
Copy link
Copy Markdown
Contributor

@Muneerali199 Muneerali199 commented Feb 20, 2026

Closes #138

Summary

  • Add PIN-based authentication for LAN clients with server-side validation and session tokens.
  • Gate WebSocket upgrades to authenticated sessions and terminate unauthorized connections.
  • Add lock screen flow on /trackpad and display current PIN on /settings (localhost only).

Changes

  • Added new server authentication modules:
    • PIN generator and validator
    • In-memory session store
  • Introduced new API endpoints:
    • POST /api/auth/pin
    • POST /api/auth/verify
  • Updated Trackpad UI to block access until PIN authentication succeeds (remote clients only).
  • Updated Settings page:
    • Displays current session PIN
    • Removed token sharing via URL

Testing

  • Ran npm install

    • Result: Found 0 vulnerabilities
  • Ran npm test

    • Executes vitest run
    • Result: No test files found (exits with code 1)

Notes

  • PIN resets automatically on server restart.
  • PIN is visible only when accessed from localhost.
  • WebSocket now rejects remote connections without a valid session token.

Summary by CodeRabbit

  • New Features
    • Implemented PIN-based authentication system for remote access.
    • Trackpad access now protected behind PIN verification screen (automatic bypass on localhost).
    • Server settings displays current authentication PIN for secure connection.
    • Remote connections require valid PIN-based authentication to establish.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 20, 2026

📝 Walkthrough

Walkthrough

This PR implements a PIN-based authentication system for WebSocket connections. It introduces a new usePinAuth hook for managing PIN authentication state on the client, server-side utilities for PIN generation and validation (pinAuth.ts), an in-memory session store with expiry-based management (sessionStore.ts), and updates the WebSocket server to use HTTP endpoints for PIN submission and token verification. The useRemoteConnection hook is refactored to accept authentication options, and both the settings and trackpad routes are updated to integrate the new PIN authentication flow.

Changes

Cohort / File(s) Summary
PIN Authentication Hook
src/hooks/usePinAuth.ts
New React hook that manages PIN authentication state, handles PIN submission via POST /api/auth/pin, verifies tokens at /api/auth/verify, stores tokens in localStorage, and exposes status, token, error, isSubmitting, and submitPin callback.
Remote Connection Hook
src/hooks/useRemoteConnection.ts
Updated to accept RemoteConnectionOptions with optional token and enabled fields. Implements early exit when disabled, uses token from options for WebSocket URL construction, and adjusts dependency array to re-subscribe on token/enabled changes.
Server PIN and Session Management
src/server/pinAuth.ts, src/server/sessionStore.ts
New modules: pinAuth.ts generates a random PIN at startup and provides utilities to validate it (using timing-safe comparison) and detect localhost requests. sessionStore.ts implements in-memory session storage with 12-hour TTL, UUID-based token generation, and secure token comparison.
WebSocket Server Authentication
src/server/websocket.ts
Replaced token-store authentication with session-based approach. Added HTTP auth endpoints: GET /api/auth/pin (localhost only), POST /api/auth/pin (PIN validation and session creation), and POST /api/auth/verify (token validation). Local connections bypass token requirement; remote connections require valid session tokens. Removed token generation via WebSocket messages.
Client Route Configuration
src/routes/settings.tsx, src/routes/trackpad.tsx
Updated to use PIN-based authentication. settings.tsx fetches PIN from /api/auth/pin on localhost and displays it in Server Settings. trackpad.tsx integrates usePinAuth hook, gates remote connection behind authentication state, and displays PIN entry UI for unauthenticated users (includes validation for 4–6 digit PINs).

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Server
    participant SessionStore
    participant PinAuth

    Client->>Server: GET /api/auth/pin (localhost only)
    Server->>PinAuth: getPin()
    PinAuth-->>Server: PIN string
    Server-->>Client: PIN display

    Client->>Client: User enters PIN
    Client->>Server: POST /api/auth/pin {pin}
    Server->>PinAuth: validatePin(pin)
    PinAuth-->>Server: true/false
    alt PIN Valid
        Server->>SessionStore: createSession(ip?)
        SessionStore-->>Server: token (UUID)
        Server-->>Client: {status: "authenticated", token}
        Client->>Client: Store token in localStorage
    else PIN Invalid
        Server-->>Client: {status: "error", error: "Invalid PIN"}
    end

    Client->>Server: POST /api/auth/verify {token}
    Server->>SessionStore: isValidSession(token)
    SessionStore-->>Server: true/false
    Server-->>Client: {valid: true/false}

    Client->>Server: WebSocket upgrade {token in query}
    alt Token Valid
        Server->>SessionStore: touchSession(token)
        SessionStore-->>Server: success
        Server-->>Client: WebSocket connected
    else Token Invalid
        Server-->>Client: 401 Unauthorized
    end
Loading
sequenceDiagram
    participant User
    participant TrackpadUI
    participant usePinAuth
    participant useRemoteConnection
    participant WebSocketServer

    User->>TrackpadUI: Visit trackpad (non-localhost)
    TrackpadUI->>usePinAuth: Hook initialization
    usePinAuth-->>TrackpadUI: status="unauthenticated"
    TrackpadUI->>TrackpadUI: Render PIN entry UI

    User->>TrackpadUI: Submit PIN
    TrackpadUI->>usePinAuth: submitPin(pin)
    usePinAuth->>WebSocketServer: POST /api/auth/pin
    WebSocketServer-->>usePinAuth: token
    usePinAuth-->>TrackpadUI: status="authenticated", token

    TrackpadUI->>useRemoteConnection: Initialize with {token, enabled: true}
    useRemoteConnection->>WebSocketServer: WebSocket upgrade {token}
    WebSocketServer-->>useRemoteConnection: Connected
    useRemoteConnection-->>TrackpadUI: status="connected"

    TrackpadUI->>TrackpadUI: Render Trackpad UI
    User->>TrackpadUI: Control trackpad
    TrackpadUI->>WebSocketServer: Send commands via WebSocket
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

🐰 A PIN hops in, session tokens dance,
No more stored secrets by chance—
Localhost knows, remotes authenticate true,
WebSockets bloom with timing-safe glue! ✨

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description covers the main changes and testing efforts, but omits all required template sections including Addressed Issues, Type of change checkboxes, Functional Verification checklist, and Additional Notes. Complete the PR description template by including Addressed Issues, selecting appropriate Type of change, completing Functional Verification checklist items, and filling in remaining template sections per repository requirements.
Docstring Coverage ⚠️ Warning Docstring coverage is 6.25% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: Add PIN-Based Authentication for LAN Clients' directly and accurately summarizes the main change in the changeset, which implements PIN-based authentication for LAN clients across multiple modules.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/routes/settings.tsx (1)

125-128: ⚠️ Potential issue | 🟡 Minor

Inconsistent localhost check — misses 127.0.0.1 and ::1.

Line 127 only checks window.location.hostname !== 'localhost', while the PIN fetch effect (lines 65-68) and trackpad.tsx (lines 17-21) also handle 127.0.0.1 and ::1. If a user accesses the settings page via http://127.0.0.1:port/settings, the LAN IP auto-detection won't run.

Proposed fix
-        if (window.location.hostname !== 'localhost') return;
+        const hostname = window.location.hostname;
+        if (hostname !== 'localhost' && hostname !== '127.0.0.1' && hostname !== '::1') return;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/settings.tsx` around lines 125 - 128, The hostname guard inside
the useEffect in settings.tsx currently only skips when hostname !== 'localhost'
so it misses requests from '127.0.0.1' and '::1'; update the check used in that
useEffect to allow the same localhost variants as the PIN fetch effect and
trackpad.tsx (i.e. treat 'localhost', '127.0.0.1' and '::1' as local) by
replacing the single-string comparison with a whitelist or regex check that
matches those three hostnames so the LAN IP auto-detection runs for all
local-access variants.
🧹 Nitpick comments (5)
src/server/sessionStore.ts (1)

39-42: O(n) linear scan with timingSafeEqual on every validation.

Each call to isValidSession and touchSession iterates all sessions with a crypto comparison. This is fine for few LAN clients but consider using a Map<string, SessionEntry> keyed by token for O(1) lookups if the session count could grow. The timing-safe comparison guards against remote timing attacks, but since these are self-generated UUIDs (high entropy, not user-supplied secrets), a plain Map lookup would be acceptable here.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/server/sessionStore.ts` around lines 39 - 42, Replace the array-based
sessions store and linear scans with a Map keyed by token to get O(1) lookups:
change the sessions collection to a Map<string, SessionEntry>, update
isValidSession to purgeExpired then use sessions.has(token) (no
timingSafeEqual), and update touchSession to lookup by token via Map.get(token)
and update its lastSeen; also update any session creation/removal code to use
Map.set/delete and ensure purgeExpired iterates Map entries and deletes expired
ones.
src/server/pinAuth.ts (1)

4-6: Variable PIN length (4–6) may cause user confusion.

The PIN length changes randomly at each server restart. The client form hints "4–6 digit PIN" but the server rejects anything that doesn't match the exact length. Users may need multiple attempts to guess the correct length unless they check the settings page. Consider fixing the length (e.g., always 6 digits) for a more predictable UX, or displaying the expected length in the client prompt.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/server/pinAuth.ts` around lines 4 - 6, The server currently chooses
PIN_LENGTH randomly on each restart (variables PIN_LENGTH, PIN_MAX, PIN) causing
the server to accept only that exact length while the client says "4–6 digit
PIN"; change PIN_LENGTH to a fixed value (e.g., 6) and compute PIN_MAX and PIN
from that fixed length so the server consistently expects a stable PIN length,
and optionally update any client prompt to match the chosen fixed length.
src/hooks/usePinAuth.ts (2)

27-48: Token verification runs on every mount even when token is known-invalid.

The verify async function is defined but there's no abort mechanism if the component unmounts before the fetch completes. Consider using an AbortController to cancel the in-flight request on cleanup to avoid state updates on an unmounted component.

Proposed fix
     useEffect(() => {
         if (typeof window === 'undefined') return;
         if (bypass) {
             setStatus('authenticated');
             return;
         }

         const stored = localStorage.getItem(STORAGE_KEY);
         if (!stored) {
             setStatus('unauthenticated');
             return;
         }

+        const controller = new AbortController();
         const verify = async () => {
             try {
                 const res = await fetch('/api/auth/verify', {
                     method: 'POST',
                     headers: { 'Content-Type': 'application/json' },
                     body: JSON.stringify({ token: stored }),
+                    signal: controller.signal,
                 });
                 if (!res.ok) throw new Error('Verification failed');
                 const data = await res.json();
                 if (data?.valid) {
                     setToken(stored);
                     setStatus('authenticated');
                 } else {
                     localStorage.removeItem(STORAGE_KEY);
                     setStatus('unauthenticated');
                 }
             } catch {
+                if (!controller.signal.aborted) {
+                    localStorage.removeItem(STORAGE_KEY);
+                }
                 setStatus('unauthenticated');
             }
         };

         verify();
+        return () => controller.abort();
     }, [bypass]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/usePinAuth.ts` around lines 27 - 48, The verify function in
usePinAuth performs a fetch('/api/auth/verify') without an abort mechanism,
causing potential state updates after unmount; fix by creating an
AbortController inside useEffect, pass controller.signal to fetch, call
controller.abort() in the effect cleanup, and guard setToken/setStatus so they
only run when the request wasn't aborted (e.g., check !controller.signal.aborted
or catch and ignore AbortError). Reference verify, setToken, setStatus,
STORAGE_KEY and the fetch('/api/auth/verify') call when making the changes.

43-45: Stale token left in localStorage on network/verification errors.

When the fetch call throws (network error, non-OK response), the stored token remains in localStorage. On every subsequent mount, the hook will re-attempt verification of a potentially invalid token. Consider removing the token here to avoid repeated futile verification attempts.

Proposed fix
             } catch {
+                localStorage.removeItem(STORAGE_KEY);
                 setStatus('unauthenticated');
             }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/usePinAuth.ts` around lines 43 - 45, In usePinAuth's catch block
(where it calls setStatus('unauthenticated')), also clear the stale auth token
from localStorage to prevent repeated failed verification attempts; add a
localStorage.removeItem('pinAuthToken') (or removeItem using the exact key the
hook uses to store the token) before or alongside setStatus('unauthenticated')
so the hook won't repeatedly try to verify a stale token on subsequent mounts.
src/hooks/useRemoteConnection.ts (1)

56-61: Unconditional reconnect on close may cause infinite retry loops.

If the server rejects the token (401 during upgrade), the socket closes immediately and the hook retries every 3 seconds indefinitely. Consider adding exponential backoff or a maximum retry count, or checking the close code to distinguish server rejection from transient network issues.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/useRemoteConnection.ts` around lines 56 - 61, The socket.onclose
handler in useRemoteConnection.ts unconditionally schedules reconnects which can
cause infinite retry loops on auth rejection; modify socket.onclose to inspect
the CloseEvent.code (and/or reason) and abort retries for authentication/upgrade
failures, add a retryCount state (e.g., retryCount) with a configurable
maxRetries and an exponential backoff (baseBackoffMs doubling each attempt) when
calling connect, ensure reconnectTimer is cleared on unmount or successful
connect, and update setStatus appropriately; touch the socket.onclose block, the
connect function, and the reconnectTimer/retryCount bookkeeping to implement
these changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/routes/trackpad.tsx`:
- Around line 226-228: The handler silently returns when the PIN check against
/^\d{4,6}$/ fails (using rawPin), so set the visible error state instead of
returning silently: update the submit handler to call the existing authError
setter (e.g., setAuthError or the local authError state used for display) with a
clear message like "PIN must be 4–6 digits" when the regex test fails;
optionally also add HTML-level validation by adding pattern="^\d{4,6}$" and
required to the PIN input so the browser shows native validation before the
handler runs.

In `@src/server/websocket.ts`:
- Around line 62-79: Add per-IP rate limiting to the POST /api/auth/pin handler
in websocket.ts: before validating the pin (before validatePin is called), track
attempts for req.socket.remoteAddress (or fallback) and enforce a limit (e.g.,
max 5 attempts per minute), returning a 429 with sendJson when exceeded; on each
failed PIN validation increment the counter and apply an escalating
timeout/temporary lockout (or exponential backoff) for that IP, and on
successful createSession reset/clear that IP’s counters; ensure the rate-store
is safe for concurrent use (in-memory map with timestamps or a small LRU cache)
and include logic to expire old entries.
- Around line 107-117: Current code attaches the auth handler directly via
server.on('request', using AUTH_HANDLER_FLAG and handleAuthRequest), which
bypasses Vite's middleware pipeline; change this to register the auth routes
inside the plugin's configureServer hook using server.middlewares.use(...) so
requests go through Vite's middleware chain and ensure the AUTH_HANDLER_FLAG is
still set only once. Also add request throttling for the POST /api/auth/pin
route (e.g., per-IP rate limiting or exponential backoff after failures) inside
the auth middleware so repeated PIN attempts are limited; update
handleAuthRequest (or the new middleware wrapper) to enforce and log
rate-limited responses.

---

Outside diff comments:
In `@src/routes/settings.tsx`:
- Around line 125-128: The hostname guard inside the useEffect in settings.tsx
currently only skips when hostname !== 'localhost' so it misses requests from
'127.0.0.1' and '::1'; update the check used in that useEffect to allow the same
localhost variants as the PIN fetch effect and trackpad.tsx (i.e. treat
'localhost', '127.0.0.1' and '::1' as local) by replacing the single-string
comparison with a whitelist or regex check that matches those three hostnames so
the LAN IP auto-detection runs for all local-access variants.

---

Nitpick comments:
In `@src/hooks/usePinAuth.ts`:
- Around line 27-48: The verify function in usePinAuth performs a
fetch('/api/auth/verify') without an abort mechanism, causing potential state
updates after unmount; fix by creating an AbortController inside useEffect, pass
controller.signal to fetch, call controller.abort() in the effect cleanup, and
guard setToken/setStatus so they only run when the request wasn't aborted (e.g.,
check !controller.signal.aborted or catch and ignore AbortError). Reference
verify, setToken, setStatus, STORAGE_KEY and the fetch('/api/auth/verify') call
when making the changes.
- Around line 43-45: In usePinAuth's catch block (where it calls
setStatus('unauthenticated')), also clear the stale auth token from localStorage
to prevent repeated failed verification attempts; add a
localStorage.removeItem('pinAuthToken') (or removeItem using the exact key the
hook uses to store the token) before or alongside setStatus('unauthenticated')
so the hook won't repeatedly try to verify a stale token on subsequent mounts.

In `@src/hooks/useRemoteConnection.ts`:
- Around line 56-61: The socket.onclose handler in useRemoteConnection.ts
unconditionally schedules reconnects which can cause infinite retry loops on
auth rejection; modify socket.onclose to inspect the CloseEvent.code (and/or
reason) and abort retries for authentication/upgrade failures, add a retryCount
state (e.g., retryCount) with a configurable maxRetries and an exponential
backoff (baseBackoffMs doubling each attempt) when calling connect, ensure
reconnectTimer is cleared on unmount or successful connect, and update setStatus
appropriately; touch the socket.onclose block, the connect function, and the
reconnectTimer/retryCount bookkeeping to implement these changes.

In `@src/server/pinAuth.ts`:
- Around line 4-6: The server currently chooses PIN_LENGTH randomly on each
restart (variables PIN_LENGTH, PIN_MAX, PIN) causing the server to accept only
that exact length while the client says "4–6 digit PIN"; change PIN_LENGTH to a
fixed value (e.g., 6) and compute PIN_MAX and PIN from that fixed length so the
server consistently expects a stable PIN length, and optionally update any
client prompt to match the chosen fixed length.

In `@src/server/sessionStore.ts`:
- Around line 39-42: Replace the array-based sessions store and linear scans
with a Map keyed by token to get O(1) lookups: change the sessions collection to
a Map<string, SessionEntry>, update isValidSession to purgeExpired then use
sessions.has(token) (no timingSafeEqual), and update touchSession to lookup by
token via Map.get(token) and update its lastSeen; also update any session
creation/removal code to use Map.set/delete and ensure purgeExpired iterates Map
entries and deletes expired ones.

Comment thread src/routes/trackpad.tsx
Comment on lines +226 to +228
if (!/^\d{4,6}$/.test(rawPin)) {
return;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Silent rejection when PIN format is invalid — no user feedback.

When the regex test fails, the form handler returns without any indication to the user. Consider setting an error message so the user knows what went wrong.

Proposed fix
                                const rawPin = String(data.get('pin') || '').trim();
                                if (!/^\d{4,6}$/.test(rawPin)) {
+                                   // Optionally surface an error to the user
                                    return;
                                }

You could reuse the existing authError display by calling a local setter or relying on the HTML pattern attribute for native validation. At minimum, adding required to the input would trigger native form validation before reaching this code.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/trackpad.tsx` around lines 226 - 228, The handler silently returns
when the PIN check against /^\d{4,6}$/ fails (using rawPin), so set the visible
error state instead of returning silently: update the submit handler to call the
existing authError setter (e.g., setAuthError or the local authError state used
for display) with a clear message like "PIN must be 4–6 digits" when the regex
test fails; optionally also add HTML-level validation by adding
pattern="^\d{4,6}$" and required to the PIN input so the browser shows native
validation before the handler runs.

Comment thread src/server/websocket.ts
Comment on lines +62 to +79
if (url.pathname === '/api/auth/pin' && req.method === 'POST') {
try {
const body = await readJsonBody(req);
const pin = typeof body?.pin === 'string' ? body.pin : '';
if (!validatePin(pin)) {
sendJson(res, 401, { error: 'Invalid PIN.' });
return true;
}

const token = createSession(req.socket.remoteAddress || undefined);
sendJson(res, 200, { token });
return true;
} catch (error: any) {
const message = error?.message === 'Payload too large' ? 'Payload too large.' : 'Invalid request.';
sendJson(res, 400, { error: message });
return true;
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

No rate limiting on PIN submission — 4-digit PINs are brute-forceable.

POST /api/auth/pin has no rate limiting. With PIN lengths of 4–6 digits (10,000–1,000,000 combinations), a LAN attacker can brute-force a 4-digit PIN in minutes. Consider adding per-IP rate limiting (e.g., max 5 attempts per minute with exponential backoff or temporary lockout).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/server/websocket.ts` around lines 62 - 79, Add per-IP rate limiting to
the POST /api/auth/pin handler in websocket.ts: before validating the pin
(before validatePin is called), track attempts for req.socket.remoteAddress (or
fallback) and enforce a limit (e.g., max 5 attempts per minute), returning a 429
with sendJson when exceeded; on each failed PIN validation increment the counter
and apply an escalating timeout/temporary lockout (or exponential backoff) for
that IP, and on successful createSession reset/clear that IP’s counters; ensure
the rate-store is safe for concurrent use (in-memory map with timestamps or a
small LRU cache) and include logic to expire old entries.

Comment thread src/server/websocket.ts
Comment on lines +107 to +117
if (!(server as any)[AUTH_HANDLER_FLAG]) {
(server as any)[AUTH_HANDLER_FLAG] = true;
server.on('request', (req: IncomingMessage, res: ServerResponse) => {
handleAuthRequest(req, res).catch((error) => {
logger.error(`Auth handler error: ${String(error)}`);
if (!res.headersSent) {
sendJson(res, 500, { error: 'Server error.' });
}
});
});
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check how createWsServer is invoked and how the server object is obtained
rg -n 'createWsServer' --type=ts -C5

# Check Vite plugin setup
rg -n 'configureServer' --type=ts -C10

# Look for how the HTTP server is created / accessed
rg -n 'httpServer' --type=ts -C3

Repository: AOSSIE-Org/Rein

Length of output: 3753


🏁 Script executed:

#!/bin/bash
# Check Nitro configuration in vite.config
cat -n vite.config.ts

# Check if there are any middleware registrations related to API routes
rg -n 'server\.middlewares' --type=ts -C5

# Check the full websocket.ts to see if there's middleware registration
wc -l src/server/websocket.ts

# Look for any API route definitions or middleware setup
rg -n 'middlewares\.use\|server\.use' --type=ts -C3

Repository: AOSSIE-Org/Rein

Length of output: 1660


🏁 Script executed:

#!/bin/bash
# Check for API routes - Nitro typically uses routes/ or server/ directories
fd -t f '\.(ts|js)$' server/routes/ api/ routes/ 2>/dev/null | head -20

# Check if there's a server/ directory with Nitro routes
find src/server -name '*.ts' -type f | head -20

# Look at the full websocket.ts to understand request flow
cat -n src/server/websocket.ts | head -120

# Check the auth handler section specifically
cat -n src/server/websocket.ts | sed -n '50,120p'

Repository: AOSSIE-Org/Rein

Length of output: 8718


🏁 Script executed:

#!/bin/bash
# Check if there's rate limiting on PIN validation attempts
rg -n 'validatePin\|rateLimit\|attempt' --type=ts -C3

# Check the pinAuth.ts file to see PIN validation logic
cat -n src/server/pinAuth.ts

# Look for session tracking or attempt limiting
cat -n src/server/sessionStore.ts

Repository: AOSSIE-Org/Rein

Length of output: 2637


🏁 Script executed:

#!/bin/bash
# Check if there are Nitro API routes handling /api/auth
find src -name '*.ts' -path '*/routes/*' | xargs grep -l 'auth' 2>/dev/null

# Check the server directory structure more carefully
find src/server -type f -name '*.ts' | sort

# Look for any API route files in common Nitro locations
ls -la src/routes/ 2>/dev/null || ls -la src/api/ 2>/dev/null || echo "No routes/ or api/ directory found"

# Check if there's a server/routes directory
ls -la src/server/ 2>/dev/null

Repository: AOSSIE-Org/Rein

Length of output: 1051


🏁 Script executed:

#!/bin/bash
# Check if there's any mention of /api handling in Vite config or comments
grep -n 'api\|middleware\|route' vite.config.ts

# Look for tanstackStart documentation or usage patterns
rg -n 'tanstackStart' --type=ts -C5

# Check if there's any test or error handling related to this
rg -n 'write after\|headersSent\|already sent' --type=ts

Repository: AOSSIE-Org/Rein

Length of output: 1057


🏁 Script executed:

#!/bin/bash
# Let me check if there are any error handling tests or existing issues with this pattern
rg -n 'POST.*auth/pin\|validatePin' --type=ts -B5 -A5

# Check if there's any rate limiting elsewhere
rg -n 'limit\|throttle\|bucket\|attempt' --type=ts

# Verify handleAuthRequest is actually called without awaiting by checking the full context
cat -n src/server/websocket.ts | sed -n '107,125p'

Repository: AOSSIE-Org/Rein

Length of output: 969


🏁 Script executed:

#!/bin/bash
# Check if there's special handling in TanstackStart or if API routes are supposed to be handled this way
grep -rn 'createServer\|middleware' src/ --type=ts | grep -v node_modules | head -10

# Verify the exact request flow - check if there are tests or documentation
find . -name '*.md' -o -name '*.test.ts' | xargs grep -l 'auth\|api' 2>/dev/null | head -5

# Check the complete websocket handler to see if there's error handling we missed
cat -n src/server/websocket.ts | sed -n '49,95p'

Repository: AOSSIE-Org/Rein

Length of output: 2320


🏁 Script executed:

#!/bin/bash
# Let me check the full websocket.ts to understand the complete flow and how the handler integrates
cat -n src/server/websocket.ts | tail -100

# Check if there's any documentation about the server setup
cat README.md 2>/dev/null | head -50

Repository: AOSSIE-Org/Rein

Length of output: 5824


🏁 Script executed:

#!/bin/bash
# Check if there are any error logs or tests documenting request handling issues
find . -name '*.test.*' -o -name '*.spec.*' | xargs grep -l 'auth\|request' 2>/dev/null | head -3

# Check the TanStack Start / Vite integration - does it have middlewares property?
rg 'server\.middlewares' . --type=ts 2>/dev/null | head -5

# Verify the exact plugin order and if configureServer is called before or after other plugins
grep -n 'configureServer\|plugins:' vite.config.ts

Repository: AOSSIE-Org/Rein

Length of output: 109


Use Vite middleware pattern for auth routes instead of raw 'request' listeners.

The auth handler is registered via server.on('request', ...) on the raw HTTP server, which bypasses Vite's standard middleware pipeline. This can cause unpredictable behavior with Vite's dev server or preview server, which have their own request handling. The idiomatic approach is to register auth routes as Vite middleware via server.middlewares.use(...) in the plugin's configureServer hook, which ensures proper ordering relative to Vite's internal handlers.

Additionally, add rate limiting to POST /api/auth/pin. The endpoint currently has no attempt throttling, making the 4-digit PIN (10,000 combinations) brute-forceable in ~17 minutes at a modest request rate. Implement per-IP rate limiting or exponential backoff after failed attempts.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/server/websocket.ts` around lines 107 - 117, Current code attaches the
auth handler directly via server.on('request', using AUTH_HANDLER_FLAG and
handleAuthRequest), which bypasses Vite's middleware pipeline; change this to
register the auth routes inside the plugin's configureServer hook using
server.middlewares.use(...) so requests go through Vite's middleware chain and
ensure the AUTH_HANDLER_FLAG is still set only once. Also add request throttling
for the POST /api/auth/pin route (e.g., per-IP rate limiting or exponential
backoff after failures) inside the auth middleware so repeated PIN attempts are
limited; update handleAuthRequest (or the new middleware wrapper) to enforce and
log rate-limited responses.

@Rozerxshashank
Copy link
Copy Markdown
Contributor

Hey @Muneerali199, see the discord chats. This was not planned. I was also going with this approach but then we decided to go with the token method with an expiry.

@Muneerali199
Copy link
Copy Markdown
Contributor Author

Ok sir @Rozerxshashank thank you for your response

@Muneerali199
Copy link
Copy Markdown
Contributor Author

@Rozerxshashank Thanks for the review and feedback. I wasn’t aware that we had finalized on the token-with-expiry approach instead of PIN-based verification. Since the direction has changed, I’ll align with the updated plan and rework the implementation accordingly. Let me confirm the exact token lifecycle and expiry behavior before updating the PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Implement PIN-Based Authentication for LAN Connections

2 participants