mirror of
https://github.com/zitadel/zitadel.git
synced 2025-05-05 20:00:48 +00:00
feat: provide option to limit (T)OTP checks (#7693)
* feat: provide option to limit (T)OTP checks * fix requests in console * update errors pkg * cleanup * cleanup * improve naming of existing config
This commit is contained in:
parent
e3f10f7e23
commit
153df2e12f
File diff suppressed because one or more lines are too long
@ -17,17 +17,49 @@
|
|||||||
<div class="lockout-content">
|
<div class="lockout-content">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="length-wrapper">
|
<div class="length-wrapper">
|
||||||
<button [disabled]="(['policy.write'] | hasRole | async) === false" mat-icon-button (click)="decrementMaxAttempts()">
|
<button
|
||||||
|
[disabled]="(['policy.write'] | hasRole | async) === false"
|
||||||
|
mat-icon-button
|
||||||
|
(click)="decrementPasswordMaxAttempts()"
|
||||||
|
>
|
||||||
<mat-icon>remove</mat-icon>
|
<mat-icon>remove</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
<span>{{ lockoutData.maxPasswordAttempts }}</span>
|
<span>{{ lockoutData.maxPasswordAttempts }}</span>
|
||||||
<button [disabled]="(['policy.write'] | hasRole | async) === false" mat-icon-button (click)="incrementMaxAttempts()">
|
<button
|
||||||
|
[disabled]="(['policy.write'] | hasRole | async) === false"
|
||||||
|
mat-icon-button
|
||||||
|
(click)="incrementPasswordMaxAttempts()"
|
||||||
|
>
|
||||||
<mat-icon>add</mat-icon>
|
<mat-icon>add</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="number-toggle-row">
|
<div class="number-toggle-row">
|
||||||
<span class="left-desc">{{ 'POLICY.DATA.MAXATTEMPTS' | translate }}</span>
|
<span class="left-desc">{{ 'POLICY.DATA.MAXPASSWORDATTEMPTS' | translate }}</span>
|
||||||
|
<span class="fill-space"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="length-wrapper">
|
||||||
|
<button
|
||||||
|
[disabled]="(['policy.write'] | hasRole | async) === false"
|
||||||
|
mat-icon-button
|
||||||
|
(click)="decrementOTPMaxAttempts()"
|
||||||
|
>
|
||||||
|
<mat-icon>remove</mat-icon>
|
||||||
|
</button>
|
||||||
|
<span>{{ lockoutData.maxOtpAttempts }}</span>
|
||||||
|
<button
|
||||||
|
[disabled]="(['policy.write'] | hasRole | async) === false"
|
||||||
|
mat-icon-button
|
||||||
|
(click)="incrementOTPMaxAttempts()"
|
||||||
|
>
|
||||||
|
<mat-icon>add</mat-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="number-toggle-row">
|
||||||
|
<span class="left-desc">{{ 'POLICY.DATA.MAXOTPATTEMPTS' | translate }}</span>
|
||||||
<span class="fill-space"></span>
|
<span class="fill-space"></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -91,24 +91,36 @@ export class PasswordLockoutPolicyComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public incrementMaxAttempts(): void {
|
public incrementPasswordMaxAttempts(): void {
|
||||||
if (this.lockoutData?.maxPasswordAttempts !== undefined) {
|
if (this.lockoutData?.maxPasswordAttempts !== undefined) {
|
||||||
this.lockoutData.maxPasswordAttempts++;
|
this.lockoutData.maxPasswordAttempts++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public decrementMaxAttempts(): void {
|
public decrementPasswordMaxAttempts(): void {
|
||||||
if (this.lockoutData?.maxPasswordAttempts && this.lockoutData?.maxPasswordAttempts > 0) {
|
if (this.lockoutData?.maxPasswordAttempts && this.lockoutData?.maxPasswordAttempts > 0) {
|
||||||
this.lockoutData.maxPasswordAttempts--;
|
this.lockoutData.maxPasswordAttempts--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public incrementOTPMaxAttempts(): void {
|
||||||
|
if (this.lockoutData?.maxOtpAttempts !== undefined) {
|
||||||
|
this.lockoutData.maxOtpAttempts++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public decrementOTPMaxAttempts(): void {
|
||||||
|
if (this.lockoutData?.maxOtpAttempts && this.lockoutData?.maxOtpAttempts > 0) {
|
||||||
|
this.lockoutData.maxOtpAttempts--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public savePolicy(): void {
|
public savePolicy(): void {
|
||||||
let promise: Promise<any>;
|
let promise: Promise<any>;
|
||||||
if (this.lockoutData) {
|
if (this.lockoutData) {
|
||||||
if (this.service instanceof AdminService) {
|
if (this.service instanceof AdminService) {
|
||||||
promise = this.service
|
promise = this.service
|
||||||
.updateLockoutPolicy(this.lockoutData.maxPasswordAttempts)
|
.updateLockoutPolicy(this.lockoutData.maxPasswordAttempts, this.lockoutData.maxOtpAttempts)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.toast.showInfo('POLICY.TOAST.SET', true);
|
this.toast.showInfo('POLICY.TOAST.SET', true);
|
||||||
this.fetchData();
|
this.fetchData();
|
||||||
@ -119,7 +131,7 @@ export class PasswordLockoutPolicyComponent implements OnInit {
|
|||||||
} else {
|
} else {
|
||||||
if ((this.lockoutData as LockoutPolicy.AsObject).isDefault) {
|
if ((this.lockoutData as LockoutPolicy.AsObject).isDefault) {
|
||||||
promise = (this.service as ManagementService)
|
promise = (this.service as ManagementService)
|
||||||
.addCustomLockoutPolicy(this.lockoutData.maxPasswordAttempts)
|
.addCustomLockoutPolicy(this.lockoutData.maxPasswordAttempts, this.lockoutData.maxOtpAttempts)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.toast.showInfo('POLICY.TOAST.SET', true);
|
this.toast.showInfo('POLICY.TOAST.SET', true);
|
||||||
this.fetchData();
|
this.fetchData();
|
||||||
@ -129,7 +141,7 @@ export class PasswordLockoutPolicyComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
promise = (this.service as ManagementService)
|
promise = (this.service as ManagementService)
|
||||||
.updateCustomLockoutPolicy(this.lockoutData.maxPasswordAttempts)
|
.updateCustomLockoutPolicy(this.lockoutData.maxPasswordAttempts, this.lockoutData.maxOtpAttempts)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.toast.showInfo('POLICY.TOAST.SET', true);
|
this.toast.showInfo('POLICY.TOAST.SET', true);
|
||||||
this.fetchData();
|
this.fetchData();
|
||||||
|
@ -957,9 +957,13 @@ export class AdminService {
|
|||||||
return this.grpcService.admin.getLockoutPolicy(req, null).then((resp) => resp.toObject());
|
return this.grpcService.admin.getLockoutPolicy(req, null).then((resp) => resp.toObject());
|
||||||
}
|
}
|
||||||
|
|
||||||
public updateLockoutPolicy(maxAttempts: number): Promise<UpdateLockoutPolicyResponse.AsObject> {
|
public updateLockoutPolicy(
|
||||||
|
maxPasswordAttempts: number,
|
||||||
|
maxOTPAttempts: number,
|
||||||
|
): Promise<UpdateLockoutPolicyResponse.AsObject> {
|
||||||
const req = new UpdateLockoutPolicyRequest();
|
const req = new UpdateLockoutPolicyRequest();
|
||||||
req.setMaxPasswordAttempts(maxAttempts);
|
req.setMaxPasswordAttempts(maxPasswordAttempts);
|
||||||
|
req.setMaxOtpAttempts(maxOTPAttempts);
|
||||||
|
|
||||||
return this.grpcService.admin.updateLockoutPolicy(req, null).then((resp) => resp.toObject());
|
return this.grpcService.admin.updateLockoutPolicy(req, null).then((resp) => resp.toObject());
|
||||||
}
|
}
|
||||||
|
@ -1587,9 +1587,13 @@ export class ManagementService {
|
|||||||
return this.grpcService.mgmt.getLockoutPolicy(req, null).then((resp) => resp.toObject());
|
return this.grpcService.mgmt.getLockoutPolicy(req, null).then((resp) => resp.toObject());
|
||||||
}
|
}
|
||||||
|
|
||||||
public addCustomLockoutPolicy(maxAttempts: number): Promise<AddCustomLockoutPolicyResponse.AsObject> {
|
public addCustomLockoutPolicy(
|
||||||
|
maxPasswordAttempts: number,
|
||||||
|
maxOTPAttempts: number,
|
||||||
|
): Promise<AddCustomLockoutPolicyResponse.AsObject> {
|
||||||
const req = new AddCustomLockoutPolicyRequest();
|
const req = new AddCustomLockoutPolicyRequest();
|
||||||
req.setMaxPasswordAttempts(maxAttempts);
|
req.setMaxPasswordAttempts(maxPasswordAttempts);
|
||||||
|
req.setMaxOtpAttempts(maxOTPAttempts);
|
||||||
|
|
||||||
return this.grpcService.mgmt.addCustomLockoutPolicy(req, null).then((resp) => resp.toObject());
|
return this.grpcService.mgmt.addCustomLockoutPolicy(req, null).then((resp) => resp.toObject());
|
||||||
}
|
}
|
||||||
@ -1599,9 +1603,13 @@ export class ManagementService {
|
|||||||
return this.grpcService.mgmt.resetLockoutPolicyToDefault(req, null).then((resp) => resp.toObject());
|
return this.grpcService.mgmt.resetLockoutPolicyToDefault(req, null).then((resp) => resp.toObject());
|
||||||
}
|
}
|
||||||
|
|
||||||
public updateCustomLockoutPolicy(maxAttempts: number): Promise<UpdateCustomLockoutPolicyResponse.AsObject> {
|
public updateCustomLockoutPolicy(
|
||||||
|
maxPasswordAttempts: number,
|
||||||
|
maxOTPAttempts: number,
|
||||||
|
): Promise<UpdateCustomLockoutPolicyResponse.AsObject> {
|
||||||
const req = new UpdateCustomLockoutPolicyRequest();
|
const req = new UpdateCustomLockoutPolicyRequest();
|
||||||
req.setMaxPasswordAttempts(maxAttempts);
|
req.setMaxPasswordAttempts(maxPasswordAttempts);
|
||||||
|
req.setMaxOtpAttempts(maxOTPAttempts);
|
||||||
|
|
||||||
return this.grpcService.mgmt.updateCustomLockoutPolicy(req, null).then((resp) => resp.toObject());
|
return this.grpcService.mgmt.updateCustomLockoutPolicy(req, null).then((resp) => resp.toObject());
|
||||||
}
|
}
|
||||||
|
@ -1653,7 +1653,8 @@
|
|||||||
"HASLOWERCASE": "има малки букви",
|
"HASLOWERCASE": "има малки букви",
|
||||||
"HASUPPERCASE": "има главни букви",
|
"HASUPPERCASE": "има главни букви",
|
||||||
"SHOWLOCKOUTFAILURES": "показва грешки при блокиране",
|
"SHOWLOCKOUTFAILURES": "показва грешки при блокиране",
|
||||||
"MAXATTEMPTS": "Максимален брой опити за парола",
|
"MAXPASSWORDATTEMPTS": "Максимален брой опити за парола",
|
||||||
|
"MAXOTPATTEMPTS": "Максимален брой опити за OTP",
|
||||||
"EXPIREWARNDAYS": "Предупреждение за изтичане след ден",
|
"EXPIREWARNDAYS": "Предупреждение за изтичане след ден",
|
||||||
"MAXAGEDAYS": "Максимална възраст в дни",
|
"MAXAGEDAYS": "Максимална възраст в дни",
|
||||||
"USERLOGINMUSTBEDOMAIN": "Добавяне на домейн на организация като суфикс към имената за вход",
|
"USERLOGINMUSTBEDOMAIN": "Добавяне на домейн на организация като суфикс към имената за вход",
|
||||||
|
@ -1660,7 +1660,8 @@
|
|||||||
"HASLOWERCASE": "obsahuje malá písmena",
|
"HASLOWERCASE": "obsahuje malá písmena",
|
||||||
"HASUPPERCASE": "obsahuje velká písmena",
|
"HASUPPERCASE": "obsahuje velká písmena",
|
||||||
"SHOWLOCKOUTFAILURES": "zobrazit neúspěšné pokusy o uzamčení",
|
"SHOWLOCKOUTFAILURES": "zobrazit neúspěšné pokusy o uzamčení",
|
||||||
"MAXATTEMPTS": "Maximální počet pokusů o heslo",
|
"MAXPASSWORDATTEMPTS": "Maximální počet pokusů o heslo",
|
||||||
|
"MAXOTPATTEMPTS": "Maximální počet pokusů o OTP",
|
||||||
"EXPIREWARNDAYS": "Upozornění na expiraci po dni",
|
"EXPIREWARNDAYS": "Upozornění na expiraci po dni",
|
||||||
"MAXAGEDAYS": "Maximální stáří v dnech",
|
"MAXAGEDAYS": "Maximální stáří v dnech",
|
||||||
"USERLOGINMUSTBEDOMAIN": "Přidat doménu organizace jako příponu k přihlašovacím jménům",
|
"USERLOGINMUSTBEDOMAIN": "Přidat doménu organizace jako příponu k přihlašovacím jménům",
|
||||||
|
@ -1659,7 +1659,8 @@
|
|||||||
"HASLOWERCASE": "erfordert Kleinbuchstaben",
|
"HASLOWERCASE": "erfordert Kleinbuchstaben",
|
||||||
"HASUPPERCASE": "erfordert Grossbuchstaben",
|
"HASUPPERCASE": "erfordert Grossbuchstaben",
|
||||||
"SHOWLOCKOUTFAILURES": "Zeige Anzahl Anmeldeversuche",
|
"SHOWLOCKOUTFAILURES": "Zeige Anzahl Anmeldeversuche",
|
||||||
"MAXATTEMPTS": "Maximale Anzahl an Versuchen",
|
"MAXPASSWORDATTEMPTS": "Maximale Anzahl an Passwort Versuchen",
|
||||||
|
"MAXOTPATTEMPTS": "Maximale Anzahl an OTP Versuchen",
|
||||||
"EXPIREWARNDAYS": "Ablauf Warnung nach Tagen",
|
"EXPIREWARNDAYS": "Ablauf Warnung nach Tagen",
|
||||||
"MAXAGEDAYS": "Maximale Gültigkeit in Tagen",
|
"MAXAGEDAYS": "Maximale Gültigkeit in Tagen",
|
||||||
"USERLOGINMUSTBEDOMAIN": "Organisationsdomain dem Loginname hinzufügen",
|
"USERLOGINMUSTBEDOMAIN": "Organisationsdomain dem Loginname hinzufügen",
|
||||||
|
@ -1660,7 +1660,8 @@
|
|||||||
"HASLOWERCASE": "must include a lowercase letter",
|
"HASLOWERCASE": "must include a lowercase letter",
|
||||||
"HASUPPERCASE": "must include an uppercase letter",
|
"HASUPPERCASE": "must include an uppercase letter",
|
||||||
"SHOWLOCKOUTFAILURES": "show lockout failures",
|
"SHOWLOCKOUTFAILURES": "show lockout failures",
|
||||||
"MAXATTEMPTS": "Password maximum Attempts",
|
"MAXPASSWORDATTEMPTS": "Password maximum attempts",
|
||||||
|
"MAXOTPATTEMPTS": "OTP maximum attempts",
|
||||||
"EXPIREWARNDAYS": "Expiration Warning after day",
|
"EXPIREWARNDAYS": "Expiration Warning after day",
|
||||||
"MAXAGEDAYS": "Max Age in days",
|
"MAXAGEDAYS": "Max Age in days",
|
||||||
"USERLOGINMUSTBEDOMAIN": "Add organization domain as suffix to loginnames",
|
"USERLOGINMUSTBEDOMAIN": "Add organization domain as suffix to loginnames",
|
||||||
|
@ -1661,7 +1661,8 @@
|
|||||||
"HASLOWERCASE": "tiene minúsculas",
|
"HASLOWERCASE": "tiene minúsculas",
|
||||||
"HASUPPERCASE": "tiene mayúsculas",
|
"HASUPPERCASE": "tiene mayúsculas",
|
||||||
"SHOWLOCKOUTFAILURES": "mostrar fallos de bloqueo",
|
"SHOWLOCKOUTFAILURES": "mostrar fallos de bloqueo",
|
||||||
"MAXATTEMPTS": "Intentos máximos",
|
"MAXPASSWORDATTEMPTS": "Intentos máximos de contraseña",
|
||||||
|
"MAXOTPATTEMPTS": "Intentos máximos de OTP",
|
||||||
"EXPIREWARNDAYS": "Aviso de expiración después de estos días: ",
|
"EXPIREWARNDAYS": "Aviso de expiración después de estos días: ",
|
||||||
"MAXAGEDAYS": "Antigüedad máxima en días",
|
"MAXAGEDAYS": "Antigüedad máxima en días",
|
||||||
"USERLOGINMUSTBEDOMAIN": "Añadir el dominio de la organización como sufijo de los nombres de inicio de sesión",
|
"USERLOGINMUSTBEDOMAIN": "Añadir el dominio de la organización como sufijo de los nombres de inicio de sesión",
|
||||||
|
@ -1659,7 +1659,8 @@
|
|||||||
"HASLOWERCASE": "a minuscule",
|
"HASLOWERCASE": "a minuscule",
|
||||||
"HASUPPERCASE": "a majuscule",
|
"HASUPPERCASE": "a majuscule",
|
||||||
"SHOWLOCKOUTFAILURES": "montrer les échecs de verrouillage",
|
"SHOWLOCKOUTFAILURES": "montrer les échecs de verrouillage",
|
||||||
"MAXATTEMPTS": "Mot de passe maximum Tentatives",
|
"MAXPASSWORDATTEMPTS": "Mot de passe maximum tentatives",
|
||||||
|
"MAXOTPATTEMPTS": "Maximal de tentatives OTP",
|
||||||
"EXPIREWARNDAYS": "Expiration Avertissement après le jour",
|
"EXPIREWARNDAYS": "Expiration Avertissement après le jour",
|
||||||
"MAXAGEDAYS": "Âge maximum en jours",
|
"MAXAGEDAYS": "Âge maximum en jours",
|
||||||
"USERLOGINMUSTBEDOMAIN": "Le nom de connexion de l'utilisateur doit contenir le nom de domaine de l'organisation",
|
"USERLOGINMUSTBEDOMAIN": "Le nom de connexion de l'utilisateur doit contenir le nom de domaine de l'organisation",
|
||||||
|
@ -1659,7 +1659,8 @@
|
|||||||
"HASLOWERCASE": "ha la minuscola",
|
"HASLOWERCASE": "ha la minuscola",
|
||||||
"HASUPPERCASE": "ha la maiuscola",
|
"HASUPPERCASE": "ha la maiuscola",
|
||||||
"SHOWLOCKOUTFAILURES": "mostra i fallimenti del blocco",
|
"SHOWLOCKOUTFAILURES": "mostra i fallimenti del blocco",
|
||||||
"MAXATTEMPTS": "Massimo numero di tentativi di password",
|
"MAXPASSWORDATTEMPTS": "Massimo numero di tentativi di password",
|
||||||
|
"MAXOTPATTEMPTS": "Massimo numero di tentativi di OTP",
|
||||||
"EXPIREWARNDAYS": "Avviso scadenza dopo il giorno",
|
"EXPIREWARNDAYS": "Avviso scadenza dopo il giorno",
|
||||||
"MAXAGEDAYS": "Lunghezza massima in giorni",
|
"MAXAGEDAYS": "Lunghezza massima in giorni",
|
||||||
"USERLOGINMUSTBEDOMAIN": "Nome utente deve contenere il dominio dell' organizzazione",
|
"USERLOGINMUSTBEDOMAIN": "Nome utente deve contenere il dominio dell' organizzazione",
|
||||||
|
@ -1656,7 +1656,8 @@
|
|||||||
"HASLOWERCASE": "小文字を含める",
|
"HASLOWERCASE": "小文字を含める",
|
||||||
"HASUPPERCASE": "大文字を含める",
|
"HASUPPERCASE": "大文字を含める",
|
||||||
"SHOWLOCKOUTFAILURES": "ロックアウトの失敗を表示する",
|
"SHOWLOCKOUTFAILURES": "ロックアウトの失敗を表示する",
|
||||||
"MAXATTEMPTS": "パスワードの最大試行",
|
"MAXPASSWORDATTEMPTS": "パスワードの最大試行",
|
||||||
|
"MAXOTPATTEMPTS": "最大OTP試行回数",
|
||||||
"EXPIREWARNDAYS": "有効期限の翌日以降の警告",
|
"EXPIREWARNDAYS": "有効期限の翌日以降の警告",
|
||||||
"MAXAGEDAYS": "最大有効期限",
|
"MAXAGEDAYS": "最大有効期限",
|
||||||
"USERLOGINMUSTBEDOMAIN": "ログイン名の接尾辞として組織ドメインを追加する",
|
"USERLOGINMUSTBEDOMAIN": "ログイン名の接尾辞として組織ドメインを追加する",
|
||||||
|
@ -1661,7 +1661,8 @@
|
|||||||
"HASLOWERCASE": "има мали букви",
|
"HASLOWERCASE": "има мали букви",
|
||||||
"HASUPPERCASE": "има големи букви",
|
"HASUPPERCASE": "има големи букви",
|
||||||
"SHOWLOCKOUTFAILURES": "прикажи неуспешни заклучувања",
|
"SHOWLOCKOUTFAILURES": "прикажи неуспешни заклучувања",
|
||||||
"MAXATTEMPTS": "Максимален број на обиди за лозинка",
|
"MAXPASSWORDATTEMPTS": "Максимален број на обиди за лозинка",
|
||||||
|
"MAXOTPATTEMPTS": "Максимални обиди за OTP",
|
||||||
"EXPIREWARNDAYS": "Предупредување за истекување по ден",
|
"EXPIREWARNDAYS": "Предупредување за истекување по ден",
|
||||||
"MAXAGEDAYS": "Максимална возраст во денови",
|
"MAXAGEDAYS": "Максимална возраст во денови",
|
||||||
"USERLOGINMUSTBEDOMAIN": "Додади организациски домен како суфикс на корисничките имиња",
|
"USERLOGINMUSTBEDOMAIN": "Додади организациски домен како суфикс на корисничките имиња",
|
||||||
|
@ -1660,7 +1660,8 @@
|
|||||||
"HASLOWERCASE": "heeft kleine letters",
|
"HASLOWERCASE": "heeft kleine letters",
|
||||||
"HASUPPERCASE": "heeft hoofdletters",
|
"HASUPPERCASE": "heeft hoofdletters",
|
||||||
"SHOWLOCKOUTFAILURES": "toon lockout mislukkingen",
|
"SHOWLOCKOUTFAILURES": "toon lockout mislukkingen",
|
||||||
"MAXATTEMPTS": "Maximum pogingen voor wachtwoord",
|
"MAXPASSWORDATTEMPTS": "Maximum pogingen voor wachtwoord",
|
||||||
|
"MAXOTPATTEMPTS": "Maximale OTP-pogingen",
|
||||||
"EXPIREWARNDAYS": "Vervaldatum Waarschuwing na dag",
|
"EXPIREWARNDAYS": "Vervaldatum Waarschuwing na dag",
|
||||||
"MAXAGEDAYS": "Maximale Leeftijd in dagen",
|
"MAXAGEDAYS": "Maximale Leeftijd in dagen",
|
||||||
"USERLOGINMUSTBEDOMAIN": "Voeg organisatie domein toe als achtervoegsel aan inlognamen",
|
"USERLOGINMUSTBEDOMAIN": "Voeg organisatie domein toe als achtervoegsel aan inlognamen",
|
||||||
|
@ -1659,7 +1659,8 @@
|
|||||||
"HASLOWERCASE": "zawiera małe litery",
|
"HASLOWERCASE": "zawiera małe litery",
|
||||||
"HASUPPERCASE": "zawiera duże litery",
|
"HASUPPERCASE": "zawiera duże litery",
|
||||||
"SHOWLOCKOUTFAILURES": "pokaż blokady nieudanych prób",
|
"SHOWLOCKOUTFAILURES": "pokaż blokady nieudanych prób",
|
||||||
"MAXATTEMPTS": "Maksymalna liczba prób wprowadzenia hasła",
|
"MAXPASSWORDATTEMPTS": "Maksymalna liczba prób wprowadzenia hasła",
|
||||||
|
"MAXOTPATTEMPTS": "Maksymalna liczba prób OTP",
|
||||||
"EXPIREWARNDAYS": "Ostrzeżenie o wygaśnięciu po dniu",
|
"EXPIREWARNDAYS": "Ostrzeżenie o wygaśnięciu po dniu",
|
||||||
"MAXAGEDAYS": "Maksymalny wiek w dniach",
|
"MAXAGEDAYS": "Maksymalny wiek w dniach",
|
||||||
"USERLOGINMUSTBEDOMAIN": "Dodaj domenę organizacji jako przyrostek do nazw logowania",
|
"USERLOGINMUSTBEDOMAIN": "Dodaj domenę organizacji jako przyrostek do nazw logowania",
|
||||||
|
@ -1661,7 +1661,8 @@
|
|||||||
"HASLOWERCASE": "tem letra minúscula",
|
"HASLOWERCASE": "tem letra minúscula",
|
||||||
"HASUPPERCASE": "tem letra maiúscula",
|
"HASUPPERCASE": "tem letra maiúscula",
|
||||||
"SHOWLOCKOUTFAILURES": "mostrar falhas de bloqueio",
|
"SHOWLOCKOUTFAILURES": "mostrar falhas de bloqueio",
|
||||||
"MAXATTEMPTS": "Máximo de tentativas de senha",
|
"MAXPASSWORDATTEMPTS": "Máximo de tentativas de senha",
|
||||||
|
"MAXOTPATTEMPTS": "Máximo de tentativas de OTP",
|
||||||
"EXPIREWARNDAYS": "Aviso de expiração após dias",
|
"EXPIREWARNDAYS": "Aviso de expiração após dias",
|
||||||
"MAXAGEDAYS": "Idade máxima em dias",
|
"MAXAGEDAYS": "Idade máxima em dias",
|
||||||
"USERLOGINMUSTBEDOMAIN": "Adicionar domínio da organização como sufixo aos nomes de login",
|
"USERLOGINMUSTBEDOMAIN": "Adicionar domínio da organização como sufixo aos nomes de login",
|
||||||
|
@ -1729,7 +1729,8 @@
|
|||||||
"HASLOWERCASE": "Содержит нижний регистр",
|
"HASLOWERCASE": "Содержит нижний регистр",
|
||||||
"HASUPPERCASE": "Содержит верхний регистр",
|
"HASUPPERCASE": "Содержит верхний регистр",
|
||||||
"SHOWLOCKOUTFAILURES": "Показать ошибки блокировки",
|
"SHOWLOCKOUTFAILURES": "Показать ошибки блокировки",
|
||||||
"MAXATTEMPTS": "Максимальное количество попыток пароля",
|
"MAXPASSWORDATTEMPTS": "Максимальное количество попыток пароля",
|
||||||
|
"MAXOTPATTEMPTS": "Максимальное количество попыток OTP",
|
||||||
"EXPIREWARNDAYS": "Предупреждение об истечении срока действия после дня",
|
"EXPIREWARNDAYS": "Предупреждение об истечении срока действия после дня",
|
||||||
"MAXAGEDAYS": "Максимальный возраст в днях",
|
"MAXAGEDAYS": "Максимальный возраст в днях",
|
||||||
"USERLOGINMUSTBEDOMAIN": "Добавить домен организации в качестве суффикса к именам логина",
|
"USERLOGINMUSTBEDOMAIN": "Добавить домен организации в качестве суффикса к именам логина",
|
||||||
|
@ -1658,7 +1658,8 @@
|
|||||||
"HASLOWERCASE": "包含小写字母",
|
"HASLOWERCASE": "包含小写字母",
|
||||||
"HASUPPERCASE": "包含大写字母",
|
"HASUPPERCASE": "包含大写字母",
|
||||||
"SHOWLOCKOUTFAILURES": "显示锁定失败",
|
"SHOWLOCKOUTFAILURES": "显示锁定失败",
|
||||||
"MAXATTEMPTS": "密码最大尝试次数",
|
"MAXPASSWORDATTEMPTS": "密码最大尝试次数",
|
||||||
|
"MAXOTPATTEMPTS": "最多尝试 OTP 次数",
|
||||||
"EXPIREWARNDAYS": "密码过期警告",
|
"EXPIREWARNDAYS": "密码过期警告",
|
||||||
"MAXAGEDAYS": "Max Age in days",
|
"MAXAGEDAYS": "Max Age in days",
|
||||||
"USERLOGINMUSTBEDOMAIN": "用户名必须包含组织域名",
|
"USERLOGINMUSTBEDOMAIN": "用户名必须包含组织域名",
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -20,7 +20,7 @@ When you configure your default settings, you can set the following:
|
|||||||
- [**Login Behavior and Access**](#login-behavior-and-access): Multifactor Authentication Options and Enforcement, Define whether Passwordless authentication methods are allowed or not, Set Login Lifetimes and advanced behavour for the login interface.
|
- [**Login Behavior and Access**](#login-behavior-and-access): Multifactor Authentication Options and Enforcement, Define whether Passwordless authentication methods are allowed or not, Set Login Lifetimes and advanced behavour for the login interface.
|
||||||
- [**Identity Providers**](#identity-providers): Define IDPs which are available for all organizations
|
- [**Identity Providers**](#identity-providers): Define IDPs which are available for all organizations
|
||||||
- [**Password Complexity**](#password-complexity): Requirements for Passwords ex. Symbols, Numbers, min length and more.
|
- [**Password Complexity**](#password-complexity): Requirements for Passwords ex. Symbols, Numbers, min length and more.
|
||||||
- [**Lockout**](#lockout): Set the maximum attempts a user can try to enter the password. When the number is exceeded, the user gets locked out and has to be unlocked.
|
- [**Lockout**](#lockout): Set the maximum attempts a user can try to enter the password or any (T)OTP method. When the number is exceeded, the user gets locked out and has to be unlocked.
|
||||||
- [**Domain settings**](#domain-settings): Whether users use their email or the generated username to login. Other Validation, SMTP settings
|
- [**Domain settings**](#domain-settings): Whether users use their email or the generated username to login. Other Validation, SMTP settings
|
||||||
- [**Branding**](#branding): Appearance of the login interface.
|
- [**Branding**](#branding): Appearance of the login interface.
|
||||||
- [**Message Texts**](#message-texts): Text and internationalization for emails
|
- [**Message Texts**](#message-texts): Text and internationalization for emails
|
||||||
@ -189,6 +189,7 @@ Define when an account should be locked.
|
|||||||
The following settings are available:
|
The following settings are available:
|
||||||
|
|
||||||
- Maximum Password Attempts: When the user has reached the maximum password attempts the account will be locked, If this is set to 0 the lockout will not trigger.
|
- Maximum Password Attempts: When the user has reached the maximum password attempts the account will be locked, If this is set to 0 the lockout will not trigger.
|
||||||
|
- Maximum OTP Attempts: When the user has reached the maximum (T)OTP attempts the account will be locked, If this is set to 0 the lockout will not trigger.
|
||||||
|
|
||||||
If an account is locked, the administrator has to unlock it in the ZITADEL console
|
If an account is locked, the administrator has to unlock it in the ZITADEL console
|
||||||
|
|
||||||
|
@ -108,7 +108,7 @@ Those settings are the same as on your instance.
|
|||||||
- [**Login Behavior and Access**](./default-settings#login-behaviour-and-access): Multifactor Authentication Options and Enforcement, Define whether Passwordless authentication methods are allowed or not, Set Login Lifetimes and advanced behavour for the login interface.
|
- [**Login Behavior and Access**](./default-settings#login-behaviour-and-access): Multifactor Authentication Options and Enforcement, Define whether Passwordless authentication methods are allowed or not, Set Login Lifetimes and advanced behavour for the login interface.
|
||||||
- [**Identity Providers**](./default-settings#identity-providers): Define IDPs which are available for all organizations
|
- [**Identity Providers**](./default-settings#identity-providers): Define IDPs which are available for all organizations
|
||||||
- [**Password Complexity**](./default-settings#password-complexity): Requirements for Passwords ex. Symbols, Numbers, min length and more.
|
- [**Password Complexity**](./default-settings#password-complexity): Requirements for Passwords ex. Symbols, Numbers, min length and more.
|
||||||
- [**Lockout**](./default-settings#lockout): Set the maximum attempts a user can try to enter the password. When the number is exceeded, the user gets locked out and has to be unlocked.
|
- [**Lockout**](./default-settings#lockout): Set the maximum attempts a user can try to enter the password or any (T)OTP method. When the number is exceeded, the user gets locked out and has to be unlocked.
|
||||||
- [**Verified domains**](/docs/guides/manage/console/organizations#verify-your-domain-name): This is where you manage your organization specific domains which can be used to build usernames
|
- [**Verified domains**](/docs/guides/manage/console/organizations#verify-your-domain-name): This is where you manage your organization specific domains which can be used to build usernames
|
||||||
- [**Domain settings**](./default-settings#domain-settings): Whether users use their email or the generated username to login. Other Validation, SMTP settings
|
- [**Domain settings**](./default-settings#domain-settings): Whether users use their email or the generated username to login. Other Validation, SMTP settings
|
||||||
- [**Branding**](./default-settings#branding): Appearance of the login interface.
|
- [**Branding**](./default-settings#branding): Appearance of the login interface.
|
||||||
|
BIN
docs/static/img/guides/console/lockout.png
vendored
BIN
docs/static/img/guides/console/lockout.png
vendored
Binary file not shown.
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 14 KiB |
@ -491,13 +491,14 @@ func (s *Server) getLockoutPolicy(ctx context.Context, orgID string) (_ *managem
|
|||||||
ctx, span := tracing.NewSpan(ctx)
|
ctx, span := tracing.NewSpan(ctx)
|
||||||
defer func() { span.EndWithError(err) }()
|
defer func() { span.EndWithError(err) }()
|
||||||
|
|
||||||
queriedLockout, err := s.query.LockoutPolicyByOrg(ctx, false, orgID, false)
|
queriedLockout, err := s.query.LockoutPolicyByOrg(ctx, false, orgID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if !queriedLockout.IsDefault {
|
if !queriedLockout.IsDefault {
|
||||||
return &management_pb.AddCustomLockoutPolicyRequest{
|
return &management_pb.AddCustomLockoutPolicyRequest{
|
||||||
MaxPasswordAttempts: uint32(queriedLockout.MaxPasswordAttempts),
|
MaxPasswordAttempts: uint32(queriedLockout.MaxPasswordAttempts),
|
||||||
|
MaxOtpAttempts: uint32(queriedLockout.MaxOTPAttempts),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
@ -8,5 +8,6 @@ import (
|
|||||||
func UpdateLockoutPolicyToDomain(p *admin.UpdateLockoutPolicyRequest) *domain.LockoutPolicy {
|
func UpdateLockoutPolicyToDomain(p *admin.UpdateLockoutPolicyRequest) *domain.LockoutPolicy {
|
||||||
return &domain.LockoutPolicy{
|
return &domain.LockoutPolicy{
|
||||||
MaxPasswordAttempts: uint64(p.MaxPasswordAttempts),
|
MaxPasswordAttempts: uint64(p.MaxPasswordAttempts),
|
||||||
|
MaxOTPAttempts: uint64(p.MaxOtpAttempts),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (s *Server) GetLockoutPolicy(ctx context.Context, req *mgmt_pb.GetLockoutPolicyRequest) (*mgmt_pb.GetLockoutPolicyResponse, error) {
|
func (s *Server) GetLockoutPolicy(ctx context.Context, req *mgmt_pb.GetLockoutPolicyRequest) (*mgmt_pb.GetLockoutPolicyResponse, error) {
|
||||||
policy, err := s.query.LockoutPolicyByOrg(ctx, true, authz.GetCtxData(ctx).OrgID, false)
|
policy, err := s.query.LockoutPolicyByOrg(ctx, true, authz.GetCtxData(ctx).OrgID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -8,11 +8,13 @@ import (
|
|||||||
func AddLockoutPolicyToDomain(p *mgmt.AddCustomLockoutPolicyRequest) *domain.LockoutPolicy {
|
func AddLockoutPolicyToDomain(p *mgmt.AddCustomLockoutPolicyRequest) *domain.LockoutPolicy {
|
||||||
return &domain.LockoutPolicy{
|
return &domain.LockoutPolicy{
|
||||||
MaxPasswordAttempts: uint64(p.MaxPasswordAttempts),
|
MaxPasswordAttempts: uint64(p.MaxPasswordAttempts),
|
||||||
|
MaxOTPAttempts: uint64(p.MaxOtpAttempts),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func UpdateLockoutPolicyToDomain(p *mgmt.UpdateCustomLockoutPolicyRequest) *domain.LockoutPolicy {
|
func UpdateLockoutPolicyToDomain(p *mgmt.UpdateCustomLockoutPolicyRequest) *domain.LockoutPolicy {
|
||||||
return &domain.LockoutPolicy{
|
return &domain.LockoutPolicy{
|
||||||
MaxPasswordAttempts: uint64(p.MaxPasswordAttempts),
|
MaxPasswordAttempts: uint64(p.MaxPasswordAttempts),
|
||||||
|
MaxOTPAttempts: uint64(p.MaxOtpAttempts),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ func ModelLockoutPolicyToPb(policy *query.LockoutPolicy) *policy_pb.LockoutPolic
|
|||||||
return &policy_pb.LockoutPolicy{
|
return &policy_pb.LockoutPolicy{
|
||||||
IsDefault: policy.IsDefault,
|
IsDefault: policy.IsDefault,
|
||||||
MaxPasswordAttempts: policy.MaxPasswordAttempts,
|
MaxPasswordAttempts: policy.MaxPasswordAttempts,
|
||||||
|
MaxOtpAttempts: policy.MaxOTPAttempts,
|
||||||
Details: object.ToViewDetailsPb(
|
Details: object.ToViewDetailsPb(
|
||||||
policy.Sequence,
|
policy.Sequence,
|
||||||
policy.CreationDate,
|
policy.CreationDate,
|
||||||
|
@ -90,7 +90,7 @@ func (s *Server) GetLegalAndSupportSettings(ctx context.Context, req *settings.G
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) GetLockoutSettings(ctx context.Context, req *settings.GetLockoutSettingsRequest) (*settings.GetLockoutSettingsResponse, error) {
|
func (s *Server) GetLockoutSettings(ctx context.Context, req *settings.GetLockoutSettingsRequest) (*settings.GetLockoutSettingsResponse, error) {
|
||||||
current, err := s.query.LockoutPolicyByOrg(ctx, true, object.ResourceOwnerFromReq(ctx, req.GetCtx()), false)
|
current, err := s.query.LockoutPolicyByOrg(ctx, true, object.ResourceOwnerFromReq(ctx, req.GetCtx()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -160,6 +160,7 @@ func legalAndSupportSettingsToPb(current *query.PrivacyPolicy) *settings.LegalAn
|
|||||||
func lockoutSettingsToPb(current *query.LockoutPolicy) *settings.LockoutSettings {
|
func lockoutSettingsToPb(current *query.LockoutPolicy) *settings.LockoutSettings {
|
||||||
return &settings.LockoutSettings{
|
return &settings.LockoutSettings{
|
||||||
MaxPasswordAttempts: current.MaxPasswordAttempts,
|
MaxPasswordAttempts: current.MaxPasswordAttempts,
|
||||||
|
MaxOtpAttempts: current.MaxOTPAttempts,
|
||||||
ResourceOwnerType: isDefaultToResourceOwnerTypePb(current.IsDefault),
|
ResourceOwnerType: isDefaultToResourceOwnerTypePb(current.IsDefault),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -339,10 +339,12 @@ func Test_legalSettingsToPb(t *testing.T) {
|
|||||||
func Test_lockoutSettingsToPb(t *testing.T) {
|
func Test_lockoutSettingsToPb(t *testing.T) {
|
||||||
arg := &query.LockoutPolicy{
|
arg := &query.LockoutPolicy{
|
||||||
MaxPasswordAttempts: 22,
|
MaxPasswordAttempts: 22,
|
||||||
|
MaxOTPAttempts: 22,
|
||||||
IsDefault: true,
|
IsDefault: true,
|
||||||
}
|
}
|
||||||
want := &settings.LockoutSettings{
|
want := &settings.LockoutSettings{
|
||||||
MaxPasswordAttempts: 22,
|
MaxPasswordAttempts: 22,
|
||||||
|
MaxOtpAttempts: 22,
|
||||||
ResourceOwnerType: settings.ResourceOwnerType_RESOURCE_OWNER_TYPE_INSTANCE,
|
ResourceOwnerType: settings.ResourceOwnerType_RESOURCE_OWNER_TYPE_INSTANCE,
|
||||||
}
|
}
|
||||||
got := lockoutSettingsToPb(arg)
|
got := lockoutSettingsToPb(arg)
|
||||||
|
@ -75,7 +75,7 @@ type loginPolicyViewProvider interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type lockoutPolicyViewProvider interface {
|
type lockoutPolicyViewProvider interface {
|
||||||
LockoutPolicyByOrg(context.Context, bool, string, bool) (*query.LockoutPolicy, error)
|
LockoutPolicyByOrg(context.Context, bool, string) (*query.LockoutPolicy, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type idpProviderViewProvider interface {
|
type idpProviderViewProvider interface {
|
||||||
@ -366,6 +366,7 @@ func lockoutPolicyToDomain(policy *query.LockoutPolicy) *domain.LockoutPolicy {
|
|||||||
},
|
},
|
||||||
Default: policy.IsDefault,
|
Default: policy.IsDefault,
|
||||||
MaxPasswordAttempts: policy.MaxPasswordAttempts,
|
MaxPasswordAttempts: policy.MaxPasswordAttempts,
|
||||||
|
MaxOTPAttempts: policy.MaxOTPAttempts,
|
||||||
ShowLockOutFailures: policy.ShowFailures,
|
ShowLockOutFailures: policy.ShowFailures,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1281,7 +1282,7 @@ func privacyPolicyToDomain(p *query.PrivacyPolicy) *domain.PrivacyPolicy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (repo *AuthRequestRepo) getLockoutPolicy(ctx context.Context, orgID string) (*query.LockoutPolicy, error) {
|
func (repo *AuthRequestRepo) getLockoutPolicy(ctx context.Context, orgID string) (*query.LockoutPolicy, error) {
|
||||||
policy, err := repo.LockoutPolicyViewProvider.LockoutPolicyByOrg(ctx, false, orgID, false)
|
policy, err := repo.LockoutPolicyViewProvider.LockoutPolicyByOrg(ctx, false, orgID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -184,7 +184,7 @@ type mockLockoutPolicy struct {
|
|||||||
policy *query.LockoutPolicy
|
policy *query.LockoutPolicy
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mockLockoutPolicy) LockoutPolicyByOrg(context.Context, bool, string, bool) (*query.LockoutPolicy, error) {
|
func (m *mockLockoutPolicy) LockoutPolicyByOrg(context.Context, bool, string) (*query.LockoutPolicy, error) {
|
||||||
return m.policy, nil
|
return m.policy, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,7 +101,8 @@ type InstanceSetup struct {
|
|||||||
ThemeMode domain.LabelPolicyThemeMode
|
ThemeMode domain.LabelPolicyThemeMode
|
||||||
}
|
}
|
||||||
LockoutPolicy struct {
|
LockoutPolicy struct {
|
||||||
MaxAttempts uint64
|
MaxPasswordAttempts uint64
|
||||||
|
MaxOTPAttempts uint64
|
||||||
ShouldShowLockoutFailure bool
|
ShouldShowLockoutFailure bool
|
||||||
}
|
}
|
||||||
EmailTemplate []byte
|
EmailTemplate []byte
|
||||||
@ -271,7 +272,7 @@ func (c *Commands) SetUpInstance(ctx context.Context, setup *InstanceSetup) (str
|
|||||||
|
|
||||||
prepareAddDefaultPrivacyPolicy(instanceAgg, setup.PrivacyPolicy.TOSLink, setup.PrivacyPolicy.PrivacyLink, setup.PrivacyPolicy.HelpLink, setup.PrivacyPolicy.SupportEmail),
|
prepareAddDefaultPrivacyPolicy(instanceAgg, setup.PrivacyPolicy.TOSLink, setup.PrivacyPolicy.PrivacyLink, setup.PrivacyPolicy.HelpLink, setup.PrivacyPolicy.SupportEmail),
|
||||||
prepareAddDefaultNotificationPolicy(instanceAgg, setup.NotificationPolicy.PasswordChange),
|
prepareAddDefaultNotificationPolicy(instanceAgg, setup.NotificationPolicy.PasswordChange),
|
||||||
prepareAddDefaultLockoutPolicy(instanceAgg, setup.LockoutPolicy.MaxAttempts, setup.LockoutPolicy.ShouldShowLockoutFailure),
|
prepareAddDefaultLockoutPolicy(instanceAgg, setup.LockoutPolicy.MaxPasswordAttempts, setup.LockoutPolicy.MaxOTPAttempts, setup.LockoutPolicy.ShouldShowLockoutFailure),
|
||||||
|
|
||||||
prepareAddDefaultLabelPolicy(
|
prepareAddDefaultLabelPolicy(
|
||||||
instanceAgg,
|
instanceAgg,
|
||||||
|
@ -109,6 +109,7 @@ func writeModelToLockoutPolicy(wm *LockoutPolicyWriteModel) *domain.LockoutPolic
|
|||||||
return &domain.LockoutPolicy{
|
return &domain.LockoutPolicy{
|
||||||
ObjectRoot: writeModelToObjectRoot(wm.WriteModel),
|
ObjectRoot: writeModelToObjectRoot(wm.WriteModel),
|
||||||
MaxPasswordAttempts: wm.MaxPasswordAttempts,
|
MaxPasswordAttempts: wm.MaxPasswordAttempts,
|
||||||
|
MaxOTPAttempts: wm.MaxOTPAttempts,
|
||||||
ShowLockOutFailures: wm.ShowLockOutFailures,
|
ShowLockOutFailures: wm.ShowLockOutFailures,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,9 +12,15 @@ import (
|
|||||||
"github.com/zitadel/zitadel/internal/zerrors"
|
"github.com/zitadel/zitadel/internal/zerrors"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *Commands) AddDefaultLockoutPolicy(ctx context.Context, maxAttempts uint64, showLockoutFailure bool) (*domain.ObjectDetails, error) {
|
func (c *Commands) AddDefaultLockoutPolicy(ctx context.Context, maxPasswordAttempts, maxOTPAttempts uint64, showLockoutFailure bool) (*domain.ObjectDetails, error) {
|
||||||
instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID())
|
instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID())
|
||||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, prepareAddDefaultLockoutPolicy(instanceAgg, maxAttempts, showLockoutFailure))
|
//nolint:staticcheck
|
||||||
|
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, prepareAddDefaultLockoutPolicy(
|
||||||
|
instanceAgg,
|
||||||
|
maxPasswordAttempts,
|
||||||
|
maxOTPAttempts,
|
||||||
|
showLockoutFailure,
|
||||||
|
))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -35,7 +41,13 @@ func (c *Commands) ChangeDefaultLockoutPolicy(ctx context.Context, policy *domai
|
|||||||
}
|
}
|
||||||
|
|
||||||
instanceAgg := InstanceAggregateFromWriteModel(&existingPolicy.LockoutPolicyWriteModel.WriteModel)
|
instanceAgg := InstanceAggregateFromWriteModel(&existingPolicy.LockoutPolicyWriteModel.WriteModel)
|
||||||
changedEvent, hasChanged := existingPolicy.NewChangedEvent(ctx, instanceAgg, policy.MaxPasswordAttempts, policy.ShowLockOutFailures)
|
changedEvent, hasChanged := existingPolicy.NewChangedEvent(
|
||||||
|
ctx,
|
||||||
|
instanceAgg,
|
||||||
|
policy.MaxPasswordAttempts,
|
||||||
|
policy.MaxOTPAttempts,
|
||||||
|
policy.ShowLockOutFailures,
|
||||||
|
)
|
||||||
if !hasChanged {
|
if !hasChanged {
|
||||||
return nil, zerrors.ThrowPreconditionFailed(nil, "INSTANCE-0psjF", "Errors.IAM.LockoutPolicy.NotChanged")
|
return nil, zerrors.ThrowPreconditionFailed(nil, "INSTANCE-0psjF", "Errors.IAM.LockoutPolicy.NotChanged")
|
||||||
}
|
}
|
||||||
@ -65,7 +77,8 @@ func (c *Commands) defaultLockoutPolicyWriteModelByID(ctx context.Context) (poli
|
|||||||
|
|
||||||
func prepareAddDefaultLockoutPolicy(
|
func prepareAddDefaultLockoutPolicy(
|
||||||
a *instance.Aggregate,
|
a *instance.Aggregate,
|
||||||
maxAttempts uint64,
|
maxPasswordAttempts,
|
||||||
|
maxOTPAttempts uint64,
|
||||||
showLockoutFailure bool,
|
showLockoutFailure bool,
|
||||||
) preparation.Validation {
|
) preparation.Validation {
|
||||||
return func() (preparation.CreateCommands, error) {
|
return func() (preparation.CreateCommands, error) {
|
||||||
@ -83,7 +96,7 @@ func prepareAddDefaultLockoutPolicy(
|
|||||||
return nil, zerrors.ThrowAlreadyExists(nil, "INSTANCE-0olDf", "Errors.Instance.LockoutPolicy.AlreadyExists")
|
return nil, zerrors.ThrowAlreadyExists(nil, "INSTANCE-0olDf", "Errors.Instance.LockoutPolicy.AlreadyExists")
|
||||||
}
|
}
|
||||||
return []eventstore.Command{
|
return []eventstore.Command{
|
||||||
instance.NewLockoutPolicyAddedEvent(ctx, &a.Aggregate, maxAttempts, showLockoutFailure),
|
instance.NewLockoutPolicyAddedEvent(ctx, &a.Aggregate, maxPasswordAttempts, maxOTPAttempts, showLockoutFailure),
|
||||||
}, nil
|
}, nil
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
@ -54,11 +54,15 @@ func (wm *InstanceLockoutPolicyWriteModel) Query() *eventstore.SearchQueryBuilde
|
|||||||
func (wm *InstanceLockoutPolicyWriteModel) NewChangedEvent(
|
func (wm *InstanceLockoutPolicyWriteModel) NewChangedEvent(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
aggregate *eventstore.Aggregate,
|
aggregate *eventstore.Aggregate,
|
||||||
maxAttempts uint64,
|
maxPasswordAttempts,
|
||||||
|
maxOTPAttempts uint64,
|
||||||
showLockoutFailure bool) (*instance.LockoutPolicyChangedEvent, bool) {
|
showLockoutFailure bool) (*instance.LockoutPolicyChangedEvent, bool) {
|
||||||
changes := make([]policy.LockoutPolicyChanges, 0)
|
changes := make([]policy.LockoutPolicyChanges, 0)
|
||||||
if wm.MaxPasswordAttempts != maxAttempts {
|
if wm.MaxPasswordAttempts != maxPasswordAttempts {
|
||||||
changes = append(changes, policy.ChangeMaxAttempts(maxAttempts))
|
changes = append(changes, policy.ChangeMaxPasswordAttempts(maxPasswordAttempts))
|
||||||
|
}
|
||||||
|
if wm.MaxOTPAttempts != maxOTPAttempts {
|
||||||
|
changes = append(changes, policy.ChangeMaxOTPAttempts(maxOTPAttempts))
|
||||||
}
|
}
|
||||||
if wm.ShowLockOutFailures != showLockoutFailure {
|
if wm.ShowLockOutFailures != showLockoutFailure {
|
||||||
changes = append(changes, policy.ChangeShowLockOutFailures(showLockoutFailure))
|
changes = append(changes, policy.ChangeShowLockOutFailures(showLockoutFailure))
|
||||||
|
@ -22,6 +22,7 @@ func TestCommandSide_AddDefaultLockoutPolicy(t *testing.T) {
|
|||||||
type args struct {
|
type args struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
maxPasswordAttempts uint64
|
maxPasswordAttempts uint64
|
||||||
|
maxOTPAttempts uint64
|
||||||
showLockOutFailures bool
|
showLockOutFailures bool
|
||||||
}
|
}
|
||||||
type res struct {
|
type res struct {
|
||||||
@ -44,6 +45,7 @@ func TestCommandSide_AddDefaultLockoutPolicy(t *testing.T) {
|
|||||||
instance.NewLockoutPolicyAddedEvent(context.Background(),
|
instance.NewLockoutPolicyAddedEvent(context.Background(),
|
||||||
&instance.NewAggregate("INSTANCE").Aggregate,
|
&instance.NewAggregate("INSTANCE").Aggregate,
|
||||||
10,
|
10,
|
||||||
|
10,
|
||||||
true,
|
true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -69,6 +71,7 @@ func TestCommandSide_AddDefaultLockoutPolicy(t *testing.T) {
|
|||||||
instance.NewLockoutPolicyAddedEvent(context.Background(),
|
instance.NewLockoutPolicyAddedEvent(context.Background(),
|
||||||
&instance.NewAggregate("INSTANCE").Aggregate,
|
&instance.NewAggregate("INSTANCE").Aggregate,
|
||||||
10,
|
10,
|
||||||
|
10,
|
||||||
true,
|
true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -77,6 +80,7 @@ func TestCommandSide_AddDefaultLockoutPolicy(t *testing.T) {
|
|||||||
args: args{
|
args: args{
|
||||||
ctx: authz.WithInstanceID(context.Background(), "INSTANCE"),
|
ctx: authz.WithInstanceID(context.Background(), "INSTANCE"),
|
||||||
maxPasswordAttempts: 10,
|
maxPasswordAttempts: 10,
|
||||||
|
maxOTPAttempts: 10,
|
||||||
showLockOutFailures: true,
|
showLockOutFailures: true,
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
@ -91,7 +95,7 @@ func TestCommandSide_AddDefaultLockoutPolicy(t *testing.T) {
|
|||||||
r := &Commands{
|
r := &Commands{
|
||||||
eventstore: tt.fields.eventstore,
|
eventstore: tt.fields.eventstore,
|
||||||
}
|
}
|
||||||
got, err := r.AddDefaultLockoutPolicy(tt.args.ctx, tt.args.maxPasswordAttempts, tt.args.showLockOutFailures)
|
got, err := r.AddDefaultLockoutPolicy(tt.args.ctx, tt.args.maxPasswordAttempts, tt.args.maxOTPAttempts, tt.args.showLockOutFailures)
|
||||||
if tt.res.err == nil {
|
if tt.res.err == nil {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
@ -135,6 +139,7 @@ func TestCommandSide_ChangeDefaultLockoutPolicy(t *testing.T) {
|
|||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
policy: &domain.LockoutPolicy{
|
policy: &domain.LockoutPolicy{
|
||||||
MaxPasswordAttempts: 10,
|
MaxPasswordAttempts: 10,
|
||||||
|
MaxOTPAttempts: 10,
|
||||||
ShowLockOutFailures: true,
|
ShowLockOutFailures: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -152,6 +157,7 @@ func TestCommandSide_ChangeDefaultLockoutPolicy(t *testing.T) {
|
|||||||
instance.NewLockoutPolicyAddedEvent(context.Background(),
|
instance.NewLockoutPolicyAddedEvent(context.Background(),
|
||||||
&instance.NewAggregate("INSTANCE").Aggregate,
|
&instance.NewAggregate("INSTANCE").Aggregate,
|
||||||
10,
|
10,
|
||||||
|
10,
|
||||||
true,
|
true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -162,6 +168,7 @@ func TestCommandSide_ChangeDefaultLockoutPolicy(t *testing.T) {
|
|||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
policy: &domain.LockoutPolicy{
|
policy: &domain.LockoutPolicy{
|
||||||
MaxPasswordAttempts: 10,
|
MaxPasswordAttempts: 10,
|
||||||
|
MaxOTPAttempts: 10,
|
||||||
ShowLockOutFailures: true,
|
ShowLockOutFailures: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -179,12 +186,13 @@ func TestCommandSide_ChangeDefaultLockoutPolicy(t *testing.T) {
|
|||||||
instance.NewLockoutPolicyAddedEvent(context.Background(),
|
instance.NewLockoutPolicyAddedEvent(context.Background(),
|
||||||
&instance.NewAggregate("INSTANCE").Aggregate,
|
&instance.NewAggregate("INSTANCE").Aggregate,
|
||||||
10,
|
10,
|
||||||
|
10,
|
||||||
true,
|
true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
expectPush(
|
expectPush(
|
||||||
newDefaultLockoutPolicyChangedEvent(context.Background(), 20, false),
|
newDefaultLockoutPolicyChangedEvent(context.Background(), 20, 20, false),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@ -192,6 +200,7 @@ func TestCommandSide_ChangeDefaultLockoutPolicy(t *testing.T) {
|
|||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
policy: &domain.LockoutPolicy{
|
policy: &domain.LockoutPolicy{
|
||||||
MaxPasswordAttempts: 20,
|
MaxPasswordAttempts: 20,
|
||||||
|
MaxOTPAttempts: 20,
|
||||||
ShowLockOutFailures: false,
|
ShowLockOutFailures: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -203,6 +212,7 @@ func TestCommandSide_ChangeDefaultLockoutPolicy(t *testing.T) {
|
|||||||
InstanceID: "INSTANCE",
|
InstanceID: "INSTANCE",
|
||||||
},
|
},
|
||||||
MaxPasswordAttempts: 20,
|
MaxPasswordAttempts: 20,
|
||||||
|
MaxOTPAttempts: 20,
|
||||||
ShowLockOutFailures: false,
|
ShowLockOutFailures: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -227,11 +237,12 @@ func TestCommandSide_ChangeDefaultLockoutPolicy(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newDefaultLockoutPolicyChangedEvent(ctx context.Context, maxAttempts uint64, showLockoutFailure bool) *instance.LockoutPolicyChangedEvent {
|
func newDefaultLockoutPolicyChangedEvent(ctx context.Context, maxPasswordAttempts, maxOTPAttempts uint64, showLockoutFailure bool) *instance.LockoutPolicyChangedEvent {
|
||||||
event, _ := instance.NewLockoutPolicyChangedEvent(ctx,
|
event, _ := instance.NewLockoutPolicyChangedEvent(ctx,
|
||||||
&instance.NewAggregate("INSTANCE").Aggregate,
|
&instance.NewAggregate("INSTANCE").Aggregate,
|
||||||
[]policy.LockoutPolicyChanges{
|
[]policy.LockoutPolicyChanges{
|
||||||
policy.ChangeMaxAttempts(maxAttempts),
|
policy.ChangeMaxPasswordAttempts(maxPasswordAttempts),
|
||||||
|
policy.ChangeMaxOTPAttempts(maxOTPAttempts),
|
||||||
policy.ChangeShowLockOutFailures(showLockoutFailure),
|
policy.ChangeShowLockOutFailures(showLockoutFailure),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -21,7 +21,13 @@ func (c *Commands) AddLockoutPolicy(ctx context.Context, resourceOwner string, p
|
|||||||
}
|
}
|
||||||
|
|
||||||
orgAgg := OrgAggregateFromWriteModel(&addedPolicy.WriteModel)
|
orgAgg := OrgAggregateFromWriteModel(&addedPolicy.WriteModel)
|
||||||
pushedEvents, err := c.eventstore.Push(ctx, org.NewLockoutPolicyAddedEvent(ctx, orgAgg, policy.MaxPasswordAttempts, policy.ShowLockOutFailures))
|
pushedEvents, err := c.eventstore.Push(ctx, org.NewLockoutPolicyAddedEvent(
|
||||||
|
ctx,
|
||||||
|
orgAgg,
|
||||||
|
policy.MaxPasswordAttempts,
|
||||||
|
policy.MaxOTPAttempts,
|
||||||
|
policy.ShowLockOutFailures,
|
||||||
|
))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -45,7 +51,7 @@ func (c *Commands) ChangeLockoutPolicy(ctx context.Context, resourceOwner string
|
|||||||
}
|
}
|
||||||
|
|
||||||
orgAgg := OrgAggregateFromWriteModel(&existingPolicy.LockoutPolicyWriteModel.WriteModel)
|
orgAgg := OrgAggregateFromWriteModel(&existingPolicy.LockoutPolicyWriteModel.WriteModel)
|
||||||
changedEvent, hasChanged := existingPolicy.NewChangedEvent(ctx, orgAgg, policy.MaxPasswordAttempts, policy.ShowLockOutFailures)
|
changedEvent, hasChanged := existingPolicy.NewChangedEvent(ctx, orgAgg, policy.MaxPasswordAttempts, policy.MaxOTPAttempts, policy.ShowLockOutFailures)
|
||||||
if !hasChanged {
|
if !hasChanged {
|
||||||
return nil, zerrors.ThrowPreconditionFailed(nil, "ORG-0JFSr", "Errors.Org.LockoutPolicy.NotChanged")
|
return nil, zerrors.ThrowPreconditionFailed(nil, "ORG-0JFSr", "Errors.Org.LockoutPolicy.NotChanged")
|
||||||
}
|
}
|
||||||
@ -106,3 +112,20 @@ func (c *Commands) orgLockoutPolicyWriteModelByID(ctx context.Context, orgID str
|
|||||||
}
|
}
|
||||||
return policy, nil
|
return policy, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Commands) getLockoutPolicy(ctx context.Context, orgID string) (*domain.LockoutPolicy, error) {
|
||||||
|
orgWm, err := c.orgLockoutPolicyWriteModelByID(ctx, orgID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if orgWm.State == domain.PolicyStateActive {
|
||||||
|
return writeModelToLockoutPolicy(&orgWm.LockoutPolicyWriteModel), nil
|
||||||
|
}
|
||||||
|
instanceWm, err := c.defaultLockoutPolicyWriteModelByID(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
policy := writeModelToLockoutPolicy(&instanceWm.LockoutPolicyWriteModel)
|
||||||
|
policy.Default = true
|
||||||
|
return policy, nil
|
||||||
|
}
|
||||||
|
@ -55,11 +55,15 @@ func (wm *OrgLockoutPolicyWriteModel) Query() *eventstore.SearchQueryBuilder {
|
|||||||
func (wm *OrgLockoutPolicyWriteModel) NewChangedEvent(
|
func (wm *OrgLockoutPolicyWriteModel) NewChangedEvent(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
aggregate *eventstore.Aggregate,
|
aggregate *eventstore.Aggregate,
|
||||||
maxAttempts uint64,
|
maxPasswordAttempts,
|
||||||
|
maxOTPAttempts uint64,
|
||||||
showLockoutFailure bool) (*org.LockoutPolicyChangedEvent, bool) {
|
showLockoutFailure bool) (*org.LockoutPolicyChangedEvent, bool) {
|
||||||
changes := make([]policy.LockoutPolicyChanges, 0)
|
changes := make([]policy.LockoutPolicyChanges, 0)
|
||||||
if wm.MaxPasswordAttempts != maxAttempts {
|
if wm.MaxPasswordAttempts != maxPasswordAttempts {
|
||||||
changes = append(changes, policy.ChangeMaxAttempts(maxAttempts))
|
changes = append(changes, policy.ChangeMaxPasswordAttempts(maxPasswordAttempts))
|
||||||
|
}
|
||||||
|
if wm.MaxOTPAttempts != maxOTPAttempts {
|
||||||
|
changes = append(changes, policy.ChangeMaxOTPAttempts(maxOTPAttempts))
|
||||||
}
|
}
|
||||||
if wm.ShowLockOutFailures != showLockoutFailure {
|
if wm.ShowLockOutFailures != showLockoutFailure {
|
||||||
changes = append(changes, policy.ChangeShowLockOutFailures(showLockoutFailure))
|
changes = append(changes, policy.ChangeShowLockOutFailures(showLockoutFailure))
|
||||||
|
@ -44,6 +44,7 @@ func TestCommandSide_AddPasswordLockoutPolicy(t *testing.T) {
|
|||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
policy: &domain.LockoutPolicy{
|
policy: &domain.LockoutPolicy{
|
||||||
MaxPasswordAttempts: 10,
|
MaxPasswordAttempts: 10,
|
||||||
|
MaxOTPAttempts: 10,
|
||||||
ShowLockOutFailures: true,
|
ShowLockOutFailures: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -61,6 +62,7 @@ func TestCommandSide_AddPasswordLockoutPolicy(t *testing.T) {
|
|||||||
org.NewLockoutPolicyAddedEvent(context.Background(),
|
org.NewLockoutPolicyAddedEvent(context.Background(),
|
||||||
&org.NewAggregate("org1").Aggregate,
|
&org.NewAggregate("org1").Aggregate,
|
||||||
10,
|
10,
|
||||||
|
10,
|
||||||
true,
|
true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -72,6 +74,7 @@ func TestCommandSide_AddPasswordLockoutPolicy(t *testing.T) {
|
|||||||
orgID: "org1",
|
orgID: "org1",
|
||||||
policy: &domain.LockoutPolicy{
|
policy: &domain.LockoutPolicy{
|
||||||
MaxPasswordAttempts: 10,
|
MaxPasswordAttempts: 10,
|
||||||
|
MaxOTPAttempts: 10,
|
||||||
ShowLockOutFailures: true,
|
ShowLockOutFailures: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -89,6 +92,7 @@ func TestCommandSide_AddPasswordLockoutPolicy(t *testing.T) {
|
|||||||
org.NewLockoutPolicyAddedEvent(context.Background(),
|
org.NewLockoutPolicyAddedEvent(context.Background(),
|
||||||
&org.NewAggregate("org1").Aggregate,
|
&org.NewAggregate("org1").Aggregate,
|
||||||
10,
|
10,
|
||||||
|
10,
|
||||||
true,
|
true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -99,6 +103,7 @@ func TestCommandSide_AddPasswordLockoutPolicy(t *testing.T) {
|
|||||||
orgID: "org1",
|
orgID: "org1",
|
||||||
policy: &domain.LockoutPolicy{
|
policy: &domain.LockoutPolicy{
|
||||||
MaxPasswordAttempts: 10,
|
MaxPasswordAttempts: 10,
|
||||||
|
MaxOTPAttempts: 10,
|
||||||
ShowLockOutFailures: true,
|
ShowLockOutFailures: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -109,6 +114,7 @@ func TestCommandSide_AddPasswordLockoutPolicy(t *testing.T) {
|
|||||||
ResourceOwner: "org1",
|
ResourceOwner: "org1",
|
||||||
},
|
},
|
||||||
MaxPasswordAttempts: 10,
|
MaxPasswordAttempts: 10,
|
||||||
|
MaxOTPAttempts: 10,
|
||||||
ShowLockOutFailures: true,
|
ShowLockOutFailures: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -163,6 +169,7 @@ func TestCommandSide_ChangePasswordLockoutPolicy(t *testing.T) {
|
|||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
policy: &domain.LockoutPolicy{
|
policy: &domain.LockoutPolicy{
|
||||||
MaxPasswordAttempts: 10,
|
MaxPasswordAttempts: 10,
|
||||||
|
MaxOTPAttempts: 10,
|
||||||
ShowLockOutFailures: true,
|
ShowLockOutFailures: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -183,6 +190,7 @@ func TestCommandSide_ChangePasswordLockoutPolicy(t *testing.T) {
|
|||||||
orgID: "org1",
|
orgID: "org1",
|
||||||
policy: &domain.LockoutPolicy{
|
policy: &domain.LockoutPolicy{
|
||||||
MaxPasswordAttempts: 10,
|
MaxPasswordAttempts: 10,
|
||||||
|
MaxOTPAttempts: 10,
|
||||||
ShowLockOutFailures: true,
|
ShowLockOutFailures: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -200,6 +208,7 @@ func TestCommandSide_ChangePasswordLockoutPolicy(t *testing.T) {
|
|||||||
org.NewLockoutPolicyAddedEvent(context.Background(),
|
org.NewLockoutPolicyAddedEvent(context.Background(),
|
||||||
&org.NewAggregate("org1").Aggregate,
|
&org.NewAggregate("org1").Aggregate,
|
||||||
10,
|
10,
|
||||||
|
10,
|
||||||
true,
|
true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -211,6 +220,7 @@ func TestCommandSide_ChangePasswordLockoutPolicy(t *testing.T) {
|
|||||||
orgID: "org1",
|
orgID: "org1",
|
||||||
policy: &domain.LockoutPolicy{
|
policy: &domain.LockoutPolicy{
|
||||||
MaxPasswordAttempts: 10,
|
MaxPasswordAttempts: 10,
|
||||||
|
MaxOTPAttempts: 10,
|
||||||
ShowLockOutFailures: true,
|
ShowLockOutFailures: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -228,12 +238,13 @@ func TestCommandSide_ChangePasswordLockoutPolicy(t *testing.T) {
|
|||||||
org.NewLockoutPolicyAddedEvent(context.Background(),
|
org.NewLockoutPolicyAddedEvent(context.Background(),
|
||||||
&org.NewAggregate("org1").Aggregate,
|
&org.NewAggregate("org1").Aggregate,
|
||||||
10,
|
10,
|
||||||
|
10,
|
||||||
true,
|
true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
expectPush(
|
expectPush(
|
||||||
newPasswordLockoutPolicyChangedEvent(context.Background(), "org1", 5, false),
|
newPasswordLockoutPolicyChangedEvent(context.Background(), "org1", 5, 5, false),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@ -242,6 +253,7 @@ func TestCommandSide_ChangePasswordLockoutPolicy(t *testing.T) {
|
|||||||
orgID: "org1",
|
orgID: "org1",
|
||||||
policy: &domain.LockoutPolicy{
|
policy: &domain.LockoutPolicy{
|
||||||
MaxPasswordAttempts: 5,
|
MaxPasswordAttempts: 5,
|
||||||
|
MaxOTPAttempts: 5,
|
||||||
ShowLockOutFailures: false,
|
ShowLockOutFailures: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -252,6 +264,7 @@ func TestCommandSide_ChangePasswordLockoutPolicy(t *testing.T) {
|
|||||||
ResourceOwner: "org1",
|
ResourceOwner: "org1",
|
||||||
},
|
},
|
||||||
MaxPasswordAttempts: 5,
|
MaxPasswordAttempts: 5,
|
||||||
|
MaxOTPAttempts: 5,
|
||||||
ShowLockOutFailures: false,
|
ShowLockOutFailures: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -334,6 +347,7 @@ func TestCommandSide_RemovePasswordLockoutPolicy(t *testing.T) {
|
|||||||
org.NewLockoutPolicyAddedEvent(context.Background(),
|
org.NewLockoutPolicyAddedEvent(context.Background(),
|
||||||
&org.NewAggregate("org1").Aggregate,
|
&org.NewAggregate("org1").Aggregate,
|
||||||
10,
|
10,
|
||||||
|
10,
|
||||||
true,
|
true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -371,11 +385,12 @@ func TestCommandSide_RemovePasswordLockoutPolicy(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newPasswordLockoutPolicyChangedEvent(ctx context.Context, orgID string, maxAttempts uint64, showLockoutFailure bool) *org.LockoutPolicyChangedEvent {
|
func newPasswordLockoutPolicyChangedEvent(ctx context.Context, orgID string, maxPasswordAttempts, maxOTPAttempts uint64, showLockoutFailure bool) *org.LockoutPolicyChangedEvent {
|
||||||
event, _ := org.NewLockoutPolicyChangedEvent(ctx,
|
event, _ := org.NewLockoutPolicyChangedEvent(ctx,
|
||||||
&org.NewAggregate(orgID).Aggregate,
|
&org.NewAggregate(orgID).Aggregate,
|
||||||
[]policy.LockoutPolicyChanges{
|
[]policy.LockoutPolicyChanges{
|
||||||
policy.ChangeMaxAttempts(maxAttempts),
|
policy.ChangeMaxPasswordAttempts(maxPasswordAttempts),
|
||||||
|
policy.ChangeMaxOTPAttempts(maxOTPAttempts),
|
||||||
policy.ChangeShowLockOutFailures(showLockoutFailure),
|
policy.ChangeShowLockOutFailures(showLockoutFailure),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -10,6 +10,7 @@ type LockoutPolicyWriteModel struct {
|
|||||||
eventstore.WriteModel
|
eventstore.WriteModel
|
||||||
|
|
||||||
MaxPasswordAttempts uint64
|
MaxPasswordAttempts uint64
|
||||||
|
MaxOTPAttempts uint64
|
||||||
ShowLockOutFailures bool
|
ShowLockOutFailures bool
|
||||||
State domain.PolicyState
|
State domain.PolicyState
|
||||||
}
|
}
|
||||||
@ -19,12 +20,16 @@ func (wm *LockoutPolicyWriteModel) Reduce() error {
|
|||||||
switch e := event.(type) {
|
switch e := event.(type) {
|
||||||
case *policy.LockoutPolicyAddedEvent:
|
case *policy.LockoutPolicyAddedEvent:
|
||||||
wm.MaxPasswordAttempts = e.MaxPasswordAttempts
|
wm.MaxPasswordAttempts = e.MaxPasswordAttempts
|
||||||
|
wm.MaxOTPAttempts = e.MaxOTPAttempts
|
||||||
wm.ShowLockOutFailures = e.ShowLockOutFailures
|
wm.ShowLockOutFailures = e.ShowLockOutFailures
|
||||||
wm.State = domain.PolicyStateActive
|
wm.State = domain.PolicyStateActive
|
||||||
case *policy.LockoutPolicyChangedEvent:
|
case *policy.LockoutPolicyChangedEvent:
|
||||||
if e.MaxPasswordAttempts != nil {
|
if e.MaxPasswordAttempts != nil {
|
||||||
wm.MaxPasswordAttempts = *e.MaxPasswordAttempts
|
wm.MaxPasswordAttempts = *e.MaxPasswordAttempts
|
||||||
}
|
}
|
||||||
|
if e.MaxOTPAttempts != nil {
|
||||||
|
wm.MaxOTPAttempts = *e.MaxOTPAttempts
|
||||||
|
}
|
||||||
if e.ShowLockOutFailures != nil {
|
if e.ShowLockOutFailures != nil {
|
||||||
wm.ShowLockOutFailures = *e.ShowLockOutFailures
|
wm.ShowLockOutFailures = *e.ShowLockOutFailures
|
||||||
}
|
}
|
||||||
|
@ -158,15 +158,38 @@ func (c *Commands) HumanCheckMFATOTP(ctx context.Context, userID, code, resource
|
|||||||
return zerrors.ThrowPreconditionFailed(nil, "COMMAND-3Mif9s", "Errors.User.MFA.OTP.NotReady")
|
return zerrors.ThrowPreconditionFailed(nil, "COMMAND-3Mif9s", "Errors.User.MFA.OTP.NotReady")
|
||||||
}
|
}
|
||||||
userAgg := UserAggregateFromWriteModel(&existingOTP.WriteModel)
|
userAgg := UserAggregateFromWriteModel(&existingOTP.WriteModel)
|
||||||
err = domain.VerifyTOTP(code, existingOTP.Secret, c.multifactors.OTP.CryptoMFA)
|
verifyErr := domain.VerifyTOTP(code, existingOTP.Secret, c.multifactors.OTP.CryptoMFA)
|
||||||
if err == nil {
|
|
||||||
|
// recheck for additional events (failed OTP checks or locks)
|
||||||
|
recheckErr := c.eventstore.FilterToQueryReducer(ctx, existingOTP)
|
||||||
|
if recheckErr != nil {
|
||||||
|
return recheckErr
|
||||||
|
}
|
||||||
|
if existingOTP.UserLocked {
|
||||||
|
return zerrors.ThrowPreconditionFailed(nil, "COMMAND-SF3fg", "Errors.User.Locked")
|
||||||
|
}
|
||||||
|
|
||||||
|
// the OTP check succeeded and the user was not locked in the meantime
|
||||||
|
if verifyErr == nil {
|
||||||
_, err = c.eventstore.Push(ctx, user.NewHumanOTPCheckSucceededEvent(ctx, userAgg, authRequestDomainToAuthRequestInfo(authRequest)))
|
_, err = c.eventstore.Push(ctx, user.NewHumanOTPCheckSucceededEvent(ctx, userAgg, authRequestDomainToAuthRequestInfo(authRequest)))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, pushErr := c.eventstore.Push(ctx, user.NewHumanOTPCheckFailedEvent(ctx, userAgg, authRequestDomainToAuthRequestInfo(authRequest)))
|
|
||||||
logging.OnError(pushErr).Error("error create password check failed event")
|
// the OTP check failed, therefore check if the limit was reached and the user must additionally be locked
|
||||||
|
commands := make([]eventstore.Command, 0, 2)
|
||||||
|
commands = append(commands, user.NewHumanOTPCheckFailedEvent(ctx, userAgg, authRequestDomainToAuthRequestInfo(authRequest)))
|
||||||
|
lockoutPolicy, err := c.getLockoutPolicy(ctx, resourceOwner)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if lockoutPolicy.MaxOTPAttempts > 0 && existingOTP.CheckFailedCount+1 >= lockoutPolicy.MaxOTPAttempts {
|
||||||
|
commands = append(commands, user.NewUserLockedEvent(ctx, userAgg))
|
||||||
|
}
|
||||||
|
|
||||||
|
_, pushErr := c.eventstore.Push(ctx, commands...)
|
||||||
|
logging.OnError(pushErr).Error("error create password check failed event")
|
||||||
|
return verifyErr
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Commands) HumanRemoveTOTP(ctx context.Context, userID, resourceOwner string) (*domain.ObjectDetails, error) {
|
func (c *Commands) HumanRemoveTOTP(ctx context.Context, userID, resourceOwner string) (*domain.ObjectDetails, error) {
|
||||||
if userID == "" {
|
if userID == "" {
|
||||||
@ -515,15 +538,38 @@ func (c *Commands) humanCheckOTP(
|
|||||||
return zerrors.ThrowPreconditionFailed(nil, "COMMAND-S34gh", "Errors.User.Code.NotFound")
|
return zerrors.ThrowPreconditionFailed(nil, "COMMAND-S34gh", "Errors.User.Code.NotFound")
|
||||||
}
|
}
|
||||||
userAgg := &user.NewAggregate(userID, existingOTP.ResourceOwner()).Aggregate
|
userAgg := &user.NewAggregate(userID, existingOTP.ResourceOwner()).Aggregate
|
||||||
err = crypto.VerifyCode(existingOTP.CodeCreationDate(), existingOTP.CodeExpiry(), existingOTP.Code(), code, c.userEncryption)
|
verifyErr := crypto.VerifyCode(existingOTP.CodeCreationDate(), existingOTP.CodeExpiry(), existingOTP.Code(), code, c.userEncryption)
|
||||||
if err == nil {
|
|
||||||
|
// recheck for additional events (failed OTP checks or locks)
|
||||||
|
recheckErr := c.eventstore.FilterToQueryReducer(ctx, existingOTP)
|
||||||
|
if recheckErr != nil {
|
||||||
|
return recheckErr
|
||||||
|
}
|
||||||
|
if existingOTP.UserLocked() {
|
||||||
|
return zerrors.ThrowPreconditionFailed(nil, "COMMAND-S6h4R", "Errors.User.Locked")
|
||||||
|
}
|
||||||
|
|
||||||
|
// the OTP check succeeded and the user was not locked in the meantime
|
||||||
|
if verifyErr == nil {
|
||||||
_, err = c.eventstore.Push(ctx, checkSucceededEvent(ctx, userAgg, authRequestDomainToAuthRequestInfo(authRequest)))
|
_, err = c.eventstore.Push(ctx, checkSucceededEvent(ctx, userAgg, authRequestDomainToAuthRequestInfo(authRequest)))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, pushErr := c.eventstore.Push(ctx, checkFailedEvent(ctx, userAgg, authRequestDomainToAuthRequestInfo(authRequest)))
|
|
||||||
logging.WithFields("userID", userID).OnError(pushErr).Error("otp failure check push failed")
|
// the OTP check failed, therefore check if the limit was reached and the user must additionally be locked
|
||||||
|
commands := make([]eventstore.Command, 0, 2)
|
||||||
|
commands = append(commands, checkFailedEvent(ctx, userAgg, authRequestDomainToAuthRequestInfo(authRequest)))
|
||||||
|
lockoutPolicy, err := c.getLockoutPolicy(ctx, resourceOwner)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if lockoutPolicy.MaxOTPAttempts > 0 && existingOTP.CheckFailedCount()+1 >= lockoutPolicy.MaxOTPAttempts {
|
||||||
|
commands = append(commands, user.NewUserLockedEvent(ctx, userAgg))
|
||||||
|
}
|
||||||
|
|
||||||
|
_, pushErr := c.eventstore.Push(ctx, commands...)
|
||||||
|
logging.WithFields("userID", userID).OnError(pushErr).Error("otp failure check push failed")
|
||||||
|
return verifyErr
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Commands) totpWriteModelByID(ctx context.Context, userID, resourceOwner string) (writeModel *HumanTOTPWriteModel, err error) {
|
func (c *Commands) totpWriteModelByID(ctx context.Context, userID, resourceOwner string) (writeModel *HumanTOTPWriteModel, err error) {
|
||||||
ctx, span := tracing.NewSpan(ctx)
|
ctx, span := tracing.NewSpan(ctx)
|
||||||
|
@ -14,6 +14,8 @@ type HumanTOTPWriteModel struct {
|
|||||||
|
|
||||||
State domain.MFAState
|
State domain.MFAState
|
||||||
Secret *crypto.CryptoValue
|
Secret *crypto.CryptoValue
|
||||||
|
CheckFailedCount uint64
|
||||||
|
UserLocked bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHumanTOTPWriteModel(userID, resourceOwner string) *HumanTOTPWriteModel {
|
func NewHumanTOTPWriteModel(userID, resourceOwner string) *HumanTOTPWriteModel {
|
||||||
@ -33,6 +35,16 @@ func (wm *HumanTOTPWriteModel) Reduce() error {
|
|||||||
wm.State = domain.MFAStateNotReady
|
wm.State = domain.MFAStateNotReady
|
||||||
case *user.HumanOTPVerifiedEvent:
|
case *user.HumanOTPVerifiedEvent:
|
||||||
wm.State = domain.MFAStateReady
|
wm.State = domain.MFAStateReady
|
||||||
|
wm.CheckFailedCount = 0
|
||||||
|
case *user.HumanOTPCheckSucceededEvent:
|
||||||
|
wm.CheckFailedCount = 0
|
||||||
|
case *user.HumanOTPCheckFailedEvent:
|
||||||
|
wm.CheckFailedCount++
|
||||||
|
case *user.UserLockedEvent:
|
||||||
|
wm.UserLocked = true
|
||||||
|
case *user.UserUnlockedEvent:
|
||||||
|
wm.CheckFailedCount = 0
|
||||||
|
wm.UserLocked = false
|
||||||
case *user.HumanOTPRemovedEvent:
|
case *user.HumanOTPRemovedEvent:
|
||||||
wm.State = domain.MFAStateRemoved
|
wm.State = domain.MFAStateRemoved
|
||||||
case *user.UserRemovedEvent:
|
case *user.UserRemovedEvent:
|
||||||
@ -50,6 +62,10 @@ func (wm *HumanTOTPWriteModel) Query() *eventstore.SearchQueryBuilder {
|
|||||||
EventTypes(user.HumanMFAOTPAddedType,
|
EventTypes(user.HumanMFAOTPAddedType,
|
||||||
user.HumanMFAOTPVerifiedType,
|
user.HumanMFAOTPVerifiedType,
|
||||||
user.HumanMFAOTPRemovedType,
|
user.HumanMFAOTPRemovedType,
|
||||||
|
user.HumanMFAOTPCheckSucceededType,
|
||||||
|
user.HumanMFAOTPCheckFailedType,
|
||||||
|
user.UserLockedType,
|
||||||
|
user.UserUnlockedType,
|
||||||
user.UserRemovedType,
|
user.UserRemovedType,
|
||||||
user.UserV1MFAOTPAddedType,
|
user.UserV1MFAOTPAddedType,
|
||||||
user.UserV1MFAOTPVerifiedType,
|
user.UserV1MFAOTPVerifiedType,
|
||||||
@ -72,6 +88,9 @@ type OTPCodeWriteModel interface {
|
|||||||
CodeCreationDate() time.Time
|
CodeCreationDate() time.Time
|
||||||
CodeExpiry() time.Duration
|
CodeExpiry() time.Duration
|
||||||
Code() *crypto.CryptoValue
|
Code() *crypto.CryptoValue
|
||||||
|
CheckFailedCount() uint64
|
||||||
|
UserLocked() bool
|
||||||
|
eventstore.QueryReducer
|
||||||
}
|
}
|
||||||
|
|
||||||
type HumanOTPSMSWriteModel struct {
|
type HumanOTPSMSWriteModel struct {
|
||||||
@ -141,6 +160,9 @@ type HumanOTPSMSCodeWriteModel struct {
|
|||||||
code *crypto.CryptoValue
|
code *crypto.CryptoValue
|
||||||
codeCreationDate time.Time
|
codeCreationDate time.Time
|
||||||
codeExpiry time.Duration
|
codeExpiry time.Duration
|
||||||
|
|
||||||
|
checkFailedCount uint64
|
||||||
|
userLocked bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wm *HumanOTPSMSCodeWriteModel) CodeCreationDate() time.Time {
|
func (wm *HumanOTPSMSCodeWriteModel) CodeCreationDate() time.Time {
|
||||||
@ -155,6 +177,14 @@ func (wm *HumanOTPSMSCodeWriteModel) Code() *crypto.CryptoValue {
|
|||||||
return wm.code
|
return wm.code
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (wm *HumanOTPSMSCodeWriteModel) CheckFailedCount() uint64 {
|
||||||
|
return wm.checkFailedCount
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wm *HumanOTPSMSCodeWriteModel) UserLocked() bool {
|
||||||
|
return wm.userLocked
|
||||||
|
}
|
||||||
|
|
||||||
func NewHumanOTPSMSCodeWriteModel(userID, resourceOwner string) *HumanOTPSMSCodeWriteModel {
|
func NewHumanOTPSMSCodeWriteModel(userID, resourceOwner string) *HumanOTPSMSCodeWriteModel {
|
||||||
return &HumanOTPSMSCodeWriteModel{
|
return &HumanOTPSMSCodeWriteModel{
|
||||||
HumanOTPSMSWriteModel: NewHumanOTPSMSWriteModel(userID, resourceOwner),
|
HumanOTPSMSWriteModel: NewHumanOTPSMSWriteModel(userID, resourceOwner),
|
||||||
@ -163,10 +193,20 @@ func NewHumanOTPSMSCodeWriteModel(userID, resourceOwner string) *HumanOTPSMSCode
|
|||||||
|
|
||||||
func (wm *HumanOTPSMSCodeWriteModel) Reduce() error {
|
func (wm *HumanOTPSMSCodeWriteModel) Reduce() error {
|
||||||
for _, event := range wm.Events {
|
for _, event := range wm.Events {
|
||||||
if e, ok := event.(*user.HumanOTPSMSCodeAddedEvent); ok {
|
switch e := event.(type) {
|
||||||
|
case *user.HumanOTPSMSCodeAddedEvent:
|
||||||
wm.code = e.Code
|
wm.code = e.Code
|
||||||
wm.codeCreationDate = e.CreationDate()
|
wm.codeCreationDate = e.CreationDate()
|
||||||
wm.codeExpiry = e.Expiry
|
wm.codeExpiry = e.Expiry
|
||||||
|
case *user.HumanOTPSMSCheckSucceededEvent:
|
||||||
|
wm.checkFailedCount = 0
|
||||||
|
case *user.HumanOTPSMSCheckFailedEvent:
|
||||||
|
wm.checkFailedCount++
|
||||||
|
case *user.UserLockedEvent:
|
||||||
|
wm.userLocked = true
|
||||||
|
case *user.UserUnlockedEvent:
|
||||||
|
wm.checkFailedCount = 0
|
||||||
|
wm.userLocked = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return wm.HumanOTPSMSWriteModel.Reduce()
|
return wm.HumanOTPSMSWriteModel.Reduce()
|
||||||
@ -179,6 +219,10 @@ func (wm *HumanOTPSMSCodeWriteModel) Query() *eventstore.SearchQueryBuilder {
|
|||||||
AggregateIDs(wm.AggregateID).
|
AggregateIDs(wm.AggregateID).
|
||||||
EventTypes(
|
EventTypes(
|
||||||
user.HumanOTPSMSCodeAddedType,
|
user.HumanOTPSMSCodeAddedType,
|
||||||
|
user.HumanOTPSMSCheckSucceededType,
|
||||||
|
user.HumanOTPSMSCheckFailedType,
|
||||||
|
user.UserLockedType,
|
||||||
|
user.UserUnlockedType,
|
||||||
user.HumanPhoneVerifiedType,
|
user.HumanPhoneVerifiedType,
|
||||||
user.HumanOTPSMSAddedType,
|
user.HumanOTPSMSAddedType,
|
||||||
user.HumanOTPSMSRemovedType,
|
user.HumanOTPSMSRemovedType,
|
||||||
@ -259,6 +303,9 @@ type HumanOTPEmailCodeWriteModel struct {
|
|||||||
code *crypto.CryptoValue
|
code *crypto.CryptoValue
|
||||||
codeCreationDate time.Time
|
codeCreationDate time.Time
|
||||||
codeExpiry time.Duration
|
codeExpiry time.Duration
|
||||||
|
|
||||||
|
checkFailedCount uint64
|
||||||
|
userLocked bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wm *HumanOTPEmailCodeWriteModel) CodeCreationDate() time.Time {
|
func (wm *HumanOTPEmailCodeWriteModel) CodeCreationDate() time.Time {
|
||||||
@ -273,6 +320,14 @@ func (wm *HumanOTPEmailCodeWriteModel) Code() *crypto.CryptoValue {
|
|||||||
return wm.code
|
return wm.code
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (wm *HumanOTPEmailCodeWriteModel) CheckFailedCount() uint64 {
|
||||||
|
return wm.checkFailedCount
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wm *HumanOTPEmailCodeWriteModel) UserLocked() bool {
|
||||||
|
return wm.userLocked
|
||||||
|
}
|
||||||
|
|
||||||
func NewHumanOTPEmailCodeWriteModel(userID, resourceOwner string) *HumanOTPEmailCodeWriteModel {
|
func NewHumanOTPEmailCodeWriteModel(userID, resourceOwner string) *HumanOTPEmailCodeWriteModel {
|
||||||
return &HumanOTPEmailCodeWriteModel{
|
return &HumanOTPEmailCodeWriteModel{
|
||||||
HumanOTPEmailWriteModel: NewHumanOTPEmailWriteModel(userID, resourceOwner),
|
HumanOTPEmailWriteModel: NewHumanOTPEmailWriteModel(userID, resourceOwner),
|
||||||
@ -281,10 +336,20 @@ func NewHumanOTPEmailCodeWriteModel(userID, resourceOwner string) *HumanOTPEmail
|
|||||||
|
|
||||||
func (wm *HumanOTPEmailCodeWriteModel) Reduce() error {
|
func (wm *HumanOTPEmailCodeWriteModel) Reduce() error {
|
||||||
for _, event := range wm.Events {
|
for _, event := range wm.Events {
|
||||||
if e, ok := event.(*user.HumanOTPEmailCodeAddedEvent); ok {
|
switch e := event.(type) {
|
||||||
|
case *user.HumanOTPEmailCodeAddedEvent:
|
||||||
wm.code = e.Code
|
wm.code = e.Code
|
||||||
wm.codeCreationDate = e.CreationDate()
|
wm.codeCreationDate = e.CreationDate()
|
||||||
wm.codeExpiry = e.Expiry
|
wm.codeExpiry = e.Expiry
|
||||||
|
case *user.HumanOTPEmailCheckSucceededEvent:
|
||||||
|
wm.checkFailedCount = 0
|
||||||
|
case *user.HumanOTPEmailCheckFailedEvent:
|
||||||
|
wm.checkFailedCount++
|
||||||
|
case *user.UserLockedEvent:
|
||||||
|
wm.userLocked = true
|
||||||
|
case *user.UserUnlockedEvent:
|
||||||
|
wm.checkFailedCount = 0
|
||||||
|
wm.userLocked = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return wm.HumanOTPEmailWriteModel.Reduce()
|
return wm.HumanOTPEmailWriteModel.Reduce()
|
||||||
@ -297,6 +362,10 @@ func (wm *HumanOTPEmailCodeWriteModel) Query() *eventstore.SearchQueryBuilder {
|
|||||||
AggregateIDs(wm.AggregateID).
|
AggregateIDs(wm.AggregateID).
|
||||||
EventTypes(
|
EventTypes(
|
||||||
user.HumanOTPEmailCodeAddedType,
|
user.HumanOTPEmailCodeAddedType,
|
||||||
|
user.HumanOTPEmailCheckSucceededType,
|
||||||
|
user.HumanOTPEmailCheckFailedType,
|
||||||
|
user.UserLockedType,
|
||||||
|
user.UserUnlockedType,
|
||||||
user.HumanEmailVerifiedType,
|
user.HumanEmailVerifiedType,
|
||||||
user.HumanOTPEmailAddedType,
|
user.HumanOTPEmailAddedType,
|
||||||
user.HumanOTPEmailRemovedType,
|
user.HumanOTPEmailRemovedType,
|
||||||
|
@ -1671,6 +1671,15 @@ func TestCommandSide_HumanCheckOTPSMS(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
expectFilter(), // recheck
|
||||||
|
expectFilter(
|
||||||
|
eventFromEventPusher(
|
||||||
|
org.NewLockoutPolicyAddedEvent(ctx,
|
||||||
|
&org.NewAggregate("orgID").Aggregate,
|
||||||
|
3, 3, true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
expectPush(
|
expectPush(
|
||||||
user.NewHumanOTPSMSCheckFailedEvent(ctx,
|
user.NewHumanOTPSMSCheckFailedEvent(ctx,
|
||||||
&user.NewAggregate("user1", "org1").Aggregate,
|
&user.NewAggregate("user1", "org1").Aggregate,
|
||||||
@ -1707,6 +1716,86 @@ func TestCommandSide_HumanCheckOTPSMS(t *testing.T) {
|
|||||||
err: zerrors.ThrowInvalidArgument(nil, "CODE-woT0xc", "Errors.User.Code.Invalid"),
|
err: zerrors.ThrowInvalidArgument(nil, "CODE-woT0xc", "Errors.User.Code.Invalid"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "invalid code, max attempts reached, error",
|
||||||
|
fields: fields{
|
||||||
|
eventstore: expectEventstore(
|
||||||
|
expectFilter(
|
||||||
|
eventFromEventPusher(
|
||||||
|
user.NewHumanOTPSMSAddedEvent(ctx,
|
||||||
|
&user.NewAggregate("user1", "org1").Aggregate,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
eventFromEventPusherWithCreationDateNow(
|
||||||
|
user.NewHumanOTPSMSCodeAddedEvent(ctx,
|
||||||
|
&user.NewAggregate("user1", "org1").Aggregate,
|
||||||
|
&crypto.CryptoValue{
|
||||||
|
CryptoType: crypto.TypeEncryption,
|
||||||
|
Algorithm: "enc",
|
||||||
|
KeyID: "id",
|
||||||
|
Crypted: []byte("other-code"),
|
||||||
|
},
|
||||||
|
time.Hour,
|
||||||
|
&user.AuthRequestInfo{
|
||||||
|
ID: "authRequestID",
|
||||||
|
UserAgentID: "userAgentID",
|
||||||
|
BrowserInfo: &user.BrowserInfo{
|
||||||
|
UserAgent: "user-agent",
|
||||||
|
AcceptLanguage: "en",
|
||||||
|
RemoteIP: net.IP{192, 0, 2, 1},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
expectFilter(), // recheck
|
||||||
|
expectFilter(
|
||||||
|
eventFromEventPusher(
|
||||||
|
org.NewLockoutPolicyAddedEvent(ctx,
|
||||||
|
&org.NewAggregate("orgID").Aggregate,
|
||||||
|
1, 1, true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
expectPush(
|
||||||
|
user.NewHumanOTPSMSCheckFailedEvent(ctx,
|
||||||
|
&user.NewAggregate("user1", "org1").Aggregate,
|
||||||
|
&user.AuthRequestInfo{
|
||||||
|
ID: "authRequestID",
|
||||||
|
UserAgentID: "userAgentID",
|
||||||
|
BrowserInfo: &user.BrowserInfo{
|
||||||
|
UserAgent: "user-agent",
|
||||||
|
AcceptLanguage: "en",
|
||||||
|
RemoteIP: net.IP{192, 0, 2, 1},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
user.NewUserLockedEvent(ctx,
|
||||||
|
&user.NewAggregate("user1", "org1").Aggregate,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
userEncryption: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
ctx: ctx,
|
||||||
|
userID: "user1",
|
||||||
|
code: "code",
|
||||||
|
resourceOwner: "org1",
|
||||||
|
authRequest: &domain.AuthRequest{
|
||||||
|
ID: "authRequestID",
|
||||||
|
AgentID: "userAgentID",
|
||||||
|
BrowserInfo: &domain.BrowserInfo{
|
||||||
|
UserAgent: "user-agent",
|
||||||
|
AcceptLanguage: "en",
|
||||||
|
RemoteIP: net.IP{192, 0, 2, 1},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
err: zerrors.ThrowInvalidArgument(nil, "CODE-woT0xc", "Errors.User.Code.Invalid"),
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "code ok",
|
name: "code ok",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
@ -1739,6 +1828,7 @@ func TestCommandSide_HumanCheckOTPSMS(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
expectFilter(), // recheck
|
||||||
expectPush(
|
expectPush(
|
||||||
user.NewHumanOTPSMSCheckSucceededEvent(ctx,
|
user.NewHumanOTPSMSCheckSucceededEvent(ctx,
|
||||||
&user.NewAggregate("user1", "org1").Aggregate,
|
&user.NewAggregate("user1", "org1").Aggregate,
|
||||||
@ -1777,6 +1867,65 @@ func TestCommandSide_HumanCheckOTPSMS(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "code ok, locked in the meantime",
|
||||||
|
fields: fields{
|
||||||
|
eventstore: expectEventstore(
|
||||||
|
expectFilter(
|
||||||
|
eventFromEventPusher(
|
||||||
|
user.NewHumanOTPSMSAddedEvent(ctx,
|
||||||
|
&user.NewAggregate("user1", "org1").Aggregate,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
eventFromEventPusherWithCreationDateNow(
|
||||||
|
user.NewHumanOTPSMSCodeAddedEvent(ctx,
|
||||||
|
&user.NewAggregate("user1", "org1").Aggregate,
|
||||||
|
&crypto.CryptoValue{
|
||||||
|
CryptoType: crypto.TypeEncryption,
|
||||||
|
Algorithm: "enc",
|
||||||
|
KeyID: "id",
|
||||||
|
Crypted: []byte("code"),
|
||||||
|
},
|
||||||
|
time.Hour,
|
||||||
|
&user.AuthRequestInfo{
|
||||||
|
ID: "authRequestID",
|
||||||
|
UserAgentID: "userAgentID",
|
||||||
|
BrowserInfo: &user.BrowserInfo{
|
||||||
|
UserAgent: "user-agent",
|
||||||
|
AcceptLanguage: "en",
|
||||||
|
RemoteIP: net.IP{192, 0, 2, 1},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
expectFilter( // recheck
|
||||||
|
user.NewUserLockedEvent(ctx,
|
||||||
|
&user.NewAggregate("user1", "org1").Aggregate,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
userEncryption: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
ctx: ctx,
|
||||||
|
userID: "user1",
|
||||||
|
code: "code",
|
||||||
|
resourceOwner: "org1",
|
||||||
|
authRequest: &domain.AuthRequest{
|
||||||
|
ID: "authRequestID",
|
||||||
|
AgentID: "userAgentID",
|
||||||
|
BrowserInfo: &domain.BrowserInfo{
|
||||||
|
UserAgent: "user-agent",
|
||||||
|
AcceptLanguage: "en",
|
||||||
|
RemoteIP: net.IP{192, 0, 2, 1},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
err: zerrors.ThrowPreconditionFailed(nil, "COMMAND-S6h4R", "Errors.User.Locked"),
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
@ -2616,6 +2765,15 @@ func TestCommandSide_HumanCheckOTPEmail(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
expectFilter(), // recheck
|
||||||
|
expectFilter(
|
||||||
|
eventFromEventPusher(
|
||||||
|
org.NewLockoutPolicyAddedEvent(ctx,
|
||||||
|
&org.NewAggregate("orgID").Aggregate,
|
||||||
|
3, 3, true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
expectPush(
|
expectPush(
|
||||||
user.NewHumanOTPEmailCheckFailedEvent(ctx,
|
user.NewHumanOTPEmailCheckFailedEvent(ctx,
|
||||||
&user.NewAggregate("user1", "org1").Aggregate,
|
&user.NewAggregate("user1", "org1").Aggregate,
|
||||||
@ -2652,6 +2810,86 @@ func TestCommandSide_HumanCheckOTPEmail(t *testing.T) {
|
|||||||
err: zerrors.ThrowInvalidArgument(nil, "CODE-woT0xc", "Errors.User.Code.Invalid"),
|
err: zerrors.ThrowInvalidArgument(nil, "CODE-woT0xc", "Errors.User.Code.Invalid"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "invalid code, max attempts reached, error",
|
||||||
|
fields: fields{
|
||||||
|
eventstore: expectEventstore(
|
||||||
|
expectFilter(
|
||||||
|
eventFromEventPusher(
|
||||||
|
user.NewHumanOTPEmailAddedEvent(ctx,
|
||||||
|
&user.NewAggregate("user1", "org1").Aggregate,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
eventFromEventPusherWithCreationDateNow(
|
||||||
|
user.NewHumanOTPEmailCodeAddedEvent(ctx,
|
||||||
|
&user.NewAggregate("user1", "org1").Aggregate,
|
||||||
|
&crypto.CryptoValue{
|
||||||
|
CryptoType: crypto.TypeEncryption,
|
||||||
|
Algorithm: "enc",
|
||||||
|
KeyID: "id",
|
||||||
|
Crypted: []byte("other-code"),
|
||||||
|
},
|
||||||
|
time.Hour,
|
||||||
|
&user.AuthRequestInfo{
|
||||||
|
ID: "authRequestID",
|
||||||
|
UserAgentID: "userAgentID",
|
||||||
|
BrowserInfo: &user.BrowserInfo{
|
||||||
|
UserAgent: "user-agent",
|
||||||
|
AcceptLanguage: "en",
|
||||||
|
RemoteIP: net.IP{192, 0, 2, 1},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
expectFilter(), // recheck
|
||||||
|
expectFilter(
|
||||||
|
eventFromEventPusher(
|
||||||
|
org.NewLockoutPolicyAddedEvent(ctx,
|
||||||
|
&org.NewAggregate("orgID").Aggregate,
|
||||||
|
1, 1, true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
expectPush(
|
||||||
|
user.NewHumanOTPEmailCheckFailedEvent(ctx,
|
||||||
|
&user.NewAggregate("user1", "org1").Aggregate,
|
||||||
|
&user.AuthRequestInfo{
|
||||||
|
ID: "authRequestID",
|
||||||
|
UserAgentID: "userAgentID",
|
||||||
|
BrowserInfo: &user.BrowserInfo{
|
||||||
|
UserAgent: "user-agent",
|
||||||
|
AcceptLanguage: "en",
|
||||||
|
RemoteIP: net.IP{192, 0, 2, 1},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
user.NewUserLockedEvent(ctx,
|
||||||
|
&user.NewAggregate("user1", "org1").Aggregate,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
userEncryption: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
ctx: ctx,
|
||||||
|
userID: "user1",
|
||||||
|
code: "code",
|
||||||
|
resourceOwner: "org1",
|
||||||
|
authRequest: &domain.AuthRequest{
|
||||||
|
ID: "authRequestID",
|
||||||
|
AgentID: "userAgentID",
|
||||||
|
BrowserInfo: &domain.BrowserInfo{
|
||||||
|
UserAgent: "user-agent",
|
||||||
|
AcceptLanguage: "en",
|
||||||
|
RemoteIP: net.IP{192, 0, 2, 1},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
err: zerrors.ThrowInvalidArgument(nil, "CODE-woT0xc", "Errors.User.Code.Invalid"),
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "code ok",
|
name: "code ok",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
@ -2684,6 +2922,7 @@ func TestCommandSide_HumanCheckOTPEmail(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
expectFilter(), // recheck
|
||||||
expectPush(
|
expectPush(
|
||||||
user.NewHumanOTPEmailCheckSucceededEvent(ctx,
|
user.NewHumanOTPEmailCheckSucceededEvent(ctx,
|
||||||
&user.NewAggregate("user1", "org1").Aggregate,
|
&user.NewAggregate("user1", "org1").Aggregate,
|
||||||
@ -2722,6 +2961,65 @@ func TestCommandSide_HumanCheckOTPEmail(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "code ok, locked in the meantime",
|
||||||
|
fields: fields{
|
||||||
|
eventstore: expectEventstore(
|
||||||
|
expectFilter(
|
||||||
|
eventFromEventPusher(
|
||||||
|
user.NewHumanOTPEmailAddedEvent(ctx,
|
||||||
|
&user.NewAggregate("user1", "org1").Aggregate,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
eventFromEventPusherWithCreationDateNow(
|
||||||
|
user.NewHumanOTPEmailCodeAddedEvent(ctx,
|
||||||
|
&user.NewAggregate("user1", "org1").Aggregate,
|
||||||
|
&crypto.CryptoValue{
|
||||||
|
CryptoType: crypto.TypeEncryption,
|
||||||
|
Algorithm: "enc",
|
||||||
|
KeyID: "id",
|
||||||
|
Crypted: []byte("code"),
|
||||||
|
},
|
||||||
|
time.Hour,
|
||||||
|
&user.AuthRequestInfo{
|
||||||
|
ID: "authRequestID",
|
||||||
|
UserAgentID: "userAgentID",
|
||||||
|
BrowserInfo: &user.BrowserInfo{
|
||||||
|
UserAgent: "user-agent",
|
||||||
|
AcceptLanguage: "en",
|
||||||
|
RemoteIP: net.IP{192, 0, 2, 1},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
expectFilter( // recheck
|
||||||
|
user.NewUserLockedEvent(ctx,
|
||||||
|
&user.NewAggregate("user1", "org1").Aggregate,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
userEncryption: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
ctx: ctx,
|
||||||
|
userID: "user1",
|
||||||
|
code: "code",
|
||||||
|
resourceOwner: "org1",
|
||||||
|
authRequest: &domain.AuthRequest{
|
||||||
|
ID: "authRequestID",
|
||||||
|
AgentID: "userAgentID",
|
||||||
|
BrowserInfo: &domain.BrowserInfo{
|
||||||
|
UserAgent: "user-agent",
|
||||||
|
AcceptLanguage: "en",
|
||||||
|
RemoteIP: net.IP{192, 0, 2, 1},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
err: zerrors.ThrowPreconditionFailed(nil, "COMMAND-S6h4R", "Errors.User.Locked"),
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
@ -1643,6 +1643,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
|
|||||||
},
|
},
|
||||||
lockoutPolicy: &domain.LockoutPolicy{
|
lockoutPolicy: &domain.LockoutPolicy{
|
||||||
MaxPasswordAttempts: 1,
|
MaxPasswordAttempts: 1,
|
||||||
|
MaxOTPAttempts: 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
|
@ -9,5 +9,6 @@ type LockoutPolicy struct {
|
|||||||
|
|
||||||
Default bool
|
Default bool
|
||||||
MaxPasswordAttempts uint64
|
MaxPasswordAttempts uint64
|
||||||
|
MaxOTPAttempts uint64
|
||||||
ShowLockOutFailures bool
|
ShowLockOutFailures bool
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,7 @@ type LockoutPolicy struct {
|
|||||||
State domain.PolicyState
|
State domain.PolicyState
|
||||||
|
|
||||||
MaxPasswordAttempts uint64
|
MaxPasswordAttempts uint64
|
||||||
|
MaxOTPAttempts uint64
|
||||||
ShowFailures bool
|
ShowFailures bool
|
||||||
|
|
||||||
IsDefault bool
|
IsDefault bool
|
||||||
@ -69,6 +70,10 @@ var (
|
|||||||
name: projection.LockoutPolicyMaxPasswordAttemptsCol,
|
name: projection.LockoutPolicyMaxPasswordAttemptsCol,
|
||||||
table: lockoutTable,
|
table: lockoutTable,
|
||||||
}
|
}
|
||||||
|
LockoutColMaxOTPAttempts = Column{
|
||||||
|
name: projection.LockoutPolicyMaxOTPAttemptsCol,
|
||||||
|
table: lockoutTable,
|
||||||
|
}
|
||||||
LockoutColIsDefault = Column{
|
LockoutColIsDefault = Column{
|
||||||
name: projection.LockoutPolicyIsDefaultCol,
|
name: projection.LockoutPolicyIsDefaultCol,
|
||||||
table: lockoutTable,
|
table: lockoutTable,
|
||||||
@ -77,13 +82,9 @@ var (
|
|||||||
name: projection.LockoutPolicyStateCol,
|
name: projection.LockoutPolicyStateCol,
|
||||||
table: lockoutTable,
|
table: lockoutTable,
|
||||||
}
|
}
|
||||||
LockoutPolicyOwnerRemoved = Column{
|
|
||||||
name: projection.LockoutPolicyOwnerRemovedCol,
|
|
||||||
table: lockoutTable,
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (q *Queries) LockoutPolicyByOrg(ctx context.Context, shouldTriggerBulk bool, orgID string, withOwnerRemoved bool) (policy *LockoutPolicy, err error) {
|
func (q *Queries) LockoutPolicyByOrg(ctx context.Context, shouldTriggerBulk bool, orgID string) (policy *LockoutPolicy, err error) {
|
||||||
ctx, span := tracing.NewSpan(ctx)
|
ctx, span := tracing.NewSpan(ctx)
|
||||||
defer func() { span.EndWithError(err) }()
|
defer func() { span.EndWithError(err) }()
|
||||||
|
|
||||||
@ -96,9 +97,6 @@ func (q *Queries) LockoutPolicyByOrg(ctx context.Context, shouldTriggerBulk bool
|
|||||||
eq := sq.Eq{
|
eq := sq.Eq{
|
||||||
LockoutColInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(),
|
LockoutColInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(),
|
||||||
}
|
}
|
||||||
if !withOwnerRemoved {
|
|
||||||
eq[LockoutPolicyOwnerRemoved.identifier()] = false
|
|
||||||
}
|
|
||||||
|
|
||||||
stmt, scan := prepareLockoutPolicyQuery(ctx, q.client)
|
stmt, scan := prepareLockoutPolicyQuery(ctx, q.client)
|
||||||
query, args, err := stmt.Where(
|
query, args, err := stmt.Where(
|
||||||
@ -153,6 +151,7 @@ func prepareLockoutPolicyQuery(ctx context.Context, db prepareDatabase) (sq.Sele
|
|||||||
LockoutColResourceOwner.identifier(),
|
LockoutColResourceOwner.identifier(),
|
||||||
LockoutColShowFailures.identifier(),
|
LockoutColShowFailures.identifier(),
|
||||||
LockoutColMaxPasswordAttempts.identifier(),
|
LockoutColMaxPasswordAttempts.identifier(),
|
||||||
|
LockoutColMaxOTPAttempts.identifier(),
|
||||||
LockoutColIsDefault.identifier(),
|
LockoutColIsDefault.identifier(),
|
||||||
LockoutColState.identifier(),
|
LockoutColState.identifier(),
|
||||||
).
|
).
|
||||||
@ -168,6 +167,7 @@ func prepareLockoutPolicyQuery(ctx context.Context, db prepareDatabase) (sq.Sele
|
|||||||
&policy.ResourceOwner,
|
&policy.ResourceOwner,
|
||||||
&policy.ShowFailures,
|
&policy.ShowFailures,
|
||||||
&policy.MaxPasswordAttempts,
|
&policy.MaxPasswordAttempts,
|
||||||
|
&policy.MaxOTPAttempts,
|
||||||
&policy.IsDefault,
|
&policy.IsDefault,
|
||||||
&policy.State,
|
&policy.State,
|
||||||
)
|
)
|
||||||
|
@ -13,16 +13,17 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
prepareLockoutPolicyStmt = `SELECT projections.lockout_policies2.id,` +
|
prepareLockoutPolicyStmt = `SELECT projections.lockout_policies3.id,` +
|
||||||
` projections.lockout_policies2.sequence,` +
|
` projections.lockout_policies3.sequence,` +
|
||||||
` projections.lockout_policies2.creation_date,` +
|
` projections.lockout_policies3.creation_date,` +
|
||||||
` projections.lockout_policies2.change_date,` +
|
` projections.lockout_policies3.change_date,` +
|
||||||
` projections.lockout_policies2.resource_owner,` +
|
` projections.lockout_policies3.resource_owner,` +
|
||||||
` projections.lockout_policies2.show_failure,` +
|
` projections.lockout_policies3.show_failure,` +
|
||||||
` projections.lockout_policies2.max_password_attempts,` +
|
` projections.lockout_policies3.max_password_attempts,` +
|
||||||
` projections.lockout_policies2.is_default,` +
|
` projections.lockout_policies3.max_otp_attempts,` +
|
||||||
` projections.lockout_policies2.state` +
|
` projections.lockout_policies3.is_default,` +
|
||||||
` FROM projections.lockout_policies2` +
|
` projections.lockout_policies3.state` +
|
||||||
|
` FROM projections.lockout_policies3` +
|
||||||
` AS OF SYSTEM TIME '-1 ms'`
|
` AS OF SYSTEM TIME '-1 ms'`
|
||||||
|
|
||||||
prepareLockoutPolicyCols = []string{
|
prepareLockoutPolicyCols = []string{
|
||||||
@ -33,6 +34,7 @@ var (
|
|||||||
"resource_owner",
|
"resource_owner",
|
||||||
"show_failure",
|
"show_failure",
|
||||||
"max_password_attempts",
|
"max_password_attempts",
|
||||||
|
"max_otp_attempts",
|
||||||
"is_default",
|
"is_default",
|
||||||
"state",
|
"state",
|
||||||
}
|
}
|
||||||
@ -82,6 +84,7 @@ func Test_LockoutPolicyPrepares(t *testing.T) {
|
|||||||
"ro",
|
"ro",
|
||||||
true,
|
true,
|
||||||
20,
|
20,
|
||||||
|
20,
|
||||||
true,
|
true,
|
||||||
domain.PolicyStateActive,
|
domain.PolicyStateActive,
|
||||||
},
|
},
|
||||||
@ -96,6 +99,7 @@ func Test_LockoutPolicyPrepares(t *testing.T) {
|
|||||||
State: domain.PolicyStateActive,
|
State: domain.PolicyStateActive,
|
||||||
ShowFailures: true,
|
ShowFailures: true,
|
||||||
MaxPasswordAttempts: 20,
|
MaxPasswordAttempts: 20,
|
||||||
|
MaxOTPAttempts: 20,
|
||||||
IsDefault: true,
|
IsDefault: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -14,7 +14,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
LockoutPolicyTable = "projections.lockout_policies2"
|
LockoutPolicyTable = "projections.lockout_policies3"
|
||||||
|
|
||||||
LockoutPolicyIDCol = "id"
|
LockoutPolicyIDCol = "id"
|
||||||
LockoutPolicyCreationDateCol = "creation_date"
|
LockoutPolicyCreationDateCol = "creation_date"
|
||||||
@ -25,8 +25,8 @@ const (
|
|||||||
LockoutPolicyResourceOwnerCol = "resource_owner"
|
LockoutPolicyResourceOwnerCol = "resource_owner"
|
||||||
LockoutPolicyInstanceIDCol = "instance_id"
|
LockoutPolicyInstanceIDCol = "instance_id"
|
||||||
LockoutPolicyMaxPasswordAttemptsCol = "max_password_attempts"
|
LockoutPolicyMaxPasswordAttemptsCol = "max_password_attempts"
|
||||||
|
LockoutPolicyMaxOTPAttemptsCol = "max_otp_attempts"
|
||||||
LockoutPolicyShowLockOutFailuresCol = "show_failure"
|
LockoutPolicyShowLockOutFailuresCol = "show_failure"
|
||||||
LockoutPolicyOwnerRemovedCol = "owner_removed"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type lockoutPolicyProjection struct{}
|
type lockoutPolicyProjection struct{}
|
||||||
@ -51,11 +51,10 @@ func (*lockoutPolicyProjection) Init() *old_handler.Check {
|
|||||||
handler.NewColumn(LockoutPolicyResourceOwnerCol, handler.ColumnTypeText),
|
handler.NewColumn(LockoutPolicyResourceOwnerCol, handler.ColumnTypeText),
|
||||||
handler.NewColumn(LockoutPolicyInstanceIDCol, handler.ColumnTypeText),
|
handler.NewColumn(LockoutPolicyInstanceIDCol, handler.ColumnTypeText),
|
||||||
handler.NewColumn(LockoutPolicyMaxPasswordAttemptsCol, handler.ColumnTypeInt64),
|
handler.NewColumn(LockoutPolicyMaxPasswordAttemptsCol, handler.ColumnTypeInt64),
|
||||||
|
handler.NewColumn(LockoutPolicyMaxOTPAttemptsCol, handler.ColumnTypeInt64, handler.Default(0)),
|
||||||
handler.NewColumn(LockoutPolicyShowLockOutFailuresCol, handler.ColumnTypeBool),
|
handler.NewColumn(LockoutPolicyShowLockOutFailuresCol, handler.ColumnTypeBool),
|
||||||
handler.NewColumn(LockoutPolicyOwnerRemovedCol, handler.ColumnTypeBool, handler.Default(false)),
|
|
||||||
},
|
},
|
||||||
handler.NewPrimaryKey(LockoutPolicyInstanceIDCol, LockoutPolicyIDCol),
|
handler.NewPrimaryKey(LockoutPolicyInstanceIDCol, LockoutPolicyIDCol),
|
||||||
handler.WithIndex(handler.NewIndex("owner_removed", []string{LockoutPolicyOwnerRemovedCol})),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -125,6 +124,7 @@ func (p *lockoutPolicyProjection) reduceAdded(event eventstore.Event) (*handler.
|
|||||||
handler.NewCol(LockoutPolicyIDCol, policyEvent.Aggregate().ID),
|
handler.NewCol(LockoutPolicyIDCol, policyEvent.Aggregate().ID),
|
||||||
handler.NewCol(LockoutPolicyStateCol, domain.PolicyStateActive),
|
handler.NewCol(LockoutPolicyStateCol, domain.PolicyStateActive),
|
||||||
handler.NewCol(LockoutPolicyMaxPasswordAttemptsCol, policyEvent.MaxPasswordAttempts),
|
handler.NewCol(LockoutPolicyMaxPasswordAttemptsCol, policyEvent.MaxPasswordAttempts),
|
||||||
|
handler.NewCol(LockoutPolicyMaxOTPAttemptsCol, policyEvent.MaxOTPAttempts),
|
||||||
handler.NewCol(LockoutPolicyShowLockOutFailuresCol, policyEvent.ShowLockOutFailures),
|
handler.NewCol(LockoutPolicyShowLockOutFailuresCol, policyEvent.ShowLockOutFailures),
|
||||||
handler.NewCol(LockoutPolicyIsDefaultCol, isDefault),
|
handler.NewCol(LockoutPolicyIsDefaultCol, isDefault),
|
||||||
handler.NewCol(LockoutPolicyResourceOwnerCol, policyEvent.Aggregate().ResourceOwner),
|
handler.NewCol(LockoutPolicyResourceOwnerCol, policyEvent.Aggregate().ResourceOwner),
|
||||||
@ -149,6 +149,9 @@ func (p *lockoutPolicyProjection) reduceChanged(event eventstore.Event) (*handle
|
|||||||
if policyEvent.MaxPasswordAttempts != nil {
|
if policyEvent.MaxPasswordAttempts != nil {
|
||||||
cols = append(cols, handler.NewCol(LockoutPolicyMaxPasswordAttemptsCol, *policyEvent.MaxPasswordAttempts))
|
cols = append(cols, handler.NewCol(LockoutPolicyMaxPasswordAttemptsCol, *policyEvent.MaxPasswordAttempts))
|
||||||
}
|
}
|
||||||
|
if policyEvent.MaxOTPAttempts != nil {
|
||||||
|
cols = append(cols, handler.NewCol(LockoutPolicyMaxOTPAttemptsCol, *policyEvent.MaxOTPAttempts))
|
||||||
|
}
|
||||||
if policyEvent.ShowLockOutFailures != nil {
|
if policyEvent.ShowLockOutFailures != nil {
|
||||||
cols = append(cols, handler.NewCol(LockoutPolicyShowLockOutFailuresCol, *policyEvent.ShowLockOutFailures))
|
cols = append(cols, handler.NewCol(LockoutPolicyShowLockOutFailuresCol, *policyEvent.ShowLockOutFailures))
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,7 @@ func TestLockoutPolicyProjection_reduces(t *testing.T) {
|
|||||||
org.AggregateType,
|
org.AggregateType,
|
||||||
[]byte(`{
|
[]byte(`{
|
||||||
"maxPasswordAttempts": 10,
|
"maxPasswordAttempts": 10,
|
||||||
|
"maxOTPAttempts": 10,
|
||||||
"showLockOutFailures": true
|
"showLockOutFailures": true
|
||||||
}`),
|
}`),
|
||||||
), org.LockoutPolicyAddedEventMapper),
|
), org.LockoutPolicyAddedEventMapper),
|
||||||
@ -41,7 +42,7 @@ func TestLockoutPolicyProjection_reduces(t *testing.T) {
|
|||||||
executer: &testExecuter{
|
executer: &testExecuter{
|
||||||
executions: []execution{
|
executions: []execution{
|
||||||
{
|
{
|
||||||
expectedStmt: "INSERT INTO projections.lockout_policies2 (creation_date, change_date, sequence, id, state, max_password_attempts, show_failure, is_default, resource_owner, instance_id) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)",
|
expectedStmt: "INSERT INTO projections.lockout_policies3 (creation_date, change_date, sequence, id, state, max_password_attempts, max_otp_attempts, show_failure, is_default, resource_owner, instance_id) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
anyArg{},
|
anyArg{},
|
||||||
anyArg{},
|
anyArg{},
|
||||||
@ -49,6 +50,7 @@ func TestLockoutPolicyProjection_reduces(t *testing.T) {
|
|||||||
"agg-id",
|
"agg-id",
|
||||||
domain.PolicyStateActive,
|
domain.PolicyStateActive,
|
||||||
uint64(10),
|
uint64(10),
|
||||||
|
uint64(10),
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
"ro-id",
|
"ro-id",
|
||||||
@ -69,6 +71,7 @@ func TestLockoutPolicyProjection_reduces(t *testing.T) {
|
|||||||
org.AggregateType,
|
org.AggregateType,
|
||||||
[]byte(`{
|
[]byte(`{
|
||||||
"maxPasswordAttempts": 10,
|
"maxPasswordAttempts": 10,
|
||||||
|
"maxOTPAttempts": 10,
|
||||||
"showLockOutFailures": true
|
"showLockOutFailures": true
|
||||||
}`),
|
}`),
|
||||||
), org.LockoutPolicyChangedEventMapper),
|
), org.LockoutPolicyChangedEventMapper),
|
||||||
@ -79,11 +82,12 @@ func TestLockoutPolicyProjection_reduces(t *testing.T) {
|
|||||||
executer: &testExecuter{
|
executer: &testExecuter{
|
||||||
executions: []execution{
|
executions: []execution{
|
||||||
{
|
{
|
||||||
expectedStmt: "UPDATE projections.lockout_policies2 SET (change_date, sequence, max_password_attempts, show_failure) = ($1, $2, $3, $4) WHERE (id = $5) AND (instance_id = $6)",
|
expectedStmt: "UPDATE projections.lockout_policies3 SET (change_date, sequence, max_password_attempts, max_otp_attempts, show_failure) = ($1, $2, $3, $4, $5) WHERE (id = $6) AND (instance_id = $7)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
anyArg{},
|
anyArg{},
|
||||||
uint64(15),
|
uint64(15),
|
||||||
uint64(10),
|
uint64(10),
|
||||||
|
uint64(10),
|
||||||
true,
|
true,
|
||||||
"agg-id",
|
"agg-id",
|
||||||
"instance-id",
|
"instance-id",
|
||||||
@ -110,7 +114,7 @@ func TestLockoutPolicyProjection_reduces(t *testing.T) {
|
|||||||
executer: &testExecuter{
|
executer: &testExecuter{
|
||||||
executions: []execution{
|
executions: []execution{
|
||||||
{
|
{
|
||||||
expectedStmt: "DELETE FROM projections.lockout_policies2 WHERE (id = $1) AND (instance_id = $2)",
|
expectedStmt: "DELETE FROM projections.lockout_policies3 WHERE (id = $1) AND (instance_id = $2)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
"agg-id",
|
"agg-id",
|
||||||
"instance-id",
|
"instance-id",
|
||||||
@ -137,7 +141,7 @@ func TestLockoutPolicyProjection_reduces(t *testing.T) {
|
|||||||
executer: &testExecuter{
|
executer: &testExecuter{
|
||||||
executions: []execution{
|
executions: []execution{
|
||||||
{
|
{
|
||||||
expectedStmt: "DELETE FROM projections.lockout_policies2 WHERE (instance_id = $1)",
|
expectedStmt: "DELETE FROM projections.lockout_policies3 WHERE (instance_id = $1)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
"agg-id",
|
"agg-id",
|
||||||
},
|
},
|
||||||
@ -156,6 +160,7 @@ func TestLockoutPolicyProjection_reduces(t *testing.T) {
|
|||||||
instance.AggregateType,
|
instance.AggregateType,
|
||||||
[]byte(`{
|
[]byte(`{
|
||||||
"maxPasswordAttempts": 10,
|
"maxPasswordAttempts": 10,
|
||||||
|
"maxOTPAttempts": 10,
|
||||||
"showLockOutFailures": true
|
"showLockOutFailures": true
|
||||||
}`),
|
}`),
|
||||||
), instance.LockoutPolicyAddedEventMapper),
|
), instance.LockoutPolicyAddedEventMapper),
|
||||||
@ -166,7 +171,7 @@ func TestLockoutPolicyProjection_reduces(t *testing.T) {
|
|||||||
executer: &testExecuter{
|
executer: &testExecuter{
|
||||||
executions: []execution{
|
executions: []execution{
|
||||||
{
|
{
|
||||||
expectedStmt: "INSERT INTO projections.lockout_policies2 (creation_date, change_date, sequence, id, state, max_password_attempts, show_failure, is_default, resource_owner, instance_id) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)",
|
expectedStmt: "INSERT INTO projections.lockout_policies3 (creation_date, change_date, sequence, id, state, max_password_attempts, max_otp_attempts, show_failure, is_default, resource_owner, instance_id) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
anyArg{},
|
anyArg{},
|
||||||
anyArg{},
|
anyArg{},
|
||||||
@ -174,6 +179,7 @@ func TestLockoutPolicyProjection_reduces(t *testing.T) {
|
|||||||
"agg-id",
|
"agg-id",
|
||||||
domain.PolicyStateActive,
|
domain.PolicyStateActive,
|
||||||
uint64(10),
|
uint64(10),
|
||||||
|
uint64(10),
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
"ro-id",
|
"ro-id",
|
||||||
@ -194,6 +200,7 @@ func TestLockoutPolicyProjection_reduces(t *testing.T) {
|
|||||||
instance.AggregateType,
|
instance.AggregateType,
|
||||||
[]byte(`{
|
[]byte(`{
|
||||||
"maxPasswordAttempts": 10,
|
"maxPasswordAttempts": 10,
|
||||||
|
"maxOTPAttempts": 10,
|
||||||
"showLockOutFailures": true
|
"showLockOutFailures": true
|
||||||
}`),
|
}`),
|
||||||
), instance.LockoutPolicyChangedEventMapper),
|
), instance.LockoutPolicyChangedEventMapper),
|
||||||
@ -204,11 +211,12 @@ func TestLockoutPolicyProjection_reduces(t *testing.T) {
|
|||||||
executer: &testExecuter{
|
executer: &testExecuter{
|
||||||
executions: []execution{
|
executions: []execution{
|
||||||
{
|
{
|
||||||
expectedStmt: "UPDATE projections.lockout_policies2 SET (change_date, sequence, max_password_attempts, show_failure) = ($1, $2, $3, $4) WHERE (id = $5) AND (instance_id = $6)",
|
expectedStmt: "UPDATE projections.lockout_policies3 SET (change_date, sequence, max_password_attempts, max_otp_attempts, show_failure) = ($1, $2, $3, $4, $5) WHERE (id = $6) AND (instance_id = $7)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
anyArg{},
|
anyArg{},
|
||||||
uint64(15),
|
uint64(15),
|
||||||
uint64(10),
|
uint64(10),
|
||||||
|
uint64(10),
|
||||||
true,
|
true,
|
||||||
"agg-id",
|
"agg-id",
|
||||||
"instance-id",
|
"instance-id",
|
||||||
@ -235,7 +243,7 @@ func TestLockoutPolicyProjection_reduces(t *testing.T) {
|
|||||||
executer: &testExecuter{
|
executer: &testExecuter{
|
||||||
executions: []execution{
|
executions: []execution{
|
||||||
{
|
{
|
||||||
expectedStmt: "DELETE FROM projections.lockout_policies2 WHERE (instance_id = $1) AND (resource_owner = $2)",
|
expectedStmt: "DELETE FROM projections.lockout_policies3 WHERE (instance_id = $1) AND (resource_owner = $2)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
"instance-id",
|
"instance-id",
|
||||||
"agg-id",
|
"agg-id",
|
||||||
|
@ -19,7 +19,8 @@ type LockoutPolicyAddedEvent struct {
|
|||||||
func NewLockoutPolicyAddedEvent(
|
func NewLockoutPolicyAddedEvent(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
aggregate *eventstore.Aggregate,
|
aggregate *eventstore.Aggregate,
|
||||||
maxAttempts uint64,
|
maxPasswordAttempts,
|
||||||
|
maxOTPAttempts uint64,
|
||||||
showLockoutFailure bool,
|
showLockoutFailure bool,
|
||||||
) *LockoutPolicyAddedEvent {
|
) *LockoutPolicyAddedEvent {
|
||||||
return &LockoutPolicyAddedEvent{
|
return &LockoutPolicyAddedEvent{
|
||||||
@ -28,7 +29,8 @@ func NewLockoutPolicyAddedEvent(
|
|||||||
ctx,
|
ctx,
|
||||||
aggregate,
|
aggregate,
|
||||||
LockoutPolicyAddedEventType),
|
LockoutPolicyAddedEventType),
|
||||||
maxAttempts,
|
maxPasswordAttempts,
|
||||||
|
maxOTPAttempts,
|
||||||
showLockoutFailure),
|
showLockoutFailure),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,8 @@ type LockoutPolicyAddedEvent struct {
|
|||||||
func NewLockoutPolicyAddedEvent(
|
func NewLockoutPolicyAddedEvent(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
aggregate *eventstore.Aggregate,
|
aggregate *eventstore.Aggregate,
|
||||||
maxAttempts uint64,
|
maxPasswordAttempts,
|
||||||
|
maxOTPAttempts uint64,
|
||||||
showLockoutFailure bool,
|
showLockoutFailure bool,
|
||||||
) *LockoutPolicyAddedEvent {
|
) *LockoutPolicyAddedEvent {
|
||||||
return &LockoutPolicyAddedEvent{
|
return &LockoutPolicyAddedEvent{
|
||||||
@ -29,7 +30,8 @@ func NewLockoutPolicyAddedEvent(
|
|||||||
ctx,
|
ctx,
|
||||||
aggregate,
|
aggregate,
|
||||||
LockoutPolicyAddedEventType),
|
LockoutPolicyAddedEventType),
|
||||||
maxAttempts,
|
maxPasswordAttempts,
|
||||||
|
maxOTPAttempts,
|
||||||
showLockoutFailure),
|
showLockoutFailure),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ type LockoutPolicyAddedEvent struct {
|
|||||||
eventstore.BaseEvent `json:"-"`
|
eventstore.BaseEvent `json:"-"`
|
||||||
|
|
||||||
MaxPasswordAttempts uint64 `json:"maxPasswordAttempts,omitempty"`
|
MaxPasswordAttempts uint64 `json:"maxPasswordAttempts,omitempty"`
|
||||||
|
MaxOTPAttempts uint64 `json:"maxOTPAttempts,omitempty"`
|
||||||
ShowLockOutFailures bool `json:"showLockOutFailures,omitempty"`
|
ShowLockOutFailures bool `json:"showLockOutFailures,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,13 +29,15 @@ func (e *LockoutPolicyAddedEvent) UniqueConstraints() []*eventstore.UniqueConstr
|
|||||||
|
|
||||||
func NewLockoutPolicyAddedEvent(
|
func NewLockoutPolicyAddedEvent(
|
||||||
base *eventstore.BaseEvent,
|
base *eventstore.BaseEvent,
|
||||||
maxAttempts uint64,
|
maxPasswordAttempts,
|
||||||
|
maxOTPAttempts uint64,
|
||||||
showLockOutFailures bool,
|
showLockOutFailures bool,
|
||||||
) *LockoutPolicyAddedEvent {
|
) *LockoutPolicyAddedEvent {
|
||||||
|
|
||||||
return &LockoutPolicyAddedEvent{
|
return &LockoutPolicyAddedEvent{
|
||||||
BaseEvent: *base,
|
BaseEvent: *base,
|
||||||
MaxPasswordAttempts: maxAttempts,
|
MaxPasswordAttempts: maxPasswordAttempts,
|
||||||
|
MaxOTPAttempts: maxOTPAttempts,
|
||||||
ShowLockOutFailures: showLockOutFailures,
|
ShowLockOutFailures: showLockOutFailures,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -56,6 +59,7 @@ type LockoutPolicyChangedEvent struct {
|
|||||||
eventstore.BaseEvent `json:"-"`
|
eventstore.BaseEvent `json:"-"`
|
||||||
|
|
||||||
MaxPasswordAttempts *uint64 `json:"maxPasswordAttempts,omitempty"`
|
MaxPasswordAttempts *uint64 `json:"maxPasswordAttempts,omitempty"`
|
||||||
|
MaxOTPAttempts *uint64 `json:"maxOTPAttempts,omitempty"`
|
||||||
ShowLockOutFailures *bool `json:"showLockOutFailures,omitempty"`
|
ShowLockOutFailures *bool `json:"showLockOutFailures,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,12 +89,18 @@ func NewLockoutPolicyChangedEvent(
|
|||||||
|
|
||||||
type LockoutPolicyChanges func(*LockoutPolicyChangedEvent)
|
type LockoutPolicyChanges func(*LockoutPolicyChangedEvent)
|
||||||
|
|
||||||
func ChangeMaxAttempts(maxAttempts uint64) func(*LockoutPolicyChangedEvent) {
|
func ChangeMaxPasswordAttempts(maxAttempts uint64) func(*LockoutPolicyChangedEvent) {
|
||||||
return func(e *LockoutPolicyChangedEvent) {
|
return func(e *LockoutPolicyChangedEvent) {
|
||||||
e.MaxPasswordAttempts = &maxAttempts
|
e.MaxPasswordAttempts = &maxAttempts
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ChangeMaxOTPAttempts(maxAttempts uint64) func(*LockoutPolicyChangedEvent) {
|
||||||
|
return func(e *LockoutPolicyChangedEvent) {
|
||||||
|
e.MaxOTPAttempts = &maxAttempts
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func ChangeShowLockOutFailures(showLockOutFailures bool) func(*LockoutPolicyChangedEvent) {
|
func ChangeShowLockOutFailures(showLockOutFailures bool) func(*LockoutPolicyChangedEvent) {
|
||||||
return func(e *LockoutPolicyChangedEvent) {
|
return func(e *LockoutPolicyChangedEvent) {
|
||||||
e.ShowLockOutFailures = &showLockOutFailures
|
e.ShowLockOutFailures = &showLockOutFailures
|
||||||
|
@ -6650,6 +6650,12 @@ message UpdateLockoutPolicyRequest {
|
|||||||
example: "\"10\""
|
example: "\"10\""
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
uint32 max_otp_attempts = 2 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "Maximum failed attempts for a single OTP type (TOTP, SMS, Email) before the account gets locked. Attempts are reset as soon as the OTP is entered correctly. If set to 0 the account will never be locked."
|
||||||
|
example: "\"10\""
|
||||||
|
}
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
message UpdateLockoutPolicyResponse {
|
message UpdateLockoutPolicyResponse {
|
||||||
|
@ -10412,6 +10412,12 @@ message AddCustomLockoutPolicyRequest {
|
|||||||
description: "When the user has reached the maximum password attempts the account will be locked, If this is set to 0 the lockout will not trigger."
|
description: "When the user has reached the maximum password attempts the account will be locked, If this is set to 0 the lockout will not trigger."
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
uint32 max_otp_attempts = 2 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "Maximum failed attempts for a single OTP type (TOTP, SMS, Email) before the account gets locked. Attempts are reset as soon as the OTP is entered correctly. If set to 0 the account will never be locked."
|
||||||
|
example: "\"10\""
|
||||||
|
}
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
message AddCustomLockoutPolicyResponse {
|
message AddCustomLockoutPolicyResponse {
|
||||||
@ -10424,6 +10430,12 @@ message UpdateCustomLockoutPolicyRequest {
|
|||||||
description: "When the user has reached the maximum password attempts the account will be locked, If this is set to 0 the lockout will not trigger."
|
description: "When the user has reached the maximum password attempts the account will be locked, If this is set to 0 the lockout will not trigger."
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
uint32 max_otp_attempts = 2 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "Maximum failed attempts for a single OTP type (TOTP, SMS, Email) before the account gets locked. Attempts are reset as soon as the OTP is entered correctly. If set to 0 the account will never be locked."
|
||||||
|
example: "\"10\""
|
||||||
|
}
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
message UpdateCustomLockoutPolicyResponse {
|
message UpdateCustomLockoutPolicyResponse {
|
||||||
|
@ -337,6 +337,12 @@ message LockoutPolicy {
|
|||||||
example: "\"10\""
|
example: "\"10\""
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
uint64 max_otp_attempts = 3 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "Maximum failed attempts for a single OTP type (TOTP, SMS, Email) before the account gets locked. Attempts are reset as soon as the OTP is entered correctly. If set to 0 the account will never be locked."
|
||||||
|
example: "\"10\""
|
||||||
|
}
|
||||||
|
];
|
||||||
bool is_default = 4 [
|
bool is_default = 4 [
|
||||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
description: "defines if the organization's admin changed the policy"
|
description: "defines if the organization's admin changed the policy"
|
||||||
|
@ -20,4 +20,10 @@ message LockoutSettings {
|
|||||||
description: "resource_owner_type returns if the settings is managed on the organization or on the instance";
|
description: "resource_owner_type returns if the settings is managed on the organization or on the instance";
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
uint64 max_otp_attempts = 3 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "Maximum failed attempts for a single OTP type (TOTP, SMS, Email) before the account gets locked. Attempts are reset as soon as the OTP is entered correctly. If set to 0 the account will never be locked."
|
||||||
|
example: "\"10\""
|
||||||
|
}
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user