1
0
mirror of https://github.com/zitadel/zitadel.git synced 2025-03-31 16:52:16 +00:00

feat: Lockout policy ()

* 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 <silvan.reusser@gmail.com>

* fix: err message

* Update internal/command/setup_step4.go

Co-authored-by: Silvan <silvan.reusser@gmail.com>

Co-authored-by: Max Peintner <max@caos.ch>
Co-authored-by: Livio Amstutz <livio.a@gmail.com>
Co-authored-by: Silvan <silvan.reusser@gmail.com>
This commit is contained in:
Fabi 2021-08-11 08:36:32 +02:00 committed by GitHub
parent 272e411e27
commit bc951985ed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
101 changed files with 2170 additions and 1574 deletions
cmd/zitadel
console
docs/docs
internal
admin/repository
api/grpc
auth/repository/eventsourcing
command
domain
iam
management/repository
org
query
repository
setup
ui/login
handler
static/i18n
migrations/cockroach
proto/zitadel

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

@ -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
PrivacyLink: https://docs.zitadel.ch/docs/legal/privacy-policy
Step18:
LockoutPolicy:
MaxPasswordAttempts: 0
ShowLockOutFailures: true

@ -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",

@ -1,10 +1,10 @@
<app-detail-layout [backRouterLink]="[ serviceType === PolicyComponentServiceType.ADMIN ? '/iam/policies' : '/org']"
[title]="'POLICY.PWD_LOCKOUT.TITLE' | translate" [description]="'POLICY.PWD_LOCKOUT.DESCRIPTION' | translate">
<p class="default" *ngIf="isDefault"> {{'POLICY.DEFAULTLABEL' | translate}}</p>
<cnsl-info-section class="default" *ngIf="isDefault"> {{'POLICY.DEFAULTLABEL' | translate}}</cnsl-info-section>
<ng-template appHasRole [appHasRole]="['policy.delete']">
<button *ngIf="serviceType === PolicyComponentServiceType.MGMT && !isDefault"
matTooltip="{{'POLICY.RESET' | translate}}" color="warn" (click)="removePolicy()" mat-stroked-button>
matTooltip="{{'POLICY.RESET' | translate}}" color="warn" (click)="resetPolicy()" mat-stroked-button>
{{'POLICY.RESET' | translate}}
</button>
</ng-template>
@ -14,22 +14,15 @@
<span class="left-desc">{{'POLICY.DATA.MAXATTEMPTS' | translate}}</span>
<span class="fill-space"></span>
<div class="length-wrapper">
<button mat-icon-button (click)="incrementMaxAttempts()">
<mat-icon>add</mat-icon>
</button>
<span>{{lockoutData?.maxAttempts}}</span>
<button mat-icon-button (click)="decrementMaxAttempts()">
<mat-icon>remove</mat-icon>
</button>
<span>{{lockoutData?.maxPasswordAttempts}}</span>
<button mat-icon-button (click)="incrementMaxAttempts()">
<mat-icon>add</mat-icon>
</button>
</div>
</div>
<div class="row">
<span class="left-desc">{{'POLICY.DATA.SHOWLOCKOUTFAILURES' | translate}}</span>
<span class="fill-space"></span>
<mat-slide-toggle color="primary" name="showLockoutFailure" ngDefaultControl
[(ngModel)]="lockoutData.showLockoutFailure">
</mat-slide-toggle>
</div>
</div>
<div class="btn-container">

@ -1,6 +1,6 @@
.default {
color: var(--color-main);
margin-top: 0;
display: block;
margin-bottom: 1rem;
}
.content {

@ -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<ManagementService>);
break;
case PolicyComponentServiceType.ADMIN:
this.service = this.injector.get(AdminService as Type<AdminService>);
break;
}
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
this.service = this.injector.get(ManagementService as Type<ManagementService>);
break;
case PolicyComponentServiceType.ADMIN:
this.service = this.injector.get(AdminService as Type<AdminService>);
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<AdminGetPasswordLockoutPolicyResponse.AsObject | MgmtGetPasswordLockoutPolicyResponse.AsObject> {
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<any>;
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<AdminGetPasswordLockoutPolicyResponse.AsObject | MgmtGetPasswordLockoutPolicyResponse.AsObject> {
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<any>;
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;
}
}
}

@ -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 { }

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

@ -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 { }

@ -14,6 +14,11 @@
<span class="fill-space"></span>
<ng-template appHasRole [appHasRole]="['user.write$', 'user.write:'+user?.id]">
<button class="unlock-button" mat-stroked-button color="warn"
*ngIf="user?.state === UserState.USER_STATE_LOCKED"
(click)="unlockUser()">{{'USER.PAGES.UNLOCK' |
translate}}</button>
<button class="state-button" mat-stroked-button color="warn"
*ngIf="user?.state !== UserState.USER_STATE_INACTIVE"
(click)="changeState(UserState.USER_STATE_INACTIVE)">{{'USER.PAGES.DEACTIVATE' |
@ -26,6 +31,7 @@
<mat-progress-bar *ngIf="loading" color="primary" mode="indeterminate"></mat-progress-bar>
<cnsl-info-section class="locked" *ngIf="user?.state === UserState.USER_STATE_LOCKED" type="WARN">{{'USER.PAGES.LOCKEDDESCRIPTION' | translate}}</cnsl-info-section>
<span *ngIf="!loading && !user">{{ 'USER.PAGES.NOUSER' | translate }}</span>
<app-card title="{{ 'USER.PAGES.LOGINNAMES' | translate }}"

@ -18,11 +18,20 @@
flex: 1;
}
.unlock-button {
margin-left: .5rem;
}
.state-button {
margin-left: .5rem;
}
}
.locked {
display: block;
margin: 1rem 0;
}
.img-phone-email {
width: 300px;
}

@ -7,7 +7,7 @@ import { take } from 'rxjs/operators';
import { ChangeType } from 'src/app/modules/changes/changes.component';
import { UserGrantContext } from 'src/app/modules/user-grants/user-grants-datasource';
import { WarnDialogComponent } from 'src/app/modules/warn-dialog/warn-dialog.component';
import { SendHumanResetPasswordNotificationRequest } from 'src/app/proto/generated/zitadel/management_pb';
import { SendHumanResetPasswordNotificationRequest, UnlockUserRequest } from 'src/app/proto/generated/zitadel/management_pb';
import { Email, Gender, Machine, Phone, Profile, User, UserState } from 'src/app/proto/generated/zitadel/user_pb';
import { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.service';
@ -16,281 +16,292 @@ import { EditDialogComponent, EditDialogType } from '../auth-user-detail/edit-di
import { ResendEmailDialogComponent } from '../auth-user-detail/resend-email-dialog/resend-email-dialog.component';
@Component({
selector: 'app-user-detail',
templateUrl: './user-detail.component.html',
styleUrls: ['./user-detail.component.scss'],
selector: 'app-user-detail',
templateUrl: './user-detail.component.html',
styleUrls: ['./user-detail.component.scss'],
})
export class UserDetailComponent implements OnInit {
public user!: User.AsObject;
public genders: Gender[] = [Gender.GENDER_MALE, Gender.GENDER_FEMALE, Gender.GENDER_DIVERSE];
public languages: string[] = ['de', 'en'];
public user!: User.AsObject;
public genders: Gender[] = [Gender.GENDER_MALE, Gender.GENDER_FEMALE, Gender.GENDER_DIVERSE];
public languages: string[] = ['de', 'en'];
public ChangeType: any = ChangeType;
public loading: boolean = false;
public ChangeType: any = ChangeType;
public loading: boolean = false;
public UserState: any = UserState;
public copied: string = '';
public USERGRANTCONTEXT: UserGrantContext = UserGrantContext.USER;
public UserState: any = UserState;
public copied: string = '';
public USERGRANTCONTEXT: UserGrantContext = UserGrantContext.USER;
public EditDialogType: any = EditDialogType;
public refreshChanges$: EventEmitter<void> = new EventEmitter();
public EditDialogType: any = EditDialogType;
public refreshChanges$: EventEmitter<void> = new EventEmitter();
constructor(
public translate: TranslateService,
private route: ActivatedRoute,
private toast: ToastService,
public mgmtUserService: ManagementService,
private _location: Location,
private dialog: MatDialog,
private router: Router,
) { }
constructor(
public translate: TranslateService,
private route: ActivatedRoute,
private toast: ToastService,
public mgmtUserService: ManagementService,
private _location: Location,
private dialog: MatDialog,
private router: Router,
) { }
refreshUser(): void {
this.refreshChanges$.emit();
this.route.params.pipe(take(1)).subscribe(params => {
const { id } = params;
this.mgmtUserService.getUserByID(id).then(resp => {
if (resp.user) {
this.user = resp.user;
}
}).catch(err => {
console.error(err);
});
refreshUser(): void {
this.refreshChanges$.emit();
this.route.params.pipe(take(1)).subscribe(params => {
const { id } = params;
this.mgmtUserService.getUserByID(id).then(resp => {
if (resp.user) {
this.user = resp.user;
}
}).catch(err => {
console.error(err);
});
});
}
public ngOnInit(): void {
this.refreshUser();
}
public unlockUser(): void {
const req = new UnlockUserRequest();
req.setId(this.user.id);
this.mgmtUserService.unlockUser(req).then(() => {
this.toast.showInfo('USER.TOAST.UNLOCKED', true);
this.refreshUser();
}).catch(error => {
this.toast.showError(error);
});
}
public changeState(newState: UserState): void {
if (newState === UserState.USER_STATE_ACTIVE) {
this.mgmtUserService.reactivateUser(this.user.id).then(() => {
this.toast.showInfo('USER.TOAST.REACTIVATED', true);
this.user.state = newState;
}).catch(error => {
this.toast.showError(error);
});
} else if (newState === UserState.USER_STATE_INACTIVE) {
this.mgmtUserService.deactivateUser(this.user.id).then(() => {
this.toast.showInfo('USER.TOAST.DEACTIVATED', true);
this.user.state = newState;
}).catch(error => {
this.toast.showError(error);
});
}
}
public saveProfile(profileData: Profile.AsObject): void {
if (this.user.human) {
this.user.human.profile = profileData;
this.mgmtUserService
.updateHumanProfile(
this.user.id,
this.user.human.profile.firstName,
this.user.human.profile.lastName,
this.user.human.profile.nickName,
this.user.human.profile.displayName,
this.user.human.profile.preferredLanguage,
this.user.human.profile.gender)
.then(() => {
this.toast.showInfo('USER.TOAST.SAVED', true);
this.refreshChanges$.emit();
})
.catch(error => {
this.toast.showError(error);
});
}
}
public ngOnInit(): void {
public saveMachine(machineData: Machine.AsObject): void {
if (this.user.machine) {
this.user.machine.name = machineData.name;
this.user.machine.description = machineData.description;
this.mgmtUserService
.updateMachine(
this.user.id,
this.user.machine.name,
this.user.machine.description)
.then(() => {
this.toast.showInfo('USER.TOAST.SAVED', true);
this.refreshChanges$.emit();
})
.catch(error => {
this.toast.showError(error);
});
}
}
public resendEmailVerification(): void {
this.mgmtUserService.resendHumanEmailVerification(this.user.id).then(() => {
this.toast.showInfo('USER.TOAST.EMAILVERIFICATIONSENT', true);
this.refreshChanges$.emit();
}).catch(error => {
this.toast.showError(error);
});
}
public resendPhoneVerification(): void {
this.mgmtUserService.resendHumanPhoneVerification(this.user.id).then(() => {
this.toast.showInfo('USER.TOAST.PHONEVERIFICATIONSENT', true);
this.refreshChanges$.emit();
}).catch(error => {
this.toast.showError(error);
});
}
public deletePhone(): void {
this.mgmtUserService.removeHumanPhone(this.user.id).then(() => {
this.toast.showInfo('USER.TOAST.PHONEREMOVED', true);
if (this.user.human) {
this.user.human.phone = new Phone().setPhone('').toObject();
this.refreshUser();
}
}
}).catch(error => {
this.toast.showError(error);
});
}
public changeState(newState: UserState): void {
if (newState === UserState.USER_STATE_ACTIVE) {
this.mgmtUserService.reactivateUser(this.user.id).then(() => {
this.toast.showInfo('USER.TOAST.REACTIVATED', true);
this.user.state = newState;
}).catch(error => {
this.toast.showError(error);
});
} else if (newState === UserState.USER_STATE_INACTIVE) {
this.mgmtUserService.deactivateUser(this.user.id).then(() => {
this.toast.showInfo('USER.TOAST.DEACTIVATED', true);
this.user.state = newState;
}).catch(error => {
this.toast.showError(error);
});
public saveEmail(email: string): void {
if (this.user.id && email) {
this.mgmtUserService.updateHumanEmail(this.user.id, email).then(() => {
this.toast.showInfo('USER.TOAST.EMAILSAVED', true);
if (this.user.state === UserState.USER_STATE_INITIAL) {
this.mgmtUserService.resendHumanInitialization(this.user.id, email ?? '').then(() => {
this.toast.showInfo('USER.TOAST.INITEMAILSENT', true);
this.refreshChanges$.emit();
}).catch(error => {
this.toast.showError(error);
});
}
}
public saveProfile(profileData: Profile.AsObject): void {
if (this.user.human) {
this.user.human.profile = profileData;
this.mgmtUserService
.updateHumanProfile(
this.user.id,
this.user.human.profile.firstName,
this.user.human.profile.lastName,
this.user.human.profile.nickName,
this.user.human.profile.displayName,
this.user.human.profile.preferredLanguage,
this.user.human.profile.gender)
.then(() => {
this.toast.showInfo('USER.TOAST.SAVED', true);
this.refreshChanges$.emit();
})
.catch(error => {
this.toast.showError(error);
});
this.user.human.email = new Email().setEmail(email).toObject();
this.refreshUser();
}
}).catch(error => {
this.toast.showError(error);
});
}
}
public saveMachine(machineData: Machine.AsObject): void {
if (this.user.machine) {
this.user.machine.name = machineData.name;
this.user.machine.description = machineData.description;
this.mgmtUserService
.updateMachine(
this.user.id,
this.user.machine.name,
this.user.machine.description)
.then(() => {
this.toast.showInfo('USER.TOAST.SAVED', true);
this.refreshChanges$.emit();
})
.catch(error => {
this.toast.showError(error);
});
}
}
public resendEmailVerification(): void {
this.mgmtUserService.resendHumanEmailVerification(this.user.id).then(() => {
this.toast.showInfo('USER.TOAST.EMAILVERIFICATIONSENT', true);
this.refreshChanges$.emit();
public savePhone(phone: string): void {
if (this.user.id && phone) {
this.mgmtUserService
.updateHumanPhone(this.user.id, phone).then(() => {
this.toast.showInfo('USER.TOAST.PHONESAVED', true);
if (this.user.human) {
this.user.human.phone = new Phone().setPhone(phone).toObject();
this.refreshUser();
}
}).catch(error => {
this.toast.showError(error);
this.toast.showError(error);
});
}
}
public resendPhoneVerification(): void {
this.mgmtUserService.resendHumanPhoneVerification(this.user.id).then(() => {
this.toast.showInfo('USER.TOAST.PHONEVERIFICATIONSENT', true);
this.refreshChanges$.emit();
public navigateBack(): void {
this._location.back();
}
public sendSetPasswordNotification(): void {
this.mgmtUserService.sendHumanResetPasswordNotification(
this.user.id,
SendHumanResetPasswordNotificationRequest.Type.TYPE_EMAIL,
).then(() => {
this.toast.showInfo('USER.TOAST.PASSWORDNOTIFICATIONSENT', true);
this.refreshChanges$.emit();
}).catch(error => {
this.toast.showError(error);
});
}
public deleteUser(): void {
const dialogRef = this.dialog.open(WarnDialogComponent, {
data: {
confirmKey: 'ACTIONS.DELETE',
cancelKey: 'ACTIONS.CANCEL',
titleKey: 'USER.DIALOG.DELETE_TITLE',
descriptionKey: 'USER.DIALOG.DELETE_DESCRIPTION',
},
width: '400px',
});
dialogRef.afterClosed().subscribe(resp => {
if (resp) {
this.mgmtUserService.removeUser(this.user.id).then(() => {
const params: Params = {
'deferredReload': true,
};
this.router.navigate(['/users/list', this.user.human ? 'humans' : 'machines'], { queryParams: params });
this.toast.showInfo('USER.TOAST.DELETED', true);
}).catch(error => {
this.toast.showError(error);
this.toast.showError(error);
});
}
}
});
}
public deletePhone(): void {
this.mgmtUserService.removeHumanPhone(this.user.id).then(() => {
this.toast.showInfo('USER.TOAST.PHONEREMOVED', true);
if (this.user.human) {
this.user.human.phone = new Phone().setPhone('').toObject();
this.refreshUser();
}
public resendInitEmail(): void {
const dialogRef = this.dialog.open(ResendEmailDialogComponent, {
width: '400px',
});
dialogRef.afterClosed().subscribe(resp => {
if (resp.send && this.user.id) {
this.mgmtUserService.resendHumanInitialization(this.user.id, resp.email ?? '').then(() => {
this.toast.showInfo('USER.TOAST.INITEMAILSENT', true);
this.refreshChanges$.emit();
}).catch(error => {
this.toast.showError(error);
this.toast.showError(error);
});
}
}
});
}
public saveEmail(email: string): void {
if (this.user.id && email) {
this.mgmtUserService.updateHumanEmail(this.user.id, email).then(() => {
this.toast.showInfo('USER.TOAST.EMAILSAVED', true);
if (this.user.state === UserState.USER_STATE_INITIAL) {
this.mgmtUserService.resendHumanInitialization(this.user.id, email ?? '').then(() => {
this.toast.showInfo('USER.TOAST.INITEMAILSENT', true);
this.refreshChanges$.emit();
}).catch(error => {
this.toast.showError(error);
});
}
if (this.user.human) {
this.user.human.email = new Email().setEmail(email).toObject();
this.refreshUser();
}
}).catch(error => {
this.toast.showError(error);
});
}
}
public savePhone(phone: string): void {
if (this.user.id && phone) {
this.mgmtUserService
.updateHumanPhone(this.user.id, phone).then(() => {
this.toast.showInfo('USER.TOAST.PHONESAVED', true);
if (this.user.human) {
this.user.human.phone = new Phone().setPhone(phone).toObject();
this.refreshUser();
}
}).catch(error => {
this.toast.showError(error);
});
}
}
public navigateBack(): void {
this._location.back();
}
public sendSetPasswordNotification(): void {
this.mgmtUserService.sendHumanResetPasswordNotification(
this.user.id,
SendHumanResetPasswordNotificationRequest.Type.TYPE_EMAIL,
).then(() => {
this.toast.showInfo('USER.TOAST.PASSWORDNOTIFICATIONSENT', true);
this.refreshChanges$.emit();
}).catch(error => {
this.toast.showError(error);
});
}
public deleteUser(): void {
const dialogRef = this.dialog.open(WarnDialogComponent, {
data: {
confirmKey: 'ACTIONS.DELETE',
cancelKey: 'ACTIONS.CANCEL',
titleKey: 'USER.DIALOG.DELETE_TITLE',
descriptionKey: 'USER.DIALOG.DELETE_DESCRIPTION',
},
width: '400px',
public openEditDialog(type: EditDialogType): void {
switch (type) {
case EditDialogType.PHONE:
const dialogRefPhone = this.dialog.open(EditDialogComponent, {
data: {
confirmKey: 'ACTIONS.SAVE',
cancelKey: 'ACTIONS.CANCEL',
labelKey: 'ACTIONS.NEWVALUE',
titleKey: 'USER.LOGINMETHODS.PHONE.EDITTITLE',
descriptionKey: 'USER.LOGINMETHODS.PHONE.EDITDESC',
value: this.user.human?.phone?.phone,
type: EditDialogType.PHONE,
},
width: '400px',
});
dialogRef.afterClosed().subscribe(resp => {
if (resp) {
this.mgmtUserService.removeUser(this.user.id).then(() => {
const params: Params = {
'deferredReload': true,
};
this.router.navigate(['/users/list', this.user.human ? 'humans' : 'machines'], { queryParams: params });
this.toast.showInfo('USER.TOAST.DELETED', true);
}).catch(error => {
this.toast.showError(error);
});
}
dialogRefPhone.afterClosed().subscribe(resp => {
if (resp) {
this.savePhone(resp);
}
});
}
public resendInitEmail(): void {
const dialogRef = this.dialog.open(ResendEmailDialogComponent, {
width: '400px',
break;
case EditDialogType.EMAIL:
const dialogRefEmail = this.dialog.open(EditDialogComponent, {
data: {
confirmKey: 'ACTIONS.SAVE',
cancelKey: 'ACTIONS.CANCEL',
labelKey: 'ACTIONS.NEWVALUE',
titleKey: 'USER.LOGINMETHODS.EMAIL.EDITTITLE',
descriptionKey: 'USER.LOGINMETHODS.EMAIL.EDITDESC',
value: this.user.human?.email?.email,
type: EditDialogType.EMAIL,
},
width: '400px',
});
dialogRef.afterClosed().subscribe(resp => {
if (resp.send && this.user.id) {
this.mgmtUserService.resendHumanInitialization(this.user.id, resp.email ?? '').then(() => {
this.toast.showInfo('USER.TOAST.INITEMAILSENT', true);
this.refreshChanges$.emit();
}).catch(error => {
this.toast.showError(error);
});
}
dialogRefEmail.afterClosed().subscribe(resp => {
if (resp) {
this.saveEmail(resp);
}
});
break;
}
public openEditDialog(type: EditDialogType): void {
switch (type) {
case EditDialogType.PHONE:
const dialogRefPhone = this.dialog.open(EditDialogComponent, {
data: {
confirmKey: 'ACTIONS.SAVE',
cancelKey: 'ACTIONS.CANCEL',
labelKey: 'ACTIONS.NEWVALUE',
titleKey: 'USER.LOGINMETHODS.PHONE.EDITTITLE',
descriptionKey: 'USER.LOGINMETHODS.PHONE.EDITDESC',
value: this.user.human?.phone?.phone,
type: EditDialogType.PHONE,
},
width: '400px',
});
dialogRefPhone.afterClosed().subscribe(resp => {
if (resp) {
this.savePhone(resp);
}
});
break;
case EditDialogType.EMAIL:
const dialogRefEmail = this.dialog.open(EditDialogComponent, {
data: {
confirmKey: 'ACTIONS.SAVE',
cancelKey: 'ACTIONS.CANCEL',
labelKey: 'ACTIONS.NEWVALUE',
titleKey: 'USER.LOGINMETHODS.EMAIL.EDITTITLE',
descriptionKey: 'USER.LOGINMETHODS.EMAIL.EDITDESC',
value: this.user.human?.email?.email,
type: EditDialogType.EMAIL,
},
width: '400px',
});
dialogRefEmail.afterClosed().subscribe(resp => {
if (resp) {
this.saveEmail(resp);
}
});
break;
}
}
}
}

@ -51,6 +51,8 @@ import {
GetIDPByIDResponse,
GetLabelPolicyRequest,
GetLabelPolicyResponse,
GetLockoutPolicyRequest,
GetLockoutPolicyResponse,
GetLoginPolicyRequest,
GetLoginPolicyResponse,
GetOrgFeaturesRequest,
@ -61,8 +63,6 @@ import {
GetPasswordAgePolicyResponse,
GetPasswordComplexityPolicyRequest,
GetPasswordComplexityPolicyResponse,
GetPasswordLockoutPolicyRequest,
GetPasswordLockoutPolicyResponse,
GetPreviewLabelPolicyRequest,
GetPreviewLabelPolicyResponse,
GetPrivacyPolicyRequest,
@ -144,6 +144,8 @@ import {
UpdateIDPResponse,
UpdateLabelPolicyRequest,
UpdateLabelPolicyResponse,
UpdateLockoutPolicyRequest,
UpdateLockoutPolicyResponse,
UpdateLoginPolicyRequest,
UpdateLoginPolicyResponse,
UpdateOrgIAMPolicyRequest,
@ -152,8 +154,6 @@ import {
UpdatePasswordAgePolicyResponse,
UpdatePasswordComplexityPolicyRequest,
UpdatePasswordComplexityPolicyResponse,
UpdatePasswordLockoutPolicyRequest,
UpdatePasswordLockoutPolicyResponse,
UpdatePrivacyPolicyRequest,
UpdatePrivacyPolicyResponse,
} from '../proto/generated/zitadel/admin_pb';
@ -431,20 +431,18 @@ export class AdminService {
/* lockout */
public getPasswordLockoutPolicy(): Promise<GetPasswordLockoutPolicyResponse.AsObject> {
const req = new GetPasswordLockoutPolicyRequest();
return this.grpcService.admin.getPasswordLockoutPolicy(req, null).then(resp => resp.toObject());
public getLockoutPolicy(): Promise<GetLockoutPolicyResponse.AsObject> {
const req = new GetLockoutPolicyRequest();
return this.grpcService.admin.getLockoutPolicy(req, null).then(resp => resp.toObject());
}
public updatePasswordLockoutPolicy(
public updateLockoutPolicy(
maxAttempts: number,
showLockoutFailures: boolean,
): Promise<UpdatePasswordLockoutPolicyResponse.AsObject> {
const req = new UpdatePasswordLockoutPolicyRequest();
req.setMaxAttempts(maxAttempts);
req.setShowLockoutFailure(showLockoutFailures);
): Promise<UpdateLockoutPolicyResponse.AsObject> {
const req = new UpdateLockoutPolicyRequest();
req.setMaxPasswordAttempts(maxAttempts);
return this.grpcService.admin.updatePasswordLockoutPolicy(req, null).then(resp => resp.toObject());
return this.grpcService.admin.updateLockoutPolicy(req, null).then(resp => resp.toObject());
}
/* label */

@ -17,14 +17,14 @@ import {
AddAppKeyResponse,
AddCustomLabelPolicyRequest,
AddCustomLabelPolicyResponse,
AddCustomLockoutPolicyRequest,
AddCustomLockoutPolicyResponse,
AddCustomLoginPolicyRequest,
AddCustomLoginPolicyResponse,
AddCustomPasswordAgePolicyRequest,
AddCustomPasswordAgePolicyResponse,
AddCustomPasswordComplexityPolicyRequest,
AddCustomPasswordComplexityPolicyResponse,
AddCustomPasswordLockoutPolicyRequest,
AddCustomPasswordLockoutPolicyResponse,
AddCustomPrivacyPolicyRequest,
AddCustomPrivacyPolicyResponse,
AddHumanUserRequest,
@ -122,6 +122,8 @@ import {
GetIAMResponse,
GetLabelPolicyRequest,
GetLabelPolicyResponse,
GetLockoutPolicyRequest,
GetLockoutPolicyResponse,
GetLoginPolicyRequest,
GetLoginPolicyResponse,
GetMyOrgRequest,
@ -138,8 +140,6 @@ import {
GetPasswordAgePolicyResponse,
GetPasswordComplexityPolicyRequest,
GetPasswordComplexityPolicyResponse,
GetPasswordLockoutPolicyRequest,
GetPasswordLockoutPolicyResponse,
GetPreviewLabelPolicyRequest,
GetPreviewLabelPolicyResponse,
GetPrivacyPolicyRequest,
@ -298,14 +298,14 @@ import {
ResetCustomVerifyPhoneMessageTextToDefaultResponse,
ResetLabelPolicyToDefaultRequest,
ResetLabelPolicyToDefaultResponse,
ResetLockoutPolicyToDefaultRequest,
ResetLockoutPolicyToDefaultResponse,
ResetLoginPolicyToDefaultRequest,
ResetLoginPolicyToDefaultResponse,
ResetPasswordAgePolicyToDefaultRequest,
ResetPasswordAgePolicyToDefaultResponse,
ResetPasswordComplexityPolicyToDefaultRequest,
ResetPasswordComplexityPolicyToDefaultResponse,
ResetPasswordLockoutPolicyToDefaultRequest,
ResetPasswordLockoutPolicyToDefaultResponse,
ResetPrivacyPolicyToDefaultRequest,
ResetPrivacyPolicyToDefaultResponse,
SendHumanResetPasswordNotificationRequest,
@ -324,20 +324,22 @@ import {
SetHumanInitialPasswordRequest,
SetPrimaryOrgDomainRequest,
SetPrimaryOrgDomainResponse,
UnlockUserRequest,
UnlockUserResponse,
UpdateAPIAppConfigRequest,
UpdateAPIAppConfigResponse,
UpdateAppRequest,
UpdateAppResponse,
UpdateCustomLabelPolicyRequest,
UpdateCustomLabelPolicyResponse,
UpdateCustomLockoutPolicyRequest,
UpdateCustomLockoutPolicyResponse,
UpdateCustomLoginPolicyRequest,
UpdateCustomLoginPolicyResponse,
UpdateCustomPasswordAgePolicyRequest,
UpdateCustomPasswordAgePolicyResponse,
UpdateCustomPasswordComplexityPolicyRequest,
UpdateCustomPasswordComplexityPolicyResponse,
UpdateCustomPasswordLockoutPolicyRequest,
UpdateCustomPasswordLockoutPolicyResponse,
UpdateCustomPrivacyPolicyRequest,
UpdateCustomPrivacyPolicyResponse,
UpdateHumanEmailRequest,
@ -556,6 +558,11 @@ export class ManagementService {
return this.grpcService.mgmt.listOrgIDPs(req, null).then(resp => resp.toObject());
}
public unlockUser(req: UnlockUserRequest):
Promise<UnlockUserResponse.AsObject> {
return this.grpcService.mgmt.unlockUser(req, null).then(resp => resp.toObject());
}
public getPrivacyPolicy():
Promise<GetPrivacyPolicyResponse.AsObject> {
const req = new GetPrivacyPolicyRequest();
@ -1105,36 +1112,31 @@ export class ManagementService {
return this.grpcService.mgmt.updateCustomPasswordComplexityPolicy(req, null).then(resp => resp.toObject());
}
public getPasswordLockoutPolicy(): Promise<GetPasswordLockoutPolicyResponse.AsObject> {
const req = new GetPasswordLockoutPolicyRequest();
return this.grpcService.mgmt.getPasswordLockoutPolicy(req, null).then(resp => resp.toObject());
public getLockoutPolicy(): Promise<GetLockoutPolicyResponse.AsObject> {
const req = new GetLockoutPolicyRequest();
return this.grpcService.mgmt.getLockoutPolicy(req, null).then(resp => resp.toObject());
}
public addCustomPasswordLockoutPolicy(
public addCustomLockoutPolicy(
maxAttempts: number,
showLockoutFailures: boolean,
): Promise<AddCustomPasswordLockoutPolicyResponse.AsObject> {
const req = new AddCustomPasswordLockoutPolicyRequest();
req.setMaxAttempts(maxAttempts);
req.setShowLockoutFailure(showLockoutFailures);
): Promise<AddCustomLockoutPolicyResponse.AsObject> {
const req = new AddCustomLockoutPolicyRequest();
req.setMaxPasswordAttempts(maxAttempts);
return this.grpcService.mgmt.addCustomPasswordLockoutPolicy(req, null).then(resp => resp.toObject());
return this.grpcService.mgmt.addCustomLockoutPolicy(req, null).then(resp => resp.toObject());
}
public resetPasswordLockoutPolicyToDefault(): Promise<ResetPasswordLockoutPolicyToDefaultResponse.AsObject> {
const req = new ResetPasswordLockoutPolicyToDefaultRequest();
return this.grpcService.mgmt.resetPasswordLockoutPolicyToDefault(req, null).then(resp => resp.toObject());
public resetLockoutPolicyToDefault(): Promise<ResetLockoutPolicyToDefaultResponse.AsObject> {
const req = new ResetLockoutPolicyToDefaultRequest();
return this.grpcService.mgmt.resetLockoutPolicyToDefault(req, null).then(resp => resp.toObject());
}
public updateCustomPasswordLockoutPolicy(
public updateCustomLockoutPolicy(
maxAttempts: number,
showLockoutFailures: boolean,
): Promise<UpdateCustomPasswordLockoutPolicyResponse.AsObject> {
const req = new UpdateCustomPasswordLockoutPolicyRequest();
req.setMaxAttempts(maxAttempts);
req.setShowLockoutFailure(showLockoutFailures);
return this.grpcService.mgmt.updateCustomPasswordLockoutPolicy(req, null).then(resp => resp.toObject());
): Promise<UpdateCustomLockoutPolicyResponse.AsObject> {
const req = new UpdateCustomLockoutPolicyRequest();
req.setMaxPasswordAttempts(maxAttempts);
return this.grpcService.mgmt.updateCustomLockoutPolicy(req, null).then(resp => resp.toObject());
}
public getLocalizedComplexityPolicyPatternErrorString(policy: PasswordComplexityPolicy.AsObject): string {

@ -174,7 +174,9 @@
"REACTIVATE": "Reaktivieren",
"DEACTIVATE": "Deaktivieren",
"FILTER": "Filter",
"DELETE": "Benutzer löschen"
"DELETE": "Benutzer löschen",
"UNLOCK": "Benutzer entsperren",
"LOCKEDDESCRIPTION":"Dieser Benutzer wurde aufgrund der Überschreitung der maximalen Anmeldeversuche gesperrt und muss zur erneuten Verwendung entsperrt werden."
},
"DIALOG": {
"DELETE_TITLE": "User löschen",
@ -421,7 +423,8 @@
"STATE": {
"0": "Unbekannt",
"1": "Aktiv",
"2": "Abgelaufen"
"2": "Abgelaufen",
"4": "Gesperrt"
},
"SEARCH": {
"FOUND": "Gefunden"
@ -460,7 +463,8 @@
"SELECTEDKEYSDELETED": "Selektierte Schlüssel gelöscht.",
"KEYADDED": "Schlüssel hinzugefügt!",
"MACHINEADDED": "Service User erstellt!",
"DELETED": "Benutzer erfolgreich gelöscht!"
"DELETED": "Benutzer erfolgreich gelöscht!",
"UNLOCKED":"Benutzer erfolgreich freigeschaltet!"
},
"MEMBERSHIPS": {
"TITLE": "ZITADEL Manager-Rollen",
@ -688,7 +692,7 @@
},
"PWD_LOCKOUT": {
"TITLE": "Passwortsperre",
"DESCRIPTION": "Standardmässig sind die Passwortwiederholungen bei Falscheingabe nicht begrenzt. Du musst diese Richtlinie installieren, wenn Du Wiederholungsversuche anzeigen, oder eine maximale Anzahl von wiederholten Passworteingaben festlegen möchtest."
"DESCRIPTION": "Lege eine maximale Anzahl an Passwordwiederholungen fest, nachdem Accounts gesperrt werden sollen."
},
"IAM_POLICY": {
"TITLE": "Zugangseinstellungen IAM",

@ -174,7 +174,9 @@
"REACTIVATE": "Reactivate",
"DEACTIVATE": "Deactivate",
"FILTER": "Filter",
"DELETE": "Delete User"
"DELETE": "Delete User",
"UNLOCK": "Unlock User",
"LOCKEDDESCRIPTION":"This user has been locked out due to exceeding the maximum login attempts and must be unlocked to be used again."
},
"DIALOG": {
"DELETE_TITLE": "Delete User",
@ -421,7 +423,8 @@
"STATE": {
"0": "Unknown",
"1": "Active",
"2": "Expired"
"2": "Expired",
"4": "Locked"
},
"SEARCH": {
"FOUND": "Found"
@ -460,7 +463,8 @@
"SELECTEDKEYSDELETED": "Selected keys deleted.",
"KEYADDED": "Key added!",
"MACHINEADDED": "Service User created!",
"DELETED": "User deleted successfully!"
"DELETED": "User deleted successfully!",
"UNLOCKED":"User unlocked successfully!"
},
"MEMBERSHIPS": {
"TITLE": "ZITADEL Manager Roles",
@ -688,7 +692,7 @@
},
"PWD_LOCKOUT": {
"TITLE": "Password Lockout",
"DESCRIPTION": "Password retries are infinite in default mode. You have to apply this policy if you want to show the number of retries, or set a maximum number of retries after which the account will be blocked."
"DESCRIPTION": "Set a maximum number of passwordretries, after which accounts will be blocked."
},
"IAM_POLICY": {
"TITLE": "IAM Access Preferences",

@ -615,24 +615,24 @@ it impacts all organisations without a customised policy
PUT: /policies/password/age
### GetPasswordLockoutPolicy
### GetLockoutPolicy
> **rpc** GetPasswordLockoutPolicy([GetPasswordLockoutPolicyRequest](#getpasswordlockoutpolicyrequest))
[GetPasswordLockoutPolicyResponse](#getpasswordlockoutpolicyresponse)
> **rpc** GetLockoutPolicy([GetLockoutPolicyRequest](#getlockoutpolicyrequest))
[GetLockoutPolicyResponse](#getlockoutpolicyresponse)
Returns the password lockout policy defined by the administrators of ZITADEL
Returns the lockout policy defined by the administrators of ZITADEL
GET: /policies/password/lockout
GET: /policies/lockout
### UpdatePasswordLockoutPolicy
### UpdateLockoutPolicy
> **rpc** UpdatePasswordLockoutPolicy([UpdatePasswordLockoutPolicyRequest](#updatepasswordlockoutpolicyrequest))
[UpdatePasswordLockoutPolicyResponse](#updatepasswordlockoutpolicyresponse)
> **rpc** UpdateLockoutPolicy([UpdateLockoutPolicyRequest](#updatelockoutpolicyrequest))
[UpdateLockoutPolicyResponse](#updatelockoutpolicyresponse)
Updates the default password lockout policy of ZITADEL
Updates the default lockout policy of ZITADEL
it impacts all organisations without a customised policy
@ -1681,6 +1681,23 @@ This is an empty request
### GetLockoutPolicyRequest
This is an empty request
### GetLockoutPolicyResponse
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| policy | zitadel.policy.v1.LockoutPolicy | - | |
### GetLoginPolicyRequest
This is an empty request
@ -1793,23 +1810,6 @@ This is an empty request
### GetPasswordLockoutPolicyRequest
This is an empty request
### GetPasswordLockoutPolicyResponse
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| policy | zitadel.policy.v1.PasswordLockoutPolicy | - | |
### GetPreviewLabelPolicyRequest
This is an empty request
@ -2924,6 +2924,28 @@ This is an empty request
### UpdateLockoutPolicyRequest
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| max_password_attempts | uint32 | failed attempts until a user gets locked | |
### UpdateLockoutPolicyResponse
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| details | zitadel.v1.ObjectDetails | - | |
### UpdateLoginPolicyRequest
@ -3022,29 +3044,6 @@ This is an empty request
### UpdatePasswordLockoutPolicyRequest
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| max_attempts | uint32 | failed attempts until a user gets locked | |
| show_lockout_failure | bool | If an error should be displayed during a lockout or not | |
### UpdatePasswordLockoutPolicyResponse
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| details | zitadel.v1.ObjectDetails | - | |
### UpdatePrivacyPolicyRequest

@ -1926,64 +1926,64 @@ The password age policy is not used at the moment
DELETE: /policies/password/age
### GetPasswordLockoutPolicy
### GetLockoutPolicy
> **rpc** GetPasswordLockoutPolicy([GetPasswordLockoutPolicyRequest](#getpasswordlockoutpolicyrequest))
[GetPasswordLockoutPolicyResponse](#getpasswordlockoutpolicyresponse)
The password lockout policy is not used at the moment
> **rpc** GetLockoutPolicy([GetLockoutPolicyRequest](#getlockoutpolicyrequest))
[GetLockoutPolicyResponse](#getlockoutpolicyresponse)
GET: /policies/password/lockout
### GetDefaultPasswordLockoutPolicy
GET: /policies/lockout
> **rpc** GetDefaultPasswordLockoutPolicy([GetDefaultPasswordLockoutPolicyRequest](#getdefaultpasswordlockoutpolicyrequest))
[GetDefaultPasswordLockoutPolicyResponse](#getdefaultpasswordlockoutpolicyresponse)
The password lockout policy is not used at the moment
### GetDefaultLockoutPolicy
> **rpc** GetDefaultLockoutPolicy([GetDefaultLockoutPolicyRequest](#getdefaultlockoutpolicyrequest))
[GetDefaultLockoutPolicyResponse](#getdefaultlockoutpolicyresponse)
GET: /policies/default/password/lockout
### AddCustomPasswordLockoutPolicy
GET: /policies/default/lockout
> **rpc** AddCustomPasswordLockoutPolicy([AddCustomPasswordLockoutPolicyRequest](#addcustompasswordlockoutpolicyrequest))
[AddCustomPasswordLockoutPolicyResponse](#addcustompasswordlockoutpolicyresponse)
The password lockout policy is not used at the moment
### AddCustomLockoutPolicy
> **rpc** AddCustomLockoutPolicy([AddCustomLockoutPolicyRequest](#addcustomlockoutpolicyrequest))
[AddCustomLockoutPolicyResponse](#addcustomlockoutpolicyresponse)
POST: /policies/password/lockout
### UpdateCustomPasswordLockoutPolicy
POST: /policies/lockout
> **rpc** UpdateCustomPasswordLockoutPolicy([UpdateCustomPasswordLockoutPolicyRequest](#updatecustompasswordlockoutpolicyrequest))
[UpdateCustomPasswordLockoutPolicyResponse](#updatecustompasswordlockoutpolicyresponse)
The password lockout policy is not used at the moment
### UpdateCustomLockoutPolicy
> **rpc** UpdateCustomLockoutPolicy([UpdateCustomLockoutPolicyRequest](#updatecustomlockoutpolicyrequest))
[UpdateCustomLockoutPolicyResponse](#updatecustomlockoutpolicyresponse)
PUT: /policies/password/lockout
### ResetPasswordLockoutPolicyToDefault
PUT: /policies/lockout
> **rpc** ResetPasswordLockoutPolicyToDefault([ResetPasswordLockoutPolicyToDefaultRequest](#resetpasswordlockoutpolicytodefaultrequest))
[ResetPasswordLockoutPolicyToDefaultResponse](#resetpasswordlockoutpolicytodefaultresponse)
The password lockout policy is not used at the moment
### ResetLockoutPolicyToDefault
> **rpc** ResetLockoutPolicyToDefault([ResetLockoutPolicyToDefaultRequest](#resetlockoutpolicytodefaultrequest))
[ResetLockoutPolicyToDefaultResponse](#resetlockoutpolicytodefaultresponse)
DELETE: /policies/password/lockout
DELETE: /policies/lockout
### GetPrivacyPolicy
@ -2769,6 +2769,28 @@ This is an empty request
### AddCustomLockoutPolicyRequest
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| max_password_attempts | uint32 | - | |
### AddCustomLockoutPolicyResponse
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| details | zitadel.v1.ObjectDetails | - | |
### AddCustomLoginPolicyRequest
@ -2845,29 +2867,6 @@ This is an empty request
### AddCustomPasswordLockoutPolicyRequest
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| max_attempts | uint32 | - | |
| show_lockout_failure | bool | - | |
### AddCustomPasswordLockoutPolicyResponse
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| details | zitadel.v1.ObjectDetails | - | |
### AddCustomPrivacyPolicyRequest
@ -3915,6 +3914,23 @@ This is an empty request
### GetDefaultLockoutPolicyRequest
This is an empty request
### GetDefaultLockoutPolicyResponse
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| policy | zitadel.policy.v1.LockoutPolicy | - | |
### GetDefaultLoginPolicyRequest
@ -3988,23 +4004,6 @@ This is an empty request
### GetDefaultPasswordLockoutPolicyRequest
This is an empty request
### GetDefaultPasswordLockoutPolicyResponse
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| policy | zitadel.policy.v1.PasswordLockoutPolicy | - | |
### GetDefaultPasswordResetMessageTextRequest
@ -4255,6 +4254,24 @@ This is an empty request
### GetLockoutPolicyRequest
This is an empty request
### GetLockoutPolicyResponse
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| policy | zitadel.policy.v1.LockoutPolicy | - | |
| is_default | bool | - | |
### GetLoginPolicyRequest
@ -4428,24 +4445,6 @@ This is an empty request
### GetPasswordLockoutPolicyRequest
This is an empty request
### GetPasswordLockoutPolicyResponse
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| policy | zitadel.policy.v1.PasswordLockoutPolicy | - | |
| is_default | bool | - | |
### GetPreviewLabelPolicyRequest
This is an empty request
@ -6529,6 +6528,23 @@ This is an empty request
### ResetLockoutPolicyToDefaultRequest
This is an empty request
### ResetLockoutPolicyToDefaultResponse
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| details | zitadel.v1.ObjectDetails | - | |
### ResetLoginPolicyToDefaultRequest
@ -6580,23 +6596,6 @@ This is an empty request
### ResetPasswordLockoutPolicyToDefaultRequest
This is an empty request
### ResetPasswordLockoutPolicyToDefaultResponse
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| details | zitadel.v1.ObjectDetails | - | |
### ResetPrivacyPolicyToDefaultRequest
This is an empty request
@ -7083,6 +7082,28 @@ This is an empty request
### UpdateCustomLockoutPolicyRequest
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| max_password_attempts | uint32 | - | |
### UpdateCustomLockoutPolicyResponse
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| details | zitadel.v1.ObjectDetails | - | |
### UpdateCustomLoginPolicyRequest
@ -7159,29 +7180,6 @@ This is an empty request
### UpdateCustomPasswordLockoutPolicyRequest
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| max_attempts | uint32 | - | |
| show_lockout_failure | bool | - | |
### UpdateCustomPasswordLockoutPolicyResponse
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| details | zitadel.v1.ObjectDetails | - | |
### UpdateCustomPrivacyPolicyRequest

@ -36,6 +36,19 @@ title: zitadel/policy.proto
### LockoutPolicy
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| details | zitadel.v1.ObjectDetails | - | |
| max_password_attempts | uint64 | - | |
| is_default | bool | - | |
### LoginPolicy
@ -98,20 +111,6 @@ title: zitadel/policy.proto
### PasswordLockoutPolicy
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| details | zitadel.v1.ObjectDetails | - | |
| max_attempts | uint64 | - | |
| show_lockout_failure | bool | - | |
| is_default | bool | - | |
### PrivacyPolicy

@ -37,6 +37,16 @@ The Login Policy defines how the login process should look like and which authen
![Login Policy](/img/manuals/policies/console_org_login.png)
## Lockout Policy
Define when a user should be blocked.
The following properties are possible:
- Maximum Password Attempts: When the user has reached the maximum password attempts the user will be locked
If a user is locked, an administrator has to unlock it in the ZITADEL console
### Multifactors / Second Factors
In the multifactors section you can configure what kind of multifactors should be allowed. For passwordless to work, it's required to enable U2F (Universial Second Factor) with PIN. There is no other option at the moment.

@ -282,30 +282,30 @@ func (repo *IAMRepository) GetDefaultPasswordAgePolicy(ctx context.Context) (*ia
return iam_es_model.PasswordAgeViewToModel(policy), nil
}
func (repo *IAMRepository) GetDefaultPasswordLockoutPolicy(ctx context.Context) (*iam_model.PasswordLockoutPolicyView, error) {
policy, viewErr := repo.View.PasswordLockoutPolicyByAggregateID(repo.SystemDefaults.IamID)
func (repo *IAMRepository) GetDefaultLockoutPolicy(ctx context.Context) (*iam_model.LockoutPolicyView, error) {
policy, viewErr := repo.View.LockoutPolicyByAggregateID(repo.SystemDefaults.IamID)
if viewErr != nil && !caos_errs.IsNotFound(viewErr) {
return nil, viewErr
}
if caos_errs.IsNotFound(viewErr) {
policy = new(iam_es_model.PasswordLockoutPolicyView)
policy = new(iam_es_model.LockoutPolicyView)
}
events, esErr := repo.getIAMEvents(ctx, policy.Sequence)
if caos_errs.IsNotFound(viewErr) && len(events) == 0 {
return nil, caos_errs.ThrowNotFound(nil, "EVENT-2M9oP", "Errors.IAM.PasswordLockoutPolicy.NotFound")
return nil, caos_errs.ThrowNotFound(nil, "EVENT-2M9oP", "Errors.IAM.LockoutPolicy.NotFound")
}
if esErr != nil {
logging.Log("EVENT-3M0xs").WithError(esErr).Debug("error retrieving new events")
return iam_es_model.PasswordLockoutViewToModel(policy), nil
return iam_es_model.LockoutViewToModel(policy), nil
}
policyCopy := *policy
for _, event := range events {
if err := policyCopy.AppendEvent(event); err != nil {
return iam_es_model.PasswordLockoutViewToModel(policy), nil
return iam_es_model.LockoutViewToModel(policy), nil
}
}
return iam_es_model.PasswordLockoutViewToModel(policy), nil
return iam_es_model.LockoutViewToModel(policy), nil
}
func (repo *IAMRepository) GetOrgIAMPolicy(ctx context.Context) (*iam_model.OrgIAMPolicyView, error) {

@ -3,6 +3,7 @@ package handler
import (
"time"
"github.com/caos/zitadel/internal/command"
"github.com/caos/zitadel/internal/eventstore/v1"
"github.com/caos/zitadel/internal/static"
@ -31,7 +32,7 @@ func (h *handler) Eventstore() v1.Eventstore {
return h.es
}
func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es v1.Eventstore, defaults systemdefaults.SystemDefaults, static static.Storage, localDevMode bool) []query.Handler {
func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es v1.Eventstore, defaults systemdefaults.SystemDefaults, command *command.Commands, static static.Storage, localDevMode bool) []query.Handler {
handlers := []query.Handler{
newOrg(
handler{view, bulkLimit, configs.cycleDuration("Org"), errorCount, es}),
@ -53,8 +54,8 @@ func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es
handler{view, bulkLimit, configs.cycleDuration("PasswordComplexityPolicy"), errorCount, es}),
newPasswordAgePolicy(
handler{view, bulkLimit, configs.cycleDuration("PasswordAgePolicy"), errorCount, es}),
newPasswordLockoutPolicy(
handler{view, bulkLimit, configs.cycleDuration("PasswordLockoutPolicy"), errorCount, es}),
newLockoutPolicy(
handler{view, bulkLimit, configs.cycleDuration("LockoutPolicy"), errorCount, es}),
newOrgIAMPolicy(
handler{view, bulkLimit, configs.cycleDuration("OrgIAMPolicy"), errorCount, es}),
newExternalIDP(

@ -13,16 +13,16 @@ import (
)
const (
passwordLockoutPolicyTable = "adminapi.password_lockout_policies"
lockoutPolicyTable = "adminapi.lockout_policies"
)
type PasswordLockoutPolicy struct {
type LockoutPolicy struct {
handler
subscription *v1.Subscription
}
func newPasswordLockoutPolicy(handler handler) *PasswordLockoutPolicy {
h := &PasswordLockoutPolicy{
func newLockoutPolicy(handler handler) *LockoutPolicy {
h := &LockoutPolicy{
handler: handler,
}
@ -31,7 +31,7 @@ func newPasswordLockoutPolicy(handler handler) *PasswordLockoutPolicy {
return h
}
func (p *PasswordLockoutPolicy) subscribe() {
func (p *LockoutPolicy) subscribe() {
p.subscription = p.es.Subscribe(p.AggregateTypes()...)
go func() {
for event := range p.subscription.Events {
@ -40,28 +40,28 @@ func (p *PasswordLockoutPolicy) subscribe() {
}()
}
func (p *PasswordLockoutPolicy) ViewModel() string {
return passwordLockoutPolicyTable
func (p *LockoutPolicy) ViewModel() string {
return lockoutPolicyTable
}
func (m *PasswordLockoutPolicy) Subscription() *v1.Subscription {
func (m *LockoutPolicy) Subscription() *v1.Subscription {
return m.subscription
}
func (p *PasswordLockoutPolicy) AggregateTypes() []es_models.AggregateType {
func (p *LockoutPolicy) AggregateTypes() []es_models.AggregateType {
return []es_models.AggregateType{model.OrgAggregate, iam_es_model.IAMAggregate}
}
func (p *PasswordLockoutPolicy) CurrentSequence() (uint64, error) {
sequence, err := p.view.GetLatestPasswordLockoutPolicySequence()
func (p *LockoutPolicy) CurrentSequence() (uint64, error) {
sequence, err := p.view.GetLatestLockoutPolicySequence()
if err != nil {
return 0, err
}
return sequence.CurrentSequence, nil
}
func (p *PasswordLockoutPolicy) EventQuery() (*es_models.SearchQuery, error) {
sequence, err := p.view.GetLatestPasswordLockoutPolicySequence()
func (p *LockoutPolicy) EventQuery() (*es_models.SearchQuery, error) {
sequence, err := p.view.GetLatestLockoutPolicySequence()
if err != nil {
return nil, err
}
@ -70,41 +70,41 @@ func (p *PasswordLockoutPolicy) EventQuery() (*es_models.SearchQuery, error) {
LatestSequenceFilter(sequence.CurrentSequence), nil
}
func (p *PasswordLockoutPolicy) Reduce(event *es_models.Event) (err error) {
func (p *LockoutPolicy) Reduce(event *es_models.Event) (err error) {
switch event.AggregateType {
case model.OrgAggregate, iam_es_model.IAMAggregate:
err = p.processPasswordLockoutPolicy(event)
err = p.processLockoutPolicy(event)
}
return err
}
func (p *PasswordLockoutPolicy) processPasswordLockoutPolicy(event *es_models.Event) (err error) {
policy := new(iam_model.PasswordLockoutPolicyView)
func (p *LockoutPolicy) processLockoutPolicy(event *es_models.Event) (err error) {
policy := new(iam_model.LockoutPolicyView)
switch event.Type {
case iam_es_model.PasswordLockoutPolicyAdded, model.PasswordLockoutPolicyAdded:
case iam_es_model.LockoutPolicyAdded, model.LockoutPolicyAdded:
err = policy.AppendEvent(event)
case iam_es_model.PasswordLockoutPolicyChanged, model.PasswordLockoutPolicyChanged:
policy, err = p.view.PasswordLockoutPolicyByAggregateID(event.AggregateID)
case iam_es_model.LockoutPolicyChanged, model.LockoutPolicyChanged:
policy, err = p.view.LockoutPolicyByAggregateID(event.AggregateID)
if err != nil {
return err
}
err = policy.AppendEvent(event)
case model.PasswordLockoutPolicyRemoved:
return p.view.DeletePasswordLockoutPolicy(event.AggregateID, event)
case model.LockoutPolicyRemoved:
return p.view.DeleteLockoutPolicy(event.AggregateID, event)
default:
return p.view.ProcessedPasswordLockoutPolicySequence(event)
return p.view.ProcessedLockoutPolicySequence(event)
}
if err != nil {
return err
}
return p.view.PutPasswordLockoutPolicy(policy, event)
return p.view.PutLockoutPolicy(policy, event)
}
func (p *PasswordLockoutPolicy) OnError(event *es_models.Event, err error) error {
logging.LogWithFields("SPOOL-nD8sie", "id", event.AggregateID).WithError(err).Warn("something went wrong in passwordLockout policy handler")
return spooler.HandleError(event, err, p.view.GetLatestPasswordLockoutPolicyFailedEvent, p.view.ProcessedPasswordLockoutPolicyFailedEvent, p.view.ProcessedPasswordLockoutPolicySequence, p.errorCountUntilSkip)
func (p *LockoutPolicy) OnError(event *es_models.Event, err error) error {
logging.LogWithFields("SPOOL-nD8sie", "id", event.AggregateID).WithError(err).Warn("something went wrong in Lockout policy handler")
return spooler.HandleError(event, err, p.view.GetLatestLockoutPolicyFailedEvent, p.view.ProcessedLockoutPolicyFailedEvent, p.view.ProcessedLockoutPolicySequence, p.errorCountUntilSkip)
}
func (p *PasswordLockoutPolicy) OnSuccess() error {
return spooler.HandleSuccess(p.view.UpdatePasswordLockoutPolicySpoolerRunTimestamp)
func (p *LockoutPolicy) OnSuccess() error {
return spooler.HandleSuccess(p.view.UpdateLockoutPolicySpoolerRunTimestamp)
}

@ -9,6 +9,7 @@ import (
"github.com/caos/zitadel/internal/admin/repository/eventsourcing/eventstore"
"github.com/caos/zitadel/internal/admin/repository/eventsourcing/spooler"
admin_view "github.com/caos/zitadel/internal/admin/repository/eventsourcing/view"
"github.com/caos/zitadel/internal/command"
sd "github.com/caos/zitadel/internal/config/systemdefaults"
"github.com/caos/zitadel/internal/config/types"
"github.com/caos/zitadel/internal/eventstore/v1"
@ -34,7 +35,7 @@ type EsRepository struct {
eventstore.UserRepo
}
func Start(ctx context.Context, conf Config, systemDefaults sd.SystemDefaults, static static.Storage, roles []string, localDevMode bool) (*EsRepository, error) {
func Start(ctx context.Context, conf Config, systemDefaults sd.SystemDefaults, command *command.Commands, static static.Storage, roles []string, localDevMode bool) (*EsRepository, error) {
es, err := v1.Start(conf.Eventstore)
if err != nil {
return nil, err
@ -48,7 +49,7 @@ func Start(ctx context.Context, conf Config, systemDefaults sd.SystemDefaults, s
return nil, err
}
spool := spooler.StartSpooler(conf.Spooler, es, view, sqlClient, systemDefaults, static, localDevMode)
spool := spooler.StartSpooler(conf.Spooler, es, view, sqlClient, systemDefaults, command, static, localDevMode)
assetsAPI := conf.APIDomain + "/assets/v1/"
statikLoginFS, err := fs.NewWithNamespace("login")

@ -2,6 +2,7 @@ package spooler
import (
"database/sql"
"github.com/caos/zitadel/internal/command"
"github.com/caos/zitadel/internal/config/systemdefaults"
"github.com/caos/zitadel/internal/eventstore/v1"
"github.com/caos/zitadel/internal/static"
@ -18,12 +19,12 @@ type SpoolerConfig struct {
Handlers handler.Configs
}
func StartSpooler(c SpoolerConfig, es v1.Eventstore, view *view.View, sql *sql.DB, defaults systemdefaults.SystemDefaults, static static.Storage, localDevMode bool) *spooler.Spooler {
func StartSpooler(c SpoolerConfig, es v1.Eventstore, view *view.View, sql *sql.DB, defaults systemdefaults.SystemDefaults, command *command.Commands, static static.Storage, localDevMode bool) *spooler.Spooler {
spoolerConfig := spooler.Config{
Eventstore: es,
Locker: &locker{dbClient: sql},
ConcurrentWorkers: c.ConcurrentWorkers,
ViewHandlers: handler.Register(c.Handlers, c.BulkLimit, c.FailureCountUntilSkip, view, es, defaults, static, localDevMode),
ViewHandlers: handler.Register(c.Handlers, c.BulkLimit, c.FailureCountUntilSkip, view, es, defaults, command, static, localDevMode),
}
spool := spoolerConfig.New()
spool.Start()

@ -0,0 +1,53 @@
package view
import (
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/iam/repository/view"
"github.com/caos/zitadel/internal/iam/repository/view/model"
global_view "github.com/caos/zitadel/internal/view/repository"
)
const (
lockoutPolicyTable = "adminapi.lockout_policies"
)
func (v *View) LockoutPolicyByAggregateID(aggregateID string) (*model.LockoutPolicyView, error) {
return view.GetLockoutPolicyByAggregateID(v.Db, lockoutPolicyTable, aggregateID)
}
func (v *View) PutLockoutPolicy(policy *model.LockoutPolicyView, event *models.Event) error {
err := view.PutLockoutPolicy(v.Db, lockoutPolicyTable, policy)
if err != nil {
return err
}
return v.ProcessedLockoutPolicySequence(event)
}
func (v *View) DeleteLockoutPolicy(aggregateID string, event *models.Event) error {
err := view.DeleteLockoutPolicy(v.Db, lockoutPolicyTable, aggregateID)
if err != nil && !errors.IsNotFound(err) {
return err
}
return v.ProcessedLockoutPolicySequence(event)
}
func (v *View) GetLatestLockoutPolicySequence() (*global_view.CurrentSequence, error) {
return v.latestSequence(lockoutPolicyTable)
}
func (v *View) ProcessedLockoutPolicySequence(event *models.Event) error {
return v.saveCurrentSequence(lockoutPolicyTable, event)
}
func (v *View) UpdateLockoutPolicySpoolerRunTimestamp() error {
return v.updateSpoolerRunSequence(lockoutPolicyTable)
}
func (v *View) GetLatestLockoutPolicyFailedEvent(sequence uint64) (*global_view.FailedEvent, error) {
return v.latestFailedEvent(lockoutPolicyTable, sequence)
}
func (v *View) ProcessedLockoutPolicyFailedEvent(failedEvent *global_view.FailedEvent) error {
return v.saveFailedEvent(failedEvent)
}

@ -1,53 +0,0 @@
package view
import (
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/iam/repository/view"
"github.com/caos/zitadel/internal/iam/repository/view/model"
global_view "github.com/caos/zitadel/internal/view/repository"
)
const (
passwordLockoutPolicyTable = "adminapi.password_lockout_policies"
)
func (v *View) PasswordLockoutPolicyByAggregateID(aggregateID string) (*model.PasswordLockoutPolicyView, error) {
return view.GetPasswordLockoutPolicyByAggregateID(v.Db, passwordLockoutPolicyTable, aggregateID)
}
func (v *View) PutPasswordLockoutPolicy(policy *model.PasswordLockoutPolicyView, event *models.Event) error {
err := view.PutPasswordLockoutPolicy(v.Db, passwordLockoutPolicyTable, policy)
if err != nil {
return err
}
return v.ProcessedPasswordLockoutPolicySequence(event)
}
func (v *View) DeletePasswordLockoutPolicy(aggregateID string, event *models.Event) error {
err := view.DeletePasswordLockoutPolicy(v.Db, passwordLockoutPolicyTable, aggregateID)
if err != nil && !errors.IsNotFound(err) {
return err
}
return v.ProcessedPasswordLockoutPolicySequence(event)
}
func (v *View) GetLatestPasswordLockoutPolicySequence() (*global_view.CurrentSequence, error) {
return v.latestSequence(passwordLockoutPolicyTable)
}
func (v *View) ProcessedPasswordLockoutPolicySequence(event *models.Event) error {
return v.saveCurrentSequence(passwordLockoutPolicyTable, event)
}
func (v *View) UpdatePasswordLockoutPolicySpoolerRunTimestamp() error {
return v.updateSpoolerRunSequence(passwordLockoutPolicyTable)
}
func (v *View) GetLatestPasswordLockoutPolicyFailedEvent(sequence uint64) (*global_view.FailedEvent, error) {
return v.latestFailedEvent(passwordLockoutPolicyTable, sequence)
}
func (v *View) ProcessedPasswordLockoutPolicyFailedEvent(failedEvent *global_view.FailedEvent) error {
return v.saveFailedEvent(failedEvent)
}

@ -43,7 +43,7 @@ type IAMRepository interface {
GetDefaultPasswordAgePolicy(ctx context.Context) (*iam_model.PasswordAgePolicyView, error)
GetDefaultPasswordLockoutPolicy(ctx context.Context) (*iam_model.PasswordLockoutPolicyView, error)
GetDefaultLockoutPolicy(ctx context.Context) (*iam_model.LockoutPolicyView, error)
GetDefaultPrivacyPolicy(ctx context.Context) (*iam_model.PrivacyPolicyView, error)

@ -0,0 +1,31 @@
package admin
import (
"context"
"github.com/caos/zitadel/internal/api/grpc/object"
policy_grpc "github.com/caos/zitadel/internal/api/grpc/policy"
admin_pb "github.com/caos/zitadel/pkg/grpc/admin"
)
func (s *Server) GetLockoutPolicy(ctx context.Context, req *admin_pb.GetLockoutPolicyRequest) (*admin_pb.GetLockoutPolicyResponse, error) {
policy, err := s.iam.GetDefaultLockoutPolicy(ctx)
if err != nil {
return nil, err
}
return &admin_pb.GetLockoutPolicyResponse{Policy: policy_grpc.ModelLockoutPolicyToPb(policy)}, nil
}
func (s *Server) UpdateLockoutPolicy(ctx context.Context, req *admin_pb.UpdateLockoutPolicyRequest) (*admin_pb.UpdateLockoutPolicyResponse, error) {
policy, err := s.command.ChangeDefaultLockoutPolicy(ctx, UpdateLockoutPolicyToDomain(req))
if err != nil {
return nil, err
}
return &admin_pb.UpdateLockoutPolicyResponse{
Details: object.ChangeToDetailsPb(
policy.Sequence,
policy.ChangeDate,
policy.ResourceOwner,
),
}, nil
}

@ -0,0 +1,12 @@
package admin
import (
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/pkg/grpc/admin"
)
func UpdateLockoutPolicyToDomain(p *admin.UpdateLockoutPolicyRequest) *domain.LockoutPolicy {
return &domain.LockoutPolicy{
MaxPasswordAttempts: uint64(p.MaxPasswordAttempts),
}
}

@ -1,31 +0,0 @@
package admin
import (
"context"
"github.com/caos/zitadel/internal/api/grpc/object"
policy_grpc "github.com/caos/zitadel/internal/api/grpc/policy"
admin_pb "github.com/caos/zitadel/pkg/grpc/admin"
)
func (s *Server) GetPasswordLockoutPolicy(ctx context.Context, req *admin_pb.GetPasswordLockoutPolicyRequest) (*admin_pb.GetPasswordLockoutPolicyResponse, error) {
policy, err := s.iam.GetDefaultPasswordLockoutPolicy(ctx)
if err != nil {
return nil, err
}
return &admin_pb.GetPasswordLockoutPolicyResponse{Policy: policy_grpc.ModelPasswordLockoutPolicyToPb(policy)}, nil
}
func (s *Server) UpdatePasswordLockoutPolicy(ctx context.Context, req *admin_pb.UpdatePasswordLockoutPolicyRequest) (*admin_pb.UpdatePasswordLockoutPolicyResponse, error) {
policy, err := s.command.ChangeDefaultPasswordLockoutPolicy(ctx, UpdatePasswordLockoutPolicyToDomain(req))
if err != nil {
return nil, err
}
return &admin_pb.UpdatePasswordLockoutPolicyResponse{
Details: object.ChangeToDetailsPb(
policy.Sequence,
policy.ChangeDate,
policy.ResourceOwner,
),
}, nil
}

@ -1,13 +0,0 @@
package admin
import (
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/pkg/grpc/admin"
)
func UpdatePasswordLockoutPolicyToDomain(p *admin.UpdatePasswordLockoutPolicyRequest) *domain.PasswordLockoutPolicy {
return &domain.PasswordLockoutPolicy{
MaxAttempts: uint64(p.MaxAttempts),
ShowLockOutFailures: p.ShowLockoutFailure,
}
}

@ -0,0 +1,63 @@
package management
import (
"context"
"github.com/caos/zitadel/internal/api/authz"
"github.com/caos/zitadel/internal/api/grpc/object"
policy_grpc "github.com/caos/zitadel/internal/api/grpc/policy"
mgmt_pb "github.com/caos/zitadel/pkg/grpc/management"
)
func (s *Server) GetLockoutPolicy(ctx context.Context, req *mgmt_pb.GetLockoutPolicyRequest) (*mgmt_pb.GetLockoutPolicyResponse, error) {
policy, err := s.org.GetLockoutPolicy(ctx)
if err != nil {
return nil, err
}
return &mgmt_pb.GetLockoutPolicyResponse{Policy: policy_grpc.ModelLockoutPolicyToPb(policy), IsDefault: policy.Default}, nil
}
func (s *Server) GetDefaultLockoutPolicy(ctx context.Context, req *mgmt_pb.GetDefaultLockoutPolicyRequest) (*mgmt_pb.GetDefaultLockoutPolicyResponse, error) {
policy, err := s.org.GetDefaultLockoutPolicy(ctx)
if err != nil {
return nil, err
}
return &mgmt_pb.GetDefaultLockoutPolicyResponse{Policy: policy_grpc.ModelLockoutPolicyToPb(policy)}, nil
}
func (s *Server) AddCustomLockoutPolicy(ctx context.Context, req *mgmt_pb.AddCustomLockoutPolicyRequest) (*mgmt_pb.AddCustomLockoutPolicyResponse, error) {
policy, err := s.command.AddLockoutPolicy(ctx, authz.GetCtxData(ctx).OrgID, AddLockoutPolicyToDomain(req))
if err != nil {
return nil, err
}
return &mgmt_pb.AddCustomLockoutPolicyResponse{
Details: object.AddToDetailsPb(
policy.Sequence,
policy.ChangeDate,
policy.ResourceOwner,
),
}, nil
}
func (s *Server) UpdateCustomLockoutPolicy(ctx context.Context, req *mgmt_pb.UpdateCustomLockoutPolicyRequest) (*mgmt_pb.UpdateCustomLockoutPolicyResponse, error) {
policy, err := s.command.ChangeLockoutPolicy(ctx, authz.GetCtxData(ctx).OrgID, UpdateLockoutPolicyToDomain(req))
if err != nil {
return nil, err
}
return &mgmt_pb.UpdateCustomLockoutPolicyResponse{
Details: object.ChangeToDetailsPb(
policy.Sequence,
policy.ChangeDate,
policy.ResourceOwner,
),
}, nil
}
func (s *Server) ResetLockoutPolicyToDefault(ctx context.Context, req *mgmt_pb.ResetLockoutPolicyToDefaultRequest) (*mgmt_pb.ResetLockoutPolicyToDefaultResponse, error) {
objectDetails, err := s.command.RemovePasswordComplexityPolicy(ctx, authz.GetCtxData(ctx).OrgID)
if err != nil {
return nil, err
}
return &mgmt_pb.ResetLockoutPolicyToDefaultResponse{
Details: object.DomainToChangeDetailsPb(objectDetails),
}, nil
}

@ -0,0 +1,18 @@
package management
import (
"github.com/caos/zitadel/internal/domain"
mgmt "github.com/caos/zitadel/pkg/grpc/management"
)
func AddLockoutPolicyToDomain(p *mgmt.AddCustomLockoutPolicyRequest) *domain.LockoutPolicy {
return &domain.LockoutPolicy{
MaxPasswordAttempts: uint64(p.MaxPasswordAttempts),
}
}
func UpdateLockoutPolicyToDomain(p *mgmt.UpdateCustomLockoutPolicyRequest) *domain.LockoutPolicy {
return &domain.LockoutPolicy{
MaxPasswordAttempts: uint64(p.MaxPasswordAttempts),
}
}

@ -1,63 +0,0 @@
package management
import (
"context"
"github.com/caos/zitadel/internal/api/authz"
"github.com/caos/zitadel/internal/api/grpc/object"
policy_grpc "github.com/caos/zitadel/internal/api/grpc/policy"
mgmt_pb "github.com/caos/zitadel/pkg/grpc/management"
)
func (s *Server) GetPasswordLockoutPolicy(ctx context.Context, req *mgmt_pb.GetPasswordLockoutPolicyRequest) (*mgmt_pb.GetPasswordLockoutPolicyResponse, error) {
policy, err := s.org.GetPasswordLockoutPolicy(ctx)
if err != nil {
return nil, err
}
return &mgmt_pb.GetPasswordLockoutPolicyResponse{Policy: policy_grpc.ModelPasswordLockoutPolicyToPb(policy), IsDefault: policy.Default}, nil
}
func (s *Server) GetDefaultPasswordLockoutPolicy(ctx context.Context, req *mgmt_pb.GetDefaultPasswordLockoutPolicyRequest) (*mgmt_pb.GetDefaultPasswordLockoutPolicyResponse, error) {
policy, err := s.org.GetDefaultPasswordLockoutPolicy(ctx)
if err != nil {
return nil, err
}
return &mgmt_pb.GetDefaultPasswordLockoutPolicyResponse{Policy: policy_grpc.ModelPasswordLockoutPolicyToPb(policy)}, nil
}
func (s *Server) AddCustomPasswordLockoutPolicy(ctx context.Context, req *mgmt_pb.AddCustomPasswordLockoutPolicyRequest) (*mgmt_pb.AddCustomPasswordLockoutPolicyResponse, error) {
policy, err := s.command.AddPasswordLockoutPolicy(ctx, authz.GetCtxData(ctx).OrgID, AddPasswordLockoutPolicyToDomain(req))
if err != nil {
return nil, err
}
return &mgmt_pb.AddCustomPasswordLockoutPolicyResponse{
Details: object.AddToDetailsPb(
policy.Sequence,
policy.ChangeDate,
policy.ResourceOwner,
),
}, nil
}
func (s *Server) UpdateCustomPasswordLockoutPolicy(ctx context.Context, req *mgmt_pb.UpdateCustomPasswordLockoutPolicyRequest) (*mgmt_pb.UpdateCustomPasswordLockoutPolicyResponse, error) {
policy, err := s.command.ChangePasswordLockoutPolicy(ctx, authz.GetCtxData(ctx).OrgID, UpdatePasswordLockoutPolicyToDomain(req))
if err != nil {
return nil, err
}
return &mgmt_pb.UpdateCustomPasswordLockoutPolicyResponse{
Details: object.ChangeToDetailsPb(
policy.Sequence,
policy.ChangeDate,
policy.ResourceOwner,
),
}, nil
}
func (s *Server) ResetPasswordLockoutPolicyToDefault(ctx context.Context, req *mgmt_pb.ResetPasswordLockoutPolicyToDefaultRequest) (*mgmt_pb.ResetPasswordLockoutPolicyToDefaultResponse, error) {
objectDetails, err := s.command.RemovePasswordComplexityPolicy(ctx, authz.GetCtxData(ctx).OrgID)
if err != nil {
return nil, err
}
return &mgmt_pb.ResetPasswordLockoutPolicyToDefaultResponse{
Details: object.DomainToChangeDetailsPb(objectDetails),
}, nil
}

@ -1,20 +0,0 @@
package management
import (
"github.com/caos/zitadel/internal/domain"
mgmt "github.com/caos/zitadel/pkg/grpc/management"
)
func AddPasswordLockoutPolicyToDomain(p *mgmt.AddCustomPasswordLockoutPolicyRequest) *domain.PasswordLockoutPolicy {
return &domain.PasswordLockoutPolicy{
MaxAttempts: uint64(p.MaxAttempts),
ShowLockOutFailures: p.ShowLockoutFailure,
}
}
func UpdatePasswordLockoutPolicyToDomain(p *mgmt.UpdateCustomPasswordLockoutPolicyRequest) *domain.PasswordLockoutPolicy {
return &domain.PasswordLockoutPolicy{
MaxAttempts: uint64(p.MaxAttempts),
ShowLockOutFailures: p.ShowLockoutFailure,
}
}

@ -6,11 +6,10 @@ import (
policy_pb "github.com/caos/zitadel/pkg/grpc/policy"
)
func ModelPasswordLockoutPolicyToPb(policy *model.PasswordLockoutPolicyView) *policy_pb.PasswordLockoutPolicy {
return &policy_pb.PasswordLockoutPolicy{
IsDefault: policy.Default,
MaxAttempts: policy.MaxAttempts,
ShowLockoutFailure: policy.ShowLockOutFailures,
func ModelLockoutPolicyToPb(policy *model.LockoutPolicyView) *policy_pb.LockoutPolicy {
return &policy_pb.LockoutPolicy{
IsDefault: policy.Default,
MaxPasswordAttempts: policy.MaxPasswordAttempts,
Details: object.ToViewDetailsPb(
policy.Sequence,
policy.CreationDate,

@ -35,14 +35,15 @@ type AuthRequestRepo struct {
View *view.View
Eventstore v1.Eventstore
UserSessionViewProvider userSessionViewProvider
UserViewProvider userViewProvider
UserCommandProvider userCommandProvider
UserEventProvider userEventProvider
OrgViewProvider orgViewProvider
LoginPolicyViewProvider loginPolicyViewProvider
IDPProviderViewProvider idpProviderViewProvider
UserGrantProvider userGrantProvider
UserSessionViewProvider userSessionViewProvider
UserViewProvider userViewProvider
UserCommandProvider userCommandProvider
UserEventProvider userEventProvider
OrgViewProvider orgViewProvider
LoginPolicyViewProvider loginPolicyViewProvider
LockoutPolicyViewProvider lockoutPolicyViewProvider
IDPProviderViewProvider idpProviderViewProvider
UserGrantProvider userGrantProvider
IdGenerator id.Generator
@ -69,6 +70,10 @@ type loginPolicyViewProvider interface {
LoginPolicyByAggregateID(string) (*iam_view_model.LoginPolicyView, error)
}
type lockoutPolicyViewProvider interface {
LockoutPolicyByAggregateID(string) (*iam_view_model.LockoutPolicyView, error)
}
type idpProviderViewProvider interface {
IDPProvidersByAggregateIDAndState(string, iam_model.IDPConfigState) ([]*iam_view_model.IDPProviderView, error)
}
@ -240,7 +245,7 @@ func (repo *AuthRequestRepo) SelectUser(ctx context.Context, id, userID, userAge
if err != nil {
return err
}
user, err := activeUserByID(ctx, repo.UserViewProvider, repo.UserEventProvider, repo.OrgViewProvider, userID)
user, err := activeUserByID(ctx, repo.UserViewProvider, repo.UserEventProvider, repo.OrgViewProvider, repo.LockoutPolicyViewProvider, userID)
if err != nil {
return err
}
@ -262,7 +267,11 @@ func (repo *AuthRequestRepo) VerifyPassword(ctx context.Context, id, userID, res
if err != nil {
return err
}
return repo.Command.HumanCheckPassword(ctx, resourceOwner, userID, password, request.WithCurrentInfo(info))
policy, err := repo.getLockoutPolicy(ctx, resourceOwner)
if err != nil {
return err
}
return repo.Command.HumanCheckPassword(ctx, resourceOwner, userID, password, request.WithCurrentInfo(info), policy)
}
func (repo *AuthRequestRepo) VerifyMFAOTP(ctx context.Context, authRequestID, userID, resourceOwner, code, userAgentID string, info *domain.BrowserInfo) (err error) {
@ -414,6 +423,10 @@ func (repo *AuthRequestRepo) getAuthRequestEnsureUser(ctx context.Context, authR
if request.UserID != userID {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-GBH32", "Errors.User.NotMatchingUserID")
}
_, err = activeUserByID(ctx, repo.UserViewProvider, repo.UserEventProvider, repo.OrgViewProvider, repo.LockoutPolicyViewProvider, request.UserID)
if err != nil {
return nil, err
}
return request, nil
}
@ -466,6 +479,11 @@ func (repo *AuthRequestRepo) fillPolicies(ctx context.Context, request *domain.A
if idpProviders != nil {
request.AllowedExternalIDPs = idpProviders
}
lockoutPolicy, err := repo.getLockoutPolicy(ctx, orgID)
if err != nil {
return err
}
request.LockoutPolicy = lockoutPolicy
privacyPolicy, err := repo.getPrivacyPolicy(ctx, orgID)
if err != nil {
return err
@ -587,7 +605,7 @@ func (repo *AuthRequestRepo) nextSteps(ctx context.Context, request *domain.Auth
}
return steps, nil
}
user, err := activeUserByID(ctx, repo.UserViewProvider, repo.UserEventProvider, repo.OrgViewProvider, request.UserID)
user, err := activeUserByID(ctx, repo.UserViewProvider, repo.UserEventProvider, repo.OrgViewProvider, repo.LockoutPolicyViewProvider, request.UserID)
if err != nil {
return nil, err
}
@ -795,6 +813,21 @@ func (repo *AuthRequestRepo) getPrivacyPolicy(ctx context.Context, orgID string)
return policy.ToDomain(), err
}
func (repo *AuthRequestRepo) getLockoutPolicy(ctx context.Context, orgID string) (*domain.LockoutPolicy, error) {
policy, err := repo.View.LockoutPolicyByAggregateID(orgID)
if errors.IsNotFound(err) {
policy, err = repo.View.LockoutPolicyByAggregateID(repo.IAMID)
if err != nil {
return nil, err
}
policy.Default = true
}
if err != nil {
return nil, err
}
return policy.ToDomain(), err
}
func (repo *AuthRequestRepo) getLabelPolicy(ctx context.Context, orgID string) (*domain.LabelPolicy, error) {
policy, err := repo.View.LabelPolicyByAggregateIDAndState(orgID, int32(domain.LabelPolicyStateActive))
if errors.IsNotFound(err) {
@ -921,7 +954,8 @@ func userSessionByIDs(ctx context.Context, provider userSessionViewProvider, eve
return user_view_model.UserSessionToModel(&sessionCopy, provider.PrefixAvatarURL()), nil
}
func activeUserByID(ctx context.Context, userViewProvider userViewProvider, userEventProvider userEventProvider, orgViewProvider orgViewProvider, userID string) (*user_model.UserView, error) {
func activeUserByID(ctx context.Context, userViewProvider userViewProvider, userEventProvider userEventProvider, orgViewProvider orgViewProvider, lockoutPolicyProvider lockoutPolicyViewProvider, userID string) (*user_model.UserView, error) {
// PLANNED: Check LockoutPolicy
user, err := userByID(ctx, userViewProvider, userEventProvider, userID)
if err != nil {
return nil, err
@ -930,7 +964,6 @@ func activeUserByID(ctx context.Context, userViewProvider userViewProvider, user
if user.HumanView == nil {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-Lm69x", "Errors.User.NotHuman")
}
if user.State == user_model.UserStateLocked || user.State == user_model.UserStateSuspend {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-FJ262", "Errors.User.Locked")
}

@ -152,6 +152,14 @@ func (m *mockLoginPolicy) LoginPolicyByAggregateID(id string) (*iam_view_model.L
return m.policy, nil
}
type mockLockoutPolicy struct {
policy *iam_view_model.LockoutPolicyView
}
func (m *mockLockoutPolicy) LockoutPolicyByAggregateID(id string) (*iam_view_model.LockoutPolicyView, error) {
return m.policy, nil
}
func (m *mockViewUser) UserByID(string) (*user_view_model.UserView, error) {
return &user_view_model.UserView{
State: int32(user_model.UserStateActive),
@ -229,6 +237,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
orgViewProvider orgViewProvider
userGrantProvider userGrantProvider
loginPolicyProvider loginPolicyViewProvider
lockoutPolicyProvider lockoutPolicyViewProvider
PasswordCheckLifeTime time.Duration
ExternalLoginCheckLifeTime time.Duration
MFAInitSkippedLifeTime time.Duration
@ -404,6 +413,11 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
},
},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
lockoutPolicyProvider: &mockLockoutPolicy{
policy: &iam_view_model.LockoutPolicyView{
ShowLockOutFailures: true,
},
},
},
args{&domain.AuthRequest{UserID: "UserID"}, false},
nil,
@ -420,6 +434,11 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
},
},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
lockoutPolicyProvider: &mockLockoutPolicy{
policy: &iam_view_model.LockoutPolicyView{
ShowLockOutFailures: true,
},
},
},
args{&domain.AuthRequest{UserID: "UserID"}, false},
nil,
@ -431,6 +450,11 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
userViewProvider: &mockViewUser{},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewErrOrg{},
lockoutPolicyProvider: &mockLockoutPolicy{
policy: &iam_view_model.LockoutPolicyView{
ShowLockOutFailures: true,
},
},
},
args{&domain.AuthRequest{UserID: "UserID"}, false},
nil,
@ -442,6 +466,11 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
userViewProvider: &mockViewUser{},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateInactive},
lockoutPolicyProvider: &mockLockoutPolicy{
policy: &iam_view_model.LockoutPolicyView{
ShowLockOutFailures: true,
},
},
},
args{&domain.AuthRequest{UserID: "UserID"}, false},
nil,
@ -456,6 +485,11 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
lockoutPolicyProvider: &mockLockoutPolicy{
policy: &iam_view_model.LockoutPolicyView{
ShowLockOutFailures: true,
},
},
},
args{&domain.AuthRequest{UserID: "UserID", LoginPolicy: &domain.LoginPolicy{}}, false},
[]domain.NextStep{&domain.PasswordStep{}},
@ -468,6 +502,11 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
userViewProvider: &mockViewUser{},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
lockoutPolicyProvider: &mockLockoutPolicy{
policy: &iam_view_model.LockoutPolicyView{
ShowLockOutFailures: true,
},
},
},
args{&domain.AuthRequest{UserID: "UserID"}, false},
nil,
@ -483,6 +522,11 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
lockoutPolicyProvider: &mockLockoutPolicy{
policy: &iam_view_model.LockoutPolicyView{
ShowLockOutFailures: true,
},
},
},
args{&domain.AuthRequest{UserID: "UserID"}, false},
[]domain.NextStep{&domain.InitUserStep{
@ -500,6 +544,11 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
MultiFactorCheckLifeTime: 10 * time.Hour,
lockoutPolicyProvider: &mockLockoutPolicy{
policy: &iam_view_model.LockoutPolicyView{
ShowLockOutFailures: true,
},
},
},
args{&domain.AuthRequest{UserID: "UserID", LoginPolicy: &domain.LoginPolicy{PasswordlessType: domain.PasswordlessTypeAllowed}}, false},
[]domain.NextStep{&domain.PasswordlessRegistrationPromptStep{}},
@ -512,8 +561,13 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
userViewProvider: &mockViewUser{
PasswordlessTokens: user_view_model.WebAuthNTokens{&user_view_model.WebAuthNView{ID: "id", State: int32(user_model.MFAStateReady)}},
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
lockoutPolicyProvider: &mockLockoutPolicy{
policy: &iam_view_model.LockoutPolicyView{
ShowLockOutFailures: true,
},
},
MultiFactorCheckLifeTime: 10 * time.Hour,
},
args{&domain.AuthRequest{UserID: "UserID", LoginPolicy: &domain.LoginPolicy{PasswordlessType: domain.PasswordlessTypeAllowed}}, false},
@ -528,8 +582,13 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
PasswordSet: true,
PasswordlessTokens: user_view_model.WebAuthNTokens{&user_view_model.WebAuthNView{ID: "id", State: int32(user_model.MFAStateReady)}},
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
lockoutPolicyProvider: &mockLockoutPolicy{
policy: &iam_view_model.LockoutPolicyView{
ShowLockOutFailures: true,
},
},
MultiFactorCheckLifeTime: 10 * time.Hour,
},
args{&domain.AuthRequest{UserID: "UserID", LoginPolicy: &domain.LoginPolicy{PasswordlessType: domain.PasswordlessTypeAllowed}}, false},
@ -550,7 +609,12 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
IsEmailVerified: false,
MFAMaxSetUp: int32(model.MFALevelMultiFactor),
},
userEventProvider: &mockEventUser{},
userEventProvider: &mockEventUser{},
lockoutPolicyProvider: &mockLockoutPolicy{
policy: &iam_view_model.LockoutPolicyView{
ShowLockOutFailures: true,
},
},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
MultiFactorCheckLifeTime: 10 * time.Hour,
},
@ -572,7 +636,12 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
PasswordInitRequired: true,
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
lockoutPolicyProvider: &mockLockoutPolicy{
policy: &iam_view_model.LockoutPolicyView{
ShowLockOutFailures: true,
},
},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
},
args{&domain.AuthRequest{UserID: "UserID", LoginPolicy: &domain.LoginPolicy{}}, false},
[]domain.NextStep{&domain.InitPasswordStep{}},
@ -588,7 +657,12 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
IsEmailVerified: true,
MFAMaxSetUp: int32(model.MFALevelSecondFactor),
},
userEventProvider: &mockEventUser{},
userEventProvider: &mockEventUser{},
lockoutPolicyProvider: &mockLockoutPolicy{
policy: &iam_view_model.LockoutPolicyView{
ShowLockOutFailures: true,
},
},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
SecondFactorCheckLifeTime: 18 * time.Hour,
},
@ -613,6 +687,11 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
loginPolicyProvider: &mockLoginPolicy{
policy: &iam_view_model.LoginPolicyView{},
},
lockoutPolicyProvider: &mockLockoutPolicy{
policy: &iam_view_model.LockoutPolicyView{
ShowLockOutFailures: true,
},
},
ExternalLoginCheckLifeTime: 10 * 24 * time.Hour,
SecondFactorCheckLifeTime: 18 * time.Hour,
},
@ -634,8 +713,13 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
userViewProvider: &mockViewUser{
PasswordSet: true,
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
lockoutPolicyProvider: &mockLockoutPolicy{
policy: &iam_view_model.LockoutPolicyView{
ShowLockOutFailures: true,
},
},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
},
args{&domain.AuthRequest{UserID: "UserID", LoginPolicy: &domain.LoginPolicy{}}, false},
@ -654,9 +738,14 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
IsEmailVerified: true,
MFAMaxSetUp: int32(model.MFALevelSecondFactor),
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
userGrantProvider: &mockUserGrants{},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
userGrantProvider: &mockUserGrants{},
lockoutPolicyProvider: &mockLockoutPolicy{
policy: &iam_view_model.LockoutPolicyView{
ShowLockOutFailures: true,
},
},
SecondFactorCheckLifeTime: 18 * time.Hour,
ExternalLoginCheckLifeTime: 10 * 24 * time.Hour,
},
@ -682,8 +771,13 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
OTPState: int32(user_model.MFAStateReady),
MFAMaxSetUp: int32(model.MFALevelMultiFactor),
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
lockoutPolicyProvider: &mockLockoutPolicy{
policy: &iam_view_model.LockoutPolicyView{
ShowLockOutFailures: true,
},
},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
SecondFactorCheckLifeTime: 18 * time.Hour,
},
@ -710,8 +804,13 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
OTPState: int32(user_model.MFAStateReady),
MFAMaxSetUp: int32(model.MFALevelSecondFactor),
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
lockoutPolicyProvider: &mockLockoutPolicy{
policy: &iam_view_model.LockoutPolicyView{
ShowLockOutFailures: true,
},
},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
SecondFactorCheckLifeTime: 18 * time.Hour,
},
@ -739,8 +838,13 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
OTPState: int32(user_model.MFAStateReady),
MFAMaxSetUp: int32(model.MFALevelSecondFactor),
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
lockoutPolicyProvider: &mockLockoutPolicy{
policy: &iam_view_model.LockoutPolicyView{
ShowLockOutFailures: true,
},
},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
ExternalLoginCheckLifeTime: 10 * 24 * time.Hour,
SecondFactorCheckLifeTime: 18 * time.Hour,
@ -771,8 +875,13 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
IsEmailVerified: true,
MFAMaxSetUp: int32(model.MFALevelSecondFactor),
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
lockoutPolicyProvider: &mockLockoutPolicy{
policy: &iam_view_model.LockoutPolicyView{
ShowLockOutFailures: true,
},
},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
SecondFactorCheckLifeTime: 18 * time.Hour,
},
@ -797,8 +906,13 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
PasswordSet: true,
MFAMaxSetUp: int32(model.MFALevelSecondFactor),
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
lockoutPolicyProvider: &mockLockoutPolicy{
policy: &iam_view_model.LockoutPolicyView{
ShowLockOutFailures: true,
},
},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
SecondFactorCheckLifeTime: 18 * time.Hour,
},
@ -823,8 +937,13 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
PasswordChangeRequired: true,
MFAMaxSetUp: int32(model.MFALevelSecondFactor),
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
lockoutPolicyProvider: &mockLockoutPolicy{
policy: &iam_view_model.LockoutPolicyView{
ShowLockOutFailures: true,
},
},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
SecondFactorCheckLifeTime: 18 * time.Hour,
},
@ -849,9 +968,14 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
IsEmailVerified: true,
MFAMaxSetUp: int32(model.MFALevelSecondFactor),
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
userGrantProvider: &mockUserGrants{},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
userGrantProvider: &mockUserGrants{},
lockoutPolicyProvider: &mockLockoutPolicy{
policy: &iam_view_model.LockoutPolicyView{
ShowLockOutFailures: true,
},
},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
SecondFactorCheckLifeTime: 18 * time.Hour,
},
@ -877,9 +1001,14 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
IsEmailVerified: true,
MFAMaxSetUp: int32(model.MFALevelSecondFactor),
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
userGrantProvider: &mockUserGrants{},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
userGrantProvider: &mockUserGrants{},
lockoutPolicyProvider: &mockLockoutPolicy{
policy: &iam_view_model.LockoutPolicyView{
ShowLockOutFailures: true,
},
},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
SecondFactorCheckLifeTime: 18 * time.Hour,
},
@ -912,6 +1041,11 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
roleCheck: true,
userGrants: 0,
},
lockoutPolicyProvider: &mockLockoutPolicy{
policy: &iam_view_model.LockoutPolicyView{
ShowLockOutFailures: true,
},
},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
SecondFactorCheckLifeTime: 18 * time.Hour,
},
@ -944,6 +1078,11 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
roleCheck: true,
userGrants: 2,
},
lockoutPolicyProvider: &mockLockoutPolicy{
policy: &iam_view_model.LockoutPolicyView{
ShowLockOutFailures: true,
},
},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
SecondFactorCheckLifeTime: 18 * time.Hour,
},
@ -969,6 +1108,11 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
IsEmailVerified: true,
MFAMaxSetUp: int32(model.MFALevelSecondFactor),
},
lockoutPolicyProvider: &mockLockoutPolicy{
policy: &iam_view_model.LockoutPolicyView{
ShowLockOutFailures: true,
},
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
SecondFactorCheckLifeTime: 18 * time.Hour,
@ -995,8 +1139,13 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
IsEmailVerified: true,
MFAMaxSetUp: int32(model.MFALevelSecondFactor),
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
lockoutPolicyProvider: &mockLockoutPolicy{
policy: &iam_view_model.LockoutPolicyView{
ShowLockOutFailures: true,
},
},
SecondFactorCheckLifeTime: 18 * time.Hour,
PasswordCheckLifeTime: 10 * 24 * time.Hour,
},
@ -1024,6 +1173,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
OrgViewProvider: tt.fields.orgViewProvider,
UserGrantProvider: tt.fields.userGrantProvider,
LoginPolicyViewProvider: tt.fields.loginPolicyProvider,
LockoutPolicyViewProvider: tt.fields.lockoutPolicyProvider,
PasswordCheckLifeTime: tt.fields.PasswordCheckLifeTime,
ExternalLoginCheckLifeTime: tt.fields.ExternalLoginCheckLifeTime,
MFAInitSkippedLifeTime: tt.fields.MFAInitSkippedLifeTime,

@ -73,6 +73,7 @@ func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es
newPrivacyPolicy(handler{view, bulkLimit, configs.cycleDuration("PrivacyPolicy"), errorCount, es}),
newCustomText(handler{view, bulkLimit, configs.cycleDuration("CustomTexts"), errorCount, es}),
newMetadata(handler{view, bulkLimit, configs.cycleDuration("Metadata"), errorCount, es}),
newLockoutPolicy(handler{view, bulkLimit, configs.cycleDuration("LockoutPolicy"), errorCount, es}),
}
}

@ -0,0 +1,110 @@
package handler
import (
"github.com/caos/logging"
"github.com/caos/zitadel/internal/eventstore/v1"
es_models "github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/eventstore/v1/query"
"github.com/caos/zitadel/internal/eventstore/v1/spooler"
iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
iam_model "github.com/caos/zitadel/internal/iam/repository/view/model"
org_es_model "github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
)
const (
lockoutPolicyTable = "auth.lockout_policies"
)
type LockoutPolicy struct {
handler
subscription *v1.Subscription
}
func newLockoutPolicy(handler handler) *LockoutPolicy {
h := &LockoutPolicy{
handler: handler,
}
h.subscribe()
return h
}
func (p *LockoutPolicy) subscribe() {
p.subscription = p.es.Subscribe(p.AggregateTypes()...)
go func() {
for event := range p.subscription.Events {
query.ReduceEvent(p, event)
}
}()
}
func (p *LockoutPolicy) ViewModel() string {
return lockoutPolicyTable
}
func (p *LockoutPolicy) Subscription() *v1.Subscription {
return p.subscription
}
func (_ *LockoutPolicy) AggregateTypes() []es_models.AggregateType {
return []es_models.AggregateType{org_es_model.OrgAggregate, iam_es_model.IAMAggregate}
}
func (p *LockoutPolicy) CurrentSequence() (uint64, error) {
sequence, err := p.view.GetLatestLockoutPolicySequence()
if err != nil {
return 0, err
}
return sequence.CurrentSequence, nil
}
func (p *LockoutPolicy) EventQuery() (*es_models.SearchQuery, error) {
sequence, err := p.view.GetLatestLockoutPolicySequence()
if err != nil {
return nil, err
}
return es_models.NewSearchQuery().
AggregateTypeFilter(p.AggregateTypes()...).
LatestSequenceFilter(sequence.CurrentSequence), nil
}
func (p *LockoutPolicy) Reduce(event *es_models.Event) (err error) {
switch event.AggregateType {
case org_es_model.OrgAggregate, iam_es_model.IAMAggregate:
err = p.processLockoutPolicy(event)
}
return err
}
func (p *LockoutPolicy) processLockoutPolicy(event *es_models.Event) (err error) {
policy := new(iam_model.LockoutPolicyView)
switch event.Type {
case iam_es_model.LockoutPolicyAdded, org_es_model.LockoutPolicyAdded:
err = policy.AppendEvent(event)
case iam_es_model.LockoutPolicyChanged, org_es_model.LockoutPolicyChanged:
policy, err = p.view.LockoutPolicyByAggregateID(event.AggregateID)
if err != nil {
return err
}
err = policy.AppendEvent(event)
case org_es_model.LockoutPolicyRemoved:
return p.view.DeleteLockoutPolicy(event.AggregateID, event)
default:
return p.view.ProcessedLockoutPolicySequence(event)
}
if err != nil {
return err
}
return p.view.PutLockoutPolicy(policy, event)
}
func (p *LockoutPolicy) OnError(event *es_models.Event, err error) error {
logging.LogWithFields("SPOOL-0pos2", "id", event.AggregateID).WithError(err).Warn("something went wrong in passwordLockout policy handler")
return spooler.HandleError(event, err, p.view.GetLatestLockoutPolicyFailedEvent, p.view.ProcessedLockoutPolicyFailedEvent, p.view.ProcessedLockoutPolicySequence, p.errorCountUntilSkip)
}
func (p *LockoutPolicy) OnSuccess() error {
return spooler.HandleSuccess(p.view.UpdateLockoutPolicySpoolerRunTimestamp)
}

@ -109,6 +109,7 @@ func Start(conf Config, authZ authz.Config, systemDefaults sd.SystemDefaults, co
OrgViewProvider: view,
IDPProviderViewProvider: view,
LoginPolicyViewProvider: view,
LockoutPolicyViewProvider: view,
UserGrantProvider: view,
IdGenerator: idGenerator,
PasswordCheckLifeTime: systemDefaults.VerificationLifetimes.PasswordCheck.Duration,

@ -0,0 +1,53 @@
package view
import (
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/iam/repository/view"
"github.com/caos/zitadel/internal/iam/repository/view/model"
global_view "github.com/caos/zitadel/internal/view/repository"
)
const (
passwordLockoutPolicyTable = "auth.lockout_policies"
)
func (v *View) LockoutPolicyByAggregateID(aggregateID string) (*model.LockoutPolicyView, error) {
return view.GetLockoutPolicyByAggregateID(v.Db, passwordLockoutPolicyTable, aggregateID)
}
func (v *View) PutLockoutPolicy(policy *model.LockoutPolicyView, event *models.Event) error {
err := view.PutLockoutPolicy(v.Db, passwordLockoutPolicyTable, policy)
if err != nil {
return err
}
return v.ProcessedLockoutPolicySequence(event)
}
func (v *View) DeleteLockoutPolicy(aggregateID string, event *models.Event) error {
err := view.DeleteLockoutPolicy(v.Db, passwordLockoutPolicyTable, aggregateID)
if err != nil && !errors.IsNotFound(err) {
return err
}
return v.ProcessedLockoutPolicySequence(event)
}
func (v *View) GetLatestLockoutPolicySequence() (*global_view.CurrentSequence, error) {
return v.latestSequence(passwordLockoutPolicyTable)
}
func (v *View) ProcessedLockoutPolicySequence(event *models.Event) error {
return v.saveCurrentSequence(passwordLockoutPolicyTable, event)
}
func (v *View) UpdateLockoutPolicySpoolerRunTimestamp() error {
return v.updateSpoolerRunSequence(passwordLockoutPolicyTable)
}
func (v *View) GetLatestLockoutPolicyFailedEvent(sequence uint64) (*global_view.FailedEvent, error) {
return v.latestFailedEvent(passwordLockoutPolicyTable, sequence)
}
func (v *View) ProcessedLockoutPolicyFailedEvent(failedEvent *global_view.FailedEvent) error {
return v.saveFailedEvent(failedEvent)
}

@ -112,10 +112,10 @@ func writeModelToPasswordComplexityPolicy(wm *PasswordComplexityPolicyWriteModel
}
}
func writeModelToPasswordLockoutPolicy(wm *PasswordLockoutPolicyWriteModel) *domain.PasswordLockoutPolicy {
return &domain.PasswordLockoutPolicy{
func writeModelToLockoutPolicy(wm *LockoutPolicyWriteModel) *domain.LockoutPolicy {
return &domain.LockoutPolicy{
ObjectRoot: writeModelToObjectRoot(wm.WriteModel),
MaxAttempts: wm.MaxAttempts,
MaxPasswordAttempts: wm.MaxPasswordAttempts,
ShowLockOutFailures: wm.ShowLockOutFailures,
}
}

@ -9,10 +9,10 @@ import (
"github.com/caos/zitadel/internal/telemetry/tracing"
)
func (c *Commands) AddDefaultPasswordLockoutPolicy(ctx context.Context, policy *domain.PasswordLockoutPolicy) (*domain.PasswordLockoutPolicy, error) {
addedPolicy := NewIAMPasswordLockoutPolicyWriteModel()
func (c *Commands) AddDefaultLockoutPolicy(ctx context.Context, policy *domain.LockoutPolicy) (*domain.LockoutPolicy, error) {
addedPolicy := NewIAMLockoutPolicyWriteModel()
iamAgg := IAMAggregateFromWriteModel(&addedPolicy.WriteModel)
event, err := c.addDefaultPasswordLockoutPolicy(ctx, iamAgg, addedPolicy, policy)
event, err := c.addDefaultLockoutPolicy(ctx, iamAgg, addedPolicy, policy)
if err != nil {
return nil, err
}
@ -25,34 +25,34 @@ func (c *Commands) AddDefaultPasswordLockoutPolicy(ctx context.Context, policy *
return nil, err
}
return writeModelToPasswordLockoutPolicy(&addedPolicy.PasswordLockoutPolicyWriteModel), nil
return writeModelToLockoutPolicy(&addedPolicy.LockoutPolicyWriteModel), nil
}
func (c *Commands) addDefaultPasswordLockoutPolicy(ctx context.Context, iamAgg *eventstore.Aggregate, addedPolicy *IAMPasswordLockoutPolicyWriteModel, policy *domain.PasswordLockoutPolicy) (eventstore.EventPusher, error) {
func (c *Commands) addDefaultLockoutPolicy(ctx context.Context, iamAgg *eventstore.Aggregate, addedPolicy *IAMLockoutPolicyWriteModel, policy *domain.LockoutPolicy) (eventstore.EventPusher, error) {
err := c.eventstore.FilterToQueryReducer(ctx, addedPolicy)
if err != nil {
return nil, err
}
if addedPolicy.State == domain.PolicyStateActive {
return nil, caos_errs.ThrowAlreadyExists(nil, "IAM-0olDf", "Errors.IAM.PasswordLockoutPolicy.AlreadyExists")
return nil, caos_errs.ThrowAlreadyExists(nil, "IAM-0olDf", "Errors.IAM.LockoutPolicy.AlreadyExists")
}
return iam_repo.NewPasswordLockoutPolicyAddedEvent(ctx, iamAgg, policy.MaxAttempts, policy.ShowLockOutFailures), nil
return iam_repo.NewLockoutPolicyAddedEvent(ctx, iamAgg, policy.MaxPasswordAttempts, policy.ShowLockOutFailures), nil
}
func (c *Commands) ChangeDefaultPasswordLockoutPolicy(ctx context.Context, policy *domain.PasswordLockoutPolicy) (*domain.PasswordLockoutPolicy, error) {
existingPolicy, err := c.defaultPasswordLockoutPolicyWriteModelByID(ctx)
func (c *Commands) ChangeDefaultLockoutPolicy(ctx context.Context, policy *domain.LockoutPolicy) (*domain.LockoutPolicy, error) {
existingPolicy, err := c.defaultLockoutPolicyWriteModelByID(ctx)
if err != nil {
return nil, err
}
if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved {
return nil, caos_errs.ThrowNotFound(nil, "IAM-0oPew", "Errors.IAM.PasswordLockoutPolicy.NotFound")
return nil, caos_errs.ThrowNotFound(nil, "IAM-0oPew", "Errors.IAM.LockoutPolicy.NotFound")
}
iamAgg := IAMAggregateFromWriteModel(&existingPolicy.PasswordLockoutPolicyWriteModel.WriteModel)
changedEvent, hasChanged := existingPolicy.NewChangedEvent(ctx, iamAgg, policy.MaxAttempts, policy.ShowLockOutFailures)
iamAgg := IAMAggregateFromWriteModel(&existingPolicy.LockoutPolicyWriteModel.WriteModel)
changedEvent, hasChanged := existingPolicy.NewChangedEvent(ctx, iamAgg, policy.MaxPasswordAttempts, policy.ShowLockOutFailures)
if !hasChanged {
return nil, caos_errs.ThrowPreconditionFailed(nil, "IAM-4M9vs", "Errors.IAM.PasswordLockoutPolicy.NotChanged")
return nil, caos_errs.ThrowPreconditionFailed(nil, "IAM-4M9vs", "Errors.IAM.LockoutPolicy.NotChanged")
}
pushedEvents, err := c.eventstore.PushEvents(ctx, changedEvent)
@ -63,14 +63,14 @@ func (c *Commands) ChangeDefaultPasswordLockoutPolicy(ctx context.Context, polic
if err != nil {
return nil, err
}
return writeModelToPasswordLockoutPolicy(&existingPolicy.PasswordLockoutPolicyWriteModel), nil
return writeModelToLockoutPolicy(&existingPolicy.LockoutPolicyWriteModel), nil
}
func (c *Commands) defaultPasswordLockoutPolicyWriteModelByID(ctx context.Context) (policy *IAMPasswordLockoutPolicyWriteModel, err error) {
func (c *Commands) defaultLockoutPolicyWriteModelByID(ctx context.Context) (policy *IAMLockoutPolicyWriteModel, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
writeModel := NewIAMPasswordLockoutPolicyWriteModel()
writeModel := NewIAMLockoutPolicyWriteModel()
err = c.eventstore.FilterToQueryReducer(ctx, writeModel)
if err != nil {
return nil, err

@ -10,13 +10,13 @@ import (
"github.com/caos/zitadel/internal/repository/policy"
)
type IAMPasswordLockoutPolicyWriteModel struct {
PasswordLockoutPolicyWriteModel
type IAMLockoutPolicyWriteModel struct {
LockoutPolicyWriteModel
}
func NewIAMPasswordLockoutPolicyWriteModel() *IAMPasswordLockoutPolicyWriteModel {
return &IAMPasswordLockoutPolicyWriteModel{
PasswordLockoutPolicyWriteModel{
func NewIAMLockoutPolicyWriteModel() *IAMLockoutPolicyWriteModel {
return &IAMLockoutPolicyWriteModel{
LockoutPolicyWriteModel{
WriteModel: eventstore.WriteModel{
AggregateID: domain.IAMID,
ResourceOwner: domain.IAMID,
@ -25,40 +25,40 @@ func NewIAMPasswordLockoutPolicyWriteModel() *IAMPasswordLockoutPolicyWriteModel
}
}
func (wm *IAMPasswordLockoutPolicyWriteModel) AppendEvents(events ...eventstore.EventReader) {
func (wm *IAMLockoutPolicyWriteModel) AppendEvents(events ...eventstore.EventReader) {
for _, event := range events {
switch e := event.(type) {
case *iam.PasswordLockoutPolicyAddedEvent:
wm.PasswordLockoutPolicyWriteModel.AppendEvents(&e.PasswordLockoutPolicyAddedEvent)
case *iam.PasswordLockoutPolicyChangedEvent:
wm.PasswordLockoutPolicyWriteModel.AppendEvents(&e.PasswordLockoutPolicyChangedEvent)
case *iam.LockoutPolicyAddedEvent:
wm.LockoutPolicyWriteModel.AppendEvents(&e.LockoutPolicyAddedEvent)
case *iam.LockoutPolicyChangedEvent:
wm.LockoutPolicyWriteModel.AppendEvents(&e.LockoutPolicyChangedEvent)
}
}
}
func (wm *IAMPasswordLockoutPolicyWriteModel) Reduce() error {
return wm.PasswordLockoutPolicyWriteModel.Reduce()
func (wm *IAMLockoutPolicyWriteModel) Reduce() error {
return wm.LockoutPolicyWriteModel.Reduce()
}
func (wm *IAMPasswordLockoutPolicyWriteModel) Query() *eventstore.SearchQueryBuilder {
func (wm *IAMLockoutPolicyWriteModel) Query() *eventstore.SearchQueryBuilder {
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
ResourceOwner(wm.ResourceOwner).
AddQuery().
AggregateTypes(iam.AggregateType).
AggregateIDs(wm.PasswordLockoutPolicyWriteModel.AggregateID).
AggregateIDs(wm.LockoutPolicyWriteModel.AggregateID).
EventTypes(
iam.PasswordLockoutPolicyAddedEventType,
iam.PasswordLockoutPolicyChangedEventType).
iam.LockoutPolicyAddedEventType,
iam.LockoutPolicyChangedEventType).
Builder()
}
func (wm *IAMPasswordLockoutPolicyWriteModel) NewChangedEvent(
func (wm *IAMLockoutPolicyWriteModel) NewChangedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
maxAttempts uint64,
showLockoutFailure bool) (*iam.PasswordLockoutPolicyChangedEvent, bool) {
changes := make([]policy.PasswordLockoutPolicyChanges, 0)
if wm.MaxAttempts != maxAttempts {
showLockoutFailure bool) (*iam.LockoutPolicyChangedEvent, bool) {
changes := make([]policy.LockoutPolicyChanges, 0)
if wm.MaxPasswordAttempts != maxAttempts {
changes = append(changes, policy.ChangeMaxAttempts(maxAttempts))
}
if wm.ShowLockOutFailures != showLockoutFailure {
@ -67,7 +67,7 @@ func (wm *IAMPasswordLockoutPolicyWriteModel) NewChangedEvent(
if len(changes) == 0 {
return nil, false
}
changedEvent, err := iam.NewPasswordLockoutPolicyChangedEvent(ctx, aggregate, changes)
changedEvent, err := iam.NewLockoutPolicyChangedEvent(ctx, aggregate, changes)
if err != nil {
return nil, false
}

@ -13,16 +13,16 @@ import (
"testing"
)
func TestCommandSide_AddDefaultPasswordLockoutPolicy(t *testing.T) {
func TestCommandSide_AddDefaultLockoutPolicy(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
policy *domain.PasswordLockoutPolicy
policy *domain.LockoutPolicy
}
type res struct {
want *domain.PasswordLockoutPolicy
want *domain.LockoutPolicy
err func(error) bool
}
tests := []struct {
@ -32,13 +32,13 @@ func TestCommandSide_AddDefaultPasswordLockoutPolicy(t *testing.T) {
res res
}{
{
name: "password lockout policy already existing, already exists error",
name: "lockout policy already existing, already exists error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
iam.NewPasswordLockoutPolicyAddedEvent(context.Background(),
iam.NewLockoutPolicyAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
10,
true,
@ -49,8 +49,8 @@ func TestCommandSide_AddDefaultPasswordLockoutPolicy(t *testing.T) {
},
args: args{
ctx: context.Background(),
policy: &domain.PasswordLockoutPolicy{
MaxAttempts: 10,
policy: &domain.LockoutPolicy{
MaxPasswordAttempts: 10,
ShowLockOutFailures: true,
},
},
@ -67,7 +67,7 @@ func TestCommandSide_AddDefaultPasswordLockoutPolicy(t *testing.T) {
expectPush(
[]*repository.Event{
eventFromEventPusher(
iam.NewPasswordLockoutPolicyAddedEvent(context.Background(),
iam.NewLockoutPolicyAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
10,
true,
@ -79,18 +79,18 @@ func TestCommandSide_AddDefaultPasswordLockoutPolicy(t *testing.T) {
},
args: args{
ctx: context.Background(),
policy: &domain.PasswordLockoutPolicy{
MaxAttempts: 10,
policy: &domain.LockoutPolicy{
MaxPasswordAttempts: 10,
ShowLockOutFailures: true,
},
},
res: res{
want: &domain.PasswordLockoutPolicy{
want: &domain.LockoutPolicy{
ObjectRoot: models.ObjectRoot{
AggregateID: "IAM",
ResourceOwner: "IAM",
},
MaxAttempts: 10,
MaxPasswordAttempts: 10,
ShowLockOutFailures: true,
},
},
@ -101,7 +101,7 @@ func TestCommandSide_AddDefaultPasswordLockoutPolicy(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.AddDefaultPasswordLockoutPolicy(tt.args.ctx, tt.args.policy)
got, err := r.AddDefaultLockoutPolicy(tt.args.ctx, tt.args.policy)
if tt.res.err == nil {
assert.NoError(t, err)
}
@ -115,16 +115,16 @@ func TestCommandSide_AddDefaultPasswordLockoutPolicy(t *testing.T) {
}
}
func TestCommandSide_ChangeDefaultPasswordLockoutPolicy(t *testing.T) {
func TestCommandSide_ChangeDefaultLockoutPolicy(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
policy *domain.PasswordLockoutPolicy
policy *domain.LockoutPolicy
}
type res struct {
want *domain.PasswordLockoutPolicy
want *domain.LockoutPolicy
err func(error) bool
}
tests := []struct {
@ -134,7 +134,7 @@ func TestCommandSide_ChangeDefaultPasswordLockoutPolicy(t *testing.T) {
res res
}{
{
name: "password lockout policy not existing, not found error",
name: "lockout policy not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
@ -143,8 +143,8 @@ func TestCommandSide_ChangeDefaultPasswordLockoutPolicy(t *testing.T) {
},
args: args{
ctx: context.Background(),
policy: &domain.PasswordLockoutPolicy{
MaxAttempts: 10,
policy: &domain.LockoutPolicy{
MaxPasswordAttempts: 10,
ShowLockOutFailures: true,
},
},
@ -159,7 +159,7 @@ func TestCommandSide_ChangeDefaultPasswordLockoutPolicy(t *testing.T) {
t,
expectFilter(
eventFromEventPusher(
iam.NewPasswordLockoutPolicyAddedEvent(context.Background(),
iam.NewLockoutPolicyAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
10,
true,
@ -170,8 +170,8 @@ func TestCommandSide_ChangeDefaultPasswordLockoutPolicy(t *testing.T) {
},
args: args{
ctx: context.Background(),
policy: &domain.PasswordLockoutPolicy{
MaxAttempts: 10,
policy: &domain.LockoutPolicy{
MaxPasswordAttempts: 10,
ShowLockOutFailures: true,
},
},
@ -186,7 +186,7 @@ func TestCommandSide_ChangeDefaultPasswordLockoutPolicy(t *testing.T) {
t,
expectFilter(
eventFromEventPusher(
iam.NewPasswordLockoutPolicyAddedEvent(context.Background(),
iam.NewLockoutPolicyAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
10,
true,
@ -196,7 +196,7 @@ func TestCommandSide_ChangeDefaultPasswordLockoutPolicy(t *testing.T) {
expectPush(
[]*repository.Event{
eventFromEventPusher(
newDefaultPasswordLockoutPolicyChangedEvent(context.Background(), 20, false),
newDefaultLockoutPolicyChangedEvent(context.Background(), 20, false),
),
},
),
@ -204,18 +204,18 @@ func TestCommandSide_ChangeDefaultPasswordLockoutPolicy(t *testing.T) {
},
args: args{
ctx: context.Background(),
policy: &domain.PasswordLockoutPolicy{
MaxAttempts: 20,
policy: &domain.LockoutPolicy{
MaxPasswordAttempts: 20,
ShowLockOutFailures: false,
},
},
res: res{
want: &domain.PasswordLockoutPolicy{
want: &domain.LockoutPolicy{
ObjectRoot: models.ObjectRoot{
AggregateID: "IAM",
ResourceOwner: "IAM",
},
MaxAttempts: 20,
MaxPasswordAttempts: 20,
ShowLockOutFailures: false,
},
},
@ -226,7 +226,7 @@ func TestCommandSide_ChangeDefaultPasswordLockoutPolicy(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.ChangeDefaultPasswordLockoutPolicy(tt.args.ctx, tt.args.policy)
got, err := r.ChangeDefaultLockoutPolicy(tt.args.ctx, tt.args.policy)
if tt.res.err == nil {
assert.NoError(t, err)
}
@ -240,10 +240,10 @@ func TestCommandSide_ChangeDefaultPasswordLockoutPolicy(t *testing.T) {
}
}
func newDefaultPasswordLockoutPolicyChangedEvent(ctx context.Context, maxAttempts uint64, showLockoutFailure bool) *iam.PasswordLockoutPolicyChangedEvent {
event, _ := iam.NewPasswordLockoutPolicyChangedEvent(ctx,
func newDefaultLockoutPolicyChangedEvent(ctx context.Context, maxAttempts uint64, showLockoutFailure bool) *iam.LockoutPolicyChangedEvent {
event, _ := iam.NewLockoutPolicyChangedEvent(ctx,
&iam.NewAggregate().Aggregate,
[]policy.PasswordLockoutPolicyChanges{
[]policy.LockoutPolicyChanges{
policy.ChangeMaxAttempts(maxAttempts),
policy.ChangeShowLockOutFailures(showLockoutFailure),
},

@ -7,21 +7,21 @@ import (
"github.com/caos/zitadel/internal/repository/org"
)
func (c *Commands) AddPasswordLockoutPolicy(ctx context.Context, resourceOwner string, policy *domain.PasswordLockoutPolicy) (*domain.PasswordLockoutPolicy, error) {
func (c *Commands) AddLockoutPolicy(ctx context.Context, resourceOwner string, policy *domain.LockoutPolicy) (*domain.LockoutPolicy, error) {
if resourceOwner == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-8fJif", "Errors.ResourceOwnerMissing")
}
addedPolicy := NewOrgPasswordLockoutPolicyWriteModel(resourceOwner)
addedPolicy := NewOrgLockoutPolicyWriteModel(resourceOwner)
err := c.eventstore.FilterToQueryReducer(ctx, addedPolicy)
if err != nil {
return nil, err
}
if addedPolicy.State == domain.PolicyStateActive {
return nil, caos_errs.ThrowAlreadyExists(nil, "ORG-0olDf", "Errors.ORG.PasswordLockoutPolicy.AlreadyExists")
return nil, caos_errs.ThrowAlreadyExists(nil, "ORG-0olDf", "Errors.ORG.LockoutPolicy.AlreadyExists")
}
orgAgg := OrgAggregateFromWriteModel(&addedPolicy.WriteModel)
pushedEvents, err := c.eventstore.PushEvents(ctx, org.NewPasswordLockoutPolicyAddedEvent(ctx, orgAgg, policy.MaxAttempts, policy.ShowLockOutFailures))
pushedEvents, err := c.eventstore.PushEvents(ctx, org.NewLockoutPolicyAddedEvent(ctx, orgAgg, policy.MaxPasswordAttempts, policy.ShowLockOutFailures))
if err != nil {
return nil, err
}
@ -29,26 +29,26 @@ func (c *Commands) AddPasswordLockoutPolicy(ctx context.Context, resourceOwner s
if err != nil {
return nil, err
}
return writeModelToPasswordLockoutPolicy(&addedPolicy.PasswordLockoutPolicyWriteModel), nil
return writeModelToLockoutPolicy(&addedPolicy.LockoutPolicyWriteModel), nil
}
func (c *Commands) ChangePasswordLockoutPolicy(ctx context.Context, resourceOwner string, policy *domain.PasswordLockoutPolicy) (*domain.PasswordLockoutPolicy, error) {
func (c *Commands) ChangeLockoutPolicy(ctx context.Context, resourceOwner string, policy *domain.LockoutPolicy) (*domain.LockoutPolicy, error) {
if resourceOwner == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-3J9fs", "Errors.ResourceOwnerMissing")
}
existingPolicy := NewOrgPasswordLockoutPolicyWriteModel(resourceOwner)
existingPolicy := NewOrgLockoutPolicyWriteModel(resourceOwner)
err := c.eventstore.FilterToQueryReducer(ctx, existingPolicy)
if err != nil {
return nil, err
}
if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved {
return nil, caos_errs.ThrowNotFound(nil, "ORG-ADfs1", "Errors.Org.PasswordLockoutPolicy.NotFound")
return nil, caos_errs.ThrowNotFound(nil, "ORG-ADfs1", "Errors.Org.LockoutPolicy.NotFound")
}
orgAgg := OrgAggregateFromWriteModel(&existingPolicy.PasswordLockoutPolicyWriteModel.WriteModel)
changedEvent, hasChanged := existingPolicy.NewChangedEvent(ctx, orgAgg, policy.MaxAttempts, policy.ShowLockOutFailures)
orgAgg := OrgAggregateFromWriteModel(&existingPolicy.LockoutPolicyWriteModel.WriteModel)
changedEvent, hasChanged := existingPolicy.NewChangedEvent(ctx, orgAgg, policy.MaxPasswordAttempts, policy.ShowLockOutFailures)
if !hasChanged {
return nil, caos_errs.ThrowPreconditionFailed(nil, "ORG-4M9vs", "Errors.Org.PasswordLockoutPolicy.NotChanged")
return nil, caos_errs.ThrowPreconditionFailed(nil, "ORG-4M9vs", "Errors.Org.LockoutPolicy.NotChanged")
}
pushedEvents, err := c.eventstore.PushEvents(ctx, changedEvent)
@ -59,23 +59,23 @@ func (c *Commands) ChangePasswordLockoutPolicy(ctx context.Context, resourceOwne
if err != nil {
return nil, err
}
return writeModelToPasswordLockoutPolicy(&existingPolicy.PasswordLockoutPolicyWriteModel), nil
return writeModelToLockoutPolicy(&existingPolicy.LockoutPolicyWriteModel), nil
}
func (c *Commands) RemovePasswordLockoutPolicy(ctx context.Context, orgID string) error {
func (c *Commands) RemoveLockoutPolicy(ctx context.Context, orgID string) error {
if orgID == "" {
return caos_errs.ThrowInvalidArgument(nil, "Org-4J9fs", "Errors.ResourceOwnerMissing")
}
existingPolicy := NewOrgPasswordLockoutPolicyWriteModel(orgID)
existingPolicy := NewOrgLockoutPolicyWriteModel(orgID)
err := c.eventstore.FilterToQueryReducer(ctx, existingPolicy)
if err != nil {
return err
}
if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved {
return caos_errs.ThrowNotFound(nil, "ORG-D4zuz", "Errors.Org.PasswordLockoutPolicy.NotFound")
return caos_errs.ThrowNotFound(nil, "ORG-D4zuz", "Errors.Org.LockoutPolicy.NotFound")
}
orgAgg := OrgAggregateFromWriteModel(&existingPolicy.WriteModel)
_, err = c.eventstore.PushEvents(ctx, org.NewPasswordLockoutPolicyRemovedEvent(ctx, orgAgg))
_, err = c.eventstore.PushEvents(ctx, org.NewLockoutPolicyRemovedEvent(ctx, orgAgg))
return err
}

@ -9,13 +9,13 @@ import (
"github.com/caos/zitadel/internal/repository/policy"
)
type OrgPasswordLockoutPolicyWriteModel struct {
PasswordLockoutPolicyWriteModel
type OrgLockoutPolicyWriteModel struct {
LockoutPolicyWriteModel
}
func NewOrgPasswordLockoutPolicyWriteModel(orgID string) *OrgPasswordLockoutPolicyWriteModel {
return &OrgPasswordLockoutPolicyWriteModel{
PasswordLockoutPolicyWriteModel{
func NewOrgLockoutPolicyWriteModel(orgID string) *OrgLockoutPolicyWriteModel {
return &OrgLockoutPolicyWriteModel{
LockoutPolicyWriteModel{
WriteModel: eventstore.WriteModel{
AggregateID: orgID,
ResourceOwner: orgID,
@ -24,42 +24,42 @@ func NewOrgPasswordLockoutPolicyWriteModel(orgID string) *OrgPasswordLockoutPoli
}
}
func (wm *OrgPasswordLockoutPolicyWriteModel) AppendEvents(events ...eventstore.EventReader) {
func (wm *OrgLockoutPolicyWriteModel) AppendEvents(events ...eventstore.EventReader) {
for _, event := range events {
switch e := event.(type) {
case *org.PasswordLockoutPolicyAddedEvent:
wm.PasswordLockoutPolicyWriteModel.AppendEvents(&e.PasswordLockoutPolicyAddedEvent)
case *org.PasswordLockoutPolicyChangedEvent:
wm.PasswordLockoutPolicyWriteModel.AppendEvents(&e.PasswordLockoutPolicyChangedEvent)
case *org.PasswordLockoutPolicyRemovedEvent:
wm.PasswordLockoutPolicyWriteModel.AppendEvents(&e.PasswordLockoutPolicyRemovedEvent)
case *org.LockoutPolicyAddedEvent:
wm.LockoutPolicyWriteModel.AppendEvents(&e.LockoutPolicyAddedEvent)
case *org.LockoutPolicyChangedEvent:
wm.LockoutPolicyWriteModel.AppendEvents(&e.LockoutPolicyChangedEvent)
case *org.LockoutPolicyRemovedEvent:
wm.LockoutPolicyWriteModel.AppendEvents(&e.LockoutPolicyRemovedEvent)
}
}
}
func (wm *OrgPasswordLockoutPolicyWriteModel) Reduce() error {
return wm.PasswordLockoutPolicyWriteModel.Reduce()
func (wm *OrgLockoutPolicyWriteModel) Reduce() error {
return wm.LockoutPolicyWriteModel.Reduce()
}
func (wm *OrgPasswordLockoutPolicyWriteModel) Query() *eventstore.SearchQueryBuilder {
func (wm *OrgLockoutPolicyWriteModel) Query() *eventstore.SearchQueryBuilder {
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
ResourceOwner(wm.ResourceOwner).
AddQuery().
AggregateTypes(org.AggregateType).
AggregateIDs(wm.PasswordLockoutPolicyWriteModel.AggregateID).
EventTypes(org.PasswordLockoutPolicyAddedEventType,
org.PasswordLockoutPolicyChangedEventType,
org.PasswordLockoutPolicyRemovedEventType).
AggregateIDs(wm.LockoutPolicyWriteModel.AggregateID).
EventTypes(org.LockoutPolicyAddedEventType,
org.LockoutPolicyChangedEventType,
org.LockoutPolicyRemovedEventType).
Builder()
}
func (wm *OrgPasswordLockoutPolicyWriteModel) NewChangedEvent(
func (wm *OrgLockoutPolicyWriteModel) NewChangedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
maxAttempts uint64,
showLockoutFailure bool) (*org.PasswordLockoutPolicyChangedEvent, bool) {
changes := make([]policy.PasswordLockoutPolicyChanges, 0)
if wm.MaxAttempts != maxAttempts {
showLockoutFailure bool) (*org.LockoutPolicyChangedEvent, bool) {
changes := make([]policy.LockoutPolicyChanges, 0)
if wm.MaxPasswordAttempts != maxAttempts {
changes = append(changes, policy.ChangeMaxAttempts(maxAttempts))
}
if wm.ShowLockOutFailures != showLockoutFailure {
@ -68,7 +68,7 @@ func (wm *OrgPasswordLockoutPolicyWriteModel) NewChangedEvent(
if len(changes) == 0 {
return nil, false
}
changedEvent, err := org.NewPasswordLockoutPolicyChangedEvent(ctx, aggregate, changes)
changedEvent, err := org.NewLockoutPolicyChangedEvent(ctx, aggregate, changes)
if err != nil {
return nil, false
}

@ -22,10 +22,10 @@ func TestCommandSide_AddPasswordLockoutPolicy(t *testing.T) {
type args struct {
ctx context.Context
orgID string
policy *domain.PasswordLockoutPolicy
policy *domain.LockoutPolicy
}
type res struct {
want *domain.PasswordLockoutPolicy
want *domain.LockoutPolicy
err func(error) bool
}
tests := []struct {
@ -43,8 +43,8 @@ func TestCommandSide_AddPasswordLockoutPolicy(t *testing.T) {
},
args: args{
ctx: context.Background(),
policy: &domain.PasswordLockoutPolicy{
MaxAttempts: 10,
policy: &domain.LockoutPolicy{
MaxPasswordAttempts: 10,
ShowLockOutFailures: true,
},
},
@ -59,7 +59,7 @@ func TestCommandSide_AddPasswordLockoutPolicy(t *testing.T) {
t,
expectFilter(
eventFromEventPusher(
org.NewPasswordLockoutPolicyAddedEvent(context.Background(),
org.NewLockoutPolicyAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
10,
true,
@ -71,8 +71,8 @@ func TestCommandSide_AddPasswordLockoutPolicy(t *testing.T) {
args: args{
ctx: context.Background(),
orgID: "org1",
policy: &domain.PasswordLockoutPolicy{
MaxAttempts: 10,
policy: &domain.LockoutPolicy{
MaxPasswordAttempts: 10,
ShowLockOutFailures: true,
},
},
@ -89,7 +89,7 @@ func TestCommandSide_AddPasswordLockoutPolicy(t *testing.T) {
expectPush(
[]*repository.Event{
eventFromEventPusher(
org.NewPasswordLockoutPolicyAddedEvent(context.Background(),
org.NewLockoutPolicyAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
10,
true,
@ -102,18 +102,18 @@ func TestCommandSide_AddPasswordLockoutPolicy(t *testing.T) {
args: args{
ctx: context.Background(),
orgID: "org1",
policy: &domain.PasswordLockoutPolicy{
MaxAttempts: 10,
policy: &domain.LockoutPolicy{
MaxPasswordAttempts: 10,
ShowLockOutFailures: true,
},
},
res: res{
want: &domain.PasswordLockoutPolicy{
want: &domain.LockoutPolicy{
ObjectRoot: models.ObjectRoot{
AggregateID: "org1",
ResourceOwner: "org1",
},
MaxAttempts: 10,
MaxPasswordAttempts: 10,
ShowLockOutFailures: true,
},
},
@ -124,7 +124,7 @@ func TestCommandSide_AddPasswordLockoutPolicy(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.AddPasswordLockoutPolicy(tt.args.ctx, tt.args.orgID, tt.args.policy)
got, err := r.AddLockoutPolicy(tt.args.ctx, tt.args.orgID, tt.args.policy)
if tt.res.err == nil {
assert.NoError(t, err)
}
@ -145,10 +145,10 @@ func TestCommandSide_ChangePasswordLockoutPolicy(t *testing.T) {
type args struct {
ctx context.Context
orgID string
policy *domain.PasswordLockoutPolicy
policy *domain.LockoutPolicy
}
type res struct {
want *domain.PasswordLockoutPolicy
want *domain.LockoutPolicy
err func(error) bool
}
tests := []struct {
@ -166,8 +166,8 @@ func TestCommandSide_ChangePasswordLockoutPolicy(t *testing.T) {
},
args: args{
ctx: context.Background(),
policy: &domain.PasswordLockoutPolicy{
MaxAttempts: 10,
policy: &domain.LockoutPolicy{
MaxPasswordAttempts: 10,
ShowLockOutFailures: true,
},
},
@ -186,8 +186,8 @@ func TestCommandSide_ChangePasswordLockoutPolicy(t *testing.T) {
args: args{
ctx: context.Background(),
orgID: "org1",
policy: &domain.PasswordLockoutPolicy{
MaxAttempts: 10,
policy: &domain.LockoutPolicy{
MaxPasswordAttempts: 10,
ShowLockOutFailures: true,
},
},
@ -202,7 +202,7 @@ func TestCommandSide_ChangePasswordLockoutPolicy(t *testing.T) {
t,
expectFilter(
eventFromEventPusher(
org.NewPasswordLockoutPolicyAddedEvent(context.Background(),
org.NewLockoutPolicyAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
10,
true,
@ -214,8 +214,8 @@ func TestCommandSide_ChangePasswordLockoutPolicy(t *testing.T) {
args: args{
ctx: context.Background(),
orgID: "org1",
policy: &domain.PasswordLockoutPolicy{
MaxAttempts: 10,
policy: &domain.LockoutPolicy{
MaxPasswordAttempts: 10,
ShowLockOutFailures: true,
},
},
@ -230,7 +230,7 @@ func TestCommandSide_ChangePasswordLockoutPolicy(t *testing.T) {
t,
expectFilter(
eventFromEventPusher(
org.NewPasswordLockoutPolicyAddedEvent(context.Background(),
org.NewLockoutPolicyAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
10,
true,
@ -249,18 +249,18 @@ func TestCommandSide_ChangePasswordLockoutPolicy(t *testing.T) {
args: args{
ctx: context.Background(),
orgID: "org1",
policy: &domain.PasswordLockoutPolicy{
MaxAttempts: 5,
policy: &domain.LockoutPolicy{
MaxPasswordAttempts: 5,
ShowLockOutFailures: false,
},
},
res: res{
want: &domain.PasswordLockoutPolicy{
want: &domain.LockoutPolicy{
ObjectRoot: models.ObjectRoot{
AggregateID: "org1",
ResourceOwner: "org1",
},
MaxAttempts: 5,
MaxPasswordAttempts: 5,
ShowLockOutFailures: false,
},
},
@ -271,7 +271,7 @@ func TestCommandSide_ChangePasswordLockoutPolicy(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.ChangePasswordLockoutPolicy(tt.args.ctx, tt.args.orgID, tt.args.policy)
got, err := r.ChangeLockoutPolicy(tt.args.ctx, tt.args.orgID, tt.args.policy)
if tt.res.err == nil {
assert.NoError(t, err)
}
@ -340,7 +340,7 @@ func TestCommandSide_RemovePasswordLockoutPolicy(t *testing.T) {
t,
expectFilter(
eventFromEventPusher(
org.NewPasswordLockoutPolicyAddedEvent(context.Background(),
org.NewLockoutPolicyAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
10,
true,
@ -350,7 +350,7 @@ func TestCommandSide_RemovePasswordLockoutPolicy(t *testing.T) {
expectPush(
[]*repository.Event{
eventFromEventPusher(
org.NewPasswordLockoutPolicyRemovedEvent(context.Background(),
org.NewLockoutPolicyRemovedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate),
),
},
@ -373,7 +373,7 @@ func TestCommandSide_RemovePasswordLockoutPolicy(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
err := r.RemovePasswordLockoutPolicy(tt.args.ctx, tt.args.orgID)
err := r.RemoveLockoutPolicy(tt.args.ctx, tt.args.orgID)
if tt.res.err == nil {
assert.NoError(t, err)
}
@ -384,10 +384,10 @@ func TestCommandSide_RemovePasswordLockoutPolicy(t *testing.T) {
}
}
func newPasswordLockoutPolicyChangedEvent(ctx context.Context, orgID string, maxAttempts uint64, showLockoutFailure bool) *org.PasswordLockoutPolicyChangedEvent {
event, _ := org.NewPasswordLockoutPolicyChangedEvent(ctx,
func newPasswordLockoutPolicyChangedEvent(ctx context.Context, orgID string, maxAttempts uint64, showLockoutFailure bool) *org.LockoutPolicyChangedEvent {
event, _ := org.NewLockoutPolicyChangedEvent(ctx,
&org.NewAggregate(orgID, orgID).Aggregate,
[]policy.PasswordLockoutPolicyChanges{
[]policy.LockoutPolicyChanges{
policy.ChangeMaxAttempts(maxAttempts),
policy.ChangeShowLockOutFailures(showLockoutFailure),
},

@ -6,29 +6,29 @@ import (
"github.com/caos/zitadel/internal/repository/policy"
)
type PasswordLockoutPolicyWriteModel struct {
type LockoutPolicyWriteModel struct {
eventstore.WriteModel
MaxAttempts uint64
MaxPasswordAttempts uint64
ShowLockOutFailures bool
State domain.PolicyState
}
func (wm *PasswordLockoutPolicyWriteModel) Reduce() error {
func (wm *LockoutPolicyWriteModel) Reduce() error {
for _, event := range wm.Events {
switch e := event.(type) {
case *policy.PasswordLockoutPolicyAddedEvent:
wm.MaxAttempts = e.MaxAttempts
case *policy.LockoutPolicyAddedEvent:
wm.MaxPasswordAttempts = e.MaxPasswordAttempts
wm.ShowLockOutFailures = e.ShowLockOutFailures
wm.State = domain.PolicyStateActive
case *policy.PasswordLockoutPolicyChangedEvent:
if e.MaxAttempts != nil {
wm.MaxAttempts = *e.MaxAttempts
case *policy.LockoutPolicyChangedEvent:
if e.MaxPasswordAttempts != nil {
wm.MaxPasswordAttempts = *e.MaxPasswordAttempts
}
if e.ShowLockOutFailures != nil {
wm.ShowLockOutFailures = *e.ShowLockOutFailures
}
case *policy.PasswordLockoutPolicyRemovedEvent:
case *policy.LockoutPolicyRemovedEvent:
wm.State = domain.PolicyStateRemoved
}
}

@ -0,0 +1,35 @@
package command
import (
"context"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/eventstore"
)
type Step18 struct {
LockoutPolicy domain.LockoutPolicy
}
func (s *Step18) Step() domain.Step {
return domain.Step18
}
func (s *Step18) execute(ctx context.Context, commandSide *Commands) error {
return commandSide.SetupStep18(ctx, s)
}
func (c *Commands) SetupStep18(ctx context.Context, step *Step18) error {
fn := func(iam *IAMWriteModel) ([]eventstore.EventPusher, error) {
iamAgg := IAMAggregateFromWriteModel(&iam.WriteModel)
addedPolicy := NewIAMLockoutPolicyWriteModel()
events, err := c.addDefaultLockoutPolicy(ctx, iamAgg, addedPolicy, &step.LockoutPolicy)
if err != nil {
return nil, err
}
logging.Log("SETUP-3m99ds").Info("default lockout policy set up")
return []eventstore.EventPusher{events}, nil
}
return c.setup(ctx, step, fn)
}

@ -3,15 +3,13 @@ package command
import (
"context"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/domain"
)
type Step4 struct {
DefaultPasswordLockoutPolicy domain.PasswordLockoutPolicy
DefaultPasswordLockoutPolicy domain.LockoutPolicy
}
func (s *Step4) Step() domain.Step {
@ -21,19 +19,12 @@ func (s *Step4) Step() domain.Step {
func (s *Step4) execute(ctx context.Context, commandSide *Commands) error {
return commandSide.SetupStep4(ctx, s)
}
//This step should not be executed when a new instance is setup, because its not used anymore
//SetupStep4 is no op in favour of step 18.
//Password lockout policy is replaced by lockout policy
func (c *Commands) SetupStep4(ctx context.Context, step *Step4) error {
fn := func(iam *IAMWriteModel) ([]eventstore.EventPusher, error) {
iamAgg := IAMAggregateFromWriteModel(&iam.WriteModel)
event, err := c.addDefaultPasswordLockoutPolicy(ctx, iamAgg, NewIAMPasswordLockoutPolicyWriteModel(), &domain.PasswordLockoutPolicy{
MaxAttempts: step.DefaultPasswordLockoutPolicy.MaxAttempts,
ShowLockOutFailures: step.DefaultPasswordLockoutPolicy.ShowLockOutFailures,
})
if err != nil {
return nil, err
}
logging.Log("SETUP-Bfnge").Info("default password lockout policy set up")
return []eventstore.EventPusher{event}, nil
return nil, nil
}
return c.setup(ctx, step, fn)
}

@ -194,7 +194,7 @@ func (c *Commands) PasswordCodeSent(ctx context.Context, orgID, userID string) (
return err
}
func (c *Commands) HumanCheckPassword(ctx context.Context, orgID, userID, password string, authRequest *domain.AuthRequest) (err error) {
func (c *Commands) HumanCheckPassword(ctx context.Context, orgID, userID, password string, authRequest *domain.AuthRequest, lockoutPolicy *domain.LockoutPolicy) (err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
@ -225,7 +225,15 @@ func (c *Commands) HumanCheckPassword(ctx context.Context, orgID, userID, passwo
_, err = c.eventstore.PushEvents(ctx, user.NewHumanPasswordCheckSucceededEvent(ctx, userAgg, authRequestDomainToAuthRequestInfo(authRequest)))
return err
}
_, err = c.eventstore.PushEvents(ctx, user.NewHumanPasswordCheckFailedEvent(ctx, userAgg, authRequestDomainToAuthRequestInfo(authRequest)))
events := make([]eventstore.EventPusher, 0)
events = append(events, user.NewHumanPasswordCheckFailedEvent(ctx, userAgg, authRequestDomainToAuthRequestInfo(authRequest)))
if lockoutPolicy != nil && lockoutPolicy.MaxPasswordAttempts > 0 {
if existingPassword.PasswordCheckFailedCount+1 >= lockoutPolicy.MaxPasswordAttempts {
events = append(events, user.NewUserLockedEvent(ctx, userAgg))
}
}
_, err = c.eventstore.PushEvents(ctx, events...)
logging.Log("COMMAND-9fj7s").OnError(err).Error("error create password check failed event")
return caos_errs.ThrowInvalidArgument(nil, "COMMAND-452ad", "Errors.User.Password.Invalid")
}

@ -16,9 +16,10 @@ type HumanPasswordWriteModel struct {
Secret *crypto.CryptoValue
SecretChangeRequired bool
Code *crypto.CryptoValue
CodeCreationDate time.Time
CodeExpiry time.Duration
Code *crypto.CryptoValue
CodeCreationDate time.Time
CodeExpiry time.Duration
PasswordCheckFailedCount uint64
UserState domain.UserState
}
@ -51,6 +52,7 @@ func (wm *HumanPasswordWriteModel) Reduce() error {
wm.Secret = e.Secret
wm.SecretChangeRequired = e.ChangeRequired
wm.Code = nil
wm.PasswordCheckFailedCount = 0
case *user.HumanPasswordCodeAddedEvent:
wm.Code = e.Code
wm.CodeCreationDate = e.CreationDate()
@ -59,6 +61,12 @@ func (wm *HumanPasswordWriteModel) Reduce() error {
if wm.UserState == domain.UserStateInitial {
wm.UserState = domain.UserStateActive
}
case *user.HumanPasswordCheckFailedEvent:
wm.PasswordCheckFailedCount += 1
case *user.HumanPasswordCheckSucceededEvent:
wm.PasswordCheckFailedCount = 0
case *user.UserUnlockedEvent:
wm.PasswordCheckFailedCount = 0
case *user.UserRemovedEvent:
wm.UserState = domain.UserStateDeleted
}
@ -78,14 +86,19 @@ func (wm *HumanPasswordWriteModel) Query() *eventstore.SearchQueryBuilder {
user.HumanPasswordChangedType,
user.HumanPasswordCodeAddedType,
user.HumanEmailVerifiedType,
user.HumanPasswordCheckFailedType,
user.HumanPasswordCheckSucceededType,
user.UserRemovedType,
user.UserUnlockedType,
user.UserV1AddedType,
user.UserV1RegisteredType,
user.UserV1InitialCodeAddedType,
user.UserV1InitializedCheckSucceededType,
user.UserV1PasswordChangedType,
user.UserV1PasswordCodeAddedType,
user.UserV1EmailVerifiedType).
user.UserV1EmailVerifiedType,
user.UserV1PasswordCheckFailedType,
user.UserV1PasswordCheckSucceededType).
Builder()
if wm.ResourceOwner != "" {

@ -1082,6 +1082,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
resourceOwner string
password string
authReq *domain.AuthRequest
lockoutPolicy *domain.LockoutPolicy
}
type res struct {
err func(error) bool
@ -1177,7 +1178,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
},
},
{
name: "password not matching, precondition error",
name: "password not matching lockout policy not relevant, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
@ -1238,6 +1239,82 @@ func TestCommandSide_CheckPassword(t *testing.T) {
ID: "request1",
AgentID: "agent1",
},
lockoutPolicy: &domain.LockoutPolicy{},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "password not matching, max password attempts reached - user locked, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.German,
domain.GenderUnspecified,
"email@test.ch",
true,
),
),
eventFromEventPusher(
user.NewHumanEmailVerifiedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
),
),
eventFromEventPusher(
user.NewHumanPasswordChangedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeHash,
Algorithm: "hash",
KeyID: "",
Crypted: []byte("password"),
},
false,
"")),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
user.NewHumanPasswordCheckFailedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&user.AuthRequestInfo{
ID: "request1",
UserAgentID: "agent1",
},
),
),
eventFromEventPusher(
user.NewUserLockedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
),
),
},
),
),
userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)),
},
args: args{
ctx: context.Background(),
userID: "user1",
password: "password1",
resourceOwner: "org1",
authReq: &domain.AuthRequest{
ID: "request1",
AgentID: "agent1",
},
lockoutPolicy: &domain.LockoutPolicy{
MaxPasswordAttempts: 1,
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
@ -1315,7 +1392,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
eventstore: tt.fields.eventstore,
userPasswordAlg: tt.fields.userPasswordAlg,
}
err := r.HumanCheckPassword(tt.args.ctx, tt.args.resourceOwner, tt.args.userID, tt.args.password, tt.args.authReq)
err := r.HumanCheckPassword(tt.args.ctx, tt.args.resourceOwner, tt.args.userID, tt.args.password, tt.args.authReq, tt.args.lockoutPolicy)
if tt.res.err == nil {
assert.NoError(t, err)
}

@ -48,6 +48,7 @@ type AuthRequest struct {
AllowedExternalIDPs []*IDPProvider
LabelPolicy *LabelPolicy
PrivacyPolicy *PrivacyPolicy
LockoutPolicy *LockoutPolicy
DefaultTranslations []*CustomText
OrgTranslations []*CustomText
}

@ -22,5 +22,5 @@ type IAM struct {
DefaultOrgIAMPolicy *OrgIAMPolicy
DefaultPasswordComplexityPolicy *PasswordComplexityPolicy
DefaultPasswordAgePolicy *PasswordAgePolicy
DefaultPasswordLockoutPolicy *PasswordLockoutPolicy
DefaultPasswordLockoutPolicy *LockoutPolicy
}

@ -18,7 +18,7 @@ type Org struct {
LabelPolicy *LabelPolicy
PasswordComplexityPolicy *PasswordComplexityPolicy
PasswordAgePolicy *PasswordAgePolicy
PasswordLockoutPolicy *PasswordLockoutPolicy
PasswordLockoutPolicy *LockoutPolicy
IDPs []*IDPConfig
}

@ -4,9 +4,10 @@ import (
"github.com/caos/zitadel/internal/eventstore/v1/models"
)
type PasswordLockoutPolicy struct {
type LockoutPolicy struct {
models.ObjectRoot
MaxAttempts uint64
Default bool
MaxPasswordAttempts uint64
ShowLockOutFailures bool
}

@ -20,6 +20,7 @@ const (
Step15
Step16
Step17
Step18
//StepCount marks the the length of possible steps (StepCount-1 == last possible step)
StepCount
)

@ -35,7 +35,7 @@ type IAM struct {
DefaultOrgIAMPolicy *OrgIAMPolicy
DefaultPasswordComplexityPolicy *PasswordComplexityPolicy
DefaultPasswordAgePolicy *PasswordAgePolicy
DefaultPasswordLockoutPolicy *PasswordLockoutPolicy
DefaultLockoutPolicy *LockoutPolicy
DefaultMailTemplate *MailTemplate
DefaultMailTexts []*MailText
}

@ -4,10 +4,10 @@ import (
"github.com/caos/zitadel/internal/eventstore/v1/models"
)
type PasswordLockoutPolicy struct {
type LockoutPolicy struct {
models.ObjectRoot
State PolicyState
MaxAttempts uint64
MaxPasswordAttempts uint64
ShowLockOutFailures bool
}

@ -5,9 +5,9 @@ import (
"time"
)
type PasswordLockoutPolicyView struct {
type LockoutPolicyView struct {
AggregateID string
MaxAttempts uint64
MaxPasswordAttempts uint64
ShowLockOutFailures bool
Default bool
@ -16,32 +16,32 @@ type PasswordLockoutPolicyView struct {
Sequence uint64
}
type PasswordLockoutPolicySearchRequest struct {
type LockoutPolicySearchRequest struct {
Offset uint64
Limit uint64
SortingColumn PasswordLockoutPolicySearchKey
SortingColumn LockoutPolicySearchKey
Asc bool
Queries []*PasswordLockoutPolicySearchQuery
Queries []*LockoutPolicySearchQuery
}
type PasswordLockoutPolicySearchKey int32
type LockoutPolicySearchKey int32
const (
PasswordLockoutPolicySearchKeyUnspecified PasswordLockoutPolicySearchKey = iota
PasswordLockoutPolicySearchKeyAggregateID
LockoutPolicySearchKeyUnspecified LockoutPolicySearchKey = iota
LockoutPolicySearchKeyAggregateID
)
type PasswordLockoutPolicySearchQuery struct {
Key PasswordLockoutPolicySearchKey
type LockoutPolicySearchQuery struct {
Key LockoutPolicySearchKey
Method domain.SearchMethod
Value interface{}
}
type PasswordLockoutPolicySearchResponse struct {
type LockoutPolicySearchResponse struct {
Offset uint64
Limit uint64
TotalResult uint64
Result []*PasswordLockoutPolicyView
Result []*LockoutPolicyView
Sequence uint64
Timestamp time.Time
}

@ -36,7 +36,7 @@ type IAM struct {
DefaultOrgIAMPolicy *OrgIAMPolicy `json:"-"`
DefaultPasswordComplexityPolicy *PasswordComplexityPolicy `json:"-"`
DefaultPasswordAgePolicy *PasswordAgePolicy `json:"-"`
DefaultPasswordLockoutPolicy *PasswordLockoutPolicy `json:"-"`
DefaultLockoutPolicy *LockoutPolicy `json:"-"`
}
func IAMToModel(iam *IAM) *model.IAM {
@ -66,8 +66,8 @@ func IAMToModel(iam *IAM) *model.IAM {
if iam.DefaultPasswordAgePolicy != nil {
converted.DefaultPasswordAgePolicy = PasswordAgePolicyToModel(iam.DefaultPasswordAgePolicy)
}
if iam.DefaultPasswordLockoutPolicy != nil {
converted.DefaultPasswordLockoutPolicy = PasswordLockoutPolicyToModel(iam.DefaultPasswordLockoutPolicy)
if iam.DefaultLockoutPolicy != nil {
converted.DefaultLockoutPolicy = LockoutPolicyToModel(iam.DefaultLockoutPolicy)
}
if iam.DefaultOrgIAMPolicy != nil {
converted.DefaultOrgIAMPolicy = OrgIAMPolicyToModel(iam.DefaultOrgIAMPolicy)
@ -166,10 +166,10 @@ func (i *IAM) AppendEvent(event *es_models.Event) (err error) {
return i.appendAddPasswordAgePolicyEvent(event)
case PasswordAgePolicyChanged:
return i.appendChangePasswordAgePolicyEvent(event)
case PasswordLockoutPolicyAdded:
return i.appendAddPasswordLockoutPolicyEvent(event)
case PasswordLockoutPolicyChanged:
return i.appendChangePasswordLockoutPolicyEvent(event)
case LockoutPolicyAdded:
return i.appendAddLockoutPolicyEvent(event)
case LockoutPolicyChanged:
return i.appendChangeLockoutPolicyEvent(event)
case OrgIAMPolicyAdded:
return i.appendAddOrgIAMPolicyEvent(event)
case OrgIAMPolicyChanged:

@ -0,0 +1,60 @@
package model
import (
"encoding/json"
"github.com/caos/zitadel/internal/errors"
es_models "github.com/caos/zitadel/internal/eventstore/v1/models"
iam_model "github.com/caos/zitadel/internal/iam/model"
)
type LockoutPolicy struct {
es_models.ObjectRoot
State int32 `json:"-"`
MaxPasswordAttempts uint64 `json:"maxPasswordAttempts"`
ShowLockOutFailures bool `json:"showLockOutFailures"`
}
func LockoutPolicyToModel(policy *LockoutPolicy) *iam_model.LockoutPolicy {
return &iam_model.LockoutPolicy{
ObjectRoot: policy.ObjectRoot,
State: iam_model.PolicyState(policy.State),
MaxPasswordAttempts: policy.MaxPasswordAttempts,
ShowLockOutFailures: policy.ShowLockOutFailures,
}
}
func (p *LockoutPolicy) Changes(changed *LockoutPolicy) map[string]interface{} {
changes := make(map[string]interface{}, 2)
if p.MaxPasswordAttempts != changed.MaxPasswordAttempts {
changes["maxAttempts"] = changed.MaxPasswordAttempts
}
if p.ShowLockOutFailures != changed.ShowLockOutFailures {
changes["showLockOutFailures"] = changed.ShowLockOutFailures
}
return changes
}
func (i *IAM) appendAddLockoutPolicyEvent(event *es_models.Event) error {
i.DefaultLockoutPolicy = new(LockoutPolicy)
err := i.DefaultLockoutPolicy.SetData(event)
if err != nil {
return err
}
i.DefaultLockoutPolicy.ObjectRoot.CreationDate = event.CreationDate
return nil
}
func (i *IAM) appendChangeLockoutPolicyEvent(event *es_models.Event) error {
return i.DefaultLockoutPolicy.SetData(event)
}
func (p *LockoutPolicy) SetData(event *es_models.Event) error {
err := json.Unmarshal(event.Data, p)
if err != nil {
return errors.ThrowInternal(err, "EVENT-7JS9d", "unable to unmarshal data")
}
return nil
}

@ -8,8 +8,8 @@ import (
func TestPasswordLockoutPolicyChanges(t *testing.T) {
type args struct {
existing *PasswordLockoutPolicy
new *PasswordLockoutPolicy
existing *LockoutPolicy
new *LockoutPolicy
}
type res struct {
changesLen int
@ -22,8 +22,8 @@ func TestPasswordLockoutPolicyChanges(t *testing.T) {
{
name: "lockout policy all attributes change",
args: args{
existing: &PasswordLockoutPolicy{MaxAttempts: 365, ShowLockOutFailures: true},
new: &PasswordLockoutPolicy{MaxAttempts: 730, ShowLockOutFailures: false},
existing: &LockoutPolicy{MaxPasswordAttempts: 365, ShowLockOutFailures: true},
new: &LockoutPolicy{MaxPasswordAttempts: 730, ShowLockOutFailures: false},
},
res: res{
changesLen: 2,
@ -32,8 +32,8 @@ func TestPasswordLockoutPolicyChanges(t *testing.T) {
{
name: "no changes",
args: args{
existing: &PasswordLockoutPolicy{MaxAttempts: 10, ShowLockOutFailures: true},
new: &PasswordLockoutPolicy{MaxAttempts: 10, ShowLockOutFailures: true},
existing: &LockoutPolicy{MaxPasswordAttempts: 10, ShowLockOutFailures: true},
new: &LockoutPolicy{MaxPasswordAttempts: 10, ShowLockOutFailures: true},
},
res: res{
changesLen: 0,
@ -53,7 +53,7 @@ func TestPasswordLockoutPolicyChanges(t *testing.T) {
func TestAppendAddPasswordLockoutPolicyEvent(t *testing.T) {
type args struct {
iam *IAM
policy *PasswordLockoutPolicy
policy *LockoutPolicy
event *es_models.Event
}
tests := []struct {
@ -65,10 +65,10 @@ func TestAppendAddPasswordLockoutPolicyEvent(t *testing.T) {
name: "append add password lockout policy event",
args: args{
iam: new(IAM),
policy: &PasswordLockoutPolicy{MaxAttempts: 10, ShowLockOutFailures: true},
policy: &LockoutPolicy{MaxPasswordAttempts: 10, ShowLockOutFailures: true},
event: new(es_models.Event),
},
result: &IAM{DefaultPasswordLockoutPolicy: &PasswordLockoutPolicy{MaxAttempts: 10, ShowLockOutFailures: true}},
result: &IAM{DefaultLockoutPolicy: &LockoutPolicy{MaxPasswordAttempts: 10, ShowLockOutFailures: true}},
},
}
for _, tt := range tests {
@ -77,12 +77,12 @@ func TestAppendAddPasswordLockoutPolicyEvent(t *testing.T) {
data, _ := json.Marshal(tt.args.policy)
tt.args.event.Data = data
}
tt.args.iam.appendAddPasswordLockoutPolicyEvent(tt.args.event)
if tt.result.DefaultPasswordLockoutPolicy.MaxAttempts != tt.args.iam.DefaultPasswordLockoutPolicy.MaxAttempts {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result.DefaultPasswordLockoutPolicy.MaxAttempts, tt.args.iam.DefaultPasswordLockoutPolicy.MaxAttempts)
tt.args.iam.appendAddLockoutPolicyEvent(tt.args.event)
if tt.result.DefaultLockoutPolicy.MaxPasswordAttempts != tt.args.iam.DefaultLockoutPolicy.MaxPasswordAttempts {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result.DefaultLockoutPolicy.MaxPasswordAttempts, tt.args.iam.DefaultLockoutPolicy.MaxPasswordAttempts)
}
if tt.result.DefaultPasswordLockoutPolicy.ShowLockOutFailures != tt.args.iam.DefaultPasswordLockoutPolicy.ShowLockOutFailures {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result.DefaultPasswordLockoutPolicy.ShowLockOutFailures, tt.args.iam.DefaultPasswordLockoutPolicy.ShowLockOutFailures)
if tt.result.DefaultLockoutPolicy.ShowLockOutFailures != tt.args.iam.DefaultLockoutPolicy.ShowLockOutFailures {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result.DefaultLockoutPolicy.ShowLockOutFailures, tt.args.iam.DefaultLockoutPolicy.ShowLockOutFailures)
}
})
}
@ -91,7 +91,7 @@ func TestAppendAddPasswordLockoutPolicyEvent(t *testing.T) {
func TestAppendChangePasswordLockoutPolicyEvent(t *testing.T) {
type args struct {
iam *IAM
policy *PasswordLockoutPolicy
policy *LockoutPolicy
event *es_models.Event
}
tests := []struct {
@ -102,14 +102,14 @@ func TestAppendChangePasswordLockoutPolicyEvent(t *testing.T) {
{
name: "append change password lockout policy event",
args: args{
iam: &IAM{DefaultPasswordLockoutPolicy: &PasswordLockoutPolicy{
MaxAttempts: 10,
iam: &IAM{DefaultLockoutPolicy: &LockoutPolicy{
MaxPasswordAttempts: 10,
}},
policy: &PasswordLockoutPolicy{MaxAttempts: 5},
policy: &LockoutPolicy{MaxPasswordAttempts: 5},
event: &es_models.Event{},
},
result: &IAM{DefaultPasswordLockoutPolicy: &PasswordLockoutPolicy{
MaxAttempts: 5,
result: &IAM{DefaultLockoutPolicy: &LockoutPolicy{
MaxPasswordAttempts: 5,
}},
},
}
@ -119,9 +119,9 @@ func TestAppendChangePasswordLockoutPolicyEvent(t *testing.T) {
data, _ := json.Marshal(tt.args.policy)
tt.args.event.Data = data
}
tt.args.iam.appendChangePasswordLockoutPolicyEvent(tt.args.event)
if tt.result.DefaultPasswordLockoutPolicy.MaxAttempts != tt.args.iam.DefaultPasswordLockoutPolicy.MaxAttempts {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result.DefaultPasswordLockoutPolicy.MaxAttempts, tt.args.iam.DefaultPasswordLockoutPolicy.MaxAttempts)
tt.args.iam.appendChangeLockoutPolicyEvent(tt.args.event)
if tt.result.DefaultLockoutPolicy.MaxPasswordAttempts != tt.args.iam.DefaultLockoutPolicy.MaxPasswordAttempts {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result.DefaultLockoutPolicy.MaxPasswordAttempts, tt.args.iam.DefaultLockoutPolicy.MaxPasswordAttempts)
}
})
}

@ -1,69 +0,0 @@
package model
import (
"encoding/json"
"github.com/caos/zitadel/internal/errors"
es_models "github.com/caos/zitadel/internal/eventstore/v1/models"
iam_model "github.com/caos/zitadel/internal/iam/model"
)
type PasswordLockoutPolicy struct {
es_models.ObjectRoot
State int32 `json:"-"`
MaxAttempts uint64 `json:"maxAttempts"`
ShowLockOutFailures bool `json:"showLockOutFailures"`
}
func PasswordLockoutPolicyFromModel(policy *iam_model.PasswordLockoutPolicy) *PasswordLockoutPolicy {
return &PasswordLockoutPolicy{
ObjectRoot: policy.ObjectRoot,
State: int32(policy.State),
MaxAttempts: policy.MaxAttempts,
ShowLockOutFailures: policy.ShowLockOutFailures,
}
}
func PasswordLockoutPolicyToModel(policy *PasswordLockoutPolicy) *iam_model.PasswordLockoutPolicy {
return &iam_model.PasswordLockoutPolicy{
ObjectRoot: policy.ObjectRoot,
State: iam_model.PolicyState(policy.State),
MaxAttempts: policy.MaxAttempts,
ShowLockOutFailures: policy.ShowLockOutFailures,
}
}
func (p *PasswordLockoutPolicy) Changes(changed *PasswordLockoutPolicy) map[string]interface{} {
changes := make(map[string]interface{}, 2)
if p.MaxAttempts != changed.MaxAttempts {
changes["maxAttempts"] = changed.MaxAttempts
}
if p.ShowLockOutFailures != changed.ShowLockOutFailures {
changes["showLockOutFailures"] = changed.ShowLockOutFailures
}
return changes
}
func (i *IAM) appendAddPasswordLockoutPolicyEvent(event *es_models.Event) error {
i.DefaultPasswordLockoutPolicy = new(PasswordLockoutPolicy)
err := i.DefaultPasswordLockoutPolicy.SetData(event)
if err != nil {
return err
}
i.DefaultPasswordLockoutPolicy.ObjectRoot.CreationDate = event.CreationDate
return nil
}
func (i *IAM) appendChangePasswordLockoutPolicyEvent(event *es_models.Event) error {
return i.DefaultPasswordLockoutPolicy.SetData(event)
}
func (p *PasswordLockoutPolicy) SetData(event *es_models.Event) error {
err := json.Unmarshal(event.Data, p)
if err != nil {
return errors.ThrowInternal(err, "EVENT-7JS9d", "unable to unmarshal data")
}
return nil
}

@ -65,8 +65,8 @@ const (
PasswordAgePolicyAdded models.EventType = "iam.policy.password.age.added"
PasswordAgePolicyChanged models.EventType = "iam.policy.password.age.changed"
PasswordLockoutPolicyAdded models.EventType = "iam.policy.password.lockout.added"
PasswordLockoutPolicyChanged models.EventType = "iam.policy.password.lockout.changed"
LockoutPolicyAdded models.EventType = "iam.policy.lockout.added"
LockoutPolicyChanged models.EventType = "iam.policy.lockout.changed"
PrivacyPolicyAdded models.EventType = "iam.policy.privacy.added"
PrivacyPolicyChanged models.EventType = "iam.policy.privacy.changed"

@ -2,6 +2,7 @@ package model
import (
"encoding/json"
"github.com/caos/zitadel/internal/domain"
org_es_model "github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
"time"
@ -14,65 +15,67 @@ import (
)
const (
PasswordLockoutKeyAggregateID = "aggregate_id"
LockoutKeyAggregateID = "aggregate_id"
)
type PasswordLockoutPolicyView struct {
type LockoutPolicyView struct {
AggregateID string `json:"-" gorm:"column:aggregate_id;primary_key"`
CreationDate time.Time `json:"-" gorm:"column:creation_date"`
ChangeDate time.Time `json:"-" gorm:"column:change_date"`
State int32 `json:"-" gorm:"column:lockout_policy_state"`
MaxAttempts uint64 `json:"maxAttempts" gorm:"column:max_attempts"`
MaxPasswordAttempts uint64 `json:"maxPasswordAttempts" gorm:"column:max_password_attempts"`
ShowLockOutFailures bool `json:"showLockOutFailures" gorm:"column:show_lockout_failures"`
Default bool `json:"-" gorm:"-"`
Sequence uint64 `json:"-" gorm:"column:sequence"`
}
func PasswordLockoutViewFromModel(policy *model.PasswordLockoutPolicyView) *PasswordLockoutPolicyView {
return &PasswordLockoutPolicyView{
func LockoutViewToModel(policy *LockoutPolicyView) *model.LockoutPolicyView {
return &model.LockoutPolicyView{
AggregateID: policy.AggregateID,
Sequence: policy.Sequence,
CreationDate: policy.CreationDate,
ChangeDate: policy.ChangeDate,
MaxAttempts: policy.MaxAttempts,
MaxPasswordAttempts: policy.MaxPasswordAttempts,
ShowLockOutFailures: policy.ShowLockOutFailures,
Default: policy.Default,
}
}
func PasswordLockoutViewToModel(policy *PasswordLockoutPolicyView) *model.PasswordLockoutPolicyView {
return &model.PasswordLockoutPolicyView{
AggregateID: policy.AggregateID,
Sequence: policy.Sequence,
CreationDate: policy.CreationDate,
ChangeDate: policy.ChangeDate,
MaxAttempts: policy.MaxAttempts,
ShowLockOutFailures: policy.ShowLockOutFailures,
Default: policy.Default,
func (p *LockoutPolicyView) ToDomain() *domain.LockoutPolicy {
return &domain.LockoutPolicy{
ObjectRoot: models.ObjectRoot{
AggregateID: p.AggregateID,
CreationDate: p.CreationDate,
ChangeDate: p.ChangeDate,
Sequence: p.Sequence,
},
MaxPasswordAttempts: p.MaxPasswordAttempts,
ShowLockOutFailures: p.ShowLockOutFailures,
Default: p.Default,
}
}
func (i *PasswordLockoutPolicyView) AppendEvent(event *models.Event) (err error) {
func (i *LockoutPolicyView) AppendEvent(event *models.Event) (err error) {
i.Sequence = event.Sequence
i.ChangeDate = event.CreationDate
switch event.Type {
case es_model.PasswordLockoutPolicyAdded, org_es_model.PasswordLockoutPolicyAdded:
case es_model.LockoutPolicyAdded, org_es_model.LockoutPolicyAdded:
i.setRootData(event)
i.CreationDate = event.CreationDate
err = i.SetData(event)
case es_model.PasswordLockoutPolicyChanged, org_es_model.PasswordLockoutPolicyChanged:
case es_model.LockoutPolicyChanged, org_es_model.LockoutPolicyChanged:
err = i.SetData(event)
}
return err
}
func (r *PasswordLockoutPolicyView) setRootData(event *models.Event) {
func (r *LockoutPolicyView) setRootData(event *models.Event) {
r.AggregateID = event.AggregateID
}
func (r *PasswordLockoutPolicyView) SetData(event *models.Event) error {
func (r *LockoutPolicyView) SetData(event *models.Event) error {
if err := json.Unmarshal(event.Data, r); err != nil {
logging.Log("EVEN-gHls0").WithError(err).Error("could not unmarshal event data")
return caos_errs.ThrowInternal(err, "MODEL-Hs8uf", "Could not unmarshal data")

@ -6,53 +6,53 @@ import (
"github.com/caos/zitadel/internal/view/repository"
)
type PasswordLockoutPolicySearchRequest iam_model.PasswordLockoutPolicySearchRequest
type PasswordLockoutPolicySearchQuery iam_model.PasswordLockoutPolicySearchQuery
type PasswordLockoutPolicySearchKey iam_model.PasswordLockoutPolicySearchKey
type LockoutPolicySearchRequest iam_model.LockoutPolicySearchRequest
type LockoutPolicySearchQuery iam_model.LockoutPolicySearchQuery
type LockoutPolicySearchKey iam_model.LockoutPolicySearchKey
func (req PasswordLockoutPolicySearchRequest) GetLimit() uint64 {
func (req LockoutPolicySearchRequest) GetLimit() uint64 {
return req.Limit
}
func (req PasswordLockoutPolicySearchRequest) GetOffset() uint64 {
func (req LockoutPolicySearchRequest) GetOffset() uint64 {
return req.Offset
}
func (req PasswordLockoutPolicySearchRequest) GetSortingColumn() repository.ColumnKey {
if req.SortingColumn == iam_model.PasswordLockoutPolicySearchKeyUnspecified {
func (req LockoutPolicySearchRequest) GetSortingColumn() repository.ColumnKey {
if req.SortingColumn == iam_model.LockoutPolicySearchKeyUnspecified {
return nil
}
return PasswordLockoutPolicySearchKey(req.SortingColumn)
return LockoutPolicySearchKey(req.SortingColumn)
}
func (req PasswordLockoutPolicySearchRequest) GetAsc() bool {
func (req LockoutPolicySearchRequest) GetAsc() bool {
return req.Asc
}
func (req PasswordLockoutPolicySearchRequest) GetQueries() []repository.SearchQuery {
func (req LockoutPolicySearchRequest) GetQueries() []repository.SearchQuery {
result := make([]repository.SearchQuery, len(req.Queries))
for i, q := range req.Queries {
result[i] = PasswordLockoutPolicySearchQuery{Key: q.Key, Value: q.Value, Method: q.Method}
result[i] = LockoutPolicySearchQuery{Key: q.Key, Value: q.Value, Method: q.Method}
}
return result
}
func (req PasswordLockoutPolicySearchQuery) GetKey() repository.ColumnKey {
return PasswordLockoutPolicySearchKey(req.Key)
func (req LockoutPolicySearchQuery) GetKey() repository.ColumnKey {
return LockoutPolicySearchKey(req.Key)
}
func (req PasswordLockoutPolicySearchQuery) GetMethod() domain.SearchMethod {
func (req LockoutPolicySearchQuery) GetMethod() domain.SearchMethod {
return req.Method
}
func (req PasswordLockoutPolicySearchQuery) GetValue() interface{} {
func (req LockoutPolicySearchQuery) GetValue() interface{} {
return req.Value
}
func (key PasswordLockoutPolicySearchKey) ToColumnName() string {
switch iam_model.PasswordLockoutPolicySearchKey(key) {
case iam_model.PasswordLockoutPolicySearchKeyAggregateID:
return PasswordLockoutKeyAggregateID
func (key LockoutPolicySearchKey) ToColumnName() string {
switch iam_model.LockoutPolicySearchKey(key) {
case iam_model.LockoutPolicySearchKeyAggregateID:
return LockoutKeyAggregateID
default:
return ""
}

@ -9,24 +9,24 @@ import (
"github.com/jinzhu/gorm"
)
func GetPasswordLockoutPolicyByAggregateID(db *gorm.DB, table, aggregateID string) (*model.PasswordLockoutPolicyView, error) {
policy := new(model.PasswordLockoutPolicyView)
aggregateIDQuery := &model.PasswordLockoutPolicySearchQuery{Key: iam_model.PasswordLockoutPolicySearchKeyAggregateID, Value: aggregateID, Method: domain.SearchMethodEquals}
func GetLockoutPolicyByAggregateID(db *gorm.DB, table, aggregateID string) (*model.LockoutPolicyView, error) {
policy := new(model.LockoutPolicyView)
aggregateIDQuery := &model.LockoutPolicySearchQuery{Key: iam_model.LockoutPolicySearchKeyAggregateID, Value: aggregateID, Method: domain.SearchMethodEquals}
query := repository.PrepareGetByQuery(table, aggregateIDQuery)
err := query(db, policy)
if caos_errs.IsNotFound(err) {
return nil, caos_errs.ThrowNotFound(nil, "VIEW-M9fsf", "Errors.IAM.PasswordLockoutPolicy.NotExisting")
return nil, caos_errs.ThrowNotFound(nil, "VIEW-M9fsf", "Errors.IAM.LockoutPolicy.NotExisting")
}
return policy, err
}
func PutPasswordLockoutPolicy(db *gorm.DB, table string, policy *model.PasswordLockoutPolicyView) error {
func PutLockoutPolicy(db *gorm.DB, table string, policy *model.LockoutPolicyView) error {
save := repository.PrepareSave(table)
return save(db, policy)
}
func DeletePasswordLockoutPolicy(db *gorm.DB, table, aggregateID string) error {
delete := repository.PrepareDeleteByKey(table, model.PasswordLockoutPolicySearchKey(iam_model.PasswordLockoutPolicySearchKeyAggregateID), aggregateID)
func DeleteLockoutPolicy(db *gorm.DB, table, aggregateID string) error {
delete := repository.PrepareDeleteByKey(table, model.LockoutPolicySearchKey(iam_model.LockoutPolicySearchKeyAggregateID), aggregateID)
return delete(db)
}

@ -501,55 +501,55 @@ func (repo *OrgRepository) GetDefaultPasswordAgePolicy(ctx context.Context) (*ia
return iam_es_model.PasswordAgeViewToModel(policy), nil
}
func (repo *OrgRepository) GetPasswordLockoutPolicy(ctx context.Context) (*iam_model.PasswordLockoutPolicyView, error) {
policy, viewErr := repo.View.PasswordLockoutPolicyByAggregateID(authz.GetCtxData(ctx).OrgID)
func (repo *OrgRepository) GetLockoutPolicy(ctx context.Context) (*iam_model.LockoutPolicyView, error) {
policy, viewErr := repo.View.LockoutPolicyByAggregateID(authz.GetCtxData(ctx).OrgID)
if viewErr != nil && !errors.IsNotFound(viewErr) {
return nil, viewErr
}
if errors.IsNotFound(viewErr) {
policy = new(iam_es_model.PasswordLockoutPolicyView)
policy = new(iam_es_model.LockoutPolicyView)
}
events, esErr := repo.getOrgEvents(ctx, repo.SystemDefaults.IamID, policy.Sequence)
if errors.IsNotFound(viewErr) && len(events) == 0 {
return repo.GetDefaultPasswordLockoutPolicy(ctx)
return repo.GetDefaultLockoutPolicy(ctx)
}
if esErr != nil {
logging.Log("EVENT-mS9od").WithError(esErr).Debug("error retrieving new events")
return iam_es_model.PasswordLockoutViewToModel(policy), nil
return iam_es_model.LockoutViewToModel(policy), nil
}
policyCopy := *policy
for _, event := range events {
if err := policyCopy.AppendEvent(event); err != nil {
return iam_es_model.PasswordLockoutViewToModel(policy), nil
return iam_es_model.LockoutViewToModel(policy), nil
}
}
return iam_es_model.PasswordLockoutViewToModel(policy), nil
return iam_es_model.LockoutViewToModel(policy), nil
}
func (repo *OrgRepository) GetDefaultPasswordLockoutPolicy(ctx context.Context) (*iam_model.PasswordLockoutPolicyView, error) {
policy, viewErr := repo.View.PasswordLockoutPolicyByAggregateID(repo.SystemDefaults.IamID)
func (repo *OrgRepository) GetDefaultLockoutPolicy(ctx context.Context) (*iam_model.LockoutPolicyView, error) {
policy, viewErr := repo.View.LockoutPolicyByAggregateID(repo.SystemDefaults.IamID)
if viewErr != nil && !errors.IsNotFound(viewErr) {
return nil, viewErr
}
if errors.IsNotFound(viewErr) {
policy = new(iam_es_model.PasswordLockoutPolicyView)
policy = new(iam_es_model.LockoutPolicyView)
}
events, esErr := repo.getIAMEvents(ctx, policy.Sequence)
if errors.IsNotFound(viewErr) && len(events) == 0 {
return nil, errors.ThrowNotFound(nil, "EVENT-cmO9s", "Errors.IAM.PasswordLockoutPolicy.NotFound")
return nil, errors.ThrowNotFound(nil, "EVENT-cmO9s", "Errors.IAM.LockoutPolicy.NotFound")
}
if esErr != nil {
logging.Log("EVENT-2Ms9f").WithError(esErr).Debug("error retrieving new events")
return iam_es_model.PasswordLockoutViewToModel(policy), nil
return iam_es_model.LockoutViewToModel(policy), nil
}
policyCopy := *policy
for _, event := range events {
if err := policyCopy.AppendEvent(event); err != nil {
return iam_es_model.PasswordLockoutViewToModel(policy), nil
return iam_es_model.LockoutViewToModel(policy), nil
}
}
policy.Default = true
return iam_es_model.PasswordLockoutViewToModel(policy), nil
return iam_es_model.LockoutViewToModel(policy), nil
}
func (repo *OrgRepository) GetPrivacyPolicy(ctx context.Context) (*iam_model.PrivacyPolicyView, error) {

@ -71,8 +71,8 @@ func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es
handler{view, bulkLimit, configs.cycleDuration("PasswordComplexityPolicy"), errorCount, es}),
newPasswordAgePolicy(
handler{view, bulkLimit, configs.cycleDuration("PasswordAgePolicy"), errorCount, es}),
newPasswordLockoutPolicy(
handler{view, bulkLimit, configs.cycleDuration("PasswordLockoutPolicy"), errorCount, es}),
newLockoutPolicy(
handler{view, bulkLimit, configs.cycleDuration("LockoutPolicy"), errorCount, es}),
newOrgIAMPolicy(
handler{view, bulkLimit, configs.cycleDuration("OrgIAMPolicy"), errorCount, es}),
newMailTemplate(

@ -13,16 +13,16 @@ import (
)
const (
passwordLockoutPolicyTable = "management.password_lockout_policies"
lockoutPolicyTable = "management.lockout_policies"
)
type PasswordLockoutPolicy struct {
type LockoutPolicy struct {
handler
subscription *v1.Subscription
}
func newPasswordLockoutPolicy(handler handler) *PasswordLockoutPolicy {
h := &PasswordLockoutPolicy{
func newLockoutPolicy(handler handler) *LockoutPolicy {
h := &LockoutPolicy{
handler: handler,
}
@ -31,7 +31,7 @@ func newPasswordLockoutPolicy(handler handler) *PasswordLockoutPolicy {
return h
}
func (m *PasswordLockoutPolicy) subscribe() {
func (m *LockoutPolicy) subscribe() {
m.subscription = m.es.Subscribe(m.AggregateTypes()...)
go func() {
for event := range m.subscription.Events {
@ -40,28 +40,28 @@ func (m *PasswordLockoutPolicy) subscribe() {
}()
}
func (p *PasswordLockoutPolicy) ViewModel() string {
return passwordLockoutPolicyTable
func (p *LockoutPolicy) ViewModel() string {
return lockoutPolicyTable
}
func (p *PasswordLockoutPolicy) Subscription() *v1.Subscription {
func (p *LockoutPolicy) Subscription() *v1.Subscription {
return p.subscription
}
func (_ *PasswordLockoutPolicy) AggregateTypes() []es_models.AggregateType {
func (_ *LockoutPolicy) AggregateTypes() []es_models.AggregateType {
return []es_models.AggregateType{model.OrgAggregate, iam_es_model.IAMAggregate}
}
func (p *PasswordLockoutPolicy) CurrentSequence() (uint64, error) {
sequence, err := p.view.GetLatestPasswordLockoutPolicySequence()
func (p *LockoutPolicy) CurrentSequence() (uint64, error) {
sequence, err := p.view.GetLatestLockoutPolicySequence()
if err != nil {
return 0, err
}
return sequence.CurrentSequence, nil
}
func (p *PasswordLockoutPolicy) EventQuery() (*es_models.SearchQuery, error) {
sequence, err := p.view.GetLatestPasswordLockoutPolicySequence()
func (p *LockoutPolicy) EventQuery() (*es_models.SearchQuery, error) {
sequence, err := p.view.GetLatestLockoutPolicySequence()
if err != nil {
return nil, err
}
@ -70,7 +70,7 @@ func (p *PasswordLockoutPolicy) EventQuery() (*es_models.SearchQuery, error) {
LatestSequenceFilter(sequence.CurrentSequence), nil
}
func (p *PasswordLockoutPolicy) Reduce(event *es_models.Event) (err error) {
func (p *LockoutPolicy) Reduce(event *es_models.Event) (err error) {
switch event.AggregateType {
case model.OrgAggregate, iam_es_model.IAMAggregate:
err = p.processPasswordLockoutPolicy(event)
@ -78,33 +78,33 @@ func (p *PasswordLockoutPolicy) Reduce(event *es_models.Event) (err error) {
return err
}
func (p *PasswordLockoutPolicy) processPasswordLockoutPolicy(event *es_models.Event) (err error) {
policy := new(iam_model.PasswordLockoutPolicyView)
func (p *LockoutPolicy) processPasswordLockoutPolicy(event *es_models.Event) (err error) {
policy := new(iam_model.LockoutPolicyView)
switch event.Type {
case iam_es_model.PasswordLockoutPolicyAdded, model.PasswordLockoutPolicyAdded:
case iam_es_model.LockoutPolicyAdded, model.LockoutPolicyAdded:
err = policy.AppendEvent(event)
case iam_es_model.PasswordLockoutPolicyChanged, model.PasswordLockoutPolicyChanged:
policy, err = p.view.PasswordLockoutPolicyByAggregateID(event.AggregateID)
case iam_es_model.LockoutPolicyChanged, model.LockoutPolicyChanged:
policy, err = p.view.LockoutPolicyByAggregateID(event.AggregateID)
if err != nil {
return err
}
err = policy.AppendEvent(event)
case model.PasswordLockoutPolicyRemoved:
return p.view.DeletePasswordLockoutPolicy(event.AggregateID, event)
case model.LockoutPolicyRemoved:
return p.view.DeleteLockoutPolicy(event.AggregateID, event)
default:
return p.view.ProcessedPasswordLockoutPolicySequence(event)
return p.view.ProcessedLockoutPolicySequence(event)
}
if err != nil {
return err
}
return p.view.PutPasswordLockoutPolicy(policy, event)
return p.view.PutLockoutPolicy(policy, event)
}
func (p *PasswordLockoutPolicy) OnError(event *es_models.Event, err error) error {
func (p *LockoutPolicy) OnError(event *es_models.Event, err error) error {
logging.LogWithFields("SPOOL-Bms8f", "id", event.AggregateID).WithError(err).Warn("something went wrong in passwordLockout policy handler")
return spooler.HandleError(event, err, p.view.GetLatestPasswordLockoutPolicyFailedEvent, p.view.ProcessedPasswordLockoutPolicyFailedEvent, p.view.ProcessedPasswordLockoutPolicySequence, p.errorCountUntilSkip)
return spooler.HandleError(event, err, p.view.GetLatestLockoutPolicyFailedEvent, p.view.ProcessedLockoutPolicyFailedEvent, p.view.ProcessedLockoutPolicySequence, p.errorCountUntilSkip)
}
func (p *PasswordLockoutPolicy) OnSuccess() error {
return spooler.HandleSuccess(p.view.UpdatePasswordLockoutPolicySpoolerRunTimestamp)
func (p *LockoutPolicy) OnSuccess() error {
return spooler.HandleSuccess(p.view.UpdateLockoutPolicySpoolerRunTimestamp)
}

@ -0,0 +1,53 @@
package view
import (
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/iam/repository/view"
"github.com/caos/zitadel/internal/iam/repository/view/model"
global_view "github.com/caos/zitadel/internal/view/repository"
)
const (
lockoutPolicyTable = "management.lockout_policies"
)
func (v *View) LockoutPolicyByAggregateID(aggregateID string) (*model.LockoutPolicyView, error) {
return view.GetLockoutPolicyByAggregateID(v.Db, lockoutPolicyTable, aggregateID)
}
func (v *View) PutLockoutPolicy(policy *model.LockoutPolicyView, event *models.Event) error {
err := view.PutLockoutPolicy(v.Db, lockoutPolicyTable, policy)
if err != nil {
return err
}
return v.ProcessedLockoutPolicySequence(event)
}
func (v *View) DeleteLockoutPolicy(aggregateID string, event *models.Event) error {
err := view.DeleteLockoutPolicy(v.Db, lockoutPolicyTable, aggregateID)
if err != nil && !errors.IsNotFound(err) {
return err
}
return v.ProcessedLockoutPolicySequence(event)
}
func (v *View) GetLatestLockoutPolicySequence() (*global_view.CurrentSequence, error) {
return v.latestSequence(lockoutPolicyTable)
}
func (v *View) ProcessedLockoutPolicySequence(event *models.Event) error {
return v.saveCurrentSequence(lockoutPolicyTable, event)
}
func (v *View) UpdateLockoutPolicySpoolerRunTimestamp() error {
return v.updateSpoolerRunSequence(lockoutPolicyTable)
}
func (v *View) GetLatestLockoutPolicyFailedEvent(sequence uint64) (*global_view.FailedEvent, error) {
return v.latestFailedEvent(lockoutPolicyTable, sequence)
}
func (v *View) ProcessedLockoutPolicyFailedEvent(failedEvent *global_view.FailedEvent) error {
return v.saveFailedEvent(failedEvent)
}

@ -1,53 +0,0 @@
package view
import (
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/iam/repository/view"
"github.com/caos/zitadel/internal/iam/repository/view/model"
global_view "github.com/caos/zitadel/internal/view/repository"
)
const (
passwordLockoutPolicyTable = "management.password_lockout_policies"
)
func (v *View) PasswordLockoutPolicyByAggregateID(aggregateID string) (*model.PasswordLockoutPolicyView, error) {
return view.GetPasswordLockoutPolicyByAggregateID(v.Db, passwordLockoutPolicyTable, aggregateID)
}
func (v *View) PutPasswordLockoutPolicy(policy *model.PasswordLockoutPolicyView, event *models.Event) error {
err := view.PutPasswordLockoutPolicy(v.Db, passwordLockoutPolicyTable, policy)
if err != nil {
return err
}
return v.ProcessedPasswordLockoutPolicySequence(event)
}
func (v *View) DeletePasswordLockoutPolicy(aggregateID string, event *models.Event) error {
err := view.DeletePasswordLockoutPolicy(v.Db, passwordLockoutPolicyTable, aggregateID)
if err != nil && !errors.IsNotFound(err) {
return err
}
return v.ProcessedPasswordLockoutPolicySequence(event)
}
func (v *View) GetLatestPasswordLockoutPolicySequence() (*global_view.CurrentSequence, error) {
return v.latestSequence(passwordLockoutPolicyTable)
}
func (v *View) ProcessedPasswordLockoutPolicySequence(event *models.Event) error {
return v.saveCurrentSequence(passwordLockoutPolicyTable, event)
}
func (v *View) UpdatePasswordLockoutPolicySpoolerRunTimestamp() error {
return v.updateSpoolerRunSequence(passwordLockoutPolicyTable)
}
func (v *View) GetLatestPasswordLockoutPolicyFailedEvent(sequence uint64) (*global_view.FailedEvent, error) {
return v.latestFailedEvent(passwordLockoutPolicyTable, sequence)
}
func (v *View) ProcessedPasswordLockoutPolicyFailedEvent(failedEvent *global_view.FailedEvent) error {
return v.saveFailedEvent(failedEvent)
}

@ -42,8 +42,8 @@ type OrgRepository interface {
GetPasswordAgePolicy(ctx context.Context) (*iam_model.PasswordAgePolicyView, error)
GetDefaultPasswordAgePolicy(ctx context.Context) (*iam_model.PasswordAgePolicyView, error)
GetPasswordLockoutPolicy(ctx context.Context) (*iam_model.PasswordLockoutPolicyView, error)
GetDefaultPasswordLockoutPolicy(ctx context.Context) (*iam_model.PasswordLockoutPolicyView, error)
GetLockoutPolicy(ctx context.Context) (*iam_model.LockoutPolicyView, error)
GetDefaultLockoutPolicy(ctx context.Context) (*iam_model.LockoutPolicyView, error)
GetPrivacyPolicy(ctx context.Context) (*iam_model.PrivacyPolicyView, error)
GetDefaultPrivacyPolicy(ctx context.Context) (*iam_model.PrivacyPolicyView, error)

@ -24,7 +24,7 @@ type Org struct {
MailTexts []*iam_model.MailText
PasswordComplexityPolicy *iam_model.PasswordComplexityPolicy
PasswordAgePolicy *iam_model.PasswordAgePolicy
PasswordLockoutPolicy *iam_model.PasswordLockoutPolicy
LockoutPolicy *iam_model.LockoutPolicy
IDPs []*iam_model.IDPConfig
}

@ -30,7 +30,7 @@ type Org struct {
LoginPolicy *iam_es_model.LoginPolicy `json:"-"`
PasswordComplexityPolicy *iam_es_model.PasswordComplexityPolicy `json:"-"`
PasswordAgePolicy *iam_es_model.PasswordAgePolicy `json:"-"`
PasswordLockoutPolicy *iam_es_model.PasswordLockoutPolicy `json:"-"`
LockoutPolicy *iam_es_model.LockoutPolicy `json:"-"`
}
func OrgToModel(org *Org) *org_model.Org {
@ -60,8 +60,8 @@ func OrgToModel(org *Org) *org_model.Org {
if org.PasswordAgePolicy != nil {
converted.PasswordAgePolicy = iam_es_model.PasswordAgePolicyToModel(org.PasswordAgePolicy)
}
if org.PasswordLockoutPolicy != nil {
converted.PasswordLockoutPolicy = iam_es_model.PasswordLockoutPolicyToModel(org.PasswordLockoutPolicy)
if org.LockoutPolicy != nil {
converted.LockoutPolicy = iam_es_model.LockoutPolicyToModel(org.LockoutPolicy)
}
return converted
}
@ -196,12 +196,12 @@ func (o *Org) AppendEvent(event *es_models.Event) (err error) {
err = o.appendChangePasswordAgePolicyEvent(event)
case PasswordAgePolicyRemoved:
o.appendRemovePasswordAgePolicyEvent(event)
case PasswordLockoutPolicyAdded:
err = o.appendAddPasswordLockoutPolicyEvent(event)
case PasswordLockoutPolicyChanged:
err = o.appendChangePasswordLockoutPolicyEvent(event)
case PasswordLockoutPolicyRemoved:
o.appendRemovePasswordLockoutPolicyEvent(event)
case LockoutPolicyAdded:
err = o.appendAddLockoutPolicyEvent(event)
case LockoutPolicyChanged:
err = o.appendChangeLockoutPolicyEvent(event)
case LockoutPolicyRemoved:
o.appendRemoveLockoutPolicyEvent(event)
}
if err != nil {
return err

@ -5,20 +5,20 @@ import (
iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
)
func (o *Org) appendAddPasswordLockoutPolicyEvent(event *es_models.Event) error {
o.PasswordLockoutPolicy = new(iam_es_model.PasswordLockoutPolicy)
err := o.PasswordLockoutPolicy.SetData(event)
func (o *Org) appendAddLockoutPolicyEvent(event *es_models.Event) error {
o.LockoutPolicy = new(iam_es_model.LockoutPolicy)
err := o.LockoutPolicy.SetData(event)
if err != nil {
return err
}
o.PasswordLockoutPolicy.ObjectRoot.CreationDate = event.CreationDate
o.LockoutPolicy.ObjectRoot.CreationDate = event.CreationDate
return nil
}
func (o *Org) appendChangePasswordLockoutPolicyEvent(event *es_models.Event) error {
return o.PasswordLockoutPolicy.SetData(event)
func (o *Org) appendChangeLockoutPolicyEvent(event *es_models.Event) error {
return o.LockoutPolicy.SetData(event)
}
func (o *Org) appendRemovePasswordLockoutPolicyEvent(event *es_models.Event) {
o.PasswordLockoutPolicy = nil
func (o *Org) appendRemoveLockoutPolicyEvent(event *es_models.Event) {
o.LockoutPolicy = nil
}

@ -7,10 +7,10 @@ import (
"testing"
)
func TestAppendAddPasswordLockoutPolicyEvent(t *testing.T) {
func TestAppendAddLockoutPolicyEvent(t *testing.T) {
type args struct {
org *Org
policy *iam_es_model.PasswordLockoutPolicy
policy *iam_es_model.LockoutPolicy
event *es_models.Event
}
tests := []struct {
@ -19,13 +19,13 @@ func TestAppendAddPasswordLockoutPolicyEvent(t *testing.T) {
result *Org
}{
{
name: "append add password age policy event",
name: "append add lockout policy event",
args: args{
org: &Org{},
policy: &iam_es_model.PasswordLockoutPolicy{MaxAttempts: 10},
policy: &iam_es_model.LockoutPolicy{MaxPasswordAttempts: 10},
event: &es_models.Event{},
},
result: &Org{PasswordLockoutPolicy: &iam_es_model.PasswordLockoutPolicy{MaxAttempts: 10}},
result: &Org{LockoutPolicy: &iam_es_model.LockoutPolicy{MaxPasswordAttempts: 10}},
},
}
for _, tt := range tests {
@ -34,18 +34,18 @@ func TestAppendAddPasswordLockoutPolicyEvent(t *testing.T) {
data, _ := json.Marshal(tt.args.policy)
tt.args.event.Data = data
}
tt.args.org.appendAddPasswordLockoutPolicyEvent(tt.args.event)
if tt.result.PasswordLockoutPolicy.MaxAttempts != tt.args.org.PasswordLockoutPolicy.MaxAttempts {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result.PasswordLockoutPolicy.MaxAttempts, tt.args.org.PasswordLockoutPolicy.MaxAttempts)
tt.args.org.appendAddLockoutPolicyEvent(tt.args.event)
if tt.result.LockoutPolicy.MaxPasswordAttempts != tt.args.org.LockoutPolicy.MaxPasswordAttempts {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result.LockoutPolicy.MaxPasswordAttempts, tt.args.org.LockoutPolicy.MaxPasswordAttempts)
}
})
}
}
func TestAppendChangePasswordLockoutPolicyEvent(t *testing.T) {
func TestAppendChangeLockoutPolicyEvent(t *testing.T) {
type args struct {
org *Org
policy *iam_es_model.PasswordLockoutPolicy
policy *iam_es_model.LockoutPolicy
event *es_models.Event
}
tests := []struct {
@ -54,16 +54,16 @@ func TestAppendChangePasswordLockoutPolicyEvent(t *testing.T) {
result *Org
}{
{
name: "append change password age policy event",
name: "append change lockout policy event",
args: args{
org: &Org{PasswordLockoutPolicy: &iam_es_model.PasswordLockoutPolicy{
MaxAttempts: 10,
org: &Org{LockoutPolicy: &iam_es_model.LockoutPolicy{
MaxPasswordAttempts: 10,
}},
policy: &iam_es_model.PasswordLockoutPolicy{MaxAttempts: 5, ShowLockOutFailures: true},
policy: &iam_es_model.LockoutPolicy{MaxPasswordAttempts: 5, ShowLockOutFailures: true},
event: &es_models.Event{},
},
result: &Org{PasswordLockoutPolicy: &iam_es_model.PasswordLockoutPolicy{
MaxAttempts: 5,
result: &Org{LockoutPolicy: &iam_es_model.LockoutPolicy{
MaxPasswordAttempts: 5,
ShowLockOutFailures: true,
}},
},
@ -74,12 +74,12 @@ func TestAppendChangePasswordLockoutPolicyEvent(t *testing.T) {
data, _ := json.Marshal(tt.args.policy)
tt.args.event.Data = data
}
tt.args.org.appendChangePasswordLockoutPolicyEvent(tt.args.event)
if tt.result.PasswordLockoutPolicy.MaxAttempts != tt.args.org.PasswordLockoutPolicy.MaxAttempts {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result.PasswordLockoutPolicy.MaxAttempts, tt.args.org.PasswordLockoutPolicy.MaxAttempts)
tt.args.org.appendChangeLockoutPolicyEvent(tt.args.event)
if tt.result.LockoutPolicy.MaxPasswordAttempts != tt.args.org.LockoutPolicy.MaxPasswordAttempts {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result.LockoutPolicy.MaxPasswordAttempts, tt.args.org.LockoutPolicy.MaxPasswordAttempts)
}
if tt.result.PasswordLockoutPolicy.ShowLockOutFailures != tt.args.org.PasswordLockoutPolicy.ShowLockOutFailures {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result.PasswordLockoutPolicy.ShowLockOutFailures, tt.args.org.PasswordLockoutPolicy.ShowLockOutFailures)
if tt.result.LockoutPolicy.ShowLockOutFailures != tt.args.org.LockoutPolicy.ShowLockOutFailures {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result.LockoutPolicy.ShowLockOutFailures, tt.args.org.LockoutPolicy.ShowLockOutFailures)
}
})
}

@ -89,9 +89,9 @@ const (
PasswordAgePolicyChanged models.EventType = "org.policy.password.age.changed"
PasswordAgePolicyRemoved models.EventType = "org.policy.password.age.removed"
PasswordLockoutPolicyAdded models.EventType = "org.policy.password.lockout.added"
PasswordLockoutPolicyChanged models.EventType = "org.policy.password.lockout.changed"
PasswordLockoutPolicyRemoved models.EventType = "org.policy.password.lockout.removed"
LockoutPolicyAdded models.EventType = "org.policy.lockout.added"
LockoutPolicyChanged models.EventType = "org.policy.lockout.changed"
LockoutPolicyRemoved models.EventType = "org.policy.lockout.removed"
PrivacyPolicyAdded models.EventType = "org.policy.privacy.added"
PrivacyPolicyChanged models.EventType = "org.policy.privacy.changed"

@ -20,7 +20,7 @@ func readModelToIAM(readModel *ReadModel) *model.IAM {
DefaultOrgIAMPolicy: readModelToOrgIAMPolicy(&readModel.DefaultOrgIAMPolicy),
DefaultPasswordAgePolicy: readModelToPasswordAgePolicy(&readModel.DefaultPasswordAgePolicy),
DefaultPasswordComplexityPolicy: readModelToPasswordComplexityPolicy(&readModel.DefaultPasswordComplexityPolicy),
DefaultPasswordLockoutPolicy: readModelToPasswordLockoutPolicy(&readModel.DefaultPasswordLockoutPolicy),
DefaultLockoutPolicy: readModelToPasswordLockoutPolicy(&readModel.DefaultPasswordLockoutPolicy),
IDPs: readModelToIDPConfigs(&readModel.IDPs),
}
}
@ -121,10 +121,10 @@ func readModelToPasswordComplexityPolicy(readModel *IAMPasswordComplexityPolicyR
MinLength: readModel.MinLength,
}
}
func readModelToPasswordLockoutPolicy(readModel *IAMPasswordLockoutPolicyReadModel) *model.PasswordLockoutPolicy {
return &model.PasswordLockoutPolicy{
ObjectRoot: readModelToObjectRoot(readModel.PasswordLockoutPolicyReadModel.ReadModel),
MaxAttempts: readModel.MaxAttempts,
func readModelToPasswordLockoutPolicy(readModel *IAMLockoutPolicyReadModel) *model.LockoutPolicy {
return &model.LockoutPolicy{
ObjectRoot: readModelToObjectRoot(readModel.LockoutPolicyReadModel.ReadModel),
MaxPasswordAttempts: readModel.MaxAttempts,
ShowLockOutFailures: readModel.ShowLockOutFailures,
}
}

@ -25,7 +25,7 @@ type ReadModel struct {
DefaultOrgIAMPolicy IAMOrgIAMPolicyReadModel
DefaultPasswordComplexityPolicy IAMPasswordComplexityPolicyReadModel
DefaultPasswordAgePolicy IAMPasswordAgePolicyReadModel
DefaultPasswordLockoutPolicy IAMPasswordLockoutPolicyReadModel
DefaultPasswordLockoutPolicy IAMLockoutPolicyReadModel
}
func NewReadModel(id string) *ReadModel {
@ -80,8 +80,8 @@ func (rm *ReadModel) AppendEvents(events ...eventstore.EventReader) {
*policy.PasswordAgePolicyChangedEvent:
rm.DefaultPasswordAgePolicy.AppendEvents(event)
case *policy.PasswordLockoutPolicyAddedEvent,
*policy.PasswordLockoutPolicyChangedEvent:
case *policy.LockoutPolicyAddedEvent,
*policy.LockoutPolicyChangedEvent:
rm.DefaultPasswordLockoutPolicy.AppendEvents(event)
}

@ -6,19 +6,19 @@ import (
"github.com/caos/zitadel/internal/repository/policy"
)
type IAMPasswordLockoutPolicyReadModel struct {
PasswordLockoutPolicyReadModel
type IAMLockoutPolicyReadModel struct {
LockoutPolicyReadModel
}
func (rm *IAMPasswordLockoutPolicyReadModel) AppendEvents(events ...eventstore.EventReader) {
func (rm *IAMLockoutPolicyReadModel) AppendEvents(events ...eventstore.EventReader) {
for _, event := range events {
switch e := event.(type) {
case *iam.PasswordLockoutPolicyAddedEvent:
rm.PasswordLockoutPolicyReadModel.AppendEvents(&e.PasswordLockoutPolicyAddedEvent)
case *iam.PasswordLockoutPolicyChangedEvent:
rm.PasswordLockoutPolicyReadModel.AppendEvents(&e.PasswordLockoutPolicyChangedEvent)
case *policy.PasswordLockoutPolicyAddedEvent, *policy.PasswordLockoutPolicyChangedEvent:
rm.PasswordLockoutPolicyReadModel.AppendEvents(e)
case *iam.LockoutPolicyAddedEvent:
rm.LockoutPolicyReadModel.AppendEvents(&e.LockoutPolicyAddedEvent)
case *iam.LockoutPolicyChangedEvent:
rm.LockoutPolicyReadModel.AppendEvents(&e.LockoutPolicyChangedEvent)
case *policy.LockoutPolicyAddedEvent, *policy.LockoutPolicyChangedEvent:
rm.LockoutPolicyReadModel.AppendEvents(e)
}
}
}

@ -7,18 +7,18 @@ import (
)
type OrgPasswordLockoutPolicyReadModel struct {
PasswordLockoutPolicyReadModel
LockoutPolicyReadModel
}
func (rm *OrgPasswordLockoutPolicyReadModel) AppendEvents(events ...eventstore.EventReader) {
for _, event := range events {
switch e := event.(type) {
case *org.PasswordLockoutPolicyAddedEvent:
rm.PasswordLockoutPolicyReadModel.AppendEvents(&e.PasswordLockoutPolicyAddedEvent)
case *org.PasswordLockoutPolicyChangedEvent:
rm.PasswordLockoutPolicyReadModel.AppendEvents(&e.PasswordLockoutPolicyChangedEvent)
case *policy.PasswordLockoutPolicyAddedEvent, *policy.PasswordLockoutPolicyChangedEvent:
rm.PasswordLockoutPolicyReadModel.AppendEvents(e)
case *org.LockoutPolicyAddedEvent:
rm.LockoutPolicyReadModel.AppendEvents(&e.LockoutPolicyAddedEvent)
case *org.LockoutPolicyChangedEvent:
rm.LockoutPolicyReadModel.AppendEvents(&e.LockoutPolicyChangedEvent)
case *policy.LockoutPolicyAddedEvent, *policy.LockoutPolicyChangedEvent:
rm.LockoutPolicyReadModel.AppendEvents(e)
}
}
}

@ -5,22 +5,22 @@ import (
"github.com/caos/zitadel/internal/repository/policy"
)
type PasswordLockoutPolicyReadModel struct {
type LockoutPolicyReadModel struct {
eventstore.ReadModel
MaxAttempts uint64
ShowLockOutFailures bool
}
func (rm *PasswordLockoutPolicyReadModel) Reduce() error {
func (rm *LockoutPolicyReadModel) Reduce() error {
for _, event := range rm.Events {
switch e := event.(type) {
case *policy.PasswordLockoutPolicyAddedEvent:
rm.MaxAttempts = e.MaxAttempts
case *policy.LockoutPolicyAddedEvent:
rm.MaxAttempts = e.MaxPasswordAttempts
rm.ShowLockOutFailures = e.ShowLockOutFailures
case *policy.PasswordLockoutPolicyChangedEvent:
if e.MaxAttempts != nil {
rm.MaxAttempts = *e.MaxAttempts
case *policy.LockoutPolicyChangedEvent:
if e.MaxPasswordAttempts != nil {
rm.MaxAttempts = *e.MaxPasswordAttempts
}
if e.ShowLockOutFailures != nil {
rm.ShowLockOutFailures = *e.ShowLockOutFailures

@ -32,8 +32,8 @@ func RegisterEventMappers(es *eventstore.Eventstore) {
RegisterFilterEventMapper(PasswordAgePolicyChangedEventType, PasswordAgePolicyChangedEventMapper).
RegisterFilterEventMapper(PasswordComplexityPolicyAddedEventType, PasswordComplexityPolicyAddedEventMapper).
RegisterFilterEventMapper(PasswordComplexityPolicyChangedEventType, PasswordComplexityPolicyChangedEventMapper).
RegisterFilterEventMapper(PasswordLockoutPolicyAddedEventType, PasswordLockoutPolicyAddedEventMapper).
RegisterFilterEventMapper(PasswordLockoutPolicyChangedEventType, PasswordLockoutPolicyChangedEventMapper).
RegisterFilterEventMapper(LockoutPolicyAddedEventType, LockoutPolicyAddedEventMapper).
RegisterFilterEventMapper(LockoutPolicyChangedEventType, LockoutPolicyChangedEventMapper).
RegisterFilterEventMapper(PrivacyPolicyAddedEventType, PrivacyPolicyAddedEventMapper).
RegisterFilterEventMapper(PrivacyPolicyChangedEventType, PrivacyPolicyChangedEventMapper).
RegisterFilterEventMapper(MemberAddedEventType, MemberAddedEventMapper).

@ -9,67 +9,67 @@ import (
)
var (
PasswordLockoutPolicyAddedEventType = iamEventTypePrefix + policy.PasswordLockoutPolicyAddedEventType
PasswordLockoutPolicyChangedEventType = iamEventTypePrefix + policy.PasswordLockoutPolicyChangedEventType
LockoutPolicyAddedEventType = iamEventTypePrefix + policy.LockoutPolicyAddedEventType
LockoutPolicyChangedEventType = iamEventTypePrefix + policy.LockoutPolicyChangedEventType
)
type PasswordLockoutPolicyAddedEvent struct {
policy.PasswordLockoutPolicyAddedEvent
type LockoutPolicyAddedEvent struct {
policy.LockoutPolicyAddedEvent
}
func NewPasswordLockoutPolicyAddedEvent(
func NewLockoutPolicyAddedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
maxAttempts uint64,
showLockoutFailure bool,
) *PasswordLockoutPolicyAddedEvent {
return &PasswordLockoutPolicyAddedEvent{
PasswordLockoutPolicyAddedEvent: *policy.NewPasswordLockoutPolicyAddedEvent(
) *LockoutPolicyAddedEvent {
return &LockoutPolicyAddedEvent{
LockoutPolicyAddedEvent: *policy.NewLockoutPolicyAddedEvent(
eventstore.NewBaseEventForPush(
ctx,
aggregate,
PasswordLockoutPolicyAddedEventType),
LockoutPolicyAddedEventType),
maxAttempts,
showLockoutFailure),
}
}
func PasswordLockoutPolicyAddedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
e, err := policy.PasswordLockoutPolicyAddedEventMapper(event)
func LockoutPolicyAddedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
e, err := policy.LockoutPolicyAddedEventMapper(event)
if err != nil {
return nil, err
}
return &PasswordLockoutPolicyAddedEvent{PasswordLockoutPolicyAddedEvent: *e.(*policy.PasswordLockoutPolicyAddedEvent)}, nil
return &LockoutPolicyAddedEvent{LockoutPolicyAddedEvent: *e.(*policy.LockoutPolicyAddedEvent)}, nil
}
type PasswordLockoutPolicyChangedEvent struct {
policy.PasswordLockoutPolicyChangedEvent
type LockoutPolicyChangedEvent struct {
policy.LockoutPolicyChangedEvent
}
func NewPasswordLockoutPolicyChangedEvent(
func NewLockoutPolicyChangedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
changes []policy.PasswordLockoutPolicyChanges,
) (*PasswordLockoutPolicyChangedEvent, error) {
changedEvent, err := policy.NewPasswordLockoutPolicyChangedEvent(
changes []policy.LockoutPolicyChanges,
) (*LockoutPolicyChangedEvent, error) {
changedEvent, err := policy.NewLockoutPolicyChangedEvent(
eventstore.NewBaseEventForPush(
ctx,
aggregate,
PasswordLockoutPolicyChangedEventType),
LockoutPolicyChangedEventType),
changes,
)
if err != nil {
return nil, err
}
return &PasswordLockoutPolicyChangedEvent{PasswordLockoutPolicyChangedEvent: *changedEvent}, nil
return &LockoutPolicyChangedEvent{LockoutPolicyChangedEvent: *changedEvent}, nil
}
func PasswordLockoutPolicyChangedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
e, err := policy.PasswordLockoutPolicyChangedEventMapper(event)
func LockoutPolicyChangedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
e, err := policy.LockoutPolicyChangedEventMapper(event)
if err != nil {
return nil, err
}
return &PasswordLockoutPolicyChangedEvent{PasswordLockoutPolicyChangedEvent: *e.(*policy.PasswordLockoutPolicyChangedEvent)}, nil
return &LockoutPolicyChangedEvent{LockoutPolicyChangedEvent: *e.(*policy.LockoutPolicyChangedEvent)}, nil
}

@ -53,9 +53,9 @@ func RegisterEventMappers(es *eventstore.Eventstore) {
RegisterFilterEventMapper(PasswordComplexityPolicyAddedEventType, PasswordComplexityPolicyAddedEventMapper).
RegisterFilterEventMapper(PasswordComplexityPolicyChangedEventType, PasswordComplexityPolicyChangedEventMapper).
RegisterFilterEventMapper(PasswordComplexityPolicyRemovedEventType, PasswordComplexityPolicyRemovedEventMapper).
RegisterFilterEventMapper(PasswordLockoutPolicyAddedEventType, PasswordLockoutPolicyAddedEventMapper).
RegisterFilterEventMapper(PasswordLockoutPolicyChangedEventType, PasswordLockoutPolicyChangedEventMapper).
RegisterFilterEventMapper(PasswordLockoutPolicyRemovedEventType, PasswordLockoutPolicyRemovedEventMapper).
RegisterFilterEventMapper(LockoutPolicyAddedEventType, LockoutPolicyAddedEventMapper).
RegisterFilterEventMapper(LockoutPolicyChangedEventType, LockoutPolicyChangedEventMapper).
RegisterFilterEventMapper(LockoutPolicyRemovedEventType, LockoutPolicyRemovedEventMapper).
RegisterFilterEventMapper(PrivacyPolicyAddedEventType, PrivacyPolicyAddedEventMapper).
RegisterFilterEventMapper(PrivacyPolicyChangedEventType, PrivacyPolicyChangedEventMapper).
RegisterFilterEventMapper(PrivacyPolicyRemovedEventType, PrivacyPolicyRemovedEventMapper).

@ -9,95 +9,95 @@ import (
)
var (
PasswordLockoutPolicyAddedEventType = orgEventTypePrefix + policy.PasswordLockoutPolicyAddedEventType
PasswordLockoutPolicyChangedEventType = orgEventTypePrefix + policy.PasswordLockoutPolicyChangedEventType
PasswordLockoutPolicyRemovedEventType = orgEventTypePrefix + policy.PasswordLockoutPolicyRemovedEventType
LockoutPolicyAddedEventType = orgEventTypePrefix + policy.LockoutPolicyAddedEventType
LockoutPolicyChangedEventType = orgEventTypePrefix + policy.LockoutPolicyChangedEventType
LockoutPolicyRemovedEventType = orgEventTypePrefix + policy.LockoutPolicyRemovedEventType
)
type PasswordLockoutPolicyAddedEvent struct {
policy.PasswordLockoutPolicyAddedEvent
type LockoutPolicyAddedEvent struct {
policy.LockoutPolicyAddedEvent
}
func NewPasswordLockoutPolicyAddedEvent(
func NewLockoutPolicyAddedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
maxAttempts uint64,
showLockoutFailure bool,
) *PasswordLockoutPolicyAddedEvent {
return &PasswordLockoutPolicyAddedEvent{
PasswordLockoutPolicyAddedEvent: *policy.NewPasswordLockoutPolicyAddedEvent(
) *LockoutPolicyAddedEvent {
return &LockoutPolicyAddedEvent{
LockoutPolicyAddedEvent: *policy.NewLockoutPolicyAddedEvent(
eventstore.NewBaseEventForPush(
ctx,
aggregate,
PasswordLockoutPolicyAddedEventType),
LockoutPolicyAddedEventType),
maxAttempts,
showLockoutFailure),
}
}
func PasswordLockoutPolicyAddedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
e, err := policy.PasswordLockoutPolicyAddedEventMapper(event)
func LockoutPolicyAddedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
e, err := policy.LockoutPolicyAddedEventMapper(event)
if err != nil {
return nil, err
}
return &PasswordLockoutPolicyAddedEvent{PasswordLockoutPolicyAddedEvent: *e.(*policy.PasswordLockoutPolicyAddedEvent)}, nil
return &LockoutPolicyAddedEvent{LockoutPolicyAddedEvent: *e.(*policy.LockoutPolicyAddedEvent)}, nil
}
type PasswordLockoutPolicyChangedEvent struct {
policy.PasswordLockoutPolicyChangedEvent
type LockoutPolicyChangedEvent struct {
policy.LockoutPolicyChangedEvent
}
func NewPasswordLockoutPolicyChangedEvent(
func NewLockoutPolicyChangedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
changes []policy.PasswordLockoutPolicyChanges,
) (*PasswordLockoutPolicyChangedEvent, error) {
changedEvent, err := policy.NewPasswordLockoutPolicyChangedEvent(
changes []policy.LockoutPolicyChanges,
) (*LockoutPolicyChangedEvent, error) {
changedEvent, err := policy.NewLockoutPolicyChangedEvent(
eventstore.NewBaseEventForPush(
ctx,
aggregate,
PasswordLockoutPolicyChangedEventType),
LockoutPolicyChangedEventType),
changes,
)
if err != nil {
return nil, err
}
return &PasswordLockoutPolicyChangedEvent{PasswordLockoutPolicyChangedEvent: *changedEvent}, nil
return &LockoutPolicyChangedEvent{LockoutPolicyChangedEvent: *changedEvent}, nil
}
func PasswordLockoutPolicyChangedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
e, err := policy.PasswordLockoutPolicyChangedEventMapper(event)
func LockoutPolicyChangedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
e, err := policy.LockoutPolicyChangedEventMapper(event)
if err != nil {
return nil, err
}
return &PasswordLockoutPolicyChangedEvent{PasswordLockoutPolicyChangedEvent: *e.(*policy.PasswordLockoutPolicyChangedEvent)}, nil
return &LockoutPolicyChangedEvent{LockoutPolicyChangedEvent: *e.(*policy.LockoutPolicyChangedEvent)}, nil
}
type PasswordLockoutPolicyRemovedEvent struct {
policy.PasswordLockoutPolicyRemovedEvent
type LockoutPolicyRemovedEvent struct {
policy.LockoutPolicyRemovedEvent
}
func NewPasswordLockoutPolicyRemovedEvent(
func NewLockoutPolicyRemovedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
) *PasswordLockoutPolicyRemovedEvent {
return &PasswordLockoutPolicyRemovedEvent{
PasswordLockoutPolicyRemovedEvent: *policy.NewPasswordLockoutPolicyRemovedEvent(
) *LockoutPolicyRemovedEvent {
return &LockoutPolicyRemovedEvent{
LockoutPolicyRemovedEvent: *policy.NewLockoutPolicyRemovedEvent(
eventstore.NewBaseEventForPush(
ctx,
aggregate,
PasswordLockoutPolicyRemovedEventType),
LockoutPolicyRemovedEventType),
),
}
}
func PasswordLockoutPolicyRemovedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
e, err := policy.PasswordLockoutPolicyRemovedEventMapper(event)
func LockoutPolicyRemovedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
e, err := policy.LockoutPolicyRemovedEventMapper(event)
if err != nil {
return nil, err
}
return &PasswordLockoutPolicyRemovedEvent{PasswordLockoutPolicyRemovedEvent: *e.(*policy.PasswordLockoutPolicyRemovedEvent)}, nil
return &LockoutPolicyRemovedEvent{LockoutPolicyRemovedEvent: *e.(*policy.LockoutPolicyRemovedEvent)}, nil
}

@ -9,41 +9,41 @@ import (
)
const (
PasswordLockoutPolicyAddedEventType = "policy.password.lockout.added"
PasswordLockoutPolicyChangedEventType = "policy.password.lockout.changed"
PasswordLockoutPolicyRemovedEventType = "policy.password.lockout.removed"
LockoutPolicyAddedEventType = "policy.lockout.added"
LockoutPolicyChangedEventType = "policy.lockout.changed"
LockoutPolicyRemovedEventType = "policy.lockout.removed"
)
type PasswordLockoutPolicyAddedEvent struct {
type LockoutPolicyAddedEvent struct {
eventstore.BaseEvent `json:"-"`
MaxAttempts uint64 `json:"maxAttempts,omitempty"`
MaxPasswordAttempts uint64 `json:"maxPasswordAttempts,omitempty"`
ShowLockOutFailures bool `json:"showLockOutFailures,omitempty"`
}
func (e *PasswordLockoutPolicyAddedEvent) Data() interface{} {
func (e *LockoutPolicyAddedEvent) Data() interface{} {
return e
}
func (e *PasswordLockoutPolicyAddedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
func (e *LockoutPolicyAddedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
return nil
}
func NewPasswordLockoutPolicyAddedEvent(
func NewLockoutPolicyAddedEvent(
base *eventstore.BaseEvent,
maxAttempts uint64,
showLockOutFailures bool,
) *PasswordLockoutPolicyAddedEvent {
) *LockoutPolicyAddedEvent {
return &PasswordLockoutPolicyAddedEvent{
return &LockoutPolicyAddedEvent{
BaseEvent: *base,
MaxAttempts: maxAttempts,
MaxPasswordAttempts: maxAttempts,
ShowLockOutFailures: showLockOutFailures,
}
}
func PasswordLockoutPolicyAddedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
e := &PasswordLockoutPolicyAddedEvent{
func LockoutPolicyAddedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
e := &LockoutPolicyAddedEvent{
BaseEvent: *eventstore.BaseEventFromRepo(event),
}
@ -55,29 +55,29 @@ func PasswordLockoutPolicyAddedEventMapper(event *repository.Event) (eventstore.
return e, nil
}
type PasswordLockoutPolicyChangedEvent struct {
type LockoutPolicyChangedEvent struct {
eventstore.BaseEvent `json:"-"`
MaxAttempts *uint64 `json:"maxAttempts,omitempty"`
MaxPasswordAttempts *uint64 `json:"maxPasswordAttempts,omitempty"`
ShowLockOutFailures *bool `json:"showLockOutFailures,omitempty"`
}
func (e *PasswordLockoutPolicyChangedEvent) Data() interface{} {
func (e *LockoutPolicyChangedEvent) Data() interface{} {
return e
}
func (e *PasswordLockoutPolicyChangedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
func (e *LockoutPolicyChangedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
return nil
}
func NewPasswordLockoutPolicyChangedEvent(
func NewLockoutPolicyChangedEvent(
base *eventstore.BaseEvent,
changes []PasswordLockoutPolicyChanges,
) (*PasswordLockoutPolicyChangedEvent, error) {
changes []LockoutPolicyChanges,
) (*LockoutPolicyChangedEvent, error) {
if len(changes) == 0 {
return nil, errors.ThrowPreconditionFailed(nil, "POLICY-sdgh6", "Errors.NoChangesFound")
}
changeEvent := &PasswordLockoutPolicyChangedEvent{
changeEvent := &LockoutPolicyChangedEvent{
BaseEvent: *base,
}
for _, change := range changes {
@ -86,22 +86,22 @@ func NewPasswordLockoutPolicyChangedEvent(
return changeEvent, nil
}
type PasswordLockoutPolicyChanges func(*PasswordLockoutPolicyChangedEvent)
type LockoutPolicyChanges func(*LockoutPolicyChangedEvent)
func ChangeMaxAttempts(maxAttempts uint64) func(*PasswordLockoutPolicyChangedEvent) {
return func(e *PasswordLockoutPolicyChangedEvent) {
e.MaxAttempts = &maxAttempts
func ChangeMaxAttempts(maxAttempts uint64) func(*LockoutPolicyChangedEvent) {
return func(e *LockoutPolicyChangedEvent) {
e.MaxPasswordAttempts = &maxAttempts
}
}
func ChangeShowLockOutFailures(showLockOutFailures bool) func(*PasswordLockoutPolicyChangedEvent) {
return func(e *PasswordLockoutPolicyChangedEvent) {
func ChangeShowLockOutFailures(showLockOutFailures bool) func(*LockoutPolicyChangedEvent) {
return func(e *LockoutPolicyChangedEvent) {
e.ShowLockOutFailures = &showLockOutFailures
}
}
func PasswordLockoutPolicyChangedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
e := &PasswordLockoutPolicyChangedEvent{
func LockoutPolicyChangedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
e := &LockoutPolicyChangedEvent{
BaseEvent: *eventstore.BaseEventFromRepo(event),
}
@ -113,26 +113,26 @@ func PasswordLockoutPolicyChangedEventMapper(event *repository.Event) (eventstor
return e, nil
}
type PasswordLockoutPolicyRemovedEvent struct {
type LockoutPolicyRemovedEvent struct {
eventstore.BaseEvent `json:"-"`
}
func (e *PasswordLockoutPolicyRemovedEvent) Data() interface{} {
func (e *LockoutPolicyRemovedEvent) Data() interface{} {
return nil
}
func (e *PasswordLockoutPolicyRemovedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
func (e *LockoutPolicyRemovedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
return nil
}
func NewPasswordLockoutPolicyRemovedEvent(base *eventstore.BaseEvent) *PasswordLockoutPolicyRemovedEvent {
return &PasswordLockoutPolicyRemovedEvent{
func NewLockoutPolicyRemovedEvent(base *eventstore.BaseEvent) *LockoutPolicyRemovedEvent {
return &LockoutPolicyRemovedEvent{
BaseEvent: *base,
}
}
func PasswordLockoutPolicyRemovedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
return &PasswordLockoutPolicyRemovedEvent{
func LockoutPolicyRemovedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
return &LockoutPolicyRemovedEvent{
BaseEvent: *eventstore.BaseEventFromRepo(event),
}, nil
}

@ -23,6 +23,7 @@ type IAMSetUp struct {
Step15 *command.Step15
Step16 *command.Step16
Step17 *command.Step17
Step18 *command.Step18
}
func (setup *IAMSetUp) Steps(currentDone domain.Step) ([]command.Step, error) {
@ -46,6 +47,7 @@ func (setup *IAMSetUp) Steps(currentDone domain.Step) ([]command.Step, error) {
setup.Step15,
setup.Step16,
setup.Step17,
setup.Step18,
} {
if step.Step() <= currentDone {
continue

@ -231,6 +231,10 @@ func (l *Login) renderNextStep(w http.ResponseWriter, r *http.Request, authReq *
}
func (l *Login) renderError(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, err error) {
if err != nil {
l.renderInternalError(w, r, authReq, err)
return
}
if authReq == nil || len(authReq.PossibleSteps) == 0 {
l.renderInternalError(w, r, authReq, caos_errs.ThrowInternal(err, "APP-OVOiT", "no possible steps"))
return
@ -292,7 +296,7 @@ func (l *Login) chooseNextStep(w http.ResponseWriter, r *http.Request, authReq *
func (l *Login) renderInternalError(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, err error) {
var msg string
if err != nil {
msg = err.Error()
_, msg = l.getErrorMessage(r, err)
}
data := l.getBaseData(r, authReq, "Error", "Internal", msg)
l.renderer.RenderTemplate(w, r, l.getTranslator(authReq), l.renderer.Templates[tmplError], data, nil)

@ -288,6 +288,7 @@ Errors:
ConfirmationWrong: Passwort Bestätigung stimmt nicht überein
Empty: Passwort ist leer
Invalid: Passwort ungültig
InvalidAndLocked: Password ist undgültig und Benutzer wurde gesperrt, melden Sie sich bei ihrem Administrator.
PasswordComplexityPolicy:
NotFound: Passwort Policy konnte nicht gefunden werden
MinLength: Passwort ist zu kurz
@ -312,6 +313,7 @@ Errors:
InvalidCode: Code ist ungültig
NotReady: Multifaktor OTP (OneTimePassword) ist nicht bereit
Locked: Benutzer ist gesperrt
SomethingWentWrong: Irgendetwas ist schief gelaufen
NotActive: Benutzer ist nicht aktiv
ExternalIDP:
IDPTypeNotImplemented: IDP Typ ist nicht implementiert
@ -319,5 +321,8 @@ Errors:
GrantRequired: Der Login an diese Applikation ist nicht möglich. Der Benutzer benötigt mindestens eine Berechtigung an der Applikation. Bitte melde dich bei deinem Administrator.
IdentityProvider:
InvalidConfig: Identitäts Provider Konfiguration ist ungültig
IAM:
LockoutPolicy:
NotExisting: Lockout Policy existiert nicht
optional: (optional)

@ -288,6 +288,7 @@ Errors:
ConfirmationWrong: Passwordconfirmation is wrong
Empty: Password is empty
Invalid: Password is invalid
InvalidAndLocked: Password is invalid and user is locked, contact your administrator.
PasswordComplexityPolicy:
NotFound: Password policy not found
MinLength: Password is to short
@ -312,6 +313,7 @@ Errors:
InvalidCode: Invalid code
NotReady: Multifactor OTP (OneTimePassword) isn't ready
Locked: User is locked
SomethingWentWrong: Something went wrong
NotActive: User is not active
ExternalIDP:
IDPTypeNotImplemented: IDP Type is not implemented
@ -319,5 +321,8 @@ Errors:
GrantRequired: Login not possible. The user is required to have at least one grant on the application. Please contact your administrator.
IdentityProvider:
InvalidConfig: Identity Provider configuration is invalid
IAM:
LockoutPolicy:
NotExisting: Lockout Policy not existing
optional: (optional)

@ -0,0 +1,45 @@
CREATE TABLE management.lockout_policies (
aggregate_id TEXT,
creation_date TIMESTAMPTZ,
change_date TIMESTAMPTZ,
lockout_policy_state SMALLINT,
sequence BIGINT,
max_password_attempts BIGINT,
show_lockout_failures BOOLEAN,
PRIMARY KEY (aggregate_id)
);
CREATE TABLE adminapi.lockout_policies (
aggregate_id TEXT,
creation_date TIMESTAMPTZ,
change_date TIMESTAMPTZ,
lockout_policy_state SMALLINT,
sequence BIGINT,
max_password_attempts BIGINT,
show_lockout_failures BOOLEAN,
PRIMARY KEY (aggregate_id)
);
CREATE TABLE auth.lockout_policies (
aggregate_id TEXT,
creation_date TIMESTAMPTZ,
change_date TIMESTAMPTZ,
lockout_policy_state SMALLINT,
sequence BIGINT,
max_password_attempts BIGINT,
show_lockout_failures BOOLEAN,
PRIMARY KEY (aggregate_id)
);
DROP TABLE management.password_lockout_policies;
DROP TABLE adminapi.password_lockout_policies;
DROP TABLE auth.password_lockout_policies;

@ -1425,10 +1425,10 @@ service AdminService {
};
}
//Returns the password lockout policy defined by the administrators of ZITADEL
rpc GetPasswordLockoutPolicy(GetPasswordLockoutPolicyRequest) returns (GetPasswordLockoutPolicyResponse) {
//Returns the lockout policy defined by the administrators of ZITADEL
rpc GetLockoutPolicy(GetLockoutPolicyRequest) returns (GetLockoutPolicyResponse) {
option (google.api.http) = {
get: "/policies/password/lockout";
get: "/policies/lockout";
};
option (zitadel.v1.auth_option) = {
@ -1437,20 +1437,19 @@ service AdminService {
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "policy";
tags: "password policy";
tags: "password lockout policy";
tags: "lockout policy";
responses: {
key: "200";
value: {
description: "default password lockout policy";
description: "default lockout policy";
};
};
};
}
//Updates the default password lockout policy of ZITADEL
//Updates the default lockout policy of ZITADEL
// it impacts all organisations without a customised policy
rpc UpdatePasswordLockoutPolicy(UpdatePasswordLockoutPolicyRequest) returns (UpdatePasswordLockoutPolicyResponse) {
rpc UpdateLockoutPolicy(UpdateLockoutPolicyRequest) returns (UpdateLockoutPolicyResponse) {
option (google.api.http) = {
put: "/policies/password/lockout";
body: "*";
@ -3086,25 +3085,23 @@ message UpdatePasswordAgePolicyResponse {
}
//This is an empty request
message GetPasswordLockoutPolicyRequest {}
message GetLockoutPolicyRequest {}
message GetPasswordLockoutPolicyResponse {
zitadel.policy.v1.PasswordLockoutPolicy policy = 1;
message GetLockoutPolicyResponse {
zitadel.policy.v1.LockoutPolicy policy = 1;
}
message UpdatePasswordLockoutPolicyRequest {
message UpdateLockoutPolicyRequest {
// failed attempts until a user gets locked
uint32 max_attempts = 1 [
uint32 max_password_attempts = 1 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "Maximum attempts before the account gets locked. Attempts are reset as soon as the password is entered correct or the password is reset."
description: "Maximum password check attempts before the account gets locked. Attempts are reset as soon as the password is entered correct or the password is reset."
example: "\"10\""
}
];
// If an error should be displayed during a lockout or not
bool show_lockout_failure = 2;
}
message UpdatePasswordLockoutPolicyResponse {
message UpdateLockoutPolicyResponse {
zitadel.v1.ObjectDetails details = 1;
}

@ -1971,10 +1971,9 @@ service ManagementService {
};
}
// The password lockout policy is not used at the moment
rpc GetPasswordLockoutPolicy(GetPasswordLockoutPolicyRequest) returns (GetPasswordLockoutPolicyResponse) {
rpc GetLockoutPolicy(GetLockoutPolicyRequest) returns (GetLockoutPolicyResponse) {
option (google.api.http) = {
get: "/policies/password/lockout"
get: "/policies/lockout"
};
option (zitadel.v1.auth_option) = {
@ -1982,10 +1981,9 @@ service ManagementService {
};
}
// The password lockout policy is not used at the moment
rpc GetDefaultPasswordLockoutPolicy(GetDefaultPasswordLockoutPolicyRequest) returns (GetDefaultPasswordLockoutPolicyResponse) {
rpc GetDefaultLockoutPolicy(GetDefaultLockoutPolicyRequest) returns (GetDefaultLockoutPolicyResponse) {
option (google.api.http) = {
get: "/policies/default/password/lockout"
get: "/policies/default/lockout"
};
option (zitadel.v1.auth_option) = {
@ -1993,10 +1991,9 @@ service ManagementService {
};
}
// The password lockout policy is not used at the moment
rpc AddCustomPasswordLockoutPolicy(AddCustomPasswordLockoutPolicyRequest) returns (AddCustomPasswordLockoutPolicyResponse) {
rpc AddCustomLockoutPolicy(AddCustomLockoutPolicyRequest) returns (AddCustomLockoutPolicyResponse) {
option (google.api.http) = {
post: "/policies/password/lockout"
post: "/policies/lockout"
body: "*"
};
@ -2005,10 +2002,9 @@ service ManagementService {
};
}
// The password lockout policy is not used at the moment
rpc UpdateCustomPasswordLockoutPolicy(UpdateCustomPasswordLockoutPolicyRequest) returns (UpdateCustomPasswordLockoutPolicyResponse) {
rpc UpdateCustomLockoutPolicy(UpdateCustomLockoutPolicyRequest) returns (UpdateCustomLockoutPolicyResponse) {
option (google.api.http) = {
put: "/policies/password/lockout"
put: "/policies/lockout"
body: "*"
};
@ -2017,10 +2013,9 @@ service ManagementService {
};
}
// The password lockout policy is not used at the moment
rpc ResetPasswordLockoutPolicyToDefault(ResetPasswordLockoutPolicyToDefaultRequest) returns (ResetPasswordLockoutPolicyToDefaultResponse) {
rpc ResetLockoutPolicyToDefault(ResetLockoutPolicyToDefaultRequest) returns (ResetLockoutPolicyToDefaultResponse) {
option (google.api.http) = {
delete: "/policies/password/lockout"
delete: "/policies/lockout"
};
option (zitadel.v1.auth_option) = {
@ -4275,42 +4270,40 @@ message ResetPasswordAgePolicyToDefaultResponse {
}
//This is an empty request
message GetPasswordLockoutPolicyRequest {}
message GetLockoutPolicyRequest {}
message GetPasswordLockoutPolicyResponse {
zitadel.policy.v1.PasswordLockoutPolicy policy = 1;
message GetLockoutPolicyResponse {
zitadel.policy.v1.LockoutPolicy policy = 1;
bool is_default = 2;
}
//This is an empty request
message GetDefaultPasswordLockoutPolicyRequest {}
message GetDefaultLockoutPolicyRequest {}
message GetDefaultPasswordLockoutPolicyResponse {
zitadel.policy.v1.PasswordLockoutPolicy policy = 1;
message GetDefaultLockoutPolicyResponse {
zitadel.policy.v1.LockoutPolicy policy = 1;
}
message AddCustomPasswordLockoutPolicyRequest {
uint32 max_attempts = 1;
bool show_lockout_failure = 2;
message AddCustomLockoutPolicyRequest {
uint32 max_password_attempts = 1;
}
message AddCustomPasswordLockoutPolicyResponse {
message AddCustomLockoutPolicyResponse {
zitadel.v1.ObjectDetails details = 1;
}
message UpdateCustomPasswordLockoutPolicyRequest {
uint32 max_attempts = 1;
bool show_lockout_failure = 2;
message UpdateCustomLockoutPolicyRequest {
uint32 max_password_attempts = 1;
}
message UpdateCustomPasswordLockoutPolicyResponse {
message UpdateCustomLockoutPolicyResponse {
zitadel.v1.ObjectDetails details = 1;
}
//This is an empty request
message ResetPasswordLockoutPolicyToDefaultRequest {}
message ResetLockoutPolicyToDefaultRequest {}
message ResetPasswordLockoutPolicyToDefaultResponse {
message ResetLockoutPolicyToDefaultResponse {
zitadel.v1.ObjectDetails details = 1;
}

Some files were not shown because too many files have changed in this diff Show More