anonaddy/resources/js/webauthn.js
2020-12-20 12:24:08 +00:00

239 lines
6.2 KiB
JavaScript
Vendored

/**
* WebAuthn client.
*
* This file is part of asbiin/laravel-webauthn project.
*
* @copyright Alexis SAETTLER © 2019
* @license MIT
*/
'use strict'
/**
* Create a new instance of WebAuthn.
*
* @param {function(string, bool)} notifyCallback
* @constructor
*/
function WebAuthn(notifyCallback = null) {
if (notifyCallback) {
this.setNotify(notifyCallback)
}
}
/**
* Register a new key.
*
* @param {PublicKeyCredentialCreationOptions} publicKey - see https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialcreationoptions
* @param {function(PublicKeyCredential)} callback User callback
*/
WebAuthn.prototype.register = function (publicKey, callback) {
let publicKeyCredential = Object.assign({}, publicKey)
publicKeyCredential.user.id = this._bufferDecode(publicKey.user.id)
publicKeyCredential.challenge = this._bufferDecode(this._base64Decode(publicKey.challenge))
if (publicKey.excludeCredentials) {
publicKeyCredential.excludeCredentials = this._credentialDecode(publicKey.excludeCredentials)
}
var self = this
navigator.credentials
.create({
publicKey: publicKeyCredential,
})
.then(
data => {
self._registerCallback(data, callback)
},
error => {
self._notify(error.name, error.message, false)
}
)
}
/**
* Register callback on register key.
*
* @param {PublicKeyCredential} publicKey @see https://www.w3.org/TR/webauthn/#publickeycredential
* @param {function(PublicKeyCredential)} callback User callback
*/
WebAuthn.prototype._registerCallback = function (publicKey, callback) {
let publicKeyCredential = {
id: publicKey.id,
type: publicKey.type,
rawId: this._bufferEncode(publicKey.rawId),
response: {
/** @see https://www.w3.org/TR/webauthn/#authenticatorattestationresponse */
clientDataJSON: this._bufferEncode(publicKey.response.clientDataJSON),
attestationObject: this._bufferEncode(publicKey.response.attestationObject),
},
}
callback(publicKeyCredential)
}
/**
* Authenticate a user.
*
* @param {PublicKeyCredentialRequestOptions} publicKey - see https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialrequestoptions
* @param {function(PublicKeyCredential)} callback User callback
*/
WebAuthn.prototype.sign = function (publicKey, callback) {
let publicKeyCredential = Object.assign({}, publicKey)
publicKeyCredential.challenge = this._bufferDecode(this._base64Decode(publicKey.challenge))
if (publicKey.allowCredentials) {
publicKeyCredential.allowCredentials = this._credentialDecode(publicKey.allowCredentials)
}
var self = this
navigator.credentials
.get({
publicKey: publicKeyCredential,
})
.then(
data => {
self._signCallback(data, callback)
},
error => {
self._notify(error.name, error.message, false)
}
)
}
/**
* Sign callback on authenticate.
*
* @param {PublicKeyCredential} publicKey @see https://www.w3.org/TR/webauthn/#publickeycredential
* @param {function(PublicKeyCredential)} callback User callback
*/
WebAuthn.prototype._signCallback = function (publicKey, callback) {
let publicKeyCredential = {
id: publicKey.id,
type: publicKey.type,
rawId: this._bufferEncode(publicKey.rawId),
response: {
/** @see https://www.w3.org/TR/webauthn/#iface-authenticatorassertionresponse */
authenticatorData: this._bufferEncode(publicKey.response.authenticatorData),
clientDataJSON: this._bufferEncode(publicKey.response.clientDataJSON),
signature: this._bufferEncode(publicKey.response.signature),
userHandle: publicKey.response.userHandle
? this._bufferEncode(publicKey.response.userHandle)
: null,
},
}
callback(publicKeyCredential)
}
/**
* Buffer encode.
*
* @param {ArrayBuffer} value
* @return {string}
*/
WebAuthn.prototype._bufferEncode = function (value) {
return window.btoa(String.fromCharCode.apply(null, new Uint8Array(value)))
}
/**
* Buffer decode.
*
* @param {ArrayBuffer} value
* @return {string}
*/
WebAuthn.prototype._bufferDecode = function (value) {
var t = window.atob(value)
return Uint8Array.from(t, c => c.charCodeAt(0))
}
/**
* Convert a base64url to a base64 string.
*
* @param {string} input
* @return {string}
*/
WebAuthn.prototype._base64Decode = function (input) {
// Replace non-url compatible chars with base64 standard chars
input = input.replace(/-/g, '+').replace(/_/g, '/')
// Pad out with standard base64 required padding characters
const pad = input.length % 4
if (pad) {
if (pad === 1) {
throw new Error(
'InvalidLengthError: Input base64url string is the wrong length to determine padding'
)
}
input += new Array(5 - pad).join('=')
}
return input
}
/**
* Credential decode.
*
* @param {PublicKeyCredentialDescriptor} credentials
* @return {PublicKeyCredentialDescriptor}
*/
WebAuthn.prototype._credentialDecode = function (credentials) {
var self = this
return credentials.map(function (data) {
return {
id: self._bufferDecode(self._base64Decode(data.id)),
type: data.type,
transports: data.transports,
}
})
}
/**
* Test is WebAuthn is supported by this navigator.
*
* @return {bool}
*/
WebAuthn.prototype.webAuthnSupport = function () {
return !(
window.PublicKeyCredential === undefined ||
typeof window.PublicKeyCredential !== 'function' ||
typeof window.PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable !== 'function'
)
}
/**
* Get the message in case WebAuthn is not supported.
*
* @return {string}
*/
WebAuthn.prototype.notSupportedMessage = function () {
if (
!window.isSecureContext &&
window.location.hostname !== 'localhost' &&
window.location.hostname !== '127.0.0.1'
) {
return 'not_secured'
}
return 'not_supported'
}
/**
* Call the notify callback.
*
* @param {string} message
* @param {bool} isError
*/
WebAuthn.prototype._notify = function (message, isError) {
if (this._notifyCallback) {
this._notifyCallback(message, isError)
}
}
/**
* Set the notify callback.
*
* @param {function(name: string, message: string, isError: bool)} callback
*/
WebAuthn.prototype.setNotify = function (callback) {
this._notifyCallback = callback
}
window.WebAuthn = WebAuthn