padloc/packages/cordova/src/platform.ts

151 lines
5.0 KiB
TypeScript

import { Platform } from "@padloc/core/src/platform";
import { base64ToBytes, bytesToBase64 } from "@padloc/core/src/encoding";
import { WebPlatform } from "@padloc/app/src/lib/platform";
import "cordova-plugin-qrscanner";
import { AuthType } from "@padloc/core/src/auth";
import { PublicKeyAuthClient } from "@padloc/core/src/auth/public-key";
import { StartRegisterAuthenticatorResponse, StartAuthRequestResponse } from "@padloc/core/src/api";
import { appleDeviceNames } from "./apple-device-names";
const cordovaReady = new Promise((resolve) => document.addEventListener("deviceready", resolve));
declare var Fingerprint: any;
declare var cordova: any;
declare var device: any;
declare var plugins: any;
export class CordovaPlatform extends WebPlatform implements Platform {
get supportedAuthTypes() {
return [AuthType.Email, AuthType.Totp, AuthType.PublicKey];
}
async scanQR() {
await cordovaReady;
return new Promise<string>((resolve, reject) => {
QRScanner.scan((err, result) => {
if (err) {
reject(err);
} else {
resolve(result);
}
});
QRScanner.show();
document.body.style.background = document.documentElement.style.background = "transparent";
});
}
async stopScanQR() {
await cordovaReady;
document.body.style.background = document.documentElement.style.background = "";
await QRScanner.destroy();
}
async getDeviceInfo() {
await cordovaReady;
const { manufacturer, model, platform, version: osVersion } = device;
return Object.assign(await super.getDeviceInfo(), {
manufacturer,
model,
platform,
osVersion,
description: appleDeviceNames[model] || model,
runtime: "cordova",
});
}
async setClipboard(val: string): Promise<void> {
await cordovaReady;
return new Promise((resolve, reject) => {
cordova.plugins.clipboard.copy(val, resolve, reject);
});
}
async getClipboard(): Promise<string> {
await cordovaReady;
return new Promise((resolve, reject) => {
cordova.plugins.clipboard.paste(resolve, reject);
});
}
async saveFile(fileName: string, type: string, data: Uint8Array) {
const url = `data:${type};df:${encodeURIComponent(fileName)};base64,${bytesToBase64(data, false)}`;
plugins.socialsharing.share(null, fileName, [url], null);
}
openExternalUrl(url: string) {
cordova.InAppBrowser.open(url, "_system");
}
supportsMFAType(type: AuthType) {
return [AuthType.Email, AuthType.Totp, AuthType.PublicKey].includes(type);
}
biometricKeyStore = {
async isSupported() {
await cordovaReady;
return new Promise<boolean>((resolve) =>
Fingerprint.isAvailable(
(res: string) => resolve(!!res),
() => resolve(false)
)
);
},
async storeKey(_id: string, key: Uint8Array) {
await cordovaReady;
return new Promise<void>((resolve, reject) => {
Fingerprint.registerBiometricSecret(
{
description: "Enable Biometric Unlock",
secret: bytesToBase64(key),
invalidateOnEnrollment: true,
disableBackup: true,
},
() => resolve(),
(error: Error) => reject(error)
);
});
},
async getKey(_id: string) {
await cordovaReady;
return new Promise<Uint8Array>((resolve, reject) => {
Fingerprint.loadBiometricSecret(
{
description: "Biometric Unlock",
disableBackup: true,
},
(key: string) => resolve(base64ToBytes(key)),
(error: Error) => reject(error)
);
});
},
};
private _publicKeyAuthClient = new PublicKeyAuthClient(this.biometricKeyStore);
protected async _prepareRegisterAuthenticator(res: StartRegisterAuthenticatorResponse) {
switch (res.type) {
case AuthType.PublicKey:
return this._publicKeyAuthClient.prepareRegistration(res.data);
default:
return super._prepareRegisterAuthenticator(res);
}
}
protected async _prepareCompleteAuthRequest(res: StartAuthRequestResponse) {
switch (res.type) {
case AuthType.PublicKey:
return this._publicKeyAuthClient.prepareAuthentication(res.data);
default:
return super._prepareCompleteAuthRequest(res);
}
}
readonly platformAuthType = AuthType.PublicKey;
supportsPlatformAuthenticator() {
return this.biometricKeyStore.isSupported();
}
}