1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-12 14:34:02 +00:00

Merge master into branch

This commit is contained in:
CarleyDiaz-Bitwarden
2022-06-30 15:22:27 -04:00
334 changed files with 19524 additions and 5154 deletions

View File

@@ -7,7 +7,7 @@ module.exports = {
displayName: "libs/angular tests",
preset: "jest-preset-angular",
testMatch: ["**/+(*.)+(spec).+(ts)"],
setupFilesAfterEnv: ["<rootDir>/spec/test.ts"],
setupFilesAfterEnv: ["<rootDir>/spec/test.setup.ts"],
collectCoverage: true,
coverageReporters: ["html", "lcov"],
coverageDirectory: "coverage",

View File

@@ -3,6 +3,7 @@ import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
import { FileDownloadService } from "@bitwarden/common/abstractions/fileDownload/fileDownload.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
@@ -36,7 +37,8 @@ export class AttachmentsComponent implements OnInit {
protected apiService: ApiService,
protected win: Window,
protected logService: LogService,
protected stateService: StateService
protected stateService: StateService,
protected fileDownloadService: FileDownloadService
) {}
async ngOnInit() {
@@ -171,7 +173,10 @@ export class AttachmentsComponent implements OnInit {
? attachment.key
: await this.cryptoService.getOrgKey(this.cipher.organizationId);
const decBuf = await this.cryptoService.decryptFromBytes(buf, key);
this.platformUtilsService.saveFile(this.win, decBuf, null, attachment.fileName);
this.fileDownloadService.download({
fileName: attachment.fileName,
blobData: decBuf,
});
} catch (e) {
this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred"));
}

View File

@@ -13,6 +13,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
import { EventService } from "@bitwarden/common/abstractions/event.service";
import { ExportService } from "@bitwarden/common/abstractions/export.service";
import { FileDownloadService } from "@bitwarden/common/abstractions/fileDownload/fileDownload.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
@@ -58,11 +59,12 @@ export class ExportComponent implements OnInit {
protected win: Window,
private logService: LogService,
private userVerificationService: UserVerificationService,
protected formBuilder: FormBuilder,
protected modalService: ModalService,
protected apiService: ApiService,
protected stateService: StateService,
protected modalConfig: ModalConfig
protected modalConfig: ModalConfig,
private formBuilder: FormBuilder,
protected fileDownloadService: FileDownloadService
) {}
async ngOnInit() {
@@ -217,6 +219,10 @@ export class ExportComponent implements OnInit {
private downloadFile(csv: string): void {
const fileName = this.getFileName();
this.platformUtilsService.saveFile(this.win, csv, { type: "text/plain" }, fileName);
this.fileDownloadService.download({
fileName: fileName,
blobData: csv,
blobOptions: { type: "text/plain" },
});
}
}

View File

@@ -15,6 +15,7 @@ import { BroadcasterService } from "@bitwarden/common/abstractions/broadcaster.s
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
import { EventService } from "@bitwarden/common/abstractions/event.service";
import { FileDownloadService } from "@bitwarden/common/abstractions/fileDownload/fileDownload.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service";
import { PasswordRepromptService } from "@bitwarden/common/abstractions/passwordReprompt.service";
@@ -76,7 +77,8 @@ export class ViewComponent implements OnDestroy, OnInit {
protected apiService: ApiService,
protected passwordRepromptService: PasswordRepromptService,
private logService: LogService,
protected stateService: StateService
protected stateService: StateService,
protected fileDownloadService: FileDownloadService
) {}
ngOnInit() {
@@ -373,7 +375,10 @@ export class ViewComponent implements OnDestroy, OnInit {
? attachment.key
: await this.cryptoService.getOrgKey(this.cipher.organizationId);
const decBuf = await this.cryptoService.decryptFromBytes(buf, key);
this.platformUtilsService.saveFile(this.win, decBuf, null, attachment.fileName);
this.fileDownloadService.download({
fileName: attachment.fileName,
blobData: decBuf,
});
} catch (e) {
this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred"));
}

View File

@@ -1,5 +1,8 @@
import { InjectionToken, Injector, LOCALE_ID, NgModule } from "@angular/core";
import { ThemingService } from "@bitwarden/angular/services/theming/theming.service";
import { AbstractThemingService } from "@bitwarden/angular/services/theming/theming.service.abstraction";
import { AbstractEncryptService } from "@bitwarden/common/abstractions/abstractEncrypt.service";
import { ApiService as ApiServiceAbstraction } from "@bitwarden/common/abstractions/api.service";
import { AppIdService as AppIdServiceAbstraction } from "@bitwarden/common/abstractions/appId.service";
import { AuditService as AuditServiceAbstraction } from "@bitwarden/common/abstractions/audit.service";
@@ -30,7 +33,7 @@ import { SendService as SendServiceAbstraction } from "@bitwarden/common/abstrac
import { SettingsService as SettingsServiceAbstraction } from "@bitwarden/common/abstractions/settings.service";
import { StateService as StateServiceAbstraction } from "@bitwarden/common/abstractions/state.service";
import { StateMigrationService as StateMigrationServiceAbstraction } from "@bitwarden/common/abstractions/stateMigration.service";
import { StorageService as StorageServiceAbstraction } from "@bitwarden/common/abstractions/storage.service";
import { AbstractStorageService } from "@bitwarden/common/abstractions/storage.service";
import { SyncService as SyncServiceAbstraction } from "@bitwarden/common/abstractions/sync.service";
import { TokenService as TokenServiceAbstraction } from "@bitwarden/common/abstractions/token.service";
import { TotpService as TotpServiceAbstraction } from "@bitwarden/common/abstractions/totp.service";
@@ -49,6 +52,7 @@ import { CipherService } from "@bitwarden/common/services/cipher.service";
import { CollectionService } from "@bitwarden/common/services/collection.service";
import { ConsoleLogService } from "@bitwarden/common/services/consoleLog.service";
import { CryptoService } from "@bitwarden/common/services/crypto.service";
import { EncryptService } from "@bitwarden/common/services/encrypt.service";
import { EnvironmentService } from "@bitwarden/common/services/environment.service";
import { EventService } from "@bitwarden/common/services/event.service";
import { ExportService } from "@bitwarden/common/services/export.service";
@@ -84,7 +88,8 @@ import { PasswordRepromptService } from "./passwordReprompt.service";
import { ValidationService } from "./validation.service";
export const WINDOW = new InjectionToken<Window>("WINDOW");
export const SECURE_STORAGE = new InjectionToken<StorageServiceAbstraction>("SECURE_STORAGE");
export const MEMORY_STORAGE = new InjectionToken<AbstractStorageService>("MEMORY_STORAGE");
export const SECURE_STORAGE = new InjectionToken<AbstractStorageService>("SECURE_STORAGE");
export const STATE_FACTORY = new InjectionToken<StateFactory>("STATE_FACTORY");
export const STATE_SERVICE_USE_CACHE = new InjectionToken<boolean>("STATE_SERVICE_USE_CACHE");
export const LOGOUT_CALLBACK = new InjectionToken<(expired: boolean, userId?: string) => void>(
@@ -94,6 +99,7 @@ export const LOCKED_CALLBACK = new InjectionToken<() => void>("LOCKED_CALLBACK")
export const CLIENT_TYPE = new InjectionToken<boolean>("CLIENT_TYPE");
export const LOCALES_DIRECTORY = new InjectionToken<string>("LOCALES_DIRECTORY");
export const SYSTEM_LANGUAGE = new InjectionToken<string>("SYSTEM_LANGUAGE");
export const LOG_MAC_FAILURES = new InjectionToken<string>("LOG_MAC_FAILURES");
@NgModule({
declarations: [],
@@ -137,10 +143,14 @@ export const SYSTEM_LANGUAGE = new InjectionToken<string>("SYSTEM_LANGUAGE");
provide: LOCKED_CALLBACK,
useValue: null,
},
{
provide: LOG_MAC_FAILURES,
useValue: true,
},
{
provide: AppIdServiceAbstraction,
useClass: AppIdService,
deps: [StorageServiceAbstraction],
deps: [AbstractStorageService],
},
{
provide: AuditServiceAbstraction,
@@ -231,6 +241,7 @@ export const SYSTEM_LANGUAGE = new InjectionToken<string>("SYSTEM_LANGUAGE");
useClass: CryptoService,
deps: [
CryptoFunctionServiceAbstraction,
AbstractEncryptService,
PlatformUtilsServiceAbstraction,
LogService,
StateServiceAbstraction,
@@ -313,8 +324,9 @@ export const SYSTEM_LANGUAGE = new InjectionToken<string>("SYSTEM_LANGUAGE");
provide: StateServiceAbstraction,
useClass: StateService,
deps: [
StorageServiceAbstraction,
AbstractStorageService,
SECURE_STORAGE,
MEMORY_STORAGE,
LogService,
StateMigrationServiceAbstraction,
STATE_FACTORY,
@@ -324,7 +336,7 @@ export const SYSTEM_LANGUAGE = new InjectionToken<string>("SYSTEM_LANGUAGE");
{
provide: StateMigrationServiceAbstraction,
useClass: StateMigrationService,
deps: [StorageServiceAbstraction, SECURE_STORAGE, STATE_FACTORY],
deps: [AbstractStorageService, SECURE_STORAGE, STATE_FACTORY],
},
{
provide: ExportServiceAbstraction,
@@ -360,6 +372,11 @@ export const SYSTEM_LANGUAGE = new InjectionToken<string>("SYSTEM_LANGUAGE");
useClass: WebCryptoFunctionService,
deps: [WINDOW],
},
{
provide: AbstractEncryptService,
useClass: EncryptService,
deps: [CryptoFunctionServiceAbstraction, LogService, LOG_MAC_FAILURES],
},
{
provide: EventServiceAbstraction,
useClass: EventService,
@@ -423,6 +440,10 @@ export const SYSTEM_LANGUAGE = new InjectionToken<string>("SYSTEM_LANGUAGE");
useClass: TwoFactorService,
deps: [I18nServiceAbstraction, PlatformUtilsServiceAbstraction],
},
{
provide: AbstractThemingService,
useClass: ThemingService,
},
],
})
export class JslibServicesModule {}

View File

@@ -0,0 +1,6 @@
import { ThemeType } from "@bitwarden/common/enums/themeType";
export interface Theme {
configuredTheme: ThemeType;
effectiveTheme: ThemeType;
}

View File

@@ -0,0 +1,19 @@
import { ThemeType } from "@bitwarden/common/enums/themeType";
import { Theme } from "./theme";
export class ThemeBuilder implements Theme {
get effectiveTheme(): ThemeType {
return this.configuredTheme != ThemeType.System ? this.configuredTheme : this.systemTheme;
}
constructor(readonly configuredTheme: ThemeType, readonly systemTheme: ThemeType) {}
updateSystemTheme(systemTheme: ThemeType): ThemeBuilder {
return new ThemeBuilder(this.configuredTheme, systemTheme);
}
updateConfiguredTheme(configuredTheme: ThemeType): ThemeBuilder {
return new ThemeBuilder(configuredTheme, this.systemTheme);
}
}

View File

@@ -0,0 +1,12 @@
import { Observable } from "rxjs";
import { ThemeType } from "@bitwarden/common/enums/themeType";
import { Theme } from "./theme";
export abstract class AbstractThemingService {
theme$: Observable<Theme>;
monitorThemeChanges: () => Promise<void>;
updateSystemTheme: (systemTheme: ThemeType) => void;
updateConfiguredTheme: (theme: ThemeType) => Promise<void>;
}

View File

@@ -0,0 +1,71 @@
import { MediaMatcher } from "@angular/cdk/layout";
import { DOCUMENT } from "@angular/common";
import { Inject, Injectable } from "@angular/core";
import { BehaviorSubject, filter, fromEvent, Observable } from "rxjs";
import { StateService } from "@bitwarden/common/abstractions/state.service";
import { ThemeType } from "@bitwarden/common/enums/themeType";
import { Theme } from "./theme";
import { ThemeBuilder } from "./themeBuilder";
import { AbstractThemingService } from "./theming.service.abstraction";
@Injectable()
export class ThemingService implements AbstractThemingService {
private _theme = new BehaviorSubject<ThemeBuilder | null>(null);
theme$: Observable<Theme> = this._theme.pipe(filter((x) => x !== null));
constructor(
private stateService: StateService,
private mediaMatcher: MediaMatcher,
@Inject(DOCUMENT) private document: Document
) {
this.monitorThemeChanges();
}
async monitorThemeChanges(): Promise<void> {
this._theme.next(
new ThemeBuilder(await this.stateService.getTheme(), await this.getSystemTheme())
);
this.monitorConfiguredThemeChanges();
this.monitorSystemThemeChanges();
}
updateSystemTheme(systemTheme: ThemeType): void {
this._theme.next(this._theme.getValue().updateSystemTheme(systemTheme));
}
async updateConfiguredTheme(theme: ThemeType): Promise<void> {
await this.stateService.setTheme(theme);
this._theme.next(this._theme.getValue().updateConfiguredTheme(theme));
}
protected monitorConfiguredThemeChanges(): void {
this.theme$.subscribe((theme: Theme) => {
this.document.documentElement.classList.remove(
"theme_" + ThemeType.Light,
"theme_" + ThemeType.Dark,
"theme_" + ThemeType.Nord,
"theme_" + ThemeType.SolarizedDark
);
this.document.documentElement.classList.add("theme_" + theme.effectiveTheme);
});
}
// We use a media match query for monitoring the system theme on web and browser, but this doesn't work for electron apps on Linux.
// In desktop we override these methods to track systemTheme with the electron renderer instead, which works for all OSs.
protected async getSystemTheme(): Promise<ThemeType> {
return this.mediaMatcher.matchMedia("(prefers-color-scheme: dark)").matches
? ThemeType.Dark
: ThemeType.Light;
}
protected monitorSystemThemeChanges(): void {
fromEvent<MediaQueryListEvent>(
this.mediaMatcher.matchMedia("(prefers-color-scheme: dark)"),
"change"
).subscribe((event) => {
this.updateSystemTheme(event.matches ? ThemeType.Dark : ThemeType.Light);
});
}
}