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

Merge branch 'main' into mer/browser-refresh/beta-3

This commit is contained in:
Merissa Weinstein
2024-11-25 15:58:21 -06:00
53 changed files with 373 additions and 96 deletions

View File

@@ -8,7 +8,6 @@ storybook-static
**/webpack.*.js
**/jest.config.js
**/gulpfile.js
apps/browser/config/config.js
apps/browser/src/auth/scripts/duo.js

View File

@@ -75,11 +75,6 @@
"concurrently",
"cross-env",
"del",
"gulp",
"gulp-if",
"gulp-json-editor",
"gulp-replace",
"gulp-zip",
"nord",
"patch-package",
"prettier",

View File

@@ -120,7 +120,6 @@ jobs:
whoami
node --version
npm --version
gulp --version
docker --version
echo "GitHub ref: $GITHUB_REF"
echo "GitHub event: $GITHUB_EVENT"

View File

@@ -4888,5 +4888,14 @@
},
"beta": {
"message": "Beta"
},
"extensionWidth": {
"message": "Extension width"
},
"wide": {
"message": "Wide"
},
"extraWide": {
"message": "Extra wide"
}
}

View File

@@ -1,6 +1,7 @@
import { BrowserApi } from "../browser/browser-api";
import { ScrollOptions } from "./abstractions/browser-popup-utils.abstractions";
import { PopupWidthOptions } from "./layout/popup-width.service";
class BrowserPopupUtils {
/**
@@ -108,7 +109,7 @@ class BrowserPopupUtils {
const defaultPopoutWindowOptions: chrome.windows.CreateData = {
type: "popup",
focused: true,
width: 380,
width: Math.max(PopupWidthOptions.default, document.body.clientWidth),
height: 630,
};
const offsetRight = 15;

View File

@@ -514,3 +514,25 @@ export const TransparentHeader: Story = {
`,
}),
};
export const WidthOptions: Story = {
render: (args) => ({
props: args,
template: /* HTML */ `
<div class="tw-flex tw-flex-col tw-gap-4 tw-text-main">
<div>Default:</div>
<div class="tw-h-[640px] tw-w-[380px] tw-border tw-border-solid tw-border-secondary-300">
<mock-vault-page></mock-vault-page>
</div>
<div>Wide:</div>
<div class="tw-h-[640px] tw-w-[480px] tw-border tw-border-solid tw-border-secondary-300">
<mock-vault-page></mock-vault-page>
</div>
<div>Extra wide:</div>
<div class="tw-h-[640px] tw-w-[600px] tw-border tw-border-solid tw-border-secondary-300">
<mock-vault-page></mock-vault-page>
</div>
</div>
`,
}),
};

View File

@@ -0,0 +1,63 @@
import { inject, Injectable } from "@angular/core";
import { map, Observable } from "rxjs";
import {
GlobalStateProvider,
KeyDefinition,
POPUP_STYLE_DISK,
} from "@bitwarden/common/platform/state";
/**
*
* Value represents width in pixels
*/
export const PopupWidthOptions = Object.freeze({
default: 380,
wide: 480,
"extra-wide": 600,
});
type PopupWidthOptions = typeof PopupWidthOptions;
export type PopupWidthOption = keyof PopupWidthOptions;
const POPUP_WIDTH_KEY_DEF = new KeyDefinition<PopupWidthOption>(POPUP_STYLE_DISK, "popup-width", {
deserializer: (s) => s,
});
/**
* Updates the extension popup width based on a user setting
**/
@Injectable({ providedIn: "root" })
export class PopupWidthService {
private static readonly LocalStorageKey = "bw-popup-width";
private readonly state = inject(GlobalStateProvider).get(POPUP_WIDTH_KEY_DEF);
readonly width$: Observable<PopupWidthOption> = this.state.state$.pipe(
map((state) => state ?? "default"),
);
async setWidth(width: PopupWidthOption) {
await this.state.update(() => width);
}
/** Begin listening for state changes */
init() {
this.width$.subscribe((width: PopupWidthOption) => {
PopupWidthService.setStyle(width);
localStorage.setItem(PopupWidthService.LocalStorageKey, width);
});
}
private static setStyle(width: PopupWidthOption) {
const pxWidth = PopupWidthOptions[width] ?? PopupWidthOptions.default;
document.body.style.width = `${pxWidth}px`;
}
/**
* To keep the popup size from flickering on bootstrap, we store the width in `localStorage` so we can quickly & synchronously reference it.
**/
static initBodyWidthFromLocalStorage() {
const storedValue = localStorage.getItem(PopupWidthService.LocalStorageKey);
this.setStyle(storedValue as any);
}
}

View File

@@ -25,6 +25,7 @@ import {
import { flagEnabled } from "../platform/flags";
import { PopupCompactModeService } from "../platform/popup/layout/popup-compact-mode.service";
import { PopupWidthService } from "../platform/popup/layout/popup-width.service";
import { PopupViewCacheService } from "../platform/popup/view-cache/popup-view-cache.service";
import { initPopupClosedListener } from "../platform/services/popup-view-cache-background.service";
import { BrowserSendStateService } from "../tools/popup/services/browser-send-state.service";
@@ -44,6 +45,7 @@ import { DesktopSyncVerificationDialogComponent } from "./components/desktop-syn
export class AppComponent implements OnInit, OnDestroy {
private viewCacheService = inject(PopupViewCacheService);
private compactModeService = inject(PopupCompactModeService);
private widthService = inject(PopupWidthService);
private lastActivity: Date;
private activeUserId: UserId;
@@ -99,6 +101,7 @@ export class AppComponent implements OnInit, OnDestroy {
await this.viewCacheService.init();
this.compactModeService.init();
this.widthService.init();
// Component states must not persist between closing and reopening the popup, otherwise they become dead objects
// Clear them aggressively to make sure this doesn't occur

View File

@@ -1,6 +1,7 @@
import { enableProdMode } from "@angular/core";
import { platformBrowserDynamic } from "@angular/platform-browser-dynamic";
import { PopupWidthService } from "../platform/popup/layout/popup-width.service";
import { BrowserPlatformUtilsService } from "../platform/services/platform-utils/browser-platform-utils.service";
require("./scss/popup.scss");
@@ -8,7 +9,8 @@ require("./scss/tailwind.css");
import { AppModule } from "./app.module";
// We put this first to minimize the delay in window changing.
// We put these first to minimize the delay in window changing.
PopupWidthService.initBodyWidthFromLocalStorage();
// Should be removed once we deprecate support for Safari 16.0 and older. See Jira ticket [PM-1861]
if (BrowserPlatformUtilsService.shouldApplySafariHeightFix(window)) {
document.documentElement.classList.add("safari_height_fix");

View File

@@ -19,7 +19,7 @@ body {
}
body {
width: 380px !important;
min-width: 380px !important;
height: 600px !important;
position: relative;
min-height: 100vh;

View File

@@ -0,0 +1,83 @@
import { TestBed } from "@angular/core/testing";
import { mock, MockProxy } from "jest-mock-extended";
import { firstValueFrom, of } from "rxjs";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
import { FamiliesPolicyService } from "./families-policy.service"; // Adjust the import as necessary
describe("FamiliesPolicyService", () => {
let service: FamiliesPolicyService;
let organizationService: MockProxy<OrganizationService>;
let policyService: MockProxy<PolicyService>;
beforeEach(() => {
organizationService = mock<OrganizationService>();
policyService = mock<PolicyService>();
TestBed.configureTestingModule({
providers: [
FamiliesPolicyService,
{ provide: OrganizationService, useValue: organizationService },
{ provide: PolicyService, useValue: policyService },
],
});
service = TestBed.inject(FamiliesPolicyService);
});
it("should return false when there are no enterprise organizations", async () => {
jest.spyOn(service, "hasSingleEnterpriseOrg$").mockReturnValue(of(false));
const result = await firstValueFrom(service.isFreeFamilyPolicyEnabled$());
expect(result).toBe(false);
});
it("should return true when the policy is enabled for the one enterprise organization", async () => {
jest.spyOn(service, "hasSingleEnterpriseOrg$").mockReturnValue(of(true));
const organizations = [{ id: "org1", canManageSponsorships: true }] as Organization[];
organizationService.getAll$.mockReturnValue(of(organizations));
const policies = [{ organizationId: "org1", enabled: true }] as Policy[];
policyService.getAll$.mockReturnValue(of(policies));
const result = await firstValueFrom(service.isFreeFamilyPolicyEnabled$());
expect(result).toBe(true);
});
it("should return false when the policy is not enabled for the one enterprise organization", async () => {
jest.spyOn(service, "hasSingleEnterpriseOrg$").mockReturnValue(of(true));
const organizations = [{ id: "org1", canManageSponsorships: true }] as Organization[];
organizationService.getAll$.mockReturnValue(of(organizations));
const policies = [{ organizationId: "org1", enabled: false }] as Policy[];
policyService.getAll$.mockReturnValue(of(policies));
const result = await firstValueFrom(service.isFreeFamilyPolicyEnabled$());
expect(result).toBe(false);
});
it("should return true when there is exactly one enterprise organization that can manage sponsorships", async () => {
const organizations = [{ id: "org1", canManageSponsorships: true }] as Organization[];
organizationService.getAll$.mockReturnValue(of(organizations));
const result = await firstValueFrom(service.hasSingleEnterpriseOrg$());
expect(result).toBe(true);
});
it("should return false when there are multiple organizations that can manage sponsorships", async () => {
const organizations = [
{ id: "org1", canManageSponsorships: true },
{ id: "org2", canManageSponsorships: true },
] as Organization[];
organizationService.getAll$.mockReturnValue(of(organizations));
const result = await firstValueFrom(service.hasSingleEnterpriseOrg$());
expect(result).toBe(false);
});
});

View File

@@ -0,0 +1,54 @@
import { Injectable } from "@angular/core";
import { map, Observable, of, switchMap } from "rxjs";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { PolicyType } from "@bitwarden/common/admin-console/enums";
@Injectable({ providedIn: "root" })
export class FamiliesPolicyService {
constructor(
private policyService: PolicyService,
private organizationService: OrganizationService,
) {}
hasSingleEnterpriseOrg$(): Observable<boolean> {
// Retrieve all organizations the user is part of
return this.organizationService.getAll$().pipe(
map((organizations) => {
// Filter to only those organizations that can manage sponsorships
const sponsorshipOrgs = organizations.filter((org) => org.canManageSponsorships);
// Check if there is exactly one organization that can manage sponsorships.
// This is important because users that are part of multiple organizations
// may always access free bitwarden family menu. We want to restrict access
// to the policy only when there is a single enterprise organization and the free family policy is turn.
return sponsorshipOrgs.length === 1;
}),
);
}
isFreeFamilyPolicyEnabled$(): Observable<boolean> {
return this.hasSingleEnterpriseOrg$().pipe(
switchMap((hasSingleEnterpriseOrg) => {
if (!hasSingleEnterpriseOrg) {
return of(false);
}
return this.organizationService.getAll$().pipe(
map((organizations) => organizations.find((org) => org.canManageSponsorships)?.id),
switchMap((enterpriseOrgId) =>
this.policyService
.getAll$(PolicyType.FreeFamiliesSponsorshipPolicy)
.pipe(
map(
(policies) =>
policies.find((policy) => policy.organizationId === enterpriseOrgId)?.enabled ??
false,
),
),
),
);
}),
);
}
}

View File

@@ -12,7 +12,12 @@
<i slot="end" class="bwi bwi-angle-right" aria-hidden="true"></i>
</a>
</bit-item>
<bit-item *ngIf="familySponsorshipAvailable$ | async">
<bit-item
*ngIf="
(familySponsorshipAvailable$ | async) &&
!((isFreeFamilyPolicyEnabled$ | async) && (hasSingleEnterpriseOrg$ | async))
"
>
<button type="button" bit-item-content (click)="openFreeBitwardenFamiliesPage()">
{{ "freeBitwardenFamilies" | i18n }}
<i slot="end" class="bwi bwi-external-link" aria-hidden="true"></i>

View File

@@ -13,6 +13,7 @@ import { BrowserApi } from "../../../../platform/browser/browser-api";
import { PopOutComponent } from "../../../../platform/popup/components/pop-out.component";
import { PopupHeaderComponent } from "../../../../platform/popup/layout/popup-header.component";
import { PopupPageComponent } from "../../../../platform/popup/layout/popup-page.component";
import { FamiliesPolicyService } from "../../../../services/families-policy.service";
@Component({
templateUrl: "more-from-bitwarden-page-v2.component.html",
@@ -30,15 +31,20 @@ import { PopupPageComponent } from "../../../../platform/popup/layout/popup-page
export class MoreFromBitwardenPageV2Component {
canAccessPremium$: Observable<boolean>;
protected familySponsorshipAvailable$: Observable<boolean>;
protected isFreeFamilyPolicyEnabled$: Observable<boolean>;
protected hasSingleEnterpriseOrg$: Observable<boolean>;
constructor(
private dialogService: DialogService,
billingAccountProfileStateService: BillingAccountProfileStateService,
private environmentService: EnvironmentService,
private organizationService: OrganizationService,
private familiesPolicyService: FamiliesPolicyService,
) {
this.canAccessPremium$ = billingAccountProfileStateService.hasPremiumFromAnySource$;
this.familySponsorshipAvailable$ = this.organizationService.familySponsorshipAvailable$;
this.hasSingleEnterpriseOrg$ = this.familiesPolicyService.hasSingleEnterpriseOrg$();
this.isFreeFamilyPolicyEnabled$ = this.familiesPolicyService.isFreeFamilyPolicyEnabled$();
}
async openFreeBitwardenFamiliesPage() {

View File

@@ -30,6 +30,7 @@
class="box-content-row box-content-row-flex text-default"
appStopClick
(click)="openFreeBitwardenFamiliesPage()"
*ngIf="!((isFreeFamilyPolicyEnabled$ | async) && (hasSingleEnterpriseOrg$ | async))"
>
<div class="row-main">{{ "freeBitwardenFamilies" | i18n }}</div>
<i class="bwi bwi-external-link bwi-lg row-sub-icon" aria-hidden="true"></i>

View File

@@ -10,6 +10,7 @@ import { DialogService } from "@bitwarden/components";
import { BrowserApi } from "../../../../platform/browser/browser-api";
import { PopOutComponent } from "../../../../platform/popup/components/pop-out.component";
import { FamiliesPolicyService } from "../../../../services/families-policy.service";
@Component({
templateUrl: "more-from-bitwarden-page.component.html",
@@ -18,13 +19,18 @@ import { PopOutComponent } from "../../../../platform/popup/components/pop-out.c
})
export class MoreFromBitwardenPageComponent {
canAccessPremium$: Observable<boolean>;
protected isFreeFamilyPolicyEnabled$: Observable<boolean>;
protected hasSingleEnterpriseOrg$: Observable<boolean>;
constructor(
private dialogService: DialogService,
private billingAccountProfileStateService: BillingAccountProfileStateService,
private environmentService: EnvironmentService,
private familiesPolicyService: FamiliesPolicyService,
) {
this.canAccessPremium$ = billingAccountProfileStateService.hasPremiumFromAnySource$;
this.hasSingleEnterpriseOrg$ = this.familiesPolicyService.hasSingleEnterpriseOrg$();
this.isFreeFamilyPolicyEnabled$ = this.familiesPolicyService.isFreeFamilyPolicyEnabled$();
}
async openFreeBitwardenFamiliesPage() {

View File

@@ -18,6 +18,11 @@
</bit-select>
</bit-form-field>
<bit-form-field>
<bit-label>{{ "extensionWidth" | i18n }}</bit-label>
<bit-select formControlName="width" [items]="widthOptions"></bit-select>
</bit-form-field>
<bit-form-control>
<input bitCheckbox formControlName="enableCompactMode" type="checkbox" />
<bit-label

View File

@@ -16,6 +16,7 @@ import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-stat
import { PopupCompactModeService } from "../../../platform/popup/layout/popup-compact-mode.service";
import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component";
import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component";
import { PopupWidthService } from "../../../platform/popup/layout/popup-width.service";
import { AppearanceV2Component } from "./appearance-v2.component";
@@ -51,6 +52,11 @@ describe("AppearanceV2Component", () => {
const setEnableRoutingAnimation = jest.fn().mockResolvedValue(undefined);
const setEnableCompactMode = jest.fn().mockResolvedValue(undefined);
const mockWidthService: Partial<PopupWidthService> = {
width$: new BehaviorSubject("default"),
setWidth: jest.fn().mockResolvedValue(undefined),
};
beforeEach(async () => {
setSelectedTheme.mockClear();
setShowFavicons.mockClear();
@@ -78,6 +84,10 @@ describe("AppearanceV2Component", () => {
provide: PopupCompactModeService,
useValue: { enabled$: enableCompactMode$, setEnabled: setEnableCompactMode },
},
{
provide: PopupWidthService,
useValue: mockWidthService,
},
],
})
.overrideComponent(AppearanceV2Component, {
@@ -102,6 +112,7 @@ describe("AppearanceV2Component", () => {
enableBadgeCounter: true,
theme: ThemeType.Nord,
enableCompactMode: false,
width: "default",
});
});

View File

@@ -12,7 +12,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { ThemeType } from "@bitwarden/common/platform/enums";
import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service";
import { BadgeModule, CheckboxModule } from "@bitwarden/components";
import { BadgeModule, CheckboxModule, Option } from "@bitwarden/components";
import { CardComponent } from "../../../../../../libs/components/src/card/card.component";
import { FormFieldModule } from "../../../../../../libs/components/src/form-field/form-field.module";
@@ -21,6 +21,10 @@ import { PopOutComponent } from "../../../platform/popup/components/pop-out.comp
import { PopupCompactModeService } from "../../../platform/popup/layout/popup-compact-mode.service";
import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component";
import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component";
import {
PopupWidthOption,
PopupWidthService,
} from "../../../platform/popup/layout/popup-width.service";
@Component({
standalone: true,
@@ -41,6 +45,8 @@ import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.co
})
export class AppearanceV2Component implements OnInit {
private compactModeService = inject(PopupCompactModeService);
private popupWidthService = inject(PopupWidthService);
private i18nService = inject(I18nService);
appearanceForm = this.formBuilder.group({
enableFavicon: false,
@@ -48,6 +54,7 @@ export class AppearanceV2Component implements OnInit {
theme: ThemeType.System,
enableAnimations: true,
enableCompactMode: false,
width: "default" as PopupWidthOption,
});
/** To avoid flashes of inaccurate values, only show the form after the entire form is populated. */
@@ -56,6 +63,13 @@ export class AppearanceV2Component implements OnInit {
/** Available theme options */
themeOptions: { name: string; value: ThemeType }[];
/** Available width options */
protected readonly widthOptions: Option<PopupWidthOption>[] = [
{ label: this.i18nService.t("default"), value: "default" },
{ label: this.i18nService.t("wide"), value: "wide" },
{ label: this.i18nService.t("extraWide"), value: "extra-wide" },
];
constructor(
private messagingService: MessagingService,
private domainSettingsService: DomainSettingsService,
@@ -81,6 +95,7 @@ export class AppearanceV2Component implements OnInit {
this.animationControlService.enableRoutingAnimation$,
);
const enableCompactMode = await firstValueFrom(this.compactModeService.enabled$);
const width = await firstValueFrom(this.popupWidthService.width$);
// Set initial values for the form
this.appearanceForm.setValue({
@@ -89,6 +104,7 @@ export class AppearanceV2Component implements OnInit {
theme,
enableAnimations,
enableCompactMode,
width,
});
this.formLoading = false;
@@ -122,6 +138,12 @@ export class AppearanceV2Component implements OnInit {
.subscribe((enableCompactMode) => {
void this.updateCompactMode(enableCompactMode);
});
this.appearanceForm.controls.width.valueChanges
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((width) => {
void this.updateWidth(width);
});
}
async updateFavicon(enableFavicon: boolean) {
@@ -144,4 +166,8 @@ export class AppearanceV2Component implements OnInit {
async updateCompactMode(enableCompactMode: boolean) {
await this.compactModeService.setEnabled(enableCompactMode);
}
async updateWidth(width: PopupWidthOption) {
await this.popupWidthService.setWidth(width);
}
}

View File

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

View File

@@ -53,7 +53,7 @@ ssh-key = { version = "=0.6.6", default-features = false, features = [
bitwarden-russh = { git = "https://github.com/bitwarden/bitwarden-russh.git", branch = "km/pm-10098/clean-russh-implementation" }
tokio = { version = "=1.40.0", features = ["io-util", "sync", "macros", "net"] }
tokio-stream = { version = "=0.1.15", features = ["net"] }
tokio-util = { version = "0.7.11", features = ["codec"] }
tokio-util = { version = "=0.7.12", features = ["codec"] }
thiserror = "=1.0.69"
typenum = "=1.17.0"
rand_chacha = "=0.3.1"

View File

@@ -18,7 +18,7 @@
"yargs": "17.7.2"
},
"devDependencies": {
"@types/node": "22.9.0",
"@types/node": "22.9.3",
"@types/node-ipc": "9.2.3",
"typescript": "4.7.4"
}
@@ -106,9 +106,9 @@
"license": "MIT"
},
"node_modules/@types/node": {
"version": "22.9.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.0.tgz",
"integrity": "sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==",
"version": "22.9.3",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.3.tgz",
"integrity": "sha512-F3u1fs/fce3FFk+DAxbxc78DF8x0cY09RRL8GnXLmkJ1jvx3TtPdWoTT5/NiYfI5ASqXBmfqJi9dZ3gxMx4lzw==",
"license": "MIT",
"dependencies": {
"undici-types": "~6.19.8"

View File

@@ -23,7 +23,7 @@
"yargs": "17.7.2"
},
"devDependencies": {
"@types/node": "22.9.0",
"@types/node": "22.9.3",
"@types/node-ipc": "9.2.3",
"typescript": "4.7.4"
},

View File

@@ -1,6 +1,6 @@
{
"name": "@bitwarden/web-vault",
"version": "2024.11.1",
"version": "2024.11.2",
"scripts": {
"build:oss": "webpack",
"build:bit": "webpack -c ../../bitwarden_license/bit-web/webpack.config.js",

View File

@@ -62,13 +62,6 @@ const routes: Routes = [
(m) => m.OrganizationReportingModule,
),
},
{
path: "access-intelligence",
loadChildren: () =>
import("../../tools/access-intelligence/access-intelligence.module").then(
(m) => m.AccessIntelligenceModule,
),
},
{
path: "billing",
loadChildren: () =>

View File

@@ -51,14 +51,12 @@ window.addEventListener("load", async () => {
*/
function redirectToDuoFrameless(redirectUrl: string) {
const validateUrl = new URL(redirectUrl);
const validDuoUrl =
validateUrl.protocol === "https:" &&
(validateUrl.hostname.endsWith(".duosecurity.com") ||
validateUrl.hostname.endsWith(".duofederal.com"));
if (
validateUrl.protocol !== "https:" ||
!(
validateUrl.hostname.endsWith("duosecurity.com") ||
validateUrl.hostname.endsWith("duofederal.com")
)
) {
if (!validDuoUrl) {
throw new Error("Invalid redirect URL");
}

View File

@@ -76,6 +76,13 @@ const routes: Routes = [
},
],
},
{
path: "access-intelligence",
loadChildren: () =>
import("../../tools/access-intelligence/access-intelligence.module").then(
(m) => m.AccessIntelligenceModule,
),
},
],
},
];

View File

@@ -21,10 +21,9 @@ import {
ToastService,
} from "@bitwarden/components";
import { CardComponent } from "@bitwarden/tools-card";
import { HeaderModule } from "../../layouts/header/header.module";
import { SharedModule } from "../../shared";
import { PipesModule } from "../../vault/individual-vault/pipes/pipes.module";
import { HeaderModule } from "@bitwarden/web-vault/app/layouts/header/header.module";
import { SharedModule } from "@bitwarden/web-vault/app/shared";
import { PipesModule } from "@bitwarden/web-vault/app/vault/individual-vault/pipes/pipes.module";
import { applicationTableMockData } from "./application-table.mock";

View File

@@ -7,10 +7,9 @@ import { debounceTime, map } from "rxjs";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { SearchModule, TableDataSource, NoItemsModule, Icons } from "@bitwarden/components";
import { CardComponent } from "@bitwarden/tools-card";
import { HeaderModule } from "../../layouts/header/header.module";
import { SharedModule } from "../../shared";
import { PipesModule } from "../../vault/individual-vault/pipes/pipes.module";
import { HeaderModule } from "@bitwarden/web-vault/app/layouts/header/header.module";
import { SharedModule } from "@bitwarden/web-vault/app/shared";
import { PipesModule } from "@bitwarden/web-vault/app/vault/individual-vault/pipes/pipes.module";
import { applicationTableMockData } from "./application-table.mock";
import { RiskInsightsTabType } from "./risk-insights.component";

View File

@@ -3,7 +3,6 @@ import { ActivatedRoute, convertToParamMap } from "@angular/router";
import { mock, MockProxy } from "jest-mock-extended";
import { of } from "rxjs";
// eslint-disable-next-line no-restricted-imports
import {
MemberCipherDetailsApiService,
PasswordHealthService,
@@ -15,9 +14,8 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { TableModule } from "@bitwarden/components";
import { LooseComponentsModule } from "../../shared";
import { PipesModule } from "../../vault/individual-vault/pipes/pipes.module";
import { LooseComponentsModule } from "@bitwarden/web-vault/app/shared";
import { PipesModule } from "@bitwarden/web-vault/app/vault/individual-vault/pipes/pipes.module";
import { PasswordHealthMembersURIComponent } from "./password-health-members-uri.component";

View File

@@ -5,7 +5,6 @@ import { ActivatedRoute } from "@angular/router";
import { map } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
// eslint-disable-next-line no-restricted-imports
import {
MemberCipherDetailsApiService,
PasswordHealthService,
@@ -24,11 +23,8 @@ import {
TableDataSource,
TableModule,
} from "@bitwarden/components";
// eslint-disable-next-line no-restricted-imports
import { HeaderModule } from "../../layouts/header/header.module";
// eslint-disable-next-line no-restricted-imports
import { PipesModule } from "../../vault/individual-vault/pipes/pipes.module";
import { HeaderModule } from "@bitwarden/web-vault/app/layouts/header/header.module";
import { PipesModule } from "@bitwarden/web-vault/app/vault/individual-vault/pipes/pipes.module";
@Component({
standalone: true,

View File

@@ -4,7 +4,6 @@ import { FormControl, FormsModule } from "@angular/forms";
import { ActivatedRoute } from "@angular/router";
import { debounceTime, map } from "rxjs";
// eslint-disable-next-line no-restricted-imports
import {
MemberCipherDetailsApiService,
PasswordHealthService,
@@ -21,12 +20,9 @@ import {
TableModule,
ToastService,
} from "@bitwarden/components";
import { HeaderModule } from "../../layouts/header/header.module";
// eslint-disable-next-line no-restricted-imports
import { SharedModule } from "../../shared";
// eslint-disable-next-line no-restricted-imports
import { PipesModule } from "../../vault/individual-vault/pipes/pipes.module";
import { HeaderModule } from "@bitwarden/web-vault/app/layouts/header/header.module";
import { SharedModule } from "@bitwarden/web-vault/app/shared";
import { PipesModule } from "@bitwarden/web-vault/app/vault/individual-vault/pipes/pipes.module";
@Component({
standalone: true,

View File

@@ -3,7 +3,6 @@ import { ActivatedRoute, convertToParamMap } from "@angular/router";
import { mock } from "jest-mock-extended";
import { of } from "rxjs";
// eslint-disable-next-line no-restricted-imports
import {
MemberCipherDetailsApiService,
PasswordHealthService,
@@ -15,9 +14,8 @@ import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/pass
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { TableModule } from "@bitwarden/components";
import { TableBodyDirective } from "@bitwarden/components/src/table/table.component";
import { LooseComponentsModule } from "../../shared";
import { PipesModule } from "../../vault/individual-vault/pipes/pipes.module";
import { LooseComponentsModule } from "@bitwarden/web-vault/app/shared";
import { PipesModule } from "@bitwarden/web-vault/app/vault/individual-vault/pipes/pipes.module";
import { PasswordHealthComponent } from "./password-health.component";

View File

@@ -5,7 +5,6 @@ import { ActivatedRoute } from "@angular/router";
import { map } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
// eslint-disable-next-line no-restricted-imports
import {
MemberCipherDetailsApiService,
PasswordHealthService,
@@ -22,13 +21,9 @@ import {
TableDataSource,
TableModule,
} from "@bitwarden/components";
// eslint-disable-next-line no-restricted-imports
import { HeaderModule } from "../../layouts/header/header.module";
// eslint-disable-next-line no-restricted-imports
import { OrganizationBadgeModule } from "../../vault/individual-vault/organization-badge/organization-badge.module";
// eslint-disable-next-line no-restricted-imports
import { PipesModule } from "../../vault/individual-vault/pipes/pipes.module";
import { HeaderModule } from "@bitwarden/web-vault/app/layouts/header/header.module";
import { OrganizationBadgeModule } from "@bitwarden/web-vault/app/vault/individual-vault/organization-badge/organization-badge.module";
import { PipesModule } from "@bitwarden/web-vault/app/vault/individual-vault/pipes/pipes.module";
@Component({
standalone: true,

View File

@@ -7,8 +7,7 @@ import { JslibModule } from "@bitwarden/angular/jslib.module";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { AsyncActionsModule, ButtonModule, TabsModule } from "@bitwarden/components";
import { HeaderModule } from "../../layouts/header/header.module";
import { HeaderModule } from "@bitwarden/web-vault/app/layouts/header/header.module";
import { AllApplicationsComponent } from "./all-applications.component";
import { CriticalApplicationsComponent } from "./critical-applications.component";

View File

@@ -228,6 +228,7 @@ export class LockV2Component implements OnInit, OnDestroy {
this.unlockOptions = null;
this.activeUnlockOption = null;
this.formGroup = null; // new form group will be created based on new active unlock option
this.isInitialLockScreen = true;
// Desktop properties:
this.biometricAsked = false;

View File

@@ -238,6 +238,9 @@ export class PolicyService implements InternalPolicyServiceAbstraction {
case PolicyType.PersonalOwnership:
// individual vault policy applies to everyone except admins and owners
return organization.isAdmin;
case PolicyType.FreeFamiliesSponsorshipPolicy:
// free Bitwarden families policy applies to everyone
return false;
default:
return organization.canManagePolicies;
}

View File

@@ -121,6 +121,10 @@ export const TRANSLATION_DISK = new StateDefinition("translation", "disk", { web
export const ANIMATION_DISK = new StateDefinition("animation", "disk");
export const TASK_SCHEDULER_DISK = new StateDefinition("taskScheduler", "disk");
// Design System
export const POPUP_STYLE_DISK = new StateDefinition("popupStyle", "disk");
// Secrets Manager
export const SM_ONBOARDING_DISK = new StateDefinition("smOnboarding", "disk", {

View File

@@ -1,3 +1,4 @@
export * from "./select.module";
export * from "./select.component";
export * from "./option";
export * from "./option.component";

View File

@@ -13,7 +13,7 @@ export const Ascii = Object.freeze({
/** The full set of characters available to the generator */
Full: Object.freeze({
Uppercase: toCharacterSet("ABCDEFGHIJKLMNOPQRSTUVWXYZ"),
Lowercase: toCharacterSet("abcdefghijkmnopqrstuvwxyz"),
Lowercase: toCharacterSet("abcdefghijklmnopqrstuvwxyz"),
Digit: toCharacterSet("0123456789"),
Special: SpecialCharacters,
} as CharacterSets),
@@ -21,7 +21,7 @@ export const Ascii = Object.freeze({
/** All characters available to the generator that are not ambiguous. */
Unmistakable: Object.freeze({
Uppercase: toCharacterSet("ABCDEFGHJKLMNPQRSTUVWXYZ"),
Lowercase: toCharacterSet("abcdefghijklmnopqrstuvwxyz"),
Lowercase: toCharacterSet("abcdefghijkmnopqrstuvwxyz"),
Digit: toCharacterSet("23456789"),
Special: SpecialCharacters,
} as CharacterSets),

36
package-lock.json generated
View File

@@ -68,7 +68,7 @@
"qrious": "4.0.2",
"rxjs": "7.8.1",
"tabbable": "6.2.0",
"tldts": "6.1.61",
"tldts": "6.1.64",
"utf-8-validate": "6.0.5",
"zone.js": "0.14.10",
"zxcvbn": "4.4.2"
@@ -111,7 +111,7 @@
"@types/koa-json": "2.0.23",
"@types/lowdb": "1.0.15",
"@types/lunr": "2.3.7",
"@types/node": "22.9.0",
"@types/node": "22.9.3",
"@types/node-fetch": "2.6.4",
"@types/node-forge": "1.3.11",
"@types/node-ipc": "9.2.3",
@@ -160,7 +160,7 @@
"postcss": "8.4.47",
"postcss-loader": "8.1.1",
"prettier": "3.3.3",
"prettier-plugin-tailwindcss": "0.6.8",
"prettier-plugin-tailwindcss": "0.6.9",
"process": "0.11.10",
"remark-gfm": "4.0.0",
"rimraf": "6.0.1",
@@ -220,7 +220,7 @@
"papaparse": "5.4.1",
"proper-lockfile": "4.1.2",
"rxjs": "7.8.1",
"tldts": "6.1.61",
"tldts": "6.1.64",
"zxcvbn": "4.4.2"
},
"bin": {
@@ -243,7 +243,7 @@
},
"apps/web": {
"name": "@bitwarden/web-vault",
"version": "2024.11.1"
"version": "2024.11.2"
},
"libs/admin-console": {
"name": "@bitwarden/admin-console",
@@ -9751,9 +9751,9 @@
"license": "MIT"
},
"node_modules/@types/node": {
"version": "22.9.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.0.tgz",
"integrity": "sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==",
"version": "22.9.3",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.3.tgz",
"integrity": "sha512-F3u1fs/fce3FFk+DAxbxc78DF8x0cY09RRL8GnXLmkJ1jvx3TtPdWoTT5/NiYfI5ASqXBmfqJi9dZ3gxMx4lzw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -27911,9 +27911,9 @@
}
},
"node_modules/prettier-plugin-tailwindcss": {
"version": "0.6.8",
"resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.8.tgz",
"integrity": "sha512-dGu3kdm7SXPkiW4nzeWKCl3uoImdd5CTZEJGxyypEPL37Wj0HT2pLqjrvSei1nTeuQfO4PUfjeW5cTUNRLZ4sA==",
"version": "0.6.9",
"resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.9.tgz",
"integrity": "sha512-r0i3uhaZAXYP0At5xGfJH876W3HHGHDp+LCRUJrs57PBeQ6mYHMwr25KH8NPX44F2yGTvdnH7OqCshlQx183Eg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -31664,21 +31664,21 @@
}
},
"node_modules/tldts": {
"version": "6.1.61",
"resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.61.tgz",
"integrity": "sha512-rv8LUyez4Ygkopqn+M6OLItAOT9FF3REpPQDkdMx5ix8w4qkuE7Vo2o/vw1nxKQYmJDV8JpAMJQr1b+lTKf0FA==",
"version": "6.1.64",
"resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.64.tgz",
"integrity": "sha512-ph4AE5BXWIOsSy9stpoeo7bYe/Cy7VfpciIH4RhVZUPItCJmhqWCN0EVzxd8BOHiyNb42vuJc6NWTjJkg91Tuw==",
"license": "MIT",
"dependencies": {
"tldts-core": "^6.1.61"
"tldts-core": "^6.1.64"
},
"bin": {
"tldts": "bin/cli.js"
}
},
"node_modules/tldts-core": {
"version": "6.1.61",
"resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.61.tgz",
"integrity": "sha512-In7VffkDWUPgwa+c9picLUxvb0RltVwTkSgMNFgvlGSWveCzGBemBqTsgJCL4EDFWZ6WH0fKTsot6yNhzy3ZzQ==",
"version": "6.1.64",
"resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.64.tgz",
"integrity": "sha512-uqnl8vGV16KsyflHOzqrYjjArjfXaU6rMPXYy2/ZWoRKCkXtghgB4VwTDXUG+t0OTGeSewNAG31/x1gCTfLt+Q==",
"license": "MIT"
},
"node_modules/tmp": {

View File

@@ -72,7 +72,7 @@
"@types/koa-json": "2.0.23",
"@types/lowdb": "1.0.15",
"@types/lunr": "2.3.7",
"@types/node": "22.9.0",
"@types/node": "22.9.3",
"@types/node-fetch": "2.6.4",
"@types/node-forge": "1.3.11",
"@types/node-ipc": "9.2.3",
@@ -121,7 +121,7 @@
"postcss": "8.4.47",
"postcss-loader": "8.1.1",
"prettier": "3.3.3",
"prettier-plugin-tailwindcss": "0.6.8",
"prettier-plugin-tailwindcss": "0.6.9",
"process": "0.11.10",
"remark-gfm": "4.0.0",
"rimraf": "6.0.1",
@@ -197,7 +197,7 @@
"qrious": "4.0.2",
"rxjs": "7.8.1",
"tabbable": "6.2.0",
"tldts": "6.1.61",
"tldts": "6.1.64",
"utf-8-validate": "6.0.5",
"zone.js": "0.14.10",
"zxcvbn": "4.4.2"