71 lines
3.4 KiB
JavaScript
71 lines
3.4 KiB
JavaScript
/* Passkeys im Browser: Anmeldung (staff/login.php) und Registrierung (Profil → Sicherheit). Spricht mit /staff/passkey.php. */
|
|
(function () {
|
|
'use strict';
|
|
function b64uToBuf(s) {
|
|
s = s.replace(/-/g, '+').replace(/_/g, '/');
|
|
while (s.length % 4) s += '=';
|
|
var bin = atob(s), buf = new Uint8Array(bin.length);
|
|
for (var i = 0; i < bin.length; i++) buf[i] = bin.charCodeAt(i);
|
|
return buf.buffer;
|
|
}
|
|
function bufToB64u(buf) {
|
|
var b = new Uint8Array(buf), s = '';
|
|
for (var i = 0; i < b.length; i++) s += String.fromCharCode(b[i]);
|
|
return btoa(s).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
}
|
|
function post(csrf, fields) {
|
|
var fd = new FormData();
|
|
fd.append('csrf_token', csrf);
|
|
Object.keys(fields).forEach(function (k) { fd.append(k, fields[k]); });
|
|
return fetch('/staff/passkey.php', { method: 'POST', body: fd, credentials: 'same-origin' }).then(function (r) {
|
|
return r.json().catch(function () { return { ok: false, error: 'Unerwartete Antwort des Servers.' }; });
|
|
});
|
|
}
|
|
function supported() {
|
|
return !!(window.PublicKeyCredential && navigator.credentials && window.isSecureContext);
|
|
}
|
|
function friendly(e) {
|
|
if (e && (e.name === 'NotAllowedError' || e.name === 'AbortError')) return 'Abgebrochen oder abgelaufen. Bitte noch einmal versuchen.';
|
|
if (e && e.name === 'InvalidStateError') return 'Dieses Gerät ist schon als Passkey registriert.';
|
|
return (e && e.message) ? e.message : 'Der Passkey konnte nicht verwendet werden.';
|
|
}
|
|
|
|
window.DeskHubPasskey = {
|
|
supported: supported,
|
|
login: function (csrf) {
|
|
return post(csrf, { action: 'login_options' }).then(function (r) {
|
|
if (!r.ok) throw new Error(r.error);
|
|
var o = r.options;
|
|
return navigator.credentials.get({ publicKey: { challenge: b64uToBuf(o.challenge), rpId: o.rpId, timeout: o.timeout, userVerification: o.userVerification, allowCredentials: [] } });
|
|
}).then(function (c) {
|
|
var rs = c.response;
|
|
return post(csrf, { action: 'login_verify', credential: JSON.stringify({ id: c.id, response: {
|
|
clientDataJSON: bufToB64u(rs.clientDataJSON), authenticatorData: bufToB64u(rs.authenticatorData), signature: bufToB64u(rs.signature),
|
|
userHandle: rs.userHandle ? bufToB64u(rs.userHandle) : ''
|
|
} }) });
|
|
}).then(function (r) {
|
|
if (!r.ok) throw new Error(r.error);
|
|
return r;
|
|
}).catch(function (e) { throw new Error(friendly(e)); });
|
|
},
|
|
register: function (csrf, name, password) {
|
|
return post(csrf, { action: 'register_options', password: password }).then(function (r) {
|
|
if (!r.ok) throw new Error(r.error);
|
|
var o = r.options;
|
|
o.challenge = b64uToBuf(o.challenge);
|
|
o.user.id = b64uToBuf(o.user.id);
|
|
o.excludeCredentials = (o.excludeCredentials || []).map(function (c) { return { type: c.type, id: b64uToBuf(c.id) }; });
|
|
return navigator.credentials.create({ publicKey: o });
|
|
}).then(function (c) {
|
|
var rs = c.response;
|
|
return post(csrf, { action: 'register_verify', name: name, credential: JSON.stringify({ id: c.id, response: {
|
|
clientDataJSON: bufToB64u(rs.clientDataJSON), attestationObject: bufToB64u(rs.attestationObject)
|
|
} }) });
|
|
}).then(function (r) {
|
|
if (!r.ok) throw new Error(r.error);
|
|
return r;
|
|
}).catch(function (e) { throw new Error(friendly(e)); });
|
|
}
|
|
};
|
|
})();
|