From bc951985eda10bfa1500569085d81efed2abb92d Mon Sep 17 00:00:00 2001 From: Fabi <38692350+fgerschwiler@users.noreply.github.com> Date: Wed, 11 Aug 2021 08:36:32 +0200 Subject: [PATCH] feat: Lockout policy (#2121) * feat: lock users if lockout policy is set * feat: setup * feat: lock user on password failes * feat: render error * feat: lock user on command side * feat: auth_req tests * feat: lockout policy docs * feat: remove show lockout failures from proto * fix: console lockout * feat: tests * fix: tests * unlock function * add unlock button * fix migration version * lockout policy * lint * Update internal/auth/repository/eventsourcing/eventstore/auth_request.go Co-authored-by: Silvan * fix: err message * Update internal/command/setup_step4.go Co-authored-by: Silvan Co-authored-by: Max Peintner Co-authored-by: Livio Amstutz Co-authored-by: Silvan --- cmd/zitadel/main.go | 2 +- cmd/zitadel/setup.yaml | 8 +- console/package-lock.json | 34 ++ .../password-lockout-policy.component.html | 19 +- .../password-lockout-policy.component.scss | 4 +- .../password-lockout-policy.component.ts | 227 ++++---- .../password-lockout-policy.module.ts | 31 +- .../src/app/modules/policy-grid/policies.ts | 13 + .../users/user-detail/user-detail.module.ts | 2 + .../user-detail/user-detail.component.html | 6 + .../user-detail/user-detail.component.scss | 9 + .../user-detail/user-detail.component.ts | 499 +++++++++--------- console/src/app/services/admin.service.ts | 26 +- console/src/app/services/mgmt.service.ts | 60 ++- console/src/assets/i18n/de.json | 12 +- console/src/assets/i18n/en.json | 12 +- docs/docs/apis/proto/admin.md | 97 ++-- docs/docs/apis/proto/management.md | 246 +++++---- docs/docs/apis/proto/policy.md | 27 +- docs/docs/manuals/admin-policies.md | 10 + .../eventsourcing/eventstore/iam.go | 14 +- .../eventsourcing/handler/handler.go | 7 +- .../handler/password_lockout_policy.go | 58 +- .../repository/eventsourcing/repository.go | 5 +- .../eventsourcing/spooler/spooler.go | 5 +- .../eventsourcing/view/lockout_policy.go | 53 ++ .../view/password_lockout_policy.go | 53 -- internal/admin/repository/iam.go | 2 +- internal/api/grpc/admin/lockout.go | 31 ++ internal/api/grpc/admin/lockout_converter.go | 12 + internal/api/grpc/admin/password_lockout.go | 31 -- .../grpc/admin/password_lockout_converter.go | 13 - .../api/grpc/management/policy_lockout.go | 63 +++ .../management/policy_lockout_converter.go | 18 + .../management/policy_password_lockout.go | 63 --- .../policy_password_lockout_converter.go | 20 - .../grpc/policy/password_lockout_policy.go | 9 +- .../eventsourcing/eventstore/auth_request.go | 59 ++- .../eventstore/auth_request_test.go | 214 ++++++-- .../eventsourcing/handler/handler.go | 1 + .../eventsourcing/handler/lockout_policy.go | 110 ++++ .../repository/eventsourcing/repository.go | 1 + .../eventsourcing/view/lockout_policy.go | 53 ++ internal/command/iam_converter.go | 6 +- .../command/iam_policy_password_lockout.go | 32 +- .../iam_policy_password_lockout_model.go | 42 +- .../iam_policy_password_lockout_test.go | 64 +-- .../command/org_policy_password_lockout.go | 32 +- .../org_policy_password_lockout_model.go | 48 +- .../org_policy_password_lockout_test.go | 68 +-- .../command/policy_password_lockout_model.go | 18 +- internal/command/setup_step18.go | 35 ++ internal/command/setup_step4.go | 19 +- internal/command/user_human_password.go | 12 +- internal/command/user_human_password_model.go | 21 +- internal/command/user_human_password_test.go | 81 ++- internal/domain/auth_request.go | 1 + internal/domain/iam.go | 2 +- internal/domain/org.go | 2 +- internal/domain/policy_password_lockout.go | 5 +- internal/domain/step.go | 1 + internal/iam/model/iam.go | 2 +- internal/iam/model/password_lockout_policy.go | 4 +- .../iam/model/password_lockout_policy_view.go | 24 +- .../iam/repository/eventsourcing/model/iam.go | 14 +- .../eventsourcing/model/lockout_policy.go | 60 +++ ..._policy_test.go => lockout_policy_test.go} | 46 +- .../model/password_lockout_policy.go | 69 --- .../repository/eventsourcing/model/types.go | 4 +- .../view/model/password_lockout_policy.go | 43 +- .../model/password_lockout_policy_query.go | 38 +- .../view/password_lockout_policy_view.go | 14 +- .../eventsourcing/eventstore/org.go | 28 +- .../eventsourcing/handler/handler.go | 4 +- .../handler/password_lockout_policy.go | 54 +- .../eventsourcing/view/lockout_policy.go | 53 ++ .../view/password_lockout_policy.go | 53 -- internal/management/repository/org.go | 4 +- internal/org/model/org.go | 2 +- .../org/repository/eventsourcing/model/org.go | 18 +- .../model/password_lockout_policy.go | 16 +- .../model/password_lockout_policy_test.go | 42 +- .../repository/eventsourcing/model/types.go | 6 +- internal/query/converter.go | 10 +- internal/query/iam_model.go | 6 +- .../iam_policy_password_lockout_model.go | 18 +- .../org_policy_password_lockout_model.go | 14 +- .../query/policy_password_lockout_model.go | 14 +- internal/repository/iam/eventstore.go | 4 +- .../repository/iam/policy_password_lockout.go | 46 +- internal/repository/org/eventstore.go | 6 +- .../repository/org/policy_password_lockout.go | 68 +-- .../policy/policy_password_lockout.go | 72 +-- internal/setup/config.go | 2 + internal/ui/login/handler/renderer.go | 6 +- internal/ui/login/static/i18n/de.yaml | 5 + internal/ui/login/static/i18n/en.yaml | 5 + migrations/cockroach/V1.59__user_lock.sql | 45 ++ proto/zitadel/admin.proto | 31 +- proto/zitadel/management.proto | 55 +- proto/zitadel/policy.proto | 11 +- 101 files changed, 2170 insertions(+), 1574 deletions(-) create mode 100644 internal/admin/repository/eventsourcing/view/lockout_policy.go delete mode 100644 internal/admin/repository/eventsourcing/view/password_lockout_policy.go create mode 100644 internal/api/grpc/admin/lockout.go create mode 100644 internal/api/grpc/admin/lockout_converter.go delete mode 100644 internal/api/grpc/admin/password_lockout.go delete mode 100644 internal/api/grpc/admin/password_lockout_converter.go create mode 100644 internal/api/grpc/management/policy_lockout.go create mode 100644 internal/api/grpc/management/policy_lockout_converter.go delete mode 100644 internal/api/grpc/management/policy_password_lockout.go delete mode 100644 internal/api/grpc/management/policy_password_lockout_converter.go create mode 100644 internal/auth/repository/eventsourcing/handler/lockout_policy.go create mode 100644 internal/auth/repository/eventsourcing/view/lockout_policy.go create mode 100644 internal/command/setup_step18.go create mode 100644 internal/iam/repository/eventsourcing/model/lockout_policy.go rename internal/iam/repository/eventsourcing/model/{password_lockout_policy_test.go => lockout_policy_test.go} (56%) delete mode 100644 internal/iam/repository/eventsourcing/model/password_lockout_policy.go create mode 100644 internal/management/repository/eventsourcing/view/lockout_policy.go delete mode 100644 internal/management/repository/eventsourcing/view/password_lockout_policy.go create mode 100644 migrations/cockroach/V1.59__user_lock.sql diff --git a/cmd/zitadel/main.go b/cmd/zitadel/main.go index 071b3499c0..0899715c2c 100644 --- a/cmd/zitadel/main.go +++ b/cmd/zitadel/main.go @@ -202,7 +202,7 @@ func startAPI(ctx context.Context, conf *Config, verifier *internal_authz.TokenV for i, role := range conf.InternalAuthZ.RolePermissionMappings { roles[i] = role.Role } - repo, err := admin_es.Start(ctx, conf.Admin, conf.SystemDefaults, static, roles, *localDevMode) + repo, err := admin_es.Start(ctx, conf.Admin, conf.SystemDefaults, command, static, roles, *localDevMode) logging.Log("API-D42tq").OnError(err).Fatal("error starting auth repo") apis := api.Create(conf.API, conf.InternalAuthZ, authZRepo, authRepo, repo, conf.SystemDefaults) diff --git a/cmd/zitadel/setup.yaml b/cmd/zitadel/setup.yaml index 64d8f055a4..c3a4fcfb9d 100644 --- a/cmd/zitadel/setup.yaml +++ b/cmd/zitadel/setup.yaml @@ -74,7 +74,7 @@ SetUp: ExpireWarnDays: 0 Step4: DefaultPasswordLockoutPolicy: - MaxAttempts: 5 + MaxPasswordAttempts: 5 ShowLockOutFailures: false Step5: DefaultOrgIAMPolicy: @@ -192,4 +192,8 @@ SetUp: Step17: PrivacyPolicy: TOSLink: https://docs.zitadel.ch/docs/legal/terms-of-service - PrivacyLink: https://docs.zitadel.ch/docs/legal/privacy-policy \ No newline at end of file + PrivacyLink: https://docs.zitadel.ch/docs/legal/privacy-policy + Step18: + LockoutPolicy: + MaxPasswordAttempts: 0 + ShowLockOutFailures: true \ No newline at end of file diff --git a/console/package-lock.json b/console/package-lock.json index f2b4fc7395..4503d465d9 100644 --- a/console/package-lock.json +++ b/console/package-lock.json @@ -35,6 +35,7 @@ "libphonenumber-js": "^1.9.16", "moment": "^2.29.1", "ngx-color": "^7.2.0", + "ngx-image-cropper": "^3.3.5", "ngx-quicklink": "^0.2.6", "rxjs": "~6.6.7", "tinycolor2": "^1.4.2", @@ -10368,6 +10369,24 @@ "@angular/core": ">=12.0.0-0" } }, + "node_modules/ngx-image-cropper": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/ngx-image-cropper/-/ngx-image-cropper-3.3.5.tgz", + "integrity": "sha512-0yRVKG5XAbVo3rOaj/iFDlekGsxEqXKU9iXFbjyvHvRT2DFs+AjwtyvINsHCWw+4ed9yA4Y+wLIUNqzA0bfxLw==", + "dependencies": { + "tslib": "^1.9.0" + }, + "peerDependencies": { + "@angular/common": ">=8.0.0", + "@angular/core": ">=8.0.0", + "@angular/platform-browser": ">=8.0.0" + } + }, + "node_modules/ngx-image-cropper/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, "node_modules/ngx-quicklink": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/ngx-quicklink/-/ngx-quicklink-0.2.7.tgz", @@ -27536,6 +27555,21 @@ "tslib": "^2.1.0" } }, + "ngx-image-cropper": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/ngx-image-cropper/-/ngx-image-cropper-3.3.5.tgz", + "integrity": "sha512-0yRVKG5XAbVo3rOaj/iFDlekGsxEqXKU9iXFbjyvHvRT2DFs+AjwtyvINsHCWw+4ed9yA4Y+wLIUNqzA0bfxLw==", + "requires": { + "tslib": "^1.9.0" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + } + } + }, "ngx-quicklink": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/ngx-quicklink/-/ngx-quicklink-0.2.7.tgz", diff --git a/console/src/app/modules/policies/password-lockout-policy/password-lockout-policy.component.html b/console/src/app/modules/policies/password-lockout-policy/password-lockout-policy.component.html index bea9af4823..76c95df885 100644 --- a/console/src/app/modules/policies/password-lockout-policy/password-lockout-policy.component.html +++ b/console/src/app/modules/policies/password-lockout-policy/password-lockout-policy.component.html @@ -1,10 +1,10 @@ -

{{'POLICY.DEFAULTLABEL' | translate}}

+ {{'POLICY.DEFAULTLABEL' | translate}} @@ -14,22 +14,15 @@ {{'POLICY.DATA.MAXATTEMPTS' | translate}}
- - {{lockoutData?.maxAttempts}} + {{lockoutData?.maxPasswordAttempts}} +
-
- {{'POLICY.DATA.SHOWLOCKOUTFAILURES' | translate}} - - - -
diff --git a/console/src/app/modules/policies/password-lockout-policy/password-lockout-policy.component.scss b/console/src/app/modules/policies/password-lockout-policy/password-lockout-policy.component.scss index 8167148521..fa6671f2d7 100644 --- a/console/src/app/modules/policies/password-lockout-policy/password-lockout-policy.component.scss +++ b/console/src/app/modules/policies/password-lockout-policy/password-lockout-policy.component.scss @@ -1,6 +1,6 @@ .default { - color: var(--color-main); - margin-top: 0; + display: block; + margin-bottom: 1rem; } .content { diff --git a/console/src/app/modules/policies/password-lockout-policy/password-lockout-policy.component.ts b/console/src/app/modules/policies/password-lockout-policy/password-lockout-policy.component.ts index f500d5a03b..3bc55d5347 100644 --- a/console/src/app/modules/policies/password-lockout-policy/password-lockout-policy.component.ts +++ b/console/src/app/modules/policies/password-lockout-policy/password-lockout-policy.component.ts @@ -3,13 +3,11 @@ import { FormGroup } from '@angular/forms'; import { ActivatedRoute } from '@angular/router'; import { Subscription } from 'rxjs'; import { switchMap } from 'rxjs/operators'; +import { GetLockoutPolicyResponse as AdminGetPasswordLockoutPolicyResponse } from 'src/app/proto/generated/zitadel/admin_pb'; import { - GetPasswordLockoutPolicyResponse as AdminGetPasswordLockoutPolicyResponse, -} from 'src/app/proto/generated/zitadel/admin_pb'; -import { - GetPasswordLockoutPolicyResponse as MgmtGetPasswordLockoutPolicyResponse, + GetLockoutPolicyResponse as MgmtGetPasswordLockoutPolicyResponse, } from 'src/app/proto/generated/zitadel/management_pb'; -import { PasswordLockoutPolicy } from 'src/app/proto/generated/zitadel/policy_pb'; +import { LockoutPolicy } from 'src/app/proto/generated/zitadel/policy_pb'; import { AdminService } from 'src/app/services/admin.service'; import { ManagementService } from 'src/app/services/mgmt.service'; import { ToastService } from 'src/app/services/toast.service'; @@ -17,127 +15,124 @@ import { ToastService } from 'src/app/services/toast.service'; import { PolicyComponentServiceType } from '../policy-component-types.enum'; @Component({ - selector: 'app-password-lockout-policy', - templateUrl: './password-lockout-policy.component.html', - styleUrls: ['./password-lockout-policy.component.scss'], + selector: 'app-password-lockout-policy', + templateUrl: './password-lockout-policy.component.html', + styleUrls: ['./password-lockout-policy.component.scss'], }) export class PasswordLockoutPolicyComponent implements OnDestroy { - @Input() public service!: ManagementService | AdminService; - public serviceType: PolicyComponentServiceType = PolicyComponentServiceType.MGMT; + @Input() public service!: ManagementService | AdminService; + public serviceType: PolicyComponentServiceType = PolicyComponentServiceType.MGMT; - public lockoutForm!: FormGroup; - public lockoutData!: PasswordLockoutPolicy.AsObject; - private sub: Subscription = new Subscription(); - public PolicyComponentServiceType: any = PolicyComponentServiceType; + public lockoutForm!: FormGroup; + public lockoutData!: LockoutPolicy.AsObject; + private sub: Subscription = new Subscription(); + public PolicyComponentServiceType: any = PolicyComponentServiceType; - constructor( - private route: ActivatedRoute, - private toast: ToastService, - private injector: Injector, - ) { - this.sub = this.route.data.pipe(switchMap(data => { - this.serviceType = data.serviceType; + constructor( + private route: ActivatedRoute, + private toast: ToastService, + private injector: Injector, + ) { + this.sub = this.route.data.pipe(switchMap(data => { + this.serviceType = data.serviceType; - switch (this.serviceType) { - case PolicyComponentServiceType.MGMT: - this.service = this.injector.get(ManagementService as Type); - break; - case PolicyComponentServiceType.ADMIN: - this.service = this.injector.get(AdminService as Type); - break; - } + switch (this.serviceType) { + case PolicyComponentServiceType.MGMT: + this.service = this.injector.get(ManagementService as Type); + break; + case PolicyComponentServiceType.ADMIN: + this.service = this.injector.get(AdminService as Type); + break; + } - return this.route.params; - })).subscribe(() => { - this.fetchData(); + return this.route.params; + })).subscribe(() => { + this.fetchData(); + }); + } + + public ngOnDestroy(): void { + this.sub.unsubscribe(); + } + + private fetchData(): void { + this.getData().then(resp => { + if (resp.policy) { + this.lockoutData = resp.policy; + } + }); + } + + private getData(): + Promise { + switch (this.serviceType) { + case PolicyComponentServiceType.MGMT: + return (this.service as ManagementService).getLockoutPolicy(); + case PolicyComponentServiceType.ADMIN: + return (this.service as AdminService).getLockoutPolicy(); + } + } + + public resetPolicy(): void { + if (this.service instanceof ManagementService) { + this.service.resetLockoutPolicyToDefault().then(() => { + this.toast.showInfo('POLICY.TOAST.RESETSUCCESS', true); + this.fetchData(); + }).catch(error => { + this.toast.showError(error); + }); + } + } + + public incrementMaxAttempts(): void { + if (this.lockoutData?.maxPasswordAttempts !== undefined) { + this.lockoutData.maxPasswordAttempts++; + } + } + + public decrementMaxAttempts(): void { + if (this.lockoutData?.maxPasswordAttempts && this.lockoutData?.maxPasswordAttempts > 0) { + this.lockoutData.maxPasswordAttempts--; + } + } + + public savePolicy(): void { + let promise: Promise; + if (this.service instanceof AdminService) { + promise = this.service.updateLockoutPolicy( + this.lockoutData.maxPasswordAttempts, + ).then(() => { + this.toast.showInfo('POLICY.TOAST.SET', true); + }).catch(error => { + this.toast.showError(error); + }); + } else { + if ((this.lockoutData as LockoutPolicy.AsObject).isDefault) { + promise = this.service.addCustomLockoutPolicy( + this.lockoutData.maxPasswordAttempts, + ).then(() => { + this.toast.showInfo('POLICY.TOAST.SET', true); + }).catch(error => { + this.toast.showError(error); }); - } - - public ngOnDestroy(): void { - this.sub.unsubscribe(); - } - - private fetchData(): void { - this.getData().then(resp => { - if (resp.policy) { - this.lockoutData = resp.policy; - } + } else { + promise = this.service.updateCustomLockoutPolicy( + this.lockoutData.maxPasswordAttempts, + ).then(() => { + this.toast.showInfo('POLICY.TOAST.SET', true); + }).catch(error => { + this.toast.showError(error); }); + } } + } - private getData(): - Promise { - switch (this.serviceType) { - case PolicyComponentServiceType.MGMT: - return (this.service as ManagementService).getPasswordLockoutPolicy(); - case PolicyComponentServiceType.ADMIN: - return (this.service as AdminService).getPasswordLockoutPolicy(); - } - } - - public removePolicy(): void { - if (this.service instanceof ManagementService) { - this.service.resetPasswordLockoutPolicyToDefault().then(() => { - this.toast.showInfo('POLICY.TOAST.RESETSUCCESS', true); - this.fetchData(); - }).catch(error => { - this.toast.showError(error); - }); - } - } - - public incrementMaxAttempts(): void { - if (this.lockoutData?.maxAttempts !== undefined) { - this.lockoutData.maxAttempts++; - } - } - - public decrementMaxAttempts(): void { - if (this.lockoutData?.maxAttempts && this.lockoutData?.maxAttempts > 0) { - this.lockoutData.maxAttempts--; - } - } - - public savePolicy(): void { - let promise: Promise; - if (this.service instanceof AdminService) { - promise = this.service.updatePasswordLockoutPolicy( - this.lockoutData.maxAttempts, - this.lockoutData.showLockoutFailure, - ).then(() => { - this.toast.showInfo('POLICY.TOAST.SET', true); - }).catch(error => { - this.toast.showError(error); - }); - } else { - if ((this.lockoutData as PasswordLockoutPolicy.AsObject).isDefault) { - promise = this.service.addCustomPasswordLockoutPolicy( - this.lockoutData.maxAttempts, - this.lockoutData.showLockoutFailure, - ).then(() => { - this.toast.showInfo('POLICY.TOAST.SET', true); - }).catch(error => { - this.toast.showError(error); - }); - } else { - promise = this.service.updateCustomPasswordLockoutPolicy( - this.lockoutData.maxAttempts, - this.lockoutData.showLockoutFailure, - ).then(() => { - this.toast.showInfo('POLICY.TOAST.SET', true); - }).catch(error => { - this.toast.showError(error); - }); - } - } - } - - public get isDefault(): boolean { - if (this.lockoutData && this.serviceType === PolicyComponentServiceType.MGMT) { - return (this.lockoutData as PasswordLockoutPolicy.AsObject).isDefault; - } else { - return false; - } + public get isDefault(): boolean { + if (this.lockoutData && this.serviceType === PolicyComponentServiceType.MGMT) { + return (this.lockoutData as LockoutPolicy.AsObject).isDefault; + } else { + return false; } + } } diff --git a/console/src/app/modules/policies/password-lockout-policy/password-lockout-policy.module.ts b/console/src/app/modules/policies/password-lockout-policy/password-lockout-policy.module.ts index d092c5cf02..d72bd450ae 100644 --- a/console/src/app/modules/policies/password-lockout-policy/password-lockout-policy.module.ts +++ b/console/src/app/modules/policies/password-lockout-policy/password-lockout-policy.module.ts @@ -9,25 +9,26 @@ import { TranslateModule } from '@ngx-translate/core'; import { HasRoleModule } from 'src/app/directives/has-role/has-role.module'; import { DetailLayoutModule } from 'src/app/modules/detail-layout/detail-layout.module'; import { InputModule } from 'src/app/modules/input/input.module'; -import { LinksModule } from '../../links/links.module'; +import { InfoSectionModule } from '../../info-section/info-section.module'; import { PasswordLockoutPolicyRoutingModule } from './password-lockout-policy-routing.module'; import { PasswordLockoutPolicyComponent } from './password-lockout-policy.component'; @NgModule({ - declarations: [PasswordLockoutPolicyComponent], - imports: [ - PasswordLockoutPolicyRoutingModule, - CommonModule, - FormsModule, - InputModule, - MatButtonModule, - MatSlideToggleModule, - MatIconModule, - HasRoleModule, - MatTooltipModule, - TranslateModule, - DetailLayoutModule, - ], + declarations: [PasswordLockoutPolicyComponent], + imports: [ + PasswordLockoutPolicyRoutingModule, + CommonModule, + FormsModule, + InputModule, + MatButtonModule, + MatSlideToggleModule, + MatIconModule, + HasRoleModule, + MatTooltipModule, + TranslateModule, + DetailLayoutModule, + InfoSectionModule, + ], }) export class PasswordLockoutPolicyModule { } diff --git a/console/src/app/modules/policy-grid/policies.ts b/console/src/app/modules/policy-grid/policies.ts index 8363a58d8e..ea61017fdf 100644 --- a/console/src/app/modules/policy-grid/policies.ts +++ b/console/src/app/modules/policy-grid/policies.ts @@ -25,6 +25,18 @@ export const COMPLEXITY_POLICY: GridPolicy = { color: 'yellow', }; +export const LOCKOUT_POLICY: GridPolicy = { + i18nTitle: 'POLICY.PWD_LOCKOUT.TITLE', + i18nDesc: 'POLICY.PWD_LOCKOUT.DESCRIPTION', + iamRouterLink: ['/iam', 'policy', PolicyComponentType.LOCKOUT], + orgRouterLink: ['/org', 'policy', PolicyComponentType.LOCKOUT], + iamWithRole: ['iam.policy.read'], + orgWithRole: ['policy.read'], + tags: ['login', 'security'], + icon: 'las la-lock', + color: 'yellow', +}; + export const IAM_POLICY = { i18nTitle: 'POLICY.IAM_POLICY.TITLE', i18nDesc: 'POLICY.IAM_POLICY.DESCRIPTION', @@ -99,6 +111,7 @@ export const LOGIN_TEXTS_POLICY = { export const POLICIES: GridPolicy[] = [ COMPLEXITY_POLICY, + LOCKOUT_POLICY, IAM_POLICY, LOGIN_POLICY, PRIVATELABEL_POLICY, diff --git a/console/src/app/pages/users/user-detail/user-detail.module.ts b/console/src/app/pages/users/user-detail/user-detail.module.ts index 05286a4107..2bf98b3ab9 100644 --- a/console/src/app/pages/users/user-detail/user-detail.module.ts +++ b/console/src/app/pages/users/user-detail/user-detail.module.ts @@ -17,6 +17,7 @@ import { MemberCreateDialogModule } from 'src/app/modules/add-member-dialog/memb import { CardModule } from 'src/app/modules/card/card.module'; import { ChangesModule } from 'src/app/modules/changes/changes.module'; import { DetailLayoutModule } from 'src/app/modules/detail-layout/detail-layout.module'; +import { InfoSectionModule } from 'src/app/modules/info-section/info-section.module'; import { InputModule } from 'src/app/modules/input/input.module'; import { MachineKeysModule } from 'src/app/modules/machine-keys/machine-keys.module'; import { MetaLayoutModule } from 'src/app/modules/meta-layout/meta-layout.module'; @@ -104,6 +105,7 @@ import { UserMfaComponent } from './user-detail/user-mfa/user-mfa.component'; LocalizedDatePipeModule, InputModule, MachineKeysModule, + InfoSectionModule, ], }) export class UserDetailModule { } diff --git a/console/src/app/pages/users/user-detail/user-detail/user-detail.component.html b/console/src/app/pages/users/user-detail/user-detail/user-detail.component.html index abd274ca46..acbaffd164 100644 --- a/console/src/app/pages/users/user-detail/user-detail/user-detail.component.html +++ b/console/src/app/pages/users/user-detail/user-detail/user-detail.component.html @@ -14,6 +14,11 @@ + +