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
108
109
110
111
112
113
114
115
116
117
118
|
"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;
}
}
|