mirror of
https://github.com/bitwarden/browser
synced 2026-02-24 08:33:29 +00:00
merge in refactor branch
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@bitwarden/web-vault",
|
||||
"version": "2024.11.2",
|
||||
"version": "2024.12.0",
|
||||
"scripts": {
|
||||
"build:oss": "webpack",
|
||||
"build:bit": "webpack -c ../../bitwarden_license/bit-web/webpack.config.js",
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Directive } from "@angular/core";
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { FormControl } from "@angular/forms";
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Directive } from "@angular/core";
|
||||
import { FormControl, FormGroup } from "@angular/forms";
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Directive, ViewChild, ViewContainerRef } from "@angular/core";
|
||||
import { FormControl } from "@angular/forms";
|
||||
import { firstValueFrom, concatMap, map, lastValueFrom, startWith, debounceTime } from "rxjs";
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import {
|
||||
OrganizationUserStatusType,
|
||||
ProviderUserStatusType,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { SelectionReadOnlyRequest } from "@bitwarden/common/admin-console/models/request/selection-read-only.request";
|
||||
|
||||
export class GroupRequest {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { CollectionAccessSelectionView } from "@bitwarden/admin-console/common";
|
||||
import { View } from "@bitwarden/common/models/view/view";
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { View } from "@bitwarden/common/models/view/view";
|
||||
|
||||
import { GroupResponse } from "../services/group/responses/group.response";
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import {
|
||||
CollectionAccessSelectionView,
|
||||
OrganizationUserDetailsResponse,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import {
|
||||
OrganizationUserUserDetailsResponse,
|
||||
CollectionAccessSelectionView,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core";
|
||||
import { UntypedFormGroup } from "@angular/forms";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Component } from "@angular/core";
|
||||
import { TestBed } from "@angular/core/testing";
|
||||
import { provideRouter } from "@angular/router";
|
||||
@@ -59,8 +61,14 @@ describe("Is Enterprise Org Guard", () => {
|
||||
{
|
||||
path: "organizations/:organizationId/enterpriseOrgsOnly",
|
||||
component: IsEnterpriseOrganizationComponent,
|
||||
canActivate: [isEnterpriseOrgGuard()],
|
||||
canActivate: [isEnterpriseOrgGuard(true)],
|
||||
},
|
||||
{
|
||||
path: "organizations/:organizationId/enterpriseOrgsOnlyNoError",
|
||||
component: IsEnterpriseOrganizationComponent,
|
||||
canActivate: [isEnterpriseOrgGuard(false)],
|
||||
},
|
||||
|
||||
{
|
||||
path: "organizations/:organizationId/billing/subscription",
|
||||
component: OrganizationUpgradeScreenComponent,
|
||||
@@ -115,6 +123,24 @@ describe("Is Enterprise Org Guard", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it.each([
|
||||
ProductTierType.Free,
|
||||
ProductTierType.Families,
|
||||
ProductTierType.Teams,
|
||||
ProductTierType.TeamsStarter,
|
||||
])("does not proceed with the navigation for productTierType '%s'", async (productTierType) => {
|
||||
const org = orgFactory({
|
||||
type: OrganizationUserType.User,
|
||||
productTierType: productTierType,
|
||||
});
|
||||
organizationService.get.calledWith(org.id).mockResolvedValue(org);
|
||||
await routerHarness.navigateByUrl(`organizations/${org.id}/enterpriseOrgsOnlyNoError`);
|
||||
expect(dialogService.openSimpleDialog).not.toHaveBeenCalled();
|
||||
expect(
|
||||
routerHarness.routeNativeElement?.querySelector("h1")?.textContent?.trim() ?? "",
|
||||
).not.toBe("This component can only be accessed by a enterprise organization!");
|
||||
});
|
||||
|
||||
it("proceeds with navigation if the organization in question is a enterprise organization", async () => {
|
||||
const org = orgFactory({ productTierType: ProductTierType.Enterprise });
|
||||
organizationService.get.calledWith(org.id).mockResolvedValue(org);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { inject } from "@angular/core";
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
@@ -17,7 +19,7 @@ import { DialogService } from "@bitwarden/components";
|
||||
* if they have access to upgrade the organization. If the organization is
|
||||
* enterprise routing proceeds."
|
||||
*/
|
||||
export function isEnterpriseOrgGuard(): CanActivateFn {
|
||||
export function isEnterpriseOrgGuard(showError: boolean = true): CanActivateFn {
|
||||
return async (route: ActivatedRouteSnapshot, _state: RouterStateSnapshot) => {
|
||||
const router = inject(Router);
|
||||
const organizationService = inject(OrganizationService);
|
||||
@@ -29,7 +31,7 @@ export function isEnterpriseOrgGuard(): CanActivateFn {
|
||||
return router.createUrlTree(["/"]);
|
||||
}
|
||||
|
||||
if (org.productTierType != ProductTierType.Enterprise) {
|
||||
if (org.productTierType != ProductTierType.Enterprise && showError) {
|
||||
// Users without billing permission can't access billing
|
||||
if (!org.canEditSubscription) {
|
||||
await dialogService.openSimpleDialog({
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Component } from "@angular/core";
|
||||
import { TestBed } from "@angular/core/testing";
|
||||
import { provideRouter } from "@angular/router";
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { inject } from "@angular/core";
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { TestBed } from "@angular/core/testing";
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { inject } from "@angular/core";
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Component } from "@angular/core";
|
||||
import { TestBed } from "@angular/core/testing";
|
||||
import { provideRouter } from "@angular/router";
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
<app-header> </app-header>
|
||||
|
||||
<bit-tab-group [(selectedIndex)]="tabIndex">
|
||||
<bit-tab [label]="'singleSignOn' | i18n">
|
||||
<section class="tw-mb-9">
|
||||
<h2 bitTypography="h2">{{ "singleSignOn" | i18n }}</h2>
|
||||
<p bitTypography="body1">
|
||||
{{ "ssoDescStart" | i18n }}
|
||||
<a bitLink routerLink="../settings/sso" class="tw-lowercase">{{ "singleSignOn" | i18n }}</a>
|
||||
{{ "ssoDescEnd" | i18n }}
|
||||
</p>
|
||||
<app-integration-grid
|
||||
[integrations]="integrationsList | filterIntegrations: IntegrationType.SSO"
|
||||
></app-integration-grid>
|
||||
</section>
|
||||
</bit-tab>
|
||||
|
||||
<bit-tab [label]="'userProvisioning' | i18n">
|
||||
<section class="tw-mb-9">
|
||||
<h2 bitTypography="h2">
|
||||
{{ "scimIntegration" | i18n }}
|
||||
</h2>
|
||||
<p bitTypography="body1">
|
||||
{{ "scimIntegrationDescStart" | i18n }}
|
||||
<a bitLink routerLink="../settings/scim">{{ "scimIntegration" | i18n }}</a>
|
||||
{{ "scimIntegrationDescEnd" | i18n }}
|
||||
</p>
|
||||
<app-integration-grid
|
||||
[integrations]="integrationsList | filterIntegrations: IntegrationType.SCIM"
|
||||
></app-integration-grid>
|
||||
</section>
|
||||
<section class="tw-mb-9">
|
||||
<h2 bitTypography="h2">
|
||||
{{ "bwdc" | i18n }}
|
||||
</h2>
|
||||
<p bitTypography="body1">{{ "bwdcDesc" | i18n }}</p>
|
||||
<app-integration-grid
|
||||
[integrations]="integrationsList | filterIntegrations: IntegrationType.BWDC"
|
||||
></app-integration-grid>
|
||||
</section>
|
||||
</bit-tab>
|
||||
|
||||
<bit-tab [label]="'eventManagement' | i18n">
|
||||
<section class="tw-mb-9">
|
||||
<h2 bitTypography="h2">
|
||||
{{ "eventManagement" | i18n }}
|
||||
</h2>
|
||||
<p bitTypography="body1">{{ "eventManagementDesc" | i18n }}</p>
|
||||
<app-integration-grid
|
||||
[integrations]="integrationsList | filterIntegrations: IntegrationType.EVENT"
|
||||
></app-integration-grid>
|
||||
</section>
|
||||
</bit-tab>
|
||||
|
||||
<bit-tab [label]="'deviceManagement' | i18n">
|
||||
<section class="tw-mb-9">
|
||||
<h2 bitTypography="h2">
|
||||
{{ "deviceManagement" | i18n }}
|
||||
</h2>
|
||||
<p bitTypography="body1">{{ "deviceManagementDesc" | i18n }}</p>
|
||||
<app-integration-grid
|
||||
[integrations]="integrationsList | filterIntegrations: IntegrationType.DEVICE"
|
||||
></app-integration-grid>
|
||||
</section>
|
||||
</bit-tab>
|
||||
</bit-tab-group>
|
||||
@@ -0,0 +1,209 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Component } from "@angular/core";
|
||||
|
||||
import { IntegrationType } from "@bitwarden/common/enums";
|
||||
|
||||
import { HeaderModule } from "../../../layouts/header/header.module";
|
||||
import { FilterIntegrationsPipe, IntegrationGridComponent, Integration } from "../../../shared/";
|
||||
import { SharedModule } from "../../../shared/shared.module";
|
||||
import { SharedOrganizationModule } from "../shared";
|
||||
|
||||
@Component({
|
||||
selector: "ac-integrations",
|
||||
templateUrl: "./integrations.component.html",
|
||||
standalone: true,
|
||||
imports: [
|
||||
SharedModule,
|
||||
SharedOrganizationModule,
|
||||
IntegrationGridComponent,
|
||||
HeaderModule,
|
||||
FilterIntegrationsPipe,
|
||||
],
|
||||
})
|
||||
export class AdminConsoleIntegrationsComponent {
|
||||
integrationsList: Integration[] = [];
|
||||
tabIndex: number;
|
||||
|
||||
constructor() {
|
||||
this.integrationsList = [
|
||||
{
|
||||
name: "AD FS",
|
||||
linkURL: "https://bitwarden.com/help/saml-adfs/",
|
||||
image: "../../../../../../../images/integrations/azure-active-directory.svg",
|
||||
type: IntegrationType.SSO,
|
||||
},
|
||||
{
|
||||
name: "Auth0",
|
||||
linkURL: "https://bitwarden.com/help/saml-auth0/",
|
||||
image: "../../../../../../../images/integrations/logo-auth0-badge-color.svg",
|
||||
type: IntegrationType.SSO,
|
||||
},
|
||||
{
|
||||
name: "AWS",
|
||||
linkURL: "https://bitwarden.com/help/saml-aws/",
|
||||
image: "../../../../../../../images/integrations/aws-color.svg",
|
||||
imageDarkMode: "../../../../../../../images/integrations/aws-darkmode.svg",
|
||||
type: IntegrationType.SSO,
|
||||
},
|
||||
{
|
||||
name: "Microsoft Entra ID",
|
||||
linkURL: "https://bitwarden.com/help/saml-azure/",
|
||||
image: "../../../../../../../images/integrations/logo-microsoft-entra-id-color.svg",
|
||||
type: IntegrationType.SSO,
|
||||
},
|
||||
{
|
||||
name: "Duo",
|
||||
linkURL: "https://bitwarden.com/help/saml-duo/",
|
||||
image: "../../../../../../../images/integrations/logo-duo-color.svg",
|
||||
type: IntegrationType.SSO,
|
||||
},
|
||||
{
|
||||
name: "Google",
|
||||
linkURL: "https://bitwarden.com/help/saml-google/",
|
||||
image: "../../../../../../../images/integrations/logo-google-badge-color.svg",
|
||||
type: IntegrationType.SSO,
|
||||
},
|
||||
{
|
||||
name: "JumpCloud",
|
||||
linkURL: "https://bitwarden.com/help/saml-jumpcloud/",
|
||||
image: "../../../../../../../images/integrations/logo-jumpcloud-badge-color.svg",
|
||||
imageDarkMode: "../../../../../../../images/integrations/jumpcloud-darkmode.svg",
|
||||
type: IntegrationType.SSO,
|
||||
},
|
||||
{
|
||||
name: "KeyCloak",
|
||||
linkURL: "https://bitwarden.com/help/saml-keycloak/",
|
||||
image: "../../../../../../../images/integrations/logo-keycloak-icon.svg",
|
||||
type: IntegrationType.SSO,
|
||||
},
|
||||
{
|
||||
name: "Okta",
|
||||
linkURL: "https://bitwarden.com/help/saml-okta/",
|
||||
image: "../../../../../../../images/integrations/logo-okta-symbol-black.svg",
|
||||
imageDarkMode: "../../../../../../../images/integrations/okta-darkmode.svg",
|
||||
type: IntegrationType.SSO,
|
||||
},
|
||||
{
|
||||
name: "OneLogin",
|
||||
linkURL: "https://bitwarden.com/help/saml-onelogin/",
|
||||
image: "../../../../../../../images/integrations/logo-onelogin-badge-color.svg",
|
||||
imageDarkMode: "../../../../../../../images/integrations/onelogin-darkmode.svg",
|
||||
type: IntegrationType.SSO,
|
||||
},
|
||||
{
|
||||
name: "PingFederate",
|
||||
linkURL: "https://bitwarden.com/help/saml-pingfederate/",
|
||||
image: "../../../../../../../images/integrations/logo-ping-identity-badge-color.svg",
|
||||
type: IntegrationType.SSO,
|
||||
},
|
||||
{
|
||||
name: "Microsoft Entra ID",
|
||||
linkURL: "https://bitwarden.com/help/microsoft-entra-id-scim-integration/",
|
||||
image: "../../../../../../../images/integrations/logo-microsoft-entra-id-color.svg",
|
||||
type: IntegrationType.SCIM,
|
||||
},
|
||||
{
|
||||
name: "Okta",
|
||||
linkURL: "https://bitwarden.com/help/okta-scim-integration/",
|
||||
image: "../../../../../../../images/integrations/logo-okta-symbol-black.svg",
|
||||
imageDarkMode: "../../../../../../../images/integrations/okta-darkmode.svg",
|
||||
type: IntegrationType.SCIM,
|
||||
},
|
||||
{
|
||||
name: "OneLogin",
|
||||
linkURL: "https://bitwarden.com/help/onelogin-scim-integration/",
|
||||
image: "../../../../../../../images/integrations/logo-onelogin-badge-color.svg",
|
||||
imageDarkMode: "../../../../../../../images/integrations/onelogin-darkmode.svg",
|
||||
type: IntegrationType.SCIM,
|
||||
},
|
||||
{
|
||||
name: "JumpCloud",
|
||||
linkURL: "https://bitwarden.com/help/jumpcloud-scim-integration/",
|
||||
image: "../../../../../../../images/integrations/logo-jumpcloud-badge-color.svg",
|
||||
imageDarkMode: "../../../../../../../images/integrations/jumpcloud-darkmode.svg",
|
||||
type: IntegrationType.SCIM,
|
||||
},
|
||||
{
|
||||
name: "Ping Identity",
|
||||
linkURL: "https://bitwarden.com/help/ping-identity-scim-integration/",
|
||||
image: "../../../../../../../images/integrations/logo-ping-identity-badge-color.svg",
|
||||
type: IntegrationType.SCIM,
|
||||
},
|
||||
{
|
||||
name: "Active Directory",
|
||||
linkURL: "https://bitwarden.com/help/ldap-directory/",
|
||||
image: "../../../../../../../images/integrations/azure-active-directory.svg",
|
||||
type: IntegrationType.BWDC,
|
||||
},
|
||||
{
|
||||
name: "Microsoft Entra ID",
|
||||
linkURL: "https://bitwarden.com/help/microsoft-entra-id/",
|
||||
image: "../../../../../../../images/integrations/logo-microsoft-entra-id-color.svg",
|
||||
type: IntegrationType.BWDC,
|
||||
},
|
||||
{
|
||||
name: "Google Workspace",
|
||||
linkURL: "https://bitwarden.com/help/workspace-directory/",
|
||||
image: "../../../../../../../images/integrations/logo-google-badge-color.svg",
|
||||
type: IntegrationType.BWDC,
|
||||
},
|
||||
{
|
||||
name: "Okta",
|
||||
linkURL: "https://bitwarden.com/help/okta-directory/",
|
||||
image: "../../../../../../../images/integrations/logo-okta-symbol-black.svg",
|
||||
imageDarkMode: "../../../../../../../images/integrations/okta-darkmode.svg",
|
||||
type: IntegrationType.BWDC,
|
||||
},
|
||||
{
|
||||
name: "OneLogin",
|
||||
linkURL: "https://bitwarden.com/help/onelogin-directory/",
|
||||
image: "../../../../../../../images/integrations/logo-onelogin-badge-color.svg",
|
||||
imageDarkMode: "../../../../../../../images/integrations/onelogin-darkmode.svg",
|
||||
type: IntegrationType.BWDC,
|
||||
},
|
||||
{
|
||||
name: "Splunk",
|
||||
linkURL: "https://bitwarden.com/help/splunk-siem/",
|
||||
image: "../../../../../../../images/integrations/logo-splunk-black.svg",
|
||||
imageDarkMode: "../../../../../../../images/integrations/splunk-darkmode.svg",
|
||||
type: IntegrationType.EVENT,
|
||||
},
|
||||
{
|
||||
name: "Microsoft Sentinel",
|
||||
linkURL: "https://bitwarden.com/help/microsoft-sentinel-siem/",
|
||||
image: "../../../../../../../images/integrations/logo-microsoft-sentinel-color.svg",
|
||||
type: IntegrationType.EVENT,
|
||||
},
|
||||
{
|
||||
name: "Rapid7",
|
||||
linkURL: "https://bitwarden.com/help/rapid7-siem/",
|
||||
image: "../../../../../../../images/integrations/logo-rapid7-black.svg",
|
||||
imageDarkMode: "../../../../../../../images/integrations/rapid7-darkmode.svg",
|
||||
type: IntegrationType.EVENT,
|
||||
},
|
||||
{
|
||||
name: "Elastic",
|
||||
linkURL: "https://bitwarden.com/help/elastic-siem/",
|
||||
image: "../../../../../../../images/integrations/logo-elastic-badge-color.svg",
|
||||
type: IntegrationType.EVENT,
|
||||
},
|
||||
{
|
||||
name: "Panther",
|
||||
linkURL: "https://bitwarden.com/help/panther-siem/",
|
||||
image: "../../../../../../../images/integrations/logo-panther-round-color.svg",
|
||||
type: IntegrationType.EVENT,
|
||||
},
|
||||
{
|
||||
name: "Microsoft Intune",
|
||||
linkURL: "https://bitwarden.com/help/deploy-browser-extensions-with-intune/",
|
||||
image: "../../../../../../../images/integrations/logo-microsoft-intune-color.svg",
|
||||
type: IntegrationType.DEVICE,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
get IntegrationType(): typeof IntegrationType {
|
||||
return IntegrationType;
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
<org-switcher [filter]="orgFilter" [hideNewButton]="hideNewOrgButton$ | async"></org-switcher>
|
||||
<bit-nav-group
|
||||
icon="bwi-filter"
|
||||
*ngIf="isAccessIntelligenceFeatureEnabled"
|
||||
*ngIf="organization.useRiskInsights"
|
||||
[text]="'accessIntelligence' | i18n"
|
||||
>
|
||||
<bit-nav-item
|
||||
@@ -60,6 +60,12 @@
|
||||
<bit-nav-item [text]="'billingHistory' | i18n" route="billing/history"></bit-nav-item>
|
||||
</ng-container>
|
||||
</bit-nav-group>
|
||||
<bit-nav-item
|
||||
icon="bwi-providers"
|
||||
[text]="'integrations' | i18n"
|
||||
route="integrations"
|
||||
*ngIf="integrationPageEnabled$ | async"
|
||||
></bit-nav-item>
|
||||
<bit-nav-group
|
||||
icon="bwi-cog"
|
||||
[text]="'settings' | i18n"
|
||||
@@ -92,7 +98,7 @@
|
||||
*ngIf="canAccessExport$ | async"
|
||||
></bit-nav-item>
|
||||
<bit-nav-item
|
||||
[text]="'domainVerification' | i18n"
|
||||
[text]="domainVerificationNavigationTextKey | i18n"
|
||||
route="settings/domain-verification"
|
||||
*ngIf="organization?.canManageDomainVerification"
|
||||
></bit-nav-item>
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { ActivatedRoute, RouterModule } from "@angular/router";
|
||||
@@ -18,8 +20,10 @@ import { PolicyService } from "@bitwarden/common/admin-console/abstractions/poli
|
||||
import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service";
|
||||
import { PolicyType, ProviderStatusType } from "@bitwarden/common/admin-console/enums";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { ProductTierType } from "@bitwarden/common/billing/enums";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { getById } from "@bitwarden/common/platform/misc";
|
||||
import { BannerModule, IconModule } from "@bitwarden/components";
|
||||
@@ -46,6 +50,9 @@ export class OrganizationLayoutComponent implements OnInit {
|
||||
protected readonly logo = AdminConsoleLogo;
|
||||
|
||||
protected orgFilter = (org: Organization) => canAccessOrgAdmin(org);
|
||||
protected domainVerificationNavigationTextKey: string;
|
||||
|
||||
protected integrationPageEnabled$: Observable<boolean>;
|
||||
|
||||
organization$: Observable<Organization>;
|
||||
canAccessExport$: Observable<boolean>;
|
||||
@@ -53,6 +60,7 @@ export class OrganizationLayoutComponent implements OnInit {
|
||||
hideNewOrgButton$: Observable<boolean>;
|
||||
organizationIsUnmanaged$: Observable<boolean>;
|
||||
isAccessIntelligenceFeatureEnabled = false;
|
||||
enterpriseOrganization$: Observable<boolean>;
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
@@ -61,15 +69,12 @@ export class OrganizationLayoutComponent implements OnInit {
|
||||
private configService: ConfigService,
|
||||
private policyService: PolicyService,
|
||||
private providerService: ProviderService,
|
||||
private i18nService: I18nService,
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
document.body.classList.remove("layout_frontend");
|
||||
|
||||
this.isAccessIntelligenceFeatureEnabled = await this.configService.getFeatureFlag(
|
||||
FeatureFlag.AccessIntelligence,
|
||||
);
|
||||
|
||||
this.organization$ = this.route.params.pipe(
|
||||
map((p) => p.organizationId),
|
||||
switchMap((id) => this.organizationService.organizations$.pipe(getById(id))),
|
||||
@@ -104,6 +109,22 @@ export class OrganizationLayoutComponent implements OnInit {
|
||||
provider.providerStatus !== ProviderStatusType.Billable,
|
||||
),
|
||||
);
|
||||
|
||||
this.integrationPageEnabled$ = combineLatest(
|
||||
this.organization$,
|
||||
this.configService.getFeatureFlag$(FeatureFlag.PM14505AdminConsoleIntegrationPage),
|
||||
).pipe(
|
||||
map(
|
||||
([org, featureFlagEnabled]) =>
|
||||
org.productTierType === ProductTierType.Enterprise && featureFlagEnabled,
|
||||
),
|
||||
);
|
||||
|
||||
this.domainVerificationNavigationTextKey = (await this.configService.getFeatureFlag(
|
||||
FeatureFlag.AccountDeprovisioning,
|
||||
))
|
||||
? "claimedDomains"
|
||||
: "domainVerification";
|
||||
}
|
||||
|
||||
canShowVaultTab(organization: Organization): boolean {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { DIALOG_DATA, DialogConfig } from "@angular/cdk/dialog";
|
||||
import { Component, Inject, OnInit } from "@angular/core";
|
||||
import { FormBuilder } from "@angular/forms";
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Component, OnDestroy, OnInit } from "@angular/core";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { concatMap, Subject, takeUntil } from "rxjs";
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog";
|
||||
import { ChangeDetectorRef, Component, Inject, OnDestroy, OnInit } from "@angular/core";
|
||||
import { FormBuilder, Validators } from "@angular/forms";
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Component } from "@angular/core";
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { FormControl } from "@angular/forms";
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog";
|
||||
import { Component, Inject, OnInit } from "@angular/core";
|
||||
import { FormControl, FormGroup } from "@angular/forms";
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Directive, OnInit } from "@angular/core";
|
||||
|
||||
import {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Directive } from "@angular/core";
|
||||
|
||||
import { OrganizationUserBulkResponse } from "@bitwarden/admin-console/common";
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { DIALOG_DATA, DialogConfig } from "@angular/cdk/dialog";
|
||||
import { Component, Inject } from "@angular/core";
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
</bit-callout>
|
||||
<ng-container *ngIf="!done">
|
||||
<bit-callout type="warning" *ngIf="users.length > 0 && !error">
|
||||
<p bitTypography="body1">{{ "deleteOrganizationUserWarning" | i18n }}</p>
|
||||
<p bitTypography="body1">{{ "deleteManyOrganizationUsersWarningDesc" | i18n }}</p>
|
||||
</bit-callout>
|
||||
<bit-table>
|
||||
<ng-container header>
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { DIALOG_DATA, DialogConfig } from "@angular/cdk/dialog";
|
||||
import { Component, Inject } from "@angular/core";
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { DialogRef, DIALOG_DATA } from "@angular/cdk/dialog";
|
||||
import { Component, Inject, OnInit } from "@angular/core";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<bit-dialog dialogSize="large" [title]="'removeUsers' | i18n">
|
||||
<bit-dialog dialogSize="large" [title]="'removeMembers' | i18n">
|
||||
<ng-container bitDialogContent>
|
||||
<bit-callout type="danger" *ngIf="users.length <= 0">
|
||||
{{ "noSelectedUsersApplicable" | i18n }}
|
||||
@@ -79,7 +79,7 @@
|
||||
[disabled]="loading"
|
||||
[bitAction]="submit"
|
||||
>
|
||||
{{ "removeUsers" | i18n }}
|
||||
{{ "removeMembers" | i18n }}
|
||||
</button>
|
||||
<button bitButton type="button" buttonType="secondary" bitDialogClose>
|
||||
{{ "close" | i18n }}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { DIALOG_DATA, DialogConfig } from "@angular/cdk/dialog";
|
||||
import { Component, Inject } from "@angular/core";
|
||||
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
<bit-dialog>
|
||||
<bit-dialog
|
||||
dialogSize="large"
|
||||
*ngIf="{ enabled: accountDeprovisioningEnabled$ | async } as accountDeprovisioning"
|
||||
>
|
||||
<ng-container bitDialogTitle>
|
||||
<h1>{{ bulkTitle }}</h1>
|
||||
<h1 *ngIf="accountDeprovisioning.enabled; else nonMemberTitle">{{ bulkMemberTitle }}</h1>
|
||||
<ng-template #nonMemberTitle>
|
||||
<h1>{{ bulkTitle }}</h1>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
|
||||
<div bitDialogContent>
|
||||
<bit-callout type="danger" *ngIf="users.length <= 0">
|
||||
{{ "noSelectedUsersApplicable" | i18n }}
|
||||
@@ -11,15 +18,81 @@
|
||||
{{ error }}
|
||||
</bit-callout>
|
||||
|
||||
<ng-container *ngIf="!done">
|
||||
<bit-callout type="warning" *ngIf="users.length > 0 && !error && isRevoking">
|
||||
<p>{{ "revokeUsersWarning" | i18n }}</p>
|
||||
<p *ngIf="this.showNoMasterPasswordWarning">
|
||||
{{ "removeMembersWithoutMasterPasswordWarning" | i18n }}
|
||||
</p>
|
||||
</bit-callout>
|
||||
<bit-callout
|
||||
type="danger"
|
||||
*ngIf="nonCompliantMembers && accountDeprovisioning.enabled"
|
||||
title="{{ 'nonCompliantMembersTitle' | i18n }}"
|
||||
>
|
||||
{{ "nonCompliantMembersError" | i18n }}
|
||||
</bit-callout>
|
||||
|
||||
<bit-table>
|
||||
<ng-container *ngIf="!done">
|
||||
<ng-container *ngIf="accountDeprovisioning.enabled">
|
||||
<div *ngIf="users.length > 0 && !error && isRevoking">
|
||||
<p>{{ "revokeMembersWarning" | i18n }}</p>
|
||||
<ul>
|
||||
<li>
|
||||
{{ "claimedAccountRevoke" | i18n }}
|
||||
</li>
|
||||
<li>
|
||||
{{ "unclaimedAccountRevoke" | i18n }}
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
{{ "restoreMembersInstructions" | i18n }}
|
||||
</p>
|
||||
<p *ngIf="this.showNoMasterPasswordWarning">
|
||||
{{ "removeMembersWithoutMasterPasswordWarning" | i18n }}
|
||||
</p>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="!accountDeprovisioning.enabled">
|
||||
<bit-callout type="warning" *ngIf="users.length > 0 && !error && isRevoking">
|
||||
<p>{{ "revokeUsersWarning" | i18n }}</p>
|
||||
</bit-callout>
|
||||
</ng-container>
|
||||
|
||||
<bit-table *ngIf="accountDeprovisioning.enabled">
|
||||
<ng-container header>
|
||||
<tr>
|
||||
<th bitCell class="tw-w-1/2">{{ "member" | i18n }}</th>
|
||||
<th bitCell *ngIf="isRevoking">{{ "details" | i18n }}</th>
|
||||
<th bitCell *ngIf="this.showNoMasterPasswordWarning">{{ "details" | i18n }}</th>
|
||||
</tr>
|
||||
</ng-container>
|
||||
<ng-template body>
|
||||
<tr bitRow *ngFor="let user of users">
|
||||
<td bitCell width="30">
|
||||
<div class="tw-flex tw-items-center">
|
||||
<div class="tw-flex tw-items-center tw-mr-6">
|
||||
<bit-avatar [text]="user | userName" [id]="user.id" size="small"></bit-avatar>
|
||||
</div>
|
||||
<div>
|
||||
{{ user.email }}
|
||||
<small class="tw-block tw-text-muted" *ngIf="user.name">{{ user.name }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td *ngIf="isRevoking" bitCell width="30">
|
||||
{{
|
||||
user.managedByOrganization ? ("claimedAccount" | i18n) : ("unclaimedAccount" | i18n)
|
||||
}}
|
||||
</td>
|
||||
<td bitCell *ngIf="this.showNoMasterPasswordWarning">
|
||||
<span class="tw-block tw-lowercase tw-text-muted">
|
||||
<ng-container *ngIf="user.hasMasterPassword === true"> - </ng-container>
|
||||
<ng-container *ngIf="user.hasMasterPassword === false">
|
||||
<i class="bwi bwi-exclamation-triangle" aria-hidden="true"></i>
|
||||
{{ "noMasterPassword" | i18n }}
|
||||
</ng-container>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
</bit-table>
|
||||
|
||||
<bit-table *ngIf="!accountDeprovisioning.enabled">
|
||||
<ng-container header>
|
||||
<tr>
|
||||
<th bitCell colspan="2">{{ "user" | i18n }}</th>
|
||||
@@ -50,21 +123,55 @@
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="done">
|
||||
<bit-table>
|
||||
<bit-table *ngIf="accountDeprovisioning.enabled">
|
||||
<ng-container header>
|
||||
<tr>
|
||||
<th bitCell colspan="2">{{ "user" | i18n }}</th>
|
||||
<th>{{ "status" | i18n }}</th>
|
||||
<th bitCell class="tw-w-1/2">{{ "member" | i18n }}</th>
|
||||
<th bitCell>{{ "status" | i18n }}</th>
|
||||
</tr>
|
||||
</ng-container>
|
||||
<ng-template body>
|
||||
<tr bitRow *ngFor="let user of users">
|
||||
<td bitCell width="30">
|
||||
<bit-avatar [text]="user | userName" [id]="user.id" size="small"></bit-avatar>
|
||||
<div class="tw-flex tw-items-center">
|
||||
<div class="tw-flex tw-items-center tw-mr-6">
|
||||
<bit-avatar [text]="user | userName" [id]="user.id" size="small"></bit-avatar>
|
||||
</div>
|
||||
<div>
|
||||
{{ user.email }}
|
||||
<small class="tw-block tw-text-muted" *ngIf="user.name">{{ user.name }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td bitCell>
|
||||
{{ user.email }}
|
||||
<small class="tw-block tw-text-muted" *ngIf="user.name">{{ user.name }}</small>
|
||||
<td bitCell *ngIf="statuses.has(user.id)">
|
||||
{{ statuses.get(user.id) }}
|
||||
</td>
|
||||
<td bitCell *ngIf="!statuses.has(user.id)">
|
||||
{{ "bulkFilteredMessage" | i18n }}
|
||||
</td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
</bit-table>
|
||||
|
||||
<bit-table *ngIf="!accountDeprovisioning.enabled">
|
||||
<ng-container header>
|
||||
<tr>
|
||||
<th bitCell class="tw-w-1/2">{{ "member" | i18n }}</th>
|
||||
<th bitCell>{{ "status" | i18n }}</th>
|
||||
</tr>
|
||||
</ng-container>
|
||||
<ng-template body>
|
||||
<tr bitRow *ngFor="let user of users">
|
||||
<td bitCell width="30">
|
||||
<div class="tw-flex tw-items-center">
|
||||
<div class="tw-flex tw-items-center tw-mr-6">
|
||||
<bit-avatar [text]="user | userName" [id]="user.id" size="small"></bit-avatar>
|
||||
</div>
|
||||
<div>
|
||||
{{ user.email }}
|
||||
<small class="tw-block tw-text-muted" *ngIf="user.name">{{ user.name }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td bitCell *ngIf="statuses.has(user.id)">
|
||||
{{ statuses.get(user.id) }}
|
||||
@@ -79,7 +186,7 @@
|
||||
</div>
|
||||
<ng-container bitDialogFooter>
|
||||
<button type="button" bitButton *ngIf="!done && users.length > 0" [bitAction]="submit">
|
||||
{{ bulkTitle }}
|
||||
{{ accountDeprovisioning.enabled ? bulkMemberTitle : bulkTitle }}
|
||||
</button>
|
||||
<button type="button" bitButton buttonType="secondary" bitDialogClose>
|
||||
{{ "close" | i18n }}
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { DIALOG_DATA } from "@angular/cdk/dialog";
|
||||
import { Component, Inject } from "@angular/core";
|
||||
import { Observable } from "rxjs";
|
||||
|
||||
import { OrganizationUserApiService } from "@bitwarden/admin-console/common";
|
||||
import { OrganizationUserStatusType } from "@bitwarden/common/admin-console/enums";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
|
||||
@@ -29,10 +34,13 @@ export class BulkRestoreRevokeComponent {
|
||||
done = false;
|
||||
error: string;
|
||||
showNoMasterPasswordWarning = false;
|
||||
nonCompliantMembers: boolean = false;
|
||||
accountDeprovisioningEnabled$: Observable<boolean>;
|
||||
|
||||
constructor(
|
||||
protected i18nService: I18nService,
|
||||
private organizationUserApiService: OrganizationUserApiService,
|
||||
private configService: ConfigService,
|
||||
@Inject(DIALOG_DATA) protected data: BulkRestoreDialogParams,
|
||||
) {
|
||||
this.isRevoking = data.isRevoking;
|
||||
@@ -41,6 +49,9 @@ export class BulkRestoreRevokeComponent {
|
||||
this.showNoMasterPasswordWarning = this.users.some(
|
||||
(u) => u.status > OrganizationUserStatusType.Invited && u.hasMasterPassword === false,
|
||||
);
|
||||
this.accountDeprovisioningEnabled$ = this.configService.getFeatureFlag$(
|
||||
FeatureFlag.AccountDeprovisioning,
|
||||
);
|
||||
}
|
||||
|
||||
get bulkTitle() {
|
||||
@@ -48,14 +59,26 @@ export class BulkRestoreRevokeComponent {
|
||||
return this.i18nService.t(titleKey);
|
||||
}
|
||||
|
||||
get bulkMemberTitle() {
|
||||
const titleKey = this.isRevoking ? "revokeMembers" : "restoreMembers";
|
||||
return this.i18nService.t(titleKey);
|
||||
}
|
||||
|
||||
submit = async () => {
|
||||
try {
|
||||
const response = await this.performBulkUserAction();
|
||||
|
||||
const bulkMessage = this.isRevoking ? "bulkRevokedMessage" : "bulkRestoredMessage";
|
||||
response.data.forEach((entry) => {
|
||||
const error = entry.error !== "" ? entry.error : this.i18nService.t(bulkMessage);
|
||||
|
||||
response.data.forEach(async (entry) => {
|
||||
const error =
|
||||
entry.error !== ""
|
||||
? this.i18nService.t("cannotRestoreAccessError")
|
||||
: this.i18nService.t(bulkMessage);
|
||||
this.statuses.set(entry.id, error);
|
||||
if (entry.error !== "") {
|
||||
this.nonCompliantMembers = true;
|
||||
}
|
||||
});
|
||||
this.done = true;
|
||||
} catch (e) {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { DIALOG_DATA, DialogConfig } from "@angular/cdk/dialog";
|
||||
import { Component, Inject, OnInit } from "@angular/core";
|
||||
|
||||
@@ -21,6 +23,7 @@ export interface BulkUserDetails {
|
||||
email: string;
|
||||
status: OrganizationUserStatusType | ProviderUserStatusType;
|
||||
hasMasterPassword?: boolean;
|
||||
managedByOrganization?: boolean;
|
||||
}
|
||||
|
||||
type BulkStatusEntry = {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog";
|
||||
import { Component, Inject, OnDestroy } from "@angular/core";
|
||||
import { FormBuilder, Validators } from "@angular/forms";
|
||||
@@ -50,6 +52,7 @@ import {
|
||||
} from "../../../shared/components/access-selector";
|
||||
|
||||
import { commaSeparatedEmails } from "./validators/comma-separated-emails.validator";
|
||||
import { inputEmailLimitValidator } from "./validators/input-email-limit.validator";
|
||||
import { orgSeatLimitReachedValidator } from "./validators/org-seat-limit-reached.validator";
|
||||
|
||||
export enum MemberDialogTab {
|
||||
@@ -172,8 +175,21 @@ export class MemberDialogComponent implements OnDestroy {
|
||||
.pipe(shareReplay({ refCount: true, bufferSize: 1 }));
|
||||
|
||||
this.editMode = this.params.organizationUserId != null;
|
||||
|
||||
let userDetails$;
|
||||
if (this.editMode) {
|
||||
this.title = this.i18nService.t("editMember");
|
||||
userDetails$ = this.userService.get(
|
||||
this.params.organizationId,
|
||||
this.params.organizationUserId,
|
||||
);
|
||||
} else {
|
||||
this.title = this.i18nService.t("inviteMember");
|
||||
userDetails$ = of(null);
|
||||
}
|
||||
|
||||
this.tabIndex = this.params.initialTab ?? MemberDialogTab.Role;
|
||||
this.title = this.i18nService.t(this.editMode ? "editMember" : "inviteMember");
|
||||
|
||||
this.isOnSecretsManagerStandalone = this.params.isOnSecretsManagerStandalone;
|
||||
|
||||
if (this.isOnSecretsManagerStandalone) {
|
||||
@@ -190,10 +206,6 @@ export class MemberDialogComponent implements OnDestroy {
|
||||
),
|
||||
);
|
||||
|
||||
const userDetails$ = this.params.organizationUserId
|
||||
? this.userService.get(this.params.organizationId, this.params.organizationUserId)
|
||||
: of(null);
|
||||
|
||||
this.allowAdminAccessToAllCollectionItems$ = this.organization$.pipe(
|
||||
map((organization) => {
|
||||
return organization.allowAdminAccessToAllCollectionItems;
|
||||
@@ -275,97 +287,28 @@ export class MemberDialogComponent implements OnDestroy {
|
||||
}
|
||||
|
||||
private setFormValidators(organization: Organization) {
|
||||
if (this.params.kind === "InviteMemberDialogParams") {
|
||||
const emailsControlValidators = [
|
||||
Validators.required,
|
||||
commaSeparatedEmails,
|
||||
orgSeatLimitReachedValidator(
|
||||
organization,
|
||||
this.params.allOrganizationUserEmails,
|
||||
this.getSeatLimitErrorMessageForPlan(organization),
|
||||
this.params.occupiedSeatCount,
|
||||
),
|
||||
];
|
||||
const _orgSeatLimitReachedValidator = [
|
||||
Validators.required,
|
||||
commaSeparatedEmails,
|
||||
orgSeatLimitReachedValidator(
|
||||
organization,
|
||||
this.params.allOrganizationUserEmails,
|
||||
this.i18nService.t("subscriptionUpgrade", organization.seats),
|
||||
),
|
||||
];
|
||||
|
||||
const emailsControl = this.formGroup.get("emails");
|
||||
emailsControl.setValidators(emailsControlValidators);
|
||||
emailsControl.updateValueAndValidity();
|
||||
}
|
||||
}
|
||||
const _inputEmailLimitValidator = [
|
||||
Validators.required,
|
||||
commaSeparatedEmails,
|
||||
inputEmailLimitValidator(organization, (maxEmailsCount: number) =>
|
||||
this.i18nService.t("tooManyEmails", maxEmailsCount),
|
||||
),
|
||||
];
|
||||
|
||||
private getSeatLimitErrorMessageForPlan(organization: Organization): string {
|
||||
const { seats, hasReseller } = organization;
|
||||
|
||||
if (hasReseller) {
|
||||
return this.i18nService.t("seatLimitReachedContactYourProvider", seats);
|
||||
}
|
||||
|
||||
return this.i18nService.t("subscriptionUpgrade", seats);
|
||||
}
|
||||
|
||||
private async handleInviteUsers(userView: OrganizationUserAdminView, organization: Organization) {
|
||||
userView.id = this.params.organizationUserId;
|
||||
const emails = [...new Set(this.formGroup.value.emails.trim().split(/\s*,\s*/))];
|
||||
|
||||
if (this.enforceEmailCountLimit(emails, organization)) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.userService.invite(emails, userView);
|
||||
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: null,
|
||||
message: this.i18nService.t("invitedUsers", this.params.name),
|
||||
});
|
||||
this.close(MemberDialogResult.Saved);
|
||||
}
|
||||
|
||||
private enforceEmailCountLimit(emails: string[], organization: Organization): boolean {
|
||||
const maxEmailsCount = organization.productTierType === ProductTierType.TeamsStarter ? 10 : 20;
|
||||
|
||||
if (emails.length > maxEmailsCount) {
|
||||
this.formGroup.controls.emails.setErrors({
|
||||
tooManyEmails: { message: this.i18nService.t("tooManyEmails", maxEmailsCount) },
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private async handleEditUser(userView: OrganizationUserAdminView) {
|
||||
await this.userService.save(userView);
|
||||
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: null,
|
||||
message: this.i18nService.t("editedUserId", this.params.name),
|
||||
});
|
||||
|
||||
this.close(MemberDialogResult.Saved);
|
||||
}
|
||||
|
||||
private async getUserView(): Promise<OrganizationUserAdminView> {
|
||||
const userView = new OrganizationUserAdminView();
|
||||
userView.id = this.params.organizationUserId;
|
||||
userView.organizationId = this.params.organizationId;
|
||||
userView.type = this.formGroup.value.type;
|
||||
userView.permissions = this.setRequestPermissions(
|
||||
userView.permissions ?? new PermissionsApi(),
|
||||
userView.type !== OrganizationUserType.Custom,
|
||||
);
|
||||
userView.collections = this.formGroup.value.access
|
||||
.filter((v) => v.type === AccessItemType.Collection)
|
||||
.map(convertToSelectionView);
|
||||
|
||||
userView.groups = (await firstValueFrom(this.restrictEditingSelf$))
|
||||
? null
|
||||
: this.formGroup.value.groups.map((m) => m.id);
|
||||
|
||||
userView.accessSecretsManager = this.formGroup.value.accessSecretsManager;
|
||||
|
||||
return userView;
|
||||
const emailsControl = this.formGroup.get("emails");
|
||||
emailsControl.setValidators(_orgSeatLimitReachedValidator);
|
||||
emailsControl.setValidators(_inputEmailLimitValidator);
|
||||
emailsControl.updateValueAndValidity();
|
||||
}
|
||||
|
||||
private loadOrganizationUser(
|
||||
@@ -519,6 +462,55 @@ export class MemberDialogComponent implements OnDestroy {
|
||||
}
|
||||
};
|
||||
|
||||
private async getUserView(): Promise<OrganizationUserAdminView> {
|
||||
const userView = new OrganizationUserAdminView();
|
||||
userView.organizationId = this.params.organizationId;
|
||||
userView.type = this.formGroup.value.type;
|
||||
|
||||
userView.permissions = this.setRequestPermissions(
|
||||
userView.permissions ?? new PermissionsApi(),
|
||||
userView.type !== OrganizationUserType.Custom,
|
||||
);
|
||||
|
||||
userView.collections = this.formGroup.value.access
|
||||
.filter((v) => v.type === AccessItemType.Collection)
|
||||
.map(convertToSelectionView);
|
||||
|
||||
userView.groups = (await firstValueFrom(this.restrictEditingSelf$))
|
||||
? null
|
||||
: this.formGroup.value.groups.map((m) => m.id);
|
||||
|
||||
userView.accessSecretsManager = this.formGroup.value.accessSecretsManager;
|
||||
|
||||
return userView;
|
||||
}
|
||||
|
||||
private async handleEditUser(userView: OrganizationUserAdminView) {
|
||||
userView.id = this.params.organizationUserId;
|
||||
await this.userService.save(userView);
|
||||
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: null,
|
||||
message: this.i18nService.t("editedUserId", this.params.name),
|
||||
});
|
||||
|
||||
this.close(MemberDialogResult.Saved);
|
||||
}
|
||||
|
||||
private async handleInviteUsers(userView: OrganizationUserAdminView, organization: Organization) {
|
||||
const emails = [...new Set(this.formGroup.value.emails.trim().split(/\s*,\s*/))];
|
||||
|
||||
await this.userService.invite(emails, userView);
|
||||
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: null,
|
||||
message: this.i18nService.t("invitedUsers"),
|
||||
});
|
||||
this.close(MemberDialogResult.Saved);
|
||||
}
|
||||
|
||||
remove = async () => {
|
||||
if (!this.editMode) {
|
||||
return;
|
||||
@@ -626,7 +618,10 @@ export class MemberDialogComponent implements OnDestroy {
|
||||
key: "deleteOrganizationUser",
|
||||
placeholders: [this.params.name],
|
||||
},
|
||||
content: { key: "deleteOrganizationUserWarning" },
|
||||
content: {
|
||||
key: "deleteOrganizationUserWarningDesc",
|
||||
placeholders: [this.params.name],
|
||||
},
|
||||
type: "warning",
|
||||
acceptButtonText: { key: "delete" },
|
||||
cancelButtonText: { key: "cancel" },
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { KeyValue } from "@angular/common";
|
||||
import { Component, Input, OnInit, OnDestroy } from "@angular/core";
|
||||
import { FormControl, FormGroup } from "@angular/forms";
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { FormControl } from "@angular/forms";
|
||||
|
||||
import { commaSeparatedEmails } from "./comma-separated-emails.validator";
|
||||
|
||||
@@ -0,0 +1,191 @@
|
||||
import { AbstractControl, FormControl } from "@angular/forms";
|
||||
|
||||
import { OrganizationUserType } from "@bitwarden/common/admin-console/enums";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { ProductTierType } from "@bitwarden/common/billing/enums";
|
||||
|
||||
import { inputEmailLimitValidator } from "./input-email-limit.validator";
|
||||
|
||||
const orgFactory = (props: Partial<Organization> = {}) =>
|
||||
Object.assign(
|
||||
new Organization(),
|
||||
{
|
||||
id: "myOrgId",
|
||||
enabled: true,
|
||||
type: OrganizationUserType.Admin,
|
||||
},
|
||||
props,
|
||||
);
|
||||
|
||||
describe("inputEmailLimitValidator", () => {
|
||||
const getErrorMessage = (max: number) => `You can only add up to ${max} unique emails.`;
|
||||
|
||||
const createUniqueEmailString = (numberOfEmails: number) =>
|
||||
Array(numberOfEmails)
|
||||
.fill(null)
|
||||
.map((_, i) => `email${i}@example.com`)
|
||||
.join(", ");
|
||||
|
||||
const createIdenticalEmailString = (numberOfEmails: number) =>
|
||||
Array(numberOfEmails)
|
||||
.fill(null)
|
||||
.map(() => `email@example.com`)
|
||||
.join(", ");
|
||||
|
||||
describe("TeamsStarter limit validation", () => {
|
||||
let teamsStarterOrganization: Organization;
|
||||
|
||||
beforeEach(() => {
|
||||
teamsStarterOrganization = orgFactory({
|
||||
productTierType: ProductTierType.TeamsStarter,
|
||||
seats: 10,
|
||||
});
|
||||
});
|
||||
|
||||
it("should return null if unique email count is within the limit", () => {
|
||||
// Arrange
|
||||
const control = new FormControl(createUniqueEmailString(3));
|
||||
|
||||
const validatorFn = inputEmailLimitValidator(teamsStarterOrganization, getErrorMessage);
|
||||
|
||||
// Act
|
||||
const result = validatorFn(control);
|
||||
|
||||
// Assert
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it("should return null if unique email count is equal the limit", () => {
|
||||
// Arrange
|
||||
const control = new FormControl(createUniqueEmailString(10));
|
||||
|
||||
const validatorFn = inputEmailLimitValidator(teamsStarterOrganization, getErrorMessage);
|
||||
|
||||
// Act
|
||||
const result = validatorFn(control);
|
||||
|
||||
// Assert
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it("should return an error if unique email count exceeds the limit", () => {
|
||||
// Arrange
|
||||
const control = new FormControl(createUniqueEmailString(11));
|
||||
|
||||
const validatorFn = inputEmailLimitValidator(teamsStarterOrganization, getErrorMessage);
|
||||
|
||||
// Act
|
||||
const result = validatorFn(control);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual({
|
||||
tooManyEmails: { message: "You can only add up to 10 unique emails." },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Non-TeamsStarter limit validation", () => {
|
||||
let nonTeamsStarterOrganization: Organization;
|
||||
|
||||
beforeEach(() => {
|
||||
nonTeamsStarterOrganization = orgFactory({
|
||||
productTierType: ProductTierType.Enterprise,
|
||||
seats: 100,
|
||||
});
|
||||
});
|
||||
|
||||
it("should return null if unique email count is within the limit", () => {
|
||||
// Arrange
|
||||
const control = new FormControl(createUniqueEmailString(3));
|
||||
|
||||
const validatorFn = inputEmailLimitValidator(nonTeamsStarterOrganization, getErrorMessage);
|
||||
|
||||
// Act
|
||||
const result = validatorFn(control);
|
||||
|
||||
// Assert
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it("should return null if unique email count is equal the limit", () => {
|
||||
// Arrange
|
||||
const control = new FormControl(createUniqueEmailString(10));
|
||||
|
||||
const validatorFn = inputEmailLimitValidator(nonTeamsStarterOrganization, getErrorMessage);
|
||||
|
||||
// Act
|
||||
const result = validatorFn(control);
|
||||
|
||||
// Assert
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it("should return an error if unique email count exceeds the limit", () => {
|
||||
// Arrange
|
||||
|
||||
const control = new FormControl(createUniqueEmailString(21));
|
||||
|
||||
const validatorFn = inputEmailLimitValidator(nonTeamsStarterOrganization, getErrorMessage);
|
||||
|
||||
// Act
|
||||
const result = validatorFn(control);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual({
|
||||
tooManyEmails: { message: "You can only add up to 20 unique emails." },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("input email validation", () => {
|
||||
let organization: Organization;
|
||||
|
||||
beforeEach(() => {
|
||||
organization = orgFactory({
|
||||
productTierType: ProductTierType.Enterprise,
|
||||
seats: 100,
|
||||
});
|
||||
});
|
||||
|
||||
it("should ignore duplicate emails and validate only unique ones", () => {
|
||||
// Arrange
|
||||
const sixUniqueEmails = createUniqueEmailString(6);
|
||||
const sixDuplicateEmails = createIdenticalEmailString(6);
|
||||
|
||||
const control = new FormControl(sixUniqueEmails + sixDuplicateEmails);
|
||||
const validatorFn = inputEmailLimitValidator(organization, getErrorMessage);
|
||||
|
||||
// Act
|
||||
const result = validatorFn(control);
|
||||
|
||||
// Assert
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it("should return null if input is null", () => {
|
||||
// Arrange
|
||||
const control: AbstractControl = new FormControl(null);
|
||||
|
||||
const validatorFn = inputEmailLimitValidator(organization, getErrorMessage);
|
||||
|
||||
// Act
|
||||
const result = validatorFn(control);
|
||||
|
||||
// Assert
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it("should return null if input is empty", () => {
|
||||
// Arrange
|
||||
const control: AbstractControl = new FormControl("");
|
||||
|
||||
const validatorFn = inputEmailLimitValidator(organization, getErrorMessage);
|
||||
|
||||
// Act
|
||||
const result = validatorFn(control);
|
||||
|
||||
// Assert
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,40 @@
|
||||
import { AbstractControl, ValidationErrors, ValidatorFn } from "@angular/forms";
|
||||
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { ProductTierType } from "@bitwarden/common/billing/enums";
|
||||
|
||||
function getUniqueInputEmails(control: AbstractControl): string[] {
|
||||
const emails: string[] = control.value
|
||||
.split(",")
|
||||
.filter((email: string) => email && email.trim() !== "");
|
||||
const uniqueEmails: string[] = Array.from(new Set(emails));
|
||||
|
||||
return uniqueEmails;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the number of unique emails in an input does not exceed the allowed maximum.
|
||||
* @param organization An object representing the organization
|
||||
* @param getErrorMessage A callback function that generates the error message. It takes the `maxEmailsCount` as a parameter.
|
||||
* @returns A function that validates an `AbstractControl` and returns `ValidationErrors` or `null`
|
||||
*/
|
||||
export function inputEmailLimitValidator(
|
||||
organization: Organization,
|
||||
getErrorMessage: (maxEmailsCount: number) => string,
|
||||
): ValidatorFn {
|
||||
return (control: AbstractControl): ValidationErrors | null => {
|
||||
if (!control.value?.trim()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const maxEmailsCount = organization.productTierType === ProductTierType.TeamsStarter ? 10 : 20;
|
||||
|
||||
const uniqueEmails = getUniqueInputEmails(control);
|
||||
|
||||
if (uniqueEmails.length <= maxEmailsCount) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return { tooManyEmails: { message: getErrorMessage(maxEmailsCount) } };
|
||||
};
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog";
|
||||
import { Component, Inject, OnDestroy, OnInit, ViewChild } from "@angular/core";
|
||||
import { FormBuilder, Validators } from "@angular/forms";
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core";
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
@@ -463,9 +465,51 @@ export class MembersComponent extends BaseMembersComponent<OrganizationUserView>
|
||||
firstValueFrom(simpleDialog.closed).then(this.handleDialogClose.bind(this));
|
||||
}
|
||||
|
||||
async edit(user: OrganizationUserView, initialTab: MemberDialogTab = MemberDialogTab.Role) {
|
||||
private async handleInviteDialog() {
|
||||
const dialog = openUserAddEditDialog(this.dialogService, {
|
||||
data: {
|
||||
name: null,
|
||||
organizationId: this.organization.id,
|
||||
organizationUserId: null,
|
||||
allOrganizationUserEmails: this.dataSource.data?.map((user) => user.email) ?? [],
|
||||
usesKeyConnector: null,
|
||||
isOnSecretsManagerStandalone: this.orgIsOnSecretsManagerStandalone,
|
||||
initialTab: MemberDialogTab.Role,
|
||||
numConfirmedMembers: this.dataSource.confirmedUserCount,
|
||||
managedByOrganization: null,
|
||||
},
|
||||
});
|
||||
|
||||
const result = await lastValueFrom(dialog.closed);
|
||||
|
||||
if (result === MemberDialogResult.Saved) {
|
||||
await this.load();
|
||||
}
|
||||
}
|
||||
|
||||
private async handleSeatLimitForFixedTiers() {
|
||||
if (!this.organization.canEditSubscription) {
|
||||
await this.showSeatLimitReachedDialog();
|
||||
return;
|
||||
}
|
||||
|
||||
const reference = openChangePlanDialog(this.dialogService, {
|
||||
data: {
|
||||
organizationId: this.organization.id,
|
||||
subscription: null,
|
||||
productTierType: this.organization.productTierType,
|
||||
},
|
||||
});
|
||||
|
||||
const result = await lastValueFrom(reference.closed);
|
||||
|
||||
if (result === ChangePlanDialogResultType.Submitted) {
|
||||
await this.load();
|
||||
}
|
||||
}
|
||||
|
||||
async invite() {
|
||||
if (
|
||||
!user &&
|
||||
this.organization.hasReseller &&
|
||||
this.organization.seats === this.dataSource.occupiedSeatCount
|
||||
) {
|
||||
@@ -474,46 +518,36 @@ export class MembersComponent extends BaseMembersComponent<OrganizationUserView>
|
||||
title: this.i18nService.t("seatLimitReached"),
|
||||
message: this.i18nService.t("contactYourProvider"),
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Invite User: Add Flow
|
||||
// Click on user email: Edit Flow
|
||||
|
||||
// User attempting to invite new users in a free org with max users
|
||||
if (
|
||||
!user &&
|
||||
this.dataSource.occupiedSeatCount === this.organization.seats &&
|
||||
isFixedSeatPlan(this.organization.productTierType)
|
||||
) {
|
||||
const reference = openChangePlanDialog(this.dialogService, {
|
||||
data: {
|
||||
organizationId: this.organization.id,
|
||||
subscription: null,
|
||||
productTierType: this.organization.productTierType,
|
||||
},
|
||||
});
|
||||
await this.handleSeatLimitForFixedTiers();
|
||||
|
||||
const result = await lastValueFrom(reference.closed);
|
||||
|
||||
if (result === ChangePlanDialogResultType.Submitted) {
|
||||
await this.load();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
await this.handleInviteDialog();
|
||||
}
|
||||
|
||||
async edit(user: OrganizationUserView, initialTab: MemberDialogTab = MemberDialogTab.Role) {
|
||||
const dialog = openUserAddEditDialog(this.dialogService, {
|
||||
data: {
|
||||
kind: "InviteMemberDialogParams",
|
||||
name: this.userNamePipe.transform(user),
|
||||
organizationId: this.organization.id,
|
||||
organizationUserId: user != null ? user.id : null,
|
||||
organizationUserId: user.id,
|
||||
occupiedSeatCount: this.dataSource.occupiedSeatCount,
|
||||
allOrganizationUserEmails: this.dataSource.data?.map((user) => user.email) ?? [],
|
||||
usesKeyConnector: user?.usesKeyConnector,
|
||||
usesKeyConnector: user.usesKeyConnector,
|
||||
isOnSecretsManagerStandalone: this.orgIsOnSecretsManagerStandalone,
|
||||
initialTab: initialTab,
|
||||
managedByOrganization: user?.managedByOrganization,
|
||||
numConfirmedMembers: this.dataSource.confirmedUserCount,
|
||||
managedByOrganization: user.managedByOrganization,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -525,9 +559,7 @@ export class MembersComponent extends BaseMembersComponent<OrganizationUserView>
|
||||
case MemberDialogResult.Saved:
|
||||
case MemberDialogResult.Revoked:
|
||||
case MemberDialogResult.Restored:
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.load();
|
||||
await this.load();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -742,7 +774,10 @@ export class MembersComponent extends BaseMembersComponent<OrganizationUserView>
|
||||
key: "deleteOrganizationUser",
|
||||
placeholders: [this.userNamePipe.transform(user)],
|
||||
},
|
||||
content: { key: "deleteOrganizationUserWarning" },
|
||||
content: {
|
||||
key: "deleteOrganizationUserWarningDesc",
|
||||
placeholders: [this.userNamePipe.transform(user)],
|
||||
},
|
||||
type: "warning",
|
||||
acceptButtonText: { key: "delete" },
|
||||
cancelButtonText: { key: "cancel" },
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
|
||||
import {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Injectable } from "@angular/core";
|
||||
|
||||
import {
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { NgModule } from "@angular/core";
|
||||
import { RouterModule, Routes } from "@angular/router";
|
||||
|
||||
import { authGuard } from "@bitwarden/angular/auth/guards";
|
||||
import { canAccessFeature } from "@bitwarden/angular/platform/guard/feature-flag.guard";
|
||||
import {
|
||||
canAccessOrgAdmin,
|
||||
canAccessGroupsTab,
|
||||
@@ -11,6 +14,7 @@ import {
|
||||
canAccessSettingsTab,
|
||||
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
|
||||
import { organizationPermissionsGuard } from "../../admin-console/organizations/guards/org-permissions.guard";
|
||||
import { organizationRedirectGuard } from "../../admin-console/organizations/guards/org-redirect.guard";
|
||||
@@ -18,6 +22,8 @@ import { OrganizationLayoutComponent } from "../../admin-console/organizations/l
|
||||
import { deepLinkGuard } from "../../auth/guards/deep-link.guard";
|
||||
import { VaultModule } from "../../vault/org-vault/vault.module";
|
||||
|
||||
import { isEnterpriseOrgGuard } from "./guards/is-enterprise-org.guard";
|
||||
import { AdminConsoleIntegrationsComponent } from "./integrations/integrations.component";
|
||||
import { GroupsComponent } from "./manage/groups.component";
|
||||
|
||||
const routes: Routes = [
|
||||
@@ -36,6 +42,17 @@ const routes: Routes = [
|
||||
path: "vault",
|
||||
loadChildren: () => VaultModule,
|
||||
},
|
||||
{
|
||||
path: "integrations",
|
||||
canActivate: [
|
||||
canAccessFeature(FeatureFlag.PM14505AdminConsoleIntegrationPage),
|
||||
isEnterpriseOrgGuard(false),
|
||||
],
|
||||
component: AdminConsoleIntegrationsComponent,
|
||||
data: {
|
||||
titleId: "integrations",
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "settings",
|
||||
loadChildren: () =>
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Directive, Input, OnInit } from "@angular/core";
|
||||
import { UntypedFormControl, UntypedFormGroup } from "@angular/forms";
|
||||
|
||||
@@ -45,7 +47,7 @@ export abstract class BasePolicyComponent implements OnInit {
|
||||
return null;
|
||||
}
|
||||
|
||||
buildRequest(policiesEnabledMap: Map<PolicyType, boolean>) {
|
||||
buildRequest() {
|
||||
const request = new PolicyRequest();
|
||||
request.enabled = this.enabled.value;
|
||||
request.type = this.policy.type;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { FormBuilder, FormGroup, Validators } from "@angular/forms";
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Component } from "@angular/core";
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { UntypedFormBuilder, Validators } from "@angular/forms";
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
import { lastValueFrom } from "rxjs";
|
||||
@@ -87,7 +89,6 @@ export class PoliciesComponent implements OnInit {
|
||||
data: {
|
||||
policy: policy,
|
||||
organizationId: this.organizationId,
|
||||
policiesEnabledMap: this.policiesEnabledMap,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -15,7 +15,13 @@
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container bitDialogFooter>
|
||||
<button bitButton buttonType="primary" [disabled]="saveDisabled" bitFormButton type="submit">
|
||||
<button
|
||||
bitButton
|
||||
buttonType="primary"
|
||||
[disabled]="saveDisabled$ | async"
|
||||
bitFormButton
|
||||
type="submit"
|
||||
>
|
||||
{{ "save" | i18n }}
|
||||
</button>
|
||||
<button bitButton buttonType="secondary" bitDialogClose type="button">
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog";
|
||||
import {
|
||||
AfterViewInit,
|
||||
@@ -8,13 +10,13 @@ import {
|
||||
ViewContainerRef,
|
||||
} from "@angular/core";
|
||||
import { FormBuilder } from "@angular/forms";
|
||||
import { Observable, map } from "rxjs";
|
||||
|
||||
import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction";
|
||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
import { PolicyRequest } from "@bitwarden/common/admin-console/models/request/policy.request";
|
||||
import { PolicyResponse } from "@bitwarden/common/admin-console/models/response/policy.response";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { DialogService, ToastService } from "@bitwarden/components";
|
||||
|
||||
import { BasePolicy, BasePolicyComponent } from "../policies";
|
||||
@@ -24,8 +26,6 @@ export type PolicyEditDialogData = {
|
||||
policy: BasePolicy;
|
||||
/** Returns a unique organization id */
|
||||
organizationId: string;
|
||||
/** A map indicating whether each policy type is enabled or disabled. */
|
||||
policiesEnabledMap: Map<PolicyType, boolean>;
|
||||
};
|
||||
|
||||
export enum PolicyEditDialogResult {
|
||||
@@ -42,7 +42,7 @@ export class PolicyEditComponent implements AfterViewInit {
|
||||
policyType = PolicyType;
|
||||
loading = true;
|
||||
enabled = false;
|
||||
saveDisabled = false;
|
||||
saveDisabled$: Observable<boolean>;
|
||||
defaultTypes: any[];
|
||||
policyComponent: BasePolicyComponent;
|
||||
|
||||
@@ -54,7 +54,6 @@ export class PolicyEditComponent implements AfterViewInit {
|
||||
@Inject(DIALOG_DATA) protected data: PolicyEditDialogData,
|
||||
private policyApiService: PolicyApiServiceAbstraction,
|
||||
private i18nService: I18nService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private cdr: ChangeDetectorRef,
|
||||
private formBuilder: FormBuilder,
|
||||
private dialogRef: DialogRef<PolicyEditDialogResult>,
|
||||
@@ -73,7 +72,9 @@ export class PolicyEditComponent implements AfterViewInit {
|
||||
this.policyComponent.policy = this.data.policy;
|
||||
this.policyComponent.policyResponse = this.policyResponse;
|
||||
|
||||
this.saveDisabled = !this.policyResponse.canToggleState;
|
||||
this.saveDisabled$ = this.policyComponent.data.statusChanges.pipe(
|
||||
map((status) => status !== "VALID" || !this.policyResponse.canToggleState),
|
||||
);
|
||||
|
||||
this.cdr.detectChanges();
|
||||
}
|
||||
@@ -96,7 +97,7 @@ export class PolicyEditComponent implements AfterViewInit {
|
||||
submit = async () => {
|
||||
let request: PolicyRequest;
|
||||
try {
|
||||
request = await this.policyComponent.buildRequest(this.data.policiesEnabledMap);
|
||||
request = await this.policyComponent.buildRequest();
|
||||
} catch (e) {
|
||||
this.toastService.showToast({ variant: "error", title: null, message: e.message });
|
||||
return;
|
||||
|
||||
@@ -2,10 +2,6 @@ import { Component } from "@angular/core";
|
||||
|
||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { PolicyRequest } from "@bitwarden/common/admin-console/models/request/policy.request";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
|
||||
import { BasePolicy, BasePolicyComponent } from "./base-policy.component";
|
||||
|
||||
@@ -24,25 +20,4 @@ export class RequireSsoPolicy extends BasePolicy {
|
||||
selector: "policy-require-sso",
|
||||
templateUrl: "require-sso.component.html",
|
||||
})
|
||||
export class RequireSsoPolicyComponent extends BasePolicyComponent {
|
||||
constructor(
|
||||
private i18nService: I18nService,
|
||||
private configService: ConfigService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async buildRequest(policiesEnabledMap: Map<PolicyType, boolean>): Promise<PolicyRequest> {
|
||||
if (await this.configService.getFeatureFlag(FeatureFlag.Pm13322AddPolicyDefinitions)) {
|
||||
// We are now relying on server-side validation only
|
||||
return super.buildRequest(policiesEnabledMap);
|
||||
}
|
||||
|
||||
const singleOrgEnabled = policiesEnabledMap.get(PolicyType.SingleOrg) ?? false;
|
||||
if (this.enabled.value && !singleOrgEnabled) {
|
||||
throw new Error(this.i18nService.t("requireSsoPolicyReqError"));
|
||||
}
|
||||
|
||||
return super.buildRequest(policiesEnabledMap);
|
||||
}
|
||||
}
|
||||
export class RequireSsoPolicyComponent extends BasePolicyComponent {}
|
||||
|
||||
@@ -2,10 +2,8 @@ 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";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
|
||||
import { BasePolicy, BasePolicyComponent } from "./base-policy.component";
|
||||
|
||||
@@ -21,10 +19,7 @@ export class SingleOrgPolicy extends BasePolicy {
|
||||
templateUrl: "single-org.component.html",
|
||||
})
|
||||
export class SingleOrgPolicyComponent extends BasePolicyComponent implements OnInit {
|
||||
constructor(
|
||||
private i18nService: I18nService,
|
||||
private configService: ConfigService,
|
||||
) {
|
||||
constructor(private configService: ConfigService) {
|
||||
super();
|
||||
}
|
||||
|
||||
@@ -44,39 +39,4 @@ export class SingleOrgPolicyComponent extends BasePolicyComponent implements OnI
|
||||
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
|
||||
return super.buildRequest(policiesEnabledMap);
|
||||
}
|
||||
|
||||
if (!this.enabled.value) {
|
||||
if (policiesEnabledMap.get(PolicyType.RequireSso) ?? false) {
|
||||
throw new Error(
|
||||
this.i18nService.t("disableRequiredError", this.i18nService.t("requireSso")),
|
||||
);
|
||||
}
|
||||
|
||||
if (policiesEnabledMap.get(PolicyType.MaximumVaultTimeout) ?? false) {
|
||||
throw new Error(
|
||||
this.i18nService.t(
|
||||
"disableRequiredError",
|
||||
this.i18nService.t("maximumVaultTimeoutLabel"),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
(await firstValueFrom(this.accountDeprovisioningEnabled$)) &&
|
||||
!this.policyResponse.canToggleState
|
||||
) {
|
||||
throw new Error(
|
||||
this.i18nService.t("disableRequiredError", this.i18nService.t("singleOrg")),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return super.buildRequest(policiesEnabledMap);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { NgModule } from "@angular/core";
|
||||
import { RouterModule, Routes } from "@angular/router";
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { ActivatedRoute, NavigationEnd, Router } from "@angular/router";
|
||||
import { filter, map, Observable, startWith, concatMap } from "rxjs";
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Component, OnDestroy, OnInit, ViewChild, ViewContainerRef } from "@angular/core";
|
||||
import { FormBuilder, Validators } from "@angular/forms";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog";
|
||||
import { Component, Inject, OnDestroy, OnInit } from "@angular/core";
|
||||
import { FormBuilder, FormControl, Validators } from "@angular/forms";
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { inject, NgModule } from "@angular/core";
|
||||
import { CanMatchFn, RouterModule, Routes } from "@angular/router";
|
||||
import { map } from "rxjs";
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { DialogRef } from "@angular/cdk/dialog";
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Meta, StoryObj } from "@storybook/angular";
|
||||
|
||||
import { AccessSelectorComponent, PermissionMode } from "./access-selector.component";
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { FormBuilder, FormControl, FormGroup } from "@angular/forms";
|
||||
import { Meta, StoryObj } from "@storybook/angular";
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
||||
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Component, forwardRef, Input, OnDestroy, OnInit } from "@angular/core";
|
||||
import {
|
||||
ControlValueAccessor,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import {
|
||||
CollectionAccessSelectionView,
|
||||
OrganizationUserUserDetailsResponse,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Component, inject } from "@angular/core";
|
||||
import { Params } from "@angular/router";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Component, OnDestroy, OnInit, ViewChild } from "@angular/core";
|
||||
import { FormBuilder, Validators } from "@angular/forms";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import {
|
||||
OrganizationUserApiService,
|
||||
OrganizationUserResetPasswordEnrollmentRequest,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Component, OnInit, ViewChild } from "@angular/core";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
import { first } from "rxjs/operators";
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { DOCUMENT } from "@angular/common";
|
||||
import { Component, Inject, NgZone, OnDestroy, OnInit } from "@angular/core";
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import {
|
||||
LoginDecryptionOptionsService,
|
||||
DefaultLoginDecryptionOptionsService,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Injectable } from "@angular/core";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { MockProxy, mock } from "jest-mock-extended";
|
||||
import { of } from "rxjs";
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { WebAuthnLoginAssertionResponseRequest } from "@bitwarden/common/auth/services/webauthn-login/request/webauthn-login-assertion-response.request";
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { WebauthnLoginAttestationResponseRequest } from "./webauthn-login-attestation-response.request";
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { RotateableKeySet } from "@bitwarden/auth/common";
|
||||
import { BaseResponse } from "@bitwarden/common/models/response/base.response";
|
||||
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { randomBytes } from "crypto";
|
||||
|
||||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Injectable, Optional } from "@angular/core";
|
||||
import { BehaviorSubject, filter, from, map, Observable, shareReplay, switchMap, tap } from "rxjs";
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Component } from "@angular/core";
|
||||
import { ActivatedRoute, Params, Router } from "@angular/router";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { CipherResponse } from "@bitwarden/common/vault/models/response/cipher.response";
|
||||
import { KdfType } from "@bitwarden/key-management";
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
export class EmergencyAccessAcceptRequest {
|
||||
token: string;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
export class EmergencyAccessConfirmRequest {
|
||||
key: string;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { EmergencyAccessType } from "../enums/emergency-access-type";
|
||||
|
||||
export class EmergencyAccessInviteRequest {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
export class EmergencyAccessPasswordRequest {
|
||||
newMasterPasswordHash: string;
|
||||
key: string;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { EmergencyAccessType } from "../enums/emergency-access-type";
|
||||
|
||||
export class EmergencyAccessUpdateRequest {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { MockProxy } from "jest-mock-extended";
|
||||
import mock from "jest-mock-extended/lib/Mock";
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Injectable } from "@angular/core";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { inject } from "@angular/core";
|
||||
import { CanActivateFn, Router } from "@angular/router";
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { FormBuilder, Validators } from "@angular/forms";
|
||||
import { Router } from "@angular/router";
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user