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 @@ + +