diff options
author | Vika <vika@fireburn.ru> | 2022-09-28 23:13:02 +0300 |
---|---|---|
committer | Vika <vika@fireburn.ru> | 2022-09-28 23:13:02 +0300 |
commit | c35a55babd9aa6e1ad8797f3d54deec2f78ff78a (patch) | |
tree | 742023e1d1e75601b7f7cc4020b53938e82416d8 /kittybox-rs/javascript/src/indieauth.ts | |
parent | f420c7fd09b0f9ef82784a9c49889f620e73e886 (diff) | |
download | kittybox-c35a55babd9aa6e1ad8797f3d54deec2f78ff78a.tar.zst |
Switch to TypeScript
This neccesitates installing TypeScript to build Kittybox, but thankfully Nix actually takes care of that. Build Kittybox with Nix and you won't have problems. Also now I can safely do stuff.
Diffstat (limited to 'kittybox-rs/javascript/src/indieauth.ts')
-rw-r--r-- | kittybox-rs/javascript/src/indieauth.ts | 145 |
1 files changed, 145 insertions, 0 deletions
diff --git a/kittybox-rs/javascript/src/indieauth.ts b/kittybox-rs/javascript/src/indieauth.ts new file mode 100644 index 0000000..8222070 --- /dev/null +++ b/kittybox-rs/javascript/src/indieauth.ts @@ -0,0 +1,145 @@ +const WEBAUTHN_TIMEOUT = 60 * 1000; + +interface KittyboxWebauthnPreRegistrationData { + challenge: string, + rp: PublicKeyCredentialRpEntity, + user: { + cred_id: string, + name: string, + displayName: string + } +} + +async function webauthn_create_credential() { + const response = await fetch("/.kittybox/webauthn/pre_register"); + const { challenge, rp, user }: KittyboxWebauthnPreRegistrationData = await response.json(); + + return await navigator.credentials.create({ + publicKey: { + challenge: Uint8Array.from(challenge, (c) => c.charCodeAt(0)), + rp: rp, + user: { + id: Uint8Array.from(user.cred_id, (c) => c.charCodeAt(0)), + name: user.name, + displayName: user.displayName + }, + pubKeyCredParams: [{alg: -7, type: "public-key"}], + authenticatorSelection: {}, + timeout: WEBAUTHN_TIMEOUT, + attestation: "none" + } + }); +} + +interface KittyboxWebauthnCredential { + id: string, + type: "public-key" +} + +interface KittyboxWebauthnPreAuthenticationData { + challenge: string, + credentials: KittyboxWebauthnCredential[] +} + +async function webauthn_authenticate() { + const response = await fetch("/.kittybox/webauthn/pre_auth"); + const { challenge, credentials } = await response.json() as unknown as KittyboxWebauthnPreAuthenticationData; + + try { + return await navigator.credentials.get({ + publicKey: { + challenge: Uint8Array.from(challenge, (c) => c.charCodeAt(0)), + allowCredentials: credentials.map(cred => ({ + id: Uint8Array.from(cred.id, c => c.charCodeAt(0)), + type: cred.type + })), + timeout: WEBAUTHN_TIMEOUT + } + }) + } catch (e) { + console.error("WebAuthn authentication failed:", e); + alert("Using your authenticator failed. (Check the DevTools for details)"); + throw e; + } +} + +async function submit_handler(e: SubmitEvent) { + e.preventDefault(); + if (e.target != null && e.target instanceof HTMLFormElement) { + const form = e.target as HTMLFormElement; + + let scopes: Array<string>; + if (form.elements.namedItem("scope") === undefined) { + scopes = [] + } else if (form.elements.namedItem("scope") instanceof Node) { + scopes = ([form.elements.namedItem("scope")] as Array<HTMLInputElement>) + .filter((e: HTMLInputElement) => e.checked) + .map((e: HTMLInputElement) => e.value); + } else { + scopes = (Array.from(form.elements.namedItem("scope") as RadioNodeList) as Array<HTMLInputElement>) + .filter((e: HTMLInputElement) => e.checked) + .map((e: HTMLInputElement) => e.value); + } + + const authorization_request = { + response_type: (form.elements.namedItem("response_type") as HTMLInputElement).value, + client_id: (form.elements.namedItem("client_id") as HTMLInputElement).value, + redirect_uri: (form.elements.namedItem("redirect_uri") as HTMLInputElement).value, + state: (form.elements.namedItem("state") as HTMLInputElement).value, + code_challenge: (form.elements.namedItem("code_challenge") as HTMLInputElement).value, + code_challenge_method: (form.elements.namedItem("code_challenge_method") as HTMLInputElement).value, + // I would love to leave that as a list, but such is the form of + // IndieAuth. application/x-www-form-urlencoded doesn't have + // lists, so scopes are space-separated instead. It is annoying. + scope: scopes.length > 0 ? scopes.join(" ") : undefined, + }; + + let credential = null; + switch ((form.elements.namedItem("auth_method") as HTMLInputElement).value) { + case "password": + credential = (form.elements.namedItem("user_password") as HTMLInputElement).value; + if (credential.length == 0) { + alert("Please enter a password.") + return + } + break; + case "webauthn": + // credential = await webauthn_authenticate(); + alert("WebAuthn isn't implemented yet!") + return + break + default: + alert("Please choose an authentication method.") + return + } + + console.log("Authorization request:", authorization_request); + console.log("Authentication method:", credential); + + const body = JSON.stringify({ + request: authorization_request, + authorization_method: credential + }); + console.log(body); + + const response = await fetch(form.action, { + method: form.method, + body: body, + headers: { + "Content-Type": "application/json" + } + }); + + if (response.ok) { + let location = response.headers.get("Location"); + if (location != null) { + window.location.href = location + } else { + throw "Error: didn't return a location" + } + } + } else { + return + } + +} |