about summary refs log tree commit diff
path: root/kittybox-rs/src/frontend/indieauth.js
diff options
context:
space:
mode:
authorVika <vika@fireburn.ru>2022-09-19 17:30:38 +0300
committerVika <vika@fireburn.ru>2022-09-19 17:30:38 +0300
commit66049566ae865e1a4bd049257d6afc0abded16e9 (patch)
tree6013a26fa98a149d103eb4402ca91d698ef02ac2 /kittybox-rs/src/frontend/indieauth.js
parent696458657b26032e6e2a987c059fd69aaa10508d (diff)
feat: indieauth support
Working:
 - Tokens and codes
 - Authenticating with a password

Not working:
 - Setting the password (need to patch onboarding)
 - WebAuthn (the JavaScript is too complicated)
Diffstat (limited to 'kittybox-rs/src/frontend/indieauth.js')
-rw-r--r--kittybox-rs/src/frontend/indieauth.js107
1 files changed, 107 insertions, 0 deletions
diff --git a/kittybox-rs/src/frontend/indieauth.js b/kittybox-rs/src/frontend/indieauth.js
new file mode 100644
index 0000000..03626b8
--- /dev/null
+++ b/kittybox-rs/src/frontend/indieauth.js
@@ -0,0 +1,107 @@
+const WEBAUTHN_TIMEOUT = 60 * 1000;
+
+async function webauthn_create_credential() {
+  const response = await fetch("/.kittybox/webauthn/pre_register");
+  const { challenge, rp, user } = 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),
+        name: user.name,
+        displayName: user.displayName
+      },
+      pubKeyCredParams: [{alg: -7, type: "public-key"}],
+      authenticatorSelection: {},
+      timeout: WEBAUTHN_TIMEOUT,
+      attestation: "none"
+    }
+  });
+}
+
+async function webauthn_authenticate() {
+  const response = await fetch("/.kittybox/webauthn/pre_auth");
+  const { challenge, credentials } = await response.json();
+
+  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) {
+  e.preventDefault();
+  const form = e.target;
+
+  const scopes = Array.from(form.elements.scope)
+      .filter(e => e.checked)
+      .map(e => e.value);
+
+  const authorization_request = {
+    response_type: form.elements.response_type.value,
+    client_id: form.elements.client_id.value,
+    redirect_uri: form.elements.redirect_uri.value,
+    state: form.elements.state.value,
+    code_challenge: form.elements.code_challenge.value,
+    code_challenge_method: form.elements.code_challenge_method.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.auth_method.value) {
+  case "password":
+    credential = form.elements.user_password.value;
+    if (credential.length == 0) {
+      alert("Please enter a password.")
+      return
+    }
+    break;
+  case "webauthn":
+    credential = await webauthn_authenticate();
+    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) {
+    window.location.href = response.headers.get("Location")
+  }
+}
+
+document.getElementById("indieauth_page")
+  .addEventListener("submit", submit_handler);