Implement code word based remote authentication (#1)

Add comprehensive remote authentication system allowing users to log in from one device by authenticating from another trusted device. Features include:

- Proof of Work (PoW) protection using PBKDF2-SHA512 to prevent abuse
- Simple pairing codes (3 words) protected by dynamic PoW difficulty
- Autocomplete pairing code input with error checking
- Real-time WebSocket communication between devices

Unlike device addition links and reset links with QR codes that only allow adding an authentication method, and that work offline over the duration of several days, this mechanism is strictly online, with 5 minute time limit.
This commit is contained in:
2025-12-08 23:56:48 +00:00
parent 83419d1845
commit 1bed2c39d8
57 changed files with 3668 additions and 503 deletions
+28 -4
View File
@@ -18,12 +18,36 @@ class AwaitableWebSocket extends WebSocket {
}
this.onclose = e => {
if (!this.#opened) {
reject(new Error(`WebSocket ${this.url} failed to connect, code ${e.code}`))
reject(new Error(`Failed to connect to server (code ${e.code})`))
return
}
this.#err = e.wasClean
? new Error(`Websocket ${this.url} closed ${e.code}`)
: new Error(`WebSocket ${this.url} closed with error ${e.code}`)
// Create user-friendly close messages
let message
if (e.wasClean) {
// Standard close codes
switch (e.code) {
case 1000: message = 'Connection closed normally'; break
case 1001: message = 'Server is going away'; break
case 1002: message = 'Protocol error'; break
case 1003: message = 'Unsupported data received'; break
case 1006: message = 'Connection lost unexpectedly'; break
case 1007: message = 'Invalid data received'; break
case 1008: message = 'Policy violation'; break
case 1009: message = 'Message too large'; break
case 1010: message = 'Extension negotiation failed'; break
case 1011: message = 'Server encountered an error'; break
case 1012: message = 'Server is restarting'; break
case 1013: message = 'Server is overloaded, try again later'; break
case 1014: message = 'Bad gateway'; break
case 1015: message = 'TLS handshake failed'; break
default: message = `Connection closed (code ${e.code})`
}
} else {
message = e.code === 1006
? 'Connection lost unexpectedly'
: `Connection closed with error (code ${e.code})`
}
this.#err = new Error(message)
this.#waiting.splice(0).forEach(p => p.reject(this.#err))
}
}