1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-26 09:33:22 +00:00

Merge branch 'main' into ps/extension-refresh

This commit is contained in:
Victoria League
2024-10-28 16:00:40 -04:00
committed by GitHub
41 changed files with 457 additions and 240 deletions

View File

@@ -18,7 +18,8 @@ import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstraction
import { CaptchaIFrame } from "@bitwarden/common/auth/captcha-iframe";
import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result";
import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
import { ClientType } from "@bitwarden/common/enums";
import { ClientType, HttpStatusCode } from "@bitwarden/common/enums";
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
@@ -26,6 +27,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { SyncService } from "@bitwarden/common/platform/sync";
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
@@ -136,6 +138,7 @@ export class LoginComponent implements OnInit, OnDestroy {
private syncService: SyncService,
private toastService: ToastService,
private logService: LogService,
private validationService: ValidationService,
) {
this.clientType = this.platformUtilsService.getClientType();
this.loginViaAuthRequestSupported = this.loginComponentService.isLoginViaAuthRequestSupported();
@@ -182,19 +185,54 @@ export class LoginComponent implements OnInit, OnDestroy {
null,
);
const authResult = await this.loginStrategyService.logIn(credentials);
try {
const authResult = await this.loginStrategyService.logIn(credentials);
await this.saveEmailSettings();
await this.handleAuthResult(authResult);
await this.saveEmailSettings();
await this.handleAuthResult(authResult);
if (this.clientType === ClientType.Desktop) {
if (this.captchaSiteKey) {
const content = document.getElementById("content") as HTMLDivElement;
content.setAttribute("style", "width:335px");
if (this.clientType === ClientType.Desktop) {
if (this.captchaSiteKey) {
const content = document.getElementById("content") as HTMLDivElement;
content.setAttribute("style", "width:335px");
}
}
} catch (error) {
this.logService.error(error);
this.handleSubmitError(error);
}
};
/**
* Handles the error from the submit function.
*
* @param error The error object.
*/
private handleSubmitError(error: unknown) {
// Handle error responses
if (error instanceof ErrorResponse) {
switch (error.statusCode) {
case HttpStatusCode.BadRequest: {
if (error.message.toLowerCase().includes("username or password is incorrect")) {
this.formGroup.controls.masterPassword.setErrors({
error: {
message: this.i18nService.t("invalidMasterPassword"),
},
});
}
break;
}
default: {
// Allow all other errors to be handled by toast
this.validationService.showError(error);
}
}
} else {
// Allow all other errors to be handled by toast
this.validationService.showError(error);
}
}
/**
* Handles the result of the authentication process.
*

View File

@@ -8,6 +8,7 @@ export class PolicyResponse extends BaseResponse {
type: PolicyType;
data: any;
enabled: boolean;
canToggleState: boolean;
constructor(response: any) {
super(response);
@@ -16,5 +17,6 @@ export class PolicyResponse extends BaseResponse {
this.type = this.getResponseProperty("Type");
this.data = this.getResponseProperty("Data");
this.enabled = this.getResponseProperty("Enabled");
this.canToggleState = this.getResponseProperty("CanToggleState") ?? true;
}
}

View File

@@ -96,7 +96,7 @@ export class DefaultSdkService implements SdkService {
let client: BitwardenClient;
const createAndInitializeClient = async () => {
if (privateKey == null || userKey == null || orgKeys == null) {
if (privateKey == null || userKey == null) {
return undefined;
}
@@ -150,7 +150,7 @@ export class DefaultSdkService implements SdkService {
kdfParams: KdfConfig,
privateKey: EncryptedString,
userKey: UserKey,
orgKeys: Record<OrganizationId, EncryptedOrganizationKeyData>,
orgKeys?: Record<OrganizationId, EncryptedOrganizationKeyData>,
) {
await client.crypto().initialize_user_crypto({
email: account.email,
@@ -169,9 +169,12 @@ export class DefaultSdkService implements SdkService {
},
privateKey,
});
// We initialize the org crypto even if the org_keys are
// null to make sure any existing org keys are cleared.
await client.crypto().initialize_org_crypto({
organizationKeys: new Map(
Object.entries(orgKeys)
Object.entries(orgKeys ?? {})
.filter(([_, v]) => v.type === "organization")
.map(([k, v]) => [k, v.key]),
),

View File

@@ -1,7 +1,10 @@
<bit-card *ngFor="let credential of credentials$ | async" class="tw-mb-2">
<div class="tw-flex tw-justify-between tw-items-center">
<div class="tw-flex tw-flex-col">
<h2 bitTypography="h6">{{ credential.credential }}</h2>
<bit-color-password
class="tw-font-mono"
[password]="credential.credential"
></bit-color-password>
<span class="tw-text-muted" bitTypography="body1">{{
credential.generationDate | date: "medium"
}}</span>

View File

@@ -9,6 +9,7 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv
import { UserId } from "@bitwarden/common/types/guid";
import {
CardComponent,
ColorPasswordModule,
IconButtonModule,
NoItemsModule,
SectionComponent,
@@ -21,6 +22,7 @@ import { GeneratedCredential, GeneratorHistoryService } from "@bitwarden/generat
selector: "bit-credential-generator-history",
templateUrl: "credential-generator-history.component.html",
imports: [
ColorPasswordModule,
CommonModule,
IconButtonModule,
NoItemsModule,

View File

@@ -8,6 +8,7 @@
<bit-form-field disableMargin>
<bit-label>{{ "numWords" | i18n }}</bit-label>
<input bitInput formControlName="numWords" id="num-words" type="number" />
<bit-hint>{{ numWordsBoundariesHint$ | async }}</bit-hint>
</bit-form-field>
</bit-card>
</div>

View File

@@ -1,9 +1,10 @@
import { coerceBooleanProperty } from "@angular/cdk/coercion";
import { OnInit, Input, Output, EventEmitter, Component, OnDestroy } from "@angular/core";
import { FormBuilder } from "@angular/forms";
import { BehaviorSubject, skip, takeUntil, Subject } from "rxjs";
import { BehaviorSubject, skip, takeUntil, Subject, ReplaySubject } from "rxjs";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { UserId } from "@bitwarden/common/types/guid";
import {
Generators,
@@ -29,11 +30,13 @@ export class PassphraseSettingsComponent implements OnInit, OnDestroy {
/** Instantiates the component
* @param accountService queries user availability
* @param generatorService settings and policy logic
* @param i18nService localize hints
* @param formBuilder reactive form controls
*/
constructor(
private formBuilder: FormBuilder,
private generatorService: CredentialGeneratorService,
private i18nService: I18nService,
private accountService: AccountService,
) {}
@@ -97,6 +100,13 @@ export class PassphraseSettingsComponent implements OnInit, OnDestroy {
this.toggleEnabled(Controls.capitalize, !constraints.capitalize?.readonly);
this.toggleEnabled(Controls.includeNumber, !constraints.includeNumber?.readonly);
const boundariesHint = this.i18nService.t(
"generatorBoundariesHint",
constraints.numWords.min,
constraints.numWords.max,
);
this.numWordsBoundariesHint.next(boundariesHint);
});
// now that outputs are set up, connect inputs
@@ -106,6 +116,11 @@ export class PassphraseSettingsComponent implements OnInit, OnDestroy {
/** display binding for enterprise policy notice */
protected policyInEffect: boolean;
private numWordsBoundariesHint = new ReplaySubject<string>(1);
/** display binding for min/max constraints of `numWords` */
protected numWordsBoundariesHint$ = this.numWordsBoundariesHint.asObservable();
private toggleEnabled(setting: keyof typeof Controls, enabled: boolean) {
if (enabled) {
this.settings.get(setting).enable({ emitEvent: false });

View File

@@ -8,6 +8,7 @@
<bit-form-field disableMargin>
<bit-label>{{ "length" | i18n }}</bit-label>
<input bitInput formControlName="length" type="number" />
<bit-hint>{{ lengthBoundariesHint$ | async }}</bit-hint>
</bit-form-field>
</bit-card>
</div>

View File

@@ -1,9 +1,10 @@
import { coerceBooleanProperty } from "@angular/cdk/coercion";
import { OnInit, Input, Output, EventEmitter, Component, OnDestroy } from "@angular/core";
import { FormBuilder } from "@angular/forms";
import { BehaviorSubject, takeUntil, Subject, map, filter, tap, skip } from "rxjs";
import { BehaviorSubject, takeUntil, Subject, map, filter, tap, skip, ReplaySubject } from "rxjs";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { UserId } from "@bitwarden/common/types/guid";
import {
Generators,
@@ -33,11 +34,13 @@ export class PasswordSettingsComponent implements OnInit, OnDestroy {
/** Instantiates the component
* @param accountService queries user availability
* @param generatorService settings and policy logic
* @param i18nService localize hints
* @param formBuilder reactive form controls
*/
constructor(
private formBuilder: FormBuilder,
private generatorService: CredentialGeneratorService,
private i18nService: I18nService,
private accountService: AccountService,
) {}
@@ -147,6 +150,13 @@ export class PasswordSettingsComponent implements OnInit, OnDestroy {
for (const [control, enabled] of toggles) {
this.toggleEnabled(control, enabled);
}
const boundariesHint = this.i18nService.t(
"generatorBoundariesHint",
constraints.length.min,
constraints.length.max,
);
this.lengthBoundariesHint.next(boundariesHint);
});
// cascade selections between checkboxes and spinboxes
@@ -208,6 +218,11 @@ export class PasswordSettingsComponent implements OnInit, OnDestroy {
/** display binding for enterprise policy notice */
protected policyInEffect: boolean;
private lengthBoundariesHint = new ReplaySubject<string>(1);
/** display binding for min/max constraints of `length` */
protected lengthBoundariesHint$ = this.lengthBoundariesHint.asObservable();
private toggleEnabled(setting: keyof typeof Controls, enabled: boolean) {
if (enabled) {
this.settings.get(setting).enable({ emitEvent: false });

View File

@@ -1,6 +1,6 @@
import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core";
import { FormBuilder } from "@angular/forms";
import { BehaviorSubject, skip, Subject, takeUntil } from "rxjs";
import { BehaviorSubject, map, skip, Subject, takeUntil, withLatestFrom } from "rxjs";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { UserId } from "@bitwarden/common/types/guid";
@@ -53,9 +53,23 @@ export class SubaddressSettingsComponent implements OnInit, OnDestroy {
const singleUserId$ = this.singleUserId$();
const settings = await this.generatorService.settings(Generators.subaddress, { singleUserId$ });
settings.pipe(takeUntil(this.destroyed$)).subscribe((s) => {
this.settings.patchValue(s, { emitEvent: false });
});
settings
.pipe(
withLatestFrom(this.accountService.activeAccount$),
map(([settings, activeAccount]) => {
// if the subaddress isn't specified, copy it from
// the user's settings
if ((settings.subaddressEmail ?? "").length < 1) {
settings.subaddressEmail = activeAccount.email;
}
return settings;
}),
takeUntil(this.destroyed$),
)
.subscribe((s) => {
this.settings.patchValue(s, { emitEvent: false });
});
// the first emission is the current value; subsequent emissions are updates
settings.pipe(skip(1), takeUntil(this.destroyed$)).subscribe(this.onUpdated);

View File

@@ -46,6 +46,7 @@
<button
*ngIf="hasPassword"
class="tw-border-l-0 last:tw-rounded-r focus-visible:tw-border-l focus-visible:tw-ml-[-1px]"
bitSuffix
type="button"
buttonType="danger"
bitIconButton="bwi-minus-circle"