1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-11-18 11:05:41 +01:00

Merge branch 'master' of github.com:bitwarden/browser into feature/safari-webext

# Conflicts:
#	src/browser/safariApp.ts
#	src/safari/safari/SafariExtensionViewController.swift
#	src/services/browserPlatformUtils.service.ts
This commit is contained in:
Hinton 2021-01-13 14:21:45 +01:00
commit db59f2791a
21 changed files with 202 additions and 71 deletions

2
jslib

@ -1 +1 @@
Subproject commit 72bf18f369068d36767794bdc0ca377f734cf373
Subproject commit cea09a22e533ef3598bb497ba0503c2fcd5b2dc1

11
package-lock.json generated
View File

@ -1784,9 +1784,9 @@
"dev": true
},
"bl": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/bl/-/bl-2.2.0.tgz",
"integrity": "sha512-wbgvOpqopSr7uq6fJrLH8EsvYMJf9gzfo2jCsL2eTy75qXPukA4pCgHamOQkZtY5vmfVtjB+P3LNlMHW5CEZXA==",
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz",
"integrity": "sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==",
"requires": {
"readable-stream": "^2.3.5",
"safe-buffer": "^5.1.1"
@ -1894,6 +1894,11 @@
"integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=",
"dev": true
},
"browser-process-hrtime": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz",
"integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow=="
},
"browser-resolve": {
"version": "1.11.3",
"resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.3.tgz",

View File

@ -94,6 +94,7 @@
"angular2-toaster": "8.0.0",
"angulartics2": "9.1.0",
"big-integer": "1.6.36",
"browser-process-hrtime": "1.0.0",
"core-js": "2.6.2",
"duo_web_sdk": "git+https://github.com/duosecurity/duo_web_sdk.git",
"font-awesome": "4.7.0",

View File

@ -606,6 +606,9 @@
"message": "WARNING",
"description": "WARNING (should stay in capitalized letters if the language permits)"
},
"confirmVaultExport": {
"message": "Confirm Vault Export"
},
"exportWarningDesc": {
"message": "This export contains your vault data in an unencrypted format. You should not store or send the exported file over unsecure channels (such as email). Delete it immediately after you are done using it."
},
@ -1381,6 +1384,9 @@
"privacyPolicy": {
"message": "Privacy Policy"
},
"hintEqualsPassword": {
"message": "Your password hint cannot be the same as your password."
},
"ok": {
"message": "Ok"
},
@ -1412,9 +1418,24 @@
"message": "Desktop application invalidated the secure communication channel. Please retry this operation"
},
"nativeMessagingInvalidEncryptionTitle": {
"message": "Desktop communication interupted"
"message": "Desktop communication interrupted"
},
"biometricsNotEnabledTitle": {
"message": "Biometrics not enabled"
},
"biometricsNotEnabledDesc": {
"message": "Browser biometrics requires desktop biometric to be enabled in the settings first."
},
"biometricsNotSupportedTitle": {
"message": "Biometrics not supported"
},
"biometricsNotSupportedDesc": {
"message": "Browser biometrics is not supported on this device."
},
"personalOwnershipSubmitError": {
"message": "Due to an Enterprise Policy, you are restricted from saving items to your personal vault. Change the Ownership option to an organization and choose from available Collections."
},
"personalOwnershipPolicyInEffect": {
"message": "An organization policy is affecting your ownership options."
}
}

View File

@ -21,6 +21,7 @@ import {
UserService,
VaultTimeoutService,
} from 'jslib/services';
import { ConsoleLogService } from 'jslib/services/consoleLog.service';
import { EventService } from 'jslib/services/event.service';
import { ExportService } from 'jslib/services/export.service';
import { NotificationsService } from 'jslib/services/notifications.service';
@ -93,6 +94,7 @@ export default class MainBackground {
i18nService: I18nServiceAbstraction;
platformUtilsService: PlatformUtilsServiceAbstraction;
constantsService: ConstantsService;
consoleLogService: ConsoleLogService;
cryptoService: CryptoServiceAbstraction;
cryptoFunctionService: CryptoFunctionServiceAbstraction;
tokenService: TokenServiceAbstraction;
@ -169,8 +171,9 @@ export default class MainBackground {
this.secureStorageService = new BrowserStorageService();
this.i18nService = new I18nService(BrowserApi.getUILanguage(window));
this.cryptoFunctionService = new WebCryptoFunctionService(window, this.platformUtilsService);
this.consoleLogService = new ConsoleLogService(false);
this.cryptoService = new CryptoService(this.storageService, this.secureStorageService,
this.cryptoFunctionService, this.platformUtilsService);
this.cryptoFunctionService, this.platformUtilsService, this.consoleLogService);
this.tokenService = new TokenService(this.storageService);
this.appIdService = new AppIdService(this.storageService);
this.apiService = new ApiService(this.tokenService, this.platformUtilsService,
@ -178,7 +181,7 @@ export default class MainBackground {
this.userService = new UserService(this.tokenService, this.storageService);
this.authService = new AuthService(this.cryptoService, this.apiService, this.userService,
this.tokenService, this.appIdService, this.i18nService, this.platformUtilsService,
this.messagingService, this.vaultTimeoutService);
this.messagingService, this.vaultTimeoutService, this.consoleLogService);
this.settingsService = new SettingsService(this.userService, this.storageService);
this.cipherService = new CipherService(this.cryptoService, this.userService, this.settingsService,
this.apiService, this.storageService, this.i18nService, () => this.searchService);
@ -186,7 +189,7 @@ export default class MainBackground {
this.storageService, this.i18nService, this.cipherService);
this.collectionService = new CollectionService(this.cryptoService, this.userService, this.storageService,
this.i18nService);
this.searchService = new SearchService(this.cipherService);
this.searchService = new SearchService(this.cipherService, this.consoleLogService);
this.sendService = new SendService(this.cryptoService, this.userService, this.apiService, this.storageService,
this.i18nService, this.cryptoFunctionService);
this.stateService = new StateService();
@ -220,7 +223,7 @@ export default class MainBackground {
this.auditService = new AuditService(this.cryptoFunctionService, this.apiService);
this.exportService = new ExportService(this.folderService, this.cipherService, this.apiService);
this.notificationsService = new NotificationsService(this.userService, this.syncService, this.appIdService,
this.apiService, this.vaultTimeoutService, () => this.logout(true));
this.apiService, this.vaultTimeoutService, () => this.logout(true), this.consoleLogService);
this.environmentService = new EnvironmentService(this.apiService, this.storageService,
this.notificationsService);
this.analytics = new Analytics(window, () => BrowserApi.gaFilter(), this.platformUtilsService,
@ -245,7 +248,7 @@ export default class MainBackground {
this.analytics, this.notificationsService, this.systemService, this.vaultTimeoutService,
this.environmentService, this.policyService, this.userService);
this.nativeMessagingBackground = new NativeMessagingBackground(this.storageService, this.cryptoService, this.cryptoFunctionService,
this.vaultTimeoutService, this.runtimeBackground, this.i18nService, this.userService, this.messagingService);
this.vaultTimeoutService, this.runtimeBackground, this.i18nService, this.userService, this.messagingService, this.appIdService);
this.commandsBackground = new CommandsBackground(this, this.passwordGenerationService,
this.platformUtilsService, this.analytics, this.vaultTimeoutService);

View File

@ -1,4 +1,5 @@
import { ConstantsService } from 'jslib/services/constants.service';
import { AppIdService } from 'jslib/abstractions/appId.service';
import { CryptoFunctionService } from 'jslib/abstractions/cryptoFunction.service';
import { CryptoService } from 'jslib/abstractions/crypto.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
@ -23,15 +24,19 @@ export class NativeMessagingBackground {
private resolver: any = null;
private privateKey: ArrayBuffer = null;
private publicKey: ArrayBuffer = null;
private secureSetupResolve: any = null;
private sharedSecret: SymmetricCryptoKey;
private appId: string;
constructor(private storageService: StorageService, private cryptoService: CryptoService,
private cryptoFunctionService: CryptoFunctionService, private vaultTimeoutService: VaultTimeoutService,
private runtimeBackground: RuntimeBackground, private i18nService: I18nService, private userService: UserService,
private messagingService: MessagingService) {}
private messagingService: MessagingService, private appIdService: AppIdService) {}
async connect() {
this.appId = await this.appIdService.getAppId();
return new Promise((resolve, reject) => {
this.port = BrowserApi.connectNative('com.8bit.bitwarden');
@ -58,6 +63,11 @@ export class NativeMessagingBackground {
this.port.disconnect();
break;
case 'setupEncryption':
// Ignore since it belongs to another device
if (message.appId !== this.appId) {
return;
}
const encrypted = Utils.fromB64ToArray(message.sharedSecret);
const decrypted = await this.cryptoFunctionService.rsaDecrypt(encrypted.buffer, this.privateKey, EncryptionAlgorithm);
@ -65,6 +75,11 @@ export class NativeMessagingBackground {
this.secureSetupResolve();
break;
case 'invalidateEncryption':
// Ignore since it belongs to another device
if (message.appId !== this.appId) {
return;
}
this.sharedSecret = null;
this.privateKey = null;
this.connected = false;
@ -75,8 +90,20 @@ export class NativeMessagingBackground {
confirmText: this.i18nService.t('ok'),
type: 'error',
});
break;
case 'verifyFingerprint': {
if (this.sharedSecret == null) {
this.showFingerprintDialog();
}
break;
}
default:
this.onMessage(message);
// Ignore since it belongs to another device
if (message.appId !== this.appId) {
return;
}
this.onMessage(message.message);
}
});
@ -88,9 +115,7 @@ export class NativeMessagingBackground {
error = chrome.runtime.lastError.message;
}
if (error === 'Specified native messaging host not found.' ||
error === 'Access to the specified native messaging host is forbidden.' ||
error === 'An unexpected error occurred') {
if (error != null) {
this.messagingService.send('showDialog', {
text: this.i18nService.t('desktopIntegrationDisabledDesc'),
title: this.i18nService.t('desktopIntegrationDisabledTitle'),
@ -118,7 +143,7 @@ export class NativeMessagingBackground {
message.timestamp = Date.now();
const encrypted = await this.cryptoService.encrypt(JSON.stringify(message), this.sharedSecret);
this.port.postMessage(encrypted);
this.postMessage({appId: this.appId, message: encrypted});
}
getResponse(): Promise<any> {
@ -127,6 +152,27 @@ export class NativeMessagingBackground {
});
}
private postMessage(message: any) {
// Wrap in try-catch to when the port disconnected without triggering `onDisconnect`.
try {
this.port.postMessage(message);
} catch (e) {
// tslint:disable-next-line
console.error("NativeMessaging port disconnected, disconnecting.");
this.sharedSecret = null;
this.privateKey = null;
this.connected = false;
this.messagingService.send('showDialog', {
text: this.i18nService.t('nativeMessagingInvalidEncryptionDesc'),
title: this.i18nService.t('nativeMessagingInvalidEncryptionTitle'),
confirmText: this.i18nService.t('ok'),
type: 'error',
});
}
}
private async onMessage(rawMessage: any) {
const message = JSON.parse(await this.cryptoService.decryptToUtf8(rawMessage, this.sharedSecret));
@ -140,6 +186,24 @@ export class NativeMessagingBackground {
case 'biometricUnlock':
await this.storageService.remove(ConstantsService.biometricAwaitingAcceptance);
if (message.response === 'not enabled') {
this.messagingService.send('showDialog', {
text: this.i18nService.t('biometricsNotEnabledDesc'),
title: this.i18nService.t('biometricsNotEnabledTitle'),
confirmText: this.i18nService.t('ok'),
type: 'error',
});
break;
} else if (message.response === 'not supported') {
this.messagingService.send('showDialog', {
text: this.i18nService.t('biometricsNotSupportedDesc'),
title: this.i18nService.t('biometricsNotSupportedTitle'),
confirmText: this.i18nService.t('ok'),
type: 'error',
});
break;
}
const enabled = await this.storageService.get(ConstantsService.biometricUnlockKey);
if (enabled === null || enabled === false) {
if (message.response === 'unlocked') {
@ -171,17 +235,10 @@ export class NativeMessagingBackground {
private async secureCommunication() {
const [publicKey, privateKey] = await this.cryptoFunctionService.rsaGenerateKeyPair(2048);
this.publicKey = publicKey;
this.privateKey = privateKey;
this.sendUnencrypted({command: 'setupEncryption', publicKey: Utils.fromBufferToB64(publicKey)});
const fingerprint = (await this.cryptoService.getFingerprint(await this.userService.getUserId(), publicKey)).join(' ');
this.messagingService.send('showDialog', {
html: `${this.i18nService.t('desktopIntegrationVerificationText')}<br><br><strong>${fingerprint}</strong>`,
title: this.i18nService.t('desktopSyncVerificationTitle'),
confirmText: this.i18nService.t('ok'),
type: 'warning',
});
return new Promise((resolve, reject) => this.secureSetupResolve = resolve);
}
@ -193,6 +250,17 @@ export class NativeMessagingBackground {
message.timestamp = Date.now();
this.port.postMessage(message);
this.postMessage({appId: this.appId, message: message});
}
private async showFingerprintDialog() {
const fingerprint = (await this.cryptoService.getFingerprint(await this.userService.getUserId(), this.publicKey)).join(' ');
this.messagingService.send('showDialog', {
html: `${this.i18nService.t('desktopIntegrationVerificationText')}<br><br><strong>${fingerprint}</strong>`,
title: this.i18nService.t('desktopSyncVerificationTitle'),
confirmText: this.i18nService.t('ok'),
type: 'warning',
});
}
}

View File

@ -455,7 +455,7 @@ export default class RuntimeBackground {
if (policy.enabled) {
const org = await this.userService.getOrganization(policy.organizationId);
if (org != null && org.enabled && org.usePolicies && !org.isAdmin
&& org.status == OrganizationUserStatusType.Confirmed) {
&& org.status === OrganizationUserStatusType.Confirmed) {
return false;
}
}

View File

@ -41,11 +41,11 @@ export class SsoComponent extends BaseSsoComponent {
this.redirectUri = url + '/sso-connector.html';
this.clientId = 'browser';
super.onSuccessfulLogin = () => {
super.onSuccessfulLogin = async () => {
await syncService.fullSync(true);
BrowserApi.reloadOpenWindows();
const thisWindow = window.open('', '_self');
thisWindow.close();
return syncService.fullSync(true);
};
}
}

View File

@ -150,6 +150,9 @@ export const routerTransition = trigger('routerTransition', [
transition('view-cipher => clone-cipher', inSlideUp),
transition('clone-cipher => view-cipher, clone-cipher => tabs', outSlideDown),
transition('view-cipher => share-cipher', inSlideUp),
transition('share-cipher => view-cipher', outSlideDown),
transition('tabs => add-cipher', inSlideUp),
transition('add-cipher => tabs', outSlideDown),
@ -159,9 +162,6 @@ export const routerTransition = trigger('routerTransition', [
transition('add-cipher => generator, edit-cipher => generator, clone-cipher => generator', inSlideUp),
transition('generator => add-cipher, generator => edit-cipher, generator => clone-cipher', outSlideDown),
transition('edit-cipher => share-cipher', inSlideUp),
transition('share-cipher => edit-cipher, share-cipher => view-cipher', outSlideDown),
transition('edit-cipher => attachments, edit-cipher => collections', inSlideLeft),
transition('attachments => edit-cipher, collections => edit-cipher', outSlideRight),

View File

@ -16,6 +16,11 @@
[ngClass]="{disabled: (!cipher.login.password || !cipher.viewPassword)}">
<i class="fa fa-lg fa-key" aria-hidden="true"></i>
</span>
<span class="row-btn" appStopClick appStopProp appA11yTitle="{{'copyVerificationCode' | i18n}}"
(click)="copy(cipher, cipher.login.totp, 'verificationCodeTotp', 'TOTP')"
[ngClass]="{disabled: (!displayTotpCopyButton(cipher))}">
<i class="fa fa-lg fa-clock-o" aria-hidden="true"></i>
</span>
</ng-container>
<ng-container *ngIf="cipher.type === cipherType.Card">
<span class="row-btn" appStopClick appStopProp appA11yTitle="{{'copyNumber' | i18n}}"

View File

@ -18,6 +18,8 @@ import { CipherView } from 'jslib/models/view/cipherView';
import { EventService } from 'jslib/abstractions/event.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { TotpService } from 'jslib/abstractions/totp.service';
import { UserService } from 'jslib/abstractions/user.service';
import { PopupUtilsService } from '../services/popup-utils.service';
@ -31,10 +33,16 @@ export class ActionButtonsComponent {
@Input() showView = false;
cipherType = CipherType;
userHasPremiumAccess = false;
constructor(private analytics: Angulartics2, private toasterService: ToasterService,
private i18nService: I18nService, private platformUtilsService: PlatformUtilsService,
private popupUtilsService: PopupUtilsService, private eventService: EventService) { }
private popupUtilsService: PopupUtilsService, private eventService: EventService,
private totpService: TotpService, private userService: UserService) { }
async ngOnInit() {
this.userHasPremiumAccess = await this.userService.canAccessPremium();
}
launch() {
if (this.cipher.type !== CipherType.Login || !this.cipher.login.canLaunch) {
@ -48,9 +56,11 @@ export class ActionButtonsComponent {
}
}
copy(cipher: CipherView, value: string, typeI18nKey: string, aType: string) {
if (value == null) {
async copy(cipher: CipherView, value: string, typeI18nKey: string, aType: string) {
if (value == null || aType === 'TOTP' && !this.displayTotpCopyButton(cipher)) {
return;
} else if (value === cipher.login.totp) {
value = await this.totpService.getCode(value);
}
if (!cipher.viewPassword) {
@ -62,13 +72,18 @@ export class ActionButtonsComponent {
this.toasterService.popAsync('info', null,
this.i18nService.t('valueCopied', this.i18nService.t(typeI18nKey)));
if (typeI18nKey === 'password') {
if (typeI18nKey === 'password' || typeI18nKey === 'verificationCodeTotp') {
this.eventService.collect(EventType.Cipher_ClientToggledHiddenFieldVisible, cipher.id);
} else if (typeI18nKey === 'securityCode') {
this.eventService.collect(EventType.Cipher_ClientCopiedCardCode, cipher.id);
}
}
displayTotpCopyButton(cipher: CipherView) {
return (cipher?.login?.hasTotp ?? false) &&
(cipher.organizationUseTotp || this.userHasPremiumAccess);
}
view() {
this.onView.emit(this.cipher);
}

View File

@ -1,10 +1,12 @@
import { CipherService } from 'jslib/abstractions/cipher.service';
import { ConsoleLogService } from 'jslib/services/consoleLog.service';
import { SearchService } from 'jslib/services/search.service';
export class PopupSearchService extends SearchService {
constructor(private mainSearchService: SearchService, cipherService: CipherService) {
super(cipherService);
constructor(private mainSearchService: SearchService, cipherService: CipherService,
consoleLogService: ConsoleLogService) {
super(cipherService, consoleLogService);
}
clearIndex() {

View File

@ -50,6 +50,7 @@ import { AuthService } from 'jslib/services/auth.service';
import { ConstantsService } from 'jslib/services/constants.service';
import { SearchService } from 'jslib/services/search.service';
import { StateService } from 'jslib/services/state.service';
import { ConsoleLogService } from 'jslib/services/consoleLog.service';
import { Analytics } from 'jslib/misc/analytics';
@ -69,11 +70,11 @@ export const authService = new AuthService(getBgService<CryptoService>('cryptoSe
getBgService<ApiService>('apiService')(), getBgService<UserService>('userService')(),
getBgService<TokenService>('tokenService')(), getBgService<AppIdService>('appIdService')(),
getBgService<I18nService>('i18nService')(), getBgService<PlatformUtilsService>('platformUtilsService')(),
messagingService, getBgService<VaultTimeoutService>('vaultTimeoutService')());
messagingService, getBgService<VaultTimeoutService>('vaultTimeoutService')(), getBgService<ConsoleLogService>('consoleLogService')());
export const searchService = new PopupSearchService(getBgService<SearchService>('searchService')(),
getBgService<CipherService>('cipherService')());
getBgService<CipherService>('cipherService')(), getBgService<ConsoleLogService>('consoleLogService')());
export function initFactory(i18nService: I18nService, storageService: StorageService,
export function initFactory(platformUtilsService: PlatformUtilsService, i18nService: I18nService, storageService: StorageService,
popupUtilsService: PopupUtilsService): Function {
return async () => {
if (!popupUtilsService.inPopup(window)) {
@ -90,7 +91,12 @@ export function initFactory(i18nService: I18nService, storageService: StorageSer
let theme = await storageService.get<string>(ConstantsService.themeKey);
if (theme == null) {
theme = 'light';
theme = platformUtilsService.getDefaultSystemTheme();
platformUtilsService.onDefaultSystemThemeChange((theme) => {
window.document.documentElement.classList.remove('theme_light', 'theme_dark');
window.document.documentElement.classList.add('theme_' + theme);
});
}
window.document.documentElement.classList.add('locale_' + i18nService.translationLocale);
window.document.documentElement.classList.add('theme_' + theme);
@ -171,7 +177,7 @@ export function initFactory(i18nService: I18nService, storageService: StorageSer
{
provide: APP_INITIALIZER,
useFactory: initFactory,
deps: [I18nService, StorageService, PopupUtilsService],
deps: [PlatformUtilsService, I18nService, StorageService, PopupUtilsService],
multi: true,
},
{

View File

@ -41,13 +41,6 @@
</div>
<div class="box-footer">
<p>{{'exportMasterPassword' | i18n}}</p>
<strong>{{'warning' | i18n}}</strong>:
<span *ngIf="!encryptedFormat">
{{'exportWarningDesc' | i18n}}
</span>
<span *ngIf="encryptedFormat">
{{'encExportWarningDesc' | i18n}}
</span>
</div>
</div>
</content>

View File

@ -14,6 +14,9 @@
</div>
</header>
<content *ngIf="cipher">
<app-callout type="info" *ngIf="allowOwnershipOptions() && !allowPersonal">
{{'personalOwnershipPolicyInEffect' | i18n}}
</app-callout>
<div class="box">
<div class="box-header">
{{'itemInformation' | i18n}}
@ -374,15 +377,6 @@
</div>
<div class="box list" *ngIf="editMode && !cloneMode">
<div class="box-content single-line">
<a class="box-content-row" href="#" appStopClick appBlurClick (click)="share()"
*ngIf="!cipher.organizationId">
<div class="row-main text-primary">
<div class="icon text-primary" aria-hidden="true">
<i class="fa fa-share-alt fa-lg fa-fw"></i>
</div>
<span>{{'shareItem' | i18n}}</span>
</div>
</a>
<a class="box-content-row" href="#" appStopClick appBlurClick (click)="delete()"
[appApiAction]="deletePromise" #deleteBtn>
<div class="row-main text-danger">

View File

@ -118,12 +118,6 @@ export class AddEditComponent extends BaseAddEditComponent {
this.router.navigate(['/attachments'], { queryParams: { cipherId: this.cipher.id } });
}
share() {
super.share();
if (this.cipher.organizationId == null) {
this.router.navigate(['/share-cipher'], { queryParams: { cipherId: this.cipher.id } });
}
}
editCollections() {
super.editCollections();

View File

@ -41,14 +41,12 @@ export class ShareComponent extends BaseShareComponent {
async submit(): Promise<boolean> {
const success = await super.submit();
if (success) {
window.setTimeout(() => {
this.location.back();
}, 200);
this.cancel();
}
return success;
}
cancel() {
this.location.back();
this.router.navigate(['/view-cipher'], { replaceUrl: true, queryParams: { cipherId: this.cipher.id } });
}
}

View File

@ -295,6 +295,14 @@
<span>{{'cloneItem' | i18n}}</span>
</div>
</a>
<a class="box-content-row" href="#" appStopClick appBlurClick (click)="share()" *ngIf="!cipher.organizationId">
<div class="row-main text-primary">
<div class="icon text-primary" aria-hidden="true">
<i class="fa fa-share-alt fa-lg fa-fw"></i>
</div>
<span>{{'shareItem' | i18n}}</span>
</div>
</a>
<a class="box-content-row" href="#" appStopClick appBlurClick (click)="restore()" *ngIf="cipher.isDeleted">
<div class="row-main text-primary">
<div class="icon text-primary" aria-hidden="true">

View File

@ -129,6 +129,13 @@ export class ViewComponent extends BaseViewComponent {
});
}
share() {
super.share();
if (this.cipher.organizationId == null) {
this.router.navigate(['/share-cipher'], { replaceUrl: true, queryParams: { cipherId: this.cipher.id } });
}
}
async fillCipher() {
const didAutofill = await this.doAutofill();
if (didAutofill) {

View File

@ -254,7 +254,7 @@ export default class AutofillService implements AutofillServiceInterface {
}
}
const autoFillResponse = await this.doAutoFill({
const totpCode = await this.doAutoFill({
cipher: cipher,
pageDetails: pageDetails,
skipTotp: !fromCommand,
@ -265,12 +265,12 @@ export default class AutofillService implements AutofillServiceInterface {
fillNewPassword: fromCommand,
});
// Only update last used index if doAutoFill didn't throw an exception
// Update last used index as autofill has succeed
if (fromCommand) {
this.cipherService.updateLastUsedIndexForUrl(tab.url);
}
return autoFillResponse;
return totpCode;
}
// Helpers

View File

@ -16,6 +16,7 @@ export default class BrowserPlatformUtilsService implements PlatformUtilsService
private showDialogResolves = new Map<number, { resolve: (value: boolean) => void, date: Date }>();
private deviceCache: DeviceType = null;
private analyticsIdCache: string = null;
private prefersColorSchemeDark = window.matchMedia('(prefers-color-scheme: dark)');
constructor(private messagingService: MessagingService,
private clipboardWriteCallback: (clipboardValue: string, clearMs: number) => void,
@ -303,4 +304,14 @@ export default class BrowserPlatformUtilsService implements PlatformUtilsService
supportsSecureStorage(): boolean {
return false;
}
getDefaultSystemTheme() {
return this.prefersColorSchemeDark.matches ? 'dark' : 'light';
}
onDefaultSystemThemeChange(callback: ((theme: 'light' | 'dark') => unknown)) {
this.prefersColorSchemeDark.addListener(({ matches }) => {
callback(matches ? 'dark' : 'light');
});
}
}