"use strict";
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, (c) => c.charCodeAt(0)),
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();
if (e.target != null && e.target instanceof HTMLFormElement) {
const form = e.target;
let scopes;
if (form.elements.namedItem("scope") === undefined) {
scopes = [];
}
else if (form.elements.namedItem("scope") instanceof Node) {
scopes = [form.elements.namedItem("scope")]
.filter((e) => e.checked)
.map((e) => e.value);
}
else {
scopes = Array.from(form.elements.namedItem("scope"))
.filter((e) => e.checked)
.map((e) => e.value);
}
const authorization_request = {
response_type: form.elements.namedItem("response_type").value,
client_id: form.elements.namedItem("client_id").value,
redirect_uri: form.elements.namedItem("redirect_uri").value,
state: form.elements.namedItem("state").value,
code_challenge: form.elements.namedItem("code_challenge").value,
code_challenge_method: form.elements.namedItem("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.namedItem("auth_method").value) {
case "password":
credential = form.elements.namedItem("user_password").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;
}
}