1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-26 17:43: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

@@ -2877,6 +2877,20 @@
"generateEmail": {
"message": "Generate email"
},
"generatorBoundariesHint": {
"message": "Value must be between $MIN$ and $MAX$",
"description": "Explains spin box minimum and maximum values to the user",
"placeholders": {
"min": {
"content": "$1",
"example": "8"
},
"max": {
"content": "$2",
"example": "128"
}
}
},
"usernameType": {
"message": "Username type"
},

View File

@@ -80,7 +80,7 @@
"papaparse": "5.4.1",
"proper-lockfile": "4.1.2",
"rxjs": "7.8.1",
"tldts": "6.1.52",
"tldts": "6.1.56",
"zxcvbn": "4.4.2"
}
}

View File

@@ -1965,9 +1965,9 @@ dependencies = [
[[package]]
name = "tokio-util"
version = "0.7.11"
version = "0.7.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1"
checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a"
dependencies = [
"bytes",
"futures-core",

View File

@@ -40,7 +40,7 @@ scopeguard = "=1.2.0"
sha2 = "=0.10.8"
thiserror = "=1.0.61"
tokio = { version = "=1.38.0", features = ["io-util", "sync", "macros"] }
tokio-util = "=0.7.11"
tokio-util = "=0.7.12"
typenum = "=1.17.0"
[target.'cfg(windows)'.dependencies]

View File

@@ -14,11 +14,11 @@
"module-alias": "2.2.3",
"node-ipc": "9.2.1",
"ts-node": "10.9.2",
"uuid": "10.0.0",
"uuid": "11.0.1",
"yargs": "17.7.2"
},
"devDependencies": {
"@types/node": "20.16.11",
"@types/node": "20.17.1",
"@types/node-ipc": "9.2.3",
"typescript": "4.7.4"
}
@@ -106,9 +106,9 @@
"license": "MIT"
},
"node_modules/@types/node": {
"version": "20.16.11",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.11.tgz",
"integrity": "sha512-y+cTCACu92FyA5fgQSAI8A1H429g7aSK2HsO7K4XYUWc4dY5IUz55JSDIYT6/VsOLfGy8vmvQYC2hfb0iF16Uw==",
"version": "20.17.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.1.tgz",
"integrity": "sha512-j2VlPv1NnwPJbaCNv69FO/1z4lId0QmGvpT41YxitRtWlg96g/j8qcv2RKsLKe2F6OJgyXhupN1Xo17b2m139Q==",
"license": "MIT",
"dependencies": {
"undici-types": "~6.19.2"
@@ -421,16 +421,16 @@
"license": "MIT"
},
"node_modules/uuid": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz",
"integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==",
"version": "11.0.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.1.tgz",
"integrity": "sha512-wt9UB5EcLhnboy1UvA1mvGPXkIIrHSu+3FmUksARfdVw9tuPf3CH/CohxO0Su1ApoKAeT6BVzAJIvjTuQVSmuQ==",
"funding": [
"https://github.com/sponsors/broofa",
"https://github.com/sponsors/ctavan"
],
"license": "MIT",
"bin": {
"uuid": "dist/bin/uuid"
"uuid": "dist/esm/bin/uuid"
}
},
"node_modules/v8-compile-cache-lib": {

View File

@@ -19,11 +19,11 @@
"module-alias": "2.2.3",
"node-ipc": "9.2.1",
"ts-node": "10.9.2",
"uuid": "10.0.0",
"uuid": "11.0.1",
"yargs": "17.7.2"
},
"devDependencies": {
"@types/node": "20.16.11",
"@types/node": "20.17.1",
"@types/node-ipc": "9.2.3",
"typescript": "4.7.4"
},

View File

@@ -2393,6 +2393,20 @@
"generateEmail": {
"message": "Generate email"
},
"generatorBoundariesHint": {
"message": "Value must be between $MIN$ and $MAX$",
"description": "Explains spin box minimum and maximum values to the user",
"placeholders": {
"min": {
"content": "$1",
"example": "8"
},
"max": {
"content": "$2",
"example": "128"
}
}
},
"usernameType": {
"message": "Username type"
},

View File

@@ -4,6 +4,15 @@
"notifications": "https://notifications.usdev.bitwarden.pw",
"scim": "https://scim.usdev.bitwarden.pw"
},
"additionalRegions": [
{
"key": "USDEV",
"domain": "usdev.bitwarden.pw",
"urls": {
"webVault": "https://vault.usdev.bitwarden.pw"
}
}
],
"flags": {
"showPasswordless": true
}

View File

@@ -23,14 +23,15 @@
<bit-tab [label]="'role' | i18n">
<ng-container *ngIf="!editMode">
<p bitTypography="body1">{{ "inviteUserDesc" | i18n }}</p>
<bit-form-field>
<bit-form-field *ngIf="remainingSeats$ | async as remainingSeats">
<bit-label>{{ "email" | i18n }}</bit-label>
<input id="emails" type="text" appAutoFocus bitInput formControlName="emails" />
<bit-hint>{{
"inviteMultipleEmailDesc"
| i18n
: (organization.productTierType === ProductTierType.TeamsStarter ? "10" : "20")
<bit-hint *ngIf="remainingSeats > 1; else singleSeat">{{
"inviteMultipleEmailDesc" | i18n: remainingSeats
}}</bit-hint>
<ng-template #singleSeat>
<bit-hint>{{ "inviteSingleEmailDesc" | i18n: remainingSeats }}</bit-hint>
</ng-template>
</bit-form-field>
</ng-container>
<bit-radio-group formControlName="type">

View File

@@ -89,6 +89,7 @@ export class MemberDialogComponent implements OnDestroy {
PermissionMode = PermissionMode;
showNoMasterPasswordWarning = false;
isOnSecretsManagerStandalone: boolean;
remainingSeats$: Observable<number>;
protected organization$: Observable<Organization>;
protected collectionAccessItems: AccessItemView[] = [];
@@ -250,6 +251,10 @@ export class MemberDialogComponent implements OnDestroy {
this.loading = false;
});
this.remainingSeats$ = this.organization$.pipe(
map((organization) => organization.seats - this.params.numConfirmedMembers),
);
}
private setFormValidators(organization: Organization) {

View File

@@ -15,7 +15,7 @@
</div>
</ng-container>
<ng-container bitDialogFooter>
<button bitButton buttonType="primary" bitFormButton type="submit">
<button bitButton buttonType="primary" [disabled]="saveDisabled" bitFormButton type="submit">
{{ "save" | i18n }}
</button>
<button bitButton buttonType="secondary" bitDialogClose type="button">

View File

@@ -42,6 +42,7 @@ export class PolicyEditComponent implements AfterViewInit {
policyType = PolicyType;
loading = true;
enabled = false;
saveDisabled = false;
defaultTypes: any[];
policyComponent: BasePolicyComponent;
@@ -72,6 +73,8 @@ export class PolicyEditComponent implements AfterViewInit {
this.policyComponent.policy = this.data.policy;
this.policyComponent.policyResponse = this.policyResponse;
this.saveDisabled = !this.policyResponse.canToggleState;
this.cdr.detectChanges();
}

View File

@@ -1,6 +1,11 @@
<bit-callout type="warning">
{{ "singleOrgPolicyWarning" | i18n }}
<bit-callout *ngIf="accountDeprovisioningEnabled$ | async; else disabledBlock" type="warning">
{{ "singleOrgPolicyMemberWarning" | i18n }}
</bit-callout>
<ng-template #disabledBlock>
<bit-callout type="warning">
{{ "singleOrgPolicyWarning" | i18n }}
</bit-callout>
</ng-template>
<bit-form-control>
<input type="checkbox" bitCheckbox [formControl]="enabled" id="enabled" />

View File

@@ -1,4 +1,5 @@
import { Component } from "@angular/core";
import { Component, OnInit } from "@angular/core";
import { firstValueFrom, Observable } from "rxjs";
import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { PolicyRequest } from "@bitwarden/common/admin-console/models/request/policy.request";
@@ -19,7 +20,7 @@ export class SingleOrgPolicy extends BasePolicy {
selector: "policy-single-org",
templateUrl: "single-org.component.html",
})
export class SingleOrgPolicyComponent extends BasePolicyComponent {
export class SingleOrgPolicyComponent extends BasePolicyComponent implements OnInit {
constructor(
private i18nService: I18nService,
private configService: ConfigService,
@@ -27,6 +28,23 @@ export class SingleOrgPolicyComponent extends BasePolicyComponent {
super();
}
protected accountDeprovisioningEnabled$: Observable<boolean> = this.configService.getFeatureFlag$(
FeatureFlag.AccountDeprovisioning,
);
async ngOnInit() {
super.ngOnInit();
const isAccountDeprovisioningEnabled = await firstValueFrom(this.accountDeprovisioningEnabled$);
this.policy.description = isAccountDeprovisioningEnabled
? "singleOrgPolicyDesc"
: "singleOrgDesc";
if (!this.policyResponse.canToggleState) {
this.enabled.disable();
}
}
async buildRequest(policiesEnabledMap: Map<PolicyType, boolean>): Promise<PolicyRequest> {
if (await this.configService.getFeatureFlag(FeatureFlag.Pm13322AddPolicyDefinitions)) {
// We are now relying on server-side validation only
@@ -48,6 +66,15 @@ export class SingleOrgPolicyComponent extends BasePolicyComponent {
),
);
}
if (
(await firstValueFrom(this.accountDeprovisioningEnabled$)) &&
!this.policyResponse.canToggleState
) {
throw new Error(
this.i18nService.t("disableRequiredError", this.i18nService.t("singleOrg")),
);
}
}
return super.buildRequest(policiesEnabledMap);

View File

@@ -1,30 +0,0 @@
<app-header></app-header>
<bit-container>
<p *ngIf="!loaded" class="text-muted">
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span class="tw-sr-only">{{ "loading" | i18n }}</span>
</p>
<ng-container *ngIf="loaded">
<table class="table table-hover table-list" *ngIf="providers && providers.length">
<tbody>
<tr *ngFor="let p of providers">
<td width="30">
<bit-avatar [text]="p.name" [id]="p.id" size="small"></bit-avatar>
</td>
<td>
<a href="#" [routerLink]="['/providers', p.id]">{{ p.name }}</a>
<ng-container *ngIf="!p.enabled">
<i
class="bwi bwi-exclamation-triangle text-danger"
title="{{ 'providerIsDisabled' | i18n }}"
aria-hidden="true"
></i>
<span class="tw-sr-only">{{ "providerIsDisabled" | i18n }}</span>
</ng-container>
</td>
</tr>
</tbody>
</table>
</ng-container>
</bit-container>

View File

@@ -1,33 +0,0 @@
import { Component, OnInit } from "@angular/core";
import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service";
import { Provider } from "@bitwarden/common/admin-console/models/domain/provider";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
@Component({
selector: "app-providers",
templateUrl: "providers.component.html",
})
export class ProvidersComponent implements OnInit {
providers: Provider[];
loaded = false;
actionPromise: Promise<any>;
constructor(
private providerService: ProviderService,
private i18nService: I18nService,
) {}
async ngOnInit() {
document.body.classList.remove("layout_frontend");
await this.load();
}
async load() {
const providers = await this.providerService.getAll();
providers.sort(Utils.getSortFunction(this.i18nService, "name"));
this.providers = providers;
this.loaded = true;
}
}

View File

@@ -1,34 +0,0 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" class="container" ngNativeValidate>
<div class="row justify-content-md-center mt-5">
<div class="col-5">
<p class="lead text-center mb-4">{{ "deleteProvider" | i18n }}</p>
<div class="card">
<div class="card-body">
<app-callout type="warning">{{ "deleteProviderWarning" | i18n }}</app-callout>
<p class="text-center">
<strong>{{ name }}</strong>
</p>
<p>{{ "deleteProviderRecoverConfirmDesc" | i18n }}</p>
<hr />
<div class="d-flex">
<button
type="submit"
class="btn btn-danger btn-block btn-submit"
[disabled]="form.loading"
>
<span>{{ "deleteProvider" | i18n }}</span>
<i
class="bwi bwi-spinner bwi-spin"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
</button>
<a routerLink="/login" class="btn btn-outline-secondary btn-block ml-2 mt-0">
{{ "cancel" | i18n }}
</a>
</div>
</div>
</div>
</div>
</div>
</form>

View File

@@ -1,63 +0,0 @@
import { Component, OnInit } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
import { firstValueFrom } from "rxjs";
import { ProviderApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/provider/provider-api.service.abstraction";
import { ProviderVerifyRecoverDeleteRequest } from "@bitwarden/common/admin-console/models/request/provider/provider-verify-recover-delete.request";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { ToastService } from "@bitwarden/components";
@Component({
selector: "app-verify-recover-delete-provider",
templateUrl: "verify-recover-delete-provider.component.html",
})
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
export class VerifyRecoverDeleteProviderComponent implements OnInit {
name: string;
formPromise: Promise<any>;
private providerId: string;
private token: string;
constructor(
private router: Router,
private providerApiService: ProviderApiServiceAbstraction,
private platformUtilsService: PlatformUtilsService,
private i18nService: I18nService,
private route: ActivatedRoute,
private logService: LogService,
private toastService: ToastService,
) {}
async ngOnInit() {
const qParams = await firstValueFrom(this.route.queryParams);
if (qParams.providerId != null && qParams.token != null && qParams.name != null) {
this.providerId = qParams.providerId;
this.token = qParams.token;
this.name = qParams.name;
} else {
await this.router.navigate(["/"]);
}
}
async submit() {
try {
const request = new ProviderVerifyRecoverDeleteRequest(this.token);
this.formPromise = this.providerApiService.providerRecoverDeleteToken(
this.providerId,
request,
);
await this.formPromise;
this.toastService.showToast({
variant: "success",
title: this.i18nService.t("providerDeleted"),
message: this.i18nService.t("providerDeletedDesc"),
});
await this.router.navigate(["/"]);
} catch (e) {
this.logService.error(e);
}
}
}

View File

@@ -3,7 +3,7 @@
<bit-container>
<app-profile></app-profile>
<div *ngIf="showChangeEmail" class="tw-mt-16">
<div *ngIf="showChangeEmail$ | async" class="tw-mt-16">
<h1 bitTypography="h1">{{ "changeEmail" | i18n }}</h1>
<app-change-email></app-change-email>
</div>

View File

@@ -1,5 +1,5 @@
import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core";
import { lastValueFrom, map, Observable, of, switchMap } from "rxjs";
import { combineLatest, from, lastValueFrom, map, Observable } from "rxjs";
import { ModalService } from "@bitwarden/angular/services/modal.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
@@ -21,7 +21,7 @@ export class AccountComponent implements OnInit {
@ViewChild("deauthorizeSessionsTemplate", { read: ViewContainerRef, static: true })
deauthModalRef: ViewContainerRef;
showChangeEmail = true;
showChangeEmail$: Observable<boolean>;
showPurgeVault$: Observable<boolean>;
constructor(
@@ -33,21 +33,36 @@ export class AccountComponent implements OnInit {
) {}
async ngOnInit() {
this.showChangeEmail = await this.userVerificationService.hasMasterPassword();
this.showPurgeVault$ = this.configService
.getFeatureFlag$(FeatureFlag.AccountDeprovisioning)
.pipe(
switchMap((isAccountDeprovisioningEnabled) =>
isAccountDeprovisioningEnabled
? this.organizationService.organizations$.pipe(
map(
(organizations) =>
!organizations.some((o) => o.userIsManagedByOrganization === true),
),
)
: of(true),
),
);
const isAccountDeprovisioningEnabled$ = this.configService.getFeatureFlag$(
FeatureFlag.AccountDeprovisioning,
);
const userIsManagedByOrganization$ = this.organizationService.organizations$.pipe(
map((organizations) => organizations.some((o) => o.userIsManagedByOrganization === true)),
);
const hasMasterPassword$ = from(this.userVerificationService.hasMasterPassword());
this.showChangeEmail$ = combineLatest([
hasMasterPassword$,
isAccountDeprovisioningEnabled$,
userIsManagedByOrganization$,
]).pipe(
map(
([hasMasterPassword, isAccountDeprovisioningEnabled, userIsManagedByOrganization]) =>
hasMasterPassword && (!isAccountDeprovisioningEnabled || !userIsManagedByOrganization),
),
);
this.showPurgeVault$ = combineLatest([
isAccountDeprovisioningEnabled$,
userIsManagedByOrganization$,
]).pipe(
map(
([isAccountDeprovisioningEnabled, userIsManagedByOrganization]) =>
!isAccountDeprovisioningEnabled || !userIsManagedByOrganization,
),
);
}
async deauthorizeSessions() {

View File

@@ -40,7 +40,6 @@ import { flagEnabled, Flags } from "../utils/flags";
import { VerifyRecoverDeleteOrgComponent } from "./admin-console/organizations/manage/verify-recover-delete-org.component";
import { AcceptFamilySponsorshipComponent } from "./admin-console/organizations/sponsorships/accept-family-sponsorship.component";
import { FamiliesForEnterpriseSetupComponent } from "./admin-console/organizations/sponsorships/families-for-enterprise-setup.component";
import { VerifyRecoverDeleteProviderComponent } from "./admin-console/providers/verify-recover-delete-provider.component";
import { CreateOrganizationComponent } from "./admin-console/settings/create-organization.component";
import { deepLinkGuard } from "./auth/guards/deep-link.guard";
import { HintComponent } from "./auth/hint.component";
@@ -156,12 +155,6 @@ const routes: Routes = [
canActivate: [unauthGuardFn()],
data: { titleId: "deleteOrganization" },
},
{
path: "verify-recover-delete-provider",
component: VerifyRecoverDeleteProviderComponent,
canActivate: [unauthGuardFn()],
data: { titleId: "deleteAccount" } satisfies RouteDataProperties,
},
{
path: "update-temp-password",
component: UpdateTempPasswordComponent,

View File

@@ -17,8 +17,6 @@ import { InactiveTwoFactorReportComponent as OrgInactiveTwoFactorReportComponent
import { ReusedPasswordsReportComponent as OrgReusedPasswordsReportComponent } from "../admin-console/organizations/tools/reused-passwords-report.component";
import { UnsecuredWebsitesReportComponent as OrgUnsecuredWebsitesReportComponent } from "../admin-console/organizations/tools/unsecured-websites-report.component";
import { WeakPasswordsReportComponent as OrgWeakPasswordsReportComponent } from "../admin-console/organizations/tools/weak-passwords-report.component";
import { ProvidersComponent } from "../admin-console/providers/providers.component";
import { VerifyRecoverDeleteProviderComponent } from "../admin-console/providers/verify-recover-delete-provider.component";
import { HintComponent } from "../auth/hint.component";
import { RecoverDeleteComponent } from "../auth/recover-delete.component";
import { RecoverTwoFactorComponent } from "../auth/recover-two-factor.component";
@@ -149,7 +147,6 @@ import { SharedModule } from "./shared.module";
PremiumBadgeComponent,
ProfileComponent,
ChangeAvatarDialogComponent,
ProvidersComponent,
PurgeVaultComponent,
RecoverDeleteComponent,
RecoverTwoFactorComponent,
@@ -176,7 +173,6 @@ import { SharedModule } from "./shared.module";
UpdateTempPasswordComponent,
VerifyEmailTokenComponent,
VerifyRecoverDeleteComponent,
VerifyRecoverDeleteProviderComponent,
],
exports: [
UserVerificationModule,
@@ -218,7 +214,6 @@ import { SharedModule } from "./shared.module";
PremiumBadgeComponent,
ProfileComponent,
ChangeAvatarDialogComponent,
ProvidersComponent,
PurgeVaultComponent,
RecoverDeleteComponent,
RecoverTwoFactorComponent,
@@ -246,7 +241,6 @@ import { SharedModule } from "./shared.module";
UserLayoutComponent,
VerifyEmailTokenComponent,
VerifyRecoverDeleteComponent,
VerifyRecoverDeleteProviderComponent,
HeaderModule,
DangerZoneComponent,
],

View File

@@ -3214,6 +3214,9 @@
}
}
},
"inviteSingleEmailDesc": {
"message": "You have 1 invite remaining."
},
"userUsingTwoStep": {
"message": "This user is using two-step login to protect their account."
},
@@ -4681,12 +4684,18 @@
"singleOrgDesc": {
"message": "Restrict members from joining other organizations."
},
"singleOrgPolicyDesc": {
"message": "Restrict members from joining other organizations. This policy is required for organizations that have enabled domain verification."
},
"singleOrgBlockCreateMessage": {
"message": "Your current organization has a policy that does not allow you to join more than one organization. Please contact your organization admins or sign up from a different Bitwarden account."
},
"singleOrgPolicyWarning": {
"message": "Organization members who are not owners or admins and are already a member of another organization will be removed from your organization."
},
"singleOrgPolicyMemberWarning": {
"message": "Non-compliant members will be placed in revoked status until they leave all other organizations. Administrators are exempt and can restore members once compliance is met."
},
"requireSso": {
"message": "Require single sign-on authentication"
},
@@ -6403,6 +6412,20 @@
"generateEmail": {
"message": "Generate email"
},
"generatorBoundariesHint": {
"message": "Value must be between $MIN$ and $MAX$",
"description": "Explains spin box minimum and maximum values to the user",
"placeholders": {
"min": {
"content": "$1",
"example": "8"
},
"max": {
"content": "$2",
"example": "128"
}
}
},
"usernameType": {
"message": "Username type"
},
@@ -9528,5 +9551,11 @@
},
"selfHostingTitleProper": {
"message": "Self-Hosting"
},
"verified-domain-single-org-warning" : {
"message": "Verifying a domain will turn on the single organization policy."
},
"single-org-revoked-user-warning": {
"message": "Non-compliant members will be revoked. Administrators can restore members once they leave all other organizations."
}
}