feat(console): set email verified on org creation, disable svg upload, password page optimizations (#4149)

* feat: set email verified on org creation

* catch svg files and throw error

* password changes

* passwordpage

* rm log

* it

* fr

* localhost env

* Update fr.json

Co-authored-by: Fabi <38692350+hifabienne@users.noreply.github.com>
Co-authored-by: Livio Spring <livio.a@gmail.com>
This commit is contained in:
Max Peintner 2022-08-26 09:34:44 +02:00 committed by GitHub
parent e39d82c031
commit cbb5e90bac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 201 additions and 127 deletions

View File

@ -17,6 +17,15 @@ const routes: Routes = [
path: 'signedout', path: 'signedout',
loadChildren: () => import('./pages/signedout/signedout.module').then((m) => m.SignedoutModule), loadChildren: () => import('./pages/signedout/signedout.module').then((m) => m.SignedoutModule),
}, },
{
path: 'orgs/create',
component: OrgCreateComponent,
canActivate: [AuthGuard, RoleGuard],
data: {
roles: ['(org.create)?(iam.write)?'],
},
loadChildren: () => import('./pages/org-create/org-create.module').then((m) => m.OrgCreateModule),
},
{ {
path: 'orgs', path: 'orgs',
loadChildren: () => import('./pages/org-list/org-list.module').then((m) => m.OrgListModule), loadChildren: () => import('./pages/org-list/org-list.module').then((m) => m.OrgListModule),
@ -52,15 +61,6 @@ const routes: Routes = [
roles: ['iam.read', 'iam.write'], roles: ['iam.read', 'iam.write'],
}, },
}, },
{
path: 'org/create',
component: OrgCreateComponent,
canActivate: [AuthGuard, RoleGuard],
data: {
roles: ['(org.create)?(iam.write)?'],
},
loadChildren: () => import('./pages/org-create/org-create.module').then((m) => m.OrgCreateModule),
},
{ {
path: 'org', path: 'org',
loadChildren: () => import('./pages/orgs/org.module').then((m) => m.OrgModule), loadChildren: () => import('./pages/orgs/org.module').then((m) => m.OrgModule),

View File

@ -28,7 +28,7 @@
<a <a
class="nav-item" class="nav-item"
[routerLinkActiveOptions]="{ exact: true }" [routerLinkActiveOptions]="{ exact: false }"
[routerLinkActive]="['active']" [routerLinkActive]="['active']"
[routerLink]="['/orgs']" [routerLink]="['/orgs']"
> >

View File

@ -56,7 +56,7 @@
</ng-template> </ng-template>
<ng-template cnslHasRole [hasRole]="['org.create', 'iam.write']"> <ng-template cnslHasRole [hasRole]="['org.create', 'iam.write']">
<button mat-button [routerLink]="['/org/create']" (click)="closedCard.emit()"> <button mat-button [routerLink]="['/orgs/create']" (click)="closedCard.emit()">
<mat-icon class="avatar">add</mat-icon> <mat-icon class="avatar">add</mat-icon>
{{ 'MENU.NEWORG' | translate }} {{ 'MENU.NEWORG' | translate }}
</button> </button>

View File

@ -8,10 +8,10 @@
</cnsl-filter-org> </cnsl-filter-org>
<ng-template actions cnslHasRole [hasRole]="['org.create', 'iam.write']"> <ng-template actions cnslHasRole [hasRole]="['org.create', 'iam.write']">
<a [routerLink]="['/org', 'create']" color="primary" mat-raised-button class="cnsl-action-button"> <a [routerLink]="['/orgs', 'create']" color="primary" mat-raised-button class="cnsl-action-button">
<mat-icon class="icon">add</mat-icon> <mat-icon class="icon">add</mat-icon>
<span>{{ 'ACTIONS.NEW' | translate }}</span> <span>{{ 'ACTIONS.NEW' | translate }}</span>
<cnsl-action-keys (actionTriggered)="gotoRouterLink(['/org', 'create'])"> </cnsl-action-keys> <cnsl-action-keys (actionTriggered)="gotoRouterLink(['/orgs', 'create'])"> </cnsl-action-keys>
</a> </a>
</ng-template> </ng-template>

View File

@ -4,15 +4,15 @@ import { MatDialog } from '@angular/material/dialog';
import { Subject, Subscription } from 'rxjs'; import { Subject, Subscription } from 'rxjs';
import { takeUntil } from 'rxjs/operators'; import { takeUntil } from 'rxjs/operators';
import { import {
GetLabelPolicyResponse as AdminGetLabelPolicyResponse, GetLabelPolicyResponse as AdminGetLabelPolicyResponse,
GetPreviewLabelPolicyResponse as AdminGetPreviewLabelPolicyResponse, GetPreviewLabelPolicyResponse as AdminGetPreviewLabelPolicyResponse,
UpdateLabelPolicyRequest, UpdateLabelPolicyRequest,
} from 'src/app/proto/generated/zitadel/admin_pb'; } from 'src/app/proto/generated/zitadel/admin_pb';
import { import {
AddCustomLabelPolicyRequest, AddCustomLabelPolicyRequest,
GetLabelPolicyResponse as MgmtGetLabelPolicyResponse, GetLabelPolicyResponse as MgmtGetLabelPolicyResponse,
GetPreviewLabelPolicyResponse as MgmtGetPreviewLabelPolicyResponse, GetPreviewLabelPolicyResponse as MgmtGetPreviewLabelPolicyResponse,
UpdateCustomLabelPolicyRequest, UpdateCustomLabelPolicyRequest,
} from 'src/app/proto/generated/zitadel/management_pb'; } from 'src/app/proto/generated/zitadel/management_pb';
import { Org } from 'src/app/proto/generated/zitadel/org_pb'; import { Org } from 'src/app/proto/generated/zitadel/org_pb';
import { LabelPolicy } from 'src/app/proto/generated/zitadel/policy_pb'; import { LabelPolicy } from 'src/app/proto/generated/zitadel/policy_pb';
@ -112,7 +112,9 @@ export class PrivateLabelingPolicyComponent implements OnInit, OnDestroy {
const file = filelist.item(0); const file = filelist.item(0);
if (file) { if (file) {
if (file.size > MAX_ALLOWED_SIZE) { if (file.size > MAX_ALLOWED_SIZE) {
this.toast.showInfo('POLICY.PRIVATELABELING.MAXSIZEEXCEEDED', true); this.toast.showError('POLICY.PRIVATELABELING.MAXSIZEEXCEEDED', false, true);
} else if (file.type === 'image/svg+xml') {
this.toast.showError('POLICY.PRIVATELABELING.NOSVGSUPPORTED', false, true);
} else { } else {
const formData = new FormData(); const formData = new FormData();
formData.append('file', file); formData.append('file', file);

View File

@ -115,14 +115,23 @@
</mat-select> </mat-select>
</cnsl-form-field> </cnsl-form-field>
<mat-checkbox <div class="email-is-verified">
class="checkbox" <mat-checkbox class="block-checkbox" formControlName="isVerified">
[(ngModel)]="usePassword" {{ 'USER.LOGINMETHODS.EMAIL.ISVERIFIED' | translate }}
[ngModelOptions]="{ standalone: true }" </mat-checkbox>
(change)="initPwdValidators()"
> <mat-checkbox
{{ 'ORG.PAGES.USEPASSWORD' | translate }}</mat-checkbox class="block-checkbox"
> [(ngModel)]="usePassword"
[ngModelOptions]="{ standalone: true }"
(change)="initPwdValidators()"
>
{{ 'ORG.PAGES.USEPASSWORD' | translate }}</mat-checkbox
>
<cnsl-info-section class="full-width desc">
<span>{{ 'USER.CREATE.INITMAILDESCRIPTION' | translate }}</span>
</cnsl-info-section>
</div>
<ng-container *ngIf="usePassword && pwdForm"> <ng-container *ngIf="usePassword && pwdForm">
<p class="section cnsl-secondary-text">{{ 'USER.CREATE.PASSWORDSECTION' | translate }}</p> <p class="section cnsl-secondary-text">{{ 'USER.CREATE.PASSWORDSECTION' | translate }}</p>

View File

@ -58,6 +58,16 @@ h1 {
flex: 1 0 33%; flex: 1 0 33%;
margin: 0 0.5rem; margin: 0 0.5rem;
} }
.email-is-verified {
flex-basis: 100%;
margin: 1.5rem 0.5rem 0 0.5rem;
.block-checkbox {
display: block;
margin: 0.25rem 0;
}
}
} }
.pwd-form { .pwd-form {
@ -109,11 +119,6 @@ h1 {
} }
} }
.checkbox {
flex-basis: 100%;
margin: 0.5rem;
}
.complexity-view { .complexity-view {
width: 100%; width: 100%;
margin: 0 0.5rem; margin: 0 0.5rem;

View File

@ -10,6 +10,7 @@ import { SetUpOrgRequest } from 'src/app/proto/generated/zitadel/admin_pb';
import { PasswordComplexityPolicy } from 'src/app/proto/generated/zitadel/policy_pb'; import { PasswordComplexityPolicy } from 'src/app/proto/generated/zitadel/policy_pb';
import { Gender } from 'src/app/proto/generated/zitadel/user_pb'; import { Gender } from 'src/app/proto/generated/zitadel/user_pb';
import { AdminService } from 'src/app/services/admin.service'; import { AdminService } from 'src/app/services/admin.service';
import { Breadcrumb, BreadcrumbService, BreadcrumbType } from 'src/app/services/breadcrumb.service';
import { GrpcAuthService } from 'src/app/services/grpc-auth.service'; import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
import { ManagementService } from 'src/app/services/mgmt.service'; import { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.service'; import { ToastService } from 'src/app/services/toast.service';
@ -69,7 +70,16 @@ export class OrgCreateComponent {
private fb: UntypedFormBuilder, private fb: UntypedFormBuilder,
private mgmtService: ManagementService, private mgmtService: ManagementService,
private authService: GrpcAuthService, private authService: GrpcAuthService,
private breadcrumbService: BreadcrumbService,
) { ) {
const instanceBread = new Breadcrumb({
type: BreadcrumbType.INSTANCE,
name: 'Instance',
routerLink: ['/instance'],
});
breadcrumbService.setBreadcrumb([instanceBread]);
this.authService this.authService
.isAllowed(['iam.write']) .isAllowed(['iam.write'])
.pipe(take(1)) .pipe(take(1))
@ -96,7 +106,9 @@ export class OrgCreateComponent {
createOrgRequest.setDomain(this.domain?.value); createOrgRequest.setDomain(this.domain?.value);
const humanRequest: SetUpOrgRequest.Human = new SetUpOrgRequest.Human(); const humanRequest: SetUpOrgRequest.Human = new SetUpOrgRequest.Human();
humanRequest.setEmail(new SetUpOrgRequest.Human.Email().setEmail(this.email?.value)); humanRequest.setEmail(
new SetUpOrgRequest.Human.Email().setEmail(this.email?.value).setIsEmailVerified(this.isVerified?.value),
);
humanRequest.setUserName(this.userName?.value); humanRequest.setUserName(this.userName?.value);
const profile: SetUpOrgRequest.Human.Profile = new SetUpOrgRequest.Human.Profile(); const profile: SetUpOrgRequest.Human.Profile = new SetUpOrgRequest.Human.Profile();
@ -143,6 +155,7 @@ export class OrgCreateComponent {
firstName: ['', [Validators.required]], firstName: ['', [Validators.required]],
lastName: ['', [Validators.required]], lastName: ['', [Validators.required]],
email: ['', [Validators.required]], email: ['', [Validators.required]],
isVerified: [false, []],
gender: [''], gender: [''],
nickName: [''], nickName: [''],
preferredLanguage: [''], preferredLanguage: [''],
@ -243,6 +256,10 @@ export class OrgCreateComponent {
return this.userForm.get('email'); return this.userForm.get('email');
} }
public get isVerified(): AbstractControl | null {
return this.userForm.get('isVerified');
}
public get nickName(): AbstractControl | null { public get nickName(): AbstractControl | null {
return this.userForm.get('nickName'); return this.userForm.get('nickName');
} }

View File

@ -9,6 +9,7 @@ import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module'; import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
import { CreateLayoutModule } from 'src/app/modules/create-layout/create-layout.module'; import { CreateLayoutModule } from 'src/app/modules/create-layout/create-layout.module';
import { InfoSectionModule } from 'src/app/modules/info-section/info-section.module';
import { InputModule } from 'src/app/modules/input/input.module'; import { InputModule } from 'src/app/modules/input/input.module';
import { PasswordComplexityViewModule } from 'src/app/modules/password-complexity-view/password-complexity-view.module'; import { PasswordComplexityViewModule } from 'src/app/modules/password-complexity-view/password-complexity-view.module';
import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module'; import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module';
@ -23,6 +24,7 @@ import { OrgCreateComponent } from './org-create.component';
CommonModule, CommonModule,
FormsModule, FormsModule,
ReactiveFormsModule, ReactiveFormsModule,
InfoSectionModule,
InputModule, InputModule,
MatButtonModule, MatButtonModule,
MatIconModule, MatIconModule,

View File

@ -1,41 +1,45 @@
<cnsl-detail-layout [hasBackButton]="true" title="{{ 'USER.PASSWORD.TITLE' | translate }}" <cnsl-detail-layout [hasBackButton]="true" title="{{ 'USER.PASSWORD.TITLE' | translate }}">
description="{{ 'USER.PASSWORD.DESCRIPTION' | translate }}"> <p class="password-info cnsl-secondary-text" sub>{{ 'USER.PASSWORD.DESCRIPTION' | translate }}</p>
<ng-container *ngIf="userId; else authUser"> <ng-container *ngIf="userId; else authUser">
<div class="validation" *ngIf="this.policy"> <form
<cnsl-password-complexity-view [policy]="this.policy" [password]="password"> *ngIf="passwordForm"
</cnsl-password-complexity-view> autocomplete="new-password"
</div> [formGroup]="passwordForm"
(ngSubmit)="setInitialPassword(userId)"
>
<div class="password-content">
<div class="side-by-side">
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'USER.PASSWORD.NEW' | translate }}</cnsl-label>
<input cnslInput autocomplete="off" name="password" formControlName="password" type="password" />
<form *ngIf="passwordForm" autocomplete="new-password" [formGroup]="passwordForm" <span cnslError *ngIf="password?.errors?.required">
(ngSubmit)="setInitialPassword(userId)"> {{ 'USER.VALIDATION.REQUIRED' | translate }}
<div class="content center"> </span>
<cnsl-form-field class="formfield"> </cnsl-form-field>
<cnsl-label>{{ 'USER.PASSWORD.NEW' | translate }}</cnsl-label> <cnsl-form-field class="formfield">
<input cnslInput autocomplete="off" name="password" formControlName="password" type="password" /> <cnsl-label>{{ 'USER.PASSWORD.CONFIRM' | translate }}</cnsl-label>
<input cnslInput autocomplete="off" name="passwordRepeat" formControlName="confirmPassword" type="password" />
<span cnslError *ngIf="confirmPassword?.errors?.notequal">
{{ 'USER.PASSWORD.NOTEQUAL' | translate }}
</span>
</cnsl-form-field>
</div>
<span cnslError *ngIf="password?.errors?.required"> <div class="validation" *ngIf="this.policy">
{{ 'USER.VALIDATION.REQUIRED' | translate }} <cnsl-password-complexity-view [policy]="this.policy" [password]="password"> </cnsl-password-complexity-view>
</span> </div>
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'USER.PASSWORD.CONFIRM' | translate }}</cnsl-label>
<input cnslInput autocomplete="off" name="passwordRepeat" formControlName="confirmPassword" type="password" />
<span cnslError *ngIf="confirmPassword?.errors?.notequal">
{{ 'USER.PASSWORD.NOTEQUAL' | translate }}
</span>
</cnsl-form-field>
</div>
<div class="btn-container">
<button class="submit-button" [disabled]="passwordForm.invalid" mat-raised-button color="primary">{{
'USER.PASSWORD.SET' | translate }}</button>
</div> </div>
<button class="submit-button" [disabled]="passwordForm.invalid" mat-raised-button color="primary">
{{ 'USER.PASSWORD.SET' | translate }}
</button>
</form> </form>
</ng-container> </ng-container>
<ng-template #authUser> <ng-template #authUser>
<form *ngIf="passwordForm" [formGroup]="passwordForm" (ngSubmit)="setPassword()"> <form *ngIf="passwordForm" [formGroup]="passwordForm" (ngSubmit)="setPassword()">
<div class="content"> <div class="password-content">
<cnsl-form-field class="formfield"> <cnsl-form-field class="formfield">
<cnsl-label>{{ 'USER.PASSWORD.OLD' | translate }}</cnsl-label> <cnsl-label>{{ 'USER.PASSWORD.OLD' | translate }}</cnsl-label>
<input cnslInput autocomplete="off" name="password" type="password" formControlName="currentPassword" /> <input cnslInput autocomplete="off" name="password" type="password" formControlName="currentPassword" />
@ -45,29 +49,36 @@
</cnsl-form-field> </cnsl-form-field>
<div class="validation between" *ngIf="this.policy"> <div class="validation between" *ngIf="this.policy">
<cnsl-password-complexity-view [policy]="this.policy" [password]="newPassword"> <cnsl-password-complexity-view [policy]="this.policy" [password]="newPassword"> </cnsl-password-complexity-view>
</cnsl-password-complexity-view>
</div> </div>
<cnsl-form-field class="formfield"> <div class="side-by-side">
<cnsl-label>{{ 'USER.PASSWORD.NEW' | translate }}</cnsl-label> <cnsl-form-field class="formfield">
<input cnslInput autocomplete="off" name="new password" type="password" formControlName="newPassword" /> <cnsl-label>{{ 'USER.PASSWORD.NEW' | translate }}</cnsl-label>
<input cnslInput autocomplete="off" name="new password" type="password" formControlName="newPassword" />
<span cnslError *ngIf="newPassword?.errors?.required"> <span cnslError *ngIf="newPassword?.errors?.required">
{{ 'USER.VALIDATION.REQUIRED' | translate }} {{ 'USER.VALIDATION.REQUIRED' | translate }}
</span> </span>
</cnsl-form-field> </cnsl-form-field>
<cnsl-form-field class="formfield"> <cnsl-form-field class="formfield">
<cnsl-label>{{ 'USER.PASSWORD.CONFIRM' | translate }}</cnsl-label> <cnsl-label>{{ 'USER.PASSWORD.CONFIRM' | translate }}</cnsl-label>
<input cnslInput autocomplete="off" name="password repeating" type="password" <input
formControlName="confirmPassword" /> cnslInput
<span cnslError *ngIf="confirmPassword?.errors?.notequal"> autocomplete="off"
{{ 'USER.PASSWORD.NOTEQUAL' | translate }} name="password repeating"
</span> type="password"
</cnsl-form-field> formControlName="confirmPassword"
/>
<span cnslError *ngIf="confirmPassword?.errors?.notequal">
{{ 'USER.PASSWORD.NOTEQUAL' | translate }}
</span>
</cnsl-form-field>
</div>
</div> </div>
<button class="submit-button" [disabled]="passwordForm.invalid" mat-raised-button color="primary">{{ <button class="submit-button" [disabled]="passwordForm.invalid" mat-raised-button color="primary">
'USER.PASSWORD.RESET' | translate }}</button> {{ 'USER.PASSWORD.RESET' | translate }}
</button>
</form> </form>
</ng-template> </ng-template>
</cnsl-detail-layout> </cnsl-detail-layout>

View File

@ -1,17 +1,34 @@
.content { .password-info {
margin: -1rem 0 0 0;
font-size: 14px;
}
.password-content {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
flex-wrap: wrap; flex-wrap: wrap;
margin: 0 -0.5rem; max-width: 40rem;
.formfield { .side-by-side {
margin: 0 0.5rem; display: flex;
flex-wrap: wrap;
flex-direction: row;
margin: 0 -0.5rem;
width: 100%; width: 100%;
max-width: 400px;
@media only screen and (max-width: 500px) {
flex-direction: column;
}
.formfield {
flex: 1;
margin: 0 0.5rem;
}
} }
&.center { .formfield {
align-items: center; max-width: 400px;
width: 100%;
} }
} }
@ -22,7 +39,7 @@
} }
.submit-button { .submit-button {
margin-top: 100px; margin-top: 2rem;
display: block; display: block;
padding: 0.5rem 4rem; padding: 0.5rem 4rem;
} }

View File

@ -33,7 +33,7 @@ export class ToastService {
} }
} }
public showError(error: any | string, isGrpc: boolean = true): void { public showError(error: any | string, isGrpc: boolean = true, i18nKey: boolean = false): void {
if (isGrpc) { if (isGrpc) {
const { message, code, metadata } = error; const { message, code, metadata } = error;
if (code !== 16) { if (code !== 16) {
@ -44,6 +44,13 @@ export class ToastService {
this.showMessage(decodeURI(message), value, false); this.showMessage(decodeURI(message), value, false);
}); });
} }
} else if (i18nKey) {
this.translate
.get(error)
.pipe(take(1))
.subscribe((value) => {
this.showMessage(value, '', false);
});
} else { } else {
this.showMessage(error as string, '', false); this.showMessage(error as string, '', false);
} }

View File

@ -482,7 +482,7 @@
"SET": "Passwort neu setzen", "SET": "Passwort neu setzen",
"RESENDNOTIFICATION": "Email zum Zurücksetzen senden", "RESENDNOTIFICATION": "Email zum Zurücksetzen senden",
"REQUIRED": "Bitte prüfe, dass alle notwendigen Felder ausgefüllt sind.", "REQUIRED": "Bitte prüfe, dass alle notwendigen Felder ausgefüllt sind.",
"MINLENGTHERROR": "Das Passwort muss mindestens {{value}} Zeichen lang sein.", "MINLENGTHERROR": "Muss mindestens {{value}} Zeichen lang sein.",
"NOTEQUAL": "Die Passwörter stimmen nicht überein." "NOTEQUAL": "Die Passwörter stimmen nicht überein."
}, },
"ID": "ID", "ID": "ID",
@ -542,10 +542,10 @@
"REQUIRED": "Das Eingabefeld ist leer.", "REQUIRED": "Das Eingabefeld ist leer.",
"MINLENGTH": "Das Passwort muss mindestens {{requiredLength}} Zeichen lang sein.", "MINLENGTH": "Das Passwort muss mindestens {{requiredLength}} Zeichen lang sein.",
"NOEMAIL": "Benutzername darf keine E-Mail-Adresse sein.", "NOEMAIL": "Benutzername darf keine E-Mail-Adresse sein.",
"UPPERCASEMISSING": "Passwort muss einen Grossbuchstaben beinhalten.", "UPPERCASEMISSING": "Muss einen Grossbuchstaben beinhalten.",
"LOWERCASEMISSING": "Passwort muss einen Kleinbuchstaben beinhalten.", "LOWERCASEMISSING": "Muss einen Kleinbuchstaben beinhalten.",
"SYMBOLERROR": "Das Passwort muss ein Symbol/Satzzeichen beinhalten.", "SYMBOLERROR": "Muss ein Symbol/Satzzeichen beinhalten.",
"NUMBERERROR": "Das Passwort muss eine Ziffer beinhalten." "NUMBERERROR": "Muss eine Ziffer beinhalten."
}, },
"STATE": { "STATE": {
"0": "Unbekannt", "0": "Unbekannt",
@ -978,9 +978,9 @@
"PWD_COMPLEXITY": { "PWD_COMPLEXITY": {
"TITLE": "Passwortkomplexität", "TITLE": "Passwortkomplexität",
"DESCRIPTION": "Stellt sicher, dass alle festgelegten Passwörter einem bestimmten Muster entsprechen.", "DESCRIPTION": "Stellt sicher, dass alle festgelegten Passwörter einem bestimmten Muster entsprechen.",
"SYMBOLANDNUMBERERROR": "Das Passwort muss ein Symbol/Satzzeichen und eine Ziffer beinhalten.", "SYMBOLANDNUMBERERROR": "Muss ein Symbol/Satzzeichen und eine Ziffer beinhalten.",
"SYMBOLERROR": "Das Passwort muss ein Symbol/Satzzeichen beinhalten.", "SYMBOLERROR": "Muss ein Symbol/Satzzeichen beinhalten.",
"NUMBERERROR": "Das Passwort muss eine Ziffer beinhalten.", "NUMBERERROR": "Muss eine Ziffer beinhalten.",
"PATTERNERROR": "Das Passwort erfüllt nicht die vorgeschriebene Richtlinie." "PATTERNERROR": "Das Passwort erfüllt nicht die vorgeschriebene Richtlinie."
}, },
"PRIVATELABELING": { "PRIVATELABELING": {
@ -1005,6 +1005,7 @@
"MAXSIZE": "Die maximale Grösse von Uploads ist mit 524kB begrenzt", "MAXSIZE": "Die maximale Grösse von Uploads ist mit 524kB begrenzt",
"EMAILNOSVG": "Das SVG Dateiformat wird nicht in emails unterstützt. Laden Sie deshalb ihr Logo im PNG oder einem anderen unterstützten Format hoch.", "EMAILNOSVG": "Das SVG Dateiformat wird nicht in emails unterstützt. Laden Sie deshalb ihr Logo im PNG oder einem anderen unterstützten Format hoch.",
"MAXSIZEEXCEEDED": "Maximale Grösse von 524kB überschritten", "MAXSIZEEXCEEDED": "Maximale Grösse von 524kB überschritten",
"NOSVGSUPPORTED": "SVG werden nicht unterstützt!",
"FONTINLOGINONLY": "Die Schriftart wird momentan nur im Login interface angezeigt.", "FONTINLOGINONLY": "Die Schriftart wird momentan nur im Login interface angezeigt.",
"VIEWS": { "VIEWS": {
"PREVIEW": "Vorschau", "PREVIEW": "Vorschau",

View File

@ -482,7 +482,7 @@
"SET": "Set New Password", "SET": "Set New Password",
"RESENDNOTIFICATION": "Send Password Reset Link", "RESENDNOTIFICATION": "Send Password Reset Link",
"REQUIRED": "Some required fields are missing.", "REQUIRED": "Some required fields are missing.",
"MINLENGTHERROR": "The password has to be at least {{value}} characters long.", "MINLENGTHERROR": "Has to be at least {{value}} characters long.",
"NOTEQUAL": "The passwords provided do not match." "NOTEQUAL": "The passwords provided do not match."
}, },
"ID": "ID", "ID": "ID",
@ -542,10 +542,10 @@
"REQUIRED": "The input field is empty.", "REQUIRED": "The input field is empty.",
"MINLENGTH": "The password has to be at least {{requiredLength}} characters long.", "MINLENGTH": "The password has to be at least {{requiredLength}} characters long.",
"NOEMAIL": "The user name cannot be an e-mail address.", "NOEMAIL": "The user name cannot be an e-mail address.",
"UPPERCASEMISSING": "The password must include an uppercase character.", "UPPERCASEMISSING": "Must include an uppercase character.",
"LOWERCASEMISSING": "The password must include a lowercase character.", "LOWERCASEMISSING": "Must include a lowercase character.",
"SYMBOLERROR": "The password must include a symbol or punctuation mark.", "SYMBOLERROR": "Must include a symbol or punctuation mark.",
"NUMBERERROR": "The password must include a digit." "NUMBERERROR": "Must include a digit."
}, },
"STATE": { "STATE": {
"0": "Unknown", "0": "Unknown",
@ -978,9 +978,9 @@
"PWD_COMPLEXITY": { "PWD_COMPLEXITY": {
"TITLE": "Password Complexity", "TITLE": "Password Complexity",
"DESCRIPTION": "Ensures that all set passwords correspond to a specific pattern", "DESCRIPTION": "Ensures that all set passwords correspond to a specific pattern",
"SYMBOLANDNUMBERERROR": "The password must consist of a digit and a symbol/punctuation mark.", "SYMBOLANDNUMBERERROR": "Must consist of a digit and a symbol/punctuation mark.",
"SYMBOLERROR": "The password must include a symbol/punctuation mark.", "SYMBOLERROR": "Must include a symbol/punctuation mark.",
"NUMBERERROR": "The password must include a digit.", "NUMBERERROR": "Must include a digit.",
"PATTERNERROR": "The password does not meet the required pattern." "PATTERNERROR": "The password does not meet the required pattern."
}, },
"PRIVATELABELING": { "PRIVATELABELING": {
@ -1005,6 +1005,7 @@
"MAXSIZE": "The maximum size is limited to 524kB", "MAXSIZE": "The maximum size is limited to 524kB",
"EMAILNOSVG": "The SVG file format is not supported in emails. Therefore upload your logo in PNG or other supported format.", "EMAILNOSVG": "The SVG file format is not supported in emails. Therefore upload your logo in PNG or other supported format.",
"MAXSIZEEXCEEDED": "Maximum size of 524kB exceeded.", "MAXSIZEEXCEEDED": "Maximum size of 524kB exceeded.",
"NOSVGSUPPORTED": "SVG are not supported!",
"FONTINLOGINONLY": "The font is currently only displayed in the login interface.", "FONTINLOGINONLY": "The font is currently only displayed in the login interface.",
"VIEWS": { "VIEWS": {
"PREVIEW": "Preview", "PREVIEW": "Preview",

View File

@ -482,7 +482,7 @@
"SET": "Définir un nouveau mot de passe", "SET": "Définir un nouveau mot de passe",
"RESENDNOTIFICATION": "Envoyer le lien de réinitialisation du mot de passe", "RESENDNOTIFICATION": "Envoyer le lien de réinitialisation du mot de passe",
"REQUIRED": "Certains champs obligatoires sont manquants.", "REQUIRED": "Certains champs obligatoires sont manquants.",
"MINLENGTHERROR": "Le mot de passe doit comporter au moins{{value}} caractères.", "MINLENGTHERROR": "Doit comporter au moins {{value}} caractères.",
"NOTEQUAL": "Les mots de passe fournis ne correspondent pas." "NOTEQUAL": "Les mots de passe fournis ne correspondent pas."
}, },
"ID": "ID", "ID": "ID",
@ -542,10 +542,10 @@
"REQUIRED": "Le champ de saisie est vide.", "REQUIRED": "Le champ de saisie est vide.",
"MINLENGTH": "Le mot de passe doit comporter au moins{{length}} caractères.", "MINLENGTH": "Le mot de passe doit comporter au moins{{length}} caractères.",
"NOEMAIL": "Le nom d'utilisateur ne peut pas être une adresse électronique.", "NOEMAIL": "Le nom d'utilisateur ne peut pas être une adresse électronique.",
"UPPERCASEMISSING": "Le mot de passe doit comporter un caractère majuscule.", "UPPERCASEMISSING": "Doit inclure un caractère majuscule.",
"LOWERCASEMISSING": "Le mot de passe doit inclure un caractère minuscule.", "LOWERCASEMISSING": "Doit inclure un caractère minuscule.",
"SYMBOLERROR": "Le mot de passe doit inclure un symbole ou un signe de ponctuation.", "SYMBOLERROR": "Doit inclure un symbole ou un signe de ponctuation.",
"NUMBERERROR": "Le mot de passe doit contenir un chiffre." "NUMBERERROR": "Doit inclure un chiffre."
}, },
"STATE": { "STATE": {
"0": "Inconnu", "0": "Inconnu",
@ -978,9 +978,9 @@
"PWD_COMPLEXITY": { "PWD_COMPLEXITY": {
"TITLE": "Complexité des mots de passe", "TITLE": "Complexité des mots de passe",
"DESCRIPTION": "Assure que tous les mots de passe définis correspondent à un modèle spécifique.", "DESCRIPTION": "Assure que tous les mots de passe définis correspondent à un modèle spécifique.",
"SYMBOLANDNUMBERERROR": "Le mot de passe doit être composé d'un chiffre et d'un symbole/une marque de ponctuation.", "SYMBOLANDNUMBERERROR": "Doit être composé d'un chiffre et d'un symbole/signe de ponctuation.",
"SYMBOLERROR": "Le mot de passe doit comprendre un symbole ou un signe de ponctuation.", "SYMBOLERROR": "Doit inclure un symbole/un signe de ponctuation.",
"NUMBERERROR": "Le mot de passe doit comprendre un chiffre.", "NUMBERERROR": "Doit inclure un chiffre.",
"PATTERNERROR": "Le mot de passe ne correspond pas au modèle requis." "PATTERNERROR": "Le mot de passe ne correspond pas au modèle requis."
}, },
"PRIVATELABELING": { "PRIVATELABELING": {
@ -1005,6 +1005,7 @@
"MAXSIZE": "La taille maximale est limitée à 524 Ko.", "MAXSIZE": "La taille maximale est limitée à 524 Ko.",
"EMAILNOSVG": "Le format de fichier SVG n'est pas supporté dans les emails. Téléchargez donc votre logo en PNG ou dans un autre format pris en charge.", "EMAILNOSVG": "Le format de fichier SVG n'est pas supporté dans les emails. Téléchargez donc votre logo en PNG ou dans un autre format pris en charge.",
"MAXSIZEEXCEEDED": "La taille maximale de 524kB est dépassée.", "MAXSIZEEXCEEDED": "La taille maximale de 524kB est dépassée.",
"NOSVGSUPPORTED": "SVG n'est pas pris en charge",
"FONTINLOGINONLY": "La police n'est actuellement affichée que dans l'interface de connexion.", "FONTINLOGINONLY": "La police n'est actuellement affichée que dans l'interface de connexion.",
"VIEWS": { "VIEWS": {
"PREVIEW": "Aperçu", "PREVIEW": "Aperçu",

View File

@ -482,7 +482,7 @@
"SET": "Imposta nuova password", "SET": "Imposta nuova password",
"RESENDNOTIFICATION": "Invia email per la reimpostazione", "RESENDNOTIFICATION": "Invia email per la reimpostazione",
"REQUIRED": "Mancano alcuni campi obbligatori.", "REQUIRED": "Mancano alcuni campi obbligatori.",
"MINLENGTHERROR": "La password deve essere lunga almeno {{valore}} caratteri.", "MINLENGTHERROR": "Deve essere lunga almeno {{valore}} caratteri.",
"NOTEQUAL": "Le password fornite non corrispondono." "NOTEQUAL": "Le password fornite non corrispondono."
}, },
"ID": "ID", "ID": "ID",
@ -540,12 +540,12 @@
"INVALIDPATTERN": "La password non soddisfa le regole definite.", "INVALIDPATTERN": "La password non soddisfa le regole definite.",
"NOTANEMAIL": "Il valore dato non \u00e8 un indirizzo e-mail", "NOTANEMAIL": "Il valore dato non \u00e8 un indirizzo e-mail",
"REQUIRED": "Il campo di input \u00e8 vuoto.", "REQUIRED": "Il campo di input \u00e8 vuoto.",
"MINLENGTH": "La password deve essere lunga almeno {{requiredLength}} caratteri.", "MINLENGTH": "Deve essere lunga almeno {{requiredLength}} caratteri.",
"NOEMAIL": "Il nome utente non pu\u00f2 essere un indirizzo e-mail.", "NOEMAIL": "Il nome utente non pu\u00f2 essere un indirizzo e-mail.",
"UPPERCASEMISSING": "La password deve includere un carattere maiuscolo.", "UPPERCASEMISSING": "Deve includere un carattere maiuscolo.",
"LOWERCASEMISSING": "La password deve includere un carattere minuscolo.", "LOWERCASEMISSING": "Deve includere un carattere minuscolo.",
"SYMBOLERROR": "La password deve includere un simbolo o un segno di punteggiatura.", "SYMBOLERROR": "Deve includere un simbolo o un segno di punteggiatura.",
"NUMBERERROR": "La password deve includere una cifra." "NUMBERERROR": "Deve includere una cifra."
}, },
"STATE": { "STATE": {
"0": "Sconosciuto", "0": "Sconosciuto",
@ -978,9 +978,9 @@
"PWD_COMPLEXITY": { "PWD_COMPLEXITY": {
"TITLE": "Complessit\u00e0 della password", "TITLE": "Complessit\u00e0 della password",
"DESCRIPTION": "Assicura che tutte le password impostate corrispondano a un modello specifico", "DESCRIPTION": "Assicura che tutte le password impostate corrispondano a un modello specifico",
"SYMBOLANDNUMBERERROR": "La password deve essere composta da una cifra e un simbolo/segno di interpunzione.", "SYMBOLANDNUMBERERROR": "Deve essere composta da una cifra e un simbolo/segno di interpunzione.",
"SYMBOLERROR": "La password deve includere un simbolo/segno di punteggiatura.", "SYMBOLERROR": "Deve includere un simbolo/segno di punteggiatura.",
"NUMBERERROR": "La password deve includere una cifra.", "NUMBERERROR": "Deve includere una cifra.",
"PATTERNERROR": "La password non corrisponde al modello richiesto." "PATTERNERROR": "La password non corrisponde al modello richiesto."
}, },
"PRIVATELABELING": { "PRIVATELABELING": {
@ -1005,6 +1005,7 @@
"MAXSIZE": "La dimensione massima \u00e8 limitata a 524kB", "MAXSIZE": "La dimensione massima \u00e8 limitata a 524kB",
"EMAILNOSVG": "Il formato di file SVG non \u00e8 supportato nelle email. Perci\u00f2 carica il tuo logo in PNG o in un altro formato supportato.", "EMAILNOSVG": "Il formato di file SVG non \u00e8 supportato nelle email. Perci\u00f2 carica il tuo logo in PNG o in un altro formato supportato.",
"MAXSIZEEXCEEDED": "Dimensione massima di 524kB superata.", "MAXSIZEEXCEEDED": "Dimensione massima di 524kB superata.",
"NOSVGSUPPORTED": "SVG non sono supportati",
"FONTINLOGINONLY": "Il carattere \u00e8 attualmente visualizzato solo nell'interfaccia di accesso.", "FONTINLOGINONLY": "Il carattere \u00e8 attualmente visualizzato solo nell'interfaccia di accesso.",
"VIEWS": { "VIEWS": {
"PREVIEW": "Anteprima", "PREVIEW": "Anteprima",

View File

@ -13,7 +13,7 @@ When planning your applications, investing time in researching your apps archite
This guide introduces you to the grouping and structuring of ZITADEL projects which forms the base for all projects. This can be used as a quick start to the [B2B scenario](./b2b), which is merely focused on planning considerations if you are having projects with multiple organizations. This guide introduces you to the grouping and structuring of ZITADEL projects which forms the base for all projects. This can be used as a quick start to the [B2B scenario](./b2b), which is merely focused on planning considerations if you are having projects with multiple organizations.
The journey of this guide starts with creating an Organization, the outermost layer of ZITADEL within your instance, as it is the vessel for projects, roles, applications and users. The journey of this guide starts with creating an Organization, the outermost layer of ZITADEL within your instance, as it is the vessel for projects, roles, applications and users.
Creation can be done from [ZITADEL Console](https://{your_domain}.zitadel.cloud/ui/console/org/create). You can choose your current account for the organization owner or create a new one. Creation can be done from [ZITADEL Console](https://{your_domain}.zitadel.cloud/ui/console/orgs/create). You can choose your current account for the organization owner or create a new one.
Depending on your Software Development Life Cycle (SDLC) you can create multiple organizations or projects to keep your applications environments seperated. Depending on your Software Development Life Cycle (SDLC) you can create multiple organizations or projects to keep your applications environments seperated.