let page s b =
Printf.sprintf {|
WebAuthn Demo
%s|} s b
let overview notes authenticated_as users =
let authenticated_as =
match authenticated_as with
| None -> "Not authenticated
"
| Some user -> Printf.sprintf {|Authenticated as %s
|} user
and links =
{|Register
|}
and users =
String.concat ""
("Users
" ::
Hashtbl.fold (fun id (name, keys) acc ->
let credentials = List.map (fun (_, cid, _) ->
Base64.encode_string ~pad:false ~alphabet:Base64.uri_safe_alphabet cid)
keys
in
(Printf.sprintf "- %s [authenticate] (%s)
" name id (String.concat ", " credentials)) :: acc)
users [] @ [ "
" ])
in
page "" (String.concat "" (notes @ [authenticated_as;links;users]))
let register_view origin user =
let script = Printf.sprintf {|
function makePublicKey(challengeData, attestation) {
let challenge = bufferDecode(challengeData.challenge);
let user_id = bufferDecode(challengeData.user.id);
return {
challenge: challenge,
rp: {
id: "%s",
name: "WebAuthn Demo from robur.coop"
},
user: {
id: user_id,
displayName: challengeData.user.displayName,
name: challengeData.user.name
},
pubKeyCredParams: [
{
type: "public-key",
alg: -7
}
],
attestation: attestation,
excludeCredentials: challengeData.excludeCredentials.map(id => ({ type: "public-key",
id: bufferDecode(id)}))
};
}
function do_register(username, attestation) {
fetch("/registration-challenge/"+username)
.then(response => response.json())
.then(function (challengeData) {
let publicKey = makePublicKey(challengeData, attestation);
navigator.credentials.create({ publicKey })
.then(function (credential) {
let response = credential.response;
let attestationObject = new Uint8Array(response.attestationObject);
let clientDataJSON = new Uint8Array(response.clientDataJSON);
let body =
JSON.stringify({
attestationObject: bufferEncode(attestationObject),
clientDataJSON: bufferEncode(clientDataJSON),
});
let headers = {'Content-type': "application/json; charset=utf-8"};
let request = new Request('/register_finish/'+challengeData.user.id, { method: 'POST', body: body, headers: headers } );
fetch(request)
.then(function (response) {
if (!response.ok && response.status != 403) {
alert("bad response: " + response.status);
return
};
response.json().then(function (success) {
alert(success ? "Successfully registered!" : "Failed to register :(");
window.location = "/";
});
});
}).catch(function (err) {
// XXX: only if the exception came from navigator.credentials.create()
if (err.name === "InvalidStateError") {
alert("authenticator already registered");
} else {
alert("exception: " + err);
}
window.location = "/";
});
});
}
function doit() {
let username = document.getElementById("username").value;
let attestation = document.getElementById("attestation").value;
return do_register(username, attestation);
}
|} origin
and body =
Printf.sprintf {|
Welcome.
|} user
in
page script body
let authenticate_view challenge credentials user =
let script =
Printf.sprintf {|
let request_options = {
challenge: bufferDecode("%s"),
allowCredentials: %s.map(x => { x.id = bufferDecode(x.id); return x }),
};
navigator.credentials.get({ publicKey: request_options })
.then(function (assertion) {
let response = assertion.response;
let authenticatorData = new Uint8Array(assertion.response.authenticatorData);
let clientDataJSON = new Uint8Array(assertion.response.clientDataJSON);
let signature = new Uint8Array(assertion.response.signature);
let userHandle = assertion.response.userHandle ? new Uint8Array(assertion.response.userHandle) : null;
let body =
JSON.stringify({
authenticatorData: bufferEncode(authenticatorData),
clientDataJSON: bufferEncode(clientDataJSON),
signature: bufferEncode(signature),
userHandle: userHandle ? bufferEncode(userHandle) : null,
});
let headers = {'Content-type': "application/json; charset=utf-8"};
let username = window.location.pathname.substring("/authenticate/".length);
let request = new Request('/authenticate_finish/'+assertion.id+'/'+username, { method: 'POST', body: body, headers: headers } );
fetch(request)
.then(function (response) {
if (!response.ok) {
alert("bad response: " + response.status);
window.location = "/";
return
};
response.json().then(function (success) {
alert(success ? "Successfully authenticated!" : "Failed to authenticate :(");
window.location = "/";
});
});
}).catch(function (err) {
alert("exception: " + err);
window.location = "/";
});
|} challenge
(Yojson.to_string (`List
(List.map (fun credential_id ->
(`Assoc ["id", `String credential_id; "type", `String "public-key"]))
credentials)))
and body =
Printf.sprintf {|
Touch your token to authenticate as %S.
|} user
in
page script body