From 2fe76acd14906086da66c90f9267554dbed6c192 Mon Sep 17 00:00:00 2001 From: Livio Spring Date: Wed, 26 Jul 2023 13:00:41 +0200 Subject: [PATCH] feat: add secret generators for OTP (#6262) This PR adds configuration options for OTP codes through Admin API. --- cmd/defaults.yaml | 14 +++ ...dialog-add-secret-generator.component.html | 9 -- .../dialog-add-secret-generator.component.ts | 49 +++++---- .../secret-generator.component.ts | 42 ++----- console/src/assets/i18n/bg.json | 4 +- console/src/assets/i18n/de.json | 4 +- console/src/assets/i18n/en.json | 4 +- console/src/assets/i18n/es.json | 4 +- console/src/assets/i18n/fr.json | 4 +- console/src/assets/i18n/it.json | 4 +- console/src/assets/i18n/ja.json | 4 +- console/src/assets/i18n/mk.json | 4 +- console/src/assets/i18n/pl.json | 4 +- console/src/assets/i18n/pt.json | 4 +- console/src/assets/i18n/zh.json | 4 +- .../manage/console/instance-settings.mdx | 2 + .../api/grpc/admin/iam_settings_converter.go | 9 ++ internal/command/instance.go | 4 + internal/command/instance_settings.go | 29 +++-- internal/command/instance_settings_test.go | 103 +++++++++++++----- internal/domain/secret_generator.go | 2 + proto/zitadel/settings.proto | 2 + 22 files changed, 199 insertions(+), 110 deletions(-) diff --git a/cmd/defaults.yaml b/cmd/defaults.yaml index e5244cbb17..40b2fc8d07 100644 --- a/cmd/defaults.yaml +++ b/cmd/defaults.yaml @@ -564,6 +564,20 @@ DefaultInstance: IncludeUpperLetters: true IncludeDigits: true 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: MinLength: 8 HasLowercase: true diff --git a/console/src/app/modules/policies/secret-generator/dialog-add-secret-generator/dialog-add-secret-generator.component.html b/console/src/app/modules/policies/secret-generator/dialog-add-secret-generator/dialog-add-secret-generator.component.html index e4c3600926..5a09a71362 100644 --- a/console/src/app/modules/policies/secret-generator/dialog-add-secret-generator/dialog-add-secret-generator.component.html +++ b/console/src/app/modules/policies/secret-generator/dialog-add-secret-generator/dialog-add-secret-generator.component.html @@ -3,15 +3,6 @@
- - {{ 'SETTING.SECRETS.GENERATORTYPE' | translate }} - - - {{ 'SETTING.SECRETS.TYPE.' + gen | translate }} - - - -

{{ 'SETTING.SECRETS.TYPE.' + generatorType?.value | translate }}

diff --git a/console/src/app/modules/policies/secret-generator/dialog-add-secret-generator/dialog-add-secret-generator.component.ts b/console/src/app/modules/policies/secret-generator/dialog-add-secret-generator/dialog-add-secret-generator.component.ts index c8d888f914..6d72286111 100644 --- a/console/src/app/modules/policies/secret-generator/dialog-add-secret-generator/dialog-add-secret-generator.component.ts +++ b/console/src/app/modules/policies/secret-generator/dialog-add-secret-generator/dialog-add-secret-generator.component.ts @@ -7,7 +7,6 @@ import { import { Duration } from 'google-protobuf/google/protobuf/duration_pb'; import { requiredValidator } from 'src/app/modules/form-field/validators/validators'; import { UpdateSecretGeneratorRequest } from 'src/app/proto/generated/zitadel/admin_pb'; -import { SecretGeneratorType } from 'src/app/proto/generated/zitadel/settings_pb'; @Component({ 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'], }) 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 specsForm!: UntypedFormGroup; @@ -33,17 +23,19 @@ export class DialogAddSecretGeneratorComponent { public dialogRef: MatDialogRef, @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({ - generatorType: [SecretGeneratorType.SECRET_GENERATOR_TYPE_APP_SECRET, [requiredValidator]], - expiry: [1, [requiredValidator]], - includeDigits: [true, [requiredValidator]], - includeLowerLetters: [true, [requiredValidator]], - includeSymbols: [true, [requiredValidator]], - includeUpperLetters: [true, [requiredValidator]], - length: [6, [requiredValidator]], + generatorType: [data.type, [requiredValidator]], + expiry: [exp, [requiredValidator]], + length: [data.config?.length ?? 6, [requiredValidator]], + includeDigits: [data.config?.includeDigits ?? true, [requiredValidator]], + includeLowerLetters: [data.config?.includeSymbols ?? true, [requiredValidator]], + includeSymbols: [data.config?.includeLowerLetters ?? true, [requiredValidator]], + includeUpperLetters: [data.config?.includeUpperLetters ?? true, [requiredValidator]], }); - - this.generatorType?.setValue(data.type); } public closeDialog(): void { @@ -52,10 +44,7 @@ export class DialogAddSecretGeneratorComponent { public closeDialogWithRequest(): void { this.req.setGeneratorType(this.generatorType?.value); - - const expiry = new Duration().setSeconds((this.expiry?.value ?? 1) * 60 * 60); - - this.req.setExpiry(expiry); + this.req.setExpiry(this.hourToDuration(this.expiry?.value)); this.req.setIncludeDigits(this.includeDigits?.value); this.req.setIncludeLowerLetters(this.includeLowerLetters?.value); this.req.setIncludeSymbols(this.includeSymbols?.value); @@ -92,4 +81,18 @@ export class DialogAddSecretGeneratorComponent { public get length(): AbstractControl | null { 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); + } } diff --git a/console/src/app/modules/policies/secret-generator/secret-generator.component.ts b/console/src/app/modules/policies/secret-generator/secret-generator.component.ts index 1bae7b67e5..44fc85df3d 100644 --- a/console/src/app/modules/policies/secret-generator/secret-generator.component.ts +++ b/console/src/app/modules/policies/secret-generator/secret-generator.component.ts @@ -1,6 +1,6 @@ import { Component, OnInit } from '@angular/core'; 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 { AdminService } from 'src/app/services/admin.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_PASSWORDLESS_INIT_CODE, 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) {} ngOnInit(): void { @@ -48,25 +51,12 @@ export class SecretGeneratorComponent implements OnInit { }); } - private updateData(): Promise | 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 { + let config = this.generators.find((gen) => gen.generatorType === generatorType); const dialogRef = this.dialog.open(DialogAddSecretGeneratorComponent, { data: { type: generatorType, + config: config, }, width: '400px', }); @@ -77,6 +67,9 @@ export class SecretGeneratorComponent implements OnInit { .updateSecretGenerator(req) .then(() => { this.toast.showInfo('SETTING.SECRETS.UPDATED', true); + setTimeout(() => { + this.fetchData(); + }, 2000); }) .catch((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); - }); - } - } } diff --git a/console/src/assets/i18n/bg.json b/console/src/assets/i18n/bg.json index ddfc380835..b5a2abb5a0 100644 --- a/console/src/assets/i18n/bg.json +++ b/console/src/assets/i18n/bg.json @@ -1095,7 +1095,9 @@ "3": "Телефонна проверка", "4": "Нулиране на парола", "5": "Инициализация без парола", - "6": "Тайна на приложението" + "6": "Тайна на приложението", + "7": "Еднократна парола (OTP) - SMS", + "8": "Еднократна парола (OTP) – имейл" }, "ADDGENERATOR": "Определете тайния външен вид", "GENERATORTYPE": "Тип", diff --git a/console/src/assets/i18n/de.json b/console/src/assets/i18n/de.json index 87b6e83fd0..01c8fb03bd 100644 --- a/console/src/assets/i18n/de.json +++ b/console/src/assets/i18n/de.json @@ -1101,7 +1101,9 @@ "3": "Telefonnummer Verificationscode", "4": "Passwort Zurücksetzen Code", "5": "Passwordless Initialisierungscode", - "6": "Applicationssecret" + "6": "Applicationssecret", + "7": "One Time Password (OTP) - SMS", + "8": "One Time Password (OTP) - Email" }, "ADDGENERATOR": "Secret Erscheinungsbild definieren", "GENERATORTYPE": "Typ", diff --git a/console/src/assets/i18n/en.json b/console/src/assets/i18n/en.json index c858d5acbb..e4c8d5dd2d 100644 --- a/console/src/assets/i18n/en.json +++ b/console/src/assets/i18n/en.json @@ -1102,7 +1102,9 @@ "3": "Phone verification", "4": "Password Reset", "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", "GENERATORTYPE": "Type", diff --git a/console/src/assets/i18n/es.json b/console/src/assets/i18n/es.json index 3a0df8980f..bc7af48408 100644 --- a/console/src/assets/i18n/es.json +++ b/console/src/assets/i18n/es.json @@ -1102,7 +1102,9 @@ "3": "Verificación de teléfono", "4": "Restablecimiento de 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", "GENERATORTYPE": "Tipo", diff --git a/console/src/assets/i18n/fr.json b/console/src/assets/i18n/fr.json index a81c6eae5d..61f2f7f780 100644 --- a/console/src/assets/i18n/fr.json +++ b/console/src/assets/i18n/fr.json @@ -1101,7 +1101,9 @@ "3": "Vérification par téléphone", "4": "Réinitialisation du 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", "GENERATORTYPE": "Type", diff --git a/console/src/assets/i18n/it.json b/console/src/assets/i18n/it.json index 387bc187d7..ed6e2fd811 100644 --- a/console/src/assets/i18n/it.json +++ b/console/src/assets/i18n/it.json @@ -1101,7 +1101,9 @@ "3": "Verificazione del numero di telefono", "4": "Ripristino Password", "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", "GENERATORTYPE": "Tipo", diff --git a/console/src/assets/i18n/ja.json b/console/src/assets/i18n/ja.json index 8ac4692d38..bdba41562b 100644 --- a/console/src/assets/i18n/ja.json +++ b/console/src/assets/i18n/ja.json @@ -1102,7 +1102,9 @@ "3": "電話番号認証", "4": "パスワードのリセット", "5": "パスワードレスの初期設定", - "6": "アプリのシークレット" + "6": "アプリのシークレット", + "7": "ワンタイムパスワード (OTP) - SMS", + "8": "ワンタイムパスワード (OTP) - 電子メール" }, "ADDGENERATOR": "シークレットの設定を定義する", "GENERATORTYPE": "タイプ", diff --git a/console/src/assets/i18n/mk.json b/console/src/assets/i18n/mk.json index b511a8ced9..87d2e8a6ef 100644 --- a/console/src/assets/i18n/mk.json +++ b/console/src/assets/i18n/mk.json @@ -1102,7 +1102,9 @@ "3": "Телефонска верификација", "4": "Промена на лозинка", "5": "Иницијализација на најава без лозинка", - "6": "Апликациска тајна" + "6": "Апликациска тајна", + "7": "Еднократна лозинка (OTP) - СМС", + "8": "Еднократна лозинка (OTP) - е-пошта" }, "ADDGENERATOR": "Дефинирајте изглед на тајна", "GENERATORTYPE": "Тип", diff --git a/console/src/assets/i18n/pl.json b/console/src/assets/i18n/pl.json index 774038f7ce..cd79105421 100644 --- a/console/src/assets/i18n/pl.json +++ b/console/src/assets/i18n/pl.json @@ -1101,7 +1101,9 @@ "3": "Weryfikacja telefonu", "4": "Resetowanie 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", "GENERATORTYPE": "Typ", diff --git a/console/src/assets/i18n/pt.json b/console/src/assets/i18n/pt.json index 00607c1596..21e5d79359 100644 --- a/console/src/assets/i18n/pt.json +++ b/console/src/assets/i18n/pt.json @@ -1102,7 +1102,9 @@ "3": "Verificação de telefone", "4": "Redefinição de 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", "GENERATORTYPE": "Tipo", diff --git a/console/src/assets/i18n/zh.json b/console/src/assets/i18n/zh.json index 423fbf027a..5247d16913 100644 --- a/console/src/assets/i18n/zh.json +++ b/console/src/assets/i18n/zh.json @@ -1101,7 +1101,9 @@ "3": "电话号码验证", "4": "重置密码", "5": "无密码认证初始化", - "6": "App 验证" + "6": "App 验证", + "7": "一次性密码 (OTP) - SMS", + "8": "一次性密码 (OTP) - 电子邮件" }, "ADDGENERATOR": "定义验证码外观", "GENERATORTYPE": "类型", diff --git a/docs/docs/guides/manage/console/instance-settings.mdx b/docs/docs/guides/manage/console/instance-settings.mdx index 2603caee52..913661c33d 100644 --- a/docs/docs/guides/manage/console/instance-settings.mdx +++ b/docs/docs/guides/manage/console/instance-settings.mdx @@ -279,6 +279,8 @@ The following secrets can be configured: - Password reset code - Passwordless initialization code - Application secrets +- One Time Password (OTP) - SMS +- One Time Password (OTP) - Email