mirror of
https://github.com/bitwarden/browser
synced 2025-12-14 07:13:32 +00:00
[EC-19] Update Organization Settings Page (#3251)
* [EC-19] Refactor existing organization settings components to its own module * [EC-19] Move SSO page to settings tab * [EC-19] Move Policies page to Settings tab Refactor Policy components into its own module * [EC-19] Move ImageSubscriptionHiddenComponent * [EC-19] Lazy load org settings module * [EC-19] Add SSO Id to SSO config view * [EC-19] Remove SSO identfier from org info page * [EC-19] Update org settings/policies to follow ADR-0011 * [EC-19] Update two-step login setup description * [EC-19] Revert nested policy components folder * [EC-19] Revert nested org setting components folder * [EC-19] Remove left over image component * [EC-19] Prettier * [EC-19] Fix missing i18n * [EC-19] Update SSO form to use CL * [EC-19] Remove unused SSO input components * [EC-19] Fix bad SSO locale identifier * [EC-19] Fix import order linting * [EC-19] Add explicit whitespace check for launch click directive * [EC-19] Add restricted import paths to eslint config * [EC-19] Tag deprecated field with Jira issue to cleanup in future release * [EC-19] Remove out of date comment * [EC-19] Move policy components to policies module * [EC-19] Remove dityRequired validator * [EC-19] Use explicit type for SSO config form * [EC-19] Fix rxjs linter errors * [EC-19] Fix RxJS eslint comments in org settings component * [EC-19] Use explicit ControlsOf<T> helper for nested SSO form groups. * [EC-19] Attribute source of ControlsOf<T> helper * [EC-19] Fix missing settings side nav links * [EC-19] Fix member/user language for policy modals
This commit is contained in:
@@ -6,7 +6,13 @@
|
||||
"no-restricted-imports": [
|
||||
"error",
|
||||
{
|
||||
"patterns": ["**/app/core/*", "**/reports/*", "**/app/shared/*"]
|
||||
"patterns": [
|
||||
"**/app/core/*",
|
||||
"**/reports/*",
|
||||
"**/app/shared/*",
|
||||
"**/organizations/settings/*",
|
||||
"**/organizations/policies/*"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -27,15 +27,17 @@ import { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.ab
|
||||
import { VaultTimeoutService } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeout.service";
|
||||
|
||||
import { PolicyListService, RouterService } from "./core";
|
||||
import { DisableSendPolicy } from "./organizations/policies/disable-send.component";
|
||||
import { MasterPasswordPolicy } from "./organizations/policies/master-password.component";
|
||||
import { PasswordGeneratorPolicy } from "./organizations/policies/password-generator.component";
|
||||
import { PersonalOwnershipPolicy } from "./organizations/policies/personal-ownership.component";
|
||||
import { RequireSsoPolicy } from "./organizations/policies/require-sso.component";
|
||||
import { ResetPasswordPolicy } from "./organizations/policies/reset-password.component";
|
||||
import { SendOptionsPolicy } from "./organizations/policies/send-options.component";
|
||||
import { SingleOrgPolicy } from "./organizations/policies/single-org.component";
|
||||
import { TwoFactorAuthenticationPolicy } from "./organizations/policies/two-factor-authentication.component";
|
||||
import {
|
||||
DisableSendPolicy,
|
||||
MasterPasswordPolicy,
|
||||
PasswordGeneratorPolicy,
|
||||
PersonalOwnershipPolicy,
|
||||
RequireSsoPolicy,
|
||||
ResetPasswordPolicy,
|
||||
SendOptionsPolicy,
|
||||
SingleOrgPolicy,
|
||||
TwoFactorAuthenticationPolicy,
|
||||
} from "./organizations/policies";
|
||||
|
||||
const BroadcasterSubscriptionId = "AppComponent";
|
||||
const IdleTimeout = 60000 * 10; // 10 minutes
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { BasePolicy } from "../organizations/policies/base-policy.component";
|
||||
import { BasePolicy } from "../organizations/policies";
|
||||
|
||||
export class PolicyListService {
|
||||
private policies: BasePolicy[] = [];
|
||||
|
||||
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
@@ -4,6 +4,7 @@ import { LooseComponentsModule } from "../../shared/loose-components.module";
|
||||
import { SharedModule } from "../../shared/shared.module";
|
||||
|
||||
import { BillingSyncApiKeyComponent } from "./billing-sync-api-key.component";
|
||||
import { ImageSubscriptionHiddenComponent } from "./image-subscription-hidden.component";
|
||||
import { OrgBillingHistoryViewComponent } from "./organization-billing-history-view.component";
|
||||
import { OrganizationBillingRoutingModule } from "./organization-billing-routing.module";
|
||||
import { OrganizationBillingTabComponent } from "./organization-billing-tab.component";
|
||||
@@ -14,6 +15,7 @@ import { OrganizationSubscriptionComponent } from "./organization-subscription.c
|
||||
declarations: [
|
||||
BillingSyncApiKeyComponent,
|
||||
OrganizationBillingTabComponent,
|
||||
ImageSubscriptionHiddenComponent,
|
||||
OrganizationSubscriptionComponent,
|
||||
OrgBillingHistoryViewComponent,
|
||||
],
|
||||
|
||||
@@ -11,11 +11,7 @@ import {
|
||||
canAccessGroupsTab,
|
||||
canAccessMembersTab,
|
||||
canAccessOrgAdmin,
|
||||
canAccessSettingsTab,
|
||||
} from "./navigation-permissions";
|
||||
import { AccountComponent } from "./settings/account.component";
|
||||
import { SettingsComponent } from "./settings/settings.component";
|
||||
import { TwoFactorSetupComponent } from "./settings/two-factor-setup.component";
|
||||
import { VaultModule } from "./vault/vault.module";
|
||||
|
||||
const routes: Routes = [
|
||||
@@ -34,18 +30,7 @@ const routes: Routes = [
|
||||
},
|
||||
{
|
||||
path: "settings",
|
||||
component: SettingsComponent,
|
||||
canActivate: [OrganizationPermissionsGuard],
|
||||
data: { organizationPermissions: canAccessSettingsTab },
|
||||
children: [
|
||||
{ path: "", pathMatch: "full", redirectTo: "account" },
|
||||
{ path: "account", component: AccountComponent, data: { titleId: "organizationInfo" } },
|
||||
{
|
||||
path: "two-factor",
|
||||
component: TwoFactorSetupComponent,
|
||||
data: { titleId: "twoStepLogin" },
|
||||
},
|
||||
],
|
||||
loadChildren: () => import("./settings").then((m) => m.OrganizationSettingsModule),
|
||||
},
|
||||
{
|
||||
path: "members",
|
||||
|
||||
12
apps/web/src/app/organizations/policies/index.ts
Normal file
12
apps/web/src/app/organizations/policies/index.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
export * from "./policies.module";
|
||||
export { BasePolicy, BasePolicyComponent } from "./base-policy.component";
|
||||
export { DisableSendPolicy } from "./disable-send.component";
|
||||
export { MasterPasswordPolicy } from "./master-password.component";
|
||||
export { PasswordGeneratorPolicy } from "./password-generator.component";
|
||||
export { PersonalOwnershipPolicy } from "./personal-ownership.component";
|
||||
export { RequireSsoPolicy } from "./require-sso.component";
|
||||
export { ResetPasswordPolicy } from "./reset-password.component";
|
||||
export { SendOptionsPolicy } from "./send-options.component";
|
||||
export { SingleOrgPolicy } from "./single-org.component";
|
||||
export { TwoFactorAuthenticationPolicy } from "./two-factor-authentication.component";
|
||||
export { PoliciesComponent } from "./policies.component";
|
||||
@@ -10,7 +10,7 @@ import { Organization } from "@bitwarden/common/models/domain/organization";
|
||||
import { PolicyResponse } from "@bitwarden/common/models/response/policyResponse";
|
||||
|
||||
import { PolicyListService } from "../../core";
|
||||
import { BasePolicy } from "../policies/base-policy.component";
|
||||
import { BasePolicy } from "../policies";
|
||||
|
||||
import { PolicyEditComponent } from "./policy-edit.component";
|
||||
|
||||
46
apps/web/src/app/organizations/policies/policies.module.ts
Normal file
46
apps/web/src/app/organizations/policies/policies.module.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { LooseComponentsModule, SharedModule } from "../../shared";
|
||||
|
||||
import { DisableSendPolicyComponent } from "./disable-send.component";
|
||||
import { MasterPasswordPolicyComponent } from "./master-password.component";
|
||||
import { PasswordGeneratorPolicyComponent } from "./password-generator.component";
|
||||
import { PersonalOwnershipPolicyComponent } from "./personal-ownership.component";
|
||||
import { PoliciesComponent } from "./policies.component";
|
||||
import { PolicyEditComponent } from "./policy-edit.component";
|
||||
import { RequireSsoPolicyComponent } from "./require-sso.component";
|
||||
import { ResetPasswordPolicyComponent } from "./reset-password.component";
|
||||
import { SendOptionsPolicyComponent } from "./send-options.component";
|
||||
import { SingleOrgPolicyComponent } from "./single-org.component";
|
||||
import { TwoFactorAuthenticationPolicyComponent } from "./two-factor-authentication.component";
|
||||
|
||||
@NgModule({
|
||||
imports: [SharedModule, LooseComponentsModule],
|
||||
declarations: [
|
||||
DisableSendPolicyComponent,
|
||||
MasterPasswordPolicyComponent,
|
||||
PasswordGeneratorPolicyComponent,
|
||||
PersonalOwnershipPolicyComponent,
|
||||
RequireSsoPolicyComponent,
|
||||
ResetPasswordPolicyComponent,
|
||||
SendOptionsPolicyComponent,
|
||||
SingleOrgPolicyComponent,
|
||||
TwoFactorAuthenticationPolicyComponent,
|
||||
PoliciesComponent,
|
||||
PolicyEditComponent,
|
||||
],
|
||||
exports: [
|
||||
DisableSendPolicyComponent,
|
||||
MasterPasswordPolicyComponent,
|
||||
PasswordGeneratorPolicyComponent,
|
||||
PersonalOwnershipPolicyComponent,
|
||||
RequireSsoPolicyComponent,
|
||||
ResetPasswordPolicyComponent,
|
||||
SendOptionsPolicyComponent,
|
||||
SingleOrgPolicyComponent,
|
||||
TwoFactorAuthenticationPolicyComponent,
|
||||
PoliciesComponent,
|
||||
PolicyEditComponent,
|
||||
],
|
||||
})
|
||||
export class PoliciesModule {}
|
||||
@@ -17,7 +17,7 @@ import { PolicyType } from "@bitwarden/common/enums/policyType";
|
||||
import { PolicyRequest } from "@bitwarden/common/models/request/policyRequest";
|
||||
import { PolicyResponse } from "@bitwarden/common/models/response/policyResponse";
|
||||
|
||||
import { BasePolicy, BasePolicyComponent } from "../policies/base-policy.component";
|
||||
import { BasePolicy, BasePolicyComponent } from "../policies";
|
||||
|
||||
@Component({
|
||||
selector: "app-policy-edit",
|
||||
@@ -51,16 +51,6 @@
|
||||
[disabled]="selfHosted || !canManageBilling"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="identifier">{{ "identifier" | i18n }}</label>
|
||||
<input
|
||||
id="identifier"
|
||||
class="form-control"
|
||||
type="text"
|
||||
name="Identifier"
|
||||
[(ngModel)]="org.identifier"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<app-avatar data="{{ org.name }}" dynamic="true" size="75" fontSize="35"></app-avatar>
|
||||
|
||||
2
apps/web/src/app/organizations/settings/index.ts
Normal file
2
apps/web/src/app/organizations/settings/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./organization-settings.module";
|
||||
export { DeleteOrganizationComponent } from "./delete-organization.component";
|
||||
@@ -0,0 +1,52 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
import { RouterModule, Routes } from "@angular/router";
|
||||
|
||||
import { Organization } from "@bitwarden/common/models/domain/organization";
|
||||
|
||||
import { OrganizationPermissionsGuard } from "../guards/org-permissions.guard";
|
||||
import { canAccessSettingsTab } from "../navigation-permissions";
|
||||
import { PoliciesComponent } from "../policies";
|
||||
|
||||
import { AccountComponent } from "./account.component";
|
||||
import { SettingsComponent } from "./settings.component";
|
||||
import { TwoFactorSetupComponent } from "./two-factor-setup.component";
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: "",
|
||||
component: SettingsComponent,
|
||||
canActivate: [OrganizationPermissionsGuard],
|
||||
data: { organizationPermissions: canAccessSettingsTab },
|
||||
children: [
|
||||
{ path: "", pathMatch: "full", redirectTo: "account" },
|
||||
{ path: "account", component: AccountComponent, data: { titleId: "organizationInfo" } },
|
||||
{
|
||||
path: "two-factor",
|
||||
component: TwoFactorSetupComponent,
|
||||
data: { titleId: "twoStepLogin" },
|
||||
},
|
||||
{
|
||||
path: "policies",
|
||||
component: PoliciesComponent,
|
||||
canActivate: [OrganizationPermissionsGuard],
|
||||
data: {
|
||||
organizationPermissions: (org: Organization) => org.canManagePolicies,
|
||||
titleId: "policies",
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "tools",
|
||||
loadChildren: () =>
|
||||
import("../tools/import-export/org-import-export.module").then(
|
||||
(m) => m.OrganizationImportExportModule
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule],
|
||||
})
|
||||
export class OrganizationSettingsRoutingModule {}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { LooseComponentsModule, SharedModule } from "../../shared";
|
||||
import { PoliciesModule } from "../policies";
|
||||
|
||||
import { AccountComponent } from "./account.component";
|
||||
import { AdjustSubscription } from "./adjust-subscription.component";
|
||||
import { ChangePlanComponent } from "./change-plan.component";
|
||||
import { DeleteOrganizationComponent } from "./delete-organization.component";
|
||||
import { DownloadLicenseComponent } from "./download-license.component";
|
||||
import { OrganizationSettingsRoutingModule } from "./organization-settings-routing.module";
|
||||
import { SettingsComponent } from "./settings.component";
|
||||
import { TwoFactorSetupComponent } from "./two-factor-setup.component";
|
||||
|
||||
@NgModule({
|
||||
imports: [SharedModule, LooseComponentsModule, PoliciesModule, OrganizationSettingsRoutingModule],
|
||||
declarations: [
|
||||
SettingsComponent,
|
||||
AccountComponent,
|
||||
AdjustSubscription,
|
||||
ChangePlanComponent,
|
||||
DeleteOrganizationComponent,
|
||||
DownloadLicenseComponent,
|
||||
TwoFactorSetupComponent,
|
||||
],
|
||||
})
|
||||
export class OrganizationSettingsModule {}
|
||||
@@ -7,14 +7,54 @@
|
||||
<a routerLink="account" class="list-group-item" routerLinkActive="active">
|
||||
{{ "organizationInfo" | i18n }}
|
||||
</a>
|
||||
<a
|
||||
routerLink="policies"
|
||||
class="list-group-item"
|
||||
routerLinkActive="active"
|
||||
*ngIf="organization?.canManagePolicies"
|
||||
>
|
||||
{{ "policies" | i18n }}
|
||||
</a>
|
||||
<a
|
||||
routerLink="two-factor"
|
||||
class="list-group-item"
|
||||
routerLinkActive="active"
|
||||
*ngIf="access2fa"
|
||||
*ngIf="organization?.use2fa"
|
||||
>
|
||||
{{ "twoStepLogin" | i18n }}
|
||||
</a>
|
||||
<a
|
||||
routerLink="tools/import"
|
||||
class="list-group-item"
|
||||
routerLinkActive="active"
|
||||
*ngIf="organization?.canAccessImportExport"
|
||||
>
|
||||
{{ "importData" | i18n }}
|
||||
</a>
|
||||
<a
|
||||
routerLink="tools/export"
|
||||
class="list-group-item"
|
||||
routerLinkActive="active"
|
||||
*ngIf="organization?.canAccessImportExport"
|
||||
>
|
||||
{{ "exportVault" | i18n }}
|
||||
</a>
|
||||
<a
|
||||
routerLink="sso"
|
||||
class="list-group-item"
|
||||
routerLinkActive="active"
|
||||
*ngIf="organization?.canManageSso"
|
||||
>
|
||||
{{ "singleSignOn" | i18n }}
|
||||
</a>
|
||||
<a
|
||||
routerLink="scim"
|
||||
class="list-group-item"
|
||||
routerLinkActive="active"
|
||||
*ngIf="organization?.canManageScim"
|
||||
>
|
||||
{{ "scim" | i18n }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,30 +1,34 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { Component, OnDestroy, OnInit } from "@angular/core";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
import { Subject, switchMap, takeUntil } from "rxjs";
|
||||
|
||||
import { OrganizationService } from "@bitwarden/common/abstractions/organization.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
import { Organization } from "@bitwarden/common/models/domain/organization";
|
||||
|
||||
@Component({
|
||||
selector: "app-org-settings",
|
||||
templateUrl: "settings.component.html",
|
||||
})
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
||||
export class SettingsComponent {
|
||||
access2fa = false;
|
||||
showBilling: boolean;
|
||||
export class SettingsComponent implements OnInit, OnDestroy {
|
||||
organization: Organization;
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private organizationService: OrganizationService,
|
||||
private platformUtilsService: PlatformUtilsService
|
||||
) {}
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
constructor(private route: ActivatedRoute, private organizationService: OrganizationService) {}
|
||||
|
||||
ngOnInit() {
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
|
||||
this.route.parent.params.subscribe(async (params) => {
|
||||
const organization = await this.organizationService.get(params.organizationId);
|
||||
this.showBilling = !this.platformUtilsService.isSelfHost() && organization.canManageBilling;
|
||||
this.access2fa = organization.use2fa;
|
||||
});
|
||||
this.route.params
|
||||
.pipe(
|
||||
switchMap(async (params) => await this.organizationService.get(params.organizationId)),
|
||||
takeUntil(this.destroy$)
|
||||
)
|
||||
.subscribe((organization) => {
|
||||
this.organization = organization;
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,8 @@ import { ProductType } from "@bitwarden/common/enums/productType";
|
||||
import { Organization } from "@bitwarden/common/models/domain/organization";
|
||||
import { OrganizationSponsorshipRedeemRequest } from "@bitwarden/common/models/request/organization/organizationSponsorshipRedeemRequest";
|
||||
|
||||
import { DeleteOrganizationComponent } from "../../organizations/settings";
|
||||
import { OrganizationPlansComponent } from "../../settings/organization-plans.component";
|
||||
import { DeleteOrganizationComponent } from "../settings/delete-organization.component";
|
||||
|
||||
@Component({
|
||||
selector: "families-for-enterprise-setup",
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
<div class="tabbed-header">
|
||||
<h1>{{ "twoStepLogin" | i18n }}</h1>
|
||||
<h1 *ngIf="!organizationId">{{ "twoStepLogin" | i18n }}</h1>
|
||||
<h1 *ngIf="organizationId">{{ "twoStepLoginEnforcement" | i18n }}</h1>
|
||||
</div>
|
||||
<p *ngIf="!organizationId">{{ "twoStepLoginDesc" | i18n }}</p>
|
||||
<p *ngIf="organizationId">{{ "twoStepLoginOrganizationDesc" | i18n }}</p>
|
||||
<ng-container *ngIf="organizationId">
|
||||
<p>
|
||||
{{ "twoStepLoginOrganizationDescStart" | i18n }}
|
||||
<a routerLink="../policies">{{ "twoStepLoginPolicy" | i18n }}.</a>
|
||||
<br />
|
||||
{{ "twoStepLoginOrganizationDuoDesc" | i18n }}
|
||||
</p>
|
||||
<p>{{ "twoStepLoginOrganizationSsoDesc" | i18n }}</p>
|
||||
</ng-container>
|
||||
<bit-callout type="warning" *ngIf="!organizationId">
|
||||
<p>{{ "twoStepLoginRecoveryWarning" | i18n }}</p>
|
||||
<button bitButton buttonType="secondary" (click)="recoveryCode()">
|
||||
@@ -55,7 +64,13 @@
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<form *ngIf="!loading" #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
|
||||
<form
|
||||
*ngIf="!loading && !organizationId"
|
||||
#form
|
||||
(ngSubmit)="submit()"
|
||||
[appApiAction]="formPromise"
|
||||
ngNativeValidate
|
||||
>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<h2 class="mt-5 spaced-header">
|
||||
|
||||
@@ -42,29 +42,10 @@ import { GroupAddEditComponent as OrgGroupAddEditComponent } from "../organizati
|
||||
import { GroupsComponent as OrgGroupsComponent } from "../organizations/manage/groups.component";
|
||||
import { ManageComponent as OrgManageComponent } from "../organizations/manage/manage.component";
|
||||
import { PeopleComponent as OrgPeopleComponent } from "../organizations/manage/people.component";
|
||||
import { PoliciesComponent as OrgPoliciesComponent } from "../organizations/manage/policies.component";
|
||||
import { PolicyEditComponent as OrgPolicyEditComponent } from "../organizations/manage/policy-edit.component";
|
||||
import { ResetPasswordComponent as OrgResetPasswordComponent } from "../organizations/manage/reset-password.component";
|
||||
import { UserAddEditComponent as OrgUserAddEditComponent } from "../organizations/manage/user-add-edit.component";
|
||||
import { UserConfirmComponent as OrgUserConfirmComponent } from "../organizations/manage/user-confirm.component";
|
||||
import { UserGroupsComponent as OrgUserGroupsComponent } from "../organizations/manage/user-groups.component";
|
||||
import { DisableSendPolicyComponent } from "../organizations/policies/disable-send.component";
|
||||
import { MasterPasswordPolicyComponent } from "../organizations/policies/master-password.component";
|
||||
import { PasswordGeneratorPolicyComponent } from "../organizations/policies/password-generator.component";
|
||||
import { PersonalOwnershipPolicyComponent } from "../organizations/policies/personal-ownership.component";
|
||||
import { RequireSsoPolicyComponent } from "../organizations/policies/require-sso.component";
|
||||
import { ResetPasswordPolicyComponent } from "../organizations/policies/reset-password.component";
|
||||
import { SendOptionsPolicyComponent } from "../organizations/policies/send-options.component";
|
||||
import { SingleOrgPolicyComponent } from "../organizations/policies/single-org.component";
|
||||
import { TwoFactorAuthenticationPolicyComponent } from "../organizations/policies/two-factor-authentication.component";
|
||||
import { AccountComponent as OrgAccountComponent } from "../organizations/settings/account.component";
|
||||
import { AdjustSubscription } from "../organizations/settings/adjust-subscription.component";
|
||||
import { ChangePlanComponent } from "../organizations/settings/change-plan.component";
|
||||
import { DeleteOrganizationComponent } from "../organizations/settings/delete-organization.component";
|
||||
import { DownloadLicenseComponent } from "../organizations/settings/download-license.component";
|
||||
import { ImageSubscriptionHiddenComponent as OrgSubscriptionHiddenComponent } from "../organizations/settings/image-subscription-hidden.component";
|
||||
import { SettingsComponent as OrgSettingComponent } from "../organizations/settings/settings.component";
|
||||
import { TwoFactorSetupComponent as OrgTwoFactorSetupComponent } from "../organizations/settings/two-factor-setup.component";
|
||||
import { AcceptFamilySponsorshipComponent } from "../organizations/sponsorships/accept-family-sponsorship.component";
|
||||
import { FamiliesForEnterpriseSetupComponent } from "../organizations/sponsorships/families-for-enterprise-setup.component";
|
||||
import { ExposedPasswordsReportComponent as OrgExposedPasswordsReportComponent } from "../organizations/tools/exposed-passwords-report.component";
|
||||
@@ -172,7 +153,6 @@ import { SharedModule } from ".";
|
||||
AddEditCustomFieldsComponent,
|
||||
AdjustPaymentComponent,
|
||||
AdjustStorageComponent,
|
||||
AdjustSubscription,
|
||||
ApiKeyComponent,
|
||||
AttachmentsComponent,
|
||||
BillingSyncKeyComponent,
|
||||
@@ -184,15 +164,11 @@ import { SharedModule } from ".";
|
||||
ChangeEmailComponent,
|
||||
ChangeKdfComponent,
|
||||
ChangePasswordComponent,
|
||||
ChangePlanComponent,
|
||||
CollectionsComponent,
|
||||
CreateOrganizationComponent,
|
||||
DeauthorizeSessionsComponent,
|
||||
DeleteAccountComponent,
|
||||
DeleteOrganizationComponent,
|
||||
DisableSendPolicyComponent,
|
||||
DomainRulesComponent,
|
||||
DownloadLicenseComponent,
|
||||
EmergencyAccessAddEditComponent,
|
||||
EmergencyAccessAttachmentsComponent,
|
||||
EmergencyAccessComponent,
|
||||
@@ -206,11 +182,9 @@ import { SharedModule } from ".";
|
||||
FrontendLayoutComponent,
|
||||
HintComponent,
|
||||
LockComponent,
|
||||
MasterPasswordPolicyComponent,
|
||||
NavbarComponent,
|
||||
NestedCheckboxComponent,
|
||||
OrganizationSwitcherComponent,
|
||||
OrgAccountComponent,
|
||||
OrgAddEditComponent,
|
||||
OrganizationLayoutComponent,
|
||||
OrganizationPlansComponent,
|
||||
@@ -230,14 +204,9 @@ import { SharedModule } from ".";
|
||||
OrgManageCollectionsComponent,
|
||||
OrgManageComponent,
|
||||
OrgPeopleComponent,
|
||||
OrgPoliciesComponent,
|
||||
OrgPolicyEditComponent,
|
||||
OrgResetPasswordComponent,
|
||||
OrgReusedPasswordsReportComponent,
|
||||
OrgSettingComponent,
|
||||
OrgToolsComponent,
|
||||
OrgTwoFactorSetupComponent,
|
||||
OrgSubscriptionHiddenComponent,
|
||||
OrgUnsecuredWebsitesReportComponent,
|
||||
OrgUserAddEditComponent,
|
||||
OrgUserConfirmComponent,
|
||||
@@ -245,12 +214,10 @@ import { SharedModule } from ".";
|
||||
OrgWeakPasswordsReportComponent,
|
||||
GeneratorComponent,
|
||||
PasswordGeneratorHistoryComponent,
|
||||
PasswordGeneratorPolicyComponent,
|
||||
PasswordRepromptComponent,
|
||||
UserVerificationPromptComponent,
|
||||
PaymentComponent,
|
||||
PaymentMethodComponent,
|
||||
PersonalOwnershipPolicyComponent,
|
||||
PreferencesComponent,
|
||||
PremiumBadgeComponent,
|
||||
PremiumComponent,
|
||||
@@ -261,25 +228,20 @@ import { SharedModule } from ".";
|
||||
RecoverTwoFactorComponent,
|
||||
RegisterComponent,
|
||||
RemovePasswordComponent,
|
||||
RequireSsoPolicyComponent,
|
||||
ResetPasswordPolicyComponent,
|
||||
SecurityComponent,
|
||||
SecurityKeysComponent,
|
||||
SendAddEditComponent,
|
||||
SendComponent,
|
||||
SendEffluxDatesComponent,
|
||||
SendOptionsPolicyComponent,
|
||||
SetPasswordComponent,
|
||||
SettingsComponent,
|
||||
ShareComponent,
|
||||
SingleOrgPolicyComponent,
|
||||
SponsoredFamiliesComponent,
|
||||
SponsoringOrgRowComponent,
|
||||
SsoComponent,
|
||||
SubscriptionComponent,
|
||||
TaxInfoComponent,
|
||||
ToolsComponent,
|
||||
TwoFactorAuthenticationPolicyComponent,
|
||||
TwoFactorAuthenticatorComponent,
|
||||
TwoFactorComponent,
|
||||
TwoFactorDuoComponent,
|
||||
@@ -316,7 +278,6 @@ import { SharedModule } from ".";
|
||||
AddEditCustomFieldsComponent,
|
||||
AdjustPaymentComponent,
|
||||
AdjustStorageComponent,
|
||||
AdjustSubscription,
|
||||
ApiKeyComponent,
|
||||
AttachmentsComponent,
|
||||
BulkActionsComponent,
|
||||
@@ -327,15 +288,11 @@ import { SharedModule } from ".";
|
||||
ChangeEmailComponent,
|
||||
ChangeKdfComponent,
|
||||
ChangePasswordComponent,
|
||||
ChangePlanComponent,
|
||||
CollectionsComponent,
|
||||
CreateOrganizationComponent,
|
||||
DeauthorizeSessionsComponent,
|
||||
DeleteAccountComponent,
|
||||
DeleteOrganizationComponent,
|
||||
DisableSendPolicyComponent,
|
||||
DomainRulesComponent,
|
||||
DownloadLicenseComponent,
|
||||
EmergencyAccessAddEditComponent,
|
||||
EmergencyAccessAttachmentsComponent,
|
||||
EmergencyAccessComponent,
|
||||
@@ -349,11 +306,9 @@ import { SharedModule } from ".";
|
||||
FrontendLayoutComponent,
|
||||
HintComponent,
|
||||
LockComponent,
|
||||
MasterPasswordPolicyComponent,
|
||||
NavbarComponent,
|
||||
NestedCheckboxComponent,
|
||||
OrganizationSwitcherComponent,
|
||||
OrgAccountComponent,
|
||||
OrgAddEditComponent,
|
||||
OrganizationLayoutComponent,
|
||||
OrganizationPlansComponent,
|
||||
@@ -373,13 +328,9 @@ import { SharedModule } from ".";
|
||||
OrgManageCollectionsComponent,
|
||||
OrgManageComponent,
|
||||
OrgPeopleComponent,
|
||||
OrgPoliciesComponent,
|
||||
OrgPolicyEditComponent,
|
||||
OrgResetPasswordComponent,
|
||||
OrgReusedPasswordsReportComponent,
|
||||
OrgSettingComponent,
|
||||
OrgToolsComponent,
|
||||
OrgTwoFactorSetupComponent,
|
||||
OrgUnsecuredWebsitesReportComponent,
|
||||
OrgUserAddEditComponent,
|
||||
OrgUserConfirmComponent,
|
||||
@@ -387,11 +338,9 @@ import { SharedModule } from ".";
|
||||
OrgWeakPasswordsReportComponent,
|
||||
GeneratorComponent,
|
||||
PasswordGeneratorHistoryComponent,
|
||||
PasswordGeneratorPolicyComponent,
|
||||
PasswordRepromptComponent,
|
||||
PaymentComponent,
|
||||
PaymentMethodComponent,
|
||||
PersonalOwnershipPolicyComponent,
|
||||
PreferencesComponent,
|
||||
PremiumBadgeComponent,
|
||||
PremiumComponent,
|
||||
@@ -402,25 +351,20 @@ import { SharedModule } from ".";
|
||||
RecoverTwoFactorComponent,
|
||||
RegisterComponent,
|
||||
RemovePasswordComponent,
|
||||
RequireSsoPolicyComponent,
|
||||
ResetPasswordPolicyComponent,
|
||||
SecurityComponent,
|
||||
SecurityKeysComponent,
|
||||
SendAddEditComponent,
|
||||
SendComponent,
|
||||
SendEffluxDatesComponent,
|
||||
SendOptionsPolicyComponent,
|
||||
SetPasswordComponent,
|
||||
SettingsComponent,
|
||||
ShareComponent,
|
||||
SingleOrgPolicyComponent,
|
||||
SponsoredFamiliesComponent,
|
||||
SponsoringOrgRowComponent,
|
||||
SsoComponent,
|
||||
SubscriptionComponent,
|
||||
TaxInfoComponent,
|
||||
ToolsComponent,
|
||||
TwoFactorAuthenticationPolicyComponent,
|
||||
TwoFactorAuthenticatorComponent,
|
||||
TwoFactorComponent,
|
||||
TwoFactorDuoComponent,
|
||||
|
||||
@@ -1288,11 +1288,24 @@
|
||||
"twoStepLogin": {
|
||||
"message": "Two-step login"
|
||||
},
|
||||
"twoStepLoginEnforcement": {
|
||||
"message": "Two-step Login Enforcement"
|
||||
},
|
||||
"twoStepLoginDesc": {
|
||||
"message": "Secure your account by requiring an additional step when logging in."
|
||||
},
|
||||
"twoStepLoginOrganizationDesc": {
|
||||
"message": "Require two-step login for your organization's users by configuring providers at the organization level."
|
||||
"twoStepLoginOrganizationDescStart": {
|
||||
"message": "Enforce Bitwarden Two-step Login options for members by using the ",
|
||||
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Enforce Bitwarden Two-step Login options for members by using the Two-step Login Policy.'"
|
||||
},
|
||||
"twoStepLoginPolicy": {
|
||||
"message": "Two-step Login Policy"
|
||||
},
|
||||
"twoStepLoginOrganizationDuoDesc": {
|
||||
"message": "To enforce Two-step Login through Duo, use the options below."
|
||||
},
|
||||
"twoStepLoginOrganizationSsoDesc": {
|
||||
"message": "If you have setup SSO or plan to, Two-step Login may already be enforced through your Identity Provider."
|
||||
},
|
||||
"twoStepLoginRecoveryWarning": {
|
||||
"message": "Enabling two-step login can permanently lock you out of your Bitwarden account. A recovery code allows you to access your account in the event that you can no longer use your normal two-step login provider (ex. you lose your device). Bitwarden support will not be able to assist you if you lose access to your account. We recommend you write down or print the recovery code and keep it in a safe place."
|
||||
@@ -3687,6 +3700,12 @@
|
||||
"ssoIdentifierRequired": {
|
||||
"message": "Organization Identifier is required."
|
||||
},
|
||||
"ssoIdentifier": {
|
||||
"message": "SSO Identifier"
|
||||
},
|
||||
"ssoIdentifierHint": {
|
||||
"message": "Provide this ID to your members to login with SSO."
|
||||
},
|
||||
"unlinkSso": {
|
||||
"message": "Unlink SSO"
|
||||
},
|
||||
@@ -4032,7 +4051,7 @@
|
||||
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
|
||||
},
|
||||
"disableSendExemption": {
|
||||
"message": "Organization users that can manage the organization's policies are exempt from this policy's enforcement."
|
||||
"message": "Organization members that can manage the organization's policies are exempt from this policy's enforcement."
|
||||
},
|
||||
"sendDisabled": {
|
||||
"message": "Send disabled",
|
||||
@@ -4051,7 +4070,7 @@
|
||||
"description": "'Sends' is a plural noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
|
||||
},
|
||||
"sendOptionsExemption": {
|
||||
"message": "Organization users that can manage the organization's policies are exempt from this policy's enforcement."
|
||||
"message": "Organization members that can manage the organization's policies are exempt from this policy's enforcement."
|
||||
},
|
||||
"disableHideEmail": {
|
||||
"message": "Always show member’s email address with recipients when creating or editing a send.",
|
||||
@@ -4362,19 +4381,19 @@
|
||||
"message": "Allow admins to reset master passwords for members."
|
||||
},
|
||||
"resetPasswordPolicyWarning": {
|
||||
"message": "Users in the organization will need to self-enroll or be auto-enrolled before administrators can reset their master password."
|
||||
"message": "Members in the organization will need to self-enroll or be auto-enrolled before administrators can reset their master password."
|
||||
},
|
||||
"resetPasswordPolicyAutoEnroll": {
|
||||
"message": "Automatic Enrollment"
|
||||
},
|
||||
"resetPasswordPolicyAutoEnrollDescription": {
|
||||
"message": "All users will be automatically enrolled in password reset once their invite is accepted and will not be allowed to withdraw."
|
||||
"message": "All members will be automatically enrolled in password reset once their invite is accepted and will not be allowed to withdraw."
|
||||
},
|
||||
"resetPasswordPolicyAutoEnrollWarning": {
|
||||
"message": "Users already in the organization will not be retroactively enrolled in password reset. They will need to self-enroll before administrators can reset their master password."
|
||||
"message": "Members already in the organization will not be retroactively enrolled in password reset. They will need to self-enroll before administrators can reset their master password."
|
||||
},
|
||||
"resetPasswordPolicyAutoEnrollCheckbox": {
|
||||
"message": "Require new users to be enrolled automatically"
|
||||
"message": "Require new members to be enrolled automatically"
|
||||
},
|
||||
"resetPasswordAutoEnrollInviteWarning": {
|
||||
"message": "This organization has an enterprise policy that will automatically enroll you in password reset. Enrollment will allow organization administrators to change your master password."
|
||||
@@ -5377,6 +5396,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"inputMaxLength": {
|
||||
"message": "Input must not exceed $COUNT$ characters in length.",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"content": "$1",
|
||||
"example": "20"
|
||||
}
|
||||
}
|
||||
},
|
||||
"fieldsNeedAttention": {
|
||||
"message": "$COUNT$ field(s) above need your attention.",
|
||||
"placeholders": {
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { Directive, Input, OnInit, Self } from "@angular/core";
|
||||
import { ControlValueAccessor, UntypedFormControl, NgControl, Validators } from "@angular/forms";
|
||||
|
||||
import { dirtyRequired } from "@bitwarden/angular/validators/dirty.validator";
|
||||
|
||||
/** For use in the SSO Config Form only - will be deprecated by the Component Library */
|
||||
@Directive()
|
||||
export abstract class BaseCvaComponent implements ControlValueAccessor, OnInit {
|
||||
@@ -15,10 +13,7 @@ export abstract class BaseCvaComponent implements ControlValueAccessor, OnInit {
|
||||
}
|
||||
|
||||
get isRequired() {
|
||||
return (
|
||||
this.controlDir.control.hasValidator(Validators.required) ||
|
||||
this.controlDir.control.hasValidator(dirtyRequired)
|
||||
);
|
||||
return this.controlDir.control.hasValidator(Validators.required);
|
||||
}
|
||||
|
||||
@Input() label: string;
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
<div class="form-group">
|
||||
<label>{{ label }}</label>
|
||||
<div class="input-group">
|
||||
<input class="form-control" readonly [value]="controlValue" />
|
||||
<div class="input-group-append" *ngIf="showLaunch">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-secondary"
|
||||
appA11yTitle="{{ 'launch' | i18n }}"
|
||||
(click)="launchUri(controlValue)"
|
||||
>
|
||||
<i class="bwi bwi-lg bwi-external-link" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="input-group-append" *ngIf="showCopy">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-secondary"
|
||||
appA11yTitle="{{ 'copyValue' | i18n }}"
|
||||
(click)="copy(controlValue)"
|
||||
>
|
||||
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,25 +0,0 @@
|
||||
import { Component, Input } from "@angular/core";
|
||||
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
|
||||
/** For use in the SSO Config Form only - will be deprecated by the Component Library */
|
||||
@Component({
|
||||
selector: "app-input-text-readonly",
|
||||
templateUrl: "input-text-readonly.component.html",
|
||||
})
|
||||
export class InputTextReadOnlyComponent {
|
||||
@Input() controlValue: string;
|
||||
@Input() label: string;
|
||||
@Input() showCopy = true;
|
||||
@Input() showLaunch = false;
|
||||
|
||||
constructor(private platformUtilsService: PlatformUtilsService) {}
|
||||
|
||||
copy(value: string) {
|
||||
this.platformUtilsService.copyToClipboard(value);
|
||||
}
|
||||
|
||||
launchUri(url: string) {
|
||||
this.platformUtilsService.launchUri(url);
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
<div class="form-group">
|
||||
<label [attr.for]="controlId">
|
||||
{{ label }}
|
||||
<small *ngIf="isRequired" class="text-muted form-text d-inline"
|
||||
>({{ "required" | i18n }})</small
|
||||
>
|
||||
</label>
|
||||
<input
|
||||
[formControl]="internalControl"
|
||||
class="form-control"
|
||||
[attr.id]="controlId"
|
||||
[attr.aria-describedby]="describedById"
|
||||
[attr.aria-invalid]="controlDir.control.invalid"
|
||||
(blur)="onBlurInternal()"
|
||||
/>
|
||||
<div *ngIf="showDescribedBy" [attr.id]="describedById">
|
||||
<small
|
||||
*ngIf="helperText != null && !controlDir.control.hasError(helperTextSameAsError)"
|
||||
class="form-text text-muted"
|
||||
>
|
||||
{{ helperText }}
|
||||
</small>
|
||||
<small class="error-inline" *ngIf="controlDir.control.hasError('required')" role="alert">
|
||||
<i class="bwi bwi-exclamation-circle" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{ "error" | i18n }}:</span>
|
||||
{{
|
||||
controlDir.control.hasError(helperTextSameAsError)
|
||||
? helperText
|
||||
: ("fieldRequiredError" | i18n: label)
|
||||
}}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,48 +0,0 @@
|
||||
import { Component, Input, OnInit } from "@angular/core";
|
||||
|
||||
import { BaseCvaComponent } from "./base-cva.component";
|
||||
|
||||
/** For use in the SSO Config Form only - will be deprecated by the Component Library */
|
||||
@Component({
|
||||
selector: "app-input-text[label][controlId]",
|
||||
templateUrl: "input-text.component.html",
|
||||
})
|
||||
export class InputTextComponent extends BaseCvaComponent implements OnInit {
|
||||
@Input() helperTextSameAsError: string;
|
||||
@Input() requiredErrorMessage: string;
|
||||
@Input() stripSpaces = false;
|
||||
|
||||
transformValue: (value: string) => string = null;
|
||||
|
||||
ngOnInit() {
|
||||
super.ngOnInit();
|
||||
if (this.stripSpaces) {
|
||||
this.transformValue = this.doStripSpaces;
|
||||
}
|
||||
}
|
||||
|
||||
writeValue(value: string) {
|
||||
this.internalControl.setValue(value == null ? "" : value);
|
||||
}
|
||||
|
||||
protected onValueChangesInternal: any = (value: string) => {
|
||||
let newValue = value;
|
||||
if (this.transformValue != null) {
|
||||
newValue = this.transformValue(value);
|
||||
this.internalControl.setValue(newValue, { emitEvent: false });
|
||||
}
|
||||
this.onChange(newValue);
|
||||
};
|
||||
|
||||
protected onValueChangeInternal(value: string) {
|
||||
let newValue = value;
|
||||
if (this.transformValue != null) {
|
||||
newValue = this.transformValue(value);
|
||||
this.internalControl.setValue(newValue, { emitEvent: false });
|
||||
}
|
||||
}
|
||||
|
||||
private doStripSpaces(value: string) {
|
||||
return value.replace(/ /g, "");
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
<div class="form-group">
|
||||
<label [attr.for]="controlId">
|
||||
{{ label }}
|
||||
<small *ngIf="isRequired" class="text-muted form-text d-inline"
|
||||
>({{ "required" | i18n }})</small
|
||||
>
|
||||
</label>
|
||||
<select
|
||||
class="form-control"
|
||||
[attr.id]="controlId"
|
||||
[attr.aria-invalid]="controlDir.control.invalid"
|
||||
[formControl]="internalControl"
|
||||
(blur)="onBlurInternal()"
|
||||
>
|
||||
<option *ngFor="let o of selectOptions" [ngValue]="o.value" disabled="{{ o.disabled }}">
|
||||
{{ o.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
@@ -1,14 +0,0 @@
|
||||
import { Component, Input } from "@angular/core";
|
||||
|
||||
import { SelectOptions } from "@bitwarden/angular/interfaces/selectOptions";
|
||||
|
||||
import { BaseCvaComponent } from "./base-cva.component";
|
||||
|
||||
/** For use in the SSO Config Form only - will be deprecated by the Component Library */
|
||||
@Component({
|
||||
selector: "app-select",
|
||||
templateUrl: "select.component.html",
|
||||
})
|
||||
export class SelectComponent extends BaseCvaComponent {
|
||||
@Input() selectOptions: SelectOptions[];
|
||||
}
|
||||
@@ -35,6 +35,14 @@
|
||||
[helperText]="'allowSsoDesc' | i18n"
|
||||
></app-input-checkbox>
|
||||
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "ssoIdentifier" | i18n }}</bit-label>
|
||||
<input bitInput type="text" [formControl]="ssoIdentifier" />
|
||||
<bit-hint>{{ "ssoIdentifierHint" | i18n }}</bit-hint>
|
||||
</bit-form-field>
|
||||
|
||||
<hr />
|
||||
|
||||
<div class="form-group">
|
||||
<label>{{ "memberDecryptionOption" | i18n }}</label>
|
||||
<div class="form-check form-check-block">
|
||||
@@ -80,66 +88,55 @@
|
||||
{{ "keyConnectorWarning" | i18n }}
|
||||
</app-callout>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="keyConnectorUrl">
|
||||
{{ "keyConnectorUrl" | i18n }}
|
||||
<small class="text-muted form-text d-inline">({{ "required" | i18n }})</small>
|
||||
</label>
|
||||
<div class="input-group">
|
||||
<input
|
||||
class="form-control"
|
||||
formControlName="keyConnectorUrl"
|
||||
id="keyConnectorUrl"
|
||||
aria-describedby="keyConnectorUrlDesc"
|
||||
(change)="haveTestedKeyConnector = false"
|
||||
appInputStripSpaces
|
||||
appA11yInvalid
|
||||
/>
|
||||
<div class="input-group-append">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-secondary"
|
||||
(click)="validateKeyConnectorUrl()"
|
||||
[disabled]="!enableTestKeyConnector"
|
||||
>
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
*ngIf="keyConnectorUrl.pending"
|
||||
></i>
|
||||
<span *ngIf="!keyConnectorUrl.pending">
|
||||
{{ "keyConnectorTest" | i18n }}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="haveTestedKeyConnector" id="keyConnectorUrlDesc" aria-live="polite">
|
||||
<small
|
||||
class="error-inline"
|
||||
*ngIf="keyConnectorUrl.hasError('invalidUrl'); else keyConnectorSuccess"
|
||||
>
|
||||
<i class="bwi bwi-exclamation-circle" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{ "error" | i18n }}:</span>
|
||||
{{ "keyConnectorTestFail" | i18n }}
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "keyConnectorUrl" | i18n }}</bit-label>
|
||||
<input
|
||||
bitInput
|
||||
type="text"
|
||||
required
|
||||
formControlName="keyConnectorUrl"
|
||||
appInputStripSpaces
|
||||
(input)="haveTestedKeyConnector = false"
|
||||
/>
|
||||
<button
|
||||
bitSuffix
|
||||
bitButton
|
||||
[disabled]="!enableTestKeyConnector"
|
||||
type="button"
|
||||
(click)="validateKeyConnectorUrl()"
|
||||
>
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
*ngIf="keyConnectorUrl.pending"
|
||||
></i>
|
||||
<span *ngIf="!keyConnectorUrl.pending">
|
||||
{{ "keyConnectorTest" | i18n }}
|
||||
</span>
|
||||
</button>
|
||||
<bit-hint
|
||||
aria-live="polite"
|
||||
*ngIf="haveTestedKeyConnector && !keyConnectorUrl.hasError('invalidUrl')"
|
||||
>
|
||||
<small class="text-success">
|
||||
<i class="bwi bwi-check-circle" aria-hidden="true"></i>
|
||||
{{ "keyConnectorTestSuccess" | i18n }}
|
||||
</small>
|
||||
<ng-template #keyConnectorSuccess>
|
||||
<small class="text-success">
|
||||
<i class="bwi bwi-check-circle" aria-hidden="true"></i>
|
||||
{{ "keyConnectorTestSuccess" | i18n }}
|
||||
</small>
|
||||
</ng-template>
|
||||
</div>
|
||||
</div>
|
||||
</bit-hint>
|
||||
</bit-form-field>
|
||||
</ng-container>
|
||||
|
||||
<app-select
|
||||
controlId="type"
|
||||
[label]="'type' | i18n"
|
||||
[selectOptions]="ssoTypeOptions"
|
||||
formControlName="configType"
|
||||
>
|
||||
</app-select>
|
||||
<hr />
|
||||
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "type" | i18n }}</bit-label>
|
||||
<select bitInput formControlName="configType">
|
||||
<option *ngFor="let o of ssoTypeOptions" [ngValue]="o.value" disabled="{{ o.disabled }}">
|
||||
{{ o.name }}
|
||||
</option>
|
||||
</select>
|
||||
</bit-form-field>
|
||||
</ng-container>
|
||||
|
||||
<!-- OIDC -->
|
||||
@@ -150,52 +147,67 @@
|
||||
<div class="config-section">
|
||||
<h2 class="secondary-header">{{ "openIdConnectConfig" | i18n }}</h2>
|
||||
|
||||
<app-input-text-readonly
|
||||
[label]="'callbackPath' | i18n"
|
||||
[controlValue]="callbackPath"
|
||||
></app-input-text-readonly>
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "callbackPath" | i18n }}</bit-label>
|
||||
<input bitInput disabled [value]="callbackPath" />
|
||||
<button
|
||||
bitButton
|
||||
bitSuffix
|
||||
type="button"
|
||||
[appCopyClick]="callbackPath"
|
||||
[appA11yTitle]="'copyValue' | i18n"
|
||||
>
|
||||
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
|
||||
</button>
|
||||
</bit-form-field>
|
||||
|
||||
<app-input-text-readonly
|
||||
[label]="'signedOutCallbackPath' | i18n"
|
||||
[controlValue]="signedOutCallbackPath"
|
||||
></app-input-text-readonly>
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "signedOutCallbackPath" | i18n }}</bit-label>
|
||||
<input bitInput disabled [value]="signedOutCallbackPath" />
|
||||
<button
|
||||
bitButton
|
||||
bitSuffix
|
||||
type="button"
|
||||
[appCopyClick]="signedOutCallbackPath"
|
||||
[appA11yTitle]="'copyValue' | i18n"
|
||||
>
|
||||
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
|
||||
</button>
|
||||
</bit-form-field>
|
||||
|
||||
<app-input-text
|
||||
[label]="'authority' | i18n"
|
||||
controlId="authority"
|
||||
[stripSpaces]="true"
|
||||
formControlName="authority"
|
||||
></app-input-text>
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "authority" | i18n }}</bit-label>
|
||||
<input bitInput type="text" formControlName="authority" appInputStripSpaces />
|
||||
</bit-form-field>
|
||||
|
||||
<app-input-text
|
||||
[label]="'clientId' | i18n"
|
||||
controlId="clientId"
|
||||
[stripSpaces]="true"
|
||||
formControlName="clientId"
|
||||
></app-input-text>
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "clientId" | i18n }}</bit-label>
|
||||
<input bitInput type="text" formControlName="clientId" appInputStripSpaces />
|
||||
</bit-form-field>
|
||||
|
||||
<app-input-text
|
||||
[label]="'clientSecret' | i18n"
|
||||
controlId="clientSecret"
|
||||
[stripSpaces]="true"
|
||||
formControlName="clientSecret"
|
||||
></app-input-text>
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "clientSecret" | i18n }}</bit-label>
|
||||
<input bitInput type="text" formControlName="clientSecret" appInputStripSpaces />
|
||||
</bit-form-field>
|
||||
|
||||
<app-input-text
|
||||
[label]="'metadataAddress' | i18n"
|
||||
controlId="metadataAddress"
|
||||
[stripSpaces]="true"
|
||||
[helperText]="'openIdAuthorityRequired' | i18n"
|
||||
formControlName="metadataAddress"
|
||||
></app-input-text>
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "metadataAddress" | i18n }}</bit-label>
|
||||
<input bitInput type="text" formControlName="metadataAddress" appInputStripSpaces />
|
||||
<bit-hint>{{ "openIdAuthorityRequired" | i18n }}</bit-hint>
|
||||
</bit-form-field>
|
||||
|
||||
<app-select
|
||||
controlId="redirectBehavior"
|
||||
[label]="'oidcRedirectBehavior' | i18n"
|
||||
[selectOptions]="connectRedirectOptions"
|
||||
formControlName="redirectBehavior"
|
||||
>
|
||||
</app-select>
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "oidcRedirectBehavior" | i18n }}</bit-label>
|
||||
<select bitInput formControlName="redirectBehavior">
|
||||
<option
|
||||
*ngFor="let o of connectRedirectOptions"
|
||||
[ngValue]="o.value"
|
||||
disabled="{{ o.disabled }}"
|
||||
>
|
||||
{{ o.name }}
|
||||
</option>
|
||||
</select>
|
||||
</bit-form-field>
|
||||
|
||||
<app-input-checkbox
|
||||
controlId="getClaimsFromUserInfoEndpoint"
|
||||
@@ -231,47 +243,41 @@
|
||||
</button>
|
||||
</div>
|
||||
<div id="customizations" [hidden]="!showOpenIdCustomizations">
|
||||
<app-input-text
|
||||
[label]="'additionalScopes' | i18n"
|
||||
controlId="additionalScopes"
|
||||
[helperText]="'separateMultipleWithComma' | i18n"
|
||||
formControlName="additionalScopes"
|
||||
></app-input-text>
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "additionalScopes" | i18n }}</bit-label>
|
||||
<input bitInput type="text" formControlName="additionalScopes" />
|
||||
<bit-hint>{{ "separateMultipleWithComma" | i18n }}</bit-hint>
|
||||
</bit-form-field>
|
||||
|
||||
<app-input-text
|
||||
[label]="'additionalUserIdClaimTypes' | i18n"
|
||||
controlId="additionalUserIdClaimTypes"
|
||||
[helperText]="'separateMultipleWithComma' | i18n"
|
||||
formControlName="additionalUserIdClaimTypes"
|
||||
></app-input-text>
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "additionalUserIdClaimTypes" | i18n }}</bit-label>
|
||||
<input bitInput type="text" formControlName="additionalUserIdClaimTypes" />
|
||||
<bit-hint>{{ "separateMultipleWithComma" | i18n }}</bit-hint>
|
||||
</bit-form-field>
|
||||
|
||||
<app-input-text
|
||||
[label]="'additionalEmailClaimTypes' | i18n"
|
||||
controlId="additionalEmailClaimTypes"
|
||||
[helperText]="'separateMultipleWithComma' | i18n"
|
||||
formControlName="additionalEmailClaimTypes"
|
||||
></app-input-text>
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "additionalEmailClaimTypes" | i18n }}</bit-label>
|
||||
<input bitInput type="text" formControlName="additionalEmailClaimTypes" />
|
||||
<bit-hint>{{ "separateMultipleWithComma" | i18n }}</bit-hint>
|
||||
</bit-form-field>
|
||||
|
||||
<app-input-text
|
||||
[label]="'additionalNameClaimTypes' | i18n"
|
||||
controlId="additionalNameClaimTypes"
|
||||
[helperText]="'separateMultipleWithComma' | i18n"
|
||||
formControlName="additionalNameClaimTypes"
|
||||
></app-input-text>
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "additionalNameClaimTypes" | i18n }}</bit-label>
|
||||
<input bitInput type="text" formControlName="additionalNameClaimTypes" />
|
||||
<bit-hint>{{ "separateMultipleWithComma" | i18n }}</bit-hint>
|
||||
</bit-form-field>
|
||||
|
||||
<app-input-text
|
||||
[label]="'acrValues' | i18n"
|
||||
controlId="acrValues"
|
||||
helperText="acr_values"
|
||||
formControlName="acrValues"
|
||||
></app-input-text>
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "acrValues" | i18n }}</bit-label>
|
||||
<input bitInput type="text" formControlName="acrValues" />
|
||||
<bit-hint>acr_values</bit-hint>
|
||||
</bit-form-field>
|
||||
|
||||
<app-input-text
|
||||
[label]="'expectedReturnAcrValue' | i18n"
|
||||
controlId="expectedReturnAcrValue"
|
||||
helperText="acr_validation"
|
||||
formControlName="expectedReturnAcrValue"
|
||||
></app-input-text>
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "expectedReturnAcrValue" | i18n }}</bit-label>
|
||||
<input bitInput type="text" formControlName="expectedReturnAcrValue" />
|
||||
<bit-hint>acr_validaton</bit-hint>
|
||||
</bit-form-field>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -282,53 +288,108 @@
|
||||
<div class="config-section">
|
||||
<h2 class="secondary-header">{{ "samlSpConfig" | i18n }}</h2>
|
||||
|
||||
<app-input-text-readonly
|
||||
[label]="'spEntityId' | i18n"
|
||||
[controlValue]="spEntityId"
|
||||
></app-input-text-readonly>
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "spEntityId" | i18n }}</bit-label>
|
||||
<input bitInput disabled [value]="spEntityId" />
|
||||
<button
|
||||
bitButton
|
||||
bitSuffix
|
||||
type="button"
|
||||
[appCopyClick]="spEntityId"
|
||||
[appA11yTitle]="'copyValue' | i18n"
|
||||
>
|
||||
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
|
||||
</button>
|
||||
</bit-form-field>
|
||||
|
||||
<app-input-text-readonly
|
||||
[label]="'spMetadataUrl' | i18n"
|
||||
[controlValue]="spMetadataUrl"
|
||||
[showLaunch]="true"
|
||||
></app-input-text-readonly>
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "spMetadataUrl" | i18n }}</bit-label>
|
||||
<input bitInput disabled [value]="spMetadataUrl" />
|
||||
<button
|
||||
bitButton
|
||||
bitSuffix
|
||||
type="button"
|
||||
[appLaunchClick]="spMetadataUrl"
|
||||
[appA11yTitle]="'launch' | i18n"
|
||||
>
|
||||
<i class="bwi bwi-lg bwi-external-link" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button
|
||||
bitButton
|
||||
bitSuffix
|
||||
type="button"
|
||||
[appCopyClick]="spMetadataUrl"
|
||||
[appA11yTitle]="'copyValue' | i18n"
|
||||
>
|
||||
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
|
||||
</button>
|
||||
</bit-form-field>
|
||||
|
||||
<app-input-text-readonly
|
||||
[label]="'spAcsUrl' | i18n"
|
||||
[controlValue]="spAcsUrl"
|
||||
></app-input-text-readonly>
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "spAcsUrl" | i18n }}</bit-label>
|
||||
<input bitInput disabled [value]="spAcsUrl" />
|
||||
<button
|
||||
bitButton
|
||||
bitSuffix
|
||||
type="button"
|
||||
[appCopyClick]="spAcsUrl"
|
||||
[appA11yTitle]="'copyValue' | i18n"
|
||||
>
|
||||
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
|
||||
</button>
|
||||
</bit-form-field>
|
||||
|
||||
<app-select
|
||||
controlId="spNameIdFormat"
|
||||
[label]="'spNameIdFormat' | i18n"
|
||||
[selectOptions]="saml2NameIdFormatOptions"
|
||||
formControlName="spNameIdFormat"
|
||||
>
|
||||
</app-select>
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "spNameIdFormat" | i18n }}</bit-label>
|
||||
<select bitInput formControlName="spNameIdFormat">
|
||||
<option
|
||||
*ngFor="let o of saml2NameIdFormatOptions"
|
||||
[ngValue]="o.value"
|
||||
disabled="{{ o.disabled }}"
|
||||
>
|
||||
{{ o.name }}
|
||||
</option>
|
||||
</select>
|
||||
</bit-form-field>
|
||||
|
||||
<app-select
|
||||
controlId="spOutboundSigningAlgorithm"
|
||||
[label]="'spOutboundSigningAlgorithm' | i18n"
|
||||
[selectOptions]="samlSigningAlgorithmOptions"
|
||||
formControlName="spOutboundSigningAlgorithm"
|
||||
>
|
||||
</app-select>
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "spOutboundSigningAlgorithm" | i18n }}</bit-label>
|
||||
<select bitInput formControlName="spOutboundSigningAlgorithm">
|
||||
<option
|
||||
*ngFor="let o of samlSigningAlgorithmOptions"
|
||||
[ngValue]="o.value"
|
||||
disabled="{{ o.disabled }}"
|
||||
>
|
||||
{{ o.name }}
|
||||
</option>
|
||||
</select>
|
||||
</bit-form-field>
|
||||
|
||||
<app-select
|
||||
controlId="spSigningBehavior"
|
||||
[label]="'spSigningBehavior' | i18n"
|
||||
[selectOptions]="saml2SigningBehaviourOptions"
|
||||
formControlName="spSigningBehavior"
|
||||
>
|
||||
</app-select>
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "spSigningBehavior" | i18n }}</bit-label>
|
||||
<select bitInput formControlName="spSigningBehavior">
|
||||
<option
|
||||
*ngFor="let o of saml2SigningBehaviourOptions"
|
||||
[ngValue]="o.value"
|
||||
disabled="{{ o.disabled }}"
|
||||
>
|
||||
{{ o.name }}
|
||||
</option>
|
||||
</select>
|
||||
</bit-form-field>
|
||||
|
||||
<app-select
|
||||
controlId="spMinIncomingSigningAlgorithm"
|
||||
[label]="'spMinIncomingSigningAlgorithm' | i18n"
|
||||
[selectOptions]="samlSigningAlgorithmOptions"
|
||||
formControlName="spMinIncomingSigningAlgorithm"
|
||||
>
|
||||
</app-select>
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "spMinIncomingSigningAlgorithm" | i18n }}</bit-label>
|
||||
<select bitInput formControlName="spMinIncomingSigningAlgorithm">
|
||||
<option
|
||||
*ngFor="let o of samlSigningAlgorithmOptions"
|
||||
[ngValue]="o.value"
|
||||
disabled="{{ o.disabled }}"
|
||||
>
|
||||
{{ o.name }}
|
||||
</option>
|
||||
</select>
|
||||
</bit-form-field>
|
||||
|
||||
<app-input-checkbox
|
||||
controlId="spWantAssertionsSigned"
|
||||
@@ -347,67 +408,62 @@
|
||||
<div class="config-section">
|
||||
<h2 class="secondary-header">{{ "samlIdpConfig" | i18n }}</h2>
|
||||
|
||||
<app-input-text
|
||||
[label]="'idpEntityId' | i18n"
|
||||
controlId="idpEntityId"
|
||||
formControlName="idpEntityId"
|
||||
></app-input-text>
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "idpEntityId" | i18n }}</bit-label>
|
||||
<input bitInput type="text" formControlName="idpEntityId" />
|
||||
</bit-form-field>
|
||||
|
||||
<app-select
|
||||
controlId="idpBindingType"
|
||||
[label]="'idpBindingType' | i18n"
|
||||
[selectOptions]="saml2BindingTypeOptions"
|
||||
formControlName="idpBindingType"
|
||||
>
|
||||
</app-select>
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "idpBindingType" | i18n }}</bit-label>
|
||||
<select bitInput formControlName="idpBindingType">
|
||||
<option
|
||||
*ngFor="let o of saml2BindingTypeOptions"
|
||||
[ngValue]="o.value"
|
||||
disabled="{{ o.disabled }}"
|
||||
>
|
||||
{{ o.name }}
|
||||
</option>
|
||||
</select>
|
||||
</bit-form-field>
|
||||
|
||||
<app-input-text
|
||||
[label]="'idpSingleSignOnServiceUrl' | i18n"
|
||||
controlId="idpSingleSignOnServiceUrl"
|
||||
[helperText]="'idpSingleSignOnServiceUrlRequired' | i18n"
|
||||
[stripSpaces]="true"
|
||||
formControlName="idpSingleSignOnServiceUrl"
|
||||
></app-input-text>
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "idpSingleSignOnServiceUrl" | i18n }}</bit-label>
|
||||
<input
|
||||
bitInput
|
||||
type="text"
|
||||
formControlName="idpSingleSignOnServiceUrl"
|
||||
appInputStripSpaces
|
||||
/>
|
||||
<bit-hint>{{ "idpSingleSignOnServiceUrlRequired" | i18n }}</bit-hint>
|
||||
</bit-form-field>
|
||||
|
||||
<app-input-text
|
||||
[label]="'idpSingleLogoutServiceUrl' | i18n"
|
||||
controlId="idpSingleLogoutServiceUrl"
|
||||
[stripSpaces]="true"
|
||||
formControlName="idpSingleLogoutServiceUrl"
|
||||
></app-input-text>
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "idpSingleLogoutServiceUrl" | i18n }}</bit-label>
|
||||
<input
|
||||
bitInput
|
||||
type="text"
|
||||
formControlName="idpSingleLogoutServiceUrl"
|
||||
appInputStripSpaces
|
||||
/>
|
||||
</bit-form-field>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="idpX509PublicCert">
|
||||
{{ "idpX509PublicCert" | i18n }}
|
||||
<small class="text-muted form-text d-inline">({{ "required" | i18n }})</small>
|
||||
</label>
|
||||
<textarea
|
||||
formControlName="idpX509PublicCert"
|
||||
class="form-control form-control-sm text-monospace"
|
||||
rows="6"
|
||||
id="idpX509PublicCert"
|
||||
appA11yInvalid
|
||||
aria-describedby="idpX509PublicCertDesc"
|
||||
></textarea>
|
||||
<small
|
||||
id="idpX509PublicCertDesc"
|
||||
class="error-inline"
|
||||
role="alert"
|
||||
*ngIf="samlForm.get('idpX509PublicCert').hasError('required')"
|
||||
>
|
||||
<i class="bwi bwi-exclamation-circle" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{ "error" | i18n }}:</span>
|
||||
{{ "fieldRequiredError" | i18n: ("idpX509PublicCert" | i18n) }}
|
||||
</small>
|
||||
</div>
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "idpX509PublicCert" | i18n }}</bit-label>
|
||||
<textarea bitInput rows="6" formControlName="idpX509PublicCert"></textarea>
|
||||
</bit-form-field>
|
||||
|
||||
<app-select
|
||||
controlId="idpOutboundSigningAlgorithm"
|
||||
[label]="'idpOutboundSigningAlgorithm' | i18n"
|
||||
[selectOptions]="samlSigningAlgorithmOptions"
|
||||
formControlName="idpOutboundSigningAlgorithm"
|
||||
>
|
||||
</app-select>
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "idpOutboundSigningAlgorithm" | i18n }}</bit-label>
|
||||
<select bitInput formControlName="idpOutboundSigningAlgorithm">
|
||||
<option
|
||||
*ngFor="let o of samlSigningAlgorithmOptions"
|
||||
[ngValue]="o.value"
|
||||
disabled="{{ o.disabled }}"
|
||||
>
|
||||
{{ o.name }}
|
||||
</option>
|
||||
</select>
|
||||
</bit-form-field>
|
||||
|
||||
<!--TODO: Uncomment once Unsolicited IdP Response is supported-->
|
||||
<!-- <app-input-checkbox
|
||||
@@ -430,19 +486,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||
<span>{{ "save" | i18n }}</span>
|
||||
</button>
|
||||
<div
|
||||
id="errorSummary"
|
||||
class="error-summary text-danger"
|
||||
*ngIf="this.getErrorCount(ssoConfigForm) as errorCount"
|
||||
>
|
||||
<i class="bwi bwi-exclamation-circle" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{ "error" | i18n }}:</span>
|
||||
{{
|
||||
(errorCount === 1 ? "formErrorSummarySingle" : "formErrorSummaryPlural") | i18n: errorCount
|
||||
}}
|
||||
</div>
|
||||
<bit-submit-button buttonType="primary" [loading]="form.loading">
|
||||
{{ "save" | i18n }}
|
||||
</bit-submit-button>
|
||||
<bit-error-summary [formGroup]="ssoConfigForm"></bit-error-summary>
|
||||
</form>
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { AbstractControl, UntypedFormBuilder, UntypedFormGroup } from "@angular/forms";
|
||||
import { Component, OnDestroy, OnInit } from "@angular/core";
|
||||
import {
|
||||
AbstractControl,
|
||||
FormBuilder,
|
||||
FormControl,
|
||||
UntypedFormGroup,
|
||||
Validators,
|
||||
} from "@angular/forms";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
import { concatMap, Subject, takeUntil } from "rxjs";
|
||||
|
||||
import { SelectOptions } from "@bitwarden/angular/interfaces/selectOptions";
|
||||
import { dirtyRequired } from "@bitwarden/angular/validators/dirty.validator";
|
||||
import { ControlsOf } from "@bitwarden/angular/types/controls-of";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { OrganizationService } from "@bitwarden/common/abstractions/organization.service";
|
||||
@@ -29,8 +36,7 @@ const defaultSigningAlgorithm = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha2
|
||||
selector: "app-org-manage-sso",
|
||||
templateUrl: "sso.component.html",
|
||||
})
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
||||
export class SsoComponent implements OnInit {
|
||||
export class SsoComponent implements OnInit, OnDestroy {
|
||||
readonly ssoType = SsoType;
|
||||
|
||||
readonly ssoTypeOptions: SelectOptions[] = [
|
||||
@@ -75,6 +81,8 @@ export class SsoComponent implements OnInit {
|
||||
{ name: "Form POST", value: OpenIdConnectRedirectBehavior.FormPost },
|
||||
];
|
||||
|
||||
private destory$ = new Subject<void>();
|
||||
|
||||
showOpenIdCustomizations = false;
|
||||
|
||||
loading = true;
|
||||
@@ -89,62 +97,69 @@ export class SsoComponent implements OnInit {
|
||||
spMetadataUrl: string;
|
||||
spAcsUrl: string;
|
||||
|
||||
enabled = this.formBuilder.control(false);
|
||||
private enabled = this.formBuilder.control(false);
|
||||
|
||||
openIdForm = this.formBuilder.group(
|
||||
private ssoIdentifier = this.formBuilder.control("", {
|
||||
validators: [Validators.maxLength(50), Validators.required],
|
||||
});
|
||||
|
||||
private openIdForm = this.formBuilder.group<ControlsOf<SsoConfigView["openId"]>>(
|
||||
{
|
||||
authority: ["", dirtyRequired],
|
||||
clientId: ["", dirtyRequired],
|
||||
clientSecret: ["", dirtyRequired],
|
||||
metadataAddress: [],
|
||||
redirectBehavior: [OpenIdConnectRedirectBehavior.RedirectGet, dirtyRequired],
|
||||
getClaimsFromUserInfoEndpoint: [],
|
||||
additionalScopes: [],
|
||||
additionalUserIdClaimTypes: [],
|
||||
additionalEmailClaimTypes: [],
|
||||
additionalNameClaimTypes: [],
|
||||
acrValues: [],
|
||||
expectedReturnAcrValue: [],
|
||||
authority: new FormControl("", Validators.required),
|
||||
clientId: new FormControl("", Validators.required),
|
||||
clientSecret: new FormControl("", Validators.required),
|
||||
metadataAddress: new FormControl(),
|
||||
redirectBehavior: new FormControl(
|
||||
OpenIdConnectRedirectBehavior.RedirectGet,
|
||||
Validators.required
|
||||
),
|
||||
getClaimsFromUserInfoEndpoint: new FormControl(),
|
||||
additionalScopes: new FormControl(),
|
||||
additionalUserIdClaimTypes: new FormControl(),
|
||||
additionalEmailClaimTypes: new FormControl(),
|
||||
additionalNameClaimTypes: new FormControl(),
|
||||
acrValues: new FormControl(),
|
||||
expectedReturnAcrValue: new FormControl(),
|
||||
},
|
||||
{
|
||||
updateOn: "blur",
|
||||
}
|
||||
);
|
||||
|
||||
samlForm = this.formBuilder.group(
|
||||
private samlForm = this.formBuilder.group<ControlsOf<SsoConfigView["saml"]>>(
|
||||
{
|
||||
spNameIdFormat: [Saml2NameIdFormat.NotConfigured],
|
||||
spOutboundSigningAlgorithm: [defaultSigningAlgorithm],
|
||||
spSigningBehavior: [Saml2SigningBehavior.IfIdpWantAuthnRequestsSigned],
|
||||
spMinIncomingSigningAlgorithm: [defaultSigningAlgorithm],
|
||||
spWantAssertionsSigned: [],
|
||||
spValidateCertificates: [],
|
||||
spNameIdFormat: new FormControl(Saml2NameIdFormat.NotConfigured),
|
||||
spOutboundSigningAlgorithm: new FormControl(defaultSigningAlgorithm),
|
||||
spSigningBehavior: new FormControl(Saml2SigningBehavior.IfIdpWantAuthnRequestsSigned),
|
||||
spMinIncomingSigningAlgorithm: new FormControl(defaultSigningAlgorithm),
|
||||
spWantAssertionsSigned: new FormControl(),
|
||||
spValidateCertificates: new FormControl(),
|
||||
|
||||
idpEntityId: ["", dirtyRequired],
|
||||
idpBindingType: [Saml2BindingType.HttpRedirect],
|
||||
idpSingleSignOnServiceUrl: [],
|
||||
idpSingleLogoutServiceUrl: [],
|
||||
idpX509PublicCert: ["", dirtyRequired],
|
||||
idpOutboundSigningAlgorithm: [defaultSigningAlgorithm],
|
||||
idpAllowUnsolicitedAuthnResponse: [],
|
||||
idpAllowOutboundLogoutRequests: [true],
|
||||
idpWantAuthnRequestsSigned: [],
|
||||
idpEntityId: new FormControl("", Validators.required),
|
||||
idpBindingType: new FormControl(Saml2BindingType.HttpRedirect),
|
||||
idpSingleSignOnServiceUrl: new FormControl(),
|
||||
idpSingleLogoutServiceUrl: new FormControl(),
|
||||
idpX509PublicCert: new FormControl("", Validators.required),
|
||||
idpOutboundSigningAlgorithm: new FormControl(defaultSigningAlgorithm),
|
||||
idpAllowUnsolicitedAuthnResponse: new FormControl(),
|
||||
idpAllowOutboundLogoutRequests: new FormControl(true),
|
||||
idpWantAuthnRequestsSigned: new FormControl(),
|
||||
},
|
||||
{
|
||||
updateOn: "blur",
|
||||
}
|
||||
);
|
||||
|
||||
ssoConfigForm = this.formBuilder.group({
|
||||
configType: [SsoType.None],
|
||||
keyConnectorEnabled: [false],
|
||||
keyConnectorUrl: [""],
|
||||
private ssoConfigForm = this.formBuilder.group<ControlsOf<SsoConfigView>>({
|
||||
configType: new FormControl(SsoType.None),
|
||||
keyConnectorEnabled: new FormControl(false),
|
||||
keyConnectorUrl: new FormControl(""),
|
||||
openId: this.openIdForm,
|
||||
saml: this.samlForm,
|
||||
});
|
||||
|
||||
constructor(
|
||||
private formBuilder: UntypedFormBuilder,
|
||||
private formBuilder: FormBuilder,
|
||||
private route: ActivatedRoute,
|
||||
private apiService: ApiService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
@@ -154,32 +169,41 @@ export class SsoComponent implements OnInit {
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
||||
this.ssoConfigForm.get("configType").valueChanges.subscribe((newType: SsoType) => {
|
||||
if (newType === SsoType.OpenIdConnect) {
|
||||
this.openIdForm.enable();
|
||||
this.samlForm.disable();
|
||||
} else if (newType === SsoType.Saml2) {
|
||||
this.openIdForm.disable();
|
||||
this.samlForm.enable();
|
||||
} else {
|
||||
this.openIdForm.disable();
|
||||
this.samlForm.disable();
|
||||
}
|
||||
});
|
||||
this.ssoConfigForm
|
||||
.get("configType")
|
||||
.valueChanges.pipe(takeUntil(this.destory$))
|
||||
.subscribe((newType: SsoType) => {
|
||||
if (newType === SsoType.OpenIdConnect) {
|
||||
this.openIdForm.enable();
|
||||
this.samlForm.disable();
|
||||
} else if (newType === SsoType.Saml2) {
|
||||
this.openIdForm.disable();
|
||||
this.samlForm.enable();
|
||||
} else {
|
||||
this.openIdForm.disable();
|
||||
this.samlForm.disable();
|
||||
}
|
||||
});
|
||||
|
||||
this.samlForm
|
||||
.get("spSigningBehavior")
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
||||
.valueChanges.subscribe(() =>
|
||||
this.samlForm.get("idpX509PublicCert").updateValueAndValidity()
|
||||
);
|
||||
.valueChanges.pipe(takeUntil(this.destory$))
|
||||
.subscribe(() => this.samlForm.get("idpX509PublicCert").updateValueAndValidity());
|
||||
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
|
||||
this.route.parent.parent.params.subscribe(async (params) => {
|
||||
this.organizationId = params.organizationId;
|
||||
await this.load();
|
||||
});
|
||||
this.route.params
|
||||
.pipe(
|
||||
concatMap(async (params) => {
|
||||
this.organizationId = params.organizationId;
|
||||
await this.load();
|
||||
}),
|
||||
takeUntil(this.destory$)
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.destory$.next();
|
||||
this.destory$.complete();
|
||||
}
|
||||
|
||||
async load() {
|
||||
@@ -199,7 +223,8 @@ export class SsoComponent implements OnInit {
|
||||
async submit() {
|
||||
this.validateForm(this.ssoConfigForm);
|
||||
|
||||
if (this.ssoConfigForm.get("keyConnectorEnabled").value) {
|
||||
if (this.ssoConfigForm.value.keyConnectorEnabled) {
|
||||
this.haveTestedKeyConnector = false;
|
||||
await this.validateKeyConnectorUrl();
|
||||
}
|
||||
|
||||
@@ -210,7 +235,8 @@ export class SsoComponent implements OnInit {
|
||||
|
||||
const request = new OrganizationSsoRequest();
|
||||
request.enabled = this.enabled.value;
|
||||
request.data = SsoConfigApi.fromView(this.ssoConfigForm.value as SsoConfigView);
|
||||
request.identifier = this.ssoIdentifier.value;
|
||||
request.data = SsoConfigApi.fromView(this.ssoConfigForm.getRawValue());
|
||||
|
||||
this.formPromise = this.organizationApiService.updateSso(this.organizationId, request);
|
||||
|
||||
@@ -237,7 +263,7 @@ export class SsoComponent implements OnInit {
|
||||
this.keyConnectorUrl.updateValueAndValidity();
|
||||
} catch {
|
||||
this.keyConnectorUrl.setErrors({
|
||||
invalidUrl: true,
|
||||
invalidUrl: { message: this.i18nService.t("keyConnectorTestFail") },
|
||||
});
|
||||
}
|
||||
|
||||
@@ -294,6 +320,7 @@ export class SsoComponent implements OnInit {
|
||||
|
||||
private populateForm(ssoSettings: OrganizationSsoResponse) {
|
||||
this.enabled.setValue(ssoSettings.enabled);
|
||||
this.ssoIdentifier.setValue(ssoSettings.identifier);
|
||||
if (ssoSettings.data != null) {
|
||||
const ssoConfigView = new SsoConfigView(ssoSettings.data);
|
||||
this.ssoConfigForm.patchValue(ssoConfigView);
|
||||
|
||||
@@ -6,7 +6,8 @@ import { Organization } from "@bitwarden/common/models/domain/organization";
|
||||
|
||||
import { OrganizationPermissionsGuard } from "src/app/organizations/guards/org-permissions.guard";
|
||||
import { OrganizationLayoutComponent } from "src/app/organizations/layouts/organization-layout.component";
|
||||
import { ManageComponent } from "src/app/organizations/manage/manage.component";
|
||||
import { canAccessSettingsTab } from "src/app/organizations/navigation-permissions";
|
||||
import { SettingsComponent } from "src/app/organizations/settings/settings.component";
|
||||
|
||||
import { ScimComponent } from "./manage/scim.component";
|
||||
import { SsoComponent } from "./manage/sso.component";
|
||||
@@ -18,8 +19,12 @@ const routes: Routes = [
|
||||
canActivate: [AuthGuard, OrganizationPermissionsGuard],
|
||||
children: [
|
||||
{
|
||||
path: "manage",
|
||||
component: ManageComponent,
|
||||
path: "settings",
|
||||
component: SettingsComponent,
|
||||
canActivate: [OrganizationPermissionsGuard],
|
||||
data: {
|
||||
organizationPermissions: canAccessSettingsTab,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "sso",
|
||||
|
||||
@@ -3,24 +3,12 @@ import { NgModule } from "@angular/core";
|
||||
import { SharedModule } from "src/app/shared/shared.module";
|
||||
|
||||
import { InputCheckboxComponent } from "./components/input-checkbox.component";
|
||||
import { InputTextReadOnlyComponent } from "./components/input-text-readonly.component";
|
||||
import { InputTextComponent } from "./components/input-text.component";
|
||||
import { SelectComponent } from "./components/select.component";
|
||||
import { ScimComponent } from "./manage/scim.component";
|
||||
import { SsoComponent } from "./manage/sso.component";
|
||||
import { OrganizationsRoutingModule } from "./organizations-routing.module";
|
||||
|
||||
// Form components are for use in the SSO Configuration Form only and should not be exported for use elsewhere.
|
||||
// They will be deprecated by the Component Library.
|
||||
@NgModule({
|
||||
imports: [SharedModule, OrganizationsRoutingModule],
|
||||
declarations: [
|
||||
InputCheckboxComponent,
|
||||
InputTextComponent,
|
||||
InputTextReadOnlyComponent,
|
||||
SelectComponent,
|
||||
SsoComponent,
|
||||
ScimComponent,
|
||||
],
|
||||
declarations: [InputCheckboxComponent, SsoComponent, ScimComponent],
|
||||
})
|
||||
export class OrganizationsModule {}
|
||||
|
||||
@@ -2,10 +2,7 @@ import { Component } from "@angular/core";
|
||||
|
||||
import { PolicyType } from "@bitwarden/common/enums/policyType";
|
||||
|
||||
import {
|
||||
BasePolicy,
|
||||
BasePolicyComponent,
|
||||
} from "src/app/organizations/policies/base-policy.component";
|
||||
import { BasePolicy, BasePolicyComponent } from "src/app/organizations/policies";
|
||||
|
||||
export class DisablePersonalVaultExportPolicy extends BasePolicy {
|
||||
name = "disablePersonalVaultExport";
|
||||
|
||||
@@ -5,10 +5,7 @@ import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { PolicyType } from "@bitwarden/common/enums/policyType";
|
||||
import { PolicyRequest } from "@bitwarden/common/models/request/policyRequest";
|
||||
|
||||
import {
|
||||
BasePolicy,
|
||||
BasePolicyComponent,
|
||||
} from "src/app/organizations/policies/base-policy.component";
|
||||
import { BasePolicy, BasePolicyComponent } from "src/app/organizations/policies";
|
||||
|
||||
export class MaximumVaultTimeoutPolicy extends BasePolicy {
|
||||
name = "maximumVaultTimeout";
|
||||
|
||||
16
libs/angular/src/directives/copy-click.directive.ts
Normal file
16
libs/angular/src/directives/copy-click.directive.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { Directive, HostListener, Input } from "@angular/core";
|
||||
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
|
||||
@Directive({
|
||||
selector: "[appCopyClick]",
|
||||
})
|
||||
export class CopyClickDirective {
|
||||
constructor(private platformUtilsService: PlatformUtilsService) {}
|
||||
|
||||
@Input("appCopyClick") valueToCopy = "";
|
||||
|
||||
@HostListener("click") onClick() {
|
||||
this.platformUtilsService.copyToClipboard(this.valueToCopy);
|
||||
}
|
||||
}
|
||||
19
libs/angular/src/directives/launch-click.directive.ts
Normal file
19
libs/angular/src/directives/launch-click.directive.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { Directive, HostListener, Input } from "@angular/core";
|
||||
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
import { Utils } from "@bitwarden/common/misc/utils";
|
||||
|
||||
@Directive({
|
||||
selector: "[appLaunchClick]",
|
||||
})
|
||||
export class LaunchClickDirective {
|
||||
constructor(private platformUtilsService: PlatformUtilsService) {}
|
||||
|
||||
@Input("appLaunchClick") uriToLaunch = "";
|
||||
|
||||
@HostListener("click") onClick() {
|
||||
if (!Utils.isNullOrWhitespace(this.uriToLaunch)) {
|
||||
this.platformUtilsService.launchUri(this.uriToLaunch);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,9 +12,11 @@ import { A11yTitleDirective } from "./directives/a11y-title.directive";
|
||||
import { ApiActionDirective } from "./directives/api-action.directive";
|
||||
import { AutofocusDirective } from "./directives/autofocus.directive";
|
||||
import { BoxRowDirective } from "./directives/box-row.directive";
|
||||
import { CopyClickDirective } from "./directives/copy-click.directive";
|
||||
import { FallbackSrcDirective } from "./directives/fallback-src.directive";
|
||||
import { InputStripSpacesDirective } from "./directives/input-strip-spaces.directive";
|
||||
import { InputVerbatimDirective } from "./directives/input-verbatim.directive";
|
||||
import { LaunchClickDirective } from "./directives/launch-click.directive";
|
||||
import { NotPremiumDirective } from "./directives/not-premium.directive";
|
||||
import { SelectCopyDirective } from "./directives/select-copy.directive";
|
||||
import { StopClickDirective } from "./directives/stop-click.directive";
|
||||
@@ -66,6 +68,8 @@ import { PasswordStrengthComponent } from "./shared/components/password-strength
|
||||
StopClickDirective,
|
||||
StopPropDirective,
|
||||
TrueFalseValueDirective,
|
||||
CopyClickDirective,
|
||||
LaunchClickDirective,
|
||||
UserNamePipe,
|
||||
PasswordStrengthComponent,
|
||||
],
|
||||
@@ -95,6 +99,8 @@ import { PasswordStrengthComponent } from "./shared/components/password-strength
|
||||
StopClickDirective,
|
||||
StopPropDirective,
|
||||
TrueFalseValueDirective,
|
||||
CopyClickDirective,
|
||||
LaunchClickDirective,
|
||||
UserNamePipe,
|
||||
PasswordStrengthComponent,
|
||||
],
|
||||
|
||||
20
libs/angular/src/types/controls-of.ts
Normal file
20
libs/angular/src/types/controls-of.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { FormControl, FormGroup } from "@angular/forms";
|
||||
|
||||
/**
|
||||
* Helper type to map a model to a strictly typed from group. Allows specifying a formGroup's
|
||||
* type explicity using an existing model/interface instead of a type being inferred
|
||||
* indirectly by control structure.
|
||||
* Source: https://netbasal.com/typed-reactive-forms-in-angular-no-longer-a-type-dream-bf6982b0af28
|
||||
* @example
|
||||
* interface UserData {
|
||||
* name: string;
|
||||
* age: number;
|
||||
* }
|
||||
* const strictlyTypedForm = this.formBuilder.group<Controls<UserData>>({
|
||||
* name: new FormControl("John"),
|
||||
* age: new FormControl(24),
|
||||
* });
|
||||
*/
|
||||
export type ControlsOf<T extends Record<string, any>> = {
|
||||
[K in keyof T]: T[K] extends Record<any, any> ? FormGroup<ControlsOf<T[K]>> : FormControl<T[K]>;
|
||||
};
|
||||
@@ -1,9 +0,0 @@
|
||||
import { AbstractControl, ValidationErrors, Validators } from "@angular/forms";
|
||||
|
||||
/**
|
||||
* Runs Validators.required on a field only if it's dirty. This prevents error messages from being displayed
|
||||
* to the user prematurely.
|
||||
*/
|
||||
export function dirtyRequired(control: AbstractControl): ValidationErrors | null {
|
||||
return control.dirty ? Validators.required(control) : null;
|
||||
}
|
||||
@@ -74,7 +74,7 @@ export class SsoConfigApi extends BaseResponse {
|
||||
spNameIdFormat: Saml2NameIdFormat;
|
||||
spOutboundSigningAlgorithm: string;
|
||||
spSigningBehavior: Saml2SigningBehavior;
|
||||
spMinIncomingSigningAlgorithm: boolean;
|
||||
spMinIncomingSigningAlgorithm: string;
|
||||
spWantAssertionsSigned: boolean;
|
||||
spValidateCertificates: boolean;
|
||||
|
||||
|
||||
@@ -2,5 +2,6 @@ import { SsoConfigApi } from "../../api/ssoConfigApi";
|
||||
|
||||
export class OrganizationSsoRequest {
|
||||
enabled = false;
|
||||
identifier: string;
|
||||
data: SsoConfigApi;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,10 @@ import { OrganizationKeysRequest } from "./organizationKeysRequest";
|
||||
|
||||
export class OrganizationUpdateRequest {
|
||||
name: string;
|
||||
/**
|
||||
* @deprecated 2022-08-03 Moved to OrganizationSsoRequest, left for backwards compatability.
|
||||
* https://bitwarden.atlassian.net/browse/EC-489
|
||||
*/
|
||||
identifier: string;
|
||||
businessName: string;
|
||||
billingEmail: string;
|
||||
|
||||
@@ -3,12 +3,14 @@ import { BaseResponse } from "../baseResponse";
|
||||
|
||||
export class OrganizationSsoResponse extends BaseResponse {
|
||||
enabled: boolean;
|
||||
identifier: string;
|
||||
data: SsoConfigApi;
|
||||
urls: SsoUrls;
|
||||
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
this.enabled = this.getResponseProperty("Enabled");
|
||||
this.identifier = this.getResponseProperty("Identifier");
|
||||
this.data =
|
||||
this.getResponseProperty("Data") != null
|
||||
? new SsoConfigApi(this.getResponseProperty("Data"))
|
||||
|
||||
@@ -34,7 +34,7 @@ export class SsoConfigView extends View {
|
||||
spNameIdFormat: Saml2NameIdFormat;
|
||||
spOutboundSigningAlgorithm: string;
|
||||
spSigningBehavior: Saml2SigningBehavior;
|
||||
spMinIncomingSigningAlgorithm: boolean;
|
||||
spMinIncomingSigningAlgorithm: string;
|
||||
spWantAssertionsSigned: boolean;
|
||||
spValidateCertificates: boolean;
|
||||
|
||||
|
||||
@@ -33,6 +33,10 @@ export class BitErrorSummary {
|
||||
return acc;
|
||||
}
|
||||
|
||||
if (!control.dirty && control.untouched) {
|
||||
return acc;
|
||||
}
|
||||
|
||||
return acc + Object.keys(control.errors).length;
|
||||
}, 0);
|
||||
}
|
||||
|
||||
@@ -28,6 +28,8 @@ export class BitErrorComponent {
|
||||
return this.i18nService.t("inputEmail");
|
||||
case "minlength":
|
||||
return this.i18nService.t("inputMinLength", this.error[1]?.requiredLength);
|
||||
case "maxlength":
|
||||
return this.i18nService.t("inputMaxLength", this.error[1]?.requiredLength);
|
||||
default:
|
||||
// Attempt to show a custom error message.
|
||||
if (this.error[1]?.message) {
|
||||
|
||||
Reference in New Issue
Block a user