mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-12 13:48:23 +00:00
Merge branch 'main' into next-merge
This commit is contained in:
@@ -105,13 +105,21 @@ func (mig *FirstInstance) Execute(ctx context.Context) error {
|
|||||||
// check if username is email style or else append @<orgname>.<custom-domain>
|
// check if username is email style or else append @<orgname>.<custom-domain>
|
||||||
//this way we have the same value as before changing `UserLoginMustBeDomain` to false
|
//this way we have the same value as before changing `UserLoginMustBeDomain` to false
|
||||||
if !mig.instanceSetup.DomainPolicy.UserLoginMustBeDomain && !strings.Contains(mig.instanceSetup.Org.Human.Username, "@") {
|
if !mig.instanceSetup.DomainPolicy.UserLoginMustBeDomain && !strings.Contains(mig.instanceSetup.Org.Human.Username, "@") {
|
||||||
mig.instanceSetup.Org.Human.Username = mig.instanceSetup.Org.Human.Username + "@" + domain.NewIAMDomainName(mig.instanceSetup.Org.Name, mig.instanceSetup.CustomDomain)
|
orgDomain, err := domain.NewIAMDomainName(mig.instanceSetup.Org.Name, mig.instanceSetup.CustomDomain)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
mig.instanceSetup.Org.Human.Username = mig.instanceSetup.Org.Human.Username + "@" + orgDomain
|
||||||
}
|
}
|
||||||
mig.instanceSetup.Org.Human.Email.Address = mig.instanceSetup.Org.Human.Email.Address.Normalize()
|
mig.instanceSetup.Org.Human.Email.Address = mig.instanceSetup.Org.Human.Email.Address.Normalize()
|
||||||
if mig.instanceSetup.Org.Human.Email.Address == "" {
|
if mig.instanceSetup.Org.Human.Email.Address == "" {
|
||||||
mig.instanceSetup.Org.Human.Email.Address = domain.EmailAddress(mig.instanceSetup.Org.Human.Username)
|
mig.instanceSetup.Org.Human.Email.Address = domain.EmailAddress(mig.instanceSetup.Org.Human.Username)
|
||||||
if !strings.Contains(string(mig.instanceSetup.Org.Human.Email.Address), "@") {
|
if !strings.Contains(string(mig.instanceSetup.Org.Human.Email.Address), "@") {
|
||||||
mig.instanceSetup.Org.Human.Email.Address = domain.EmailAddress(mig.instanceSetup.Org.Human.Username + "@" + domain.NewIAMDomainName(mig.instanceSetup.Org.Name, mig.instanceSetup.CustomDomain))
|
orgDomain, err := domain.NewIAMDomainName(mig.instanceSetup.Org.Name, mig.instanceSetup.CustomDomain)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
mig.instanceSetup.Org.Human.Email.Address = domain.EmailAddress(mig.instanceSetup.Org.Human.Username + "@" + orgDomain)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
<div class="main-container">
|
<div class="main-container">
|
||||||
<ng-container *ngIf="(authService.user | async) || {} as user">
|
<ng-container *ngIf="(authService.userSubject | async) || {} as user">
|
||||||
<cnsl-header
|
<cnsl-header
|
||||||
*ngIf="user && user !== {}"
|
*ngIf="user && user !== {}"
|
||||||
[org]="org"
|
[org]="org"
|
||||||
|
@@ -225,9 +225,11 @@ export class AppComponent implements OnDestroy {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.isDarkTheme = this.themeService.isDarkTheme;
|
this.isDarkTheme = this.themeService.isDarkTheme;
|
||||||
this.isDarkTheme.subscribe((dark) => this.onSetTheme(dark ? 'dark-theme' : 'light-theme'));
|
this.isDarkTheme
|
||||||
|
.pipe(takeUntil(this.destroy$))
|
||||||
|
.subscribe((dark) => this.onSetTheme(dark ? 'dark-theme' : 'light-theme'));
|
||||||
|
|
||||||
this.translate.onLangChange.subscribe((language: LangChangeEvent) => {
|
this.translate.onLangChange.pipe(takeUntil(this.destroy$)).subscribe((language: LangChangeEvent) => {
|
||||||
this.document.documentElement.lang = language.lang;
|
this.document.documentElement.lang = language.lang;
|
||||||
this.language = language.lang;
|
this.language = language.lang;
|
||||||
});
|
});
|
||||||
@@ -271,7 +273,7 @@ export class AppComponent implements OnDestroy {
|
|||||||
this.translate.addLangs(supportedLanguages);
|
this.translate.addLangs(supportedLanguages);
|
||||||
this.translate.setDefaultLang(fallbackLanguage);
|
this.translate.setDefaultLang(fallbackLanguage);
|
||||||
|
|
||||||
this.authService.user.subscribe((userprofile) => {
|
this.authService.userSubject.pipe(takeUntil(this.destroy$)).subscribe((userprofile) => {
|
||||||
if (userprofile) {
|
if (userprofile) {
|
||||||
const cropped = navigator.language.split('-')[0] ?? fallbackLanguage;
|
const cropped = navigator.language.split('-')[0] ?? fallbackLanguage;
|
||||||
const fallbackLang = cropped.match(supportedLanguagesRegexp) ? cropped : fallbackLanguage;
|
const fallbackLang = cropped.match(supportedLanguagesRegexp) ? cropped : fallbackLanguage;
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router';
|
import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router';
|
||||||
import { Observable } from 'rxjs';
|
import { map, Observable, take } from 'rxjs';
|
||||||
import { map, tap } from 'rxjs/operators';
|
|
||||||
|
|
||||||
import { GrpcAuthService } from '../services/grpc-auth.service';
|
import { GrpcAuthService } from '../services/grpc-auth.service';
|
||||||
|
|
||||||
@@ -19,11 +18,13 @@ export class UserGuard {
|
|||||||
state: RouterStateSnapshot,
|
state: RouterStateSnapshot,
|
||||||
): Observable<boolean> | Promise<boolean> | boolean {
|
): Observable<boolean> | Promise<boolean> | boolean {
|
||||||
return this.authService.user.pipe(
|
return this.authService.user.pipe(
|
||||||
map((user) => user?.id !== route.params['id']),
|
take(1),
|
||||||
tap((isNotMe) => {
|
map((user) => {
|
||||||
if (!isNotMe) {
|
const isMe = user?.id === route.params['id'];
|
||||||
|
if (isMe) {
|
||||||
this.router.navigate(['/users', 'me']);
|
this.router.navigate(['/users', 'me']);
|
||||||
}
|
}
|
||||||
|
return !isMe;
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
title="{{ 'APP.PAGES.CREATE' | translate }}"
|
title="{{ 'APP.PAGES.CREATE' | translate }}"
|
||||||
class="app-create-wrapper"
|
class="app-create-wrapper"
|
||||||
[createSteps]="
|
[createSteps]="
|
||||||
!devmode
|
!pro
|
||||||
? appType?.value?.createType === AppCreateType.OIDC
|
? appType?.value?.createType === AppCreateType.OIDC
|
||||||
? appType?.value.oidcAppType !== OIDCAppType.OIDC_APP_TYPE_NATIVE
|
? appType?.value.oidcAppType !== OIDCAppType.OIDC_APP_TYPE_NATIVE
|
||||||
? 4
|
? 4
|
||||||
@@ -20,13 +20,13 @@
|
|||||||
<h1>{{ 'APP.PAGES.CREATE_DESC_TITLE' | translate }}</h1>
|
<h1>{{ 'APP.PAGES.CREATE_DESC_TITLE' | translate }}</h1>
|
||||||
<mat-progress-bar class="progress-bar" color="primary" *ngIf="loading" mode="indeterminate"></mat-progress-bar>
|
<mat-progress-bar class="progress-bar" color="primary" *ngIf="loading" mode="indeterminate"></mat-progress-bar>
|
||||||
|
|
||||||
<mat-checkbox class="proswitch" color="primary" [(ngModel)]="devmode">
|
<mat-checkbox class="proswitch" color="primary" [(ngModel)]="pro">
|
||||||
{{ 'APP.PROSWITCH' | translate }}
|
{{ 'APP.PROSWITCH' | translate }}
|
||||||
</mat-checkbox>
|
</mat-checkbox>
|
||||||
|
|
||||||
<mat-horizontal-stepper
|
<mat-horizontal-stepper
|
||||||
class="stepper {{ 'last-edited-step-' + stepper.selectedIndex }}"
|
class="stepper {{ 'last-edited-step-' + stepper.selectedIndex }}"
|
||||||
*ngIf="!devmode"
|
*ngIf="!pro"
|
||||||
linear
|
linear
|
||||||
#stepper
|
#stepper
|
||||||
labelPosition="bottom"
|
labelPosition="bottom"
|
||||||
@@ -109,6 +109,15 @@
|
|||||||
{{ 'APP.OIDC.REDIRECTDESCRIPTIONWEB' | translate }}
|
{{ 'APP.OIDC.REDIRECTDESCRIPTIONWEB' | translate }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<mat-slide-toggle
|
||||||
|
color="primary"
|
||||||
|
[(ngModel)]="devMode"
|
||||||
|
[ngModelOptions]="{ standalone: true }"
|
||||||
|
matTooltip="{{ 'APP.OIDC.DEVMODEDESC' | translate }}"
|
||||||
|
>
|
||||||
|
{{ 'APP.OIDC.DEVMODE' | translate }}
|
||||||
|
</mat-slide-toggle>
|
||||||
|
|
||||||
<cnsl-redirect-uris
|
<cnsl-redirect-uris
|
||||||
class="redirect-section"
|
class="redirect-section"
|
||||||
[disabled]="false"
|
[disabled]="false"
|
||||||
@@ -116,6 +125,7 @@
|
|||||||
[(ngModel)]="redirectUris"
|
[(ngModel)]="redirectUris"
|
||||||
[getValues]="requestRedirectValuesSubject$"
|
[getValues]="requestRedirectValuesSubject$"
|
||||||
title="{{ 'APP.OIDC.REDIRECT' | translate }}"
|
title="{{ 'APP.OIDC.REDIRECT' | translate }}"
|
||||||
|
[devMode]="devMode"
|
||||||
data-e2e="redirect-uris"
|
data-e2e="redirect-uris"
|
||||||
>
|
>
|
||||||
</cnsl-redirect-uris>
|
</cnsl-redirect-uris>
|
||||||
@@ -144,6 +154,7 @@
|
|||||||
[getValues]="requestRedirectValuesSubject$"
|
[getValues]="requestRedirectValuesSubject$"
|
||||||
title="{{ 'APP.OIDC.POSTLOGOUTREDIRECT' | translate }}"
|
title="{{ 'APP.OIDC.POSTLOGOUTREDIRECT' | translate }}"
|
||||||
[isNative]="appType?.value.oidcAppType === OIDCAppType.OIDC_APP_TYPE_NATIVE"
|
[isNative]="appType?.value.oidcAppType === OIDCAppType.OIDC_APP_TYPE_NATIVE"
|
||||||
|
[devMode]="devMode"
|
||||||
data-e2e="postlogout-uris"
|
data-e2e="postlogout-uris"
|
||||||
>
|
>
|
||||||
</cnsl-redirect-uris>
|
</cnsl-redirect-uris>
|
||||||
@@ -299,6 +310,19 @@
|
|||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container *ngIf="appType?.value?.createType === AppCreateType.OIDC">
|
||||||
|
<div class="row">
|
||||||
|
<span class="left cnsl-secondary-text">
|
||||||
|
{{ 'APP.OIDC.DEVMODE' | translate }}
|
||||||
|
</span>
|
||||||
|
<span class="right">
|
||||||
|
<span>
|
||||||
|
{{ devMode ? ('APP.OIDC.DEVMODE_ENABLED' | translate) : ('APP.OIDC.DEVMODE_DISABLED' | translate) }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
<ng-container *ngIf="appType?.value?.createType === AppCreateType.API">
|
<ng-container *ngIf="appType?.value?.createType === AppCreateType.API">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<span class="left cnsl-secondary-text">
|
<span class="left cnsl-secondary-text">
|
||||||
@@ -342,7 +366,7 @@
|
|||||||
</ng-template>
|
</ng-template>
|
||||||
</mat-horizontal-stepper>
|
</mat-horizontal-stepper>
|
||||||
|
|
||||||
<div *ngIf="devmode" class="dev">
|
<div *ngIf="pro" class="dev">
|
||||||
<form [formGroup]="form" (ngSubmit)="createApp()" data-e2e="create-app-wizzard-3">
|
<form [formGroup]="form" (ngSubmit)="createApp()" data-e2e="create-app-wizzard-3">
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<cnsl-form-field class="formfield">
|
<cnsl-form-field class="formfield">
|
||||||
@@ -439,6 +463,14 @@
|
|||||||
"
|
"
|
||||||
>
|
>
|
||||||
<div class="formfield full-width">
|
<div class="formfield full-width">
|
||||||
|
<mat-slide-toggle
|
||||||
|
color="primary"
|
||||||
|
[(ngModel)]="devMode"
|
||||||
|
[ngModelOptions]="{ standalone: true }"
|
||||||
|
matTooltip="{{ 'APP.OIDC.DEVMODEDESC' | translate }}"
|
||||||
|
>
|
||||||
|
{{ 'APP.OIDC.DEVMODE' | translate }}
|
||||||
|
</mat-slide-toggle>
|
||||||
<cnsl-redirect-uris
|
<cnsl-redirect-uris
|
||||||
class="redirect-section"
|
class="redirect-section"
|
||||||
[disabled]="false"
|
[disabled]="false"
|
||||||
|
@@ -28,6 +28,7 @@ import { Breadcrumb, BreadcrumbService, BreadcrumbType } from 'src/app/services/
|
|||||||
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';
|
||||||
|
|
||||||
|
import { MatLegacySlideToggleChange } from '@angular/material/legacy-slide-toggle';
|
||||||
import { AppSecretDialogComponent } from '../app-secret-dialog/app-secret-dialog.component';
|
import { AppSecretDialogComponent } from '../app-secret-dialog/app-secret-dialog.component';
|
||||||
import {
|
import {
|
||||||
BASIC_AUTH_METHOD,
|
BASIC_AUTH_METHOD,
|
||||||
@@ -51,7 +52,7 @@ const MAX_ALLOWED_SIZE = 1 * 1024 * 1024;
|
|||||||
export class AppCreateComponent implements OnInit, OnDestroy {
|
export class AppCreateComponent implements OnInit, OnDestroy {
|
||||||
private subscription: Subscription = new Subscription();
|
private subscription: Subscription = new Subscription();
|
||||||
private destroyed$: Subject<void> = new Subject();
|
private destroyed$: Subject<void> = new Subject();
|
||||||
public devmode: boolean = false;
|
public pro: boolean = false;
|
||||||
public projectId: string = '';
|
public projectId: string = '';
|
||||||
public loading: boolean = false;
|
public loading: boolean = false;
|
||||||
|
|
||||||
@@ -239,7 +240,16 @@ export class AppCreateComponent implements OnInit, OnDestroy {
|
|||||||
this.oidcAppRequest.setPostLogoutRedirectUrisList(value);
|
this.oidcAppRequest.setPostLogoutRedirectUrisList(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get devMode() {
|
||||||
|
return this.oidcAppRequest.toObject().devMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public set devMode(value: boolean) {
|
||||||
|
this.oidcAppRequest.setDevMode(value);
|
||||||
|
}
|
||||||
|
|
||||||
public ngOnInit(): void {
|
public ngOnInit(): void {
|
||||||
|
this.devMode = false;
|
||||||
this.subscription = this.route.params.subscribe((params) => this.getData(params));
|
this.subscription = this.route.params.subscribe((params) => this.getData(params));
|
||||||
|
|
||||||
const projectId = this.route.snapshot.paramMap.get('projectid');
|
const projectId = this.route.snapshot.paramMap.get('projectid');
|
||||||
@@ -362,9 +372,9 @@ export class AppCreateComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public createApp(): void {
|
public createApp(): void {
|
||||||
const appOIDCCheck = this.devmode ? this.isDevOIDC : this.isStepperOIDC;
|
const appOIDCCheck = this.pro ? this.isDevOIDC : this.isStepperOIDC;
|
||||||
const appAPICheck = this.devmode ? this.isDevAPI : this.isStepperAPI;
|
const appAPICheck = this.pro ? this.isDevAPI : this.isStepperAPI;
|
||||||
const appSAMLCheck = this.devmode ? this.isDevSAML : this.isStepperSAML;
|
const appSAMLCheck = this.pro ? this.isDevSAML : this.isStepperSAML;
|
||||||
|
|
||||||
if (appOIDCCheck) {
|
if (appOIDCCheck) {
|
||||||
this.requestRedirectValuesSubject$.next();
|
this.requestRedirectValuesSubject$.next();
|
||||||
|
@@ -427,7 +427,7 @@
|
|||||||
|
|
||||||
<div class="app-info-row">
|
<div class="app-info-row">
|
||||||
<div class="app-info-wrapper" *ngFor="let wellKnownV of wellKnownMap$ | async | keyvalue">
|
<div class="app-info-wrapper" *ngFor="let wellKnownV of wellKnownMap$ | async | keyvalue">
|
||||||
<ng-container *ngIf="wellKnownV.key.endsWith('endpoint')">
|
<ng-container *ngIf="wellKnownV.key.endsWith('endpoint') || wellKnownV.key.toString() === 'jwks_uri'">
|
||||||
<p class="app-info-row-title cnsl-secondary-text">{{ wellKnownV.key }}</p>
|
<p class="app-info-row-title cnsl-secondary-text">{{ wellKnownV.key }}</p>
|
||||||
<div class="app-copy-row">
|
<div class="app-copy-row">
|
||||||
<div *ngIf="wellKnownV.value" class="environment">
|
<div *ngIf="wellKnownV.value" class="environment">
|
||||||
|
@@ -3,6 +3,7 @@ import { AuthConfig, OAuthService } from 'angular-oauth2-oidc';
|
|||||||
import { BehaviorSubject, from, lastValueFrom, Observable } from 'rxjs';
|
import { BehaviorSubject, from, lastValueFrom, Observable } from 'rxjs';
|
||||||
|
|
||||||
import { StatehandlerService } from './statehandler/statehandler.service';
|
import { StatehandlerService } from './statehandler/statehandler.service';
|
||||||
|
import { ToastService } from './toast.service';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
@@ -15,6 +16,7 @@ export class AuthenticationService {
|
|||||||
constructor(
|
constructor(
|
||||||
private oauthService: OAuthService,
|
private oauthService: OAuthService,
|
||||||
private statehandler: StatehandlerService,
|
private statehandler: StatehandlerService,
|
||||||
|
private toast: ToastService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public initConfig(data: AuthConfig): void {
|
public initConfig(data: AuthConfig): void {
|
||||||
@@ -39,7 +41,10 @@ export class AuthenticationService {
|
|||||||
}
|
}
|
||||||
this.oauthService.configure(this.authConfig);
|
this.oauthService.configure(this.authConfig);
|
||||||
this.oauthService.strictDiscoveryDocumentValidation = false;
|
this.oauthService.strictDiscoveryDocumentValidation = false;
|
||||||
await this.oauthService.loadDiscoveryDocumentAndTryLogin();
|
await this.oauthService.loadDiscoveryDocumentAndTryLogin().catch((error) => {
|
||||||
|
this.toast.showError(error, false, false);
|
||||||
|
});
|
||||||
|
|
||||||
this._authenticated = this.oauthService.hasValidAccessToken();
|
this._authenticated = this.oauthService.hasValidAccessToken();
|
||||||
if (!this.oauthService.hasValidIdToken() || !this.authenticated || partialConfig || force) {
|
if (!this.oauthService.hasValidIdToken() || !this.authenticated || partialConfig || force) {
|
||||||
const newState = await lastValueFrom(this.statehandler.createState());
|
const newState = await lastValueFrom(this.statehandler.createState());
|
||||||
|
@@ -1,19 +1,8 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { SortDirection } from '@angular/material/sort';
|
import { SortDirection } from '@angular/material/sort';
|
||||||
import { OAuthService } from 'angular-oauth2-oidc';
|
import { OAuthService } from 'angular-oauth2-oidc';
|
||||||
import { BehaviorSubject, from, merge, Observable, of, Subject } from 'rxjs';
|
import { BehaviorSubject, forkJoin, from, Observable, of, Subject } from 'rxjs';
|
||||||
import {
|
import { catchError, distinctUntilChanged, filter, finalize, map, switchMap, timeout, withLatestFrom } from 'rxjs/operators';
|
||||||
catchError,
|
|
||||||
distinctUntilChanged,
|
|
||||||
filter,
|
|
||||||
finalize,
|
|
||||||
map,
|
|
||||||
mergeMap,
|
|
||||||
switchMap,
|
|
||||||
take,
|
|
||||||
timeout,
|
|
||||||
withLatestFrom,
|
|
||||||
} from 'rxjs/operators';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AddMyAuthFactorOTPEmailRequest,
|
AddMyAuthFactorOTPEmailRequest,
|
||||||
@@ -184,25 +173,21 @@ export class GrpcAuthService {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
this.user = merge(
|
this.user = forkJoin([
|
||||||
of(this.oauthService.getAccessToken()).pipe(filter((token) => (token ? true : false))),
|
of(this.oauthService.getAccessToken()),
|
||||||
this.oauthService.events.pipe(
|
this.oauthService.events.pipe(
|
||||||
filter((e) => e.type === 'token_received'),
|
filter((e) => e.type === 'token_received'),
|
||||||
timeout(this.oauthService.waitForTokenInMsec || 0),
|
timeout(this.oauthService.waitForTokenInMsec || 0),
|
||||||
catchError((_) => of(null)), // timeout is not an error
|
catchError((_) => of(null)), // timeout is not an error
|
||||||
map((_) => this.oauthService.getAccessToken()),
|
map((_) => this.oauthService.getAccessToken()),
|
||||||
),
|
),
|
||||||
).pipe(
|
]).pipe(
|
||||||
take(1),
|
filter((token) => (token ? true : false)),
|
||||||
mergeMap(() => {
|
distinctUntilChanged(),
|
||||||
|
switchMap(() => {
|
||||||
return from(
|
return from(
|
||||||
this.getMyUser().then((resp) => {
|
this.getMyUser().then((resp) => {
|
||||||
const user = resp.user;
|
return resp.user;
|
||||||
if (user) {
|
|
||||||
return user;
|
|
||||||
} else {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
|
@@ -1977,6 +1977,8 @@
|
|||||||
"CLIENTSECRET_DESCRIPTION": "Пазете клиентската си тайна на сигурно място, тъй като тя ще изчезне, след като диалоговият прозорец бъде затворен.",
|
"CLIENTSECRET_DESCRIPTION": "Пазете клиентската си тайна на сигурно място, тъй като тя ще изчезне, след като диалоговият прозорец бъде затворен.",
|
||||||
"REGENERATESECRET": "Повторно генериране на клиентска тайна",
|
"REGENERATESECRET": "Повторно генериране на клиентска тайна",
|
||||||
"DEVMODE": "Режим на разработка",
|
"DEVMODE": "Режим на разработка",
|
||||||
|
"DEVMODE_ENABLED": "Активиран",
|
||||||
|
"DEVMODE_DISABLED": "Деактивиран",
|
||||||
"DEVMODEDESC": "Внимание: При активиран режим на разработка URI адресите за пренасочване няма да бъдат валидирани.",
|
"DEVMODEDESC": "Внимание: При активиран режим на разработка URI адресите за пренасочване няма да бъдат валидирани.",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE": "Пропуснете страницата за успешно влизане",
|
"SKIPNATIVEAPPSUCCESSPAGE": "Пропуснете страницата за успешно влизане",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Пропуснете страницата за успех след влизане в това родно приложение.",
|
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Пропуснете страницата за успех след влизане в това родно приложение.",
|
||||||
|
@@ -1986,6 +1986,8 @@
|
|||||||
"CLIENTSECRET_DESCRIPTION": "Verwahre das Client Secret an einem sicheren Ort, da es nicht mehr angezeigt werden kann, sobald der Dialog geschlossen wird.",
|
"CLIENTSECRET_DESCRIPTION": "Verwahre das Client Secret an einem sicheren Ort, da es nicht mehr angezeigt werden kann, sobald der Dialog geschlossen wird.",
|
||||||
"REGENERATESECRET": "Client Secret neu generieren",
|
"REGENERATESECRET": "Client Secret neu generieren",
|
||||||
"DEVMODE": "Entwicklermodus",
|
"DEVMODE": "Entwicklermodus",
|
||||||
|
"DEVMODE_ENABLED": "Aktiviert",
|
||||||
|
"DEVMODE_DISABLED": "Deaktiviert",
|
||||||
"DEVMODEDESC": "Bei eingeschaltetem Entwicklermodus werden die Weiterleitungs-URIs im OIDC-Flow nicht validiert.",
|
"DEVMODEDESC": "Bei eingeschaltetem Entwicklermodus werden die Weiterleitungs-URIs im OIDC-Flow nicht validiert.",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE": "Login Erfolgseite überspringen",
|
"SKIPNATIVEAPPSUCCESSPAGE": "Login Erfolgseite überspringen",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Erfolgseite nach dem Login für diese Native Applikation überspringen.",
|
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Erfolgseite nach dem Login für diese Native Applikation überspringen.",
|
||||||
|
@@ -1995,6 +1995,8 @@
|
|||||||
"CLIENTSECRET_DESCRIPTION": "Keep your client secret at a safe place as it will disappear once the dialog is closed.",
|
"CLIENTSECRET_DESCRIPTION": "Keep your client secret at a safe place as it will disappear once the dialog is closed.",
|
||||||
"REGENERATESECRET": "Regenerate Client Secret",
|
"REGENERATESECRET": "Regenerate Client Secret",
|
||||||
"DEVMODE": "Development Mode",
|
"DEVMODE": "Development Mode",
|
||||||
|
"DEVMODE_ENABLED": "Enabled",
|
||||||
|
"DEVMODE_DISABLED": "Disabled",
|
||||||
"DEVMODEDESC": "Beware: With development mode enabled redirect URIs will not be validated.",
|
"DEVMODEDESC": "Beware: With development mode enabled redirect URIs will not be validated.",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE": "Skip Login Success Page",
|
"SKIPNATIVEAPPSUCCESSPAGE": "Skip Login Success Page",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Skip the success page after a login for this native app.",
|
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Skip the success page after a login for this native app.",
|
||||||
|
@@ -1983,6 +1983,8 @@
|
|||||||
"CLIENTSECRET_DESCRIPTION": "Mantén tu secreto de cliente en un lugar seguro puesto que desaparecerá una vez que se cierre el diálogo.",
|
"CLIENTSECRET_DESCRIPTION": "Mantén tu secreto de cliente en un lugar seguro puesto que desaparecerá una vez que se cierre el diálogo.",
|
||||||
"REGENERATESECRET": "Regenerar secreto de cliente",
|
"REGENERATESECRET": "Regenerar secreto de cliente",
|
||||||
"DEVMODE": "Modo Desarrollo",
|
"DEVMODE": "Modo Desarrollo",
|
||||||
|
"DEVMODE_ENABLED": "Activado",
|
||||||
|
"DEVMODE_DISABLED": "Desactivado",
|
||||||
"DEVMODEDESC": "Cuidado: Si el modo de desarrollo está activado las URIs de redirección no serán validadas.",
|
"DEVMODEDESC": "Cuidado: Si el modo de desarrollo está activado las URIs de redirección no serán validadas.",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE": "Saltar página de inicio de sesión con éxito",
|
"SKIPNATIVEAPPSUCCESSPAGE": "Saltar página de inicio de sesión con éxito",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Sáltate la página de éxito después de iniciar sesión en esta app nativa.",
|
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Sáltate la página de éxito después de iniciar sesión en esta app nativa.",
|
||||||
|
@@ -1987,6 +1987,8 @@
|
|||||||
"CLIENTSECRET_DESCRIPTION": "Conservez votre secret client dans un endroit sûr car il disparaîtra une fois la boîte de dialogue fermée.",
|
"CLIENTSECRET_DESCRIPTION": "Conservez votre secret client dans un endroit sûr car il disparaîtra une fois la boîte de dialogue fermée.",
|
||||||
"REGENERATESECRET": "Régénérer le secret du client",
|
"REGENERATESECRET": "Régénérer le secret du client",
|
||||||
"DEVMODE": "Mode développement",
|
"DEVMODE": "Mode développement",
|
||||||
|
"DEVMODE_ENABLED": "Activé",
|
||||||
|
"DEVMODE_DISABLED": "Désactivé",
|
||||||
"DEVMODEDESC": "Attention",
|
"DEVMODEDESC": "Attention",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE": "Sauter la page de succès de connexion",
|
"SKIPNATIVEAPPSUCCESSPAGE": "Sauter la page de succès de connexion",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Sauter la page de succès après la connexion pour cette application native",
|
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Sauter la page de succès après la connexion pour cette application native",
|
||||||
|
@@ -1987,6 +1987,8 @@
|
|||||||
"CLIENTSECRET_DESCRIPTION": "Salvate il Client Secret in un luogo sicuro, perch\u00e9 non sarà più disponibile dopo aver chiuso la finestra di dialogo",
|
"CLIENTSECRET_DESCRIPTION": "Salvate il Client Secret in un luogo sicuro, perch\u00e9 non sarà più disponibile dopo aver chiuso la finestra di dialogo",
|
||||||
"REGENERATESECRET": "Rigenera il Client Secret",
|
"REGENERATESECRET": "Rigenera il Client Secret",
|
||||||
"DEVMODE": "Modalit\u00e0 di sviluppo (DEV Mode)",
|
"DEVMODE": "Modalit\u00e0 di sviluppo (DEV Mode)",
|
||||||
|
"DEVMODE_ENABLED": "Attivato",
|
||||||
|
"DEVMODE_DISABLED": "Disattivato",
|
||||||
"DEVMODEDESC": "Attenzione: Con la modalit\u00e0 di sviluppo abilitata, gli URI di reindirizzamento non saranno convalidati.",
|
"DEVMODEDESC": "Attenzione: Con la modalit\u00e0 di sviluppo abilitata, gli URI di reindirizzamento non saranno convalidati.",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE": "Salta la pagina di successo dopo il login",
|
"SKIPNATIVEAPPSUCCESSPAGE": "Salta la pagina di successo dopo il login",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Salta la pagina di successo dopo il login per questa applicazione nativa",
|
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Salta la pagina di successo dopo il login per questa applicazione nativa",
|
||||||
|
@@ -1978,6 +1978,8 @@
|
|||||||
"CLIENTSECRET_DESCRIPTION": "クライアントシークレットは、ダイアログを閉じると消えてしまうので、安全な場所に保管してください。",
|
"CLIENTSECRET_DESCRIPTION": "クライアントシークレットは、ダイアログを閉じると消えてしまうので、安全な場所に保管してください。",
|
||||||
"REGENERATESECRET": "クライアントシークレットを再生成する",
|
"REGENERATESECRET": "クライアントシークレットを再生成する",
|
||||||
"DEVMODE": "開発モード",
|
"DEVMODE": "開発モード",
|
||||||
|
"DEVMODE_ENABLED": "アクティブ化された",
|
||||||
|
"DEVMODE_DISABLED": "無効化されました",
|
||||||
"DEVMODEDESC": "注意:開発モードを有効にすると、URIが認証されません。",
|
"DEVMODEDESC": "注意:開発モードを有効にすると、URIが認証されません。",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE": "ログイン後に成功ページをスキップする",
|
"SKIPNATIVEAPPSUCCESSPAGE": "ログイン後に成功ページをスキップする",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "このネイティブアプリのログイン後に成功ページをスキップする",
|
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "このネイティブアプリのログイン後に成功ページをスキップする",
|
||||||
|
@@ -1984,6 +1984,8 @@
|
|||||||
"CLIENTSECRET_DESCRIPTION": "Чувајте ја вашата клиентска тајна на безбедно место, бидејќи ќе исчезне откако ќе се затвори дијалогот.",
|
"CLIENTSECRET_DESCRIPTION": "Чувајте ја вашата клиентска тајна на безбедно место, бидејќи ќе исчезне откако ќе се затвори дијалогот.",
|
||||||
"REGENERATESECRET": "Генерирај нова клиентска тајна",
|
"REGENERATESECRET": "Генерирај нова клиентска тајна",
|
||||||
"DEVMODE": "Development mode",
|
"DEVMODE": "Development mode",
|
||||||
|
"DEVMODE_ENABLED": "Активиран",
|
||||||
|
"DEVMODE_DISABLED": "Деактивирано",
|
||||||
"DEVMODEDESC": "Внимавајте: Со овозможен Development mode, URIs за пренасочување нема да бидат валидирани.",
|
"DEVMODEDESC": "Внимавајте: Со овозможен Development mode, URIs за пренасочување нема да бидат валидирани.",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE": "Прескокни страница за успешна најава",
|
"SKIPNATIVEAPPSUCCESSPAGE": "Прескокни страница за успешна најава",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Прескокнете ја страницата за успешна најава за оваа нативна апликација.",
|
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Прескокнете ја страницата за успешна најава за оваа нативна апликација.",
|
||||||
|
@@ -1987,6 +1987,8 @@
|
|||||||
"CLIENTSECRET_DESCRIPTION": "Trzymaj swój sekret klienta w bezpiecznym miejscu, ponieważ zniknie on po zamknięciu okna dialogowego.",
|
"CLIENTSECRET_DESCRIPTION": "Trzymaj swój sekret klienta w bezpiecznym miejscu, ponieważ zniknie on po zamknięciu okna dialogowego.",
|
||||||
"REGENERATESECRET": "Odtwórz sekret klienta",
|
"REGENERATESECRET": "Odtwórz sekret klienta",
|
||||||
"DEVMODE": "Tryb rozwoju",
|
"DEVMODE": "Tryb rozwoju",
|
||||||
|
"DEVMODE_ENABLED": "Aktywowany",
|
||||||
|
"DEVMODE_DISABLED": "Dezaktywowane",
|
||||||
"DEVMODEDESC": "Uwaga: przy włączonym trybie rozwoju adresy URI przekierowania nie będą sprawdzane.",
|
"DEVMODEDESC": "Uwaga: przy włączonym trybie rozwoju adresy URI przekierowania nie będą sprawdzane.",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE": "Pomiń stronę sukcesu po zalogowaniu",
|
"SKIPNATIVEAPPSUCCESSPAGE": "Pomiń stronę sukcesu po zalogowaniu",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Pomiń stronę sukcesu po zalogowaniu dla tej Natywny aplikację",
|
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Pomiń stronę sukcesu po zalogowaniu dla tej Natywny aplikację",
|
||||||
|
@@ -1982,6 +1982,8 @@
|
|||||||
"CLIENTSECRET_DESCRIPTION": "Mantenha o segredo do cliente em um local seguro, pois ele desaparecerá assim que o diálogo for fechado.",
|
"CLIENTSECRET_DESCRIPTION": "Mantenha o segredo do cliente em um local seguro, pois ele desaparecerá assim que o diálogo for fechado.",
|
||||||
"REGENERATESECRET": "Regenerar Segredo do Cliente",
|
"REGENERATESECRET": "Regenerar Segredo do Cliente",
|
||||||
"DEVMODE": "Modo de Desenvolvimento",
|
"DEVMODE": "Modo de Desenvolvimento",
|
||||||
|
"DEVMODE_ENABLED": "Ativado",
|
||||||
|
"DEVMODE_DISABLED": "Desativado",
|
||||||
"DEVMODEDESC": "Atenção: Com o modo de desenvolvimento habilitado, as URIs de redirecionamento não serão validadas.",
|
"DEVMODEDESC": "Atenção: Com o modo de desenvolvimento habilitado, as URIs de redirecionamento não serão validadas.",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE": "Pular Página de Sucesso de Login",
|
"SKIPNATIVEAPPSUCCESSPAGE": "Pular Página de Sucesso de Login",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Pule a página de sucesso após o login para este aplicativo nativo.",
|
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Pule a página de sucesso após o login para este aplicativo nativo.",
|
||||||
|
@@ -1986,6 +1986,8 @@
|
|||||||
"CLIENTSECRET_DESCRIPTION": "将您的客户保密在一个安全的地方,因为一旦对话框关闭,便无法再次查看。",
|
"CLIENTSECRET_DESCRIPTION": "将您的客户保密在一个安全的地方,因为一旦对话框关闭,便无法再次查看。",
|
||||||
"REGENERATESECRET": "重新生成客户端密钥",
|
"REGENERATESECRET": "重新生成客户端密钥",
|
||||||
"DEVMODE": "开发模式",
|
"DEVMODE": "开发模式",
|
||||||
|
"DEVMODE_ENABLED": "活性",
|
||||||
|
"DEVMODE_DISABLED": "已停用",
|
||||||
"DEVMODEDESC": "注意:启用开发模式的重定向 URI 将不会被验证。",
|
"DEVMODEDESC": "注意:启用开发模式的重定向 URI 将不会被验证。",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE": "登录后跳过成功页面",
|
"SKIPNATIVEAPPSUCCESSPAGE": "登录后跳过成功页面",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "登录后跳过本机应用的成功页面",
|
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "登录后跳过本机应用的成功页面",
|
||||||
|
69
docs/docs/guides/integrate/identity-providers/keycloak.mdx
Normal file
69
docs/docs/guides/integrate/identity-providers/keycloak.mdx
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
---
|
||||||
|
title: Configure Keycloak as an Identity Provider in ZITADEL
|
||||||
|
sidebar_label: Keycloak generic OIDC
|
||||||
|
id: keycloak
|
||||||
|
---
|
||||||
|
|
||||||
|
import GeneralConfigDescription from './_general_config_description.mdx';
|
||||||
|
import Intro from './_intro.mdx';
|
||||||
|
import CustomLoginPolicy from './_custom_login_policy.mdx';
|
||||||
|
import IDPsOverview from './_idps_overview.mdx';
|
||||||
|
import GenericOIDC from './_generic_oidc.mdx';
|
||||||
|
import Activate from './_activate.mdx';
|
||||||
|
import TestSetup from './_test_setup.mdx';
|
||||||
|
|
||||||
|
<Intro provider="Keycloak"/>
|
||||||
|
|
||||||
|
## Keycloak Configuration
|
||||||
|
|
||||||
|
### Register a new client
|
||||||
|
|
||||||
|
1. Login to your Keycloak account and go to the clients list: <$KEYCLOAK-DOMAIN/auth/admin/$REALM/console/#/$REALM/clients>
|
||||||
|
2. Click on "Create Client"
|
||||||
|
3. Choose OpenID Connect as Client Type and give your client an ID
|
||||||
|
4. Enable Client authentication and the standard flow and direct access grants as authentication flow
|
||||||
|
5. Add the valid redirect URIs
|
||||||
|
- {your-domain}/ui/login/login/externalidp/callback
|
||||||
|
- Example redirect url for the domain `https://acme-gzoe4x.zitadel.cloud` would look like this: `https://acme-gzoe4x.zitadel.cloud/ui/login/login/externalidp/callback`
|
||||||
|
6. Go to the credentials tab and copy the secret
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
## ZITADEL configuration
|
||||||
|
|
||||||
|
### Add custom login policy
|
||||||
|
|
||||||
|
<CustomLoginPolicy/>
|
||||||
|
|
||||||
|
### Go to the IdP providers overview
|
||||||
|
|
||||||
|
<IDPsOverview templates="Generic OIDC"/>
|
||||||
|
|
||||||
|
### Create a new generic OIDC provider
|
||||||
|
|
||||||
|
<GenericOIDC
|
||||||
|
name=": e.g. Keycloak"
|
||||||
|
issuer=": The domain where your Keycloak can be reached with the path /auth/realms/$REALM, Example: https://lemur-0.cloud-iam.com/auth/realms/acme"
|
||||||
|
clientid=": Client id from the client previously created in your Keycloak account"
|
||||||
|
/>
|
||||||
|
|
||||||
|
|
||||||
|
<GeneralConfigDescription provider_account="Keycloak account" />
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Activate IdP
|
||||||
|
|
||||||
|
<Activate/>
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Test the setup
|
||||||
|
|
||||||
|
<TestSetup loginscreen="your Keycloak login"/>
|
||||||
|
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
@@ -20,7 +20,7 @@ This means that the users and also their authorizations will be managed within Z
|
|||||||
An organization is the ZITADEL resource which contains users, projects, applications, policies and so on.
|
An organization is the ZITADEL resource which contains users, projects, applications, policies and so on.
|
||||||
In an organization projects and users are managed by the organization.
|
In an organization projects and users are managed by the organization.
|
||||||
You need at least one organization for your own company in our case "The Timing Company".
|
You need at least one organization for your own company in our case "The Timing Company".
|
||||||
As next step grate an organization for each of your costumers.
|
Your next step is to create an organization for each of your customers.
|
||||||
|
|
||||||
## Project
|
## Project
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ You can configure `check roles on authentication` on the project, if you want to
|
|||||||
|
|
||||||
To give a customer permissions to a project, a project grant to the customers organization is needed (search the granted organization by its domain).
|
To give a customer permissions to a project, a project grant to the customers organization is needed (search the granted organization by its domain).
|
||||||
It is also possible to delegate only specific roles of the project to a certain customer.
|
It is also possible to delegate only specific roles of the project to a certain customer.
|
||||||
As soon as a project grant exists, the customer will see the project in the granted projects section of his organization and will be able to authorize his own users to the given project.
|
As soon as a project grant exists, the customer will see the project in the granted projects section of their organization and will be able to authorize their own users to the given project.
|
||||||
|
|
||||||
## Authorizations
|
## Authorizations
|
||||||
|
|
||||||
|
@@ -6,14 +6,14 @@ title: Technical Advisory 10002
|
|||||||
|
|
||||||
Version: TBD
|
Version: TBD
|
||||||
|
|
||||||
Date: Calendar week 40/41
|
Date: Calendar week 44
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|
||||||
Since Angular Material v15 many of the UI components have been refactored
|
Since Angular Material v15 many of the UI components have been refactored
|
||||||
to be based on the official Material Design Components for Web (MDC).
|
to be based on the official Material Design Components for Web (MDC).
|
||||||
These refactored components do not support dynamic styling, so in order to keep the library up-to-date,
|
These refactored components do not support dynamic styling, so in order to keep the library up-to-date,
|
||||||
the console UI will loose its dynamic theming capability.
|
the console UI will lose its dynamic theming capability.
|
||||||
|
|
||||||
## Statement
|
## Statement
|
||||||
|
|
||||||
@@ -23,7 +23,7 @@ As soon as the release version is published, we will include the version here.
|
|||||||
## Mitigation
|
## Mitigation
|
||||||
|
|
||||||
If you need users to have your branding settings
|
If you need users to have your branding settings
|
||||||
(background-, button-, link and text coloring), you should implemement your
|
(background-, button-, link and text coloring), you should implement your
|
||||||
own user facing UI yourself and not use ZITADELs console UI. Assets like your logo and icons will still be used.
|
own user facing UI yourself and not use ZITADELs console UI. Assets like your logo and icons will still be used.
|
||||||
|
|
||||||
## Impact
|
## Impact
|
||||||
|
46
docs/docs/support/advisory/a10003.md
Normal file
46
docs/docs/support/advisory/a10003.md
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
---
|
||||||
|
title: Technical Advisory 10003
|
||||||
|
---
|
||||||
|
|
||||||
|
## Date and Version
|
||||||
|
|
||||||
|
Version: 2.38.0
|
||||||
|
|
||||||
|
Date: Calendar week 41
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
When users are redirected to the ZITADEL Login-UI without any organizational context, they're currently presented a login screen,
|
||||||
|
based on the instance settings, e.g. available IDPs and possible login mechanisms. If the user will then register himself,
|
||||||
|
by the registration form or through an IDP, the user will always be created on the default organization.
|
||||||
|
|
||||||
|
This behaviour led to confusion, e.g. when activating IDPs on default org would not show up in the Login-UI, because they would still be loaded from the instance settings.
|
||||||
|
|
||||||
|
To improve this, we're introducing the following change:
|
||||||
|
If users are redirected to the Login-UI without any organizational context, they will be presented a login screen based on the settings of the default organization (incl. IDPs).
|
||||||
|
|
||||||
|
:::note
|
||||||
|
If the registration (and also authentication) needs to occur on a specified organization, apps can already
|
||||||
|
specify this by providing [an organization scope](https://zitadel.com/docs/apis/openidoauth/scopes#reserved-scopes).
|
||||||
|
:::
|
||||||
|
|
||||||
|
## Statement
|
||||||
|
|
||||||
|
This change was tracked in the following PR:
|
||||||
|
[feat(login): use default org for login without provided org context](https://github.com/zitadel/zitadel/pull/6625), which was released in Version [2.38.0](https://github.com/zitadel/zitadel/releases/tag/v2.38.0)
|
||||||
|
|
||||||
|
## Mitigation
|
||||||
|
|
||||||
|
There's no action needed on your side currently as existing instances are not affected directly and IAM_OWNER can activate the flag at their own pace.
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
Once this update has been released and deployed, newly created instances will always use the default organization and its settings as default context for the login.
|
||||||
|
|
||||||
|
Already existing instances will still use the instance settings by default and can switch to the new default by ["Activating the 'LoginDefaultOrg' feature"](https://zitadel.com/docs/apis/resources/admin/admin-service-activate-feature-login-default-org) through the Admin API.
|
||||||
|
**This change is irreversible!**
|
||||||
|
|
||||||
|
:::note
|
||||||
|
Regardless of the change:
|
||||||
|
If a known username is entered on the first screen, the login switches its context to the organization of that user and settings will be updated to that organization as well.
|
||||||
|
:::
|
37
docs/docs/support/advisory/a10004.md
Normal file
37
docs/docs/support/advisory/a10004.md
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
---
|
||||||
|
title: Technical Advisory 10004
|
||||||
|
---
|
||||||
|
|
||||||
|
## Date and Version
|
||||||
|
|
||||||
|
Version: 2.39.0
|
||||||
|
|
||||||
|
Date: 2023-10-14
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
Due to storage optimisations ZITADEL changes the behaviour of sequences.
|
||||||
|
This change improves command (create, update, delete) performance of ZITADEL.
|
||||||
|
|
||||||
|
Sequences are no longer unique inside an instance.
|
||||||
|
From now on sequences are upcounting per aggregate id.
|
||||||
|
For example sequences of newly created users begin at 1.
|
||||||
|
Existing sequences remain untouched.
|
||||||
|
|
||||||
|
## Statement
|
||||||
|
|
||||||
|
This change is tracked in the following PR: [new eventstore framework](https://github.com/zitadel/zitadel/issues/5358).
|
||||||
|
As soon as the release version is published, we will include the version here.
|
||||||
|
|
||||||
|
## Mitigation
|
||||||
|
|
||||||
|
If you use the ListEvents API to scrape events use the creation date instead of the sequence.
|
||||||
|
If you use sequences on a list of objects it's no longer garanteed to have unique sequences across the list.
|
||||||
|
Therefore it's recommended to use the change data of the objects instead.
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
Once this update has been released and deployed, sequences are no longer unique inside an instance.
|
||||||
|
ZITADEL will increase parallel write capabilities, because there is no global sequence to track anymore.
|
||||||
|
Editor service does not respond the different services of ZITADEL anymore, it returns zitadel.
|
||||||
|
As we are switching to resource based API's there is no need for this field anymore.
|
33
docs/docs/support/advisory/a10005.md
Normal file
33
docs/docs/support/advisory/a10005.md
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
---
|
||||||
|
title: Technical Advisory 10005
|
||||||
|
---
|
||||||
|
|
||||||
|
## Date and Version
|
||||||
|
|
||||||
|
Version: 2.39.0
|
||||||
|
|
||||||
|
Date: Calendar week 41/42 2023
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
Migrating to version >= 2.39 from < 2.39 will cause down time during setup starts and the new version is started.
|
||||||
|
This is caused by storage optimisations which replace the `eventstore.events` database table with the new `eventstore.events2` table.
|
||||||
|
All existing events are migrated during the execution of the `zitadel setup` command.
|
||||||
|
New events will be inserted into the new `eventstore.events2` table. The old table `evetstore.events` is renamed to `eventstore.events_old` and will be dropped in a future release of ZITADEL.
|
||||||
|
|
||||||
|
## Statement
|
||||||
|
|
||||||
|
This change is tracked in the following PR: [new eventstore framework](https://github.com/zitadel/zitadel/issues/5358).
|
||||||
|
As soon as the release version is published, we will include the version here.
|
||||||
|
|
||||||
|
## Mitigation
|
||||||
|
|
||||||
|
If you use this table for TRIGGERS or Change data capture please check the new table definition and change your code to use `eventstore.events2`.
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
Once the setup step renamed the table. Old versions of ZITADEL are not able to read/write events.
|
||||||
|
|
||||||
|
:::note
|
||||||
|
If the upgrade fails make sure to rename `eventstore.events_old` to `eventstore.events`. This change enables older ZITADEL versions to work properly.
|
||||||
|
:::
|
@@ -68,7 +68,55 @@ We understand that these advisories may include breaking changes, and we aim to
|
|||||||
ZITADEL hosted Login-UI is not affected by this change.
|
ZITADEL hosted Login-UI is not affected by this change.
|
||||||
</td>
|
</td>
|
||||||
<td>TBD</td>
|
<td>TBD</td>
|
||||||
<td>Calendar week 40/41</td>
|
<td>Calendar week 44</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a href="./advisory/a10003">A-10003</a>
|
||||||
|
</td>
|
||||||
|
<td>Login-UI - Default Context</td>
|
||||||
|
<td>Breaking Behaviour Change</td>
|
||||||
|
<td>
|
||||||
|
When users are redirected to the ZITADEL Login-UI without any organizational context,
|
||||||
|
they're currently presented a login screen, based on the instance settings,
|
||||||
|
e.g. available IDPs and possible login mechanisms. If the user will then register himself,
|
||||||
|
by the registration form or through an IDP, the user will always be created on the default organization.
|
||||||
|
With the introduced change, the settings will no longer be loaded from the instance, but rather the default organization directly.
|
||||||
|
</td>
|
||||||
|
<td>2.38.0</td>
|
||||||
|
<td>Calendar week 41</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a href="./advisory/a10004">A-10004</a>
|
||||||
|
</td>
|
||||||
|
<td>Sequence uniquenes</td>
|
||||||
|
<td>Breaking Behaviour Change</td>
|
||||||
|
<td>
|
||||||
|
Due to storage optimisations ZITADEL changes the behaviour of sequences.
|
||||||
|
This change improves command (create, update, delete) performance of ZITADEL.
|
||||||
|
Sequences are no longer unique inside an instance.
|
||||||
|
From now on sequences are upcounting per aggregate id.
|
||||||
|
For example sequences of newly created users begin at 1.
|
||||||
|
Existing sequences remain untouched.
|
||||||
|
</td>
|
||||||
|
<td>2.39.0</td>
|
||||||
|
<td>2023-10-14</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a href="./advisory/a10005">A-10005</a>
|
||||||
|
</td>
|
||||||
|
<td>Expected downtime during upgrade</td>
|
||||||
|
<td>Expected downtime during upgrade</td>
|
||||||
|
<td>
|
||||||
|
Migrating to versions >= 2.39 from < 2.39 will cause down time during setup starts and the new version is started.
|
||||||
|
This is caused by storage optimisations which replace the `eventstore.events` database table with the new `eventstore.events2` table.
|
||||||
|
All existing events are migrated during the execution of the `zitadel setup` command.
|
||||||
|
New events will be inserted into the new `eventstore.events2` table. The old table `evetstore.events` is renamed to `eventstore.events_old` and will be dropped in a future release of ZITADEL.
|
||||||
|
</td>
|
||||||
|
<td>2.39.0</td>
|
||||||
|
<td>Calendar week 41/42 2023</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
@@ -244,6 +244,7 @@ module.exports = {
|
|||||||
"guides/integrate/identity-providers/openldap",
|
"guides/integrate/identity-providers/openldap",
|
||||||
"guides/integrate/identity-providers/migrate",
|
"guides/integrate/identity-providers/migrate",
|
||||||
"guides/integrate/identity-providers/okta",
|
"guides/integrate/identity-providers/okta",
|
||||||
|
"guides/integrate/identity-providers/keycloak",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
BIN
docs/static/img/guides/keycloak_add_client.png
vendored
Normal file
BIN
docs/static/img/guides/keycloak_add_client.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 339 KiB |
BIN
docs/static/img/guides/keycloak_client_secret.png
vendored
Normal file
BIN
docs/static/img/guides/keycloak_client_secret.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 286 KiB |
BIN
docs/static/img/guides/keycloak_login.png
vendored
Normal file
BIN
docs/static/img/guides/keycloak_login.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 148 KiB |
BIN
docs/static/img/guides/zitadel_activate_keycloak.png
vendored
Normal file
BIN
docs/static/img/guides/zitadel_activate_keycloak.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 215 KiB |
BIN
docs/static/img/guides/zitadel_keycloak_create_provider.png
vendored
Normal file
BIN
docs/static/img/guides/zitadel_keycloak_create_provider.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 287 KiB |
BIN
docs/static/img/guides/zitadel_login_keycloak.png
vendored
Normal file
BIN
docs/static/img/guides/zitadel_login_keycloak.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 111 KiB |
@@ -66,7 +66,11 @@ func (s *Server) ListOrgs(ctx context.Context, req *admin_pb.ListOrgsRequest) (*
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) SetUpOrg(ctx context.Context, req *admin_pb.SetUpOrgRequest) (*admin_pb.SetUpOrgResponse, error) {
|
func (s *Server) SetUpOrg(ctx context.Context, req *admin_pb.SetUpOrgRequest) (*admin_pb.SetUpOrgResponse, error) {
|
||||||
userIDs, err := s.getClaimedUserIDsOfOrgDomain(ctx, domain.NewIAMDomainName(req.Org.Name, authz.GetInstance(ctx).RequestedDomain()))
|
orgDomain, err := domain.NewIAMDomainName(req.Org.Name, authz.GetInstance(ctx).RequestedDomain())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
userIDs, err := s.getClaimedUserIDsOfOrgDomain(ctx, orgDomain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@@ -72,7 +72,11 @@ func (s *Server) ListOrgChanges(ctx context.Context, req *mgmt_pb.ListOrgChanges
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) AddOrg(ctx context.Context, req *mgmt_pb.AddOrgRequest) (*mgmt_pb.AddOrgResponse, error) {
|
func (s *Server) AddOrg(ctx context.Context, req *mgmt_pb.AddOrgRequest) (*mgmt_pb.AddOrgResponse, error) {
|
||||||
userIDs, err := s.getClaimedUserIDsOfOrgDomain(ctx, domain.NewIAMDomainName(req.Name, authz.GetInstance(ctx).RequestedDomain()), "")
|
orgDomain, err := domain.NewIAMDomainName(req.Name, authz.GetInstance(ctx).RequestedDomain())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
userIDs, err := s.getClaimedUserIDsOfOrgDomain(ctx, orgDomain, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@@ -79,7 +79,8 @@ func createInstancePbToAddHuman(req *system_pb.CreateInstanceRequest_Human, defa
|
|||||||
// check if default username is email style or else append @<orgname>.<custom-domain>
|
// check if default username is email style or else append @<orgname>.<custom-domain>
|
||||||
// this way we have the same value as before changing `UserLoginMustBeDomain` to false
|
// this way we have the same value as before changing `UserLoginMustBeDomain` to false
|
||||||
if !userLoginMustBeDomain && !strings.Contains(user.Username, "@") {
|
if !userLoginMustBeDomain && !strings.Contains(user.Username, "@") {
|
||||||
user.Username = user.Username + "@" + domain.NewIAMDomainName(org, externalDomain)
|
orgDomain, _ := domain.NewIAMDomainName(org, externalDomain)
|
||||||
|
user.Username = user.Username + "@" + orgDomain
|
||||||
}
|
}
|
||||||
if req.UserName != "" {
|
if req.UserName != "" {
|
||||||
user.Username = req.UserName
|
user.Username = req.UserName
|
||||||
@@ -185,7 +186,8 @@ func AddInstancePbToSetupInstance(req *system_pb.AddInstanceRequest, defaultInst
|
|||||||
// check if default username is email style or else append @<orgname>.<custom-domain>
|
// check if default username is email style or else append @<orgname>.<custom-domain>
|
||||||
// this way we have the same value as before changing `UserLoginMustBeDomain` to false
|
// this way we have the same value as before changing `UserLoginMustBeDomain` to false
|
||||||
if !instance.DomainPolicy.UserLoginMustBeDomain && !strings.Contains(instance.Org.Human.Username, "@") {
|
if !instance.DomainPolicy.UserLoginMustBeDomain && !strings.Contains(instance.Org.Human.Username, "@") {
|
||||||
instance.Org.Human.Username = instance.Org.Human.Username + "@" + domain.NewIAMDomainName(instance.Org.Name, externalDomain)
|
orgDomain, _ := domain.NewIAMDomainName(instance.Org.Name, externalDomain)
|
||||||
|
instance.Org.Human.Username = instance.Org.Human.Username + "@" + orgDomain
|
||||||
}
|
}
|
||||||
if req.OwnerPassword != nil {
|
if req.OwnerPassword != nil {
|
||||||
instance.Org.Human.Password = req.OwnerPassword.Password
|
instance.Org.Human.Password = req.OwnerPassword.Password
|
||||||
|
@@ -9,7 +9,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (s *Server) AddOTPSMS(ctx context.Context, req *user.AddOTPSMSRequest) (*user.AddOTPSMSResponse, error) {
|
func (s *Server) AddOTPSMS(ctx context.Context, req *user.AddOTPSMSRequest) (*user.AddOTPSMSResponse, error) {
|
||||||
details, err := s.command.AddHumanOTPSMS(ctx, req.GetUserId(), authz.GetCtxData(ctx).ResourceOwner)
|
details, err := s.command.AddHumanOTPSMS(ctx, req.GetUserId(), authz.GetCtxData(ctx).OrgID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -18,7 +18,7 @@ func (s *Server) AddOTPSMS(ctx context.Context, req *user.AddOTPSMSRequest) (*us
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) RemoveOTPSMS(ctx context.Context, req *user.RemoveOTPSMSRequest) (*user.RemoveOTPSMSResponse, error) {
|
func (s *Server) RemoveOTPSMS(ctx context.Context, req *user.RemoveOTPSMSRequest) (*user.RemoveOTPSMSResponse, error) {
|
||||||
objectDetails, err := s.command.RemoveHumanOTPSMS(ctx, req.GetUserId(), authz.GetCtxData(ctx).ResourceOwner)
|
objectDetails, err := s.command.RemoveHumanOTPSMS(ctx, req.GetUserId(), authz.GetCtxData(ctx).OrgID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -26,7 +26,7 @@ func (s *Server) RemoveOTPSMS(ctx context.Context, req *user.RemoveOTPSMSRequest
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) AddOTPEmail(ctx context.Context, req *user.AddOTPEmailRequest) (*user.AddOTPEmailResponse, error) {
|
func (s *Server) AddOTPEmail(ctx context.Context, req *user.AddOTPEmailRequest) (*user.AddOTPEmailResponse, error) {
|
||||||
details, err := s.command.AddHumanOTPEmail(ctx, req.GetUserId(), authz.GetCtxData(ctx).ResourceOwner)
|
details, err := s.command.AddHumanOTPEmail(ctx, req.GetUserId(), authz.GetCtxData(ctx).OrgID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -35,7 +35,7 @@ func (s *Server) AddOTPEmail(ctx context.Context, req *user.AddOTPEmailRequest)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) RemoveOTPEmail(ctx context.Context, req *user.RemoveOTPEmailRequest) (*user.RemoveOTPEmailResponse, error) {
|
func (s *Server) RemoveOTPEmail(ctx context.Context, req *user.RemoveOTPEmailRequest) (*user.RemoveOTPEmailResponse, error) {
|
||||||
objectDetails, err := s.command.RemoveHumanOTPEmail(ctx, req.GetUserId(), authz.GetCtxData(ctx).ResourceOwner)
|
objectDetails, err := s.command.RemoveHumanOTPEmail(ctx, req.GetUserId(), authz.GetCtxData(ctx).OrgID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@@ -15,7 +15,7 @@ import (
|
|||||||
|
|
||||||
func (s *Server) RegisterPasskey(ctx context.Context, req *user.RegisterPasskeyRequest) (resp *user.RegisterPasskeyResponse, err error) {
|
func (s *Server) RegisterPasskey(ctx context.Context, req *user.RegisterPasskeyRequest) (resp *user.RegisterPasskeyResponse, err error) {
|
||||||
var (
|
var (
|
||||||
resourceOwner = authz.GetCtxData(ctx).ResourceOwner
|
resourceOwner = authz.GetCtxData(ctx).OrgID
|
||||||
authenticator = passkeyAuthenticatorToDomain(req.GetAuthenticator())
|
authenticator = passkeyAuthenticatorToDomain(req.GetAuthenticator())
|
||||||
)
|
)
|
||||||
if code := req.GetCode(); code != nil {
|
if code := req.GetCode(); code != nil {
|
||||||
@@ -65,7 +65,7 @@ func passkeyRegistrationDetailsToPb(details *domain.WebAuthNRegistrationDetails,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) VerifyPasskeyRegistration(ctx context.Context, req *user.VerifyPasskeyRegistrationRequest) (*user.VerifyPasskeyRegistrationResponse, error) {
|
func (s *Server) VerifyPasskeyRegistration(ctx context.Context, req *user.VerifyPasskeyRegistrationRequest) (*user.VerifyPasskeyRegistrationResponse, error) {
|
||||||
resourceOwner := authz.GetCtxData(ctx).ResourceOwner
|
resourceOwner := authz.GetCtxData(ctx).OrgID
|
||||||
pkc, err := req.GetPublicKeyCredential().MarshalJSON()
|
pkc, err := req.GetPublicKeyCredential().MarshalJSON()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, caos_errs.ThrowInternal(err, "USERv2-Pha2o", "Errors.Internal")
|
return nil, caos_errs.ThrowInternal(err, "USERv2-Pha2o", "Errors.Internal")
|
||||||
@@ -80,7 +80,7 @@ func (s *Server) VerifyPasskeyRegistration(ctx context.Context, req *user.Verify
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) CreatePasskeyRegistrationLink(ctx context.Context, req *user.CreatePasskeyRegistrationLinkRequest) (resp *user.CreatePasskeyRegistrationLinkResponse, err error) {
|
func (s *Server) CreatePasskeyRegistrationLink(ctx context.Context, req *user.CreatePasskeyRegistrationLinkRequest) (resp *user.CreatePasskeyRegistrationLinkResponse, err error) {
|
||||||
resourceOwner := authz.GetCtxData(ctx).ResourceOwner
|
resourceOwner := authz.GetCtxData(ctx).OrgID
|
||||||
|
|
||||||
switch medium := req.Medium.(type) {
|
switch medium := req.Medium.(type) {
|
||||||
case nil:
|
case nil:
|
||||||
|
@@ -48,7 +48,7 @@ func notificationTypeToDomain(notificationType user.NotificationType) domain.Not
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) SetPassword(ctx context.Context, req *user.SetPasswordRequest) (_ *user.SetPasswordResponse, err error) {
|
func (s *Server) SetPassword(ctx context.Context, req *user.SetPasswordRequest) (_ *user.SetPasswordResponse, err error) {
|
||||||
var resourceOwner = authz.GetCtxData(ctx).ResourceOwner
|
var resourceOwner = authz.GetCtxData(ctx).OrgID
|
||||||
var details *domain.ObjectDetails
|
var details *domain.ObjectDetails
|
||||||
|
|
||||||
switch v := req.GetVerification().(type) {
|
switch v := req.GetVerification().(type) {
|
||||||
|
@@ -11,7 +11,7 @@ import (
|
|||||||
|
|
||||||
func (s *Server) RegisterTOTP(ctx context.Context, req *user.RegisterTOTPRequest) (*user.RegisterTOTPResponse, error) {
|
func (s *Server) RegisterTOTP(ctx context.Context, req *user.RegisterTOTPRequest) (*user.RegisterTOTPResponse, error) {
|
||||||
return totpDetailsToPb(
|
return totpDetailsToPb(
|
||||||
s.command.AddUserTOTP(ctx, req.GetUserId(), authz.GetCtxData(ctx).ResourceOwner),
|
s.command.AddUserTOTP(ctx, req.GetUserId(), authz.GetCtxData(ctx).OrgID),
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -28,7 +28,7 @@ func totpDetailsToPb(totp *domain.TOTP, err error) (*user.RegisterTOTPResponse,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) VerifyTOTPRegistration(ctx context.Context, req *user.VerifyTOTPRegistrationRequest) (*user.VerifyTOTPRegistrationResponse, error) {
|
func (s *Server) VerifyTOTPRegistration(ctx context.Context, req *user.VerifyTOTPRegistrationRequest) (*user.VerifyTOTPRegistrationResponse, error) {
|
||||||
objectDetails, err := s.command.CheckUserTOTP(ctx, req.GetUserId(), req.GetCode(), authz.GetCtxData(ctx).ResourceOwner)
|
objectDetails, err := s.command.CheckUserTOTP(ctx, req.GetUserId(), req.GetCode(), authz.GetCtxData(ctx).OrgID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@@ -12,7 +12,7 @@ import (
|
|||||||
|
|
||||||
func (s *Server) RegisterU2F(ctx context.Context, req *user.RegisterU2FRequest) (*user.RegisterU2FResponse, error) {
|
func (s *Server) RegisterU2F(ctx context.Context, req *user.RegisterU2FRequest) (*user.RegisterU2FResponse, error) {
|
||||||
return u2fRegistrationDetailsToPb(
|
return u2fRegistrationDetailsToPb(
|
||||||
s.command.RegisterUserU2F(ctx, req.GetUserId(), authz.GetCtxData(ctx).ResourceOwner, req.GetDomain()),
|
s.command.RegisterUserU2F(ctx, req.GetUserId(), authz.GetCtxData(ctx).OrgID, req.GetDomain()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ func u2fRegistrationDetailsToPb(details *domain.WebAuthNRegistrationDetails, err
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) VerifyU2FRegistration(ctx context.Context, req *user.VerifyU2FRegistrationRequest) (*user.VerifyU2FRegistrationResponse, error) {
|
func (s *Server) VerifyU2FRegistration(ctx context.Context, req *user.VerifyU2FRegistrationRequest) (*user.VerifyU2FRegistrationResponse, error) {
|
||||||
resourceOwner := authz.GetCtxData(ctx).ResourceOwner
|
resourceOwner := authz.GetCtxData(ctx).OrgID
|
||||||
pkc, err := req.GetPublicKeyCredential().MarshalJSON()
|
pkc, err := req.GetPublicKeyCredential().MarshalJSON()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, caos_errs.ThrowInternal(err, "USERv2-IeTh4", "Errors.Internal")
|
return nil, caos_errs.ThrowInternal(err, "USERv2-IeTh4", "Errors.Internal")
|
||||||
|
@@ -161,7 +161,11 @@ func (l *Login) Handler() http.Handler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (l *Login) getClaimedUserIDsOfOrgDomain(ctx context.Context, orgName string) ([]string, error) {
|
func (l *Login) getClaimedUserIDsOfOrgDomain(ctx context.Context, orgName string) ([]string, error) {
|
||||||
loginName, err := query.NewUserPreferredLoginNameSearchQuery("@"+domain.NewIAMDomainName(orgName, authz.GetInstance(ctx).RequestedDomain()), query.TextEndsWithIgnoreCase)
|
orgDomain, err := domain.NewIAMDomainName(orgName, authz.GetInstance(ctx).RequestedDomain())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
loginName, err := query.NewUserPreferredLoginNameSearchQuery("@"+orgDomain, query.TextEndsWithIgnoreCase)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@@ -238,7 +238,10 @@ func AddOrgCommand(ctx context.Context, a *org.Aggregate, name string, userIDs .
|
|||||||
if name = strings.TrimSpace(name); name == "" {
|
if name = strings.TrimSpace(name); name == "" {
|
||||||
return nil, errors.ThrowInvalidArgument(nil, "ORG-mruNY", "Errors.Invalid.Argument")
|
return nil, errors.ThrowInvalidArgument(nil, "ORG-mruNY", "Errors.Invalid.Argument")
|
||||||
}
|
}
|
||||||
defaultDomain := domain.NewIAMDomainName(name, authz.GetInstance(ctx).RequestedDomain())
|
defaultDomain, err := domain.NewIAMDomainName(name, authz.GetInstance(ctx).RequestedDomain())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||||
return []eventstore.Command{
|
return []eventstore.Command{
|
||||||
org.NewOrgAddedEvent(ctx, &a.Aggregate, name),
|
org.NewOrgAddedEvent(ctx, &a.Aggregate, name),
|
||||||
|
@@ -309,13 +309,16 @@ func (c *Commands) changeDefaultDomain(ctx context.Context, orgID, newName strin
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
iamDomain := authz.GetInstance(ctx).RequestedDomain()
|
iamDomain := authz.GetInstance(ctx).RequestedDomain()
|
||||||
defaultDomain := domain.NewIAMDomainName(orgDomains.OrgName, iamDomain)
|
defaultDomain, _ := domain.NewIAMDomainName(orgDomains.OrgName, iamDomain)
|
||||||
isPrimary := defaultDomain == orgDomains.PrimaryDomain
|
isPrimary := defaultDomain == orgDomains.PrimaryDomain
|
||||||
orgAgg := OrgAggregateFromWriteModel(&orgDomains.WriteModel)
|
orgAgg := OrgAggregateFromWriteModel(&orgDomains.WriteModel)
|
||||||
for _, orgDomain := range orgDomains.Domains {
|
for _, orgDomain := range orgDomains.Domains {
|
||||||
if orgDomain.State == domain.OrgDomainStateActive {
|
if orgDomain.State == domain.OrgDomainStateActive {
|
||||||
if orgDomain.Domain == defaultDomain {
|
if orgDomain.Domain == defaultDomain {
|
||||||
newDefaultDomain := domain.NewIAMDomainName(newName, iamDomain)
|
newDefaultDomain, err := domain.NewIAMDomainName(newName, iamDomain)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
events := []eventstore.Command{
|
events := []eventstore.Command{
|
||||||
org.NewDomainAddedEvent(ctx, orgAgg, newDefaultDomain),
|
org.NewDomainAddedEvent(ctx, orgAgg, newDefaultDomain),
|
||||||
org.NewDomainVerifiedEvent(ctx, orgAgg, newDefaultDomain),
|
org.NewDomainVerifiedEvent(ctx, orgAgg, newDefaultDomain),
|
||||||
@@ -338,7 +341,7 @@ func (c *Commands) removeCustomDomains(ctx context.Context, orgID string) ([]eve
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
hasDefault := false
|
hasDefault := false
|
||||||
defaultDomain := domain.NewIAMDomainName(orgDomains.OrgName, authz.GetInstance(ctx).RequestedDomain())
|
defaultDomain, _ := domain.NewIAMDomainName(orgDomains.OrgName, authz.GetInstance(ctx).RequestedDomain())
|
||||||
isPrimary := defaultDomain == orgDomains.PrimaryDomain
|
isPrimary := defaultDomain == orgDomains.PrimaryDomain
|
||||||
orgAgg := OrgAggregateFromWriteModel(&orgDomains.WriteModel)
|
orgAgg := OrgAggregateFromWriteModel(&orgDomains.WriteModel)
|
||||||
events := make([]eventstore.Command, 0, len(orgDomains.Domains))
|
events := make([]eventstore.Command, 0, len(orgDomains.Domains))
|
||||||
|
@@ -25,7 +25,8 @@ func (o *Org) IsValid() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (o *Org) AddIAMDomain(iamDomain string) {
|
func (o *Org) AddIAMDomain(iamDomain string) {
|
||||||
o.Domains = append(o.Domains, &OrgDomain{Domain: NewIAMDomainName(o.Name, iamDomain), Verified: true, Primary: true})
|
orgDomain, _ := NewIAMDomainName(o.Name, iamDomain)
|
||||||
|
o.Domains = append(o.Domains, &OrgDomain{Domain: orgDomain, Verified: true, Primary: true})
|
||||||
}
|
}
|
||||||
|
|
||||||
type OrgState int32
|
type OrgState int32
|
||||||
|
@@ -6,6 +6,7 @@ import (
|
|||||||
|
|
||||||
http_util "github.com/zitadel/zitadel/internal/api/http"
|
http_util "github.com/zitadel/zitadel/internal/api/http"
|
||||||
"github.com/zitadel/zitadel/internal/crypto"
|
"github.com/zitadel/zitadel/internal/crypto"
|
||||||
|
"github.com/zitadel/zitadel/internal/errors"
|
||||||
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -32,15 +33,18 @@ func (domain *OrgDomain) GenerateVerificationCode(codeGenerator crypto.Generator
|
|||||||
return validationCode, nil
|
return validationCode, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewIAMDomainName(orgName, iamDomain string) string {
|
func NewIAMDomainName(orgName, iamDomain string) (string, error) {
|
||||||
// Reference: label domain requirements https://www.nic.ad.jp/timeline/en/20th/appendix1.html
|
// Reference: label domain requirements https://www.nic.ad.jp/timeline/en/20th/appendix1.html
|
||||||
|
|
||||||
// Replaces spaces in org name with hyphens
|
// Replaces spaces in org name with hyphens
|
||||||
label := strings.ReplaceAll(orgName, " ", "-")
|
label := strings.ReplaceAll(orgName, " ", "-")
|
||||||
|
|
||||||
// The label must only contains alphanumeric characters and hyphens
|
// The label must only contains alphanumeric characters and hyphens
|
||||||
// Invalid characters are replaced with and empty space
|
// Invalid characters are replaced with and empty space but as #6471,
|
||||||
label = string(regexp.MustCompile(`[^a-zA-Z0-9-]`).ReplaceAll([]byte(label), []byte("")))
|
// as these domains are not used to host ZITADEL, but only for user names,
|
||||||
|
// the characters shouldn't matter that much so we'll accept unicode
|
||||||
|
// characters, accented characters (\p{L}\p{M}), numbers and hyphens.
|
||||||
|
label = string(regexp.MustCompile(`[^\p{L}\p{M}0-9-]`).ReplaceAll([]byte(label), []byte("")))
|
||||||
|
|
||||||
// The label cannot exceed 63 characters
|
// The label cannot exceed 63 characters
|
||||||
if len(label) > 63 {
|
if len(label) > 63 {
|
||||||
@@ -64,7 +68,12 @@ func NewIAMDomainName(orgName, iamDomain string) string {
|
|||||||
label = label[:len(label)-1]
|
label = label[:len(label)-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
return strings.ToLower(label + "." + iamDomain)
|
// Empty string should be invalid
|
||||||
|
if len(label) > 0 {
|
||||||
|
return strings.ToLower(label + "." + iamDomain), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", errors.ThrowInvalidArgument(nil, "ORG-RrfXY", "Errors.Org.Domain.EmptyString")
|
||||||
}
|
}
|
||||||
|
|
||||||
type OrgDomainValidationType int32
|
type OrgDomainValidationType int32
|
||||||
|
@@ -41,10 +41,10 @@ func TestNewIAMDomainName(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "replace invalid characters [^a-zA-Z0-9-] with empty spaces",
|
name: "replace invalid characters [^a-zA-Z0-9-] with empty spaces",
|
||||||
args: args{
|
args: args{
|
||||||
orgName: "mí Örg name?",
|
orgName: "mí >**name?",
|
||||||
iamDomain: "localhost",
|
iamDomain: "localhost",
|
||||||
},
|
},
|
||||||
result: "m-rg-name.localhost",
|
result: "mí-name.localhost",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "label created from org name size is not greater than 63 chars",
|
name: "label created from org name size is not greater than 63 chars",
|
||||||
@@ -78,10 +78,18 @@ func TestNewIAMDomainName(t *testing.T) {
|
|||||||
},
|
},
|
||||||
result: "my-super-long-organization-name-with-many-many-many-characters.localhost",
|
result: "my-super-long-organization-name-with-many-many-many-characters.localhost",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "string full with invalid characters returns empty",
|
||||||
|
args: args{
|
||||||
|
orgName: "*¿=@^[])",
|
||||||
|
iamDomain: "localhost",
|
||||||
|
},
|
||||||
|
result: "",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
domain := NewIAMDomainName(tt.args.orgName, tt.args.iamDomain)
|
domain, _ := NewIAMDomainName(tt.args.orgName, tt.args.iamDomain)
|
||||||
if tt.result != domain {
|
if tt.result != domain {
|
||||||
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result, domain)
|
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result, domain)
|
||||||
}
|
}
|
||||||
|
@@ -46,5 +46,6 @@ func (o *Org) GetPrimaryDomain() *OrgDomain {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (o *Org) AddIAMDomain(iamDomain string) {
|
func (o *Org) AddIAMDomain(iamDomain string) {
|
||||||
o.Domains = append(o.Domains, &OrgDomain{Domain: domain.NewIAMDomainName(o.Name, iamDomain), Verified: true, Primary: true})
|
orgDomain, _ := domain.NewIAMDomainName(o.Name, iamDomain)
|
||||||
|
o.Domains = append(o.Domains, &OrgDomain{Domain: orgDomain, Verified: true, Primary: true})
|
||||||
}
|
}
|
||||||
|
@@ -204,6 +204,7 @@ Errors:
|
|||||||
Domain:
|
Domain:
|
||||||
AlreadyExists: Домейнът вече съществува
|
AlreadyExists: Домейнът вече съществува
|
||||||
InvalidCharacter: "Само буквено-цифрови знаци, . "
|
InvalidCharacter: "Само буквено-цифрови знаци, . "
|
||||||
|
EmptyString: Невалидни нецифрови и азбучни знаци бяха заменени с празни интервали и полученият домейн е празен низ
|
||||||
IDP:
|
IDP:
|
||||||
InvalidSearchQuery: Невалидна заявка за търсене
|
InvalidSearchQuery: Невалидна заявка за търсене
|
||||||
ClientIDMissing: Липсва ClientID
|
ClientIDMissing: Липсва ClientID
|
||||||
|
@@ -202,6 +202,7 @@ Errors:
|
|||||||
Domain:
|
Domain:
|
||||||
AlreadyExists: Domäne existiert bereits
|
AlreadyExists: Domäne existiert bereits
|
||||||
InvalidCharacter: Nur alphanumerische Zeichen, . und - sind für eine Domäne erlaubt
|
InvalidCharacter: Nur alphanumerische Zeichen, . und - sind für eine Domäne erlaubt
|
||||||
|
EmptyString: Ungültige nicht numerische und alphabetische Zeichen wurden durch Leerzeichen ersetzt und die resultierende Domäne ist eine leere Zeichenfolge
|
||||||
IDP:
|
IDP:
|
||||||
InvalidSearchQuery: Ungültiger Suchparameter
|
InvalidSearchQuery: Ungültiger Suchparameter
|
||||||
ClientIDMissing: ClientID fehlt
|
ClientIDMissing: ClientID fehlt
|
||||||
|
@@ -202,6 +202,7 @@ Errors:
|
|||||||
Domain:
|
Domain:
|
||||||
AlreadyExists: Domain already exists
|
AlreadyExists: Domain already exists
|
||||||
InvalidCharacter: Only alphanumeric characters, . and - are allowed for a domain
|
InvalidCharacter: Only alphanumeric characters, . and - are allowed for a domain
|
||||||
|
EmptyString: Invalid non numeric and alphabetical characters were replaced with empty spaces and resulting domain is an empty string
|
||||||
IDP:
|
IDP:
|
||||||
InvalidSearchQuery: Invalid search query
|
InvalidSearchQuery: Invalid search query
|
||||||
ClientIDMissing: ClientID missing
|
ClientIDMissing: ClientID missing
|
||||||
|
@@ -202,6 +202,7 @@ Errors:
|
|||||||
Domain:
|
Domain:
|
||||||
AlreadyExists: El dominio ya existe
|
AlreadyExists: El dominio ya existe
|
||||||
InvalidCharacter: Solo caracteres alfanuméricos, . y - se permiten para un dominio
|
InvalidCharacter: Solo caracteres alfanuméricos, . y - se permiten para un dominio
|
||||||
|
EmptyString: Los caracteres alfabéticos y no numéricos no válidos se reemplazaron con espacios vacíos y el dominio resultante es una cadena vacía
|
||||||
IDP:
|
IDP:
|
||||||
InvalidSearchQuery: Consulta de búsqueda no válida
|
InvalidSearchQuery: Consulta de búsqueda no válida
|
||||||
ClientIDMissing: Falta ClientID
|
ClientIDMissing: Falta ClientID
|
||||||
|
@@ -202,6 +202,7 @@ Errors:
|
|||||||
Domain:
|
Domain:
|
||||||
AlreadyExists: Le domaine existe déjà
|
AlreadyExists: Le domaine existe déjà
|
||||||
InvalidCharacter: Seuls les caractères alphanumériques, . et - sont autorisés pour un domaine
|
InvalidCharacter: Seuls les caractères alphanumériques, . et - sont autorisés pour un domaine
|
||||||
|
EmptyString: Les caractères non numériques et alphabétiques non valides ont été remplacés par des espaces vides et le domaine résultant est une chaîne vide
|
||||||
IDP:
|
IDP:
|
||||||
InvalidSearchQuery: Paramètre de recherche non valide
|
InvalidSearchQuery: Paramètre de recherche non valide
|
||||||
ClientIDMissing: ID client manquant
|
ClientIDMissing: ID client manquant
|
||||||
|
@@ -201,6 +201,8 @@ Errors:
|
|||||||
IdpIsNotOIDC: La configurazione IDP non è di tipo oidc
|
IdpIsNotOIDC: La configurazione IDP non è di tipo oidc
|
||||||
Domain:
|
Domain:
|
||||||
AlreadyExists: Il dominio già esistente
|
AlreadyExists: Il dominio già esistente
|
||||||
|
InvalidCharacter: Solo caratteri alfanumerici, . e - sono consentiti per un dominio
|
||||||
|
EmptyString: I caratteri non numerici e alfabetici non validi sono stati sostituiti con spazi vuoti e il dominio risultante è una stringa vuota
|
||||||
IDP:
|
IDP:
|
||||||
InvalidSearchQuery: Parametro di ricerca non valido
|
InvalidSearchQuery: Parametro di ricerca non valido
|
||||||
InvalidCharacter: Per un dominio sono ammessi solo caratteri alfanumerici, . e -
|
InvalidCharacter: Per un dominio sono ammessi solo caratteri alfanumerici, . e -
|
||||||
|
@@ -194,6 +194,7 @@ Errors:
|
|||||||
Domain:
|
Domain:
|
||||||
AlreadyExists: ドメインはすでに存在します
|
AlreadyExists: ドメインはすでに存在します
|
||||||
InvalidCharacter: ドメインは英数字、'.'、'-'のみ使用可能です。
|
InvalidCharacter: ドメインは英数字、'.'、'-'のみ使用可能です。
|
||||||
|
EmptyString: 無効な数字およびアルファベット以外の文字は空のスペースに置き換えられ、結果のドメインは空の文字列になります
|
||||||
IDP:
|
IDP:
|
||||||
InvalidSearchQuery: 無効な検索クエリです
|
InvalidSearchQuery: 無効な検索クエリです
|
||||||
ClientIDMissing: クライアントIDがありません
|
ClientIDMissing: クライアントIDがありません
|
||||||
|
@@ -202,6 +202,7 @@ Errors:
|
|||||||
Domain:
|
Domain:
|
||||||
AlreadyExists: Доменот веќе постои
|
AlreadyExists: Доменот веќе постои
|
||||||
InvalidCharacter: Дозволени се само алфанумерички знаци, . и - се дозволени за домен
|
InvalidCharacter: Дозволени се само алфанумерички знаци, . и - се дозволени за домен
|
||||||
|
EmptyString: Неважечките ненумерички и азбучни знаци се заменети со празни места и добиениот домен е празна низа
|
||||||
IDP:
|
IDP:
|
||||||
InvalidSearchQuery: Невалидно пребарување
|
InvalidSearchQuery: Невалидно пребарување
|
||||||
ClientID Missing: ClientID недостасува
|
ClientID Missing: ClientID недостасува
|
||||||
|
@@ -202,6 +202,7 @@ Errors:
|
|||||||
Domain:
|
Domain:
|
||||||
AlreadyExists: Domena już istnieje
|
AlreadyExists: Domena już istnieje
|
||||||
InvalidCharacter: Tylko znaki alfanumeryczne, . i - są dozwolone dla domeny
|
InvalidCharacter: Tylko znaki alfanumeryczne, . i - są dozwolone dla domeny
|
||||||
|
EmptyString: Nieprawidłowe znaki inne niż numeryczne i alfabetyczne zostały zastąpione pustymi spacjami, a wynikowa domena jest pustym ciągiem znaków
|
||||||
IDP:
|
IDP:
|
||||||
InvalidSearchQuery: Nieprawidłowe zapytanie wyszukiwania
|
InvalidSearchQuery: Nieprawidłowe zapytanie wyszukiwania
|
||||||
ClientIDMissing: Brak ClientID
|
ClientIDMissing: Brak ClientID
|
||||||
|
@@ -200,6 +200,7 @@ Errors:
|
|||||||
Domain:
|
Domain:
|
||||||
AlreadyExists: Domínio já existe
|
AlreadyExists: Domínio já existe
|
||||||
InvalidCharacter: Apenas caracteres alfanuméricos, . e - são permitidos para um domínio
|
InvalidCharacter: Apenas caracteres alfanuméricos, . e - são permitidos para um domínio
|
||||||
|
EmptyString: Caracteres não numéricos e alfabéticos inválidos foram substituídos por espaços vazios e o domínio resultante é uma string vazia
|
||||||
IDP:
|
IDP:
|
||||||
InvalidSearchQuery: Consulta de pesquisa inválida
|
InvalidSearchQuery: Consulta de pesquisa inválida
|
||||||
ClientIDMissing: ClientID ausente
|
ClientIDMissing: ClientID ausente
|
||||||
|
@@ -202,6 +202,7 @@ Errors:
|
|||||||
Domain:
|
Domain:
|
||||||
AlreadyExists: 域名已存在
|
AlreadyExists: 域名已存在
|
||||||
InvalidCharacter: 只有字母数字字符,.和 - 允许用于域名中
|
InvalidCharacter: 只有字母数字字符,.和 - 允许用于域名中
|
||||||
|
EmptyString: 无效的非数字和字母字符被替换为空格,结果域是空字符串
|
||||||
IDP:
|
IDP:
|
||||||
InvalidSearchQuery: 无效的搜索查询
|
InvalidSearchQuery: 无效的搜索查询
|
||||||
ClientIDMissing: 客户端 ID 丢失
|
ClientIDMissing: 客户端 ID 丢失
|
||||||
|
Reference in New Issue
Block a user