Merge branch 'main' into next

This commit is contained in:
Livio Spring 2023-11-10 11:09:25 +01:00
commit af24208b38
No known key found for this signature in database
GPG Key ID: 26BB1C2FA5952CF0
155 changed files with 11226 additions and 819 deletions

View File

@ -39,16 +39,15 @@ Telemetry:
# Port ZITADEL will listen on
Port: 8080 # ZITADEL_PORT
# Port ZITADEL is exposed on, it can differ from port e.g. if you proxy the traffic
# !!! Changing this after the initial setup breaks your system !!!
# ExternalPort is the port on which end users access ZITADEL.
# It can differ from Port e.g. if a reverse proxy forwards the traffic to ZITADEL
# Read more about external access: https://zitadel.com/docs/self-hosting/manage/custom-domain
ExternalPort: 8080 # ZITADEL_EXTERNALPORT
# Domain/hostname ZITADEL is exposed externally
# !!! Changing this after the initial setup breaks your system !!!
# ExternalPort is the domain on which end users access ZITADEL.
# Read more about external access: https://zitadel.com/docs/self-hosting/manage/custom-domain
ExternalDomain: localhost # ZITADEL_EXTERNALDOMAIN
# specifies if ZITADEL is exposed externally through TLS
# this must be set to true even if TLS is not enabled on ZITADEL itself
# but TLS traffic is terminated on a reverse proxy
# !!! Changing this after the initial setup breaks your system !!!
# ExternalSecure specifies if ZITADEL is exposed externally using HTTPS or HTTP.
# Read more about external access: https://zitadel.com/docs/self-hosting/manage/custom-domain
ExternalSecure: true # ZITADEL_EXTERNALSECURE
TLS:
# If enabled, ZITADEL will serve all traffic over TLS (HTTPS and gRPC)

View File

@ -9,12 +9,14 @@ import (
internal_authz "github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/config/hook"
"github.com/zitadel/zitadel/internal/config/network"
"github.com/zitadel/zitadel/internal/domain"
)
type Config struct {
Log *logging.Config
Port uint16
TLS network.TLS
}
func MustNewConfig(v *viper.Viper) *Config {

View File

@ -1,6 +1,7 @@
package ready
import (
"crypto/tls"
"net"
"net/http"
"os"
@ -26,7 +27,13 @@ func New() *cobra.Command {
}
func ready(config *Config) bool {
res, err := http.Get("http://" + net.JoinHostPort("localhost", strconv.Itoa(int(config.Port))) + "/debug/ready")
scheme := "https"
if !config.TLS.Enabled {
scheme = "http"
}
// Checking the TLS cert is not in the scope of the readiness check
httpClient := http.Client{Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}}
res, err := httpClient.Get(scheme + "://" + net.JoinHostPort("localhost", strconv.Itoa(int(config.Port))) + "/debug/ready")
if err != nil {
logging.WithError(err).Warn("ready check failed")
return false

View File

@ -367,16 +367,16 @@ func startAPIs(
return fmt.Errorf("error starting admin repo: %w", err)
}
if err := apis.RegisterServer(ctx, system.CreateServer(commands, queries, config.Database.DatabaseName(), config.DefaultInstance, config.ExternalDomain)); err != nil {
if err := apis.RegisterServer(ctx, system.CreateServer(commands, queries, config.Database.DatabaseName(), config.DefaultInstance, config.ExternalDomain), tlsConfig); err != nil {
return err
}
if err := apis.RegisterServer(ctx, admin.CreateServer(config.Database.DatabaseName(), commands, queries, config.SystemDefaults, config.ExternalSecure, keys.User, config.AuditLogRetention)); err != nil {
if err := apis.RegisterServer(ctx, admin.CreateServer(config.Database.DatabaseName(), commands, queries, config.SystemDefaults, config.ExternalSecure, keys.User, config.AuditLogRetention), tlsConfig); err != nil {
return err
}
if err := apis.RegisterServer(ctx, management.CreateServer(commands, queries, config.SystemDefaults, keys.User, config.ExternalSecure)); err != nil {
if err := apis.RegisterServer(ctx, management.CreateServer(commands, queries, config.SystemDefaults, keys.User, config.ExternalSecure), tlsConfig); err != nil {
return err
}
if err := apis.RegisterServer(ctx, auth.CreateServer(commands, queries, authRepo, config.SystemDefaults, keys.User, config.ExternalSecure)); err != nil {
if err := apis.RegisterServer(ctx, auth.CreateServer(commands, queries, authRepo, config.SystemDefaults, keys.User, config.ExternalSecure), tlsConfig); err != nil {
return err
}
if err := apis.RegisterService(ctx, user_v2.CreateServer(commands, queries, keys.User, keys.IDPConfig, idp.CallbackURL(config.ExternalSecure), idp.SAMLRootURL(config.ExternalSecure))); err != nil {

View File

@ -25,6 +25,9 @@
"@angular/router": "^16.2.5",
"@angular/service-worker": "^16.2.5",
"@ctrl/ngx-codemirror": "^6.1.0",
"@fortawesome/angular-fontawesome": "^0.13.0",
"@fortawesome/fontawesome-svg-core": "^6.4.2",
"@fortawesome/free-brands-svg-icons": "^6.4.2",
"@grpc/grpc-js": "^1.9.3",
"@ngx-translate/core": "^14.0.0",
"angular-oauth2-oidc": "^15.0.1",

View File

@ -2,6 +2,7 @@ import { CommonModule, registerLocaleData } from '@angular/common';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import localeBg from '@angular/common/locales/bg';
import localeDe from '@angular/common/locales/de';
import localeCs from '@angular/common/locales/cs';
import localeEn from '@angular/common/locales/en';
import localeEs from '@angular/common/locales/es';
import localeFr from '@angular/common/locales/fr';
@ -11,6 +12,7 @@ import localeMk from '@angular/common/locales/mk';
import localePl from '@angular/common/locales/pl';
import localePt from '@angular/common/locales/pt';
import localeZh from '@angular/common/locales/zh';
import localeRu from '@angular/common/locales/ru';
import { APP_INITIALIZER, NgModule } from '@angular/core';
import { MatNativeDateModule } from '@angular/material/core';
import { MatDialogModule } from '@angular/material/dialog';
@ -89,6 +91,10 @@ registerLocaleData(localePt);
i18nIsoCountries.registerLocale(require('i18n-iso-countries/langs/pt.json'));
registerLocaleData(localeMk);
i18nIsoCountries.registerLocale(require('i18n-iso-countries/langs/mk.json'));
registerLocaleData(localeRu);
i18nIsoCountries.registerLocale(require('i18n-iso-countries/langs/ru.json'));
registerLocaleData(localeCs);
i18nIsoCountries.registerLocale(require('i18n-iso-countries/langs/cs.json'));
export class WebpackTranslateLoader implements TranslateLoader {
getTranslation(lang: string): Observable<any> {

View File

@ -17,7 +17,7 @@
<i class="text-3xl lab la-github"></i>
</a>
<a target="_blank" rel="noreferrer" href="https://twitter.com/zitadel">
<i class="text-3xl lab la-twitter"></i>
<fa-icon [icon]="faXTwitter" size="xl"></fa-icon>
</a>
<a target="_blank" rel="noreferrer" href="https://www.linkedin.com/company/zitadel/">
<i class="text-3xl lab la-linkedin"></i>

View File

@ -1,6 +1,7 @@
import { Component, OnInit } from '@angular/core';
import { PrivacyPolicy } from 'src/app/proto/generated/zitadel/policy_pb';
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
import { faXTwitter } from '@fortawesome/free-brands-svg-icons';
@Component({
selector: 'cnsl-footer',
@ -9,6 +10,7 @@ import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
})
export class FooterComponent implements OnInit {
public policy?: PrivacyPolicy.AsObject;
public faXTwitter = faXTwitter;
constructor(public authService: GrpcAuthService) {}
ngOnInit(): void {

View File

@ -4,10 +4,11 @@ import { TranslateModule } from '@ngx-translate/core';
import { ThemeSettingModule } from '../theme-setting/theme-setting.module';
import { FooterComponent } from './footer.component';
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
@NgModule({
declarations: [FooterComponent],
imports: [CommonModule, TranslateModule, ThemeSettingModule],
imports: [CommonModule, TranslateModule, ThemeSettingModule, FontAwesomeModule],
exports: [FooterComponent],
})
export class FooterModule {}

View File

@ -85,6 +85,10 @@
<img class="idp-logo apple light" src="./assets/images/idp/apple.svg" alt="apple" />
Apple
</div>
<div class="idp-table-provider-type" *ngSwitchCase="ProviderType.PROVIDER_TYPE_SAML">
<img class="idp-logo" src="./assets/images/idp/saml-icon.svg" alt="saml" />
SAML SP
</div>
<div class="idp-table-provider-type" *ngSwitchDefault>coming soon</div>
</div>
</td>

View File

@ -263,6 +263,8 @@ export class IdpTableComponent implements OnInit, OnDestroy {
return [row.owner === IDPOwnerType.IDP_OWNER_TYPE_SYSTEM ? '/instance' : '/org', 'provider', 'github', row.id];
case ProviderType.PROVIDER_TYPE_APPLE:
return [row.owner === IDPOwnerType.IDP_OWNER_TYPE_SYSTEM ? '/instance' : '/org', 'provider', 'apple', row.id];
case ProviderType.PROVIDER_TYPE_SAML:
return [row.owner === IDPOwnerType.IDP_OWNER_TYPE_SYSTEM ? '/instance' : '/org', 'provider', 'saml', row.id];
}
}
}

View File

@ -196,4 +196,20 @@
<span class="title">Active Directory / LDAP</span>
</div>
</a>
<a
class="item card"
[routerLink]="
serviceType === PolicyComponentServiceType.ADMIN
? ['/instance', 'provider', 'saml', 'create']
: serviceType === PolicyComponentServiceType.MGMT
? ['/org', 'provider', 'saml', 'create']
: []
"
>
<img class="idp-logo" src="./assets/images/idp/saml-icon.svg" alt="oauth" />
<div class="text-container">
<span class="title">SAML SP</span>
</div>
</a>
</div>

View File

@ -146,7 +146,6 @@ export class ProviderJWTComponent {
req.setJwtEndpoint(this.jwtEndpoint?.value);
req.setKeysEndpoint(this.keysEndpoint?.value);
req.setProviderOptions(this.options);
this.loading = true;
this.service
.addJWTProvider(req)

View File

@ -0,0 +1,68 @@
<cnsl-create-layout
title="{{ id ? ('IDP.DETAIL.TITLE' | translate) : ('IDP.CREATE.TITLE' | translate) }}"
(closed)="close()"
>
<div class="identity-provider-create-content">
<div class="title-row">
<img class="idp-logo" src="./assets/images/idp/saml-icon.svg" alt="saml" />
<h1>{{ 'IDP.CREATE.SAML.TITLE' | translate }}</h1>
<mat-spinner diameter="25" *ngIf="loading" color="primary"></mat-spinner>
</div>
<p class="identity-provider-desc cnsl-secondary-text">
{{ !provider ? ('IDP.CREATE.SAML.DESCRIPTION' | translate) : ('IDP.DETAIL.DESCRIPTION' | translate) }}
</p>
<form [formGroup]="form" (ngSubmit)="submitForm()">
<div class="identity-provider-content">
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.NAME' | translate }}</cnsl-label>
<input cnslInput formControlName="name" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.SAML.METADATAXML' | translate }}</cnsl-label>
<input cnslInput formControlName="metadataXml" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.SAML.METADATAURL' | translate }}</cnsl-label>
<input cnslInput formControlName="metadataUrl" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.SAML.BINDING' | translate }}</cnsl-label>
<mat-select formControlName="binding">
<mat-option *ngFor="let binding of bindingValues" [value]="binding">{{ binding }}</mat-option>
</mat-select>
</cnsl-form-field>
<mat-checkbox formControlName="withSignedRequest">{{ 'IDP.SAML.SIGNEDREQUEST' | translate }}</mat-checkbox>
</div>
<div class="identity-provider-optional-h-wrapper">
<h2>{{ 'IDP.OPTIONAL' | translate }}</h2>
<button (click)="showOptional = !showOptional" type="button" mat-icon-button>
<mat-icon *ngIf="showOptional">keyboard_arrow_up</mat-icon
><mat-icon *ngIf="!showOptional">keyboard_arrow_down</mat-icon>
</button>
</div>
<div *ngIf="showOptional">
<cnsl-provider-options
[initialOptions]="provider?.config?.options"
(optionsChanged)="options = $event"
></cnsl-provider-options>
</div>
<div class="identity-provider-create-actions">
<button
color="primary"
mat-raised-button
class="continue-button"
[disabled]="form.invalid || form.disabled"
type="submit"
>
<span *ngIf="id">{{ 'ACTIONS.SAVE' | translate }}</span>
<span *ngIf="!id">{{ 'ACTIONS.CREATE' | translate }}</span>
</button>
</div>
</form>
</div>
</cnsl-create-layout>

View File

@ -0,0 +1,21 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ProviderSamlSpComponent } from './provider-saml-sp.component';
describe('ProviderSamlSpComponent', () => {
let component: ProviderSamlSpComponent;
let fixture: ComponentFixture<ProviderSamlSpComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [ProviderSamlSpComponent],
});
fixture = TestBed.createComponent(ProviderSamlSpComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,224 @@
import { Component, Injector, Type } from '@angular/core';
import { Location } from '@angular/common';
import { Options, Provider } from '../../../proto/generated/zitadel/idp_pb';
import { AbstractControl, FormGroup, UntypedFormControl, UntypedFormGroup } from '@angular/forms';
import { PolicyComponentServiceType } from '../../policies/policy-component-types.enum';
import { ManagementService } from '../../../services/mgmt.service';
import { AdminService } from '../../../services/admin.service';
import { ToastService } from '../../../services/toast.service';
import { GrpcAuthService } from '../../../services/grpc-auth.service';
import { take } from 'rxjs';
import { ActivatedRoute } from '@angular/router';
import { Breadcrumb, BreadcrumbService, BreadcrumbType } from '../../../services/breadcrumb.service';
import { requiredValidator } from '../../form-field/validators/validators';
import {
AddSAMLProviderRequest as AdminAddSAMLProviderRequest,
GetProviderByIDRequest as AdminGetProviderByIDRequest,
UpdateSAMLProviderRequest as AdminUpdateSAMLProviderRequest,
} from 'src/app/proto/generated/zitadel/admin_pb';
import {
AddSAMLProviderRequest as MgmtAddSAMLProviderRequest,
GetProviderByIDRequest as MgmtGetProviderByIDRequest,
UpdateSAMLProviderRequest as MgmtUpdateSAMLProviderRequest,
} from 'src/app/proto/generated/zitadel/management_pb';
import * as zitadel_idp_pb from '../../../proto/generated/zitadel/idp_pb';
@Component({
selector: 'cnsl-provider-saml-sp',
templateUrl: './provider-saml-sp.component.html',
styleUrls: ['./provider-saml-sp.component.scss'],
})
export class ProviderSamlSpComponent {
public id: string | null = '';
public loading: boolean = false;
public provider?: Provider.AsObject;
public form!: FormGroup;
public showOptional: boolean = false;
public options: Options = new Options().setIsCreationAllowed(true).setIsLinkingAllowed(true);
public serviceType: PolicyComponentServiceType = PolicyComponentServiceType.MGMT;
private service!: ManagementService | AdminService;
bindingValues: string[] = Object.keys(zitadel_idp_pb.SAMLBinding);
constructor(
private _location: Location,
private toast: ToastService,
private authService: GrpcAuthService,
private route: ActivatedRoute,
private injector: Injector,
private breadcrumbService: BreadcrumbService,
) {
this._buildBreadcrumbs();
this._initializeForm();
this._checkFormPermissions();
}
private _initializeForm(): void {
this.form = new UntypedFormGroup({
name: new UntypedFormControl('', [requiredValidator]),
metadataXml: new UntypedFormControl('', [requiredValidator]),
metadataUrl: new UntypedFormControl('', [requiredValidator]),
binding: new UntypedFormControl(this.bindingValues[0], [requiredValidator]),
withSignedRequest: new UntypedFormControl(true, [requiredValidator]),
});
}
private _checkFormPermissions(): void {
this.authService
.isAllowed(
this.serviceType === PolicyComponentServiceType.ADMIN
? ['iam.idp.write']
: this.serviceType === PolicyComponentServiceType.MGMT
? ['org.idp.write']
: [],
)
.pipe(take(1))
.subscribe((allowed) => {
if (allowed) {
this.form.enable();
} else {
this.form.disable();
}
});
}
private _buildBreadcrumbs(): void {
this.route.data.pipe(take(1)).subscribe((data) => {
this.serviceType = data['serviceType'];
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
this.service = this.injector.get(ManagementService as Type<ManagementService>);
const bread: Breadcrumb = {
type: BreadcrumbType.ORG,
routerLink: ['/org'],
};
this.breadcrumbService.setBreadcrumb([bread]);
break;
case PolicyComponentServiceType.ADMIN:
this.service = this.injector.get(AdminService as Type<AdminService>);
const iamBread = new Breadcrumb({
type: BreadcrumbType.ORG,
name: 'Instance',
routerLink: ['/instance'],
});
this.breadcrumbService.setBreadcrumb([iamBread]);
break;
}
this.id = this.route.snapshot.paramMap.get('id');
if (this.id) {
this.getData(this.id);
}
});
}
public updateSAMLProvider(): void {
if (this.provider) {
const req =
this.serviceType === PolicyComponentServiceType.MGMT
? new MgmtUpdateSAMLProviderRequest()
: new AdminUpdateSAMLProviderRequest();
req.setId(this.provider.id);
req.setName(this.name?.value);
req.setMetadataUrl(this.metadataUrl?.value);
req.setMetadataXml(this.metadataXml?.value);
// @ts-ignore
req.setBinding(zitadel_idp_pb.SAMLBinding[`${this.biding?.value}`]);
req.setProviderOptions(this.options);
this.loading = true;
this.service
.updateSAMLProvider(req)
.then((idp) => {
setTimeout(() => {
this.loading = false;
this.close();
}, 2000);
})
.catch((error) => {
this.toast.showError(error);
this.loading = false;
});
}
}
public addSAMLProvider(): void {
const req =
this.serviceType === PolicyComponentServiceType.MGMT
? new MgmtAddSAMLProviderRequest()
: new AdminAddSAMLProviderRequest();
req.setName(this.name?.value);
req.setMetadataUrl(this.metadataUrl?.value);
req.setMetadataXml(this.metadataXml?.value);
req.setProviderOptions(this.options);
// @ts-ignore
req.setBinding(zitadel_idp_pb.SAMLBinding[`${this.biding?.value}`]);
req.setWithSignedRequest(this.withSignedRequest?.value);
this.loading = true;
this.service
.addSAMLProvider(req)
.then((idp) => {
setTimeout(() => {
this.loading = false;
this.close();
}, 2000);
})
.catch((error) => {
this.toast.showError(error);
this.loading = false;
});
}
public submitForm(): void {
this.provider ? this.updateSAMLProvider() : this.addSAMLProvider();
}
private getData(id: string): void {
const req =
this.serviceType === PolicyComponentServiceType.ADMIN
? new AdminGetProviderByIDRequest()
: new MgmtGetProviderByIDRequest();
req.setId(id);
this.service
.getProviderByID(req)
.then((resp) => {
this.provider = resp.idp;
this.loading = false;
if (this.provider?.config?.saml) {
this.form.patchValue(this.provider.config.saml);
this.name?.setValue(this.provider.name);
}
})
.catch((error) => {
this.toast.showError(error);
this.loading = false;
});
}
close(): void {
this._location.back();
}
private get name(): AbstractControl | null {
return this.form.get('name');
}
private get metadataXml(): AbstractControl | null {
return this.form.get('metadataXml');
}
private get metadataUrl(): AbstractControl | null {
return this.form.get('metadataUrl');
}
private get biding(): AbstractControl | null {
return this.form.get('binding');
}
private get withSignedRequest(): AbstractControl | null {
return this.form.get('withSignedRequest');
}
}

View File

@ -13,6 +13,7 @@ import { ProviderJWTComponent } from './provider-jwt/provider-jwt.component';
import { ProviderLDAPComponent } from './provider-ldap/provider-ldap.component';
import { ProviderOAuthComponent } from './provider-oauth/provider-oauth.component';
import { ProviderOIDCComponent } from './provider-oidc/provider-oidc.component';
import { ProviderSamlSpComponent } from './provider-saml-sp/provider-saml-sp.component';
const typeMap = {
[ProviderType.PROVIDER_TYPE_AZURE_AD]: { path: 'azure-ad', component: ProviderAzureADComponent },
@ -29,6 +30,7 @@ const typeMap = {
[ProviderType.PROVIDER_TYPE_OIDC]: { path: 'oidc', component: ProviderOIDCComponent },
[ProviderType.PROVIDER_TYPE_LDAP]: { path: 'ldap', component: ProviderLDAPComponent },
[ProviderType.PROVIDER_TYPE_APPLE]: { path: 'apple', component: ProviderAppleComponent },
[ProviderType.PROVIDER_TYPE_SAML]: { path: 'saml', component: ProviderSamlSpComponent },
};
const routes: Routes = Object.entries(typeMap).map(([key, value]) => {

View File

@ -29,6 +29,7 @@ import { ProviderLDAPComponent } from './provider-ldap/provider-ldap.component';
import { ProviderOAuthComponent } from './provider-oauth/provider-oauth.component';
import { ProviderOIDCComponent } from './provider-oidc/provider-oidc.component';
import { ProvidersRoutingModule } from './providers-routing.module';
import { ProviderSamlSpComponent } from './provider-saml-sp/provider-saml-sp.component';
@NgModule({
declarations: [
@ -45,6 +46,7 @@ import { ProvidersRoutingModule } from './providers-routing.module';
ProviderOAuthComponent,
ProviderLDAPComponent,
ProviderAppleComponent,
ProviderSamlSpComponent,
],
imports: [
ProvidersRoutingModule,

View File

@ -40,6 +40,8 @@ import {
AddNotificationPolicyResponse,
AddOIDCSettingsRequest,
AddOIDCSettingsResponse,
AddSAMLProviderRequest,
AddSAMLProviderResponse,
AddSecondFactorToLoginPolicyRequest,
AddSecondFactorToLoginPolicyResponse,
AddSMSProviderTwilioRequest,
@ -262,6 +264,8 @@ import {
UpdatePasswordComplexityPolicyResponse,
UpdatePrivacyPolicyRequest,
UpdatePrivacyPolicyResponse,
UpdateSAMLProviderRequest,
UpdateSAMLProviderResponse,
UpdateSecretGeneratorRequest,
UpdateSecretGeneratorResponse,
UpdateSMSProviderTwilioRequest,
@ -1170,6 +1174,14 @@ export class AdminService {
return this.grpcService.admin.updateJWTProvider(req, null).then((resp) => resp.toObject());
}
public addSAMLProvider(req: AddSAMLProviderRequest): Promise<AddSAMLProviderResponse.AsObject> {
return this.grpcService.admin.addSAMLProvider(req, null).then((resp) => resp.toObject());
}
public updateSAMLProvider(req: UpdateSAMLProviderRequest): Promise<UpdateSAMLProviderResponse.AsObject> {
return this.grpcService.admin.updateSAMLProvider(req, null).then((resp) => resp.toObject());
}
public addGitHubEnterpriseServerProvider(
req: AddGitHubEnterpriseServerProviderRequest,
): Promise<AddGitHubEnterpriseServerProviderResponse.AsObject> {

View File

@ -83,6 +83,8 @@ import {
AddProjectRoleResponse,
AddSAMLAppRequest,
AddSAMLAppResponse,
AddSAMLProviderRequest,
AddSAMLProviderResponse,
AddSecondFactorToLoginPolicyRequest,
AddSecondFactorToLoginPolicyResponse,
AddUserGrantRequest,
@ -511,6 +513,8 @@ import {
UpdateProjectRoleResponse,
UpdateSAMLAppConfigRequest,
UpdateSAMLAppConfigResponse,
UpdateSAMLProviderRequest,
UpdateSAMLProviderResponse,
UpdateUserGrantRequest,
UpdateUserGrantResponse,
UpdateUserNameRequest,
@ -1037,6 +1041,14 @@ export class ManagementService {
return this.grpcService.mgmt.updateJWTProvider(req, null).then((resp) => resp.toObject());
}
public addSAMLProvider(req: AddSAMLProviderRequest): Promise<AddSAMLProviderResponse.AsObject> {
return this.grpcService.mgmt.addSAMLProvider(req, null).then((resp) => resp.toObject());
}
public updateSAMLProvider(req: UpdateSAMLProviderRequest): Promise<UpdateSAMLProviderResponse.AsObject> {
return this.grpcService.mgmt.updateSAMLProvider(req, null).then((resp) => resp.toObject());
}
public addGitHubEnterpriseServerProvider(
req: AddGitHubEnterpriseServerProviderRequest,
): Promise<AddGitHubEnterpriseServerProviderResponse.AsObject> {

View File

@ -1,3 +1,3 @@
export const supportedLanguages = ['de', 'en', 'es', 'fr', 'it', 'ja', 'pl', 'zh', 'bg', 'pt', 'mk'];
export const supportedLanguagesRegexp: RegExp = /de|en|es|fr|it|ja|pl|zh|bg|pt|mk/;
export const supportedLanguages = ['de', 'en', 'es', 'fr', 'it', 'ja', 'pl', 'zh', 'bg', 'pt', 'mk', 'cs', 'ru'];
export const supportedLanguagesRegexp: RegExp = /de|en|es|fr|it|ja|pl|zh|bg|pt|mk|cs|ru/;
export const fallbackLanguage: string = 'en';

View File

@ -1053,7 +1053,9 @@
"zh": "简体中文",
"bg": "Български",
"pt": "Portuguese",
"mk": "Македонски"
"mk": "Македонски",
"cs": "Čeština",
"ru": "Русский"
},
"SMTP": {
"TITLE": "SMTP настройки",
@ -1264,7 +1266,9 @@
"zh": "简体中文",
"bg": "Български",
"pt": "Portuguese",
"mk": "Македонски"
"mk": "Македонски",
"cs": "Čeština",
"ru": "Русский"
},
"KEYS": {
"emailVerificationDoneText": "Проверката на имейл е извършена",
@ -2149,7 +2153,9 @@
"zh": "简体中文",
"bg": "Български",
"pt": "португалски",
"mk": "Македонски"
"mk": "Македонски",
"cs": "Čeština",
"ru": "Русский"
},
"MEMBER": {
"ADD": "Добавяне на мениджър",

File diff suppressed because it is too large Load Diff

View File

@ -1059,7 +1059,9 @@
"zh": "简体中文",
"bg": "Български",
"pt": "Portuguese",
"mk": "Македонски"
"mk": "Македонски",
"cs": "Čeština",
"ru": "Русский"
},
"SMTP": {
"TITLE": "SMTP Einstellungen",
@ -1270,7 +1272,9 @@
"zh": "简体中文",
"bg": "Български",
"pt": "Portuguese",
"mk": "Македонски"
"mk": "Македонски",
"cs": "Čeština",
"ru": "Русский"
},
"KEYS": {
"emailVerificationDoneText": "Email Verification erfolgreich",
@ -2158,7 +2162,9 @@
"zh": "简体中文",
"bg": "Български",
"pt": "Portuguese",
"mk": "Македонски"
"mk": "Македонски",
"cs": "Čeština",
"ru": "Русский"
},
"MEMBER": {
"ADD": "Manager hinzufügen",

View File

@ -1060,7 +1060,9 @@
"zh": "简体中文",
"bg": "Български",
"pt": "Portuguese",
"mk": "Македонски"
"mk": "Македонски",
"cs": "Čeština",
"ru": "Русский"
},
"SMTP": {
"TITLE": "SMTP Settings",
@ -1271,7 +1273,9 @@
"zh": "简体中文",
"bg": "Български",
"pt": "Portuguese",
"mk": "Македонски"
"mk": "Македонски",
"cs": "Čeština",
"ru": "Русский"
},
"KEYS": {
"emailVerificationDoneText": "Email verification done",
@ -1726,6 +1730,10 @@
"APPLE": {
"TITLE": "Sign in with Apple",
"DESCRIPTION": "Enter the credentials for your Apple Provider"
},
"SAML": {
"TITLE": "Sign in with Saml SP",
"DESCRIPTION": "Enter the credentials for your SAML Provider"
}
},
"DETAIL": {
@ -1847,6 +1855,12 @@
"UPLOADPRIVATEKEY": "Upload Private Key",
"KEYMAXSIZEEXCEEDED": "Maximum size of 5kB exceeded."
},
"SAML": {
"METADATAXML": "Metadata Xml",
"METADATAURL": "Metadata URL",
"BINDING": "Binding",
"SIGNEDREQUEST": "Signed Request"
},
"TOAST": {
"SAVED": "Successfully saved.",
"REACTIVATED": "Idp reactivated.",
@ -2167,7 +2181,9 @@
"zh": "简体中文",
"bg": "Български",
"pt": "Portuguese",
"mk": "Македонски"
"mk": "Македонски",
"cs": "Čeština",
"ru": "Русский"
},
"MEMBER": {
"ADD": "Add a Manager",

View File

@ -1060,7 +1060,9 @@
"zh": "简体中文",
"bg": "Български",
"pt": "Portuguese",
"mk": "Македонски"
"mk": "Македонски",
"cs": "Čeština",
"ru": "Русский"
},
"SMTP": {
"TITLE": "Ajustes SMTP",
@ -1271,7 +1273,9 @@
"zh": "简体中文",
"bg": "Български",
"pt": "Portuguese",
"mk": "Македонски"
"mk": "Македонски",
"cs": "Čeština",
"ru": "Русский"
},
"KEYS": {
"emailVerificationDoneText": "Verificación de email realizada",
@ -2155,7 +2159,9 @@
"zh": "简体中文",
"bg": "Български",
"pt": "Portuguese",
"mk": "Македонски"
"mk": "Македонски",
"cs": "Čeština",
"ru": "Русский"
},
"MEMBER": {
"ADD": "Añadir un Mánager",

View File

@ -1059,7 +1059,9 @@
"zh": "简体中文",
"bg": "Български",
"pt": "Portuguese",
"mk": "Македонски"
"mk": "Македонски",
"cs": "Čeština",
"ru": "Русский"
},
"SMTP": {
"TITLE": "Paramètres SMTP",
@ -1270,7 +1272,9 @@
"zh": "简体中文",
"bg": "Български",
"pt": "Portuguese",
"mk": "Македонски"
"mk": "Македонски",
"cs": "Čeština",
"ru": "Русский"
},
"KEYS": {
"emailVerificationDoneText": "Vérification de l'email effectuée",
@ -2159,7 +2163,9 @@
"zh": "简体中文",
"bg": "Български",
"pt": "Portuguese",
"mk": "Македонски"
"mk": "Македонски",
"cs": "Čeština",
"ru": "Русский"
},
"MEMBER": {
"ADD": "Ajouter un manager",

View File

@ -1059,7 +1059,9 @@
"zh": "简体中文",
"bg": "Български",
"pt": "Portuguese",
"mk": "Македонски"
"mk": "Македонски",
"cs": "Čeština",
"ru": "Русский"
},
"SMTP": {
"TITLE": "Impostazioni SMTP",
@ -1270,7 +1272,9 @@
"zh": "简体中文",
"bg": "Български",
"pt": "Portuguese",
"mk": "Македонски"
"mk": "Македонски",
"cs": "Čeština",
"ru": "Русский"
},
"KEYS": {
"emailVerificationDoneText": "Verifica dell'e-mail terminata con successo.",
@ -2159,7 +2163,9 @@
"zh": "简体中文",
"bg": "Български",
"pt": "Portuguese",
"mk": "Македонски"
"mk": "Македонски",
"cs": "Čeština",
"ru": "Русский"
},
"MEMBER": {
"ADD": "Aggiungi un manager",

View File

@ -1060,7 +1060,9 @@
"zh": "简体中文",
"bg": "Български",
"pt": "Portuguese",
"mk": "Македонски"
"mk": "Македонски",
"cs": "Čeština",
"ru": "Русский"
},
"SMTP": {
"TITLE": "SMTP設定",
@ -1266,7 +1268,9 @@
"zh": "简体中文",
"bg": "Български",
"pt": "Portuguese",
"mk": "Македонски"
"mk": "Македонски",
"cs": "Čeština",
"ru": "Русский"
},
"KEYS": {
"emailVerificationDoneText": "メール認証が完了しました",
@ -2150,7 +2154,9 @@
"zh": "简体中文",
"bg": "Български",
"pt": "Portuguese",
"mk": "Македонски"
"mk": "Македонски",
"cs": "Čeština",
"ru": "Русский"
},
"MEMBER": {
"ADD": "マネージャーを追加する",

View File

@ -1061,7 +1061,9 @@
"zh": "简体中文",
"bg": "Български",
"pt": "Portuguese",
"mk": "Македонски"
"mk": "Македонски",
"cs": "Čeština",
"ru": "Русский"
},
"SMTP": {
"TITLE": "SMTP подесувања",
@ -1272,7 +1274,9 @@
"zh": "简体中文",
"bg": "Български",
"pt": "Portuguese",
"mk": "Македонски"
"mk": "Македонски",
"cs": "Čeština",
"ru": "Русский"
},
"KEYS": {
"emailVerificationDoneText": "Е-поштата е верифицирана",
@ -2156,7 +2160,9 @@
"zh": "简体中文",
"bg": "Български",
"pt": "Portuguese",
"mk": "Македонски"
"mk": "Македонски",
"cs": "Čeština",
"ru": "Русский"
},
"MEMBER": {
"ADD": "Додај Менаџер",

View File

@ -1059,7 +1059,9 @@
"zh": "简体中文",
"bg": "Български",
"pt": "Portuguese",
"mk": "Македонски"
"mk": "Македонски",
"cs": "Čeština",
"ru": "Русский"
},
"SMTP": {
"TITLE": "Ustawienia SMTP",
@ -1270,7 +1272,9 @@
"zh": "简体中文",
"bg": "Български",
"pt": "Portuguese",
"mk": "Македонски"
"mk": "Македонски",
"cs": "Čeština",
"ru": "Русский"
},
"KEYS": {
"emailVerificationDoneText": "Weryfikacja adresu e-mail zakończona",
@ -2159,7 +2163,9 @@
"zh": "简体中文",
"bg": "Български",
"pt": "Portuguese",
"mk": "Македонски"
"mk": "Македонски",
"cs": "Čeština",
"ru": "Русский"
},
"MEMBER": {
"ADD": "Dodaj managera",

View File

@ -1061,7 +1061,9 @@
"zh": "简体中文",
"bg": "Български",
"pt": "Portuguese",
"mk": "Македонски"
"mk": "Македонски",
"cs": "Čeština",
"ru": "Русский"
},
"SMTP": {
"TITLE": "Configurações SMTP",
@ -1272,7 +1274,9 @@
"zh": "简体中文",
"bg": "Български",
"pt": "Portuguese",
"mk": "Македонски"
"mk": "Македонски",
"cs": "Čeština",
"ru": "Русский"
},
"KEYS": {
"emailVerificationDoneText": "Verificação de email concluída",
@ -2154,7 +2158,9 @@
"zh": "简体中文",
"bg": "Български",
"pt": "Portuguese",
"mk": "Македонски"
"mk": "Македонски",
"cs": "Čeština",
"ru": "Русский"
},
"MEMBER": {
"ADD": "Adicionar um Gerente",

File diff suppressed because it is too large Load Diff

View File

@ -1059,7 +1059,9 @@
"zh": "简体中文",
"bg": "Български",
"pt": "Portuguese",
"mk": "Македонски"
"mk": "Македонски",
"cs": "Čeština",
"ru": "Русский"
},
"SMTP": {
"TITLE": "SMTP 设置",
@ -1269,7 +1271,9 @@
"zh": "简体中文",
"bg": "Български",
"pt": "Portuguese",
"mk": "Македонски"
"mk": "Македонски",
"cs": "Čeština",
"ru": "Русский"
},
"KEYS": {
"emailVerificationDoneText": "电子邮件验证完成",
@ -2158,7 +2162,9 @@
"zh": "简体中文",
"bg": "Български",
"pt": "Portuguese",
"mk": "Македонски"
"mk": "Македонски",
"cs": "Čeština",
"ru": "Русский"
},
"MEMBER": {
"ADD": "添加管理者",

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="none">
<g fill="#C22E33">
<path d="M7.754 2l.463.41c.343.304.687.607 1.026.915C11.44 5.32 13.3 7.565 14.7 10.149c.072.132.137.268.202.403l.098.203-.108.057-.081-.115-.21-.299-.147-.214c-1.019-1.479-2.04-2.96-3.442-4.145a6.563 6.563 0 00-1.393-.904c-1.014-.485-1.916-.291-2.69.505-.736.757-1.118 1.697-1.463 2.653-.045.123-.092.245-.139.367l-.082.215-.172-.055c.1-.348.192-.698.284-1.049.21-.795.42-1.59.712-2.356.31-.816.702-1.603 1.093-2.39.169-.341.338-.682.5-1.025h.092z"/>
<path d="M8.448 11.822c-1.626.77-5.56 1.564-7.426 1.36C.717 11.576 3.71 4.05 5.18 2.91l-.095.218a4.638 4.638 0 01-.138.303l-.066.129c-.76 1.462-1.519 2.926-1.908 4.53a7.482 7.482 0 00-.228 1.689c-.01 1.34.824 2.252 2.217 2.309.67.027 1.347-.043 2.023-.114.294-.03.587-.061.88-.084.108-.008.214-.021.352-.039l.231-.028z"/>
<path d="M3.825 14.781c-.445.034-.89.068-1.333.108 4.097.39 8.03-.277 11.91-1.644-1.265-2.23-2.97-3.991-4.952-5.522.026.098.084.169.141.239l.048.06c.17.226.348.448.527.67.409.509.818 1.018 1.126 1.578.778 1.42.356 2.648-1.168 3.296-1.002.427-2.097.718-3.18.892-1.03.164-2.075.243-3.119.323z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -2047,6 +2047,32 @@
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.50.0.tgz#9e93b850f0f3fa35f5fa59adfd03adae8488e484"
integrity sha512-NCC3zz2+nvYd+Ckfh87rA47zfu2QsQpvc6k1yzTk+b9KzRj0wkGa8LSoGOXN6Zv4lRf/EIoZ80biDh9HOI+RNQ==
"@fortawesome/angular-fontawesome@^0.13.0":
version "0.13.0"
resolved "https://registry.yarnpkg.com/@fortawesome/angular-fontawesome/-/angular-fontawesome-0.13.0.tgz#3e343ff5ea62934cb0309a52bbf732adecf216fc"
integrity sha512-gzSPRdveOXNO7NIiMgTyB46aiHG0i98KinnAEqHXi8qzraM/kCcHn/0y3f4MhemX6kftwsFli0IU8RyHmtXlSQ==
dependencies:
tslib "^2.4.1"
"@fortawesome/fontawesome-common-types@6.4.2":
version "6.4.2"
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.4.2.tgz#1766039cad33f8ad87f9467b98e0d18fbc8f01c5"
integrity sha512-1DgP7f+XQIJbLFCTX1V2QnxVmpLdKdzzo2k8EmvDOePfchaIGQ9eCHj2up3/jNEbZuBqel5OxiaOJf37TWauRA==
"@fortawesome/fontawesome-svg-core@^6.4.2":
version "6.4.2"
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.4.2.tgz#37f4507d5ec645c8b50df6db14eced32a6f9be09"
integrity sha512-gjYDSKv3TrM2sLTOKBc5rH9ckje8Wrwgx1CxAPbN5N3Fm4prfi7NsJVWd1jklp7i5uSCVwhZS5qlhMXqLrpAIg==
dependencies:
"@fortawesome/fontawesome-common-types" "6.4.2"
"@fortawesome/free-brands-svg-icons@^6.4.2":
version "6.4.2"
resolved "https://registry.yarnpkg.com/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.4.2.tgz#9b8e78066ea6dd563da5dfa686615791d0f7cc71"
integrity sha512-LKOwJX0I7+mR/cvvf6qIiqcERbdnY+24zgpUSouySml+5w8B4BJOx8EhDR/FTKAu06W12fmUIcv6lzPSwYKGGg==
dependencies:
"@fortawesome/fontawesome-common-types" "6.4.2"
"@gar/promisify@^1.1.3":
version "1.1.3"
resolved "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz"

View File

@ -43,7 +43,7 @@ Per default you will only search for users within the selected organization. If
## Configure roles
If you run a self hosted ZITADEL istance you can define your custom roles by overwriting the defaults.yaml
In the InternalAuthZ section you will find all the roles and which permissions the have.
In the InternalAuthZ section you will find all the roles and which permissions they have.
Example:
```bash
@ -53,4 +53,4 @@ InternalAuthZ:
Permissions:
- "iam.read"
- "iam.write"
```
```

View File

@ -45,6 +45,8 @@ ZITADEL is available in the following languages
- Bulgarian (bg)
- Portuguese (pt)
- Macedonian (mk)
- Czech (cs)
- Russina (ru)
A language is displayed based on your agent's language header. The default language is English.

View File

@ -1,31 +1,65 @@
---
title: Run ZITADEL on a Custom Domain
sidebar: Custom Domain
title: External ZITADEL Access
sidebar_label: Instance Not Found
---
# Run ZITADEL on a (Sub)domain of Your Choice
## Why do I get an "Instance not found" error?
Also, ZITADEL has the [concept of virtual instances](/concepts/structure/instance#multiple-virtual-instances).
It uses a requests Host header to determine which virtual instance to use.
This is useful for multi-tenancy and resource sharing, for example in SaaS scenarios.
For most cases however, ZITADEL should run on exactly one domain.
This guide assumes you are already familiar with [configuring ZITADEL](./configure).
You most probably need to configure these fields for making ZITADEL work on your custom domain.
## Standard Config
For security reasons, ZITADEL only serves requests sent to the expected protocol, host and port.
If not using localhost as ExternalDomain, ExternalSecure must be true and you need to serve the ZITADEL console over HTTPS.
ZITADEL only serves requests sent to the expected protocol, host and port.
For local testing purposes, you can use following configuration:
```yaml
ExternalSecure: true
ExternalDomain: 'zitadel.my.domain'
ExternalPort: 443
ExternalDomain: localhost
ExternalPort: 8080
ExternalSecure: false
```
## Database Initialization Steps Config
For productive setups however, we recommend using HTTPS and a custom domain:
ZITADEL creates random subdomains for each instance created.
However, for the first instance, this is most probably not the desired behavior.
In this case the `ExternalDomain`-field of the configuration is used.
```yaml
ExternalDomain: 'zitadel.my.domain'
ExternalPort: 443
ExternalSecure: true
```
## Example
## Changing ExternalDomain, ExternalPort or ExternalSecure
You can change the ExternalDomain, ExternalPort and ExternalSecure configuration options at any time.
However, for ZITADEL to be able to pick up the changes, [you need to rerun ZITADELs setup phase](/self-hosting/manage/updating_scaling#the-setup-phase).
## Running ZITADEL behind a Reverse Proxy
If you run ZITADEL behind a reverse proxy, you need to ensure that it sends the correct request headers to ZITADEL.
The proxy must either ensure that
- the original *Host* header value is assigned to the *Forwarded* headers host directive.
- the original requests *Host* header value is unchanged by the proxy.
Check out the [reverse proxy configuration examples](/self-hosting/manage/reverseproxy/reverse_proxy) for more information.
## Organization Domains
Note that by default, you cannot access ZITADEL at an organizations domain.
Organization level domains [are intended for routing users by their login methods to their correct organization](http://localhost:3000/docs/guides/solution-scenarios/domain-discovery).
However, if you want to access ZITADEL at an organization domain, [you can add additional domains using the System API](/apis/resources/system/system-service-add-domain#adds-a-domain-to-an-instance).
Be aware that you won't automatically have the organizations context when you access ZITADEL like this.
## Generated Subdomains
ZITADEL creates random subdomains for [each new virtual instance](/concepts/structure/instance#multiple-virtual-instances).
You can immediately access the ZITADEL Console an APIs using these subdomains without further actions.
## More Information
- [Check out the production-near loadbalancing example with Traefik](/self-hosting/deploy/loadbalancing-example)
- [Explore some concrete proxy configuration examples for ZITADEL using the domain 127.0.0.1.sslip.io](/self-hosting/manage/reverseproxy/reverse_proxy)
Go to the [loadbalancing example with Traefik](/docs/self-hosting/deploy/loadbalancing-example) for seeing a working example configuration.

View File

@ -1,3 +0,0 @@
:::caution
[The Cloudflare tunnel client currently has an issue which allows it not to force HTTP/2 usage towards the origin.](https://github.com/cloudflare/cloudflared/issues/682)

View File

@ -1,166 +0,0 @@
## TLS mode external
```
LoadModule mpm_event_module modules/mod_mpm_event.so
LoadModule authn_file_module modules/mod_authn_file.so
LoadModule authn_core_module modules/mod_authn_core.so
LoadModule authz_host_module modules/mod_authz_host.so
LoadModule authz_groupfile_module modules/mod_authz_groupfile.so
LoadModule authz_user_module modules/mod_authz_user.so
LoadModule authz_core_module modules/mod_authz_core.so
LoadModule access_compat_module modules/mod_access_compat.so
LoadModule auth_basic_module modules/mod_auth_basic.so
LoadModule reqtimeout_module modules/mod_reqtimeout.so
LoadModule filter_module modules/mod_filter.so
LoadModule mime_module modules/mod_mime.so
LoadModule log_config_module modules/mod_log_config.so
LoadModule env_module modules/mod_env.so
LoadModule headers_module modules/mod_headers.so
LoadModule setenvif_module modules/mod_setenvif.so
LoadModule version_module modules/mod_version.so
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
LoadModule ssl_module modules/mod_ssl.so
LoadModule proxy_http2_module modules/mod_proxy_http2.so
LoadModule unixd_module modules/mod_unixd.so
LoadModule status_module modules/mod_status.so
LoadModule autoindex_module modules/mod_autoindex.so
LoadModule dir_module modules/mod_dir.so
LoadModule alias_module modules/mod_alias.so
LoadModule rewrite_module modules/mod_rewrite.so
ServerRoot "/usr/local/apache2"
LogLevel warn
ErrorLog /proc/self/fd/2
CustomLog /proc/self/fd/1 "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\""
ServerName my.domain
Listen 80
Listen 443
SSLRandomSeed startup builtin
SSLRandomSeed connect builtin
<VirtualHost *:80>
ServerName my.domain
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI}
</VirtualHost>
<VirtualHost *:443>
ServerName my.domain
ProxyPreserveHost On
SSLCertificateFile /certs/server.crt
SSLCertificateKeyFile /certs/server.key
ProxyPass / h2c://localhost:8080/
ProxyPassReverse / h2c://localhost:8080/
</VirtualHost>
```
## TLS mode enabled
```
LoadModule mpm_event_module modules/mod_mpm_event.so
LoadModule authn_file_module modules/mod_authn_file.so
LoadModule authn_core_module modules/mod_authn_core.so
LoadModule authz_host_module modules/mod_authz_host.so
LoadModule authz_groupfile_module modules/mod_authz_groupfile.so
LoadModule authz_user_module modules/mod_authz_user.so
LoadModule authz_core_module modules/mod_authz_core.so
LoadModule access_compat_module modules/mod_access_compat.so
LoadModule auth_basic_module modules/mod_auth_basic.so
LoadModule reqtimeout_module modules/mod_reqtimeout.so
LoadModule filter_module modules/mod_filter.so
LoadModule mime_module modules/mod_mime.so
LoadModule log_config_module modules/mod_log_config.so
LoadModule env_module modules/mod_env.so
LoadModule headers_module modules/mod_headers.so
LoadModule setenvif_module modules/mod_setenvif.so
LoadModule version_module modules/mod_version.so
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
LoadModule ssl_module modules/mod_ssl.so
LoadModule proxy_http2_module modules/mod_proxy_http2.so
LoadModule unixd_module modules/mod_unixd.so
LoadModule status_module modules/mod_status.so
LoadModule autoindex_module modules/mod_autoindex.so
LoadModule dir_module modules/mod_dir.so
LoadModule alias_module modules/mod_alias.so
LoadModule rewrite_module modules/mod_rewrite.so
LoadModule http2_module modules/mod_http2.so
ServerRoot "/usr/local/apache2"
LogLevel debug
ErrorLog /proc/self/fd/2
CustomLog /proc/self/fd/1 "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\""
ServerName my.domain
Listen 80
Listen 443
SSLRandomSeed startup builtin
SSLRandomSeed connect builtin
<VirtualHost *:80>
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI}
</VirtualHost>
<VirtualHost *:443>
ProxyPreserveHost On
SSLEngine on
SSLProxyEngine on
SSLCertificateFile /certs/server.crt
SSLCertificateKeyFile /certs/server.key
ProxyPass / h2://localhost:8080/
</VirtualHost>
```
## TLS mode disabled
```
LoadModule mpm_event_module modules/mod_mpm_event.so
LoadModule authn_file_module modules/mod_authn_file.so
LoadModule authn_core_module modules/mod_authn_core.so
LoadModule authz_host_module modules/mod_authz_host.so
LoadModule authz_groupfile_module modules/mod_authz_groupfile.so
LoadModule authz_user_module modules/mod_authz_user.so
LoadModule authz_core_module modules/mod_authz_core.so
LoadModule access_compat_module modules/mod_access_compat.so
LoadModule auth_basic_module modules/mod_auth_basic.so
LoadModule reqtimeout_module modules/mod_reqtimeout.so
LoadModule filter_module modules/mod_filter.so
LoadModule mime_module modules/mod_mime.so
LoadModule log_config_module modules/mod_log_config.so
LoadModule env_module modules/mod_env.so
LoadModule headers_module modules/mod_headers.so
LoadModule setenvif_module modules/mod_setenvif.so
LoadModule version_module modules/mod_version.so
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
LoadModule ssl_module modules/mod_ssl.so
LoadModule proxy_http2_module modules/mod_proxy_http2.so
LoadModule unixd_module modules/mod_unixd.so
LoadModule status_module modules/mod_status.so
LoadModule autoindex_module modules/mod_autoindex.so
LoadModule dir_module modules/mod_dir.so
LoadModule alias_module modules/mod_alias.so
LoadModule rewrite_module modules/mod_rewrite.so
ServerRoot "/usr/local/apache2"
LogLevel warn
ErrorLog /proc/self/fd/2
CustomLog /proc/self/fd/1 "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\""
ServerName my.domain
Listen 80
<VirtualHost *:80>
ServerName my.domain
ProxyPreserveHost On
ProxyPass / h2c://localhost:8080/
ProxyPassReverse / h2c://localhost:8080/
</VirtualHost>
```

View File

@ -1,4 +0,0 @@
## More information
- [You can read here about the TLS Modes](/self-hosting/manage/tls_modes)
- [And here about how ZITADEL makes use of HTTP/2](/self-hosting/manage/http2)

View File

@ -1,95 +0,0 @@
## TLS mode external
```
worker_processes 1;
events {
worker_connections 1024;
}
http {
server {
listen 443;
ssl_certificate ssl/certificate.pem;
ssl_certificate_key ssl/key.pem;
location / {
grpc_pass grpc://localhost:8080;
grpc_set_header Host $host;
}
}
}
```
:::info
If another port than the default HTTPS port 443 is used replace
` grpc_set_header Host $host;`
with
` grpc_set_header Host $host:$server_port;`
:::
## TLS mode enabled
```
worker_processes 1;
events {
worker_connections 1024;
}
http {
server {
listen 443;
ssl_certificate ssl/certificate.pem;
ssl_certificate_key ssl/key.pem;
location / {
grpc_pass grpcs://localhost:8080;
grpc_set_header Host $host;
}
}
}
```
:::info
If another port than the default HTTPS port 443 is used replace
` grpc_set_header Host $host;`
with
` grpc_set_header Host $host:$server_port;`
:::
## TLS mode disabled
```
worker_processes 1;
events {
worker_connections 1024;
}
http {
server {
listen 80;
location / {
grpc_pass grpc://localhost:8080;
grpc_set_header Host $host;
}
}
}
```
:::info
If another port than the default HTTP port 80 is used replace
` grpc_set_header Host $host;`
with
` grpc_set_header Host $host:$server_port;`
:::

View File

@ -0,0 +1,2 @@
- [Read more about ZITADELs TLS Modes](/self-hosting/manage/tls_modes)
- [Read more about how ZITADEL uses HTTP/2](/self-hosting/manage/http2)

View File

@ -0,0 +1,16 @@
import CodeBlock from '@theme/CodeBlock';
import ComposeYaml from "!!raw-loader!./docker-compose.yaml";
<>With these examples, you create and run a minimal {props.link} configuration for ZITADEL with <a href="https://docs.docker.com/compose">Docker Compose</a>.
Whereas the guide focuses on the configuration for {props.link}, you can inspect the configurations for ZITADEL and the database in the base Docker Compose file.</>
<details>
<summary>base docker-compose.yaml</summary>
<CodeBlock language="yaml">{ComposeYaml}</CodeBlock>
</details>
<>For running {props.link}, you will extend the base Docker Compose file with the {props.link} specific Docker Compose file.</>
<details>
<summary>specific docker-compose.yaml</summary>
<CodeBlock language="yaml">{props.compose}</CodeBlock>
</details>

View File

@ -0,0 +1,90 @@
import CodeBlock from '@theme/CodeBlock';
export const Description = ({mode, link}) => {
let desc
switch (mode) {
case "disabled":
desc = <>Neither {link} nor ZITADEL terminates TLS.
Nevertheless, {link} forwards unencrypted HTTP/2 traffic, aka h2c, to ZITADEL.</>;
break;
case "external":
desc = <>{link} terminates TLS and forwards the requests to ZITADEL via unencrypted h2c.
This example uses an unsafe self-signed certificate for {link}</>;
break;
case "enabled":
desc = <>{link} terminates TLS and forwards the requests to ZITADEL via encrypted HTTP/2.
This example uses an unsafe self-signed certificate for {link} and the same for ZITADEL.</>;
break;
}
return (
<>
{desc}
<>By executing the commands below, you will download the files necessary to run ZITADEL behind {link} with the following config:</>
</>)
}
export const Commands = ({mode, name, lower, configfilename}) => {
let genCert = '# Generate a self signed certificate and key.\nopenssl req -x509 -batch -subj "/CN=127.0.0.1.sslip.io/O=ZITADEL Demo" -nodes -newkey rsa:2048 -keyout ./selfsigned.key -out ./selfsigned.crt\n\n';
let connPort = "443"
let connInsecureFlag = "--insecure "
let connScheme = "https"
let grpcPlainTextFlag = ""
if (mode === "disabled") {
genCert = ''
connPort = "80"
grpcPlainTextFlag = "--plaintext "
connScheme = "http"
// We only need that flag for TLS connections with the self-signed cert
connInsecureFlag = ""
}
return (
<div>
<CodeBlock language="bash">
{'# Download the configuration files.'}{'\n'}
{'export ZITADEL_CONFIG_FILES=https://raw.githubusercontent.com/zitadel/zitadel/main/docs/docs/self-hosting/manage/reverseproxy\n'}
{`wget $\{ZITADEL_CONFIG_FILES\}/docker-compose.yaml -O docker-compose-base.yaml`}{'\n'}
{'wget $\{ZITADEL_CONFIG_FILES\}/'}{lower}{'/docker-compose.yaml -O docker-compose-'}{lower}{'.yaml'}{'\n'}
{'wget $\{ZITADEL_CONFIG_FILES\}/'}{lower}{'/'}{configfilename}{' -O '}{configfilename}{'\n'}
{'\n'}
{genCert}
{'# Run the database, ZITADEL and '}{name}{'.'}{'\n'}
{'docker compose --file docker-compose-base.yaml --file docker-compose-'}{lower}{'.yaml up --detach proxy-'}{mode}{'-tls'}{'\n'}
{'\n'}
{'# Test that gRPC and HTTP APIs work. Empty brackets like {} means success.\n'}
{'sleep 3\n'}
{'grpcurl '}{connInsecureFlag}{grpcPlainTextFlag}{'127.0.0.1.sslip.io:'}{connPort}{' zitadel.admin.v1.AdminService/Healthz\n'}
{'curl '}{connInsecureFlag}{connScheme}{'://127.0.0.1.sslip.io:'}{connPort}{'/admin/v1/healthz\n'}
</CodeBlock>
</div>
)
}
export const LoginURL = ({mode}) => {
let scheme = "https";
if (mode === "disabled") {
scheme = "http"
}
const url = scheme + "://127.0.0.1.sslip.io/ui/console";
return <a href={url}>{url}</a>
}
<Description mode={props.mode} name={props.providername} link={props.link}/>
<details open>
<summary>{props.configfilename}</summary>
<CodeBlock>{props.configfilecontent}</CodeBlock>
</details>
<Commands mode={props.mode} name={props.providername} lower={props.lower} configfilename={props.configfilename}/>
<>When the docker compose command exits successfully, go to <LoginURL mode={props.mode}/> and log in:</>
- **username**: *zitadel-admin@<span></span>zitadel.127.0.0.1.sslip.io*
- **password**: *Password1!*
If the console loads normally, you know that the HTTP and gRPC-Web and gRPC APIs are working correctly.
<CodeBlock language="bash">
{'# You can now stop the database, ZITADEL and '}{props.providername}{'.'}{'\n'}
{'docker compose --file docker-compose-base.yaml --file docker-compose-'}{props.lower}{'.yaml down'}{'\n'}
</CodeBlock>

View File

@ -1,150 +0,0 @@
## TLS mode external
```yaml
entrypoints:
web:
address: ":80"
websecure:
address: ":443"
tls:
stores:
default:
defaultCertificate:
providers:
file:
filename: /etc/traefik/traefik.yaml
http:
middlewares:
zitadel:
headers:
isDevelopment: false
allowedHosts:
- 'localhost'
customRequestHeaders:
:authority: 'localhost'
redirect-to-https:
redirectScheme:
scheme: https
port: 443
permanent: true
routers:
router0:
entryPoints:
- web
middlewares:
- redirect-to-https
rule: 'HostRegexp(`localhost`, `{subdomain:[a-z]+}.localhost`)'
service: zitadel
router1:
entryPoints:
- websecure
service: zitadel
middlewares:
- zitadel
rule: 'HostRegexp(`localhost`, `{subdomain:[a-z]+}.localhost`)'
tls:
domains:
- main: "localhost"
sans:
- "*.localhost"
- "localhost"
services:
zitadel:
loadBalancer:
servers:
- url: h2c://localhost:8080
passHostHeader: true
```
## TLS mode enabled
```yaml
entrypoints:
web:
address: ":80"
websecure:
address: ":443"
tls:
stores:
default:
defaultCertificate:
providers:
file:
filename: /etc/traefik/traefik.yaml
http:
middlewares:
zitadel:
headers:
isDevelopment: false
allowedHosts:
- 'localhost'
customRequestHeaders:
:authority: 'localhost'
redirect-to-https:
redirectScheme:
scheme: https
port: 443
permanent: true
routers:
router0:
entryPoints:
- web
middlewares:
- redirect-to-https
rule: 'HostRegexp(`localhost`, `{subdomain:[a-z]+}.localhost`)'
service: zitadel
# The actual ZITADEL router
router1:
entryPoints:
- websecure
service: zitadel
middlewares:
- zitadel
rule: 'HostRegexp(`localhost`, `{subdomain:[a-z]+}.localhost`)'
tls:
domains:
- main: "localhost"
sans:
- "*.localhost"
- "localhost"
services:
zitadel:
loadBalancer:
servers:
- url: https://localhost:8080
passHostHeader: true
```
## TLS mode disabled
```yaml
entrypoints:
web:
address: ":80"
providers:
file:
filename: /etc/traefik/traefik.yaml
http:
middlewares:
zitadel:
headers:
isDevelopment: false
allowedHosts:
- 'localhost'
customRequestHeaders:
:authority: 'localhost'
routers:
router0:
entryPoints:
- web
middlewares:
- redirect-to-https
rule: 'HostRegexp(`localhost`, `{subdomain:[a-z]+}.localhost`)'
service: zitadel
services:
zitadel:
loadBalancer:
servers:
- url: h2c://localhost:8080
passHostHeader: true
```

View File

@ -0,0 +1,38 @@
---
title: Configure ZITADEL with Caddy
sidebar_label: Caddy
---
import ProxyGuideOverview from '../_proxy_guide_overview.mdx';
import ProxyGuideTLSMode from '../_proxy_guide_tls_mode.mdx';
import ProxyGuideMore from '../_proxy_guide_more.mdx';
import Compose from "!!raw-loader!./docker-compose.yaml";
import ConfigDisabled from "!!raw-loader!./disabled-tls.Caddyfile";
import ConfigExternal from "!!raw-loader!./external-tls.Caddyfile";
import ConfigEnabled from "!!raw-loader!./enabled-tls.Caddyfile";
export const providername = 'Caddy';
export const lower = "caddy";
export const link = <a href="https://caddyserver.com/">{providername}</a>
<ProxyGuideOverview link={link} compose={Compose}></ProxyGuideOverview>
You can either setup your environment for <a href={'#tls-mode-external'}>TLS mode external</a> or <a href={'#tls-mode-enabled'}>TLS mode enabled</a>.
<!-- grpc NOT WORKING
## TLS mode disabled
<ProxyGuideTLSMode mode="disabled" configfilename="disabled-tls.Caddyfile" configfilecontent={ConfigDisabled} providername={providername} link={link} lower={lower}></ProxyGuideTLSMode>
-->
## TLS mode external
<ProxyGuideTLSMode mode="external" configfilename="external-tls.Caddyfile" configfilecontent={ConfigExternal} providername={providername} link={link} lower={lower}></ProxyGuideTLSMode>
## TLS mode enabled
<ProxyGuideTLSMode mode="enabled" configfilename="enabled-tls.Caddyfile" configfilecontent={ConfigEnabled} providername={providername} link={link} lower={lower}></ProxyGuideTLSMode>
## More Information
<ProxyGuideMore></ProxyGuideMore>

View File

@ -0,0 +1,3 @@
http://127.0.0.1.sslip.io {
reverse_proxy h2c://zitadel-disabled-tls:8080
}

View File

@ -0,0 +1,37 @@
version: '3.8'
services:
proxy-disabled-tls:
image: "caddy:2.7.5-alpine"
volumes:
- "./disabled-tls.Caddyfile:/etc/caddy/Caddyfile:ro"
ports:
- "80:80"
depends_on:
zitadel-disabled-tls:
condition: 'service_healthy'
proxy-external-tls:
image: "caddy:2.7.5-alpine"
volumes:
- "./external-tls.Caddyfile:/etc/caddy/Caddyfile:ro"
- "./selfsigned.crt:/etc/certs/selfsigned.crt:ro"
- "./selfsigned.key:/etc/certs/selfsigned.key:ro"
ports:
- "443:443"
depends_on:
zitadel-external-tls:
condition: 'service_healthy'
proxy-enabled-tls:
image: "caddy:2.7.5-alpine"
volumes:
- "./enabled-tls.Caddyfile:/etc/caddy/Caddyfile:ro"
- "./selfsigned.crt:/etc/certs/selfsigned.crt:ro"
- "./selfsigned.key:/etc/certs/selfsigned.key:ro"
ports:
- "443:443"
depends_on:
zitadel-enabled-tls:
condition: 'service_healthy'

View File

@ -0,0 +1,8 @@
https://127.0.0.1.sslip.io {
tls /etc/certs/selfsigned.crt /etc/certs/selfsigned.key
reverse_proxy https://zitadel-enabled-tls:8080 {
transport http {
tls_insecure_skip_verify
}
}
}

View File

@ -0,0 +1,4 @@
https://127.0.0.1.sslip.io {
tls /etc/certs/selfsigned.crt /etc/certs/selfsigned.key
reverse_proxy h2c://zitadel-external-tls:8080
}

View File

@ -1,3 +1,8 @@
---
title: Configure ZITADEL with Cloudflare
sidebar_label: Cloudflare
---
## Settings
- [Make sure HTTP/2 is enabled](https://support.cloudflare.com/hc/en-us/articles/200168076-Understanding-Cloudflare-HTTP-2-and-HTTP-3-Support)

View File

@ -0,0 +1,8 @@
---
title: Configure ZITADEL with Cloudflare Tunnel
sidebar_label: Cloudflare Tunnel
---
:::caution
[The Cloudflare tunnel client currently has an issue which disallows it to force HTTP/2 usage towards the origin.](https://github.com/cloudflare/cloudflared/issues/682)

View File

@ -0,0 +1,90 @@
version: '3.8'
services:
zitadel-disabled-tls:
extends:
service: zitadel-init
command: 'start-from-setup --masterkey "MasterkeyNeedsToHave32Characters" --config /zitadel.yaml --steps /zitadel.yaml'
environment:
- ZITADEL_EXTERNALPORT=80
- ZITADEL_EXTERNALSECURE=false
- ZITADEL_TLS_ENABLED=false
depends_on:
zitadel-init:
condition: 'service_completed_successfully'
db:
condition: 'service_healthy'
zitadel-external-tls:
extends:
service: zitadel-init
command: 'start-from-setup --masterkey "MasterkeyNeedsToHave32Characters" --config /zitadel.yaml --steps /zitadel.yaml'
environment:
- ZITADEL_EXTERNALPORT=443
- ZITADEL_EXTERNALSECURE=true
- ZITADEL_TLS_ENABLED=false
depends_on:
zitadel-init:
condition: 'service_completed_successfully'
db:
condition: 'service_healthy'
zitadel-enabled-tls:
extends:
service: zitadel-init
command: 'start-from-setup --masterkey "MasterkeyNeedsToHave32Characters" --config /zitadel.yaml --steps /zitadel.yaml'
environment:
- ZITADEL_EXTERNALPORT=443
- ZITADEL_EXTERNALSECURE=true
- ZITADEL_TLS_ENABLED=true
- ZITADEL_TLS_CERTPATH=/etc/certs/selfsigned.crt
- ZITADEL_TLS_KEYPATH=/etc/certs/selfsigned.key
volumes:
- ./selfsigned.crt:/etc/certs/selfsigned.crt
- ./selfsigned.key:/etc/certs/selfsigned.key
depends_on:
zitadel-init:
condition: 'service_completed_successfully'
db:
condition: 'service_healthy'
zitadel-init:
user: '$UID'
image: '${ZITADEL_IMAGE:-ghcr.io/zitadel/zitadel:latest}'
command: 'init --config /zitadel.yaml'
depends_on:
db:
condition: 'service_healthy'
environment:
# Using an external domain other than localhost proofs, that the proxy configuration works.
# If ZITADEL can't resolve a requests original host to this domain,
# it will return a 404 Instance not found error.
- ZITADEL_EXTERNALDOMAIN=127.0.0.1.sslip.io
# ZITADEL accesses the database via the docker network.
- ZITADEL_DATABASE_COCKROACH_HOST=db
# In case something doesn't work as expected,
# it can be handy to be able to read the access logs.
- ZITADEL_LOGSTORE_ACCESS_STDOUT_ENABLED=true
# For convenience, ZITADEL should not ask to change the initial admin users password.
- ZITADEL_FIRSTINSTANCE_ORG_HUMAN_PASSWORDCHANGEREQUIRED=false
healthcheck:
test: ["CMD", "/app/zitadel", "ready"]
interval: '10s'
timeout: '5s'
retries: 5
start_period: '10s'
db:
restart: 'always'
image: 'cockroachdb/cockroach:latest'
command: 'start-single-node --insecure --http-addr :9090'
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:9090/health?ready=1']
interval: '10s'
timeout: '30s'
retries: 5
start_period: '20s'
ports:
- "26257:26257"
- "9090:9090"

View File

@ -0,0 +1,37 @@
version: '3.8'
services:
proxy-disabled-tls:
image: "httpd:2.4.58-alpine"
volumes:
- "./httpd-disabled-tls.conf:/usr/local/apache2/conf/httpd.conf"
ports:
- "80:80"
depends_on:
zitadel-disabled-tls:
condition: 'service_healthy'
proxy-external-tls:
image: "httpd:2.4.58-alpine"
volumes:
- "./httpd-external-tls.conf:/usr/local/apache2/conf/httpd.conf"
- "./selfsigned.crt:/etc/certs/selfsigned.crt:ro"
- "./selfsigned.key:/etc/certs/selfsigned.key:ro"
ports:
- "443:443"
depends_on:
zitadel-external-tls:
condition: 'service_healthy'
proxy-enabled-tls:
image: "httpd:2.4.58-alpine"
volumes:
- "./httpd-enabled-tls.conf:/usr/local/apache2/conf/httpd.conf"
- "./selfsigned.crt:/etc/certs/selfsigned.crt:ro"
- "./selfsigned.key:/etc/certs/selfsigned.key:ro"
ports:
- "443:443"
depends_on:
zitadel-enabled-tls:
condition: 'service_healthy'

View File

@ -0,0 +1,39 @@
LoadModule mpm_event_module modules/mod_mpm_event.so
LoadModule authn_file_module modules/mod_authn_file.so
LoadModule authn_core_module modules/mod_authn_core.so
LoadModule authz_host_module modules/mod_authz_host.so
LoadModule authz_groupfile_module modules/mod_authz_groupfile.so
LoadModule authz_user_module modules/mod_authz_user.so
LoadModule authz_core_module modules/mod_authz_core.so
LoadModule access_compat_module modules/mod_access_compat.so
LoadModule auth_basic_module modules/mod_auth_basic.so
LoadModule reqtimeout_module modules/mod_reqtimeout.so
LoadModule filter_module modules/mod_filter.so
LoadModule mime_module modules/mod_mime.so
LoadModule log_config_module modules/mod_log_config.so
LoadModule env_module modules/mod_env.so
LoadModule headers_module modules/mod_headers.so
LoadModule setenvif_module modules/mod_setenvif.so
LoadModule version_module modules/mod_version.so
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
LoadModule ssl_module modules/mod_ssl.so
LoadModule proxy_http2_module modules/mod_proxy_http2.so
LoadModule unixd_module modules/mod_unixd.so
LoadModule status_module modules/mod_status.so
LoadModule autoindex_module modules/mod_autoindex.so
LoadModule dir_module modules/mod_dir.so
LoadModule alias_module modules/mod_alias.so
LoadModule rewrite_module modules/mod_rewrite.so
ServerRoot "/usr/local/apache2"
LogLevel debug
ErrorLog /proc/self/fd/2
CustomLog /proc/self/fd/1 "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\""
Listen 80
<VirtualHost *:80>
ProxyPass / h2c://zitadel-disabled-tls:8080/
ProxyPreserveHost on
</VirtualHost>

View File

@ -0,0 +1,47 @@
LoadModule mpm_event_module modules/mod_mpm_event.so
LoadModule authn_file_module modules/mod_authn_file.so
LoadModule authn_core_module modules/mod_authn_core.so
LoadModule authz_host_module modules/mod_authz_host.so
LoadModule authz_groupfile_module modules/mod_authz_groupfile.so
LoadModule authz_user_module modules/mod_authz_user.so
LoadModule authz_core_module modules/mod_authz_core.so
LoadModule access_compat_module modules/mod_access_compat.so
LoadModule auth_basic_module modules/mod_auth_basic.so
LoadModule reqtimeout_module modules/mod_reqtimeout.so
LoadModule filter_module modules/mod_filter.so
LoadModule mime_module modules/mod_mime.so
LoadModule log_config_module modules/mod_log_config.so
LoadModule env_module modules/mod_env.so
LoadModule headers_module modules/mod_headers.so
LoadModule setenvif_module modules/mod_setenvif.so
LoadModule version_module modules/mod_version.so
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
LoadModule ssl_module modules/mod_ssl.so
LoadModule proxy_http2_module modules/mod_proxy_http2.so
LoadModule unixd_module modules/mod_unixd.so
LoadModule status_module modules/mod_status.so
LoadModule autoindex_module modules/mod_autoindex.so
LoadModule dir_module modules/mod_dir.so
LoadModule alias_module modules/mod_alias.so
LoadModule rewrite_module modules/mod_rewrite.so
LoadModule http2_module modules/mod_http2.so
ServerRoot "/usr/local/apache2"
LogLevel debug
ErrorLog /proc/self/fd/2
CustomLog /proc/self/fd/1 "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\""
Listen 443
SSLRandomSeed startup builtin
SSLRandomSeed connect builtin
<VirtualHost *:443>
ProxyPass / h2://zitadel-enabled-tls:8080/
ProxyPreserveHost on
SSLEngine on
SSLProxyEngine on
SSLCertificateFile /etc/certs/selfsigned.crt
SSLCertificateKeyFile /etc/certs/selfsigned.key
</VirtualHost>

View File

@ -0,0 +1,43 @@
LoadModule mpm_event_module modules/mod_mpm_event.so
LoadModule authn_file_module modules/mod_authn_file.so
LoadModule authn_core_module modules/mod_authn_core.so
LoadModule authz_host_module modules/mod_authz_host.so
LoadModule authz_groupfile_module modules/mod_authz_groupfile.so
LoadModule authz_user_module modules/mod_authz_user.so
LoadModule authz_core_module modules/mod_authz_core.so
LoadModule access_compat_module modules/mod_access_compat.so
LoadModule auth_basic_module modules/mod_auth_basic.so
LoadModule reqtimeout_module modules/mod_reqtimeout.so
LoadModule filter_module modules/mod_filter.so
LoadModule mime_module modules/mod_mime.so
LoadModule log_config_module modules/mod_log_config.so
LoadModule env_module modules/mod_env.so
LoadModule headers_module modules/mod_headers.so
LoadModule setenvif_module modules/mod_setenvif.so
LoadModule version_module modules/mod_version.so
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
LoadModule ssl_module modules/mod_ssl.so
LoadModule proxy_http2_module modules/mod_proxy_http2.so
LoadModule unixd_module modules/mod_unixd.so
LoadModule status_module modules/mod_status.so
LoadModule autoindex_module modules/mod_autoindex.so
LoadModule dir_module modules/mod_dir.so
LoadModule alias_module modules/mod_alias.so
LoadModule rewrite_module modules/mod_rewrite.so
LoadModule http2_module modules/mod_http2.so
LogLevel debug
ErrorLog /proc/self/fd/2
CustomLog /proc/self/fd/1 "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\""
Listen 443
<VirtualHost *:443>
ProxyPass / h2c://zitadel-external-tls:8080/
ProxyPassReverse / h2c://zitadel-external-tls:8080/
ProxyPreserveHost on
SSLEngine on
SSLCertificateFile /etc/certs/selfsigned.crt
SSLCertificateKeyFile /etc/certs/selfsigned.key
</VirtualHost>

View File

@ -0,0 +1,38 @@
---
title: Configure ZITADEL with Apache httpd
sidebar_label: Apache httpd
---
import ProxyGuideOverview from '../_proxy_guide_overview.mdx';
import ProxyGuideTLSMode from '../_proxy_guide_tls_mode.mdx';
import Compose from "!!raw-loader!./docker-compose.yaml";
import ConfigDisabled from "!!raw-loader!./httpd-disabled-tls.conf";
import ConfigExternal from "!!raw-loader!./httpd-external-tls.conf";
import ConfigEnabled from "!!raw-loader!./httpd-enabled-tls.conf";
export const providername = "Apache httpd";
export const lower = "httpd";
export const link = <a href="https://httpd.apache.org//">{providername}</a>
<ProxyGuideOverview link={link} compose={Compose}></ProxyGuideOverview>
You can either setup your environment for <a href={'#tls-mode-disabled'}>TLS mode disabled</a>, <a href={'#tls-mode-external'}>TLS mode external</a> or <a href={'#tls-mode-enabled'}>TLS mode enabled</a>.
## TLS mode disabled
<!-- grpc NOT WORKING -->
<ProxyGuideTLSMode mode="disabled" configfilename="httpd-disabled-tls.conf" configfilecontent={ConfigDisabled} providername={providername} link={link} lower={lower}></ProxyGuideTLSMode>
## TLS mode external
<!-- grpc NOT WORKING -->
<ProxyGuideTLSMode mode="external" configfilename="httpd-external-tls.conf" configfilecontent={ConfigExternal} providername={providername} link={link} lower={lower}></ProxyGuideTLSMode>
## TLS mode enabled
<!-- grpc NOT WORKING -->
<ProxyGuideTLSMode mode="enabled" configfilename="httpd-enabled-tls.conf" configfilecontent={ConfigEnabled} providername={providername} link={link} lower={lower}></ProxyGuideTLSMode>
## More Information
<ProxyGuideMore></ProxyGuideMore>

View File

@ -0,0 +1,37 @@
version: '3.8'
services:
proxy-disabled-tls:
image: "nginx:1.25.3-alpine"
volumes:
- "./nginx-disabled-tls.conf:/etc/nginx/nginx.conf:ro"
ports:
- "80:80"
depends_on:
zitadel-disabled-tls:
condition: 'service_healthy'
proxy-external-tls:
image: "nginx:1.25.3-alpine"
volumes:
- "./nginx-external-tls.conf:/etc/nginx/nginx.conf:ro"
- "./selfsigned.crt:/etc/certs/selfsigned.crt:ro"
- "./selfsigned.key:/etc/certs/selfsigned.key:ro"
ports:
- "443:443"
depends_on:
zitadel-external-tls:
condition: 'service_healthy'
proxy-enabled-tls:
image: "nginx:1.25.3-alpine"
volumes:
- "./nginx-enabled-tls.conf:/etc/nginx/nginx.conf:ro"
- "./selfsigned.crt:/etc/certs/selfsigned.crt:ro"
- "./selfsigned.key:/etc/certs/selfsigned.key:ro"
ports:
- "443:443"
depends_on:
zitadel-enabled-tls:
condition: 'service_healthy'

View File

@ -0,0 +1,13 @@
events {
worker_connections 1024;
}
http {
server {
listen 80;
http2 on;
location / {
grpc_pass grpc://zitadel-disabled-tls:8080;
grpc_set_header Host $host:$server_port;
}
}
}

View File

@ -0,0 +1,15 @@
events {
worker_connections 1024;
}
http {
server {
listen 443 ssl;
http2 on;
ssl_certificate /etc/certs/selfsigned.crt;
ssl_certificate_key /etc/certs/selfsigned.key;
location / {
grpc_pass grpcs://zitadel-enabled-tls:8080;
grpc_set_header Host $host:$server_port;
}
}
}

View File

@ -0,0 +1,15 @@
events {
worker_connections 1024;
}
http {
server {
listen 443 ssl;
http2 on;
ssl_certificate /etc/certs/selfsigned.crt;
ssl_certificate_key /etc/certs/selfsigned.key;
location / {
grpc_pass grpc://zitadel-external-tls:8080;
grpc_set_header Host $host:$server_port;
}
}
}

View File

@ -0,0 +1,35 @@
---
title: Configure ZITADEL with NGINX
sidebar_label: NGINX
---
import ProxyGuideOverview from '../_proxy_guide_overview.mdx';
import ProxyGuideTLSMode from '../_proxy_guide_tls_mode.mdx';
import Compose from "!!raw-loader!./docker-compose.yaml";
import ConfigDisabled from "!!raw-loader!./nginx-disabled-tls.conf";
import ConfigExternal from "!!raw-loader!./nginx-external-tls.conf";
import ConfigEnabled from "!!raw-loader!./nginx-enabled-tls.conf";
export const providername = 'NGINX';
export const lower = "nginx";
export const link = <a href="https://nginx.com/">{providername}</a>;
<ProxyGuideOverview link={link} compose={Compose}></ProxyGuideOverview>
You can either setup your environment for <a href={'#tls-mode-disabled'}>TLS mode disabled</a>, <a href={'#tls-mode-external'}>TLS mode external</a> or <a href={'#tls-mode-enabled'}>TLS mode enabled</a>.
## TLS mode disabled
<ProxyGuideTLSMode mode="disabled" configfilename="nginx-disabled-tls.conf" configfilecontent={ConfigDisabled} providername={providername} link={link} lower={lower}></ProxyGuideTLSMode>
## TLS mode external
<ProxyGuideTLSMode mode="external" configfilename="nginx-external-tls.conf" configfilecontent={ConfigExternal} providername={providername} link={link} lower={lower}></ProxyGuideTLSMode>
## TLS mode enabled
<ProxyGuideTLSMode mode="enabled" configfilename="nginx-enabled-tls.conf" configfilecontent={ConfigEnabled} providername={providername} link={link} lower={lower}></ProxyGuideTLSMode>
## More Information
<ProxyGuideMore></ProxyGuideMore>

View File

@ -2,58 +2,13 @@
title: Reverse Proxy Configuration
---
import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";
import Zcloud from "./_zitadel_cloud.mdx";
import Nginx from "./_nginx.mdx";
import Traefik from "./_traefik.mdx";
import Caddy from "./_caddy.mdx";
import Httpd from "./_httpd.mdx";
import Cftunnel from "./_cloudflare_tunnel.mdx";
import Cloudflare from "./_cloudflare.mdx";
import More from "./_more.mdx";
Check out one of the following guides to configure your favorite reverse proxy:
# Proxy Configuration
- [Traefik](/self-hosting/manage/reverseproxy/traefik)
- [NGINX](/self-hosting/manage/reverseproxy/nginx)
- [Caddy](/self-hosting/manage/reverseproxy/caddy)
<!-- grpc NOT WORKING - [Apache httpd](/self-hosting/manage/reverseproxy/httpd) -->
- [Cloudflare](/self-hosting/manage/reverseproxy/cloudflare)
- [Cloudflare Tunnel](/self-hosting/manage/reverseproxy/cloudflare_tunnel)
- [Fronting ZITADEL Cloud](/self-hosting/manage/reverseproxy/zitadel_cloud)
<Tabs
groupId="proxy-vendor"
default="zcloud"
values={[
{ label: "ZITADEL Cloud", value: "zcloud" },
{ label: "NGINX", value: "nginx" },
{ label: "Traefik", value: "traefik" },
{ label: "Caddy", value: "caddy" },
{ label: "Apache httpd", value: "httpd" },
{ label: "Cloudflare Tunnel", value: "cftunnel" },
{ label: "Cloudflare", value: "cf" },
]}
>
<TabItem value="zcloud">
<Zcloud />
<More />
</TabItem>
<TabItem value="nginx">
<Nginx />
<More />
</TabItem>
<TabItem value="traefik">
<Traefik />
<More />
</TabItem>
<TabItem value="caddy">
<Caddy />
<More />
</TabItem>
<TabItem value="httpd">
<Httpd />
<More />
</TabItem>
<TabItem value="cftunnel">
<Cftunnel />
<More />
</TabItem>
<TabItem value="cf">
<Cloudflare />
<More />
</TabItem>
</Tabs>

View File

@ -0,0 +1,37 @@
version: '3.8'
services:
proxy-disabled-tls:
image: "traefik:v2.10.5"
volumes:
- "./traefik-disabled-tls.yaml:/etc/traefik/traefik.yaml:ro"
ports:
- "80:80"
depends_on:
zitadel-disabled-tls:
condition: 'service_healthy'
proxy-external-tls:
image: "traefik:v2.10.5"
volumes:
- "./traefik-external-tls.yaml:/etc/traefik/traefik.yaml:ro"
- "./selfsigned.crt:/etc/certs/selfsigned.crt:ro"
- "./selfsigned.key:/etc/certs/selfsigned.key:ro"
ports:
- "443:443"
depends_on:
zitadel-external-tls:
condition: 'service_healthy'
proxy-enabled-tls:
image: "traefik:v2.10.5"
volumes:
- "./traefik-enabled-tls.yaml:/etc/traefik/traefik.yaml:ro"
- "./selfsigned.crt:/etc/certs/selfsigned.crt:ro"
- "./selfsigned.key:/etc/certs/selfsigned.key:ro"
ports:
- "443:443"
depends_on:
zitadel-enabled-tls:
condition: 'service_healthy'

View File

@ -0,0 +1,20 @@
log:
level: "DEBUG"
providers:
file:
filename: "/etc/traefik/traefik.yaml"
entrypoints:
web:
address: ":80"
http:
routers:
router:
entryPoints:
- "web"
service: "zitadel"
rule: 'PathPrefix(`/`)'
services:
zitadel:
loadBalancer:
servers:
- url: "h2c://zitadel-disabled-tls:8080"

View File

@ -0,0 +1,31 @@
log:
level: "DEBUG"
providers:
file:
filename: "/etc/traefik/traefik.yaml"
entrypoints:
web:
address: ":443"
http:
routers:
router:
entryPoints:
- "web"
service: "zitadel"
rule: 'PathPrefix(`/`)'
tls: {}
services:
zitadel:
loadBalancer:
serversTransport: "zitadel"
servers:
- url: "https://zitadel-enabled-tls:8080"
serversTransports:
zitadel:
insecureSkipVerify: true
tls:
stores:
default:
defaultCertificate:
certFile: /etc/certs/selfsigned.crt
keyFile: /etc/certs/selfsigned.key

View File

@ -0,0 +1,27 @@
log:
level: "DEBUG"
providers:
file:
filename: "/etc/traefik/traefik.yaml"
entrypoints:
web:
address: ":443"
http:
routers:
router:
entryPoints:
- "web"
service: "zitadel"
rule: 'PathPrefix(`/`)'
tls: {}
services:
zitadel:
loadBalancer:
servers:
- url: "h2c://zitadel-external-tls:8080"
tls:
stores:
default:
defaultCertificate:
certFile: /etc/certs/selfsigned.crt
keyFile: /etc/certs/selfsigned.key

View File

@ -0,0 +1,35 @@
---
title: Configure ZITADEL with Traefik
sidebar_label: Traefik
---
import ProxyGuideOverview from '../_proxy_guide_overview.mdx';
import ProxyGuideTLSMode from '../_proxy_guide_tls_mode.mdx';
import Compose from "!!raw-loader!./docker-compose.yaml";
import ConfigDisabled from "!!raw-loader!./traefik-disabled-tls.yaml";
import ConfigExternal from "!!raw-loader!./traefik-external-tls.yaml";
import ConfigEnabled from "!!raw-loader!./traefik-enabled-tls.yaml";
export const providername = 'Traefik';
export const lower = "traefik";
export const link = <a href="https://doc.traefik.io/traefik/">{providername}</a>;
<ProxyGuideOverview link={link} compose={Compose}></ProxyGuideOverview>
You can either setup your environment for <a href={'#tls-mode-disabled'}>TLS mode disabled</a>, <a href={'#tls-mode-external'}>TLS mode external</a> or <a href={'#tls-mode-enabled'}>TLS mode enabled</a>.
## TLS mode disabled
<ProxyGuideTLSMode mode="disabled" configfilename="traefik-disabled-tls.yaml" configfilecontent={ConfigDisabled} providername={providername} link={link} lower={lower}></ProxyGuideTLSMode>
## TLS mode external
<ProxyGuideTLSMode mode="external" configfilename="traefik-external-tls.yaml" configfilecontent={ConfigExternal} providername={providername} link={link} lower={lower}></ProxyGuideTLSMode>
## TLS mode enabled
<ProxyGuideTLSMode mode="enabled" configfilename="traefik-enabled-tls.yaml" configfilecontent={ConfigEnabled} providername={providername} link={link} lower={lower}></ProxyGuideTLSMode>
## More Information
<ProxyGuideMore></ProxyGuideMore>

View File

@ -1,3 +1,8 @@
---
title: Front ZITADEL Cloud with a CDN, WAF or Reverse Proxy
sidebar_label: Fronting ZITADEL Cloud
---
## Fronting ZITADEL Cloud
You can use your reverseproxy, content delivery network (CDN) or web application firewall (WAF) to front ZITADEL Cloud.

View File

@ -2,8 +2,8 @@
title: TLS Modes
---
To allow ZITADEL to be run on any kind of infrastructure it allows to configure on how tho handle TLS connections.
There are three mode of operation: `external`, `enabled`, `disabled`.
To run ZITADEL on any kind of infrastructure, you can configure on how to handle TLS connections.
There are three modes of operation: `disabled`, `external`, `enabled`.
Generally this command is set as argument while starting ZITADEL. For example like this:
@ -11,6 +11,16 @@ Generally this command is set as argument while starting ZITADEL. For example li
zitadel start-from-init --masterkey "MasterkeyNeedsToHave32Characters" --tlsMode disabled
```
## Disabled
With the mode `disabled`, you instruct ZITADEL to await all connections with plain http without TLS.
:::caution
Be aware this is not a secure setup and should only be used for test systems!
:::
## External
The mode `external` allows you to configure ZITADEL in such a way that it will instruct its clients to use https.
@ -19,8 +29,8 @@ However ZITADEL delegates the management of TLS connections to a reverseproxy, w
## Enabled
When using the mode `enabled` ZITADEL is setup to await incoming connections in an encrypted fashion.
Wether it is from a client directly, a reverseproxy or web application firewall.
This allows http connections to be secured at the transport level the whole way.
Whether it is from a client directly, a reverse proxy or web application firewall.
This allows HTTP connections to be secured at the transport level the whole way.
If you use the mode `enabled` you need to configure ZITADEL with the necessary TLS settings.
@ -42,17 +52,10 @@ TLS:
Cert: #<bas64 encoded content of a pem file>
```
## Disabled
## More Information
With the mode `disabled` ZITADEL is instructed to await all connections with plain http without TLS.
Beware that ZITADEL uses HTTP/2 for all its connections.
If you are using the mode `external` or `disabled` make sure to verify h2c compatibility.
:::caution
Be aware this is not a secure setup and should only be used for test systems!
:::
## HTTP/2
To allow ZITADEL to function properly please make sure that HTTP/2 is enabled. If you are using the mode `external` or `disabled` make sure to verify h2c compatibilty.
You can read more about how ZITADEL utilizes in our [HTTP/2 docs](/self-hosting/manage/http2).
- [Read more abouth how ZITADEL utilizes HTTP/2](/self-hosting/manage/http2).
- [Explore some concrete proxy configuration examples for ZITADEL](/self-hosting/manage/reverseproxy/reverse_proxy).

View File

@ -61,6 +61,8 @@ ZITADEL uses the privileged and preexisting database user configured in `Databas
- If not already done, it grants the necessary permissions ZITADEL needs to the non privileged user.
- If they dont exist already, it creates all schemas and some basic tables.
The init phase is idempotent if executed with the same binary version.
### The Setup Phase
During `zitadel setup`, ZITADEL creates projection tables and migrates existing data.
@ -70,7 +72,10 @@ When deploying a new ZITADEL version,
make sure the setup phase runs before you roll out the new `zitadel start` processes.
The setup phase is executed in subsequent steps
whereas a new version's execution takes over where the last execution stopped.
Therefore, configuration changes relevant for the setup phase wont take effect in regard to the setup execution.
Some configuration changes are only applied during the setup phase, like ExternalDomain, ExternalPort and ExternalSecure.
The setup phase is idempotent if executed with the same binary version.
### The Runtime Phase

View File

@ -4,7 +4,7 @@ title: Technical Advisory 10002
## Date and Version
Version: TBD
Version: 2.40.0
Date: Calendar week 44

View File

@ -67,7 +67,7 @@ We understand that these advisories may include breaking changes, and we aim to
facing UI yourself and not use ZITADELs console UI.
ZITADEL hosted Login-UI is not affected by this change.
</td>
<td>TBD</td>
<td>2.40.0</td>
<td>Calendar week 44</td>
</tr>
<tr>

View File

@ -658,7 +658,24 @@ module.exports = {
"self-hosting/manage/production",
"self-hosting/manage/productionchecklist",
"self-hosting/manage/configure/configure",
"self-hosting/manage/reverseproxy/reverse_proxy",
{
type: "category",
collapsed: false,
label: "Reverse Proxy",
link: {
type: "doc",
id: "self-hosting/manage/reverseproxy/reverse_proxy",
},
items: [
"self-hosting/manage/reverseproxy/traefik/traefik",
"self-hosting/manage/reverseproxy/nginx/nginx",
"self-hosting/manage/reverseproxy/caddy/caddy",
// "self-hosting/manage/reverseproxy/httpd/httpd", grpc NOT WORKING
"self-hosting/manage/reverseproxy/cloudflare/cloudflare",
"self-hosting/manage/reverseproxy/cloudflare_tunnel/cloudflare_tunnel",
"self-hosting/manage/reverseproxy/zitadel_cloud/zitadel_cloud",
],
},
"self-hosting/manage/custom-domain",
"self-hosting/manage/http2",
"self-hosting/manage/tls_modes",

View File

@ -22,7 +22,7 @@ services:
ports:
- "8080:8080"
healthcheck:
test: ["CMD", "/app/zitadel", "ready"]
test: ["CMD", "/app/zitadel", "ready", "--config", "/zitadel.yaml" ]
interval: '10s'
timeout: '5s'
retries: 5

View File

@ -63,7 +63,7 @@ func New(
}
api.grpcServer = server.CreateServer(api.verifier, authZ, queries, http2HostName, tlsConfig, accessInterceptor.AccessService())
api.grpcGateway, err = server.CreateGateway(ctx, port, http1HostName, accessInterceptor)
api.grpcGateway, err = server.CreateGateway(ctx, port, http1HostName, accessInterceptor, tlsConfig)
if err != nil {
return nil, err
}
@ -80,7 +80,7 @@ func New(
// creates a new grpc gateway and registers it as a separate http handler
//
// used for v1 api (system, admin, mgmt, auth)
func (a *API) RegisterServer(ctx context.Context, grpcServer server.WithGatewayPrefix) error {
func (a *API) RegisterServer(ctx context.Context, grpcServer server.WithGatewayPrefix, tlsConfig *tls.Config) error {
grpcServer.RegisterServer(a.grpcServer)
handler, prefix, err := server.CreateGatewayWithPrefix(
ctx,
@ -89,6 +89,7 @@ func (a *API) RegisterServer(ctx context.Context, grpcServer server.WithGatewayP
a.http1HostName,
a.accessInterceptor,
a.queries,
tlsConfig,
)
if err != nil {
return err

View File

@ -60,7 +60,6 @@ func eventRequestToFilter(ctx context.Context, req *admin_pb.ListEventsRequest)
AwaitOpenTransactions().
ResourceOwner(req.ResourceOwner).
EditorUser(req.EditorUserId).
CreationDateAfter(req.CreationDate.AsTime()).
SequenceGreater(req.Sequence)
if len(aggregateIDs) > 0 || len(aggregateTypes) > 0 || len(eventTypes) > 0 {
@ -71,8 +70,11 @@ func eventRequestToFilter(ctx context.Context, req *admin_pb.ListEventsRequest)
Builder()
}
if req.Asc {
if req.GetAsc() {
builder.OrderAsc()
builder.CreationDateAfter(req.CreationDate.AsTime())
} else {
builder.CreationDateBefore(req.CreationDate.AsTime())
}
return builder, nil

View File

@ -24,7 +24,7 @@ import (
func ListUsersRequestToModel(req *mgmt_pb.ListUsersRequest) (*query.UserSearchQueries, error) {
offset, limit, asc := object.ListQueryToModel(req.Query)
queries, err := user_grpc.UserQueriesToQuery(req.Queries)
queries, err := user_grpc.UserQueriesToQuery(req.Queries, 0 /*start from level 0*/)
if err != nil {
return nil, err
}

View File

@ -2,6 +2,7 @@ package server
import (
"context"
"crypto/tls"
"fmt"
"net/http"
"strings"
@ -9,6 +10,7 @@ import (
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/zitadel/logging"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
healthpb "google.golang.org/grpc/health/grpc_health_v1"
"google.golang.org/protobuf/encoding/protojson"
@ -89,10 +91,11 @@ func CreateGatewayWithPrefix(
http1HostName string,
accessInterceptor *http_mw.AccessInterceptor,
queries *query.Queries,
tlsConfig *tls.Config,
) (http.Handler, string, error) {
runtimeMux := runtime.NewServeMux(serveMuxOptions...)
opts := []grpc.DialOption{
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithTransportCredentials(grpcCredentials(tlsConfig)),
grpc.WithUnaryInterceptor(client_middleware.DefaultTracingClient()),
}
connection, err := dial(ctx, port, opts)
@ -106,11 +109,17 @@ func CreateGatewayWithPrefix(
return addInterceptors(runtimeMux, http1HostName, accessInterceptor, queries), g.GatewayPathPrefix(), nil
}
func CreateGateway(ctx context.Context, port uint16, http1HostName string, accessInterceptor *http_mw.AccessInterceptor) (*Gateway, error) {
func CreateGateway(
ctx context.Context,
port uint16,
http1HostName string,
accessInterceptor *http_mw.AccessInterceptor,
tlsConfig *tls.Config,
) (*Gateway, error) {
connection, err := dial(ctx,
port,
[]grpc.DialOption{
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithTransportCredentials(grpcCredentials(tlsConfig)),
grpc.WithUnaryInterceptor(client_middleware.DefaultTracingClient()),
})
if err != nil {
@ -217,3 +226,15 @@ func (r *cookieResponseWriter) WriteHeader(status int) {
}
r.ResponseWriter.WriteHeader(status)
}
func grpcCredentials(tlsConfig *tls.Config) credentials.TransportCredentials {
creds := insecure.NewCredentials()
if tlsConfig != nil {
tlsConfigClone := tlsConfig.Clone()
// We don't want to verify the certificate of the internal grpc server
// That's up to the client who called the gRPC gateway
tlsConfigClone.InsecureSkipVerify = true
creds = credentials.NewTLS(tlsConfigClone)
}
return creds
}

View File

@ -4,6 +4,7 @@ import (
"context"
"net"
"net/http"
"time"
"google.golang.org/protobuf/types/known/structpb"
"google.golang.org/protobuf/types/known/timestamppb"
@ -17,9 +18,20 @@ import (
"github.com/zitadel/zitadel/internal/domain"
caos_errs "github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/query"
objpb "github.com/zitadel/zitadel/pkg/grpc/object"
session "github.com/zitadel/zitadel/pkg/grpc/session/v2beta"
)
var (
timestampComparisons = map[objpb.TimestampQueryMethod]query.TimestampComparison{
objpb.TimestampQueryMethod_TIMESTAMP_QUERY_METHOD_EQUALS: query.TimestampEquals,
objpb.TimestampQueryMethod_TIMESTAMP_QUERY_METHOD_GREATER: query.TimestampGreater,
objpb.TimestampQueryMethod_TIMESTAMP_QUERY_METHOD_GREATER_OR_EQUALS: query.TimestampGreaterOrEquals,
objpb.TimestampQueryMethod_TIMESTAMP_QUERY_METHOD_LESS: query.TimestampLess,
objpb.TimestampQueryMethod_TIMESTAMP_QUERY_METHOD_LESS_OR_EQUALS: query.TimestampLessOrEquals,
}
)
func (s *Server) GetSession(ctx context.Context, req *session.GetSessionRequest) (*session.GetSessionResponse, error) {
res, err := s.query.SessionByID(ctx, true, req.GetSessionId(), req.GetSessionToken())
if err != nil {
@ -46,7 +58,7 @@ func (s *Server) ListSessions(ctx context.Context, req *session.ListSessionsRequ
}
func (s *Server) CreateSession(ctx context.Context, req *session.CreateSessionRequest) (*session.CreateSessionResponse, error) {
checks, metadata, userAgent, err := s.createSessionRequestToCommand(ctx, req)
checks, metadata, userAgent, lifetime, err := s.createSessionRequestToCommand(ctx, req)
if err != nil {
return nil, err
}
@ -55,7 +67,7 @@ func (s *Server) CreateSession(ctx context.Context, req *session.CreateSessionRe
return nil, err
}
set, err := s.command.CreateSession(ctx, cmds, metadata, userAgent)
set, err := s.command.CreateSession(ctx, cmds, metadata, userAgent, lifetime)
if err != nil {
return nil, err
}
@ -78,7 +90,7 @@ func (s *Server) SetSession(ctx context.Context, req *session.SetSessionRequest)
return nil, err
}
set, err := s.command.UpdateSession(ctx, req.GetSessionId(), req.GetSessionToken(), cmds, req.GetMetadata())
set, err := s.command.UpdateSession(ctx, req.GetSessionId(), req.GetSessionToken(), cmds, req.GetMetadata(), req.GetLifetime().AsDuration())
if err != nil {
return nil, err
}
@ -113,13 +125,14 @@ func sessionsToPb(sessions []*query.Session) []*session.Session {
func sessionToPb(s *query.Session) *session.Session {
return &session.Session{
Id: s.ID,
CreationDate: timestamppb.New(s.CreationDate),
ChangeDate: timestamppb.New(s.ChangeDate),
Sequence: s.Sequence,
Factors: factorsToPb(s),
Metadata: s.Metadata,
UserAgent: userAgentToPb(s.UserAgent),
Id: s.ID,
CreationDate: timestamppb.New(s.CreationDate),
ChangeDate: timestamppb.New(s.ChangeDate),
Sequence: s.Sequence,
Factors: factorsToPb(s),
Metadata: s.Metadata,
UserAgent: userAgentToPb(s.UserAgent),
ExpirationDate: expirationToPb(s.Expiration),
}
}
@ -147,6 +160,13 @@ func userAgentToPb(ua domain.UserAgent) *session.UserAgent {
return out
}
func expirationToPb(expiration time.Time) *timestamppb.Timestamp {
if expiration.IsZero() {
return nil
}
return timestamppb.New(expiration)
}
func factorsToPb(s *query.Session) *session.Factors {
user := userFactorToPb(s.UserFactor)
if user == nil {
@ -231,9 +251,10 @@ func listSessionsRequestToQuery(ctx context.Context, req *session.ListSessionsRe
}
return &query.SessionsSearchQueries{
SearchRequest: query.SearchRequest{
Offset: offset,
Limit: limit,
Asc: asc,
Offset: offset,
Limit: limit,
Asc: asc,
SortingColumn: fieldNameToSessionColumn(req.GetSortingColumn()),
},
Queries: queries,
}, nil
@ -241,8 +262,8 @@ func listSessionsRequestToQuery(ctx context.Context, req *session.ListSessionsRe
func sessionQueriesToQuery(ctx context.Context, queries []*session.SearchQuery) (_ []query.SearchQuery, err error) {
q := make([]query.SearchQuery, len(queries)+1)
for i, query := range queries {
q[i], err = sessionQueryToQuery(query)
for i, v := range queries {
q[i], err = sessionQueryToQuery(v)
if err != nil {
return nil, err
}
@ -255,10 +276,14 @@ func sessionQueriesToQuery(ctx context.Context, queries []*session.SearchQuery)
return q, nil
}
func sessionQueryToQuery(query *session.SearchQuery) (query.SearchQuery, error) {
switch q := query.Query.(type) {
func sessionQueryToQuery(sq *session.SearchQuery) (query.SearchQuery, error) {
switch q := sq.Query.(type) {
case *session.SearchQuery_IdsQuery:
return idsQueryToQuery(q.IdsQuery)
case *session.SearchQuery_UserIdQuery:
return query.NewUserIDSearchQuery(q.UserIdQuery.GetId())
case *session.SearchQuery_CreationDateQuery:
return creationDateQueryToQuery(q.CreationDateQuery)
default:
return nil, caos_errs.ThrowInvalidArgument(nil, "GRPC-Sfefs", "List.Query.Invalid")
}
@ -268,12 +293,26 @@ func idsQueryToQuery(q *session.IDsQuery) (query.SearchQuery, error) {
return query.NewSessionIDsSearchQuery(q.Ids)
}
func (s *Server) createSessionRequestToCommand(ctx context.Context, req *session.CreateSessionRequest) ([]command.SessionCommand, map[string][]byte, *domain.UserAgent, error) {
func creationDateQueryToQuery(q *session.CreationDateQuery) (query.SearchQuery, error) {
comparison := timestampComparisons[q.GetMethod()]
return query.NewCreationDateQuery(q.GetCreationDate().AsTime(), comparison)
}
func fieldNameToSessionColumn(field session.SessionFieldName) query.Column {
switch field {
case session.SessionFieldName_SESSION_FIELD_NAME_CREATION_DATE:
return query.SessionColumnCreationDate
default:
return query.Column{}
}
}
func (s *Server) createSessionRequestToCommand(ctx context.Context, req *session.CreateSessionRequest) ([]command.SessionCommand, map[string][]byte, *domain.UserAgent, time.Duration, error) {
checks, err := s.checksToCommand(ctx, req.Checks)
if err != nil {
return nil, nil, nil, err
return nil, nil, nil, 0, err
}
return checks, req.GetMetadata(), userAgentToCommand(req.GetUserAgent()), nil
return checks, req.GetMetadata(), userAgentToCommand(req.GetUserAgent()), req.GetLifetime().AsDuration(), nil
}
func userAgentToCommand(userAgent *session.UserAgent) *domain.UserAgent {

View File

@ -15,6 +15,7 @@ import (
"github.com/stretchr/testify/require"
"google.golang.org/grpc/metadata"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/durationpb"
"github.com/zitadel/zitadel/internal/integration"
object "github.com/zitadel/zitadel/pkg/grpc/object/v2beta"
@ -54,7 +55,7 @@ func TestMain(m *testing.M) {
}())
}
func verifyCurrentSession(t testing.TB, id, token string, sequence uint64, window time.Duration, metadata map[string][]byte, userAgent *session.UserAgent, factors ...wantFactor) *session.Session {
func verifyCurrentSession(t testing.TB, id, token string, sequence uint64, window time.Duration, metadata map[string][]byte, userAgent *session.UserAgent, expirationWindow time.Duration, factors ...wantFactor) *session.Session {
t.Helper()
require.NotEmpty(t, id)
require.NotEmpty(t, token)
@ -75,6 +76,11 @@ func verifyCurrentSession(t testing.TB, id, token string, sequence uint64, windo
if !proto.Equal(userAgent, s.GetUserAgent()) {
t.Errorf("user agent =\n%v\nwant\n%v", s.GetUserAgent(), userAgent)
}
if expirationWindow == 0 {
assert.Nil(t, s.GetExpirationDate())
} else {
assert.WithinRange(t, s.GetExpirationDate().AsTime(), time.Now().Add(-expirationWindow), time.Now().Add(expirationWindow))
}
verifyFactors(t, s.GetFactors(), window, factors)
return s
@ -137,12 +143,13 @@ func verifyFactors(t testing.TB, factors *session.Factors, window time.Duration,
func TestServer_CreateSession(t *testing.T) {
tests := []struct {
name string
req *session.CreateSessionRequest
want *session.CreateSessionResponse
wantErr bool
wantFactors []wantFactor
wantUserAgent *session.UserAgent
name string
req *session.CreateSessionRequest
want *session.CreateSessionResponse
wantErr bool
wantFactors []wantFactor
wantUserAgent *session.UserAgent
wantExpirationWindow time.Duration
}{
{
name: "empty session",
@ -182,6 +189,27 @@ func TestServer_CreateSession(t *testing.T) {
},
},
},
{
name: "negative lifetime",
req: &session.CreateSessionRequest{
Metadata: map[string][]byte{"foo": []byte("bar")},
Lifetime: durationpb.New(-5 * time.Minute),
},
wantErr: true,
},
{
name: "lifetime",
req: &session.CreateSessionRequest{
Metadata: map[string][]byte{"foo": []byte("bar")},
Lifetime: durationpb.New(5 * time.Minute),
},
want: &session.CreateSessionResponse{
Details: &object.Details{
ResourceOwner: Tester.Organisation.ID,
},
},
wantExpirationWindow: 5 * time.Minute,
},
{
name: "with user",
req: &session.CreateSessionRequest{
@ -253,7 +281,7 @@ func TestServer_CreateSession(t *testing.T) {
require.NoError(t, err)
integration.AssertDetails(t, tt.want, got)
verifyCurrentSession(t, got.GetSessionId(), got.GetSessionToken(), got.GetDetails().GetSequence(), time.Minute, tt.req.GetMetadata(), tt.wantUserAgent, tt.wantFactors...)
verifyCurrentSession(t, got.GetSessionId(), got.GetSessionToken(), got.GetDetails().GetSequence(), time.Minute, tt.req.GetMetadata(), tt.wantUserAgent, tt.wantExpirationWindow, tt.wantFactors...)
})
}
}
@ -276,7 +304,7 @@ func TestServer_CreateSession_webauthn(t *testing.T) {
},
})
require.NoError(t, err)
verifyCurrentSession(t, createResp.GetSessionId(), createResp.GetSessionToken(), createResp.GetDetails().GetSequence(), time.Minute, nil, nil)
verifyCurrentSession(t, createResp.GetSessionId(), createResp.GetSessionToken(), createResp.GetDetails().GetSequence(), time.Minute, nil, nil, 0)
assertionData, err := Tester.WebAuthN.CreateAssertionResponse(createResp.GetChallenges().GetWebAuthN().GetPublicKeyCredentialRequestOptions(), true)
require.NoError(t, err)
@ -292,7 +320,7 @@ func TestServer_CreateSession_webauthn(t *testing.T) {
},
})
require.NoError(t, err)
verifyCurrentSession(t, createResp.GetSessionId(), updateResp.GetSessionToken(), updateResp.GetDetails().GetSequence(), time.Minute, nil, nil, wantUserFactor, wantWebAuthNFactorUserVerified)
verifyCurrentSession(t, createResp.GetSessionId(), updateResp.GetSessionToken(), updateResp.GetDetails().GetSequence(), time.Minute, nil, nil, 0, wantUserFactor, wantWebAuthNFactorUserVerified)
}
func TestServer_CreateSession_successfulIntent(t *testing.T) {
@ -308,7 +336,7 @@ func TestServer_CreateSession_successfulIntent(t *testing.T) {
},
})
require.NoError(t, err)
verifyCurrentSession(t, createResp.GetSessionId(), createResp.GetSessionToken(), createResp.GetDetails().GetSequence(), time.Minute, nil, nil)
verifyCurrentSession(t, createResp.GetSessionId(), createResp.GetSessionToken(), createResp.GetDetails().GetSequence(), time.Minute, nil, nil, 0)
intentID, token, _, _ := Tester.CreateSuccessfulOAuthIntent(t, idpID, User.GetUserId(), "id")
updateResp, err := Client.SetSession(CTX, &session.SetSessionRequest{
@ -322,7 +350,7 @@ func TestServer_CreateSession_successfulIntent(t *testing.T) {
},
})
require.NoError(t, err)
verifyCurrentSession(t, createResp.GetSessionId(), updateResp.GetSessionToken(), updateResp.GetDetails().GetSequence(), time.Minute, nil, nil, wantUserFactor, wantIntentFactor)
verifyCurrentSession(t, createResp.GetSessionId(), updateResp.GetSessionToken(), updateResp.GetDetails().GetSequence(), time.Minute, nil, nil, 0, wantUserFactor, wantIntentFactor)
}
func TestServer_CreateSession_successfulIntentUnknownUserID(t *testing.T) {
@ -338,7 +366,7 @@ func TestServer_CreateSession_successfulIntentUnknownUserID(t *testing.T) {
},
})
require.NoError(t, err)
verifyCurrentSession(t, createResp.GetSessionId(), createResp.GetSessionToken(), createResp.GetDetails().GetSequence(), time.Minute, nil, nil)
verifyCurrentSession(t, createResp.GetSessionId(), createResp.GetSessionToken(), createResp.GetDetails().GetSequence(), time.Minute, nil, nil, 0)
idpUserID := "id"
intentID, token, _, _ := Tester.CreateSuccessfulOAuthIntent(t, idpID, "", idpUserID)
@ -365,7 +393,7 @@ func TestServer_CreateSession_successfulIntentUnknownUserID(t *testing.T) {
},
})
require.NoError(t, err)
verifyCurrentSession(t, createResp.GetSessionId(), updateResp.GetSessionToken(), updateResp.GetDetails().GetSequence(), time.Minute, nil, nil, wantUserFactor, wantIntentFactor)
verifyCurrentSession(t, createResp.GetSessionId(), updateResp.GetSessionToken(), updateResp.GetDetails().GetSequence(), time.Minute, nil, nil, 0, wantUserFactor, wantIntentFactor)
}
func TestServer_CreateSession_startedIntentFalseToken(t *testing.T) {
@ -381,7 +409,7 @@ func TestServer_CreateSession_startedIntentFalseToken(t *testing.T) {
},
})
require.NoError(t, err)
verifyCurrentSession(t, createResp.GetSessionId(), createResp.GetSessionToken(), createResp.GetDetails().GetSequence(), time.Minute, nil, nil)
verifyCurrentSession(t, createResp.GetSessionId(), createResp.GetSessionToken(), createResp.GetDetails().GetSequence(), time.Minute, nil, nil, 0)
intentID := Tester.CreateIntent(t, idpID)
_, err = Client.SetSession(CTX, &session.SetSessionRequest{
@ -433,7 +461,7 @@ func TestServer_SetSession_flow(t *testing.T) {
createResp, err := Client.CreateSession(CTX, &session.CreateSessionRequest{})
require.NoError(t, err)
sessionToken := createResp.GetSessionToken()
verifyCurrentSession(t, createResp.GetSessionId(), sessionToken, createResp.GetDetails().GetSequence(), time.Minute, nil, nil)
verifyCurrentSession(t, createResp.GetSessionId(), sessionToken, createResp.GetDetails().GetSequence(), time.Minute, nil, nil, 0)
t.Run("check user", func(t *testing.T) {
resp, err := Client.SetSession(CTX, &session.SetSessionRequest{
@ -449,7 +477,7 @@ func TestServer_SetSession_flow(t *testing.T) {
})
require.NoError(t, err)
sessionToken = resp.GetSessionToken()
verifyCurrentSession(t, createResp.GetSessionId(), sessionToken, resp.GetDetails().GetSequence(), time.Minute, nil, nil, wantUserFactor)
verifyCurrentSession(t, createResp.GetSessionId(), sessionToken, resp.GetDetails().GetSequence(), time.Minute, nil, nil, 0, wantUserFactor)
})
t.Run("check webauthn, user verified (passkey)", func(t *testing.T) {
@ -464,7 +492,7 @@ func TestServer_SetSession_flow(t *testing.T) {
},
})
require.NoError(t, err)
verifyCurrentSession(t, createResp.GetSessionId(), resp.GetSessionToken(), resp.GetDetails().GetSequence(), time.Minute, nil, nil)
verifyCurrentSession(t, createResp.GetSessionId(), resp.GetSessionToken(), resp.GetDetails().GetSequence(), time.Minute, nil, nil, 0)
sessionToken = resp.GetSessionToken()
assertionData, err := Tester.WebAuthN.CreateAssertionResponse(resp.GetChallenges().GetWebAuthN().GetPublicKeyCredentialRequestOptions(), true)
@ -481,7 +509,7 @@ func TestServer_SetSession_flow(t *testing.T) {
})
require.NoError(t, err)
sessionToken = resp.GetSessionToken()
verifyCurrentSession(t, createResp.GetSessionId(), sessionToken, resp.GetDetails().GetSequence(), time.Minute, nil, nil, wantUserFactor, wantWebAuthNFactorUserVerified)
verifyCurrentSession(t, createResp.GetSessionId(), sessionToken, resp.GetDetails().GetSequence(), time.Minute, nil, nil, 0, wantUserFactor, wantWebAuthNFactorUserVerified)
})
userAuthCtx := Tester.WithAuthorizationToken(CTX, sessionToken)
@ -508,7 +536,7 @@ func TestServer_SetSession_flow(t *testing.T) {
},
})
require.NoError(t, err)
verifyCurrentSession(t, createResp.GetSessionId(), resp.GetSessionToken(), resp.GetDetails().GetSequence(), time.Minute, nil, nil)
verifyCurrentSession(t, createResp.GetSessionId(), resp.GetSessionToken(), resp.GetDetails().GetSequence(), time.Minute, nil, nil, 0)
sessionToken = resp.GetSessionToken()
assertionData, err := Tester.WebAuthN.CreateAssertionResponse(resp.GetChallenges().GetWebAuthN().GetPublicKeyCredentialRequestOptions(), false)
@ -525,7 +553,7 @@ func TestServer_SetSession_flow(t *testing.T) {
})
require.NoError(t, err)
sessionToken = resp.GetSessionToken()
verifyCurrentSession(t, createResp.GetSessionId(), sessionToken, resp.GetDetails().GetSequence(), time.Minute, nil, nil, wantUserFactor, wantWebAuthNFactor)
verifyCurrentSession(t, createResp.GetSessionId(), sessionToken, resp.GetDetails().GetSequence(), time.Minute, nil, nil, 0, wantUserFactor, wantWebAuthNFactor)
})
}
})
@ -544,7 +572,7 @@ func TestServer_SetSession_flow(t *testing.T) {
})
require.NoError(t, err)
sessionToken = resp.GetSessionToken()
verifyCurrentSession(t, createResp.GetSessionId(), sessionToken, resp.GetDetails().GetSequence(), time.Minute, nil, nil, wantUserFactor, wantWebAuthNFactor, wantTOTPFactor)
verifyCurrentSession(t, createResp.GetSessionId(), sessionToken, resp.GetDetails().GetSequence(), time.Minute, nil, nil, 0, wantUserFactor, wantWebAuthNFactor, wantTOTPFactor)
})
t.Run("check OTP SMS", func(t *testing.T) {
@ -556,7 +584,7 @@ func TestServer_SetSession_flow(t *testing.T) {
},
})
require.NoError(t, err)
verifyCurrentSession(t, createResp.GetSessionId(), resp.GetSessionToken(), resp.GetDetails().GetSequence(), time.Minute, nil, nil)
verifyCurrentSession(t, createResp.GetSessionId(), resp.GetSessionToken(), resp.GetDetails().GetSequence(), time.Minute, nil, nil, 0)
sessionToken = resp.GetSessionToken()
otp := resp.GetChallenges().GetOtpSms()
@ -573,7 +601,7 @@ func TestServer_SetSession_flow(t *testing.T) {
})
require.NoError(t, err)
sessionToken = resp.GetSessionToken()
verifyCurrentSession(t, createResp.GetSessionId(), sessionToken, resp.GetDetails().GetSequence(), time.Minute, nil, nil, wantUserFactor, wantWebAuthNFactor, wantOTPSMSFactor)
verifyCurrentSession(t, createResp.GetSessionId(), sessionToken, resp.GetDetails().GetSequence(), time.Minute, nil, nil, 0, wantUserFactor, wantWebAuthNFactor, wantOTPSMSFactor)
})
t.Run("check OTP Email", func(t *testing.T) {
@ -587,7 +615,7 @@ func TestServer_SetSession_flow(t *testing.T) {
},
})
require.NoError(t, err)
verifyCurrentSession(t, createResp.GetSessionId(), resp.GetSessionToken(), resp.GetDetails().GetSequence(), time.Minute, nil, nil)
verifyCurrentSession(t, createResp.GetSessionId(), resp.GetSessionToken(), resp.GetDetails().GetSequence(), time.Minute, nil, nil, 0)
sessionToken = resp.GetSessionToken()
otp := resp.GetChallenges().GetOtpEmail()
@ -604,10 +632,34 @@ func TestServer_SetSession_flow(t *testing.T) {
})
require.NoError(t, err)
sessionToken = resp.GetSessionToken()
verifyCurrentSession(t, createResp.GetSessionId(), sessionToken, resp.GetDetails().GetSequence(), time.Minute, nil, nil, wantUserFactor, wantWebAuthNFactor, wantOTPEmailFactor)
verifyCurrentSession(t, createResp.GetSessionId(), sessionToken, resp.GetDetails().GetSequence(), time.Minute, nil, nil, 0, wantUserFactor, wantWebAuthNFactor, wantOTPEmailFactor)
})
}
func TestServer_SetSession_expired(t *testing.T) {
createResp, err := Client.CreateSession(CTX, &session.CreateSessionRequest{
Lifetime: durationpb.New(20 * time.Second),
})
require.NoError(t, err)
// test session token works
sessionResp, err := Tester.Client.SessionV2.SetSession(CTX, &session.SetSessionRequest{
SessionId: createResp.GetSessionId(),
SessionToken: createResp.GetSessionToken(),
Lifetime: durationpb.New(20 * time.Second),
})
require.NoError(t, err)
// ensure session expires and does not work anymore
time.Sleep(20 * time.Second)
_, err = Tester.Client.SessionV2.SetSession(CTX, &session.SetSessionRequest{
SessionId: createResp.GetSessionId(),
SessionToken: sessionResp.GetSessionToken(),
Lifetime: durationpb.New(20 * time.Second),
})
require.Error(t, err)
}
func Test_ZITADEL_API_missing_authentication(t *testing.T) {
// create new, empty session
createResp, err := Client.CreateSession(CTX, &session.CreateSessionRequest{})
@ -629,7 +681,7 @@ func Test_ZITADEL_API_missing_mfa(t *testing.T) {
}
func Test_ZITADEL_API_success(t *testing.T) {
id, token, _, _ := Tester.CreateVerfiedWebAuthNSession(t, CTX, User.GetUserId())
id, token, _, _ := Tester.CreateVerifiedWebAuthNSession(t, CTX, User.GetUserId())
ctx := Tester.WithAuthorizationToken(context.Background(), token)
sessionResp, err := Tester.Client.SessionV2.GetSession(ctx, &session.GetSessionRequest{SessionId: id})
@ -641,7 +693,7 @@ func Test_ZITADEL_API_success(t *testing.T) {
}
func Test_ZITADEL_API_session_not_found(t *testing.T) {
id, token, _, _ := Tester.CreateVerfiedWebAuthNSession(t, CTX, User.GetUserId())
id, token, _, _ := Tester.CreateVerifiedWebAuthNSession(t, CTX, User.GetUserId())
// test session token works
ctx := Tester.WithAuthorizationToken(context.Background(), token)
@ -658,3 +710,18 @@ func Test_ZITADEL_API_session_not_found(t *testing.T) {
_, err = Tester.Client.SessionV2.GetSession(ctx, &session.GetSessionRequest{SessionId: id})
require.Error(t, err)
}
func Test_ZITADEL_API_session_expired(t *testing.T) {
id, token, _, _ := Tester.CreateVerifiedWebAuthNSessionWithLifetime(t, CTX, User.GetUserId(), 20*time.Second)
// test session token works
ctx := Tester.WithAuthorizationToken(context.Background(), token)
_, err := Tester.Client.SessionV2.GetSession(ctx, &session.GetSessionRequest{SessionId: id})
require.NoError(t, err)
// ensure session expires and does not work anymore
time.Sleep(20 * time.Second)
sessionResp, err := Tester.Client.SessionV2.GetSession(ctx, &session.GetSessionRequest{SessionId: id})
require.Error(t, err)
require.Nil(t, sessionResp)
}

View File

@ -14,6 +14,7 @@ import (
"google.golang.org/protobuf/types/known/timestamppb"
"github.com/zitadel/zitadel/internal/api/authz"
objpb "github.com/zitadel/zitadel/pkg/grpc/object"
"github.com/zitadel/zitadel/internal/domain"
caos_errs "github.com/zitadel/zitadel/internal/errors"
@ -22,12 +23,16 @@ import (
session "github.com/zitadel/zitadel/pkg/grpc/session/v2beta"
)
var (
creationDate = time.Date(2023, 10, 10, 14, 15, 0, 0, time.UTC)
)
func Test_sessionsToPb(t *testing.T) {
now := time.Now()
past := now.Add(-time.Hour)
sessions := []*query.Session{
{ // no factor, with user agent
{ // no factor, with user agent and expiration
ID: "999",
CreationDate: now,
ChangeDate: now,
@ -42,6 +47,7 @@ func Test_sessionsToPb(t *testing.T) {
IP: net.IPv4(1, 2, 3, 4),
Header: http.Header{"foo": []string{"foo", "bar"}},
},
Expiration: now,
},
{ // user factor
ID: "999",
@ -124,7 +130,7 @@ func Test_sessionsToPb(t *testing.T) {
}
want := []*session.Session{
{ // no factor, with user agent
{ // no factor, with user agent and expiration
Id: "999",
CreationDate: timestamppb.New(now),
ChangeDate: timestamppb.New(now),
@ -139,6 +145,7 @@ func Test_sessionsToPb(t *testing.T) {
"foo": {Values: []string{"foo", "bar"}},
},
},
ExpirationDate: timestamppb.New(now),
},
{ // user factor
Id: "999",
@ -307,11 +314,18 @@ func mustNewListQuery(t testing.TB, column query.Column, list []any, compare que
return q
}
func mustNewTimestampQuery(t testing.TB, column query.Column, ts time.Time, compare query.TimestampComparison) query.SearchQuery {
q, err := query.NewTimestampQuery(column, ts, compare)
require.NoError(t, err)
return q
}
func Test_listSessionsRequestToQuery(t *testing.T) {
type args struct {
ctx context.Context
req *session.ListSessionsRequest
}
tests := []struct {
name string
args args
@ -335,6 +349,26 @@ func Test_listSessionsRequestToQuery(t *testing.T) {
},
},
},
{
name: "default request with sorting column",
args: args{
ctx: authz.NewMockContext("123", "456", "789"),
req: &session.ListSessionsRequest{
SortingColumn: session.SessionFieldName_SESSION_FIELD_NAME_CREATION_DATE,
},
},
want: &query.SessionsSearchQueries{
SearchRequest: query.SearchRequest{
Offset: 0,
Limit: 0,
SortingColumn: query.SessionColumnCreationDate,
Asc: false,
},
Queries: []query.SearchQuery{
mustNewTextQuery(t, query.SessionColumnCreator, "789", query.TextEquals),
},
},
},
{
name: "with list query and sessions",
args: args{
@ -356,6 +390,17 @@ func Test_listSessionsRequestToQuery(t *testing.T) {
Ids: []string{"4", "5", "6"},
},
}},
{Query: &session.SearchQuery_UserIdQuery{
UserIdQuery: &session.UserIDQuery{
Id: "10",
},
}},
{Query: &session.SearchQuery_CreationDateQuery{
CreationDateQuery: &session.CreationDateQuery{
CreationDate: timestamppb.New(creationDate),
Method: objpb.TimestampQueryMethod_TIMESTAMP_QUERY_METHOD_GREATER,
},
}},
},
},
},
@ -368,6 +413,8 @@ func Test_listSessionsRequestToQuery(t *testing.T) {
Queries: []query.SearchQuery{
mustNewListQuery(t, query.SessionColumnID, []interface{}{"1", "2", "3"}, query.ListIn),
mustNewListQuery(t, query.SessionColumnID, []interface{}{"4", "5", "6"}, query.ListIn),
mustNewTextQuery(t, query.SessionColumnUserID, "10", query.TextEquals),
mustNewTimestampQuery(t, query.SessionColumnCreationDate, creationDate, query.TimestampGreater),
mustNewTextQuery(t, query.SessionColumnCreator, "789", query.TextEquals),
},
},
@ -485,7 +532,7 @@ func Test_sessionQueryToQuery(t *testing.T) {
wantErr: caos_errs.ThrowInvalidArgument(nil, "GRPC-Sfefs", "List.Query.Invalid"),
},
{
name: "query",
name: "ids query",
args: args{&session.SearchQuery{
Query: &session.SearchQuery_IdsQuery{
IdsQuery: &session.IDsQuery{
@ -495,6 +542,40 @@ func Test_sessionQueryToQuery(t *testing.T) {
}},
want: mustNewListQuery(t, query.SessionColumnID, []interface{}{"1", "2", "3"}, query.ListIn),
},
{
name: "user id query",
args: args{&session.SearchQuery{
Query: &session.SearchQuery_UserIdQuery{
UserIdQuery: &session.UserIDQuery{
Id: "10",
},
},
}},
want: mustNewTextQuery(t, query.SessionColumnUserID, "10", query.TextEquals),
},
{
name: "creation date query",
args: args{&session.SearchQuery{
Query: &session.SearchQuery_CreationDateQuery{
CreationDateQuery: &session.CreationDateQuery{
CreationDate: timestamppb.New(creationDate),
Method: objpb.TimestampQueryMethod_TIMESTAMP_QUERY_METHOD_LESS,
},
},
}},
want: mustNewTimestampQuery(t, query.SessionColumnCreationDate, creationDate, query.TimestampLess),
},
{
name: "creation date query with default method",
args: args{&session.SearchQuery{
Query: &session.SearchQuery_CreationDateQuery{
CreationDateQuery: &session.CreationDateQuery{
CreationDate: timestamppb.New(creationDate),
},
},
}},
want: mustNewTimestampQuery(t, query.SessionColumnCreationDate, creationDate, query.TimestampEquals),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {

View File

@ -7,10 +7,10 @@ import (
user_pb "github.com/zitadel/zitadel/pkg/grpc/user"
)
func UserQueriesToQuery(queries []*user_pb.SearchQuery) (_ []query.SearchQuery, err error) {
func UserQueriesToQuery(queries []*user_pb.SearchQuery, level uint8) (_ []query.SearchQuery, err error) {
q := make([]query.SearchQuery, len(queries))
for i, query := range queries {
q[i], err = UserQueryToQuery(query)
q[i], err = UserQueryToQuery(query, level)
if err != nil {
return nil, err
}
@ -18,7 +18,11 @@ func UserQueriesToQuery(queries []*user_pb.SearchQuery) (_ []query.SearchQuery,
return q, nil
}
func UserQueryToQuery(query *user_pb.SearchQuery) (query.SearchQuery, error) {
func UserQueryToQuery(query *user_pb.SearchQuery, level uint8) (query.SearchQuery, error) {
if level > 20 {
// can't go deeper than 20 levels of nesting.
return nil, errors.ThrowInvalidArgument(nil, "USER-zsQ97", "Errors.User.TooManyNestingLevels")
}
switch q := query.Query.(type) {
case *user_pb.SearchQuery_UserNameQuery:
return UserNameQueryToQuery(q.UserNameQuery)
@ -42,6 +46,12 @@ func UserQueryToQuery(query *user_pb.SearchQuery) (query.SearchQuery, error) {
return ResourceOwnerQueryToQuery(q.ResourceOwner)
case *user_pb.SearchQuery_InUserIdsQuery:
return InUserIdsQueryToQuery(q.InUserIdsQuery)
case *user_pb.SearchQuery_OrQuery:
return OrQueryToQuery(q.OrQuery, level)
case *user_pb.SearchQuery_AndQuery:
return AndQueryToQuery(q.AndQuery, level)
case *user_pb.SearchQuery_NotQuery:
return NotQueryToQuery(q.NotQuery, level)
default:
return nil, errors.ThrowInvalidArgument(nil, "GRPC-vR9nC", "List.Query.Invalid")
}
@ -90,3 +100,24 @@ func ResourceOwnerQueryToQuery(q *user_pb.ResourceOwnerQuery) (query.SearchQuery
func InUserIdsQueryToQuery(q *user_pb.InUserIDQuery) (query.SearchQuery, error) {
return query.NewUserInUserIdsSearchQuery(q.UserIds)
}
func OrQueryToQuery(q *user_pb.OrQuery, level uint8) (query.SearchQuery, error) {
mappedQueries, err := UserQueriesToQuery(q.Queries, level+1)
if err != nil {
return nil, err
}
return query.NewUserOrSearchQuery(mappedQueries)
}
func AndQueryToQuery(q *user_pb.AndQuery, level uint8) (query.SearchQuery, error) {
mappedQueries, err := UserQueriesToQuery(q.Queries, level+1)
if err != nil {
return nil, err
}
return query.NewUserAndSearchQuery(mappedQueries)
}
func NotQueryToQuery(q *user_pb.NotQuery, level uint8) (query.SearchQuery, error) {
mappedQuery, err := UserQueryToQuery(q.Query, level+1)
if err != nil {
return nil, err
}
return query.NewUserNotSearchQuery(mappedQuery)
}

View File

@ -16,13 +16,13 @@ import (
func TestServer_AddOTPSMS(t *testing.T) {
userID := Tester.CreateHumanUser(CTX).GetUserId()
Tester.RegisterUserPasskey(CTX, userID)
_, sessionToken, _, _ := Tester.CreateVerfiedWebAuthNSession(t, CTX, userID)
_, sessionToken, _, _ := Tester.CreateVerifiedWebAuthNSession(t, CTX, userID)
// TODO: add when phone can be added to user
/*
userIDPhone := Tester.CreateHumanUser(CTX).GetUserId()
Tester.RegisterUserPasskey(CTX, userIDPhone)
_, sessionTokenPhone, _, _ := Tester.CreateVerfiedWebAuthNSession(t, CTX, userIDPhone)
_, sessionTokenPhone, _, _ := Tester.CreateVerifiedWebAuthNSession(t, CTX, userIDPhone)
*/
type args struct {
ctx context.Context
@ -99,7 +99,7 @@ func TestServer_RemoveOTPSMS(t *testing.T) {
/*
userID := Tester.CreateHumanUser(CTX).GetUserId()
Tester.RegisterUserPasskey(CTX, userID)
_, sessionToken, _, _ := Tester.CreateVerfiedWebAuthNSession(t, CTX, userID)
_, sessionToken, _, _ := Tester.CreateVerifiedWebAuthNSession(t, CTX, userID)
*/
type args struct {
@ -157,7 +157,7 @@ func TestServer_RemoveOTPSMS(t *testing.T) {
func TestServer_AddOTPEmail(t *testing.T) {
userID := Tester.CreateHumanUser(CTX).GetUserId()
Tester.RegisterUserPasskey(CTX, userID)
_, sessionToken, _, _ := Tester.CreateVerfiedWebAuthNSession(t, CTX, userID)
_, sessionToken, _, _ := Tester.CreateVerifiedWebAuthNSession(t, CTX, userID)
userVerified := Tester.CreateHumanUser(CTX)
_, err := Tester.Client.UserV2.VerifyEmail(CTX, &user.VerifyEmailRequest{
@ -166,7 +166,7 @@ func TestServer_AddOTPEmail(t *testing.T) {
})
require.NoError(t, err)
Tester.RegisterUserPasskey(CTX, userVerified.GetUserId())
_, sessionTokenVerified, _, _ := Tester.CreateVerfiedWebAuthNSession(t, CTX, userVerified.GetUserId())
_, sessionTokenVerified, _, _ := Tester.CreateVerifiedWebAuthNSession(t, CTX, userVerified.GetUserId())
type args struct {
ctx context.Context
@ -238,11 +238,11 @@ func TestServer_AddOTPEmail(t *testing.T) {
func TestServer_RemoveOTPEmail(t *testing.T) {
userID := Tester.CreateHumanUser(CTX).GetUserId()
Tester.RegisterUserPasskey(CTX, userID)
_, sessionToken, _, _ := Tester.CreateVerfiedWebAuthNSession(t, CTX, userID)
_, sessionToken, _, _ := Tester.CreateVerifiedWebAuthNSession(t, CTX, userID)
userVerified := Tester.CreateHumanUser(CTX)
Tester.RegisterUserPasskey(CTX, userVerified.GetUserId())
_, sessionTokenVerified, _, _ := Tester.CreateVerfiedWebAuthNSession(t, CTX, userVerified.GetUserId())
_, sessionTokenVerified, _, _ := Tester.CreateVerifiedWebAuthNSession(t, CTX, userVerified.GetUserId())
userVerifiedCtx := Tester.WithAuthorizationToken(context.Background(), sessionTokenVerified)
_, err := Tester.Client.UserV2.VerifyEmail(userVerifiedCtx, &user.VerifyEmailRequest{
UserId: userVerified.GetUserId(),

View File

@ -36,7 +36,7 @@ func TestOPStorage_CreateAuthRequest(t *testing.T) {
func TestOPStorage_CreateAccessToken_code(t *testing.T) {
clientID := createClient(t)
authRequestID := createAuthRequest(t, clientID, redirectURI)
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerfiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
linkResp, err := Tester.Client.OIDCv2.CreateCallback(CTXLOGIN, &oidc_pb.CreateCallbackRequest{
AuthRequestId: authRequestID,
CallbackKind: &oidc_pb.CreateCallbackRequest_Session{
@ -75,7 +75,7 @@ func TestOPStorage_CreateAccessToken_code(t *testing.T) {
func TestOPStorage_CreateAccessToken_implicit(t *testing.T) {
clientID := createImplicitClient(t)
authRequestID := createAuthRequestImplicit(t, clientID, redirectURIImplicit)
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerfiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
linkResp, err := Tester.Client.OIDCv2.CreateCallback(CTXLOGIN, &oidc_pb.CreateCallbackRequest{
AuthRequestId: authRequestID,
CallbackKind: &oidc_pb.CreateCallbackRequest_Session{
@ -125,7 +125,7 @@ func TestOPStorage_CreateAccessToken_implicit(t *testing.T) {
func TestOPStorage_CreateAccessAndRefreshTokens_code(t *testing.T) {
clientID := createClient(t)
authRequestID := createAuthRequest(t, clientID, redirectURI, oidc.ScopeOpenID, oidc.ScopeOfflineAccess)
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerfiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
linkResp, err := Tester.Client.OIDCv2.CreateCallback(CTXLOGIN, &oidc_pb.CreateCallbackRequest{
AuthRequestId: authRequestID,
CallbackKind: &oidc_pb.CreateCallbackRequest_Session{
@ -150,7 +150,7 @@ func TestOPStorage_CreateAccessAndRefreshTokens_refresh(t *testing.T) {
provider, err := Tester.CreateRelyingParty(CTX, clientID, redirectURI)
require.NoError(t, err)
authRequestID := createAuthRequest(t, clientID, redirectURI, oidc.ScopeOpenID, oidc.ScopeOfflineAccess)
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerfiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
linkResp, err := Tester.Client.OIDCv2.CreateCallback(CTXLOGIN, &oidc_pb.CreateCallbackRequest{
AuthRequestId: authRequestID,
CallbackKind: &oidc_pb.CreateCallbackRequest_Session{
@ -186,7 +186,7 @@ func TestOPStorage_RevokeToken_access_token(t *testing.T) {
provider, err := Tester.CreateRelyingParty(CTX, clientID, redirectURI)
require.NoError(t, err)
authRequestID := createAuthRequest(t, clientID, redirectURI, oidc.ScopeOpenID, oidc.ScopeOfflineAccess)
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerfiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
linkResp, err := Tester.Client.OIDCv2.CreateCallback(CTXLOGIN, &oidc_pb.CreateCallbackRequest{
AuthRequestId: authRequestID,
CallbackKind: &oidc_pb.CreateCallbackRequest_Session{
@ -229,7 +229,7 @@ func TestOPStorage_RevokeToken_access_token_invalid_token_hint_type(t *testing.T
provider, err := Tester.CreateRelyingParty(CTX, clientID, redirectURI)
require.NoError(t, err)
authRequestID := createAuthRequest(t, clientID, redirectURI, oidc.ScopeOpenID, oidc.ScopeOfflineAccess)
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerfiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
linkResp, err := Tester.Client.OIDCv2.CreateCallback(CTXLOGIN, &oidc_pb.CreateCallbackRequest{
AuthRequestId: authRequestID,
CallbackKind: &oidc_pb.CreateCallbackRequest_Session{
@ -266,7 +266,7 @@ func TestOPStorage_RevokeToken_refresh_token(t *testing.T) {
provider, err := Tester.CreateRelyingParty(CTX, clientID, redirectURI)
require.NoError(t, err)
authRequestID := createAuthRequest(t, clientID, redirectURI, oidc.ScopeOpenID, oidc.ScopeOfflineAccess)
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerfiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
linkResp, err := Tester.Client.OIDCv2.CreateCallback(CTXLOGIN, &oidc_pb.CreateCallbackRequest{
AuthRequestId: authRequestID,
CallbackKind: &oidc_pb.CreateCallbackRequest_Session{
@ -309,7 +309,7 @@ func TestOPStorage_RevokeToken_refresh_token_invalid_token_type_hint(t *testing.
provider, err := Tester.CreateRelyingParty(CTX, clientID, redirectURI)
require.NoError(t, err)
authRequestID := createAuthRequest(t, clientID, redirectURI, oidc.ScopeOpenID, oidc.ScopeOfflineAccess)
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerfiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
linkResp, err := Tester.Client.OIDCv2.CreateCallback(CTXLOGIN, &oidc_pb.CreateCallbackRequest{
AuthRequestId: authRequestID,
CallbackKind: &oidc_pb.CreateCallbackRequest_Session{
@ -344,7 +344,7 @@ func TestOPStorage_RevokeToken_refresh_token_invalid_token_type_hint(t *testing.
func TestOPStorage_RevokeToken_invalid_client(t *testing.T) {
clientID := createClient(t)
authRequestID := createAuthRequest(t, clientID, redirectURI, oidc.ScopeOpenID, oidc.ScopeOfflineAccess)
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerfiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
linkResp, err := Tester.Client.OIDCv2.CreateCallback(CTXLOGIN, &oidc_pb.CreateCallbackRequest{
AuthRequestId: authRequestID,
CallbackKind: &oidc_pb.CreateCallbackRequest_Session{
@ -376,7 +376,7 @@ func TestOPStorage_TerminateSession(t *testing.T) {
provider, err := Tester.CreateRelyingParty(CTX, clientID, redirectURI)
require.NoError(t, err)
authRequestID := createAuthRequest(t, clientID, redirectURI)
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerfiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
linkResp, err := Tester.Client.OIDCv2.CreateCallback(CTXLOGIN, &oidc_pb.CreateCallbackRequest{
AuthRequestId: authRequestID,
CallbackKind: &oidc_pb.CreateCallbackRequest_Session{
@ -413,7 +413,7 @@ func TestOPStorage_TerminateSession_refresh_grant(t *testing.T) {
provider, err := Tester.CreateRelyingParty(CTX, clientID, redirectURI)
require.NoError(t, err)
authRequestID := createAuthRequest(t, clientID, redirectURI, oidc.ScopeOpenID, oidc.ScopeOfflineAccess)
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerfiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
linkResp, err := Tester.Client.OIDCv2.CreateCallback(CTXLOGIN, &oidc_pb.CreateCallbackRequest{
AuthRequestId: authRequestID,
CallbackKind: &oidc_pb.CreateCallbackRequest_Session{
@ -457,7 +457,7 @@ func TestOPStorage_TerminateSession_empty_id_token_hint(t *testing.T) {
provider, err := Tester.CreateRelyingParty(CTX, clientID, redirectURI)
require.NoError(t, err)
authRequestID := createAuthRequest(t, clientID, redirectURI)
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerfiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
linkResp, err := Tester.Client.OIDCv2.CreateCallback(CTXLOGIN, &oidc_pb.CreateCallbackRequest{
AuthRequestId: authRequestID,
CallbackKind: &oidc_pb.CreateCallbackRequest_Session{

View File

@ -21,7 +21,7 @@ import (
func TestOPStorage_SetUserinfoFromToken(t *testing.T) {
clientID := createClient(t)
authRequestID := createAuthRequest(t, clientID, redirectURI, oidc.ScopeOpenID, oidc.ScopeProfile, oidc.ScopeEmail, oidc.ScopeOfflineAccess)
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerfiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
linkResp, err := Tester.Client.OIDCv2.CreateCallback(CTXLOGIN, &oidc_pb.CreateCallbackRequest{
AuthRequestId: authRequestID,
CallbackKind: &oidc_pb.CreateCallbackRequest_Session{
@ -67,7 +67,7 @@ func TestOPStorage_SetIntrospectionFromToken(t *testing.T) {
scope := []string{oidc.ScopeOpenID, oidc.ScopeProfile, oidc.ScopeEmail, oidc.ScopeOfflineAccess}
authRequestID := createAuthRequest(t, app.GetClientId(), redirectURI, scope...)
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerfiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
linkResp, err := Tester.Client.OIDCv2.CreateCallback(CTXLOGIN, &oidc_pb.CreateCallbackRequest{
AuthRequestId: authRequestID,
CallbackKind: &oidc_pb.CreateCallbackRequest_Session{

View File

@ -57,7 +57,7 @@ func TestMain(m *testing.M) {
func Test_ZITADEL_API_missing_audience_scope(t *testing.T) {
clientID := createClient(t)
authRequestID := createAuthRequest(t, clientID, redirectURI, oidc.ScopeOpenID)
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerfiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
linkResp, err := Tester.Client.OIDCv2.CreateCallback(CTXLOGIN, &oidc_pb.CreateCallbackRequest{
AuthRequestId: authRequestID,
CallbackKind: &oidc_pb.CreateCallbackRequest_Session{
@ -148,7 +148,7 @@ func Test_ZITADEL_API_missing_mfa(t *testing.T) {
func Test_ZITADEL_API_success(t *testing.T) {
clientID := createClient(t)
authRequestID := createAuthRequest(t, clientID, redirectURI, oidc.ScopeOpenID, zitadelAudienceScope)
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerfiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
linkResp, err := Tester.Client.OIDCv2.CreateCallback(CTXLOGIN, &oidc_pb.CreateCallbackRequest{
AuthRequestId: authRequestID,
CallbackKind: &oidc_pb.CreateCallbackRequest_Session{
@ -177,7 +177,7 @@ func Test_ZITADEL_API_success(t *testing.T) {
func Test_ZITADEL_API_inactive_access_token(t *testing.T) {
clientID := createClient(t)
authRequestID := createAuthRequest(t, clientID, redirectURI, oidc.ScopeOpenID, oidc.ScopeOfflineAccess, zitadelAudienceScope)
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerfiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
linkResp, err := Tester.Client.OIDCv2.CreateCallback(CTXLOGIN, &oidc_pb.CreateCallbackRequest{
AuthRequestId: authRequestID,
CallbackKind: &oidc_pb.CreateCallbackRequest_Session{
@ -219,7 +219,7 @@ func Test_ZITADEL_API_terminated_session(t *testing.T) {
provider, err := Tester.CreateRelyingParty(CTX, clientID, redirectURI)
require.NoError(t, err)
authRequestID := createAuthRequest(t, clientID, redirectURI, oidc.ScopeOpenID, oidc.ScopeOfflineAccess, zitadelAudienceScope)
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerfiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
linkResp, err := Tester.Client.OIDCv2.CreateCallback(CTXLOGIN, &oidc_pb.CreateCallbackRequest{
AuthRequestId: authRequestID,
CallbackKind: &oidc_pb.CreateCallbackRequest_Session{

View File

@ -119,7 +119,8 @@ func NewServer(
}
server := &Server{
LegacyServer: op.NewLegacyServer(provider, endpoints(config.CustomEndpoints)),
LegacyServer: op.NewLegacyServer(provider, endpoints(config.CustomEndpoints)),
signingKeyAlgorithm: config.SigningKeyAlgorithm,
}
metricTypes := []metrics.MetricType{metrics.MetricTypeRequestCount, metrics.MetricTypeStatusCode, metrics.MetricTypeTotalCount}
server.Handler = op.RegisterLegacyServer(server, op.WithHTTPMiddleware(

View File

@ -6,12 +6,14 @@ import (
"github.com/zitadel/oidc/v3/pkg/oidc"
"github.com/zitadel/oidc/v3/pkg/op"
"github.com/zitadel/zitadel/internal/telemetry/tracing"
)
type Server struct {
http.Handler
*op.LegacyServer
signingKeyAlgorithm string
}
func endpoints(endpointConfig *EndpointConfig) op.Endpoints {
@ -79,7 +81,7 @@ func (s *Server) Discovery(ctx context.Context, r *op.Request[struct{}]) (_ *op.
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
return s.LegacyServer.Discovery(ctx, r)
return op.NewResponse(s.createDiscoveryConfig(ctx)), nil
}
func (s *Server) Keys(ctx context.Context, r *op.Request[struct{}]) (_ *op.Response, err error) {
@ -186,3 +188,34 @@ func (s *Server) EndSession(ctx context.Context, r *op.Request[oidc.EndSessionRe
return s.LegacyServer.EndSession(ctx, r)
}
func (s *Server) createDiscoveryConfig(ctx context.Context) *oidc.DiscoveryConfiguration {
issuer := op.IssuerFromContext(ctx)
return &oidc.DiscoveryConfiguration{
Issuer: issuer,
AuthorizationEndpoint: s.Endpoints().Authorization.Absolute(issuer),
TokenEndpoint: s.Endpoints().Token.Absolute(issuer),
IntrospectionEndpoint: s.Endpoints().Introspection.Absolute(issuer),
UserinfoEndpoint: s.Endpoints().Userinfo.Absolute(issuer),
RevocationEndpoint: s.Endpoints().Revocation.Absolute(issuer),
EndSessionEndpoint: s.Endpoints().EndSession.Absolute(issuer),
JwksURI: s.Endpoints().JwksURI.Absolute(issuer),
DeviceAuthorizationEndpoint: s.Endpoints().DeviceAuthorization.Absolute(issuer),
ScopesSupported: op.Scopes(s.Provider()),
ResponseTypesSupported: op.ResponseTypes(s.Provider()),
GrantTypesSupported: op.GrantTypes(s.Provider()),
SubjectTypesSupported: op.SubjectTypes(s.Provider()),
IDTokenSigningAlgValuesSupported: []string{s.signingKeyAlgorithm},
RequestObjectSigningAlgValuesSupported: op.RequestObjectSigAlgorithms(s.Provider()),
TokenEndpointAuthMethodsSupported: op.AuthMethodsTokenEndpoint(s.Provider()),
TokenEndpointAuthSigningAlgValuesSupported: op.TokenSigAlgorithms(s.Provider()),
IntrospectionEndpointAuthSigningAlgValuesSupported: op.IntrospectionSigAlgorithms(s.Provider()),
IntrospectionEndpointAuthMethodsSupported: op.AuthMethodsIntrospectionEndpoint(s.Provider()),
RevocationEndpointAuthSigningAlgValuesSupported: op.RevocationSigAlgorithms(s.Provider()),
RevocationEndpointAuthMethodsSupported: op.AuthMethodsRevocationEndpoint(s.Provider()),
ClaimsSupported: op.SupportedClaims(s.Provider()),
CodeChallengeMethodsSupported: op.CodeChallengeMethods(s.Provider()),
UILocalesSupported: s.Provider().SupportedUILocales(),
RequestParameterSupported: s.Provider().RequestObjectSupported(),
}
}

View File

@ -0,0 +1,119 @@
package oidc
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/zitadel/oidc/v3/pkg/oidc"
"github.com/zitadel/oidc/v3/pkg/op"
"golang.org/x/text/language"
)
func TestServer_createDiscoveryConfig(t *testing.T) {
type fields struct {
LegacyServer *op.LegacyServer
signingKeyAlgorithm string
}
type args struct {
ctx context.Context
}
tests := []struct {
name string
fields fields
args args
want *oidc.DiscoveryConfiguration
}{
{
"config",
fields{
LegacyServer: op.NewLegacyServer(
func() *op.Provider {
provider, _ := op.NewForwardedOpenIDProvider("path",
&op.Config{
CodeMethodS256: true,
AuthMethodPost: true,
AuthMethodPrivateKeyJWT: true,
GrantTypeRefreshToken: true,
RequestObjectSupported: true,
SupportedUILocales: []language.Tag{language.English, language.German},
},
nil,
)
return provider
}(),
op.Endpoints{
Authorization: op.NewEndpoint("auth"),
Token: op.NewEndpoint("token"),
Introspection: op.NewEndpoint("introspect"),
Userinfo: op.NewEndpoint("userinfo"),
Revocation: op.NewEndpoint("revoke"),
EndSession: op.NewEndpoint("logout"),
JwksURI: op.NewEndpoint("keys"),
DeviceAuthorization: op.NewEndpoint("device"),
},
),
signingKeyAlgorithm: "RS256",
},
args{
ctx: op.ContextWithIssuer(context.Background(), "https://issuer.com"),
},
&oidc.DiscoveryConfiguration{
Issuer: "https://issuer.com",
AuthorizationEndpoint: "https://issuer.com/auth",
TokenEndpoint: "https://issuer.com/token",
IntrospectionEndpoint: "https://issuer.com/introspect",
UserinfoEndpoint: "https://issuer.com/userinfo",
RevocationEndpoint: "https://issuer.com/revoke",
EndSessionEndpoint: "https://issuer.com/logout",
DeviceAuthorizationEndpoint: "https://issuer.com/device",
CheckSessionIframe: "",
JwksURI: "https://issuer.com/keys",
RegistrationEndpoint: "",
ScopesSupported: []string{oidc.ScopeOpenID, oidc.ScopeProfile, oidc.ScopeEmail, oidc.ScopePhone, oidc.ScopeAddress, oidc.ScopeOfflineAccess},
ResponseTypesSupported: []string{string(oidc.ResponseTypeCode), string(oidc.ResponseTypeIDTokenOnly), string(oidc.ResponseTypeIDToken)},
ResponseModesSupported: nil,
GrantTypesSupported: []oidc.GrantType{oidc.GrantTypeCode, oidc.GrantTypeImplicit, oidc.GrantTypeRefreshToken, oidc.GrantTypeBearer},
ACRValuesSupported: nil,
SubjectTypesSupported: []string{"public"},
IDTokenSigningAlgValuesSupported: []string{"RS256"},
IDTokenEncryptionAlgValuesSupported: nil,
IDTokenEncryptionEncValuesSupported: nil,
UserinfoSigningAlgValuesSupported: nil,
UserinfoEncryptionAlgValuesSupported: nil,
UserinfoEncryptionEncValuesSupported: nil,
RequestObjectSigningAlgValuesSupported: []string{"RS256"},
RequestObjectEncryptionAlgValuesSupported: nil,
RequestObjectEncryptionEncValuesSupported: nil,
TokenEndpointAuthMethodsSupported: []oidc.AuthMethod{oidc.AuthMethodNone, oidc.AuthMethodBasic, oidc.AuthMethodPost, oidc.AuthMethodPrivateKeyJWT},
TokenEndpointAuthSigningAlgValuesSupported: []string{"RS256"},
RevocationEndpointAuthMethodsSupported: []oidc.AuthMethod{oidc.AuthMethodNone, oidc.AuthMethodBasic, oidc.AuthMethodPost, oidc.AuthMethodPrivateKeyJWT},
RevocationEndpointAuthSigningAlgValuesSupported: []string{"RS256"},
IntrospectionEndpointAuthMethodsSupported: []oidc.AuthMethod{oidc.AuthMethodBasic, oidc.AuthMethodPrivateKeyJWT},
IntrospectionEndpointAuthSigningAlgValuesSupported: []string{"RS256"},
DisplayValuesSupported: nil,
ClaimTypesSupported: nil,
ClaimsSupported: []string{"sub", "aud", "exp", "iat", "iss", "auth_time", "nonce", "acr", "amr", "c_hash", "at_hash", "act", "scopes", "client_id", "azp", "preferred_username", "name", "family_name", "given_name", "locale", "email", "email_verified", "phone_number", "phone_number_verified"},
ClaimsParameterSupported: false,
CodeChallengeMethodsSupported: []oidc.CodeChallengeMethod{"S256"},
ServiceDocumentation: "",
ClaimsLocalesSupported: nil,
UILocalesSupported: []language.Tag{language.English, language.German},
RequestParameterSupported: true,
RequestURIParameterSupported: false,
RequireRequestURIRegistration: false,
OPPolicyURI: "",
OPTermsOfServiceURI: "",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &Server{
LegacyServer: tt.fields.LegacyServer,
signingKeyAlgorithm: tt.fields.signingKeyAlgorithm,
}
assert.Equalf(t, tt.want, s.createDiscoveryConfig(tt.args.ctx), "createDiscoveryConfig(%v)", tt.args.ctx)
})
}
}

View File

@ -242,6 +242,8 @@ RegistrationUser:
Bulgarian: Български
Portuguese: Português
Macedonian: Македонски
Czech: Čeština
Russian: Русский
GenderLabel: Пол
Female: Женски пол
Male: Мъжки
@ -277,6 +279,8 @@ ExternalRegistrationUserOverview:
Bulgarian: Български
Portuguese: Português
Macedonian: Македонски
Czech: Čeština
Russian: Русский
TosAndPrivacyLabel: Правила и условия
TosConfirm: Приемам
TosLinkText: TOS
@ -336,7 +340,8 @@ ExternalNotFound:
Bulgarian: Български
Portuguese: Português
Macedonian: Македонски
Czech: Čeština
Russian: Русский
DeviceAuth:
Title: Упълномощаване на устройството
UserCode:

View File

@ -0,0 +1,487 @@
Login:
Title: Vítejte zpět!
Description: Zadejte své přihlašovací údaje.
TitleLinking: Přihlášení pro propojení uživatele
DescriptionLinking: Zadejte své přihlašovací údaje pro propojení vašeho externího účtu.
LoginNameLabel: Přihlašovací jméno
UsernamePlaceHolder: uživatelské jméno
LoginnamePlaceHolder: uzivatelskejmeno@doména
ExternalUserDescription: Přihlášení s externím účtem.
MustBeMemberOfOrg: Uživatel musí být členem organizace {{.OrgName}}.
RegisterButtonText: Registrovat
NextButtonText: Další
LDAP:
Title: Přihlášení
Description: Zadejte své přihlašovací údaje.
LoginNameLabel: Přihlašovací jméno
PasswordLabel: Heslo
NextButtonText: Další
SelectAccount:
Title: Vyberte účet
Description: Použijte svůj účet
TitleLinking: Vyberte účet pro propojení uživatele
DescriptionLinking: Vyberte účet pro propojení s vaším externím uživatelem.
OtherUser: Jiný uživatel
SessionState0: aktivní
SessionState1: Odhlášen
MustBeMemberOfOrg: Uživatel musí být členem organizace {{.OrgName}}.
Password:
Title: Heslo
Description: Zadejte své přihlašovací údaje.
PasswordLabel: Heslo
MinLength: Minimální délka
HasUppercase: Velké písmeno
HasLowercase: Malé písmeno
HasNumber: Číslo
HasSymbol: Symbol
Confirmation: Potvrzení shody
ResetLinkText: Obnovit heslo
BackButtonText: Zpět
NextButtonText: Další
UsernameChange:
Title: Změna uživatelského jména
Description: Nastavte své nové uživatelské jméno
UsernameLabel: Uživatelské jméno
CancelButtonText: Zrušit
NextButtonText: Další
UsernameChangeDone:
Title: Uživatelské jméno bylo změněno
Description: Vaše uživatelské jméno bylo úspěšně změněno.
NextButtonText: Další
InitPassword:
Title: Nastavit heslo
Description: Obdrželi jste kód, který musíte vložit níže, abyste nastavili své nové heslo.
CodeLabel: Kód
NewPasswordLabel: Nové heslo
NewPasswordConfirmLabel: Potvrzení hesla
ResendButtonText: Znovu poslat kód
NextButtonText: Další
InitPasswordDone:
Title: Heslo nastaveno
Description: Heslo bylo úspěšně nastaveno
NextButtonText: Další
CancelButtonText: Zrušit
InitUser:
Title: Aktivovat uživatele
Description: Ověřte váš e-mail pomocí níže uvedeného kódu a nastavte své heslo.
CodeLabel: Kód
NewPasswordLabel: Nové heslo
NewPasswordConfirm: Potvrzení hesla
NextButtonText: Další
ResendButtonText: Znovu poslat kód
InitUserDone:
Title: Uživatel aktivován
Description: E-mail ověřen a heslo úspěšně nastaveno
NextButtonText: Další
CancelButtonText: Zrušit
InitMFAPrompt:
Title: Nastavení 2-faktorové autentizace
Description: 2-faktorová autentizace vám poskytuje další zabezpečení pro váš uživatelský účet. Tím je zajištěno, že k vašemu účtu máte přístup pouze vy.
Provider0: Aplikace pro ověřování (např. Google/Microsoft Authenticator, Authy)
Provider1: Zařízením závislé (např. FaceID, Windows Hello, Otisk prstu)
Provider3: OTP SMS
Provider4: OTP E-mail
NextButtonText: Další
SkipButtonText: Přeskočit
InitMFAOTP:
Title: 2-Faktorové ověření
Description: Vytvořte svou 2-faktorovou autentizaci. Stáhněte si ověřovací aplikaci, pokud ji ještě nemáte.
OTPDescription: Naskenujte kód svou ověřovací aplikací (např. Google/Microsoft Authenticator, Authy) nebo zkopírujte tajemství a vložte níže vygenerovaný kód.
SecretLabel: Tajemství
CodeLabel: Kód
NextButtonText: Další
CancelButtonText: Zrušit
InitMFAOTPSMS:
Title: 2-Faktorové ověření
DescriptionPhone: Vytvořte svou 2-faktorovou autentizaci. Zadejte své telefonní číslo k jeho ověření.
DescriptionCode: Vytvořte svou 2-faktorovou autentizaci. Zadejte obdržený kód k ověření vašeho telefonního čísla.
PhoneLabel: Telefon
CodeLabel: Kód
EditButtonText: Upravit
ResendButtonText: Znovu poslat kód
NextButtonText: Další
InitMFAU2F:
Title: Přidat bezpečnostní klíč
Description: Bezpečnostní klíč je ověřovací metoda, která může být integrována do vašeho telefonu, používat Bluetooth nebo se přímo zapojit do USB portu vašeho počítače.
TokenNameLabel: Název bezpečnostního klíče / zařízení
NotSupported: WebAuthN není podporován vaším prohlížečem. Ujistěte se, že máte aktuální verzi, nebo použijte jiný (např. Chrome, Safari, Firefox)
RegisterTokenButtonText: Přidat bezpečnostní klíč
ErrorRetry: Zkuste to znovu, vytvořte novou výzvu nebo vyberte jinou metodu.
InitMFADone:
Title: 2-Faktor ověřen
Description: Skvělé! Úspěšně jste nastavili svou 2-faktorovou autentizaci a váš účet je nyní mnohem bezpečnější. Faktor musí být zadán při každém přihlášení.
NextButtonText: Další
CancelButtonText: Zrušit
MFAProvider:
Provider0: Aplikace pro ověřování (např. Google/Microsoft Authenticator, Authy)
Provider1: Zařízením závislé (např. FaceID, Windows Hello, Otisk prstu)
Provider3: OTP SMS
Provider4: OTP E-mail
ChooseOther: nebo vyberte jinou možnost
VerifyMFAOTP:
Title: Ověřte 2-Faktor
Description: Ověřte váš druhý faktor
CodeLabel: Kód
NextButtonText: Další
VerifyOTP:
Title: Ověřte 2-Faktor
Description: Ověřte váš druhý faktor
CodeLabel: Kód
ResendButtonText: Znovu poslat kód
NextButtonText: Další
VerifyMFAU2F:
Title: 2-Faktorové ověření
Description: Ověřte váš 2-Faktor pomocí registrovaného zařízení (např. FaceID, Windows Hello, Otisk prstu)
NotSupported: WebAuthN není podporován vaším prohlížečem. Ujistěte se, že používáte nejnovější verzi, nebo změňte prohlížeč na podporovaný (Chrome, Safari, Firefox)
ErrorRetry: Zkuste to znovu, vytvořte nový požadavek nebo vyberte jinou metodu.
ValidateTokenButtonText: Ověřte 2-Faktor
Passwordless:
Title: Bezheslové přihlášení
Description: Přihlaste se pomocí ověřovacích metod poskytnutých vaším zařízením, jako je FaceID, Windows Hello nebo Otisk prstu.
NotSupported: WebAuthN není podporován vaším prohlížečem. Ujistěte se, že máte aktuální verzi, nebo použijte jiný (např. Chrome, Safari, Firefox).
ErrorRetry: Zkuste to znovu, vytvořte novou výzvu nebo vyberte jinou metodu.
LoginWithPwButtonText: Přihlásit se heslem
ValidateTokenButtonText: Přihlásit se bez hesla
PasswordlessPrompt:
Title: Nastavení bezheslového přihlášení
Description: Chcete nastavit přihlášení bez hesla? (Ověřovací metody vašeho zařízení, jako je FaceID, Windows Hello nebo Otisk prstu)
DescriptionInit: Musíte nastavit přihlášení bez hesla. Použijte odkaz, který jste obdrželi, pro registraci vašeho zařízení.
PasswordlessButtonText: Přihlásit se bez hesla
NextButtonText: Další
SkipButtonText: Přeskočit
PasswordlessRegistration:
Title: Nastavení bezheslového přihlášení
Description: Přidejte své ověření zadáním názvu (např. MůjMobil, MacBook atd.) a poté kliknutím na tlačítko 'Registrovat bez hesla' níže.
TokenNameLabel: Název zařízení
NotSupported: WebAuthN není podporován vaším prohlížečem. Ujistěte se, že máte aktuální verzi, nebo použijte jiný (např. Chrome, Safari, Firefox).
RegisterTokenButtonText: Registrovat bez hesla
ErrorRetry: Zkuste to znovu, vytvořte novou výzvu nebo vyberte jinou metodu.
PasswordlessRegistrationDone:
Title: Nastavení bezheslového přihlášení dokončeno
Description: Zařízení pro přihlášení bez hesla úspěšně přidáno.
DescriptionClose: Nyní můžete toto okno zavřít.
NextButtonText: Další
CancelButtonText: Zrušit
PasswordChange:
Title: Změna hesla
Description: Změňte si heslo. Zadejte své staré a nové heslo.
OldPasswordLabel: Staré heslo
NewPasswordLabel: Nové heslo
NewPasswordConfirmLabel: Potvrzení hesla
CancelButtonText: Zrušit
NextButtonText: Další
Footer: Patička
PasswordChangeDone:
Title: Změna hesla
Description: Vaše heslo bylo úspěšně změněno.
NextButtonText: Další
PasswordResetDone:
Title: Odkaz na resetování hesla odeslán
Description: Pro dokončení změny hesla zkontrolujte váš e-mail a postupujte podle instrukcí.
NextButtonText: Další
EmailVerification:
Title: Ověření e-mailu
Description: Poslali jsme vám e-mail pro ověření vaší adresy. Zadejte kód do níže uvedeného formuláře.
CodeLabel: Kód
NextButtonText: Další
ResendButtonText: Znovu poslat kód
EmailVerificationDone:
Title: Ověření e-mailu
Description: Vaše e-mailová adresa byla úspěšně ověřena.
NextButtonText: Další
CancelButtonText: Zrušit
LoginButtonText: Přihlásit se
RegisterOption:
Title: Možnosti registrace
Description: Vyberte si, jak se chcete zaregistrovat
RegisterUsernamePasswordButtonText: Pomocí uživatelského jména a hesla
ExternalLoginDescription: nebo se zaregistrujte s externím uživatelem
LoginButtonText: Přihlásit se
RegistrationUser:
Title: Registrace
Description: Zadejte své uživatelské údaje. Váš e-mail bude použit jako vaše přihlašovací jméno.
DescriptionOrgRegister: Zadejte své uživatelské údaje.
EmailLabel: E-mail
UsernameLabel: Uživatelské jméno
FirstnameLabel: Křestní jméno
LastnameLabel: Příjmení
LanguageLabel: Jazyk
German: Deutsch
English: English
Italian: Italiano
French: Français
Chinese: 简体中文
Polish: Polski
Japanese: 日本語
Spanish: Español
Bulgarian: Български
Portuguese: Português
Macedonian: Македонски
Czech: Čeština
Russian: Русский
GenderLabel: Pohlaví
Female: Žena
Male: Muž
Diverse: různorodé / X
PasswordLabel: Heslo
PasswordConfirmLabel: Potvrzení hesla
TosAndPrivacyLabel: Obchodní podmínky
TosConfirm: Souhlasím s
TosLinkText: obchodními podmínkami
PrivacyConfirm: Souhlasím se
PrivacyLinkText: zásadami ochrany osobních údajů
ExternalLogin: nebo se zaregistrujte s externím uživatelem
BackButtonText: Přihlásit se
NextButtonText: Další
ExternalRegistrationUserOverview:
Title: Registrace externího uživatele
Description: Vaše uživatelské údaje byly převzaty z vybraného poskytovatele. Nyní je můžete změnit nebo doplnit.
EmailLabel: E-mail
UsernameLabel: Uživatelské jméno
FirstnameLabel: Křestní jméno
LastnameLabel: Příjmení
NicknameLabel: Přezdívka
PhoneLabel: Telefonní číslo
LanguageLabel: Jazyk
German: Deutsch
English: English
Italian: Italiano
French: Français
Chinese: 简体中文
Polish: Polski
Japanese: 日本語
Spanish: Español
Bulgarian: Български
Portuguese: Português
Macedonian: Македонски
Czech: Čeština
Russian: Русский
TosAndPrivacyLabel: Obchodní podmínky
TosConfirm: Souhlasím s
TosLinkText: obchodními podmínkami
PrivacyConfirm: Souhlasím se
PrivacyLinkText: zásadami ochrany osobních údajů
ExternalLogin: nebo se zaregistrujte s externím uživatelem
BackButtonText: Zpět
NextButtonText: Uložit
RegistrationOrg:
Title: Registrace organizace
Description: Zadejte název vaší organizace a uživatelské údaje.
OrgNameLabel: Název organizace
EmailLabel: E-mail
UsernameLabel: Uživatelské jméno
FirstnameLabel: Křestní jméno
LastnameLabel: Příjmení
PasswordLabel: Heslo
PasswordConfirmLabel: Potvrzení hesla
TosAndPrivacyLabel: Obchodní podmínky
TosConfirm: Souhlasím s
TosLinkText: obchodními podmínkami
PrivacyConfirm: Souhlasím se
PrivacyLinkText: zásadami ochrany osobních údajů
SaveButtonText: Vytvořit organizaci
LoginSuccess:
Title: Úspěšné přihlášení
AutoRedirectDescription: Budete automaticky přesměrováni zpět do vaší aplikace. Pokud ne, klikněte na tlačítko níže. Poté můžete okno zavřít.
RedirectedDescription: Nyní můžete okno zavřít.
NextButtonText: Další
LogoutDone:
Title: Odhlášení proběhlo úspěšně
Description: Byli jste úspěšně odhlášeni.
LoginButtonText: Přihlásit se
LinkingUsersDone:
Title: Propojení uživatele
Description: Uživatel propojen.
CancelButtonText: Zrušit
NextButtonText: Další
ExternalNotFound:
Title: Externí uživatel nenalezen
Description: Externí uživatel nebyl nalezen. Chcete propojit svého uživatele nebo automaticky zaregistrovat nového?
LinkButtonText: Propojit
AutoRegisterButtonText: Registrovat
TosAndPrivacyLabel: Obchodní podmínky
TosConfirm: Souhlasím s
TosLinkText: obchodními podmínkami
PrivacyConfirm: Souhlasím s
PrivacyLinkText: zásadami ochrany osobních údajů
German: Deutsch
English: English
Italian: Italiano
French: Français
Chinese: 简体中文
Polish: Polski
Japanese: 日本語
Spanish: Español
Bulgarian: Български
Portuguese: Português
Macedonian: Македонски
Czech: Čeština
Russian: Русский
DeviceAuth:
Title: Autorizace zařízení
UserCode:
Label: Uživatelský kód
Description: Zadejte uživatelský kód zobrazený na zařízení.
ButtonNext: Další
Action:
Description: Povolte přístup zařízení.
GrantDevice: chystáte se povolit zařízení
AccessToScopes: přístup k následujícím rozsahům
Button:
Allow: Povolit
Deny: Zamítnout
Done:
Description: Hotovo.
Approved: Autorizace zařízení schválena. Nyní se můžete vrátit k zařízení.
Denied: Autorizace zařízení zamítnuta. Nyní se můžete vrátit k zařízení.
Footer:
PoweredBy: Provozováno pomocí
Tos: Obchodní podmínky
PrivacyPolicy: Zásady ochrany osobních údajů
Help: Pomoc
SupportEmail: E-mailová podpora
SignIn: Přihlašte se pomocí {{.Provider}}
Errors:
Internal: Došlo k interní chybě
AuthRequest:
NotFound: Authrequest nebyl nalezen
UserAgentNotCorresponding: User Agent neodpovídá
UserAgentNotFound: ID User Agenta nebylo nalezeno
TokenNotFound: Token nebyl nalezen
RequestTypeNotSupported: Typ požadavku není podporován
MissingParameters: Chybějící požadované parametry
User:
NotFound: Uživatel nebyl nalezen
AlreadyExists: Uživatel již existuje
Inactive: Uživatel je neaktivní
NotFoundOnOrg: Uživatel nebyl nalezen ve zvolené organizaci
NotAllowedOrg: Uživatel není členem požadované organizace
NotMatchingUserID: ID uživatele a uživatele v authrequestu se neshodují
UserIDMissing: ID uživatele chybí
Invalid: Neplatná uživatelská data
DomainNotAllowedAsUsername: Doména je již rezervována a nelze ji použít jako uživatelské jméno
NotAllowedToLink: Uživatel nemá povoleno propojení s externím poskytovatelem přihlášení
Profile:
NotFound: Profil nenalezen
NotChanged: Profil nebyl změněn
Empty: Profil je prázdný
FirstNameEmpty: Jméno v profilu je prázdné
LastNameEmpty: Příjmení v profilu je prázdné
IDMissing: Chybí ID profilu
Email:
NotFound: E-mail nenalezen
Invalid: E-mail je neplatný
AlreadyVerified: E-mail je již ověřen
NotChanged: E-mail nebyl změněn
Empty: E-mail je prázdný
IDMissing: Chybí ID e-mailu
Phone:
NotFound: Telefon nenalezen
Invalid: Telefon je neplatný
AlreadyVerified: Telefon již byl ověřen
Empty: Telefon je prázdný
NotChanged: Telefon nebyl změněn
Address:
NotFound: Adresa nenalezena
NotChanged: Adresa nebyla změněna
Username:
AlreadyExists: Uživatelské jméno již je obsazené
Reserved: Uživatelské jméno není volné
Empty: Uživatelské jméno je prázdné
Password:
ConfirmationWrong: Potvrzení hesla je nesprávné
Empty: Heslo je prázdné
Invalid: Heslo je neplatné
InvalidAndLocked: Heslo je neplatné a uživatel je uzamčen, kontaktujte svého správce.
NotChanged: Nové heslo nesmí být stejné jako stávající heslo
UsernameOrPassword:
Invalid: Uživatelské jméno nebo heslo je neplatné
PasswordComplexityPolicy:
NotFound: Zásady složitosti hesla nenalezeny
MinLength: Heslo je příliš krátké
HasLower: Heslo musí obsahovat malé písmeno
HasUpper: Heslo musí obsahovat velké písmeno
HasNumber: Heslo musí obsahovat číslo
HasSymbol: Heslo musí obsahovat symbol
Code:
Expired: Kód vypršel
Invalid: Kód je neplatný
Empty: Kód je prázdný
CryptoCodeNil: Krypto kód je nulový
NotFound: Kód nebyl nalezen
GeneratorAlgNotSupported: Nepodporovaný algoritmus generátoru
EmailVerify:
UserIDEmpty: ID uživatele je prázdné
ExternalData:
CouldNotRead: Externí data nebylo možné správně přečíst
MFA:
NoProviders: Žádní dostupní poskytovatelé vícefaktorového ověřování
OTP:
AlreadyReady: Vícefaktorové OTP (jednorázové heslo) je již nastaveno
NotExisting: Vícefaktorové OTP (jednorázové heslo) neexistuje
InvalidCode: Neplatný kód
NotReady: Vícefaktorové OTP (jednorázové heslo) není připraveno
Locked: Uživatel je uzamčen
SomethingWentWrong: Něco se pokazilo
NotActive: Uživatel není aktivní
ExternalIDP:
IDPTypeNotImplemented: Typ IDP není implementován
NotAllowed: Externí poskytovatel přihlášení není povolen
IDPConfigIDEmpty: ID konfigurace poskytovatele identity je prázdné
ExternalUserIDEmpty: Externí ID uživatele je prázdné
UserDisplayNameEmpty: Zobrazované jméno uživatele je prázdné
NoExternalUserData: Nebyla přijata žádná externí uživatelská data
CreationNotAllowed: Vytvoření nového uživatele není na tomto poskytovateli povoleno
LinkingNotAllowed: Propojení uživatele není na tomto poskytovateli povoleno
GrantRequired: Přihlášení není možné. Uživatel musí mít alespoň jeden oprávnění na aplikaci. Prosím, kontaktujte svého správce.
ProjectRequired: Přihlášení není možné. Organizace uživatele musí být přidělena k projektu. Prosím, kontaktujte svého správce.
IdentityProvider:
InvalidConfig: Konfigurace poskytovatele identity je neplatná
IAM:
LockoutPolicy:
NotExisting: Zásady uzamčení neexistují
Org:
LoginPolicy:
RegistrationNotAllowed: Registrace není povolena
DeviceAuth:
NotExisting: Kód uživatelského zařízení neexistuje
optional: (volitelné)

View File

@ -245,6 +245,8 @@ RegistrationUser:
Bulgarian: Български
Portuguese: Português
Macedonian: Македонски
Czech: Čeština
Russian: Русский
GenderLabel: Geschlecht
Female: weiblich
Male: männlich
@ -281,6 +283,8 @@ ExternalRegistrationUserOverview:
Bulgarian: Български
Portuguese: Português
Macedonian: Македонски
Czech: Čeština
Russian: Русский
TosAndPrivacyLabel: Allgemeine Geschäftsbedingungen und Datenschutz
TosConfirm: Ich akzeptiere die
TosLinkText: AGB
@ -345,7 +349,8 @@ ExternalNotFound:
Bulgarian: Български
Portuguese: Português
Macedonian: Македонски
Czech: Čeština
Russian: Русский
DeviceAuth:
Title: Gerätezulassung
UserCode:

View File

@ -246,6 +246,8 @@ RegistrationUser:
Bulgarian: Български
Portuguese: Português
Macedonian: Македонски
Czech: Čeština
Russian: Русский
GenderLabel: Gender
Female: Female
Male: Male
@ -282,6 +284,8 @@ ExternalRegistrationUserOverview:
Bulgarian: Български
Portuguese: Português
Macedonian: Македонски
Czech: Čeština
Russian: Русский
TosAndPrivacyLabel: Terms and conditions
TosConfirm: I accept the
TosLinkText: TOS
@ -346,7 +350,8 @@ ExternalNotFound:
Bulgarian: Български
Portuguese: Português
Macedonian: Македонски
Czech: Čeština
Russian: Русский
DeviceAuth:
Title: Device Authorization
UserCode:

View File

@ -246,6 +246,8 @@ RegistrationUser:
Bulgarian: Български
Portuguese: Português
Macedonian: Македонски
Czech: Čeština
Russian: Русский
GenderLabel: Género
Female: Mujer
Male: Hombre
@ -282,6 +284,8 @@ ExternalRegistrationUserOverview:
Bulgarian: Български
Portuguese: Português
Macedonian: Македонски
Czech: Čeština
Russian: Русский
TosAndPrivacyLabel: Términos y condiciones
TosConfirm: Acepto los
TosLinkText: TDS
@ -346,6 +350,8 @@ ExternalNotFound:
Bulgarian: Български
Portuguese: Português
Macedonian: Македонски
Czech: Čeština
Russian: Русский
Footer:
PoweredBy: Powered By

View File

@ -246,6 +246,8 @@ RegistrationUser:
Bulgarian: Български
Portuguese: Português
Macedonian: Македонски
Czech: Čeština
Russian: Русский
GenderLabel: Genre
Female: Femme
Male: Homme
@ -282,6 +284,8 @@ ExternalRegistrationUserOverview:
Bulgarian: Български
Portuguese: Português
Macedonian: Македонски
Czech: Čeština
Russian: Русский
TosAndPrivacyLabel: Termes et conditions
TosConfirm: J'accepte les
TosLinkText: TOS
@ -346,6 +350,8 @@ ExternalNotFound:
Bulgarian: Български
Portuguese: Português
Macedonian: Македонски
Czech: Čeština
Russian: Русский
DeviceAuth:
Title: Autorisation de l'appareil

Some files were not shown because too many files have changed in this diff Show More