1
0
mirror of https://github.com/bitwarden/browser synced 2026-01-03 17:13:47 +00:00

Move desktop into apps/desktop

This commit is contained in:
Hinton
2022-05-05 17:16:23 +02:00
parent 9852f2ec22
commit 28bc4113b9
331 changed files with 2 additions and 2 deletions

View File

@@ -0,0 +1,76 @@
import * as fs from "fs";
import * as path from "path";
import { I18nService as BaseI18nService } from "jslib-common/services/i18n.service";
export class I18nService extends BaseI18nService {
constructor(systemLanguage: string, localesDirectory: string) {
super(systemLanguage, localesDirectory, (formattedLocale: string) => {
const filePath = path.join(
__dirname,
this.localesDirectory + "/" + formattedLocale + "/messages.json"
);
const localesJson = fs.readFileSync(filePath, "utf8");
const locales = JSON.parse(localesJson.replace(/^\uFEFF/, "")); // strip the BOM
return Promise.resolve(locales);
});
// Please leave 'en' where it is, as it's our fallback language in case no translation can be found
this.supportedTranslationLocales = [
"en",
"af",
"az",
"be",
"bg",
"bn",
"bs",
"ca",
"cs",
"da",
"de",
"el",
"en-GB",
"en-IN",
"eo",
"es",
"et",
"fa",
"fi",
"fil",
"fr",
"he",
"hi",
"hr",
"hu",
"id",
"it",
"ja",
"ka",
"km",
"kn",
"ko",
"lv",
"me",
"ml",
"nb",
"nl",
"nn",
"pl",
"pt-BR",
"pt-PT",
"ro",
"ru",
"si",
"sk",
"sl",
"sr",
"sv",
"th",
"tr",
"uk",
"vi",
"zh-CN",
"zh-TW",
];
}
}

View File

@@ -0,0 +1,28 @@
import { Injectable } from "@angular/core";
import { CanActivate } from "@angular/router";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service";
const maxAllowedAccounts = 5;
@Injectable()
export class LoginGuardService implements CanActivate {
protected homepage = "vault";
constructor(
private stateService: StateService,
private platformUtilsService: PlatformUtilsService,
private i18nService: I18nService
) {}
async canActivate() {
const accounts = this.stateService.accounts.getValue();
if (accounts != null && Object.keys(accounts).length >= maxAllowedAccounts) {
this.platformUtilsService.showToast("error", null, this.i18nService.t("accountLimitReached"));
return false;
}
return true;
}
}

View File

@@ -0,0 +1,187 @@
import { Injectable } from "@angular/core";
import { ipcRenderer } from "electron";
import Swal from "sweetalert2";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { KeySuffixOptions } from "jslib-common/enums/keySuffixOptions";
import { Utils } from "jslib-common/misc/utils";
import { EncString } from "jslib-common/models/domain/encString";
import { SymmetricCryptoKey } from "jslib-common/models/domain/symmetricCryptoKey";
const MessageValidTimeout = 10 * 1000;
const EncryptionAlgorithm = "sha1";
type Message = {
command: string;
userId?: string;
timestamp?: number;
publicKey?: string;
};
type OuterMessage = {
message: Message | EncString;
appId: string;
};
@Injectable()
export class NativeMessagingService {
private sharedSecrets = new Map<string, SymmetricCryptoKey>();
constructor(
private cryptoFunctionService: CryptoFunctionService,
private cryptoService: CryptoService,
private platformUtilService: PlatformUtilsService,
private logService: LogService,
private i18nService: I18nService,
private messagingService: MessagingService,
private stateService: StateService
) {}
init() {
ipcRenderer.on("nativeMessaging", async (_event: any, message: any) => {
this.messageHandler(message);
});
}
private async messageHandler(msg: OuterMessage) {
const appId = msg.appId;
const rawMessage = msg.message;
// Request to setup secure encryption
if ("command" in rawMessage && rawMessage.command === "setupEncryption") {
const remotePublicKey = Utils.fromB64ToArray(rawMessage.publicKey).buffer;
// Valudate the UserId to ensure we are logged into the same account.
const userIds = Object.keys(this.stateService.accounts.getValue());
if (!userIds.includes(rawMessage.userId)) {
ipcRenderer.send("nativeMessagingReply", { command: "wrongUserId", appId: appId });
return;
}
if (await this.stateService.getEnableBrowserIntegrationFingerprint()) {
ipcRenderer.send("nativeMessagingReply", { command: "verifyFingerprint", appId: appId });
const fingerprint = (
await this.cryptoService.getFingerprint(
await this.stateService.getUserId(),
remotePublicKey
)
).join(" ");
this.messagingService.send("setFocus");
// Await confirmation that fingerprint is correct
const submitted = await Swal.fire({
titleText: this.i18nService.t("verifyBrowserTitle"),
html: `${this.i18nService.t("verifyBrowserDesc")}<br><br><strong>${fingerprint}</strong>`,
showCancelButton: true,
cancelButtonText: this.i18nService.t("cancel"),
showConfirmButton: true,
confirmButtonText: this.i18nService.t("approve"),
allowOutsideClick: false,
});
if (submitted.value !== true) {
return;
}
}
this.secureCommunication(remotePublicKey, appId);
return;
}
if (this.sharedSecrets.get(appId) == null) {
ipcRenderer.send("nativeMessagingReply", { command: "invalidateEncryption", appId: appId });
return;
}
const message: Message = JSON.parse(
await this.cryptoService.decryptToUtf8(rawMessage as EncString, this.sharedSecrets.get(appId))
);
// Shared secret is invalidated, force re-authentication
if (message == null) {
ipcRenderer.send("nativeMessagingReply", { command: "invalidateEncryption", appId: appId });
return;
}
if (Math.abs(message.timestamp - Date.now()) > MessageValidTimeout) {
this.logService.error("NativeMessage is to old, ignoring.");
return;
}
switch (message.command) {
case "biometricUnlock": {
if (!this.platformUtilService.supportsBiometric()) {
return this.send({ command: "biometricUnlock", response: "not supported" }, appId);
}
if (!(await this.stateService.getBiometricUnlock({ userId: message.userId }))) {
this.send({ command: "biometricUnlock", response: "not enabled" }, appId);
return await Swal.fire({
title: this.i18nService.t("biometricsNotEnabledTitle"),
text: this.i18nService.t("biometricsNotEnabledDesc"),
showCancelButton: true,
cancelButtonText: this.i18nService.t("cancel"),
showConfirmButton: false,
});
}
const key = await this.cryptoService.getKeyFromStorage(
KeySuffixOptions.Biometric,
message.userId
);
if (key != null) {
this.send(
{ command: "biometricUnlock", response: "unlocked", keyB64: key.keyB64 },
appId
);
} else {
this.send({ command: "biometricUnlock", response: "canceled" }, appId);
}
break;
}
default:
this.logService.error("NativeMessage, got unknown command.");
break;
}
}
private async send(message: any, appId: string) {
message.timestamp = Date.now();
const encrypted = await this.cryptoService.encrypt(
JSON.stringify(message),
this.sharedSecrets.get(appId)
);
ipcRenderer.send("nativeMessagingReply", { appId: appId, message: encrypted });
}
private async secureCommunication(remotePublicKey: ArrayBuffer, appId: string) {
const secret = await this.cryptoFunctionService.randomBytes(64);
this.sharedSecrets.set(appId, new SymmetricCryptoKey(secret));
const encryptedSecret = await this.cryptoFunctionService.rsaEncrypt(
secret,
remotePublicKey,
EncryptionAlgorithm
);
ipcRenderer.send("nativeMessagingReply", {
appId: appId,
command: "setupEncryption",
sharedSecret: Utils.fromBufferToB64(encryptedSecret),
});
}
}

View File

@@ -0,0 +1,10 @@
import { Injectable } from "@angular/core";
import { PasswordRepromptService as BasePasswordRepromptService } from "jslib-angular/services/passwordReprompt.service";
import { PasswordRepromptComponent } from "../app/components/password-reprompt.component";
@Injectable()
export class PasswordRepromptService extends BasePasswordRepromptService {
component = PasswordRepromptComponent;
}

View File

@@ -0,0 +1,16 @@
import { StateService as StateServiceAbstraction } from "jslib-common/abstractions/state.service";
import { GlobalState } from "jslib-common/models/domain/globalState";
import { StateService as BaseStateService } from "jslib-common/services/state.service";
import { Account } from "../models/account";
export class StateService
extends BaseStateService<GlobalState, Account>
implements StateServiceAbstraction
{
async addAccount(account: Account) {
// Apply desktop overides to default account values
account = new Account(account);
await super.addAccount(account);
}
}