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:
commit
db59f2791a
2
jslib
2
jslib
@ -1 +1 @@
|
||||
Subproject commit 72bf18f369068d36767794bdc0ca377f734cf373
|
||||
Subproject commit cea09a22e533ef3598bb497ba0503c2fcd5b2dc1
|
11
package-lock.json
generated
11
package-lock.json
generated
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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."
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -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),
|
||||
|
||||
|
@ -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}}"
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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,
|
||||
},
|
||||
{
|
||||
|
@ -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>
|
||||
|
@ -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">
|
||||
|
@ -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();
|
||||
|
@ -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 } });
|
||||
}
|
||||
}
|
||||
|
@ -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">
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user