1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-14 23:33:31 +00:00

Merged with master and fixed conflicts

This commit is contained in:
gbubemismith
2023-06-06 13:35:15 -04:00
435 changed files with 19787 additions and 21845 deletions

View File

@@ -2,8 +2,9 @@ const { pathsToModuleNameMapper } = require("ts-jest");
const { compilerOptions } = require("../shared/tsconfig.libs");
const sharedConfig = require("../../libs/shared/jest.config.base");
const sharedConfig = require("../../libs/shared/jest.config.angular");
/** @type {import('jest').Config} */
module.exports = {
...sharedConfig,
displayName: "libs/angular tests",

View File

@@ -13,7 +13,7 @@
},
"license": "GPL-3.0",
"scripts": {
"clean": "rimraf dist/**/*",
"clean": "rimraf dist",
"build": "npm run clean && tsc",
"build:watch": "npm run clean && tsc -watch"
}

View File

@@ -0,0 +1,87 @@
<label class="environment-selector-btn">
{{ "region" | i18n }}:
<a
(click)="toggle(null)"
cdkOverlayOrigin
#trigger="cdkOverlayOrigin"
aria-haspopup="menu"
aria-controls="cdk-overlay-container"
[ngSwitch]="selectedEnvironment"
>
<label *ngSwitchCase="ServerEnvironmentType.US" class="text-primary">{{ "us" | i18n }}</label>
<label *ngSwitchCase="ServerEnvironmentType.EU" class="text-primary">{{ "eu" | i18n }}</label>
<label *ngSwitchCase="ServerEnvironmentType.SelfHosted" class="text-primary">{{
"selfHosted" | i18n
}}</label>
<i class="bwi bwi-fw bwi-sm bwi-angle-down" aria-hidden="true"></i>
</a>
</label>
<ng-template
cdkConnectedOverlay
[cdkConnectedOverlayOrigin]="trigger"
(backdropClick)="close()"
(detach)="close()"
[cdkConnectedOverlayOpen]="isOpen"
[cdkConnectedOverlayPositions]="overlayPostition"
>
<div class="box-content">
<div class="environment-selector-dialog" [@transformPanel]="'open'" role="dialog">
<button
type="button"
class="environment-selector-dialog-item"
(click)="toggle(ServerEnvironmentType.US)"
>
<i
class="bwi bwi-fw bwi-sm bwi-check"
style="padding-bottom: 1px"
aria-hidden="true"
[style.visibility]="
selectedEnvironment === ServerEnvironmentType.US ? 'visible' : 'hidden'
"
></i>
<img class="img-us" alt="" />
<span>{{ "us" | i18n }}</span>
</button>
<br />
<button
type="button"
class="environment-selector-dialog-item"
(click)="toggle(ServerEnvironmentType.EU)"
*ngIf="euServerFlagEnabled"
>
<i
class="bwi bwi-fw bwi-sm bwi-check"
style="padding-bottom: 1px"
aria-hidden="true"
[style.visibility]="
selectedEnvironment === ServerEnvironmentType.EU ? 'visible' : 'hidden'
"
></i>
<img class="img-eu" alt="" />
<span>{{ "eu" | i18n }}</span>
</button>
<br *ngIf="euServerFlagEnabled" />
<button
type="button"
class="environment-selector-dialog-item"
(click)="toggle(ServerEnvironmentType.SelfHosted)"
>
<i
class="bwi bwi-fw bwi-sm bwi-check"
style="padding-bottom: 1px"
aria-hidden="true"
[style.visibility]="
selectedEnvironment === ServerEnvironmentType.SelfHosted ? 'visible' : 'hidden'
"
></i>
<i
class="bwi bwi-fw bwi-sm bwi-pencil-square"
style="padding-bottom: 1px"
aria-hidden="true"
></i>
<span>{{ "selfHosted" | i18n }}</span>
</button>
</div>
</div>
</ng-template>

View File

@@ -0,0 +1,109 @@
import { animate, state, style, transition, trigger } from "@angular/animations";
import { ConnectedPosition } from "@angular/cdk/overlay";
import { Component, EventEmitter, OnDestroy, OnInit, Output } from "@angular/core";
import { Router } from "@angular/router";
import { Subject, takeUntil } from "rxjs";
import { ConfigServiceAbstraction } from "@bitwarden/common/abstractions/config/config.service.abstraction";
import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
@Component({
selector: "environment-selector",
templateUrl: "environment-selector.component.html",
animations: [
trigger("transformPanel", [
state(
"void",
style({
opacity: 0,
})
),
transition(
"void => open",
animate(
"100ms linear",
style({
opacity: 1,
})
)
),
transition("* => void", animate("100ms linear", style({ opacity: 0 }))),
]),
],
})
export class EnvironmentSelectorComponent implements OnInit, OnDestroy {
@Output() onOpenSelfHostedSettings = new EventEmitter();
euServerFlagEnabled: boolean;
isOpen = false;
showingModal = false;
selectedEnvironment: ServerEnvironment;
ServerEnvironmentType = ServerEnvironment;
overlayPostition: ConnectedPosition[] = [
{
originX: "start",
originY: "bottom",
overlayX: "start",
overlayY: "top",
},
];
protected componentDestroyed$: Subject<void> = new Subject();
constructor(
protected environmentService: EnvironmentService,
protected configService: ConfigServiceAbstraction,
protected router: Router
) {}
async ngOnInit() {
this.configService.serverConfig$.pipe(takeUntil(this.componentDestroyed$)).subscribe(() => {
this.updateEnvironmentInfo();
});
this.updateEnvironmentInfo();
}
ngOnDestroy(): void {
this.componentDestroyed$.next();
this.componentDestroyed$.complete();
}
async toggle(option: ServerEnvironment) {
this.isOpen = !this.isOpen;
if (option === null) {
return;
}
if (option === ServerEnvironment.EU) {
await this.environmentService.setUrls({ base: "https://vault.bitwarden.eu" });
} else if (option === ServerEnvironment.US) {
await this.environmentService.setUrls({ base: "https://vault.bitwarden.com" });
} else if (option === ServerEnvironment.SelfHosted) {
this.onOpenSelfHostedSettings.emit();
}
this.updateEnvironmentInfo();
}
async updateEnvironmentInfo() {
this.euServerFlagEnabled = await this.configService.getFeatureFlagBool(
FeatureFlag.DisplayEuEnvironmentFlag
);
const webvaultUrl = this.environmentService.getWebVaultUrl();
if (this.environmentService.isSelfHosted()) {
this.selectedEnvironment = ServerEnvironment.SelfHosted;
} else if (webvaultUrl != null && webvaultUrl.includes("bitwarden.eu")) {
this.selectedEnvironment = ServerEnvironment.EU;
} else {
this.selectedEnvironment = ServerEnvironment.US;
}
}
close() {
this.isOpen = false;
this.updateEnvironmentInfo();
}
}
enum ServerEnvironment {
US = "US",
EU = "EU",
SelfHosted = "Self-hosted",
}

View File

@@ -3,9 +3,9 @@ import { FormBuilder, Validators } from "@angular/forms";
import { ActivatedRoute, Router } from "@angular/router";
import { take } from "rxjs/operators";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { AppIdService } from "@bitwarden/common/abstractions/appId.service";
import { CryptoFunctionService } from "@bitwarden/common/abstractions/cryptoFunction.service";
import { DevicesApiServiceAbstraction } from "@bitwarden/common/abstractions/devices/devices-api.service.abstraction";
import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service";
import {
AllValidationErrors,
@@ -35,7 +35,6 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit
onSuccessfulLoginNavigate: () => Promise<any>;
onSuccessfulLoginTwoFactorNavigate: () => Promise<any>;
onSuccessfulLoginForceResetNavigate: () => Promise<any>;
selfHosted = false;
showLoginWithDevice: boolean;
validatedEmail = false;
paramEmailSet = false;
@@ -55,7 +54,7 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit
}
constructor(
protected apiService: ApiService,
protected devicesApiService: DevicesApiServiceAbstraction,
protected appIdService: AppIdService,
protected authService: AuthService,
protected router: Router,
@@ -73,7 +72,6 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit
protected loginService: LoginService
) {
super(environmentService, i18nService, platformUtilsService);
this.selfHosted = platformUtilsService.isSelfHost();
}
get selfHostedDomain() {
@@ -295,9 +293,10 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit
async getLoginWithDevice(email: string) {
try {
const deviceIdentifier = await this.appIdService.getAppId();
const res = await this.apiService.getKnownDevice(email, deviceIdentifier);
//ensure the application is not self-hosted
this.showLoginWithDevice = res && !this.selfHosted;
this.showLoginWithDevice = await this.devicesApiService.getKnownDevice(
email,
deviceIdentifier
);
} catch (e) {
this.showLoginWithDevice = false;
}

View File

@@ -202,7 +202,9 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI
}
if (this.onSuccessfulLogin != null) {
this.loginService.clearValues();
await this.onSuccessfulLogin();
// Note: awaiting this will currently cause a hang on desktop & browser as they will wait for a full sync to complete
// before nagivating to the success route.
this.onSuccessfulLogin();
}
if (response.resetMasterPassword) {
this.successRoute = "set-password";

View File

@@ -4,6 +4,8 @@ import { EnvironmentService } from "@bitwarden/common/abstractions/environment.s
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { ModalService } from "../services/modal.service";
@Directive()
export class EnvironmentComponent {
@Output() onSaved = new EventEmitter();
@@ -19,7 +21,8 @@ export class EnvironmentComponent {
constructor(
protected platformUtilsService: PlatformUtilsService,
protected environmentService: EnvironmentService,
protected i18nService: I18nService
protected i18nService: I18nService,
private modalService: ModalService
) {
const urls = this.environmentService.getUrls();
@@ -59,5 +62,6 @@ export class EnvironmentComponent {
protected saved() {
this.onSaved.emit();
this.modalService.closeAll();
}
}

View File

@@ -1,5 +0,0 @@
export interface SelectOptions {
name: string;
value: any;
disabled?: boolean;
}

View File

@@ -10,6 +10,8 @@ import { ConfigApiServiceAbstraction } from "@bitwarden/common/abstractions/conf
import { ConfigServiceAbstraction } from "@bitwarden/common/abstractions/config/config.service.abstraction";
import { CryptoService as CryptoServiceAbstraction } from "@bitwarden/common/abstractions/crypto.service";
import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@bitwarden/common/abstractions/cryptoFunction.service";
import { DeviceCryptoServiceAbstraction } from "@bitwarden/common/abstractions/device-crypto.service.abstraction";
import { DevicesApiServiceAbstraction } from "@bitwarden/common/abstractions/devices/devices-api.service.abstraction";
import { EncryptService } from "@bitwarden/common/abstractions/encrypt.service";
import { EnvironmentService as EnvironmentServiceAbstraction } from "@bitwarden/common/abstractions/environment.service";
import { EventCollectionService as EventCollectionServiceAbstraction } from "@bitwarden/common/abstractions/event/event-collection.service";
@@ -90,6 +92,8 @@ import { ConsoleLogService } from "@bitwarden/common/services/consoleLog.service
import { CryptoService } from "@bitwarden/common/services/crypto.service";
import { EncryptServiceImplementation } from "@bitwarden/common/services/cryptography/encrypt.service.implementation";
import { MultithreadEncryptServiceImplementation } from "@bitwarden/common/services/cryptography/multithread-encrypt.service.implementation";
import { DeviceCryptoService } from "@bitwarden/common/services/device-crypto.service.implementation";
import { DevicesApiServiceImplementation } from "@bitwarden/common/services/devices/devices-api.service.implementation";
import { EnvironmentService } from "@bitwarden/common/services/environment.service";
import { EventCollectionService } from "@bitwarden/common/services/event/event-collection.service";
import { EventUploadService } from "@bitwarden/common/services/event/event-upload.service";
@@ -351,6 +355,8 @@ import { AbstractThemingService } from "./theming/theming.service.abstraction";
PlatformUtilsServiceAbstraction,
LogService,
StateServiceAbstraction,
AppIdServiceAbstraction,
DevicesApiServiceAbstraction,
],
},
{
@@ -615,7 +621,12 @@ import { AbstractThemingService } from "./theming/theming.service.abstraction";
{
provide: ConfigServiceAbstraction,
useClass: ConfigService,
deps: [StateServiceAbstraction, ConfigApiServiceAbstraction, AuthServiceAbstraction],
deps: [
StateServiceAbstraction,
ConfigApiServiceAbstraction,
AuthServiceAbstraction,
EnvironmentServiceAbstraction,
],
},
{
provide: ConfigApiServiceAbstraction,
@@ -651,6 +662,23 @@ import { AbstractThemingService } from "./theming/theming.service.abstraction";
useClass: OrgDomainApiService,
deps: [OrgDomainServiceAbstraction, ApiServiceAbstraction],
},
{
provide: DevicesApiServiceAbstraction,
useClass: DevicesApiServiceImplementation,
deps: [ApiServiceAbstraction],
},
{
provide: DeviceCryptoServiceAbstraction,
useClass: DeviceCryptoService,
deps: [
CryptoFunctionServiceAbstraction,
CryptoServiceAbstraction,
EncryptService,
StateServiceAbstraction,
AppIdServiceAbstraction,
DevicesApiServiceAbstraction,
],
},
],
})
export class JslibServicesModule {}

View File

@@ -63,7 +63,7 @@ export class ThemingService implements AbstractThemingService {
protected monitorSystemThemeChanges(): void {
fromEvent<MediaQueryListEvent>(
this.window.matchMedia("(prefers-color-scheme: dark)"),
window.matchMedia("(prefers-color-scheme: dark)"),
"change"
).subscribe((event) => {
this.updateSystemTheme(event.matches ? ThemeType.Dark : ThemeType.Light);

View File

@@ -1,6 +1,6 @@
import { Directive, EventEmitter, OnDestroy, OnInit, Output } from "@angular/core";
import { UntypedFormBuilder, Validators } from "@angular/forms";
import { merge, takeUntil, Subject, startWith } from "rxjs";
import { merge, startWith, Subject, takeUntil } from "rxjs";
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
@@ -21,7 +21,11 @@ export class ExportComponent implements OnInit, OnDestroy {
@Output() onSaved = new EventEmitter();
formPromise: Promise<string>;
disabledByPolicy = false;
private _disabledByPolicy = false;
protected get disabledByPolicy(): boolean {
return this._disabledByPolicy;
}
exportForm = this.formBuilder.group({
format: ["json"],
@@ -59,11 +63,12 @@ export class ExportComponent implements OnInit, OnDestroy {
.policyAppliesToActiveUser$(PolicyType.DisablePersonalVaultExport)
.pipe(takeUntil(this.destroy$))
.subscribe((policyAppliesToActiveUser) => {
this.disabledByPolicy = policyAppliesToActiveUser;
this._disabledByPolicy = policyAppliesToActiveUser;
if (this.disabledByPolicy) {
this.exportForm.disable();
}
});
await this.checkExportDisabled();
merge(
this.exportForm.get("format").valueChanges,
this.exportForm.get("fileEncryptionType").valueChanges
@@ -77,12 +82,6 @@ export class ExportComponent implements OnInit, OnDestroy {
this.destroy$.next();
}
async checkExportDisabled() {
if (this.disabledByPolicy) {
this.exportForm.disable();
}
}
get encryptedFormat() {
return this.format === "encrypted_json";
}

View File

@@ -198,4 +198,18 @@ export class FormSelectionList<
this.selectItem(selectedItem.id, selectedItem);
}
}
/**
* Helper method to iterate over each "selected" form control and its corresponding item
* @param fn - The function to call for each form control and its corresponding item
*/
forEachControlItem(
fn: (control: AbstractControl<Partial<TControlValue>, TControlValue>, value: TItem) => void
) {
for (let i = 0; i < this.formArray.length; i++) {
// The selectedItems array and formArray are explicitly kept in sync,
// so we can safely assume the index of the form control and item are the same
fn(this.formArray.at(i), this.selectedItems[i]);
}
}
}