diff options
| author | Osmium Sorcerer <os@sof.beauty> | 2026-04-07 13:19:40 +0000 |
|---|---|---|
| committer | Osmium Sorcerer <os@sof.beauty> | 2026-04-18 16:52:23 +0000 |
| commit | aa4c30bb6d1e46b5019065fba6c0eb3c08aa1f34 (patch) | |
| tree | 2b6bf1557122d56eecbdcbc2d063289bc16d1186 /webAO/client.ts | |
| parent | ae7ef2c6c76947ea12cbb1592152d9c80fd1a8f3 (diff) | |
Bring in the subprotocol (the same as what's used on the desktop client
for public-key authentication) to carry the relevant messages:
- AuthRequest: first step in the flow, the client sends it to signal the
intent to authenticate to the server.
- AssertCredential and AssertionFinish: server's challenge and client's
response, respectively, to finalize the flow.
- RegisterCredential and RegistrationFinish: same structure as the
above. Unlike the simple public-key auth with an out-of-band setup,
passkeys require user interaction to register. User must be
authorized.
Validate all relevant checks on the API side, and hand the data over to
the server for it to verify attestations and assertions.
Because it's a primary auth mechanism (not a second factor), require
user verification.
As we don't use any other method on web, add a passkey button as the
only sign-in interface. Passkeys are discoverable, we don't even need a
username.
Diffstat (limited to 'webAO/client.ts')
| -rw-r--r-- | webAO/client.ts | 35 |
1 files changed, 33 insertions, 2 deletions
diff --git a/webAO/client.ts b/webAO/client.ts index 50eb18b..43b04f0 100644 --- a/webAO/client.ts +++ b/webAO/client.ts @@ -21,6 +21,8 @@ import { fetchEvidenceList, fetchCharacterList, } from "./client/fetchLists"; +import { ExMsgType, handleRegisterCredential, handleAssertCredential, AuthState } from "./ext_packet" +import { initWebAuthn } from "./auth" const { ip: serverIP, connect, mode, theme, serverName, char: autoChar, area: autoArea } = queryParser(); export { autoChar, autoArea }; @@ -156,6 +158,7 @@ class Client extends EventEmitter { emote_extensions: string[]; emotions_extensions: string[]; background_extensions: string[]; + authState: AuthState; constructor(connectionString: string) { super(); @@ -172,6 +175,7 @@ class Client extends EventEmitter { this.serv.addEventListener("close", this.emit.bind(this, "close")); this.serv.addEventListener("message", this.emit.bind(this, "message")); this.serv.addEventListener("error", this.emit.bind(this, "error")); + this.serv.binaryType = "arraybuffer"; // If the client is still not connected 5 seconds after attempting to join // It's fair to assume that the server is not reachable @@ -215,7 +219,8 @@ class Client extends EventEmitter { this.charicon_extensions = [".webp", ".png"]; this.emote_extensions = [".webp", ".png", ".apng", ".gif"]; this.emotions_extensions = [".webp", ".png"]; - this.background_extensions = [".webp", ".png", ".apng", ".gif"];; + this.background_extensions = [".webp", ".png", ".apng", ".gif"]; + this.authState = AuthState.None; } /** @@ -269,6 +274,7 @@ class Client extends EventEmitter { document.getElementById("client_charselect").style.display = "none"; appendICNotice("Connected"); client.joinServer(); + initWebAuthn(); } /** @@ -304,7 +310,13 @@ class Client extends EventEmitter { const msg = e.data; console.debug(`S: ${msg}`); - this.handle_server_packet(msg); + if (typeof msg === "string") { + this.handle_server_packet(msg); + return; + } + + const msgBytes = new Uint8Array(msg); + this.handle_ext_packet(msgBytes); } /** @@ -413,6 +425,25 @@ class Client extends EventEmitter { fetchEvidenceList(); fetchCharacterList(); } + + handle_ext_packet(msg: Uint8Array) { + if (msg.length === 0) { + return; + } + const type = msg[0]; + const body = msg.subarray(1); + switch (type) { + case ExMsgType.RegisterCredential: + handleRegisterCredential(body); + break; + case ExMsgType.AssertCredential: + handleAssertCredential(body); + break; + default: + console.log("Invalid message received: ${type}"); + break; + } + } } export default Client; |
