feat: add secret generators for OTP (#6262)

This PR adds configuration options for OTP codes through Admin API.
This commit is contained in:
Livio Spring 2023-07-26 13:00:41 +02:00 committed by GitHub
parent 2241c82134
commit 2fe76acd14
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 199 additions and 110 deletions

View File

@ -564,6 +564,20 @@ DefaultInstance:
IncludeUpperLetters: true IncludeUpperLetters: true
IncludeDigits: true IncludeDigits: true
IncludeSymbols: false IncludeSymbols: false
OTPSMS:
Length: 8
Expiry: "5m"
IncludeLowerLetters: false
IncludeUpperLetters: false
IncludeDigits: true
IncludeSymbols: false
OTPEmail:
Length: 8
Expiry: "5m"
IncludeLowerLetters: false
IncludeUpperLetters: false
IncludeDigits: true
IncludeSymbols: false
PasswordComplexityPolicy: PasswordComplexityPolicy:
MinLength: 8 MinLength: 8
HasLowercase: true HasLowercase: true

View File

@ -3,15 +3,6 @@
</h1> </h1>
<div mat-dialog-content> <div mat-dialog-content>
<form *ngIf="specsForm" (ngSubmit)="closeDialogWithRequest()" [formGroup]="specsForm"> <form *ngIf="specsForm" (ngSubmit)="closeDialogWithRequest()" [formGroup]="specsForm">
<cnsl-form-field class="type-form-field" label="Access Code" required="true">
<cnsl-label>{{ 'SETTING.SECRETS.GENERATORTYPE' | translate }}</cnsl-label>
<mat-select formControlName="generatorType">
<mat-option *ngFor="let gen of availableGenerators" [value]="gen">
<span>{{ 'SETTING.SECRETS.TYPE.' + gen | translate }}</span>
</mat-option>
</mat-select>
</cnsl-form-field>
<h2 class="generator-type">{{ 'SETTING.SECRETS.TYPE.' + generatorType?.value | translate }}</h2> <h2 class="generator-type">{{ 'SETTING.SECRETS.TYPE.' + generatorType?.value | translate }}</h2>
<cnsl-form-field class="generator-form-field" label="Expiration"> <cnsl-form-field class="generator-form-field" label="Expiration">

View File

@ -7,7 +7,6 @@ import {
import { Duration } from 'google-protobuf/google/protobuf/duration_pb'; import { Duration } from 'google-protobuf/google/protobuf/duration_pb';
import { requiredValidator } from 'src/app/modules/form-field/validators/validators'; import { requiredValidator } from 'src/app/modules/form-field/validators/validators';
import { UpdateSecretGeneratorRequest } from 'src/app/proto/generated/zitadel/admin_pb'; import { UpdateSecretGeneratorRequest } from 'src/app/proto/generated/zitadel/admin_pb';
import { SecretGeneratorType } from 'src/app/proto/generated/zitadel/settings_pb';
@Component({ @Component({
selector: 'cnsl-dialog-add-secret-generator', selector: 'cnsl-dialog-add-secret-generator',
@ -15,15 +14,6 @@ import { SecretGeneratorType } from 'src/app/proto/generated/zitadel/settings_pb
styleUrls: ['./dialog-add-secret-generator.component.scss'], styleUrls: ['./dialog-add-secret-generator.component.scss'],
}) })
export class DialogAddSecretGeneratorComponent { export class DialogAddSecretGeneratorComponent {
public SecretGeneratorType: any = SecretGeneratorType;
public availableGenerators: SecretGeneratorType[] = [
SecretGeneratorType.SECRET_GENERATOR_TYPE_INIT_CODE,
SecretGeneratorType.SECRET_GENERATOR_TYPE_VERIFY_EMAIL_CODE,
SecretGeneratorType.SECRET_GENERATOR_TYPE_VERIFY_PHONE_CODE,
SecretGeneratorType.SECRET_GENERATOR_TYPE_PASSWORD_RESET_CODE,
SecretGeneratorType.SECRET_GENERATOR_TYPE_PASSWORDLESS_INIT_CODE,
SecretGeneratorType.SECRET_GENERATOR_TYPE_APP_SECRET,
];
public req: UpdateSecretGeneratorRequest = new UpdateSecretGeneratorRequest(); public req: UpdateSecretGeneratorRequest = new UpdateSecretGeneratorRequest();
public specsForm!: UntypedFormGroup; public specsForm!: UntypedFormGroup;
@ -33,17 +23,19 @@ export class DialogAddSecretGeneratorComponent {
public dialogRef: MatDialogRef<DialogAddSecretGeneratorComponent>, public dialogRef: MatDialogRef<DialogAddSecretGeneratorComponent>,
@Inject(MAT_DIALOG_DATA) public data: any, @Inject(MAT_DIALOG_DATA) public data: any,
) { ) {
let exp = 1;
if (data.config?.expiry !== undefined) {
exp = this.durationToHour(data.config?.expiry);
}
this.specsForm = this.fb.group({ this.specsForm = this.fb.group({
generatorType: [SecretGeneratorType.SECRET_GENERATOR_TYPE_APP_SECRET, [requiredValidator]], generatorType: [data.type, [requiredValidator]],
expiry: [1, [requiredValidator]], expiry: [exp, [requiredValidator]],
includeDigits: [true, [requiredValidator]], length: [data.config?.length ?? 6, [requiredValidator]],
includeLowerLetters: [true, [requiredValidator]], includeDigits: [data.config?.includeDigits ?? true, [requiredValidator]],
includeSymbols: [true, [requiredValidator]], includeLowerLetters: [data.config?.includeSymbols ?? true, [requiredValidator]],
includeUpperLetters: [true, [requiredValidator]], includeSymbols: [data.config?.includeLowerLetters ?? true, [requiredValidator]],
length: [6, [requiredValidator]], includeUpperLetters: [data.config?.includeUpperLetters ?? true, [requiredValidator]],
}); });
this.generatorType?.setValue(data.type);
} }
public closeDialog(): void { public closeDialog(): void {
@ -52,10 +44,7 @@ export class DialogAddSecretGeneratorComponent {
public closeDialogWithRequest(): void { public closeDialogWithRequest(): void {
this.req.setGeneratorType(this.generatorType?.value); this.req.setGeneratorType(this.generatorType?.value);
this.req.setExpiry(this.hourToDuration(this.expiry?.value));
const expiry = new Duration().setSeconds((this.expiry?.value ?? 1) * 60 * 60);
this.req.setExpiry(expiry);
this.req.setIncludeDigits(this.includeDigits?.value); this.req.setIncludeDigits(this.includeDigits?.value);
this.req.setIncludeLowerLetters(this.includeLowerLetters?.value); this.req.setIncludeLowerLetters(this.includeLowerLetters?.value);
this.req.setIncludeSymbols(this.includeSymbols?.value); this.req.setIncludeSymbols(this.includeSymbols?.value);
@ -92,4 +81,18 @@ export class DialogAddSecretGeneratorComponent {
public get length(): AbstractControl | null { public get length(): AbstractControl | null {
return this.specsForm.get('length'); return this.specsForm.get('length');
} }
private durationToHour(duration: Duration.AsObject): number {
if (duration.seconds === 0) {
return 0;
}
return (duration.seconds + duration.nanos / 1000000) / 3600;
}
private hourToDuration(hour: number): Duration {
const exp = hour * 60 * 60;
const sec = Math.floor(exp);
const nanos = Math.round((exp - sec) * 1000000);
return new Duration().setSeconds(sec).setNanos(nanos);
}
} }

View File

@ -1,6 +1,6 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog'; import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog';
import { UpdateSecretGeneratorRequest, UpdateSecretGeneratorResponse } from 'src/app/proto/generated/zitadel/admin_pb'; import { UpdateSecretGeneratorRequest } from 'src/app/proto/generated/zitadel/admin_pb';
import { OIDCSettings, SecretGenerator, SecretGeneratorType } from 'src/app/proto/generated/zitadel/settings_pb'; import { OIDCSettings, SecretGenerator, SecretGeneratorType } from 'src/app/proto/generated/zitadel/settings_pb';
import { AdminService } from 'src/app/services/admin.service'; import { AdminService } from 'src/app/services/admin.service';
import { ToastService } from 'src/app/services/toast.service'; import { ToastService } from 'src/app/services/toast.service';
@ -25,7 +25,10 @@ export class SecretGeneratorComponent implements OnInit {
SecretGeneratorType.SECRET_GENERATOR_TYPE_PASSWORD_RESET_CODE, SecretGeneratorType.SECRET_GENERATOR_TYPE_PASSWORD_RESET_CODE,
SecretGeneratorType.SECRET_GENERATOR_TYPE_PASSWORDLESS_INIT_CODE, SecretGeneratorType.SECRET_GENERATOR_TYPE_PASSWORDLESS_INIT_CODE,
SecretGeneratorType.SECRET_GENERATOR_TYPE_APP_SECRET, SecretGeneratorType.SECRET_GENERATOR_TYPE_APP_SECRET,
SecretGeneratorType.SECRET_GENERATOR_TYPE_OTP_SMS,
SecretGeneratorType.SECRET_GENERATOR_TYPE_OTP_EMAIL,
]; ];
constructor(private service: AdminService, private toast: ToastService, private dialog: MatDialog) {} constructor(private service: AdminService, private toast: ToastService, private dialog: MatDialog) {}
ngOnInit(): void { ngOnInit(): void {
@ -48,25 +51,12 @@ export class SecretGeneratorComponent implements OnInit {
}); });
} }
private updateData(): Promise<UpdateSecretGeneratorResponse.AsObject> | void {
const dialogRef = this.dialog.open(DialogAddSecretGeneratorComponent, {
data: {},
width: '400px',
});
dialogRef.afterClosed().subscribe((req: UpdateSecretGeneratorRequest) => {
if (req) {
return (this.service as AdminService).updateSecretGenerator(req);
} else {
return;
}
});
}
public openGeneratorDialog(generatorType: SecretGeneratorType): void { public openGeneratorDialog(generatorType: SecretGeneratorType): void {
let config = this.generators.find((gen) => gen.generatorType === generatorType);
const dialogRef = this.dialog.open(DialogAddSecretGeneratorComponent, { const dialogRef = this.dialog.open(DialogAddSecretGeneratorComponent, {
data: { data: {
type: generatorType, type: generatorType,
config: config,
}, },
width: '400px', width: '400px',
}); });
@ -77,6 +67,9 @@ export class SecretGeneratorComponent implements OnInit {
.updateSecretGenerator(req) .updateSecretGenerator(req)
.then(() => { .then(() => {
this.toast.showInfo('SETTING.SECRETS.UPDATED', true); this.toast.showInfo('SETTING.SECRETS.UPDATED', true);
setTimeout(() => {
this.fetchData();
}, 2000);
}) })
.catch((error) => { .catch((error) => {
this.toast.showError(error); this.toast.showError(error);
@ -86,21 +79,4 @@ export class SecretGeneratorComponent implements OnInit {
} }
}); });
} }
public savePolicy(): void {
const prom = this.updateData();
if (prom) {
prom
.then(() => {
this.toast.showInfo('SETTING.SMTP.SAVED', true);
this.loading = true;
setTimeout(() => {
this.fetchData();
}, 2000);
})
.catch((error) => {
this.toast.showError(error);
});
}
}
} }

View File

@ -1095,7 +1095,9 @@
"3": "Телефонна проверка", "3": "Телефонна проверка",
"4": "Нулиране на парола", "4": "Нулиране на парола",
"5": "Инициализация без парола", "5": "Инициализация без парола",
"6": "Тайна на приложението" "6": "Тайна на приложението",
"7": "Еднократна парола (OTP) - SMS",
"8": "Еднократна парола (OTP) имейл"
}, },
"ADDGENERATOR": "Определете тайния външен вид", "ADDGENERATOR": "Определете тайния външен вид",
"GENERATORTYPE": "Тип", "GENERATORTYPE": "Тип",

View File

@ -1101,7 +1101,9 @@
"3": "Telefonnummer Verificationscode", "3": "Telefonnummer Verificationscode",
"4": "Passwort Zurücksetzen Code", "4": "Passwort Zurücksetzen Code",
"5": "Passwordless Initialisierungscode", "5": "Passwordless Initialisierungscode",
"6": "Applicationssecret" "6": "Applicationssecret",
"7": "One Time Password (OTP) - SMS",
"8": "One Time Password (OTP) - Email"
}, },
"ADDGENERATOR": "Secret Erscheinungsbild definieren", "ADDGENERATOR": "Secret Erscheinungsbild definieren",
"GENERATORTYPE": "Typ", "GENERATORTYPE": "Typ",

View File

@ -1102,7 +1102,9 @@
"3": "Phone verification", "3": "Phone verification",
"4": "Password Reset", "4": "Password Reset",
"5": "Passwordless Initialization", "5": "Passwordless Initialization",
"6": "App Secret" "6": "App Secret",
"7": "One Time Password (OTP) - SMS",
"8": "One Time Password (OTP) - Email"
}, },
"ADDGENERATOR": "Define Secret Appearance", "ADDGENERATOR": "Define Secret Appearance",
"GENERATORTYPE": "Type", "GENERATORTYPE": "Type",

View File

@ -1102,7 +1102,9 @@
"3": "Verificación de teléfono", "3": "Verificación de teléfono",
"4": "Restablecimiento de contraseña", "4": "Restablecimiento de contraseña",
"5": "Inicialización de acceso sin contraseña", "5": "Inicialización de acceso sin contraseña",
"6": "Secreto de App" "6": "Secreto de App",
"7": "One Time Password (OTP) - SMS",
"8": "One Time Password (OTP) - email"
}, },
"ADDGENERATOR": "Definir apariencia del secreto", "ADDGENERATOR": "Definir apariencia del secreto",
"GENERATORTYPE": "Tipo", "GENERATORTYPE": "Tipo",

View File

@ -1101,7 +1101,9 @@
"3": "Vérification par téléphone", "3": "Vérification par téléphone",
"4": "Réinitialisation du mot de passe", "4": "Réinitialisation du mot de passe",
"5": "Initialisation sans mot de passe", "5": "Initialisation sans mot de passe",
"6": "Secret de l'application" "6": "Secret de l'application",
"7": "Mot de passe à usage unique (OTP) - SMS",
"8": "Mot de passe à usage unique (OTP) - e-mail"
}, },
"ADDGENERATOR": "Définir l'apparence du secret", "ADDGENERATOR": "Définir l'apparence du secret",
"GENERATORTYPE": "Type", "GENERATORTYPE": "Type",

View File

@ -1101,7 +1101,9 @@
"3": "Verificazione del numero di telefono", "3": "Verificazione del numero di telefono",
"4": "Ripristino Password", "4": "Ripristino Password",
"5": "Inizializzazione Passwordless", "5": "Inizializzazione Passwordless",
"6": "Segreto dell'applicazione" "6": "Segreto dell'applicazione",
"7": "One Time Password (OTP) - SMS",
"8": "One Time Password (OTP) - email"
}, },
"ADDGENERATOR": "Definisci aspetto", "ADDGENERATOR": "Definisci aspetto",
"GENERATORTYPE": "Tipo", "GENERATORTYPE": "Tipo",

View File

@ -1102,7 +1102,9 @@
"3": "電話番号認証", "3": "電話番号認証",
"4": "パスワードのリセット", "4": "パスワードのリセット",
"5": "パスワードレスの初期設定", "5": "パスワードレスの初期設定",
"6": "アプリのシークレット" "6": "アプリのシークレット",
"7": "ワンタイムパスワード (OTP) - SMS",
"8": "ワンタイムパスワード (OTP) - 電子メール"
}, },
"ADDGENERATOR": "シークレットの設定を定義する", "ADDGENERATOR": "シークレットの設定を定義する",
"GENERATORTYPE": "タイプ", "GENERATORTYPE": "タイプ",

View File

@ -1102,7 +1102,9 @@
"3": "Телефонска верификација", "3": "Телефонска верификација",
"4": "Промена на лозинка", "4": "Промена на лозинка",
"5": "Иницијализација на најава без лозинка", "5": "Иницијализација на најава без лозинка",
"6": "Апликациска тајна" "6": "Апликациска тајна",
"7": "Еднократна лозинка (OTP) - СМС",
"8": "Еднократна лозинка (OTP) - е-пошта"
}, },
"ADDGENERATOR": "Дефинирајте изглед на тајна", "ADDGENERATOR": "Дефинирајте изглед на тајна",
"GENERATORTYPE": "Тип", "GENERATORTYPE": "Тип",

View File

@ -1101,7 +1101,9 @@
"3": "Weryfikacja telefonu", "3": "Weryfikacja telefonu",
"4": "Resetowanie hasła", "4": "Resetowanie hasła",
"5": "Inicjalizacja bez hasła", "5": "Inicjalizacja bez hasła",
"6": "Sekret aplikacji" "6": "Sekret aplikacji",
"7": "Hasło jednorazowe (OTP) - SMS",
"8": "Hasło jednorazowe (OTP) — e-mail"
}, },
"ADDGENERATOR": "Zdefiniuj wygląd sekretu", "ADDGENERATOR": "Zdefiniuj wygląd sekretu",
"GENERATORTYPE": "Typ", "GENERATORTYPE": "Typ",

View File

@ -1102,7 +1102,9 @@
"3": "Verificação de telefone", "3": "Verificação de telefone",
"4": "Redefinição de senha", "4": "Redefinição de senha",
"5": "Inicialização sem senha", "5": "Inicialização sem senha",
"6": "Segredo do aplicativo" "6": "Segredo do aplicativo",
"7": "Senha única (OTP) - SMS",
"8": "Senha única (OTP) - e-mail"
}, },
"ADDGENERATOR": "Definir aparência de segredo", "ADDGENERATOR": "Definir aparência de segredo",
"GENERATORTYPE": "Tipo", "GENERATORTYPE": "Tipo",

View File

@ -1101,7 +1101,9 @@
"3": "电话号码验证", "3": "电话号码验证",
"4": "重置密码", "4": "重置密码",
"5": "无密码认证初始化", "5": "无密码认证初始化",
"6": "App 验证" "6": "App 验证",
"7": "一次性密码 (OTP) - SMS",
"8": "一次性密码 (OTP) - 电子邮件"
}, },
"ADDGENERATOR": "定义验证码外观", "ADDGENERATOR": "定义验证码外观",
"GENERATORTYPE": "类型", "GENERATORTYPE": "类型",

View File

@ -279,6 +279,8 @@ The following secrets can be configured:
- Password reset code - Password reset code
- Passwordless initialization code - Passwordless initialization code
- Application secrets - Application secrets
- One Time Password (OTP) - SMS
- One Time Password (OTP) - Email
<img <img
src="/docs/img/guides/console/secretappearance.png" src="/docs/img/guides/console/secretappearance.png"

View File

@ -72,6 +72,7 @@ func SecretGeneratorsToPb(generators []*query.SecretGenerator) []*settings_pb.Se
func SecretGeneratorToPb(generator *query.SecretGenerator) *settings_pb.SecretGenerator { func SecretGeneratorToPb(generator *query.SecretGenerator) *settings_pb.SecretGenerator {
mapped := &settings_pb.SecretGenerator{ mapped := &settings_pb.SecretGenerator{
GeneratorType: SecretGeneratorTypeToPb(generator.GeneratorType),
Length: uint32(generator.Length), Length: uint32(generator.Length),
Expiry: durationpb.New(generator.Expiry), Expiry: durationpb.New(generator.Expiry),
IncludeUpperLetters: generator.IncludeUpperLetters, IncludeUpperLetters: generator.IncludeUpperLetters,
@ -97,6 +98,10 @@ func SecretGeneratorTypeToPb(generatorType domain.SecretGeneratorType) settings_
return settings_pb.SecretGeneratorType_SECRET_GENERATOR_TYPE_PASSWORDLESS_INIT_CODE return settings_pb.SecretGeneratorType_SECRET_GENERATOR_TYPE_PASSWORDLESS_INIT_CODE
case domain.SecretGeneratorTypeAppSecret: case domain.SecretGeneratorTypeAppSecret:
return settings_pb.SecretGeneratorType_SECRET_GENERATOR_TYPE_APP_SECRET return settings_pb.SecretGeneratorType_SECRET_GENERATOR_TYPE_APP_SECRET
case domain.SecretGeneratorTypeOTPSMS:
return settings_pb.SecretGeneratorType_SECRET_GENERATOR_TYPE_OTP_SMS
case domain.SecretGeneratorTypeOTPEmail:
return settings_pb.SecretGeneratorType_SECRET_GENERATOR_TYPE_OTP_EMAIL
default: default:
return settings_pb.SecretGeneratorType_SECRET_GENERATOR_TYPE_UNSPECIFIED return settings_pb.SecretGeneratorType_SECRET_GENERATOR_TYPE_UNSPECIFIED
} }
@ -116,6 +121,10 @@ func SecretGeneratorTypeToDomain(generatorType settings_pb.SecretGeneratorType)
return domain.SecretGeneratorTypePasswordlessInitCode return domain.SecretGeneratorTypePasswordlessInitCode
case settings_pb.SecretGeneratorType_SECRET_GENERATOR_TYPE_APP_SECRET: case settings_pb.SecretGeneratorType_SECRET_GENERATOR_TYPE_APP_SECRET:
return domain.SecretGeneratorTypeAppSecret return domain.SecretGeneratorTypeAppSecret
case settings_pb.SecretGeneratorType_SECRET_GENERATOR_TYPE_OTP_SMS:
return domain.SecretGeneratorTypeOTPSMS
case settings_pb.SecretGeneratorType_SECRET_GENERATOR_TYPE_OTP_EMAIL:
return domain.SecretGeneratorTypeOTPEmail
default: default:
return domain.SecretGeneratorTypeUnspecified return domain.SecretGeneratorTypeUnspecified
} }

View File

@ -48,6 +48,8 @@ type InstanceSetup struct {
PasswordVerificationCode *crypto.GeneratorConfig PasswordVerificationCode *crypto.GeneratorConfig
PasswordlessInitCode *crypto.GeneratorConfig PasswordlessInitCode *crypto.GeneratorConfig
DomainVerification *crypto.GeneratorConfig DomainVerification *crypto.GeneratorConfig
OTPSMS *crypto.GeneratorConfig
OTPEmail *crypto.GeneratorConfig
} }
PasswordComplexityPolicy struct { PasswordComplexityPolicy struct {
MinLength uint64 MinLength uint64
@ -201,6 +203,8 @@ func (c *Commands) SetUpInstance(ctx context.Context, setup *InstanceSetup) (str
prepareAddSecretGeneratorConfig(instanceAgg, domain.SecretGeneratorTypePasswordResetCode, setup.SecretGenerators.PasswordVerificationCode), prepareAddSecretGeneratorConfig(instanceAgg, domain.SecretGeneratorTypePasswordResetCode, setup.SecretGenerators.PasswordVerificationCode),
prepareAddSecretGeneratorConfig(instanceAgg, domain.SecretGeneratorTypePasswordlessInitCode, setup.SecretGenerators.PasswordlessInitCode), prepareAddSecretGeneratorConfig(instanceAgg, domain.SecretGeneratorTypePasswordlessInitCode, setup.SecretGenerators.PasswordlessInitCode),
prepareAddSecretGeneratorConfig(instanceAgg, domain.SecretGeneratorTypeVerifyDomain, setup.SecretGenerators.DomainVerification), prepareAddSecretGeneratorConfig(instanceAgg, domain.SecretGeneratorTypeVerifyDomain, setup.SecretGenerators.DomainVerification),
prepareAddSecretGeneratorConfig(instanceAgg, domain.SecretGeneratorTypeOTPSMS, setup.SecretGenerators.OTPSMS),
prepareAddSecretGeneratorConfig(instanceAgg, domain.SecretGeneratorTypeOTPEmail, setup.SecretGenerators.OTPEmail),
prepareAddDefaultPasswordComplexityPolicy( prepareAddDefaultPasswordComplexityPolicy(
instanceAgg, instanceAgg,

View File

@ -79,10 +79,26 @@ func (c *Commands) ChangeSecretGeneratorConfig(ctx context.Context, generatorTyp
if err != nil { if err != nil {
return nil, err return nil, err
} }
if generatorWriteModel.State == domain.SecretGeneratorStateUnspecified || generatorWriteModel.State == domain.SecretGeneratorStateRemoved {
return nil, errors.ThrowNotFound(nil, "COMMAND-3n9ls", "Errors.SecretGenerator.NotFound")
}
instanceAgg := InstanceAggregateFromWriteModel(&generatorWriteModel.WriteModel) instanceAgg := InstanceAggregateFromWriteModel(&generatorWriteModel.WriteModel)
if generatorWriteModel.State == domain.SecretGeneratorStateUnspecified || generatorWriteModel.State == domain.SecretGeneratorStateRemoved {
err = c.pushAppendAndReduce(ctx, generatorWriteModel,
instance.NewSecretGeneratorAddedEvent(
ctx,
instanceAgg,
generatorType,
config.Length,
config.Expiry,
config.IncludeLowerLetters,
config.IncludeUpperLetters,
config.IncludeDigits,
config.IncludeSymbols,
),
)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&generatorWriteModel.WriteModel), nil
}
changedEvent, hasChanged, err := generatorWriteModel.NewChangedEvent( changedEvent, hasChanged, err := generatorWriteModel.NewChangedEvent(
ctx, ctx,
@ -100,12 +116,7 @@ func (c *Commands) ChangeSecretGeneratorConfig(ctx context.Context, generatorTyp
if !hasChanged { if !hasChanged {
return nil, errors.ThrowPreconditionFailed(nil, "COMMAND-m0o3f", "Errors.NoChangesFound") return nil, errors.ThrowPreconditionFailed(nil, "COMMAND-m0o3f", "Errors.NoChangesFound")
} }
pushedEvents, err := c.eventstore.Push(ctx, changedEvent) if err = c.pushAppendAndReduce(ctx, generatorWriteModel, changedEvent); err != nil {
if err != nil {
return nil, err
}
err = AppendAndReduce(generatorWriteModel, pushedEvents...)
if err != nil {
return nil, err return nil, err
} }
return writeModelToObjectDetails(&generatorWriteModel.WriteModel), nil return writeModelToObjectDetails(&generatorWriteModel.WriteModel), nil

View File

@ -6,8 +6,8 @@ import (
"time" "time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/domain"
caos_errs "github.com/zitadel/zitadel/internal/errors" caos_errs "github.com/zitadel/zitadel/internal/errors"
@ -155,7 +155,7 @@ func TestCommandSide_AddSecretGenerator(t *testing.T) {
func TestCommandSide_ChangeSecretGenerator(t *testing.T) { func TestCommandSide_ChangeSecretGenerator(t *testing.T) {
type fields struct { type fields struct {
eventstore *eventstore.Eventstore eventstore func(t *testing.T) *eventstore.Eventstore
} }
type args struct { type args struct {
ctx context.Context ctx context.Context
@ -176,12 +176,10 @@ func TestCommandSide_ChangeSecretGenerator(t *testing.T) {
{ {
name: "empty generatortype, invalid error", name: "empty generatortype, invalid error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(),
t,
),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: authz.WithInstanceID(context.Background(), "INSTANCE"),
generator: &crypto.GeneratorConfig{}, generator: &crypto.GeneratorConfig{},
generatorType: domain.SecretGeneratorTypeUnspecified, generatorType: domain.SecretGeneratorTypeUnspecified,
}, },
@ -190,26 +188,53 @@ func TestCommandSide_ChangeSecretGenerator(t *testing.T) {
}, },
}, },
{ {
name: "generator not existing, not found error", name: "generator not existing, new added ok",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter(), expectFilter(),
expectPush(
[]*repository.Event{
eventFromEventPusherWithInstanceID(
"INSTANCE",
instance.NewSecretGeneratorAddedEvent(
context.Background(),
&instance.NewAggregate("INSTANCE").Aggregate,
domain.SecretGeneratorTypeInitCode,
4,
time.Hour*1,
true,
true,
true,
true,
),
),
},
uniqueConstraintsFromEventConstraintWithInstanceID("INSTANCE", instance.NewAddSecretGeneratorTypeUniqueConstraint(domain.SecretGeneratorTypeInitCode)),
),
), ),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: authz.WithInstanceID(context.Background(), "INSTANCE"),
generator: &crypto.GeneratorConfig{
Length: 4,
Expiry: 1 * time.Hour,
IncludeLowerLetters: true,
IncludeUpperLetters: true,
IncludeDigits: true,
IncludeSymbols: true,
},
generatorType: domain.SecretGeneratorTypeInitCode, generatorType: domain.SecretGeneratorTypeInitCode,
}, },
res: res{ res: res{
err: caos_errs.IsNotFound, want: &domain.ObjectDetails{
ResourceOwner: "INSTANCE",
},
}, },
}, },
{ {
name: "generator removed, not found error", name: "generator removed, new added ok",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
instance.NewSecretGeneratorAddedEvent( instance.NewSecretGeneratorAddedEvent(
@ -230,21 +255,49 @@ func TestCommandSide_ChangeSecretGenerator(t *testing.T) {
domain.SecretGeneratorTypeInitCode), domain.SecretGeneratorTypeInitCode),
), ),
), ),
expectPush(
[]*repository.Event{
eventFromEventPusherWithInstanceID(
"INSTANCE",
instance.NewSecretGeneratorAddedEvent(
context.Background(),
&instance.NewAggregate("INSTANCE").Aggregate,
domain.SecretGeneratorTypeInitCode,
4,
time.Hour*1,
true,
true,
true,
true,
),
),
},
uniqueConstraintsFromEventConstraintWithInstanceID("INSTANCE", instance.NewAddSecretGeneratorTypeUniqueConstraint(domain.SecretGeneratorTypeInitCode)),
),
), ),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: authz.WithInstanceID(context.Background(), "INSTANCE"),
generator: &crypto.GeneratorConfig{
Length: 4,
Expiry: 1 * time.Hour,
IncludeLowerLetters: true,
IncludeUpperLetters: true,
IncludeDigits: true,
IncludeSymbols: true,
},
generatorType: domain.SecretGeneratorTypeInitCode, generatorType: domain.SecretGeneratorTypeInitCode,
}, },
res: res{ res: res{
err: caos_errs.IsNotFound, want: &domain.ObjectDetails{
ResourceOwner: "INSTANCE",
},
}, },
}, },
{ {
name: "no changes, precondition error", name: "no changes, precondition error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
instance.NewSecretGeneratorAddedEvent( instance.NewSecretGeneratorAddedEvent(
@ -263,7 +316,7 @@ func TestCommandSide_ChangeSecretGenerator(t *testing.T) {
), ),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: authz.WithInstanceID(context.Background(), "INSTANCE"),
generator: &crypto.GeneratorConfig{ generator: &crypto.GeneratorConfig{
Length: 4, Length: 4,
Expiry: 1 * time.Hour, Expiry: 1 * time.Hour,
@ -281,8 +334,7 @@ func TestCommandSide_ChangeSecretGenerator(t *testing.T) {
{ {
name: "secret generator change, ok", name: "secret generator change, ok",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
instance.NewSecretGeneratorAddedEvent( instance.NewSecretGeneratorAddedEvent(
@ -300,7 +352,7 @@ func TestCommandSide_ChangeSecretGenerator(t *testing.T) {
), ),
expectPush( expectPush(
[]*repository.Event{ []*repository.Event{
eventFromEventPusher( eventFromEventPusherWithInstanceID("INSTANCE",
newSecretGeneratorChangedEvent(context.Background(), newSecretGeneratorChangedEvent(context.Background(),
domain.SecretGeneratorTypeInitCode, domain.SecretGeneratorTypeInitCode,
8, 8,
@ -308,14 +360,15 @@ func TestCommandSide_ChangeSecretGenerator(t *testing.T) {
false, false,
false, false,
false, false,
false), false,
),
), ),
}, },
), ),
), ),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: authz.WithInstanceID(context.Background(), "INSTANCE"),
generator: &crypto.GeneratorConfig{ generator: &crypto.GeneratorConfig{
Length: 8, Length: 8,
Expiry: 2 * time.Hour, Expiry: 2 * time.Hour,
@ -336,7 +389,7 @@ func TestCommandSide_ChangeSecretGenerator(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
r := &Commands{ r := &Commands{
eventstore: tt.fields.eventstore, eventstore: tt.fields.eventstore(t),
} }
got, err := r.ChangeSecretGeneratorConfig(tt.args.ctx, tt.args.generatorType, tt.args.generator) got, err := r.ChangeSecretGeneratorConfig(tt.args.ctx, tt.args.generatorType, tt.args.generator)
if tt.res.err == nil { if tt.res.err == nil {

View File

@ -11,6 +11,8 @@ const (
SecretGeneratorTypePasswordResetCode SecretGeneratorTypePasswordResetCode
SecretGeneratorTypePasswordlessInitCode SecretGeneratorTypePasswordlessInitCode
SecretGeneratorTypeAppSecret SecretGeneratorTypeAppSecret
SecretGeneratorTypeOTPSMS
SecretGeneratorTypeOTPEmail
secretGeneratorTypeCount secretGeneratorTypeCount
) )

View File

@ -48,6 +48,8 @@ enum SecretGeneratorType {
SECRET_GENERATOR_TYPE_PASSWORD_RESET_CODE = 4; SECRET_GENERATOR_TYPE_PASSWORD_RESET_CODE = 4;
SECRET_GENERATOR_TYPE_PASSWORDLESS_INIT_CODE = 5; SECRET_GENERATOR_TYPE_PASSWORDLESS_INIT_CODE = 5;
SECRET_GENERATOR_TYPE_APP_SECRET = 6; SECRET_GENERATOR_TYPE_APP_SECRET = 6;
SECRET_GENERATOR_TYPE_OTP_SMS = 7;
SECRET_GENERATOR_TYPE_OTP_EMAIL = 8;
} }
message SMTPConfig { message SMTPConfig {