@aparajita/capacitor-biometric-auth
Version:
Provides access to the native biometric auth & device security APIs for Capacitor apps
327 lines (314 loc) • 13.9 kB
JavaScript
var capacitorBiometricAuth = (function (exports, core, app) {
'use strict';
// noinspection JSUnusedGlobalSymbols
/**
* The type of biometry supported by the device.
*/
exports.BiometryType = void 0;
(function (BiometryType) {
BiometryType[BiometryType["none"] = 0] = "none";
/**
* iOS Touch ID
*/
BiometryType[BiometryType["touchId"] = 1] = "touchId";
/**
* iOS Face ID
*/
BiometryType[BiometryType["faceId"] = 2] = "faceId";
/**
* Android fingerprint authentication
*/
BiometryType[BiometryType["fingerprintAuthentication"] = 3] = "fingerprintAuthentication";
/**
* Android face authentication
*/
BiometryType[BiometryType["faceAuthentication"] = 4] = "faceAuthentication";
/**
* Android iris authentication
*/
BiometryType[BiometryType["irisAuthentication"] = 5] = "irisAuthentication";
})(exports.BiometryType || (exports.BiometryType = {}));
exports.AndroidBiometryStrength = void 0;
(function (AndroidBiometryStrength) {
/**
* `authenticate()` will present any available biometry.
*/
AndroidBiometryStrength[AndroidBiometryStrength["weak"] = 0] = "weak";
/**
* `authenticate()` will only present strong biometry.
*/
AndroidBiometryStrength[AndroidBiometryStrength["strong"] = 1] = "strong";
})(exports.AndroidBiometryStrength || (exports.AndroidBiometryStrength = {}));
/**
* If the `authenticate()` method throws an exception, the `BiometryError`
* instance contains a `.code` property which will contain one of these strings,
* indicating what the error was.
*
* See https://developer.apple.com/documentation/localauthentication/laerror
* for a description of each error code.
*/
exports.BiometryErrorType = void 0;
(function (BiometryErrorType) {
BiometryErrorType["none"] = "";
BiometryErrorType["appCancel"] = "appCancel";
BiometryErrorType["authenticationFailed"] = "authenticationFailed";
BiometryErrorType["invalidContext"] = "invalidContext";
BiometryErrorType["notInteractive"] = "notInteractive";
BiometryErrorType["passcodeNotSet"] = "passcodeNotSet";
BiometryErrorType["systemCancel"] = "systemCancel";
BiometryErrorType["userCancel"] = "userCancel";
BiometryErrorType["userFallback"] = "userFallback";
BiometryErrorType["biometryLockout"] = "biometryLockout";
BiometryErrorType["biometryNotAvailable"] = "biometryNotAvailable";
BiometryErrorType["biometryNotEnrolled"] = "biometryNotEnrolled";
BiometryErrorType["noDeviceCredential"] = "noDeviceCredential";
})(exports.BiometryErrorType || (exports.BiometryErrorType = {}));
/**
* `authenticate()` throws instances of this class.
*/
class BiometryError {
constructor(message, code) {
this.message = message;
this.code = code;
}
}
const kBiometryTypeNameMap = {
[exports.BiometryType.none]: '',
[exports.BiometryType.touchId]: 'Touch ID',
[exports.BiometryType.faceId]: 'Face ID',
[exports.BiometryType.fingerprintAuthentication]: 'Fingerprint Authentication',
[exports.BiometryType.faceAuthentication]: 'Face Authentication',
[exports.BiometryType.irisAuthentication]: 'Iris Authentication',
};
/**
* Return a human-readable name for a BiometryType.
*/
// eslint-disable-next-line import/prefer-default-export
function getBiometryName(type) {
return kBiometryTypeNameMap[type] || '';
}
const proxy = core.registerPlugin('BiometricAuthNative', {
web: async () => Promise.resolve().then(function () { return web; }).then((module) => new module.BiometricAuthWeb()),
ios: async () => Promise.resolve().then(function () { return native; }).then((module) => new module.BiometricAuthNative(proxy)),
android: async () => Promise.resolve().then(function () { return native; }).then((module) => new module.BiometricAuthNative(proxy)),
});
class BiometricAuthBase extends core.WebPlugin {
async authenticate(options) {
try {
await this.internalAuthenticate(options);
}
catch (error) {
// error will be an instance of CapacitorException on native platforms,
// an instance of BiometryError on the web.
if (error instanceof core.CapacitorException) {
throw new BiometryError(error.message, error.code);
}
else {
throw error;
}
}
}
async addResumeListener(listener) {
return app.App.addListener('appStateChange', ({ isActive }) => {
if (isActive) {
this.checkBiometry()
.then((info) => {
listener(info);
})
.catch(console.error);
}
});
}
}
// eslint-disable-next-line import/prefer-default-export
class BiometricAuthWeb extends BiometricAuthBase {
constructor() {
super(...arguments);
this.biometryType = exports.BiometryType.none;
this.biometryTypes = [];
this.biometryIsEnrolled = false;
this.deviceIsSecure = false;
}
// On the web, return the fake biometry set by setBiometryType().
async checkBiometry() {
const hasBiometry = this.biometryType !== exports.BiometryType.none;
const available = hasBiometry && this.biometryIsEnrolled;
let reason = '';
let code = exports.BiometryErrorType.none;
if (!hasBiometry) {
reason = 'No biometry is available';
code = exports.BiometryErrorType.biometryNotAvailable;
}
else if (!this.biometryIsEnrolled) {
reason = 'Biometry is not enrolled';
code = exports.BiometryErrorType.biometryNotEnrolled;
}
return Promise.resolve({
isAvailable: available,
strongBiometryIsAvailable: this.biometryIsEnrolled && this.hasStrongBiometry(),
biometryType: this.biometryType,
biometryTypes: this.biometryTypes,
deviceIsSecure: this.deviceIsSecure,
reason,
code,
});
}
hasStrongBiometry() {
return this.biometryTypes.some((type) => type === exports.BiometryType.faceId ||
type === exports.BiometryType.touchId ||
type === exports.BiometryType.fingerprintAuthentication);
}
/* eslint-disable no-alert */
// On the web, fake authentication with a confirm dialog.
async internalAuthenticate(options) {
const result = await this.checkBiometry();
// First try biometry if available.
if (result.isAvailable) {
if (confirm(
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- we want to use the default value if options?.reason is an empty string
(options === null || options === void 0 ? void 0 : options.reason) ||
`Authenticate with ${result.biometryTypes
.map((type) => getBiometryName(type))
.join(' or ')}?`)) {
return;
}
}
if (options === null || options === void 0 ? void 0 : options.allowDeviceCredential) {
// Either biometry is not available, or the user declined to use it
// and device security is allowed.
if (result.deviceIsSecure) {
if (confirm('Authenticate with device security?')) {
return;
}
else {
throw new BiometryError('User cancelled', exports.BiometryErrorType.userCancel);
}
}
else if (result.isAvailable) {
throw new BiometryError('Device is not secure', exports.BiometryErrorType.noDeviceCredential);
}
}
else if (!result.isAvailable) {
// Biometry is not available and device security is not allowed.
if (result.biometryType === exports.BiometryType.none) {
throw new BiometryError('Biometry is not available', exports.BiometryErrorType.biometryNotAvailable);
}
else {
throw new BiometryError('Biometry is not enrolled', exports.BiometryErrorType.biometryNotEnrolled);
}
}
// The user declined to use biometry and device credentials not allowed.
throw new BiometryError('User cancelled', exports.BiometryErrorType.userCancel);
}
// Web only, used for simulating biometric authentication.
async setBiometryType(type) {
if (type === undefined) {
return Promise.resolve();
}
const types = Array.isArray(type) ? type : [type];
this.biometryTypes = [];
this.biometryType = exports.BiometryType.none;
if (types.length === 0) {
return Promise.resolve();
}
if (isBiometryTypes(types)) {
this.biometryType = types[0];
if (this.biometryType !== exports.BiometryType.none) {
this.biometryTypes = types;
}
}
else {
for (let i = 0; i < types.length; i++) {
// eslint-disable-next-line no-prototype-builtins
if (exports.BiometryType.hasOwnProperty(types[i])) {
const biometryType = exports.BiometryType[types[i]];
if (this.biometryType === exports.BiometryType.none) {
this.biometryTypes = [];
}
else {
this.biometryTypes.push(biometryType);
}
if (i === 0) {
this.biometryType = biometryType;
}
}
}
}
return Promise.resolve();
}
// Web only, used for simulating device unlock security.
async setBiometryIsEnrolled(enrolled) {
this.biometryIsEnrolled = enrolled;
return Promise.resolve();
}
// Web only, used for simulating device unlock security.
async setDeviceIsSecure(isSecure) {
this.deviceIsSecure = isSecure;
return Promise.resolve();
}
}
function isBiometryTypes(value) {
return Object.values(exports.BiometryType).includes(value[0]);
}
var web = /*#__PURE__*/Object.freeze({
__proto__: null,
BiometricAuthWeb: BiometricAuthWeb
});
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/require-await */
// eslint-disable-next-line import/prefer-default-export
class BiometricAuthNative extends BiometricAuthBase {
constructor(capProxy) {
super();
/*
In order to call native methods and maintain the ability to
call pure Javascript methods as well, we have to bind the native methods
to the proxy.
capProxy is a proxy of an instance of this class, so it is safe
to cast it to this class.
*/
const proxy = capProxy;
/* eslint-disable @typescript-eslint/unbound-method */
this.checkBiometry = proxy.checkBiometry;
this.internalAuthenticate = proxy.internalAuthenticate;
/* eslint-enable @typescript-eslint/unbound-method */
}
// @native
async checkBiometry() {
// Never used, but we have to satisfy the compiler.
return Promise.resolve({
isAvailable: false,
strongBiometryIsAvailable: false,
biometryType: exports.BiometryType.none,
biometryTypes: [],
deviceIsSecure: false,
reason: '',
code: exports.BiometryErrorType.none,
strongReason: '',
strongCode: exports.BiometryErrorType.none,
});
}
// @native
// On native platforms, this will present the native authentication UI.
async internalAuthenticate(options) { }
// Web only, used for simulating biometric authentication.
async setBiometryType(type) {
console.warn('setBiometryType() is web only');
}
// Web only, used for simulating biometry enrollment.
async setBiometryIsEnrolled(enrolled) {
console.warn('setBiometryEnrolled() is web only');
}
// Web only, used for simulating device security.
async setDeviceIsSecure(isSecure) {
console.warn('setDeviceIsSecure() is web only');
}
}
var native = /*#__PURE__*/Object.freeze({
__proto__: null,
BiometricAuthNative: BiometricAuthNative
});
exports.BiometricAuth = proxy;
exports.BiometryError = BiometryError;
exports.getBiometryName = getBiometryName;
return exports;
})({}, capacitorExports, app);