1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-20 10:13:31 +00:00

Merge branch 'master' into copy-totp-on-auto-fill

This commit is contained in:
Thomas Rittson
2021-05-05 12:23:37 +10:00
committed by GitHub
123 changed files with 19534 additions and 10368 deletions

View File

@@ -20,6 +20,7 @@ import {
} from 'jslib/abstractions';
import { EventService } from 'jslib/abstractions/event.service';
import { CipherRepromptType } from 'jslib/enums/cipherRepromptType';
import { EventType } from 'jslib/enums/eventType';
const CardAttributes: string[] = ['autoCompleteType', 'data-stripe', 'htmlName', 'htmlID', 'label-tag',
@@ -254,9 +255,13 @@ export default class AutofillService implements AutofillServiceInterface {
}
}
if (cipher.reprompt !== CipherRepromptType.None) {
return;
}
const copyTotpOnAutoFill = await this.totpService.isAutoCopyOnAutoFillEnabled();
const shouldCopyTotp = fromCommand || copyTotpOnAutoFill;
const totpCode = await this.doAutoFill({
cipher: cipher,
pageDetails: pageDetails,

View File

@@ -6,16 +6,14 @@ import { DeviceType } from 'jslib/enums/deviceType';
import { MessagingService } from 'jslib/abstractions/messaging.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { AnalyticsIds } from 'jslib/misc/analytics';
const DialogPromiseExpiration = 600000; // 10 minutes
export default class BrowserPlatformUtilsService implements PlatformUtilsService {
identityClientId: string = 'browser';
private showDialogResolves = new Map<number, { resolve: (value: boolean) => void, date: Date }>();
private passwordDialogResolves = new Map<number, { tryResolve: (canceled: boolean, password: string) => Promise<boolean>, date: Date }>();
private deviceCache: DeviceType = null;
private analyticsIdCache: string = null;
private prefersColorSchemeDark = window.matchMedia('(prefers-color-scheme: dark)');
constructor(private messagingService: MessagingService,
@@ -82,15 +80,6 @@ export default class BrowserPlatformUtilsService implements PlatformUtilsService
return false;
}
analyticsId(): string {
if (this.analyticsIdCache) {
return this.analyticsIdCache;
}
this.analyticsIdCache = (AnalyticsIds as any)[this.getDevice()];
return this.analyticsIdCache;
}
async isViewOpen(): Promise<boolean> {
if (await BrowserApi.isPopupOpen()) {
return true;
@@ -122,16 +111,12 @@ export default class BrowserPlatformUtilsService implements PlatformUtilsService
BrowserApi.downloadFile(win, blobData, blobOptions, fileName);
}
getApplicationVersion(): string {
return BrowserApi.getApplicationVersion();
getApplicationVersion(): Promise<string> {
return Promise.resolve(BrowserApi.getApplicationVersion());
}
supportsU2f(win: Window): boolean {
if (win != null && (win as any).u2f != null) {
return true;
}
return this.isChrome() || this.isOpera() || this.isVivaldi();
supportsWebAuthn(win: Window): boolean {
return (typeof(PublicKeyCredential) !== 'undefined');
}
supportsDuo(): boolean {
@@ -148,10 +133,12 @@ export default class BrowserPlatformUtilsService implements PlatformUtilsService
});
}
showDialog(text: string, title?: string, confirmText?: string, cancelText?: string, type?: string) {
showDialog(body: string, title?: string, confirmText?: string, cancelText?: string, type?: string,
bodyIsHtml: boolean = false) {
const dialogId = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER);
this.messagingService.send('showDialog', {
text: text,
text: bodyIsHtml ? null : body,
html: bodyIsHtml ? body : null,
title: title,
confirmText: confirmText,
cancelText: cancelText,
@@ -163,11 +150,30 @@ export default class BrowserPlatformUtilsService implements PlatformUtilsService
});
}
eventTrack(action: string, label?: string, options?: any) {
this.messagingService.send('analyticsEventTrack', {
action: action,
label: label,
options: options,
async showPasswordDialog(title: string, body: string, passwordValidation: (value: string) => Promise<boolean>) {
const dialogId = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER);
this.messagingService.send('showPasswordDialog', {
title: title,
body: body,
dialogId: dialogId,
});
return new Promise<boolean>(resolve => {
this.passwordDialogResolves.set(dialogId, {
tryResolve: async (canceled: boolean, password: string) => {
if (canceled) {
resolve(false);
return false;
}
if (await passwordValidation(password)) {
resolve(true);
return true;
}
},
date: new Date(),
});
});
}
@@ -278,20 +284,41 @@ export default class BrowserPlatformUtilsService implements PlatformUtilsService
}
// Clean up old promises
const deleteIds: number[] = [];
this.showDialogResolves.forEach((val, key) => {
const age = new Date().getTime() - val.date.getTime();
if (age > DialogPromiseExpiration) {
deleteIds.push(key);
this.showDialogResolves.delete(key);
}
});
deleteIds.forEach(id => {
this.showDialogResolves.delete(id);
});
}
supportsBiometric() {
return Promise.resolve(!this.isFirefox() && !this.isSafari());
async resolvePasswordDialogPromise(dialogId: number, canceled: boolean, password: string): Promise<boolean> {
let result = false;
if (this.passwordDialogResolves.has(dialogId)) {
const resolveObj = this.passwordDialogResolves.get(dialogId);
if (await resolveObj.tryResolve(canceled, password)) {
this.passwordDialogResolves.delete(dialogId);
result = true;
}
}
// Clean up old promises
this.passwordDialogResolves.forEach((val, key) => {
const age = new Date().getTime() - val.date.getTime();
if (age > DialogPromiseExpiration) {
this.passwordDialogResolves.delete(key);
}
});
return result;
}
async supportsBiometric() {
if (this.isFirefox()) {
return parseInt((await browser.runtime.getBrowserInfo()).version.split('.')[0], 10) >= 87;
}
return true;
}
authenticateBiometric() {
@@ -312,8 +339,8 @@ export default class BrowserPlatformUtilsService implements PlatformUtilsService
return false;
}
getDefaultSystemTheme() {
return this.prefersColorSchemeDark.matches ? 'dark' : 'light';
getDefaultSystemTheme(): Promise<'light' | 'dark'> {
return Promise.resolve(this.prefersColorSchemeDark.matches ? 'dark' : 'light');
}
onDefaultSystemThemeChange(callback: ((theme: 'light' | 'dark') => unknown)) {

View File

@@ -22,7 +22,7 @@ export default class BrowserStorageService implements StorageService {
async save(key: string, obj: any): Promise<any> {
if (obj == null) {
// Fix safari not liking null in set
return new Promise(resolve => {
return new Promise<void>(resolve => {
this.chromeStorageApi.remove(key, () => {
resolve();
});
@@ -30,7 +30,7 @@ export default class BrowserStorageService implements StorageService {
}
const keyedObj = { [key]: obj };
return new Promise(resolve => {
return new Promise<void>(resolve => {
this.chromeStorageApi.set(keyedObj, () => {
resolve();
});
@@ -38,7 +38,7 @@ export default class BrowserStorageService implements StorageService {
}
async remove(key: string): Promise<any> {
return new Promise(resolve => {
return new Promise<void>(resolve => {
this.chromeStorageApi.remove(key, () => {
resolve();
});

View File

@@ -0,0 +1,29 @@
import { VaultTimeoutService as BaseVaultTimeoutService } from 'jslib/services/vaultTimeout.service';
import { SafariApp } from '../browser/safariApp';
export default class VaultTimeoutService extends BaseVaultTimeoutService {
startCheck() {
this.checkVaultTimeout();
if (this.platformUtilsService.isSafari()) {
this.checkSafari();
} else {
setInterval(() => this.checkVaultTimeout(), 10 * 1000); // check every 10 seconds
}
}
// This is a work-around to safari adding an arbitary delay to setTimeout and
// setIntervals. It works by calling the native extension which sleeps for 10s,
// efficiently replicating setInterval.
async checkSafari() {
while (true) {
try {
await SafariApp.sendMessageToApp('sleep');
this.checkVaultTimeout();
} catch (e) {
// tslint:disable-next-line
console.log('Exception Safari VaultTimeout', e);
}
}
}
}