1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
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;
let scopes = form.elements.scope === undefined ? [] : 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);
|