merge 'main' into next
2
.github/workflows/lint.yml
vendored
@ -52,7 +52,7 @@ jobs:
|
|||||||
-
|
-
|
||||||
uses: bufbuild/buf-breaking-action@v1
|
uses: bufbuild/buf-breaking-action@v1
|
||||||
with:
|
with:
|
||||||
against: "https://github.com/${{ github.repository }}.git#branch=main"
|
against: "https://github.com/${{ github.repository }}.git#branch=${{ github.base_ref }}"
|
||||||
|
|
||||||
console:
|
console:
|
||||||
if: ${{ github.event_name == 'pull_request' }}
|
if: ${{ github.event_name == 'pull_request' }}
|
||||||
|
@ -101,7 +101,8 @@ Please make sure you cover your changes with tests before marking a Pull Request
|
|||||||
- [ ] Integration tests against the gRPC server ensure that probable good and bad read and write permissions are tested.
|
- [ ] Integration tests against the gRPC server ensure that probable good and bad read and write permissions are tested.
|
||||||
- [ ] Integration tests against the gRPC server ensure that the API is easily usable despite eventual consistency.
|
- [ ] Integration tests against the gRPC server ensure that the API is easily usable despite eventual consistency.
|
||||||
- [ ] Integration tests against the gRPC server ensure that all probable login and registration flows are covered."
|
- [ ] Integration tests against the gRPC server ensure that all probable login and registration flows are covered."
|
||||||
- [ ] Integration tests ensure that certain commands send expected notifications.
|
- [ ] Integration tests ensure that certain commands emit expected events that trigger notifications.
|
||||||
|
- [ ] Integration tests ensure that certain events trigger expected notifications.
|
||||||
|
|
||||||
## Contribute
|
## Contribute
|
||||||
|
|
||||||
|
12
README.md
@ -34,10 +34,10 @@ Do you look for a user management that's quickly set up like Auth0 and open sour
|
|||||||
|
|
||||||
Do you have a project that requires multi-tenant user management with self-service for your customers?
|
Do you have a project that requires multi-tenant user management with self-service for your customers?
|
||||||
|
|
||||||
Look no further — ZITADEL combines the ease of Auth0 with the versatility of Keycloak.
|
Look no further — ZITADEL is the identity infrastructure, simplified for you.
|
||||||
|
|
||||||
We provide you with a wide range of out-of-the-box features to accelerate your project.
|
We provide you with a wide range of out-of-the-box features to accelerate your project.
|
||||||
Multi-tenancy with branding customization, secure login, self-service, OpenID Connect, OAuth2.x, SAML2, LDAP, Passwordless with FIDO2 (including Passkeys), OTP, U2F, and an unlimited audit trail is there for you, ready to use.
|
Multi-tenancy with branding customization, secure login, self-service, OpenID Connect, OAuth2.x, SAML2, LDAP, Passkeys / FIDO2, OTP, U2F, and an unlimited audit trail is there for you, ready to use.
|
||||||
|
|
||||||
With ZITADEL you can rely on a hardened and extensible turnkey solution to solve all of your authentication and authorization needs.
|
With ZITADEL you can rely on a hardened and extensible turnkey solution to solve all of your authentication and authorization needs.
|
||||||
|
|
||||||
@ -67,7 +67,7 @@ See all guides [here](https://zitadel.com/docs/self-hosting/deploy/overview)
|
|||||||
|
|
||||||
If you want to experience a hands-free ZITADEL, you should use [ZITADEL Cloud](https://zitadel.cloud).
|
If you want to experience a hands-free ZITADEL, you should use [ZITADEL Cloud](https://zitadel.cloud).
|
||||||
|
|
||||||
It is free for up to 25'000 authenticated requests and provides you all the features that make ZITADEL great.
|
ZITADEL Cloud comes with a free tier and provides you all the features that you find in the open source version.
|
||||||
Learn more about the [pay-as-you-go pricing](https://zitadel.com/pricing).
|
Learn more about the [pay-as-you-go pricing](https://zitadel.com/pricing).
|
||||||
|
|
||||||
### Example applications
|
### Example applications
|
||||||
@ -84,6 +84,7 @@ We built ZITADEL with a complex multi-tenancy architecture in mind and provide t
|
|||||||
Yet it offers everything you need for a customer identity ([CIAM](https://zitadel.com/docs/guides/solution-scenarios/b2c)) use case.
|
Yet it offers everything you need for a customer identity ([CIAM](https://zitadel.com/docs/guides/solution-scenarios/b2c)) use case.
|
||||||
|
|
||||||
- [API-first approach](https://zitadel.com/docs/apis/introduction)
|
- [API-first approach](https://zitadel.com/docs/apis/introduction)
|
||||||
|
- [Multi-tenancy](https://zitadel.com/docs/guides/solution-scenarios/b2b) authentication and access management
|
||||||
- Strong audit trail thanks to [event sourcing](https://zitadel.com/docs/concepts/eventstore/overview) as storage pattern
|
- Strong audit trail thanks to [event sourcing](https://zitadel.com/docs/concepts/eventstore/overview) as storage pattern
|
||||||
- [Actions](https://zitadel.com/docs/apis/actions/introduction) to react on events with custom code and extended ZITADEL for you needs
|
- [Actions](https://zitadel.com/docs/apis/actions/introduction) to react on events with custom code and extended ZITADEL for you needs
|
||||||
- [Branding](https://zitadel.com/docs/guides/manage/customize/branding) for a uniform user experience across multiple organizations
|
- [Branding](https://zitadel.com/docs/guides/manage/customize/branding) for a uniform user experience across multiple organizations
|
||||||
@ -94,12 +95,15 @@ Yet it offers everything you need for a customer identity ([CIAM](https://zitade
|
|||||||
|
|
||||||
Authentication
|
Authentication
|
||||||
- Single Sign On (SSO)
|
- Single Sign On (SSO)
|
||||||
- Passwordless with FIDO2 support (Including Passkeys)
|
- Passkeys support (FIDO2 / WebAuthN)
|
||||||
- Username / Password
|
- Username / Password
|
||||||
- Multifactor authentication with OTP, U2F, Email OTP, SMS OTP
|
- Multifactor authentication with OTP, U2F, Email OTP, SMS OTP
|
||||||
- LDAP
|
- LDAP
|
||||||
|
- External enterprise identity providers and social logins
|
||||||
|
- [Device authorization](https://zitadel.com/docs/guides/solution-scenarios/device-authorization)
|
||||||
- [OpenID Connect certified](https://openid.net/certification/#OPs) => [OIDC Endpoints](https://zitadel.com/docs/apis/openidoauth/endpoints)
|
- [OpenID Connect certified](https://openid.net/certification/#OPs) => [OIDC Endpoints](https://zitadel.com/docs/apis/openidoauth/endpoints)
|
||||||
- [SAML 2.0](http://docs.oasis-open.org/security/saml/Post2.0/sstc-saml-tech-overview-2.0.html) => [SAML Endpoints](https://zitadel.com/docs/apis/saml/endpoints)
|
- [SAML 2.0](http://docs.oasis-open.org/security/saml/Post2.0/sstc-saml-tech-overview-2.0.html) => [SAML Endpoints](https://zitadel.com/docs/apis/saml/endpoints)
|
||||||
|
- [Custom sessions](https://zitadel.com/docs/guides/integrate/login-ui/username-password) if you need to go beyond OIDC or SAML
|
||||||
- [Machine-to-machine](https://zitadel.com/docs/guides/integrate/serviceusers) with JWT profile, Personal Access Tokens (PAT), and Client Credentials
|
- [Machine-to-machine](https://zitadel.com/docs/guides/integrate/serviceusers) with JWT profile, Personal Access Tokens (PAT), and Client Credentials
|
||||||
|
|
||||||
Multi-Tenancy
|
Multi-Tenancy
|
||||||
|
@ -105,13 +105,21 @@ func (mig *FirstInstance) Execute(ctx context.Context) error {
|
|||||||
// check if username is email style or else append @<orgname>.<custom-domain>
|
// check if username is email style or else append @<orgname>.<custom-domain>
|
||||||
//this way we have the same value as before changing `UserLoginMustBeDomain` to false
|
//this way we have the same value as before changing `UserLoginMustBeDomain` to false
|
||||||
if !mig.instanceSetup.DomainPolicy.UserLoginMustBeDomain && !strings.Contains(mig.instanceSetup.Org.Human.Username, "@") {
|
if !mig.instanceSetup.DomainPolicy.UserLoginMustBeDomain && !strings.Contains(mig.instanceSetup.Org.Human.Username, "@") {
|
||||||
mig.instanceSetup.Org.Human.Username = mig.instanceSetup.Org.Human.Username + "@" + domain.NewIAMDomainName(mig.instanceSetup.Org.Name, mig.instanceSetup.CustomDomain)
|
orgDomain, err := domain.NewIAMDomainName(mig.instanceSetup.Org.Name, mig.instanceSetup.CustomDomain)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
mig.instanceSetup.Org.Human.Username = mig.instanceSetup.Org.Human.Username + "@" + orgDomain
|
||||||
}
|
}
|
||||||
mig.instanceSetup.Org.Human.Email.Address = mig.instanceSetup.Org.Human.Email.Address.Normalize()
|
mig.instanceSetup.Org.Human.Email.Address = mig.instanceSetup.Org.Human.Email.Address.Normalize()
|
||||||
if mig.instanceSetup.Org.Human.Email.Address == "" {
|
if mig.instanceSetup.Org.Human.Email.Address == "" {
|
||||||
mig.instanceSetup.Org.Human.Email.Address = domain.EmailAddress(mig.instanceSetup.Org.Human.Username)
|
mig.instanceSetup.Org.Human.Email.Address = domain.EmailAddress(mig.instanceSetup.Org.Human.Username)
|
||||||
if !strings.Contains(string(mig.instanceSetup.Org.Human.Email.Address), "@") {
|
if !strings.Contains(string(mig.instanceSetup.Org.Human.Email.Address), "@") {
|
||||||
mig.instanceSetup.Org.Human.Email.Address = domain.EmailAddress(mig.instanceSetup.Org.Human.Username + "@" + domain.NewIAMDomainName(mig.instanceSetup.Org.Name, mig.instanceSetup.CustomDomain))
|
orgDomain, err := domain.NewIAMDomainName(mig.instanceSetup.Org.Name, mig.instanceSetup.CustomDomain)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
mig.instanceSetup.Org.Human.Email.Address = domain.EmailAddress(mig.instanceSetup.Org.Human.Username + "@" + orgDomain)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -235,7 +235,6 @@ func startZitadel(config *Config, masterKey string, server chan<- *Server) error
|
|||||||
commands,
|
commands,
|
||||||
queries,
|
queries,
|
||||||
eventstoreClient,
|
eventstoreClient,
|
||||||
assets.AssetAPIFromDomain(config.ExternalSecure, config.ExternalPort),
|
|
||||||
config.Login.DefaultOTPEmailURLV2,
|
config.Login.DefaultOTPEmailURLV2,
|
||||||
config.SystemDefaults.Notifications.FileSystemPath,
|
config.SystemDefaults.Notifications.FileSystemPath,
|
||||||
keys.User,
|
keys.User,
|
||||||
@ -311,6 +310,8 @@ func startAPIs(
|
|||||||
authZRepo,
|
authZRepo,
|
||||||
queries,
|
queries,
|
||||||
}
|
}
|
||||||
|
// always set the origin in the context if available in the http headers, no matter for what protocol
|
||||||
|
router.Use(middleware.OriginHandler)
|
||||||
verifier := internal_authz.Start(repo, http_util.BuildHTTP(config.ExternalDomain, config.ExternalPort, config.ExternalSecure), config.SystemAPIUsers)
|
verifier := internal_authz.Start(repo, http_util.BuildHTTP(config.ExternalDomain, config.ExternalPort, config.ExternalSecure), config.SystemAPIUsers)
|
||||||
tlsConfig, err := config.TLS.Config()
|
tlsConfig, err := config.TLS.Config()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -444,7 +445,6 @@ func startAPIs(
|
|||||||
if err := apis.RegisterService(ctx, oidc_v2.CreateServer(commands, queries, oidcProvider, config.ExternalSecure)); err != nil {
|
if err := apis.RegisterService(ctx, oidc_v2.CreateServer(commands, queries, oidcProvider, config.ExternalSecure)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle grpc at last to be able to handle the root, because grpc and gateway require a lot of different prefixes
|
// handle grpc at last to be able to handle the root, because grpc and gateway require a lot of different prefixes
|
||||||
apis.RouteGRPC()
|
apis.RouteGRPC()
|
||||||
return nil
|
return nil
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<div class="main-container">
|
<div class="main-container">
|
||||||
<ng-container *ngIf="(authService.user | async) || {} as user">
|
<ng-container *ngIf="(authService.userSubject | async) || {} as user">
|
||||||
<cnsl-header
|
<cnsl-header
|
||||||
*ngIf="user && user !== {}"
|
*ngIf="user && user !== {}"
|
||||||
[org]="org"
|
[org]="org"
|
||||||
|
@ -225,9 +225,11 @@ export class AppComponent implements OnDestroy {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.isDarkTheme = this.themeService.isDarkTheme;
|
this.isDarkTheme = this.themeService.isDarkTheme;
|
||||||
this.isDarkTheme.subscribe((dark) => this.onSetTheme(dark ? 'dark-theme' : 'light-theme'));
|
this.isDarkTheme
|
||||||
|
.pipe(takeUntil(this.destroy$))
|
||||||
|
.subscribe((dark) => this.onSetTheme(dark ? 'dark-theme' : 'light-theme'));
|
||||||
|
|
||||||
this.translate.onLangChange.subscribe((language: LangChangeEvent) => {
|
this.translate.onLangChange.pipe(takeUntil(this.destroy$)).subscribe((language: LangChangeEvent) => {
|
||||||
this.document.documentElement.lang = language.lang;
|
this.document.documentElement.lang = language.lang;
|
||||||
this.language = language.lang;
|
this.language = language.lang;
|
||||||
});
|
});
|
||||||
@ -271,7 +273,7 @@ export class AppComponent implements OnDestroy {
|
|||||||
this.translate.addLangs(supportedLanguages);
|
this.translate.addLangs(supportedLanguages);
|
||||||
this.translate.setDefaultLang(fallbackLanguage);
|
this.translate.setDefaultLang(fallbackLanguage);
|
||||||
|
|
||||||
this.authService.user.subscribe((userprofile) => {
|
this.authService.userSubject.pipe(takeUntil(this.destroy$)).subscribe((userprofile) => {
|
||||||
if (userprofile) {
|
if (userprofile) {
|
||||||
const cropped = navigator.language.split('-')[0] ?? fallbackLanguage;
|
const cropped = navigator.language.split('-')[0] ?? fallbackLanguage;
|
||||||
const fallbackLang = cropped.match(supportedLanguagesRegexp) ? cropped : fallbackLanguage;
|
const fallbackLang = cropped.match(supportedLanguagesRegexp) ? cropped : fallbackLanguage;
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router';
|
import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router';
|
||||||
import { Observable } from 'rxjs';
|
import { map, Observable, take } from 'rxjs';
|
||||||
import { map, tap } from 'rxjs/operators';
|
|
||||||
|
|
||||||
import { GrpcAuthService } from '../services/grpc-auth.service';
|
import { GrpcAuthService } from '../services/grpc-auth.service';
|
||||||
|
|
||||||
@ -19,11 +18,13 @@ export class UserGuard {
|
|||||||
state: RouterStateSnapshot,
|
state: RouterStateSnapshot,
|
||||||
): Observable<boolean> | Promise<boolean> | boolean {
|
): Observable<boolean> | Promise<boolean> | boolean {
|
||||||
return this.authService.user.pipe(
|
return this.authService.user.pipe(
|
||||||
map((user) => user?.id !== route.params['id']),
|
take(1),
|
||||||
tap((isNotMe) => {
|
map((user) => {
|
||||||
if (!isNotMe) {
|
const isMe = user?.id === route.params['id'];
|
||||||
|
if (isMe) {
|
||||||
this.router.navigate(['/users', 'me']);
|
this.router.navigate(['/users', 'me']);
|
||||||
}
|
}
|
||||||
|
return !isMe;
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
color="primary"
|
color="primary"
|
||||||
name="hasUppercase"
|
name="hasUppercase"
|
||||||
ngDefaultControl
|
ngDefaultControl
|
||||||
|
data-e2e="notification-policy-checkbox"
|
||||||
[(ngModel)]="notificationData.passwordChange"
|
[(ngModel)]="notificationData.passwordChange"
|
||||||
[disabled]="(['policy.write'] | hasRole | async) === false"
|
[disabled]="(['policy.write'] | hasRole | async) === false"
|
||||||
>
|
>
|
||||||
@ -43,6 +44,7 @@
|
|||||||
color="primary"
|
color="primary"
|
||||||
type="submit"
|
type="submit"
|
||||||
mat-raised-button
|
mat-raised-button
|
||||||
|
data-e2e="save-notification-policy-button"
|
||||||
>
|
>
|
||||||
{{ 'ACTIONS.SAVE' | translate }}
|
{{ 'ACTIONS.SAVE' | translate }}
|
||||||
</button>
|
</button>
|
||||||
|
@ -1,24 +0,0 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
||||||
|
|
||||||
import { NotificationSettingsComponent } from './notification-settings.component';
|
|
||||||
|
|
||||||
describe('NotificationSettingsComponent', () => {
|
|
||||||
let component: NotificationSettingsComponent;
|
|
||||||
let fixture: ComponentFixture<NotificationSettingsComponent>;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
await TestBed.configureTestingModule({
|
|
||||||
declarations: [NotificationSettingsComponent],
|
|
||||||
}).compileComponents();
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
fixture = TestBed.createComponent(NotificationSettingsComponent);
|
|
||||||
component = fixture.componentInstance;
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create', () => {
|
|
||||||
expect(component).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
@ -33,6 +33,7 @@
|
|||||||
class="ok-button"
|
class="ok-button"
|
||||||
color="primary"
|
color="primary"
|
||||||
(click)="closeDialogWithRequest()"
|
(click)="closeDialogWithRequest()"
|
||||||
|
data-e2e="save-sms-settings-button"
|
||||||
>
|
>
|
||||||
<span>{{ 'ACTIONS.SAVE' | translate }}</span>
|
<span>{{ 'ACTIONS.SAVE' | translate }}</span>
|
||||||
</button>
|
</button>
|
@ -14,7 +14,6 @@ import {
|
|||||||
import { SMSProvider, TwilioConfig } from 'src/app/proto/generated/zitadel/settings_pb';
|
import { SMSProvider, TwilioConfig } from 'src/app/proto/generated/zitadel/settings_pb';
|
||||||
import { AdminService } from 'src/app/services/admin.service';
|
import { AdminService } from 'src/app/services/admin.service';
|
||||||
import { ToastService } from 'src/app/services/toast.service';
|
import { ToastService } from 'src/app/services/toast.service';
|
||||||
|
|
||||||
import { PasswordDialogComponent } from '../password-dialog/password-dialog.component';
|
import { PasswordDialogComponent } from '../password-dialog/password-dialog.component';
|
||||||
|
|
||||||
enum SMSProviderType {
|
enum SMSProviderType {
|
@ -0,0 +1,56 @@
|
|||||||
|
<h2>{{ 'SETTING.SMS.TITLE' | translate }}</h2>
|
||||||
|
|
||||||
|
<div class="spinner-wr">
|
||||||
|
<mat-spinner diameter="30" *ngIf="smsProvidersLoading" color="primary"></mat-spinner>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sms-providers">
|
||||||
|
<cnsl-card class="sms-card" [nomargin]="true">
|
||||||
|
<div class="sms-provider">
|
||||||
|
<h4 class="title">Twilio</h4>
|
||||||
|
|
||||||
|
<span
|
||||||
|
*ngIf="twilio"
|
||||||
|
class="state"
|
||||||
|
[ngClass]="{
|
||||||
|
active: twilio.state === SMSProviderConfigState.SMS_PROVIDER_CONFIG_ACTIVE,
|
||||||
|
inactive: twilio.state === SMSProviderConfigState.SMS_PROVIDER_CONFIG_INACTIVE
|
||||||
|
}"
|
||||||
|
>{{ 'SETTING.SMS.SMSPROVIDERSTATE.' + twilio.state | translate }}</span
|
||||||
|
>
|
||||||
|
|
||||||
|
<span class="fill-space"></span>
|
||||||
|
<button
|
||||||
|
*ngIf="twilio && twilio.id"
|
||||||
|
[disabled]="(['iam.write'] | hasRole | async) === false"
|
||||||
|
mat-stroked-button
|
||||||
|
(click)="toggleSMSProviderState(twilio.id)"
|
||||||
|
>
|
||||||
|
<span *ngIf="twilio.state === SMSProviderConfigState.SMS_PROVIDER_CONFIG_ACTIVE">{{
|
||||||
|
'ACTIONS.DEACTIVATE' | translate
|
||||||
|
}}</span>
|
||||||
|
<span *ngIf="twilio.state === SMSProviderConfigState.SMS_PROVIDER_CONFIG_INACTIVE">{{
|
||||||
|
'ACTIONS.ACTIVATE' | translate
|
||||||
|
}}</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
*ngIf="twilio && twilio.id"
|
||||||
|
color="warn"
|
||||||
|
[disabled]="(['iam.write'] | hasRole | async) === false"
|
||||||
|
mat-icon-button
|
||||||
|
(click)="removeSMSProvider(twilio.id)"
|
||||||
|
data-e2e="remove-sms-provider-button"
|
||||||
|
>
|
||||||
|
<i class="las la-trash"></i>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
[disabled]="(['iam.write'] | hasRole | async) === false"
|
||||||
|
mat-icon-button
|
||||||
|
(click)="editSMSProvider()"
|
||||||
|
data-e2e="new-twilio-button"
|
||||||
|
>
|
||||||
|
<i class="las la-pen"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</cnsl-card>
|
||||||
|
</div>
|
@ -2,35 +2,6 @@
|
|||||||
margin: 0.5rem 0;
|
margin: 0.5rem 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.smtp-form-field,
|
|
||||||
.info-section-warn {
|
|
||||||
max-width: 400px;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-section-warn {
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.smtp-checkbox {
|
|
||||||
max-width: 400px;
|
|
||||||
display: block;
|
|
||||||
margin: 1rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.set-password-btn {
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.general-btn-container {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-start;
|
|
||||||
|
|
||||||
.save-button {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.sms-providers {
|
.sms-providers {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
@ -0,0 +1,24 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { NotificationSMSProviderComponent } from './notification-sms-provider.component';
|
||||||
|
|
||||||
|
describe('NotificationSMSProviderComponent', () => {
|
||||||
|
let component: NotificationSMSProviderComponent;
|
||||||
|
let fixture: ComponentFixture<NotificationSMSProviderComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [NotificationSMSProviderComponent],
|
||||||
|
}).compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(NotificationSMSProviderComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,140 @@
|
|||||||
|
import { Component, Input } from '@angular/core';
|
||||||
|
import { AddSMSProviderTwilioRequest, UpdateSMSProviderTwilioRequest } from 'src/app/proto/generated/zitadel/admin_pb';
|
||||||
|
import { SMSProvider, SMSProviderConfigState } from 'src/app/proto/generated/zitadel/settings_pb';
|
||||||
|
|
||||||
|
import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog';
|
||||||
|
import { AdminService } from 'src/app/services/admin.service';
|
||||||
|
import { ToastService } from 'src/app/services/toast.service';
|
||||||
|
import { InfoSectionType } from '../../info-section/info-section.component';
|
||||||
|
import { WarnDialogComponent } from '../../warn-dialog/warn-dialog.component';
|
||||||
|
import { PolicyComponentServiceType } from '../policy-component-types.enum';
|
||||||
|
import { DialogAddSMSProviderComponent } from './dialog-add-sms-provider/dialog-add-sms-provider.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'cnsl-notification-sms-provider',
|
||||||
|
templateUrl: './notification-sms-provider.component.html',
|
||||||
|
styleUrls: ['./notification-sms-provider.component.scss'],
|
||||||
|
})
|
||||||
|
export class NotificationSMSProviderComponent {
|
||||||
|
@Input() public serviceType!: PolicyComponentServiceType;
|
||||||
|
public smsProviders: SMSProvider.AsObject[] = [];
|
||||||
|
|
||||||
|
public smsProvidersLoading: boolean = false;
|
||||||
|
|
||||||
|
public SMSProviderConfigState: any = SMSProviderConfigState;
|
||||||
|
public InfoSectionType: any = InfoSectionType;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private service: AdminService,
|
||||||
|
private dialog: MatDialog,
|
||||||
|
private toast: ToastService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
private fetchData(): void {
|
||||||
|
this.smsProvidersLoading = true;
|
||||||
|
this.service
|
||||||
|
.listSMSProviders()
|
||||||
|
.then((smsProviders) => {
|
||||||
|
this.smsProvidersLoading = false;
|
||||||
|
if (smsProviders.resultList) {
|
||||||
|
this.smsProviders = smsProviders.resultList;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.smsProvidersLoading = false;
|
||||||
|
this.toast.showError(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public editSMSProvider(): void {
|
||||||
|
const dialogRef = this.dialog.open(DialogAddSMSProviderComponent, {
|
||||||
|
width: '400px',
|
||||||
|
data: {
|
||||||
|
smsProviders: this.smsProviders,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
dialogRef.afterClosed().subscribe((req: AddSMSProviderTwilioRequest | UpdateSMSProviderTwilioRequest) => {
|
||||||
|
if (req) {
|
||||||
|
if (!!this.twilio) {
|
||||||
|
this.service
|
||||||
|
.updateSMSProviderTwilio(req as UpdateSMSProviderTwilioRequest)
|
||||||
|
.then(() => {
|
||||||
|
this.toast.showInfo('SETTING.SMS.TWILIO.ADDED', true);
|
||||||
|
this.fetchData();
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.toast.showError(error);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.service
|
||||||
|
.addSMSProviderTwilio(req as AddSMSProviderTwilioRequest)
|
||||||
|
.then(() => {
|
||||||
|
this.toast.showInfo('SETTING.SMS.TWILIO.ADDED', true);
|
||||||
|
this.fetchData();
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.toast.showError(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public toggleSMSProviderState(id: string): void {
|
||||||
|
const provider = this.smsProviders.find((p) => p.id === id);
|
||||||
|
if (provider) {
|
||||||
|
if (provider.state === SMSProviderConfigState.SMS_PROVIDER_CONFIG_ACTIVE) {
|
||||||
|
this.service
|
||||||
|
.deactivateSMSProvider(id)
|
||||||
|
.then(() => {
|
||||||
|
this.toast.showInfo('SETTING.SMS.DEACTIVATED', true);
|
||||||
|
this.fetchData();
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.toast.showError(error);
|
||||||
|
});
|
||||||
|
} else if (provider.state === SMSProviderConfigState.SMS_PROVIDER_CONFIG_INACTIVE) {
|
||||||
|
this.service
|
||||||
|
.activateSMSProvider(id)
|
||||||
|
.then(() => {
|
||||||
|
this.toast.showInfo('SETTING.SMS.ACTIVATED', true);
|
||||||
|
this.fetchData();
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.toast.showError(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public removeSMSProvider(id: string): void {
|
||||||
|
const dialogRef = this.dialog.open(WarnDialogComponent, {
|
||||||
|
data: {
|
||||||
|
confirmKey: 'ACTIONS.DELETE',
|
||||||
|
cancelKey: 'ACTIONS.CANCEL',
|
||||||
|
titleKey: 'SETTING.SMS.REMOVEPROVIDER',
|
||||||
|
descriptionKey: 'SETTING.SMS.REMOVEPROVIDER_DESC',
|
||||||
|
},
|
||||||
|
width: '400px',
|
||||||
|
});
|
||||||
|
|
||||||
|
dialogRef.afterClosed().subscribe((resp) => {
|
||||||
|
if (resp) {
|
||||||
|
this.service
|
||||||
|
.removeSMSProvider(id)
|
||||||
|
.then(() => {
|
||||||
|
this.toast.showInfo('SETTING.SMS.TWILIO.REMOVED', true);
|
||||||
|
this.fetchData();
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.toast.showError(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public get twilio(): SMSProvider.AsObject | undefined {
|
||||||
|
return this.smsProviders.find((p) => p.twilio);
|
||||||
|
}
|
||||||
|
}
|
@ -15,11 +15,10 @@ import { InfoSectionModule } from '../../info-section/info-section.module';
|
|||||||
import { InputModule } from '../../input/input.module';
|
import { InputModule } from '../../input/input.module';
|
||||||
import { WarnDialogModule } from '../../warn-dialog/warn-dialog.module';
|
import { WarnDialogModule } from '../../warn-dialog/warn-dialog.module';
|
||||||
import { DialogAddSMSProviderComponent } from './dialog-add-sms-provider/dialog-add-sms-provider.component';
|
import { DialogAddSMSProviderComponent } from './dialog-add-sms-provider/dialog-add-sms-provider.component';
|
||||||
import { NotificationSettingsComponent } from './notification-settings.component';
|
import { NotificationSMSProviderComponent } from './notification-sms-provider.component';
|
||||||
import { PasswordDialogComponent } from './password-dialog/password-dialog.component';
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [NotificationSettingsComponent, DialogAddSMSProviderComponent, PasswordDialogComponent],
|
declarations: [NotificationSMSProviderComponent, DialogAddSMSProviderComponent],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
CardModule,
|
CardModule,
|
||||||
@ -38,6 +37,6 @@ import { PasswordDialogComponent } from './password-dialog/password-dialog.compo
|
|||||||
MatSelectModule,
|
MatSelectModule,
|
||||||
TranslateModule,
|
TranslateModule,
|
||||||
],
|
],
|
||||||
exports: [NotificationSettingsComponent],
|
exports: [NotificationSMSProviderComponent],
|
||||||
})
|
})
|
||||||
export class NotificationSettingsModule {}
|
export class NotificationSMSProviderModule {}
|
@ -4,7 +4,13 @@
|
|||||||
<div mat-dialog-content>
|
<div mat-dialog-content>
|
||||||
<cnsl-form-field class="formfield">
|
<cnsl-form-field class="formfield">
|
||||||
<cnsl-label>{{ data.i18nLabel | translate }}</cnsl-label>
|
<cnsl-label>{{ data.i18nLabel | translate }}</cnsl-label>
|
||||||
<input cnslInput [(ngModel)]="password" type="password" autocomplete="new-password" />
|
<input
|
||||||
|
cnslInput
|
||||||
|
[(ngModel)]="password"
|
||||||
|
type="password"
|
||||||
|
autocomplete="new-password"
|
||||||
|
data-e2e="notification-setting-password"
|
||||||
|
/>
|
||||||
</cnsl-form-field>
|
</cnsl-form-field>
|
||||||
</div>
|
</div>
|
||||||
<div mat-dialog-actions class="action">
|
<div mat-dialog-actions class="action">
|
||||||
@ -19,6 +25,7 @@
|
|||||||
mat-raised-button
|
mat-raised-button
|
||||||
class="ok-button"
|
class="ok-button"
|
||||||
(click)="closeDialog(password)"
|
(click)="closeDialog(password)"
|
||||||
|
data-e2e="save-notification-setting-password-button"
|
||||||
>
|
>
|
||||||
{{ 'ACTIONS.OK' | translate }}
|
{{ 'ACTIONS.OK' | translate }}
|
||||||
</button>
|
</button>
|
@ -1,7 +1,7 @@
|
|||||||
<h2>{{ 'SETTING.SMTP.TITLE' | translate }}</h2>
|
<h2>{{ 'SETTING.SMTP.TITLE' | translate }}</h2>
|
||||||
|
|
||||||
<div class="spinner-wr">
|
<div class="spinner-wr">
|
||||||
<mat-spinner diameter="30" *ngIf="smtpLoading || smsProvidersLoading" color="primary"></mat-spinner>
|
<mat-spinner diameter="30" *ngIf="smtpLoading" color="primary"></mat-spinner>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<cnsl-info-section
|
<cnsl-info-section
|
||||||
@ -48,6 +48,7 @@
|
|||||||
(click)="setSMTPPassword()"
|
(click)="setSMTPPassword()"
|
||||||
type="button"
|
type="button"
|
||||||
mat-stroked-button
|
mat-stroked-button
|
||||||
|
data-e2e="add-smtp-password-button"
|
||||||
>
|
>
|
||||||
{{ 'SETTING.SMTP.SETPASSWORD' | translate }}
|
{{ 'SETTING.SMTP.SETPASSWORD' | translate }}
|
||||||
</button>
|
</button>
|
||||||
@ -60,55 +61,9 @@
|
|||||||
color="primary"
|
color="primary"
|
||||||
type="submit"
|
type="submit"
|
||||||
mat-raised-button
|
mat-raised-button
|
||||||
|
data-e2e="save-smtp-settings-button"
|
||||||
>
|
>
|
||||||
{{ 'ACTIONS.SAVE' | translate }}
|
{{ 'ACTIONS.SAVE' | translate }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<br />
|
|
||||||
<h2>{{ 'SETTING.SMS.TITLE' | translate }}</h2>
|
|
||||||
<div class="sms-providers">
|
|
||||||
<cnsl-card class="sms-card" [nomargin]="true">
|
|
||||||
<div class="sms-provider">
|
|
||||||
<h4 class="title">Twilio</h4>
|
|
||||||
|
|
||||||
<span
|
|
||||||
*ngIf="twilio"
|
|
||||||
class="state"
|
|
||||||
[ngClass]="{
|
|
||||||
active: twilio.state === SMSProviderConfigState.SMS_PROVIDER_CONFIG_ACTIVE,
|
|
||||||
inactive: twilio.state === SMSProviderConfigState.SMS_PROVIDER_CONFIG_INACTIVE
|
|
||||||
}"
|
|
||||||
>{{ 'SETTING.SMS.SMSPROVIDERSTATE.' + twilio.state | translate }}</span
|
|
||||||
>
|
|
||||||
|
|
||||||
<span class="fill-space"></span>
|
|
||||||
<button
|
|
||||||
*ngIf="twilio && twilio.id"
|
|
||||||
[disabled]="(['iam.write'] | hasRole | async) === false"
|
|
||||||
mat-stroked-button
|
|
||||||
(click)="toggleSMSProviderState(twilio.id)"
|
|
||||||
>
|
|
||||||
<span *ngIf="twilio.state === SMSProviderConfigState.SMS_PROVIDER_CONFIG_ACTIVE">{{
|
|
||||||
'ACTIONS.DEACTIVATE' | translate
|
|
||||||
}}</span>
|
|
||||||
<span *ngIf="twilio.state === SMSProviderConfigState.SMS_PROVIDER_CONFIG_INACTIVE">{{
|
|
||||||
'ACTIONS.ACTIVATE' | translate
|
|
||||||
}}</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
*ngIf="twilio && twilio.id"
|
|
||||||
color="warn"
|
|
||||||
[disabled]="(['iam.write'] | hasRole | async) === false"
|
|
||||||
mat-icon-button
|
|
||||||
(click)="removeSMSProvider(twilio.id)"
|
|
||||||
>
|
|
||||||
<i class="las la-trash"></i>
|
|
||||||
</button>
|
|
||||||
<button [disabled]="(['iam.write'] | hasRole | async) === false" mat-icon-button (click)="editSMSProvider()">
|
|
||||||
<i class="las la-pen"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</cnsl-card>
|
|
||||||
</div>
|
|
@ -0,0 +1,32 @@
|
|||||||
|
.spinner-wr {
|
||||||
|
margin: 0.5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.smtp-form-field,
|
||||||
|
.info-section-warn {
|
||||||
|
max-width: 400px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-section-warn {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.smtp-checkbox {
|
||||||
|
max-width: 400px;
|
||||||
|
display: block;
|
||||||
|
margin: 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.set-password-btn {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.general-btn-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
|
||||||
|
.save-button {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { NotificationSMTPProviderComponent } from './notification-smtp-provider.component';
|
||||||
|
|
||||||
|
describe('NotificationSMTPProviderComponent', () => {
|
||||||
|
let component: NotificationSMTPProviderComponent;
|
||||||
|
let fixture: ComponentFixture<NotificationSMTPProviderComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [NotificationSMTPProviderComponent],
|
||||||
|
}).compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(NotificationSMTPProviderComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -3,45 +3,33 @@ import { AbstractControl, UntypedFormBuilder, UntypedFormGroup } from '@angular/
|
|||||||
import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog';
|
import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog';
|
||||||
import { take } from 'rxjs';
|
import { take } from 'rxjs';
|
||||||
import {
|
import {
|
||||||
AddSMSProviderTwilioRequest,
|
|
||||||
AddSMTPConfigRequest,
|
AddSMTPConfigRequest,
|
||||||
AddSMTPConfigResponse,
|
AddSMTPConfigResponse,
|
||||||
UpdateSMSProviderTwilioRequest,
|
|
||||||
UpdateSMTPConfigPasswordRequest,
|
UpdateSMTPConfigPasswordRequest,
|
||||||
UpdateSMTPConfigRequest,
|
UpdateSMTPConfigRequest,
|
||||||
UpdateSMTPConfigResponse,
|
UpdateSMTPConfigResponse,
|
||||||
} from 'src/app/proto/generated/zitadel/admin_pb';
|
} from 'src/app/proto/generated/zitadel/admin_pb';
|
||||||
import { DebugNotificationProvider, SMSProvider, SMSProviderConfigState } from 'src/app/proto/generated/zitadel/settings_pb';
|
|
||||||
import { AdminService } from 'src/app/services/admin.service';
|
import { AdminService } from 'src/app/services/admin.service';
|
||||||
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
|
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
|
||||||
import { ToastService } from 'src/app/services/toast.service';
|
import { ToastService } from 'src/app/services/toast.service';
|
||||||
import { requiredValidator } from '../../form-field/validators/validators';
|
import { requiredValidator } from '../../form-field/validators/validators';
|
||||||
|
|
||||||
import { InfoSectionType } from '../../info-section/info-section.component';
|
import { InfoSectionType } from '../../info-section/info-section.component';
|
||||||
import { WarnDialogComponent } from '../../warn-dialog/warn-dialog.component';
|
import { PasswordDialogComponent } from '../notification-sms-provider/password-dialog/password-dialog.component';
|
||||||
import { PolicyComponentServiceType } from '../policy-component-types.enum';
|
import { PolicyComponentServiceType } from '../policy-component-types.enum';
|
||||||
import { DialogAddSMSProviderComponent } from './dialog-add-sms-provider/dialog-add-sms-provider.component';
|
|
||||||
import { PasswordDialogComponent } from './password-dialog/password-dialog.component';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'cnsl-notification-settings',
|
selector: 'cnsl-notification-smtp-provider',
|
||||||
templateUrl: './notification-settings.component.html',
|
templateUrl: './notification-smtp-provider.component.html',
|
||||||
styleUrls: ['./notification-settings.component.scss'],
|
styleUrls: ['./notification-smtp-provider.component.scss'],
|
||||||
})
|
})
|
||||||
export class NotificationSettingsComponent implements OnInit {
|
export class NotificationSMTPProviderComponent implements OnInit {
|
||||||
@Input() public serviceType!: PolicyComponentServiceType;
|
@Input() public serviceType!: PolicyComponentServiceType;
|
||||||
public smsProviders: SMSProvider.AsObject[] = [];
|
|
||||||
public logNotificationProvider!: DebugNotificationProvider.AsObject;
|
|
||||||
public fileNotificationProvider!: DebugNotificationProvider.AsObject;
|
|
||||||
|
|
||||||
public smtpLoading: boolean = false;
|
public smtpLoading: boolean = false;
|
||||||
public smsProvidersLoading: boolean = false;
|
|
||||||
public logProviderLoading: boolean = false;
|
|
||||||
public fileProviderLoading: boolean = false;
|
|
||||||
|
|
||||||
public form!: UntypedFormGroup;
|
public form!: UntypedFormGroup;
|
||||||
|
|
||||||
public SMSProviderConfigState: any = SMSProviderConfigState;
|
|
||||||
public InfoSectionType: any = InfoSectionType;
|
public InfoSectionType: any = InfoSectionType;
|
||||||
|
|
||||||
public hasSMTPConfig: boolean = false;
|
public hasSMTPConfig: boolean = false;
|
||||||
@ -96,46 +84,6 @@ export class NotificationSettingsComponent implements OnInit {
|
|||||||
this.hasSMTPConfig = false;
|
this.hasSMTPConfig = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.smsProvidersLoading = true;
|
|
||||||
this.service
|
|
||||||
.listSMSProviders()
|
|
||||||
.then((smsProviders) => {
|
|
||||||
this.smsProvidersLoading = false;
|
|
||||||
if (smsProviders.resultList) {
|
|
||||||
this.smsProviders = smsProviders.resultList;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
this.smsProvidersLoading = false;
|
|
||||||
this.toast.showError(error);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.logProviderLoading = true;
|
|
||||||
this.service
|
|
||||||
.getLogNotificationProvider()
|
|
||||||
.then((logNotificationProvider) => {
|
|
||||||
this.logProviderLoading = false;
|
|
||||||
if (logNotificationProvider.provider) {
|
|
||||||
this.logNotificationProvider = logNotificationProvider.provider;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
this.logProviderLoading = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.fileProviderLoading = true;
|
|
||||||
this.service
|
|
||||||
.getFileSystemNotificationProvider()
|
|
||||||
.then((fileNotificationProvider) => {
|
|
||||||
this.fileProviderLoading = false;
|
|
||||||
if (fileNotificationProvider.provider) {
|
|
||||||
this.fileNotificationProvider = fileNotificationProvider.provider;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
this.fileProviderLoading = false;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateData(): Promise<UpdateSMTPConfigResponse.AsObject | AddSMTPConfigResponse> {
|
private updateData(): Promise<UpdateSMTPConfigResponse.AsObject | AddSMTPConfigResponse> {
|
||||||
@ -175,41 +123,6 @@ export class NotificationSettingsComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public editSMSProvider(): void {
|
|
||||||
const dialogRef = this.dialog.open(DialogAddSMSProviderComponent, {
|
|
||||||
width: '400px',
|
|
||||||
data: {
|
|
||||||
smsProviders: this.smsProviders,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
dialogRef.afterClosed().subscribe((req: AddSMSProviderTwilioRequest | UpdateSMSProviderTwilioRequest) => {
|
|
||||||
if (req) {
|
|
||||||
if (!!this.twilio) {
|
|
||||||
this.service
|
|
||||||
.updateSMSProviderTwilio(req as UpdateSMSProviderTwilioRequest)
|
|
||||||
.then(() => {
|
|
||||||
this.toast.showInfo('SETTING.SMS.TWILIO.ADDED', true);
|
|
||||||
this.fetchData();
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
this.toast.showError(error);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.service
|
|
||||||
.addSMSProviderTwilio(req as AddSMSProviderTwilioRequest)
|
|
||||||
.then(() => {
|
|
||||||
this.toast.showInfo('SETTING.SMS.TWILIO.ADDED', true);
|
|
||||||
this.fetchData();
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
this.toast.showError(error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public setSMTPPassword(): void {
|
public setSMTPPassword(): void {
|
||||||
const dialogRef = this.dialog.open(PasswordDialogComponent, {
|
const dialogRef = this.dialog.open(PasswordDialogComponent, {
|
||||||
width: '400px',
|
width: '400px',
|
||||||
@ -236,63 +149,6 @@ export class NotificationSettingsComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public toggleSMSProviderState(id: string): void {
|
|
||||||
const provider = this.smsProviders.find((p) => p.id === id);
|
|
||||||
if (provider) {
|
|
||||||
if (provider.state === SMSProviderConfigState.SMS_PROVIDER_CONFIG_ACTIVE) {
|
|
||||||
this.service
|
|
||||||
.deactivateSMSProvider(id)
|
|
||||||
.then(() => {
|
|
||||||
this.toast.showInfo('SETTING.SMS.DEACTIVATED', true);
|
|
||||||
this.fetchData();
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
this.toast.showError(error);
|
|
||||||
});
|
|
||||||
} else if (provider.state === SMSProviderConfigState.SMS_PROVIDER_CONFIG_INACTIVE) {
|
|
||||||
this.service
|
|
||||||
.activateSMSProvider(id)
|
|
||||||
.then(() => {
|
|
||||||
this.toast.showInfo('SETTING.SMS.ACTIVATED', true);
|
|
||||||
this.fetchData();
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
this.toast.showError(error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public removeSMSProvider(id: string): void {
|
|
||||||
const dialogRef = this.dialog.open(WarnDialogComponent, {
|
|
||||||
data: {
|
|
||||||
confirmKey: 'ACTIONS.DELETE',
|
|
||||||
cancelKey: 'ACTIONS.CANCEL',
|
|
||||||
titleKey: 'SETTING.SMS.REMOVEPROVIDER',
|
|
||||||
descriptionKey: 'SETTING.SMS.REMOVEPROVIDER_DESC',
|
|
||||||
},
|
|
||||||
width: '400px',
|
|
||||||
});
|
|
||||||
|
|
||||||
dialogRef.afterClosed().subscribe((resp) => {
|
|
||||||
if (resp) {
|
|
||||||
this.service
|
|
||||||
.removeSMSProvider(id)
|
|
||||||
.then(() => {
|
|
||||||
this.toast.showInfo('SETTING.SMS.TWILIO.REMOVED', true);
|
|
||||||
this.fetchData();
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
this.toast.showError(error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public get twilio(): SMSProvider.AsObject | undefined {
|
|
||||||
return this.smsProviders.find((p) => p.twilio);
|
|
||||||
}
|
|
||||||
|
|
||||||
public get senderAddress(): AbstractControl | null {
|
public get senderAddress(): AbstractControl | null {
|
||||||
return this.form.get('senderAddress');
|
return this.form.get('senderAddress');
|
||||||
}
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
|
import { MatLegacyButtonModule as MatButtonModule } from '@angular/material/legacy-button';
|
||||||
|
import { MatLegacyCheckboxModule as MatCheckboxModule } from '@angular/material/legacy-checkbox';
|
||||||
|
import { MatLegacyProgressSpinnerModule as MatProgressSpinnerModule } from '@angular/material/legacy-progress-spinner';
|
||||||
|
import { MatLegacySelectModule as MatSelectModule } from '@angular/material/legacy-select';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module';
|
||||||
|
|
||||||
|
import { CardModule } from '../../card/card.module';
|
||||||
|
import { FormFieldModule } from '../../form-field/form-field.module';
|
||||||
|
import { InfoSectionModule } from '../../info-section/info-section.module';
|
||||||
|
import { InputModule } from '../../input/input.module';
|
||||||
|
import { WarnDialogModule } from '../../warn-dialog/warn-dialog.module';
|
||||||
|
import { PasswordDialogComponent } from '../notification-sms-provider/password-dialog/password-dialog.component';
|
||||||
|
import { NotificationSMTPProviderComponent } from './notification-smtp-provider.component';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [NotificationSMTPProviderComponent, PasswordDialogComponent],
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
CardModule,
|
||||||
|
InfoSectionModule,
|
||||||
|
FormsModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
HasRolePipeModule,
|
||||||
|
MatButtonModule,
|
||||||
|
MatCheckboxModule,
|
||||||
|
InputModule,
|
||||||
|
MatIconModule,
|
||||||
|
FormFieldModule,
|
||||||
|
WarnDialogModule,
|
||||||
|
MatSelectModule,
|
||||||
|
MatProgressSpinnerModule,
|
||||||
|
MatSelectModule,
|
||||||
|
TranslateModule,
|
||||||
|
],
|
||||||
|
exports: [NotificationSMTPProviderComponent],
|
||||||
|
})
|
||||||
|
export class NotificationSMTPProviderModule {}
|
@ -51,7 +51,7 @@ export const NOTIFICATION_GROUP: SettingLinks = {
|
|||||||
i18nTitle: 'SETTINGS.GROUPS.NOTIFICATIONS',
|
i18nTitle: 'SETTINGS.GROUPS.NOTIFICATIONS',
|
||||||
i18nDesc: 'SETTINGS.LIST.NOTIFICATIONS_DESC',
|
i18nDesc: 'SETTINGS.LIST.NOTIFICATIONS_DESC',
|
||||||
iamRouterLink: ['/settings'],
|
iamRouterLink: ['/settings'],
|
||||||
queryParams: { id: 'notifications' },
|
queryParams: { id: 'smtpprovider' },
|
||||||
iamWithRole: ['iam.policy.read'],
|
iamWithRole: ['iam.policy.read'],
|
||||||
icon: 'las la-bell',
|
icon: 'las la-bell',
|
||||||
color: 'red',
|
color: 'red',
|
||||||
|
@ -27,19 +27,18 @@
|
|||||||
<ng-container *ngIf="currentSetting === 'idp'">
|
<ng-container *ngIf="currentSetting === 'idp'">
|
||||||
<cnsl-idp-settings [serviceType]="serviceType"></cnsl-idp-settings>
|
<cnsl-idp-settings [serviceType]="serviceType"></cnsl-idp-settings>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container *ngIf="currentSetting === 'notifications' && serviceType === PolicyComponentServiceType.ADMIN">
|
<ng-container *ngIf="currentSetting === 'notifications'">
|
||||||
<cnsl-notification-policy [serviceType]="serviceType"></cnsl-notification-policy>
|
<cnsl-notification-policy [serviceType]="serviceType"></cnsl-notification-policy>
|
||||||
<cnsl-notification-settings [serviceType]="serviceType"></cnsl-notification-settings>
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
<ng-container *ngIf="currentSetting === 'smtpprovider' && serviceType === PolicyComponentServiceType.ADMIN">
|
||||||
<ng-container *ngIf="currentSetting === 'notifications' && serviceType === PolicyComponentServiceType.MGMT">
|
<cnsl-notification-smtp-provider [serviceType]="serviceType"></cnsl-notification-smtp-provider>
|
||||||
<cnsl-notification-policy [serviceType]="serviceType"></cnsl-notification-policy
|
</ng-container>
|
||||||
></ng-container>
|
<ng-container *ngIf="currentSetting === 'smsprovider' && serviceType === PolicyComponentServiceType.ADMIN">
|
||||||
|
<cnsl-notification-sms-provider [serviceType]="serviceType"></cnsl-notification-sms-provider>
|
||||||
|
</ng-container>
|
||||||
<ng-container *ngIf="currentSetting === 'oidc' && serviceType === PolicyComponentServiceType.ADMIN">
|
<ng-container *ngIf="currentSetting === 'oidc' && serviceType === PolicyComponentServiceType.ADMIN">
|
||||||
<cnsl-oidc-configuration></cnsl-oidc-configuration>
|
<cnsl-oidc-configuration></cnsl-oidc-configuration>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-container *ngIf="currentSetting === 'secrets' && serviceType === PolicyComponentServiceType.ADMIN">
|
<ng-container *ngIf="currentSetting === 'secrets' && serviceType === PolicyComponentServiceType.ADMIN">
|
||||||
<cnsl-secret-generator></cnsl-secret-generator>
|
<cnsl-secret-generator></cnsl-secret-generator>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
@ -13,7 +13,8 @@ import { LoginPolicyModule } from '../policies/login-policy/login-policy.module'
|
|||||||
import { LoginTextsPolicyModule } from '../policies/login-texts/login-texts.module';
|
import { LoginTextsPolicyModule } from '../policies/login-texts/login-texts.module';
|
||||||
import { MessageTextsPolicyModule } from '../policies/message-texts/message-texts.module';
|
import { MessageTextsPolicyModule } from '../policies/message-texts/message-texts.module';
|
||||||
import { NotificationPolicyModule } from '../policies/notification-policy/notification-policy.module';
|
import { NotificationPolicyModule } from '../policies/notification-policy/notification-policy.module';
|
||||||
import { NotificationSettingsModule } from '../policies/notification-settings/notification-settings.module';
|
import { NotificationSMSProviderModule } from '../policies/notification-sms-provider/notification-sms-provider.module';
|
||||||
|
import { NotificationSMTPProviderModule } from '../policies/notification-smtp-provider/notification-smtp-provider.module';
|
||||||
import { OIDCConfigurationModule } from '../policies/oidc-configuration/oidc-configuration.module';
|
import { OIDCConfigurationModule } from '../policies/oidc-configuration/oidc-configuration.module';
|
||||||
import { PasswordComplexityPolicyModule } from '../policies/password-complexity-policy/password-complexity-policy.module';
|
import { PasswordComplexityPolicyModule } from '../policies/password-complexity-policy/password-complexity-policy.module';
|
||||||
import { PasswordLockoutPolicyModule } from '../policies/password-lockout-policy/password-lockout-policy.module';
|
import { PasswordLockoutPolicyModule } from '../policies/password-lockout-policy/password-lockout-policy.module';
|
||||||
@ -46,7 +47,8 @@ import { SettingsListComponent } from './settings-list.component';
|
|||||||
DomainPolicyModule,
|
DomainPolicyModule,
|
||||||
TranslateModule,
|
TranslateModule,
|
||||||
HasRolePipeModule,
|
HasRolePipeModule,
|
||||||
NotificationSettingsModule,
|
NotificationSMTPProviderModule,
|
||||||
|
NotificationSMSProviderModule,
|
||||||
OIDCConfigurationModule,
|
OIDCConfigurationModule,
|
||||||
SecretGeneratorModule,
|
SecretGeneratorModule,
|
||||||
],
|
],
|
||||||
|
@ -98,15 +98,25 @@ export const NOTIFICATIONS: SidenavSetting = {
|
|||||||
groupI18nKey: 'SETTINGS.GROUPS.NOTIFICATIONS',
|
groupI18nKey: 'SETTINGS.GROUPS.NOTIFICATIONS',
|
||||||
requiredRoles: {
|
requiredRoles: {
|
||||||
[PolicyComponentServiceType.ADMIN]: ['iam.policy.read'],
|
[PolicyComponentServiceType.ADMIN]: ['iam.policy.read'],
|
||||||
|
[PolicyComponentServiceType.MGMT]: ['policy.read'],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const NOTIFICATION_POLICY: SidenavSetting = {
|
export const SMTP_PROVIDER: SidenavSetting = {
|
||||||
id: 'notifications',
|
id: 'smtpprovider',
|
||||||
i18nKey: 'SETTINGS.LIST.NOTIFICATIONS',
|
i18nKey: 'SETTINGS.LIST.SMTP_PROVIDER',
|
||||||
groupI18nKey: 'SETTINGS.GROUPS.NOTIFICATIONS',
|
groupI18nKey: 'SETTINGS.GROUPS.NOTIFICATIONS',
|
||||||
requiredRoles: {
|
requiredRoles: {
|
||||||
[PolicyComponentServiceType.MGMT]: ['policy.read'],
|
[PolicyComponentServiceType.ADMIN]: ['iam.policy.read'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SMS_PROVIDER: SidenavSetting = {
|
||||||
|
id: 'smsprovider',
|
||||||
|
i18nKey: 'SETTINGS.LIST.SMS_PROVIDER',
|
||||||
|
groupI18nKey: 'SETTINGS.GROUPS.NOTIFICATIONS',
|
||||||
|
requiredRoles: {
|
||||||
|
[PolicyComponentServiceType.ADMIN]: ['iam.policy.read'],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -21,6 +21,8 @@ import {
|
|||||||
PRIVACYPOLICY,
|
PRIVACYPOLICY,
|
||||||
SECRETS,
|
SECRETS,
|
||||||
SECURITY,
|
SECURITY,
|
||||||
|
SMS_PROVIDER,
|
||||||
|
SMTP_PROVIDER,
|
||||||
} from '../../modules/settings-list/settings';
|
} from '../../modules/settings-list/settings';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -36,6 +38,8 @@ export class InstanceSettingsComponent implements OnInit, OnDestroy {
|
|||||||
// notifications
|
// notifications
|
||||||
// { showWarn: true, ...NOTIFICATIONS },
|
// { showWarn: true, ...NOTIFICATIONS },
|
||||||
NOTIFICATIONS,
|
NOTIFICATIONS,
|
||||||
|
SMTP_PROVIDER,
|
||||||
|
SMS_PROVIDER,
|
||||||
// login
|
// login
|
||||||
LOGIN,
|
LOGIN,
|
||||||
IDP,
|
IDP,
|
||||||
@ -80,7 +84,10 @@ export class InstanceSettingsComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.settingsList = this.authService.isAllowedMapper(this.defaultSettingsList, (setting) => setting.requiredRoles.admin);
|
this.settingsList = this.authService.isAllowedMapper(
|
||||||
|
this.defaultSettingsList,
|
||||||
|
(setting) => setting.requiredRoles.admin || [],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
|
@ -15,7 +15,7 @@ import {
|
|||||||
LOGIN,
|
LOGIN,
|
||||||
LOGINTEXTS,
|
LOGINTEXTS,
|
||||||
MESSAGETEXTS,
|
MESSAGETEXTS,
|
||||||
NOTIFICATION_POLICY,
|
NOTIFICATIONS,
|
||||||
PRIVACYPOLICY,
|
PRIVACYPOLICY,
|
||||||
VERIFIED_DOMAINS,
|
VERIFIED_DOMAINS,
|
||||||
} from '../../modules/settings-list/settings';
|
} from '../../modules/settings-list/settings';
|
||||||
@ -34,7 +34,7 @@ export class OrgSettingsComponent implements OnInit {
|
|||||||
IDP,
|
IDP,
|
||||||
COMPLEXITY,
|
COMPLEXITY,
|
||||||
LOCKOUT,
|
LOCKOUT,
|
||||||
NOTIFICATION_POLICY,
|
NOTIFICATIONS,
|
||||||
VERIFIED_DOMAINS,
|
VERIFIED_DOMAINS,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
BRANDING,
|
BRANDING,
|
||||||
@ -68,7 +68,7 @@ export class OrgSettingsComponent implements OnInit {
|
|||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.settingsList = this.authService
|
this.settingsList = this.authService
|
||||||
.isAllowedMapper(this.defaultSettingsList, (setting) => setting.requiredRoles.mgmt)
|
.isAllowedMapper(this.defaultSettingsList, (setting) => setting.requiredRoles.mgmt || [])
|
||||||
.pipe(take(1));
|
.pipe(take(1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
title="{{ 'APP.PAGES.CREATE' | translate }}"
|
title="{{ 'APP.PAGES.CREATE' | translate }}"
|
||||||
class="app-create-wrapper"
|
class="app-create-wrapper"
|
||||||
[createSteps]="
|
[createSteps]="
|
||||||
!devmode
|
!pro
|
||||||
? appType?.value?.createType === AppCreateType.OIDC
|
? appType?.value?.createType === AppCreateType.OIDC
|
||||||
? appType?.value.oidcAppType !== OIDCAppType.OIDC_APP_TYPE_NATIVE
|
? appType?.value.oidcAppType !== OIDCAppType.OIDC_APP_TYPE_NATIVE
|
||||||
? 4
|
? 4
|
||||||
@ -20,13 +20,13 @@
|
|||||||
<h1>{{ 'APP.PAGES.CREATE_DESC_TITLE' | translate }}</h1>
|
<h1>{{ 'APP.PAGES.CREATE_DESC_TITLE' | translate }}</h1>
|
||||||
<mat-progress-bar class="progress-bar" color="primary" *ngIf="loading" mode="indeterminate"></mat-progress-bar>
|
<mat-progress-bar class="progress-bar" color="primary" *ngIf="loading" mode="indeterminate"></mat-progress-bar>
|
||||||
|
|
||||||
<mat-checkbox class="proswitch" color="primary" [(ngModel)]="devmode">
|
<mat-checkbox class="proswitch" color="primary" [(ngModel)]="pro">
|
||||||
{{ 'APP.PROSWITCH' | translate }}
|
{{ 'APP.PROSWITCH' | translate }}
|
||||||
</mat-checkbox>
|
</mat-checkbox>
|
||||||
|
|
||||||
<mat-horizontal-stepper
|
<mat-horizontal-stepper
|
||||||
class="stepper {{ 'last-edited-step-' + stepper.selectedIndex }}"
|
class="stepper {{ 'last-edited-step-' + stepper.selectedIndex }}"
|
||||||
*ngIf="!devmode"
|
*ngIf="!pro"
|
||||||
linear
|
linear
|
||||||
#stepper
|
#stepper
|
||||||
labelPosition="bottom"
|
labelPosition="bottom"
|
||||||
@ -109,6 +109,15 @@
|
|||||||
{{ 'APP.OIDC.REDIRECTDESCRIPTIONWEB' | translate }}
|
{{ 'APP.OIDC.REDIRECTDESCRIPTIONWEB' | translate }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<mat-slide-toggle
|
||||||
|
color="primary"
|
||||||
|
[(ngModel)]="devMode"
|
||||||
|
[ngModelOptions]="{ standalone: true }"
|
||||||
|
matTooltip="{{ 'APP.OIDC.DEVMODEDESC' | translate }}"
|
||||||
|
>
|
||||||
|
{{ 'APP.OIDC.DEVMODE' | translate }}
|
||||||
|
</mat-slide-toggle>
|
||||||
|
|
||||||
<cnsl-redirect-uris
|
<cnsl-redirect-uris
|
||||||
class="redirect-section"
|
class="redirect-section"
|
||||||
[disabled]="false"
|
[disabled]="false"
|
||||||
@ -116,6 +125,7 @@
|
|||||||
[(ngModel)]="redirectUris"
|
[(ngModel)]="redirectUris"
|
||||||
[getValues]="requestRedirectValuesSubject$"
|
[getValues]="requestRedirectValuesSubject$"
|
||||||
title="{{ 'APP.OIDC.REDIRECT' | translate }}"
|
title="{{ 'APP.OIDC.REDIRECT' | translate }}"
|
||||||
|
[devMode]="devMode"
|
||||||
data-e2e="redirect-uris"
|
data-e2e="redirect-uris"
|
||||||
>
|
>
|
||||||
</cnsl-redirect-uris>
|
</cnsl-redirect-uris>
|
||||||
@ -144,6 +154,7 @@
|
|||||||
[getValues]="requestRedirectValuesSubject$"
|
[getValues]="requestRedirectValuesSubject$"
|
||||||
title="{{ 'APP.OIDC.POSTLOGOUTREDIRECT' | translate }}"
|
title="{{ 'APP.OIDC.POSTLOGOUTREDIRECT' | translate }}"
|
||||||
[isNative]="appType?.value.oidcAppType === OIDCAppType.OIDC_APP_TYPE_NATIVE"
|
[isNative]="appType?.value.oidcAppType === OIDCAppType.OIDC_APP_TYPE_NATIVE"
|
||||||
|
[devMode]="devMode"
|
||||||
data-e2e="postlogout-uris"
|
data-e2e="postlogout-uris"
|
||||||
>
|
>
|
||||||
</cnsl-redirect-uris>
|
</cnsl-redirect-uris>
|
||||||
@ -299,6 +310,19 @@
|
|||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container *ngIf="appType?.value?.createType === AppCreateType.OIDC">
|
||||||
|
<div class="row">
|
||||||
|
<span class="left cnsl-secondary-text">
|
||||||
|
{{ 'APP.OIDC.DEVMODE' | translate }}
|
||||||
|
</span>
|
||||||
|
<span class="right">
|
||||||
|
<span>
|
||||||
|
{{ devMode ? ('APP.OIDC.DEVMODE_ENABLED' | translate) : ('APP.OIDC.DEVMODE_DISABLED' | translate) }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
<ng-container *ngIf="appType?.value?.createType === AppCreateType.API">
|
<ng-container *ngIf="appType?.value?.createType === AppCreateType.API">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<span class="left cnsl-secondary-text">
|
<span class="left cnsl-secondary-text">
|
||||||
@ -342,7 +366,7 @@
|
|||||||
</ng-template>
|
</ng-template>
|
||||||
</mat-horizontal-stepper>
|
</mat-horizontal-stepper>
|
||||||
|
|
||||||
<div *ngIf="devmode" class="dev">
|
<div *ngIf="pro" class="dev">
|
||||||
<form [formGroup]="form" (ngSubmit)="createApp()" data-e2e="create-app-wizzard-3">
|
<form [formGroup]="form" (ngSubmit)="createApp()" data-e2e="create-app-wizzard-3">
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<cnsl-form-field class="formfield">
|
<cnsl-form-field class="formfield">
|
||||||
@ -439,6 +463,14 @@
|
|||||||
"
|
"
|
||||||
>
|
>
|
||||||
<div class="formfield full-width">
|
<div class="formfield full-width">
|
||||||
|
<mat-slide-toggle
|
||||||
|
color="primary"
|
||||||
|
[(ngModel)]="devMode"
|
||||||
|
[ngModelOptions]="{ standalone: true }"
|
||||||
|
matTooltip="{{ 'APP.OIDC.DEVMODEDESC' | translate }}"
|
||||||
|
>
|
||||||
|
{{ 'APP.OIDC.DEVMODE' | translate }}
|
||||||
|
</mat-slide-toggle>
|
||||||
<cnsl-redirect-uris
|
<cnsl-redirect-uris
|
||||||
class="redirect-section"
|
class="redirect-section"
|
||||||
[disabled]="false"
|
[disabled]="false"
|
||||||
|
@ -28,6 +28,7 @@ import { Breadcrumb, BreadcrumbService, BreadcrumbType } from 'src/app/services/
|
|||||||
import { ManagementService } from 'src/app/services/mgmt.service';
|
import { ManagementService } from 'src/app/services/mgmt.service';
|
||||||
import { ToastService } from 'src/app/services/toast.service';
|
import { ToastService } from 'src/app/services/toast.service';
|
||||||
|
|
||||||
|
import { MatLegacySlideToggleChange } from '@angular/material/legacy-slide-toggle';
|
||||||
import { AppSecretDialogComponent } from '../app-secret-dialog/app-secret-dialog.component';
|
import { AppSecretDialogComponent } from '../app-secret-dialog/app-secret-dialog.component';
|
||||||
import {
|
import {
|
||||||
BASIC_AUTH_METHOD,
|
BASIC_AUTH_METHOD,
|
||||||
@ -51,7 +52,7 @@ const MAX_ALLOWED_SIZE = 1 * 1024 * 1024;
|
|||||||
export class AppCreateComponent implements OnInit, OnDestroy {
|
export class AppCreateComponent implements OnInit, OnDestroy {
|
||||||
private subscription: Subscription = new Subscription();
|
private subscription: Subscription = new Subscription();
|
||||||
private destroyed$: Subject<void> = new Subject();
|
private destroyed$: Subject<void> = new Subject();
|
||||||
public devmode: boolean = false;
|
public pro: boolean = false;
|
||||||
public projectId: string = '';
|
public projectId: string = '';
|
||||||
public loading: boolean = false;
|
public loading: boolean = false;
|
||||||
|
|
||||||
@ -239,7 +240,16 @@ export class AppCreateComponent implements OnInit, OnDestroy {
|
|||||||
this.oidcAppRequest.setPostLogoutRedirectUrisList(value);
|
this.oidcAppRequest.setPostLogoutRedirectUrisList(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get devMode() {
|
||||||
|
return this.oidcAppRequest.toObject().devMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public set devMode(value: boolean) {
|
||||||
|
this.oidcAppRequest.setDevMode(value);
|
||||||
|
}
|
||||||
|
|
||||||
public ngOnInit(): void {
|
public ngOnInit(): void {
|
||||||
|
this.devMode = false;
|
||||||
this.subscription = this.route.params.subscribe((params) => this.getData(params));
|
this.subscription = this.route.params.subscribe((params) => this.getData(params));
|
||||||
|
|
||||||
const projectId = this.route.snapshot.paramMap.get('projectid');
|
const projectId = this.route.snapshot.paramMap.get('projectid');
|
||||||
@ -362,9 +372,9 @@ export class AppCreateComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public createApp(): void {
|
public createApp(): void {
|
||||||
const appOIDCCheck = this.devmode ? this.isDevOIDC : this.isStepperOIDC;
|
const appOIDCCheck = this.pro ? this.isDevOIDC : this.isStepperOIDC;
|
||||||
const appAPICheck = this.devmode ? this.isDevAPI : this.isStepperAPI;
|
const appAPICheck = this.pro ? this.isDevAPI : this.isStepperAPI;
|
||||||
const appSAMLCheck = this.devmode ? this.isDevSAML : this.isStepperSAML;
|
const appSAMLCheck = this.pro ? this.isDevSAML : this.isStepperSAML;
|
||||||
|
|
||||||
if (appOIDCCheck) {
|
if (appOIDCCheck) {
|
||||||
this.requestRedirectValuesSubject$.next();
|
this.requestRedirectValuesSubject$.next();
|
||||||
|
@ -427,7 +427,7 @@
|
|||||||
|
|
||||||
<div class="app-info-row">
|
<div class="app-info-row">
|
||||||
<div class="app-info-wrapper" *ngFor="let wellKnownV of wellKnownMap$ | async | keyvalue">
|
<div class="app-info-wrapper" *ngFor="let wellKnownV of wellKnownMap$ | async | keyvalue">
|
||||||
<ng-container *ngIf="wellKnownV.key.endsWith('endpoint')">
|
<ng-container *ngIf="wellKnownV.key.endsWith('endpoint') || wellKnownV.key.toString() === 'jwks_uri'">
|
||||||
<p class="app-info-row-title cnsl-secondary-text">{{ wellKnownV.key }}</p>
|
<p class="app-info-row-title cnsl-secondary-text">{{ wellKnownV.key }}</p>
|
||||||
<div class="app-copy-row">
|
<div class="app-copy-row">
|
||||||
<div *ngIf="wellKnownV.value" class="environment">
|
<div *ngIf="wellKnownV.value" class="environment">
|
||||||
|
@ -3,6 +3,7 @@ import { AuthConfig, OAuthService } from 'angular-oauth2-oidc';
|
|||||||
import { BehaviorSubject, from, lastValueFrom, Observable } from 'rxjs';
|
import { BehaviorSubject, from, lastValueFrom, Observable } from 'rxjs';
|
||||||
|
|
||||||
import { StatehandlerService } from './statehandler/statehandler.service';
|
import { StatehandlerService } from './statehandler/statehandler.service';
|
||||||
|
import { ToastService } from './toast.service';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
@ -15,6 +16,7 @@ export class AuthenticationService {
|
|||||||
constructor(
|
constructor(
|
||||||
private oauthService: OAuthService,
|
private oauthService: OAuthService,
|
||||||
private statehandler: StatehandlerService,
|
private statehandler: StatehandlerService,
|
||||||
|
private toast: ToastService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public initConfig(data: AuthConfig): void {
|
public initConfig(data: AuthConfig): void {
|
||||||
@ -39,7 +41,10 @@ export class AuthenticationService {
|
|||||||
}
|
}
|
||||||
this.oauthService.configure(this.authConfig);
|
this.oauthService.configure(this.authConfig);
|
||||||
this.oauthService.strictDiscoveryDocumentValidation = false;
|
this.oauthService.strictDiscoveryDocumentValidation = false;
|
||||||
await this.oauthService.loadDiscoveryDocumentAndTryLogin();
|
await this.oauthService.loadDiscoveryDocumentAndTryLogin().catch((error) => {
|
||||||
|
this.toast.showError(error, false, false);
|
||||||
|
});
|
||||||
|
|
||||||
this._authenticated = this.oauthService.hasValidAccessToken();
|
this._authenticated = this.oauthService.hasValidAccessToken();
|
||||||
if (!this.oauthService.hasValidIdToken() || !this.authenticated || partialConfig || force) {
|
if (!this.oauthService.hasValidIdToken() || !this.authenticated || partialConfig || force) {
|
||||||
const newState = await lastValueFrom(this.statehandler.createState());
|
const newState = await lastValueFrom(this.statehandler.createState());
|
||||||
|
@ -1,19 +1,8 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { SortDirection } from '@angular/material/sort';
|
import { SortDirection } from '@angular/material/sort';
|
||||||
import { OAuthService } from 'angular-oauth2-oidc';
|
import { OAuthService } from 'angular-oauth2-oidc';
|
||||||
import { BehaviorSubject, from, merge, Observable, of, Subject } from 'rxjs';
|
import { BehaviorSubject, forkJoin, from, Observable, of, Subject } from 'rxjs';
|
||||||
import {
|
import { catchError, distinctUntilChanged, filter, finalize, map, switchMap, timeout, withLatestFrom } from 'rxjs/operators';
|
||||||
catchError,
|
|
||||||
distinctUntilChanged,
|
|
||||||
filter,
|
|
||||||
finalize,
|
|
||||||
map,
|
|
||||||
mergeMap,
|
|
||||||
switchMap,
|
|
||||||
take,
|
|
||||||
timeout,
|
|
||||||
withLatestFrom,
|
|
||||||
} from 'rxjs/operators';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AddMyAuthFactorOTPEmailRequest,
|
AddMyAuthFactorOTPEmailRequest,
|
||||||
@ -184,25 +173,21 @@ export class GrpcAuthService {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
this.user = merge(
|
this.user = forkJoin([
|
||||||
of(this.oauthService.getAccessToken()).pipe(filter((token) => (token ? true : false))),
|
of(this.oauthService.getAccessToken()),
|
||||||
this.oauthService.events.pipe(
|
this.oauthService.events.pipe(
|
||||||
filter((e) => e.type === 'token_received'),
|
filter((e) => e.type === 'token_received'),
|
||||||
timeout(this.oauthService.waitForTokenInMsec || 0),
|
timeout(this.oauthService.waitForTokenInMsec || 0),
|
||||||
catchError((_) => of(null)), // timeout is not an error
|
catchError((_) => of(null)), // timeout is not an error
|
||||||
map((_) => this.oauthService.getAccessToken()),
|
map((_) => this.oauthService.getAccessToken()),
|
||||||
),
|
),
|
||||||
).pipe(
|
]).pipe(
|
||||||
take(1),
|
filter((token) => (token ? true : false)),
|
||||||
mergeMap(() => {
|
distinctUntilChanged(),
|
||||||
|
switchMap(() => {
|
||||||
return from(
|
return from(
|
||||||
this.getMyUser().then((resp) => {
|
this.getMyUser().then((resp) => {
|
||||||
const user = resp.user;
|
return resp.user;
|
||||||
if (user) {
|
|
||||||
return user;
|
|
||||||
} else {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
|
@ -71,7 +71,7 @@ export const ONBOARDING_EVENTS: OnboardingActions[] = [
|
|||||||
eventType: 'instance.smtp.config.added',
|
eventType: 'instance.smtp.config.added',
|
||||||
oneof: ['instance.smtp.config.added', 'instance.smtp.config.changed'],
|
oneof: ['instance.smtp.config.added', 'instance.smtp.config.changed'],
|
||||||
link: ['/settings'],
|
link: ['/settings'],
|
||||||
fragment: 'notifications',
|
fragment: 'smtpprovider',
|
||||||
iconClasses: 'las la-envelope',
|
iconClasses: 'las la-envelope',
|
||||||
darkcolor: yellowdark,
|
darkcolor: yellowdark,
|
||||||
lightcolor: yellowlight,
|
lightcolor: yellowlight,
|
||||||
|
@ -1013,6 +1013,8 @@
|
|||||||
"LOCKOUT": "Блокиране",
|
"LOCKOUT": "Блокиране",
|
||||||
"COMPLEXITY": "Сложност на паролата",
|
"COMPLEXITY": "Сложност на паролата",
|
||||||
"NOTIFICATIONS": "Настройки за известията",
|
"NOTIFICATIONS": "Настройки за известията",
|
||||||
|
"SMTP_PROVIDER": "SMTP доставчик",
|
||||||
|
"SMS_PROVIDER": "Доставчик на SMS/телефон",
|
||||||
"NOTIFICATIONS_DESC": "Настройки за SMTP и SMS",
|
"NOTIFICATIONS_DESC": "Настройки за SMTP и SMS",
|
||||||
"MESSAGETEXTS": "Текстове на съобщения",
|
"MESSAGETEXTS": "Текстове на съобщения",
|
||||||
"IDP": "Доставчици на идентичност",
|
"IDP": "Доставчици на идентичност",
|
||||||
@ -1975,6 +1977,8 @@
|
|||||||
"CLIENTSECRET_DESCRIPTION": "Пазете клиентската си тайна на сигурно място, тъй като тя ще изчезне, след като диалоговият прозорец бъде затворен.",
|
"CLIENTSECRET_DESCRIPTION": "Пазете клиентската си тайна на сигурно място, тъй като тя ще изчезне, след като диалоговият прозорец бъде затворен.",
|
||||||
"REGENERATESECRET": "Повторно генериране на клиентска тайна",
|
"REGENERATESECRET": "Повторно генериране на клиентска тайна",
|
||||||
"DEVMODE": "Режим на разработка",
|
"DEVMODE": "Режим на разработка",
|
||||||
|
"DEVMODE_ENABLED": "Активиран",
|
||||||
|
"DEVMODE_DISABLED": "Деактивиран",
|
||||||
"DEVMODEDESC": "Внимание: При активиран режим на разработка URI адресите за пренасочване няма да бъдат валидирани.",
|
"DEVMODEDESC": "Внимание: При активиран режим на разработка URI адресите за пренасочване няма да бъдат валидирани.",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE": "Пропуснете страницата за успешно влизане",
|
"SKIPNATIVEAPPSUCCESSPAGE": "Пропуснете страницата за успешно влизане",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Пропуснете страницата за успех след влизане в това родно приложение.",
|
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Пропуснете страницата за успех след влизане в това родно приложение.",
|
||||||
|
@ -1019,6 +1019,8 @@
|
|||||||
"LOCKOUT": "Sperrmechanismen",
|
"LOCKOUT": "Sperrmechanismen",
|
||||||
"COMPLEXITY": "Passwordkomplexität",
|
"COMPLEXITY": "Passwordkomplexität",
|
||||||
"NOTIFICATIONS": "Benachrichtigungseinstellungen",
|
"NOTIFICATIONS": "Benachrichtigungseinstellungen",
|
||||||
|
"SMTP_PROVIDER": "SMTP-Anbieter",
|
||||||
|
"SMS_PROVIDER": "SMS / Telefon Anbieter",
|
||||||
"NOTIFICATIONS_DESC": "SMTP und SMS Einstellungen",
|
"NOTIFICATIONS_DESC": "SMTP und SMS Einstellungen",
|
||||||
"MESSAGETEXTS": "Benachrichtigungstexte",
|
"MESSAGETEXTS": "Benachrichtigungstexte",
|
||||||
"IDP": "Identitätsanbieter",
|
"IDP": "Identitätsanbieter",
|
||||||
@ -1984,6 +1986,8 @@
|
|||||||
"CLIENTSECRET_DESCRIPTION": "Verwahre das Client Secret an einem sicheren Ort, da es nicht mehr angezeigt werden kann, sobald der Dialog geschlossen wird.",
|
"CLIENTSECRET_DESCRIPTION": "Verwahre das Client Secret an einem sicheren Ort, da es nicht mehr angezeigt werden kann, sobald der Dialog geschlossen wird.",
|
||||||
"REGENERATESECRET": "Client Secret neu generieren",
|
"REGENERATESECRET": "Client Secret neu generieren",
|
||||||
"DEVMODE": "Entwicklermodus",
|
"DEVMODE": "Entwicklermodus",
|
||||||
|
"DEVMODE_ENABLED": "Aktiviert",
|
||||||
|
"DEVMODE_DISABLED": "Deaktiviert",
|
||||||
"DEVMODEDESC": "Bei eingeschaltetem Entwicklermodus werden die Weiterleitungs-URIs im OIDC-Flow nicht validiert.",
|
"DEVMODEDESC": "Bei eingeschaltetem Entwicklermodus werden die Weiterleitungs-URIs im OIDC-Flow nicht validiert.",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE": "Login Erfolgseite überspringen",
|
"SKIPNATIVEAPPSUCCESSPAGE": "Login Erfolgseite überspringen",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Erfolgseite nach dem Login für diese Native Applikation überspringen.",
|
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Erfolgseite nach dem Login für diese Native Applikation überspringen.",
|
||||||
|
@ -1019,7 +1019,9 @@
|
|||||||
"LOGIN": "Login Behavior and Security",
|
"LOGIN": "Login Behavior and Security",
|
||||||
"LOCKOUT": "Lockout",
|
"LOCKOUT": "Lockout",
|
||||||
"COMPLEXITY": "Password complexity",
|
"COMPLEXITY": "Password complexity",
|
||||||
"NOTIFICATIONS": "Notification settings",
|
"NOTIFICATIONS": "Notifications",
|
||||||
|
"SMTP_PROVIDER": "SMTP Provider",
|
||||||
|
"SMS_PROVIDER": "SMS/Phone Provider",
|
||||||
"NOTIFICATIONS_DESC": "SMTP and SMS Settings",
|
"NOTIFICATIONS_DESC": "SMTP and SMS Settings",
|
||||||
"MESSAGETEXTS": "Message Texts",
|
"MESSAGETEXTS": "Message Texts",
|
||||||
"IDP": "Identity Providers",
|
"IDP": "Identity Providers",
|
||||||
@ -1993,6 +1995,8 @@
|
|||||||
"CLIENTSECRET_DESCRIPTION": "Keep your client secret at a safe place as it will disappear once the dialog is closed.",
|
"CLIENTSECRET_DESCRIPTION": "Keep your client secret at a safe place as it will disappear once the dialog is closed.",
|
||||||
"REGENERATESECRET": "Regenerate Client Secret",
|
"REGENERATESECRET": "Regenerate Client Secret",
|
||||||
"DEVMODE": "Development Mode",
|
"DEVMODE": "Development Mode",
|
||||||
|
"DEVMODE_ENABLED": "Enabled",
|
||||||
|
"DEVMODE_DISABLED": "Disabled",
|
||||||
"DEVMODEDESC": "Beware: With development mode enabled redirect URIs will not be validated.",
|
"DEVMODEDESC": "Beware: With development mode enabled redirect URIs will not be validated.",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE": "Skip Login Success Page",
|
"SKIPNATIVEAPPSUCCESSPAGE": "Skip Login Success Page",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Skip the success page after a login for this native app.",
|
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Skip the success page after a login for this native app.",
|
||||||
|
@ -1020,6 +1020,8 @@
|
|||||||
"LOCKOUT": "Bloqueo",
|
"LOCKOUT": "Bloqueo",
|
||||||
"COMPLEXITY": "Complejidad de contraseña",
|
"COMPLEXITY": "Complejidad de contraseña",
|
||||||
"NOTIFICATIONS": "Ajustes de notificación",
|
"NOTIFICATIONS": "Ajustes de notificación",
|
||||||
|
"SMTP_PROVIDER": "Proveedor SMTP",
|
||||||
|
"SMS_PROVIDER": "Proveedor SMS/Teléfono",
|
||||||
"NOTIFICATIONS_DESC": "Ajustes SMTP y SMS",
|
"NOTIFICATIONS_DESC": "Ajustes SMTP y SMS",
|
||||||
"MESSAGETEXTS": "Mensajes de texto",
|
"MESSAGETEXTS": "Mensajes de texto",
|
||||||
"IDP": "Proveedores de identidad",
|
"IDP": "Proveedores de identidad",
|
||||||
@ -1981,6 +1983,8 @@
|
|||||||
"CLIENTSECRET_DESCRIPTION": "Mantén tu secreto de cliente en un lugar seguro puesto que desaparecerá una vez que se cierre el diálogo.",
|
"CLIENTSECRET_DESCRIPTION": "Mantén tu secreto de cliente en un lugar seguro puesto que desaparecerá una vez que se cierre el diálogo.",
|
||||||
"REGENERATESECRET": "Regenerar secreto de cliente",
|
"REGENERATESECRET": "Regenerar secreto de cliente",
|
||||||
"DEVMODE": "Modo Desarrollo",
|
"DEVMODE": "Modo Desarrollo",
|
||||||
|
"DEVMODE_ENABLED": "Activado",
|
||||||
|
"DEVMODE_DISABLED": "Desactivado",
|
||||||
"DEVMODEDESC": "Cuidado: Si el modo de desarrollo está activado las URIs de redirección no serán validadas.",
|
"DEVMODEDESC": "Cuidado: Si el modo de desarrollo está activado las URIs de redirección no serán validadas.",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE": "Saltar página de inicio de sesión con éxito",
|
"SKIPNATIVEAPPSUCCESSPAGE": "Saltar página de inicio de sesión con éxito",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Sáltate la página de éxito después de iniciar sesión en esta app nativa.",
|
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Sáltate la página de éxito después de iniciar sesión en esta app nativa.",
|
||||||
|
@ -1019,6 +1019,8 @@
|
|||||||
"LOCKOUT": "Verrouillage",
|
"LOCKOUT": "Verrouillage",
|
||||||
"COMPLEXITY": "Complexité du mot de passe",
|
"COMPLEXITY": "Complexité du mot de passe",
|
||||||
"NOTIFICATIONS": "Paramètres de notification",
|
"NOTIFICATIONS": "Paramètres de notification",
|
||||||
|
"SMTP_PROVIDER": "Fournisseur SMTP",
|
||||||
|
"SMS_PROVIDER": "SMS/Téléphone Fournisseur",
|
||||||
"NOTIFICATIONS_DESC": "Paramètres SMTP et SMS",
|
"NOTIFICATIONS_DESC": "Paramètres SMTP et SMS",
|
||||||
"MESSAGETEXTS": "Textes des messages",
|
"MESSAGETEXTS": "Textes des messages",
|
||||||
"IDP": "Fournisseurs d'identité",
|
"IDP": "Fournisseurs d'identité",
|
||||||
@ -1985,6 +1987,8 @@
|
|||||||
"CLIENTSECRET_DESCRIPTION": "Conservez votre secret client dans un endroit sûr car il disparaîtra une fois la boîte de dialogue fermée.",
|
"CLIENTSECRET_DESCRIPTION": "Conservez votre secret client dans un endroit sûr car il disparaîtra une fois la boîte de dialogue fermée.",
|
||||||
"REGENERATESECRET": "Régénérer le secret du client",
|
"REGENERATESECRET": "Régénérer le secret du client",
|
||||||
"DEVMODE": "Mode développement",
|
"DEVMODE": "Mode développement",
|
||||||
|
"DEVMODE_ENABLED": "Activé",
|
||||||
|
"DEVMODE_DISABLED": "Désactivé",
|
||||||
"DEVMODEDESC": "Attention",
|
"DEVMODEDESC": "Attention",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE": "Sauter la page de succès de connexion",
|
"SKIPNATIVEAPPSUCCESSPAGE": "Sauter la page de succès de connexion",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Sauter la page de succès après la connexion pour cette application native",
|
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Sauter la page de succès après la connexion pour cette application native",
|
||||||
|
@ -1019,6 +1019,8 @@
|
|||||||
"LOCKOUT": "Meccanismi di bloccaggio",
|
"LOCKOUT": "Meccanismi di bloccaggio",
|
||||||
"COMPLEXITY": "Complessità della password",
|
"COMPLEXITY": "Complessità della password",
|
||||||
"NOTIFICATIONS": "Impostazioni di notifica",
|
"NOTIFICATIONS": "Impostazioni di notifica",
|
||||||
|
"SMTP_PROVIDER": "Fornitore SMTP",
|
||||||
|
"SMS_PROVIDER": "Fornitore di servizi SMS/telefonici",
|
||||||
"NOTIFICATIONS_DESC": "Impostazioni SMTP e SMS",
|
"NOTIFICATIONS_DESC": "Impostazioni SMTP e SMS",
|
||||||
"MESSAGETEXTS": "Testi di notifica",
|
"MESSAGETEXTS": "Testi di notifica",
|
||||||
"IDP": "Fornitori di identità",
|
"IDP": "Fornitori di identità",
|
||||||
@ -1985,6 +1987,8 @@
|
|||||||
"CLIENTSECRET_DESCRIPTION": "Salvate il Client Secret in un luogo sicuro, perch\u00e9 non sarà più disponibile dopo aver chiuso la finestra di dialogo",
|
"CLIENTSECRET_DESCRIPTION": "Salvate il Client Secret in un luogo sicuro, perch\u00e9 non sarà più disponibile dopo aver chiuso la finestra di dialogo",
|
||||||
"REGENERATESECRET": "Rigenera il Client Secret",
|
"REGENERATESECRET": "Rigenera il Client Secret",
|
||||||
"DEVMODE": "Modalit\u00e0 di sviluppo (DEV Mode)",
|
"DEVMODE": "Modalit\u00e0 di sviluppo (DEV Mode)",
|
||||||
|
"DEVMODE_ENABLED": "Attivato",
|
||||||
|
"DEVMODE_DISABLED": "Disattivato",
|
||||||
"DEVMODEDESC": "Attenzione: Con la modalit\u00e0 di sviluppo abilitata, gli URI di reindirizzamento non saranno convalidati.",
|
"DEVMODEDESC": "Attenzione: Con la modalit\u00e0 di sviluppo abilitata, gli URI di reindirizzamento non saranno convalidati.",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE": "Salta la pagina di successo dopo il login",
|
"SKIPNATIVEAPPSUCCESSPAGE": "Salta la pagina di successo dopo il login",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Salta la pagina di successo dopo il login per questa applicazione nativa",
|
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Salta la pagina di successo dopo il login per questa applicazione nativa",
|
||||||
|
@ -1020,6 +1020,8 @@
|
|||||||
"LOCKOUT": "ロックアウト",
|
"LOCKOUT": "ロックアウト",
|
||||||
"COMPLEXITY": "パスワードの複雑さ",
|
"COMPLEXITY": "パスワードの複雑さ",
|
||||||
"NOTIFICATIONS": "通知設定",
|
"NOTIFICATIONS": "通知設定",
|
||||||
|
"SMTP_PROVIDER": "SMTPプロバイダー",
|
||||||
|
"SMS_PROVIDER": "SMS/電話プロバイダー",
|
||||||
"NOTIFICATIONS_DESC": "SMTPおよびSMS設定",
|
"NOTIFICATIONS_DESC": "SMTPおよびSMS設定",
|
||||||
"MESSAGETEXTS": "メッセージテキスト",
|
"MESSAGETEXTS": "メッセージテキスト",
|
||||||
"IDP": "IDプロバイダー",
|
"IDP": "IDプロバイダー",
|
||||||
@ -1976,6 +1978,8 @@
|
|||||||
"CLIENTSECRET_DESCRIPTION": "クライアントシークレットは、ダイアログを閉じると消えてしまうので、安全な場所に保管してください。",
|
"CLIENTSECRET_DESCRIPTION": "クライアントシークレットは、ダイアログを閉じると消えてしまうので、安全な場所に保管してください。",
|
||||||
"REGENERATESECRET": "クライアントシークレットを再生成する",
|
"REGENERATESECRET": "クライアントシークレットを再生成する",
|
||||||
"DEVMODE": "開発モード",
|
"DEVMODE": "開発モード",
|
||||||
|
"DEVMODE_ENABLED": "アクティブ化された",
|
||||||
|
"DEVMODE_DISABLED": "無効化されました",
|
||||||
"DEVMODEDESC": "注意:開発モードを有効にすると、URIが認証されません。",
|
"DEVMODEDESC": "注意:開発モードを有効にすると、URIが認証されません。",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE": "ログイン後に成功ページをスキップする",
|
"SKIPNATIVEAPPSUCCESSPAGE": "ログイン後に成功ページをスキップする",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "このネイティブアプリのログイン後に成功ページをスキップする",
|
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "このネイティブアプリのログイン後に成功ページをスキップする",
|
||||||
|
@ -1021,6 +1021,8 @@
|
|||||||
"LOCKOUT": "Забрана на пристап",
|
"LOCKOUT": "Забрана на пристап",
|
||||||
"COMPLEXITY": "Сложеност на лозинката",
|
"COMPLEXITY": "Сложеност на лозинката",
|
||||||
"NOTIFICATIONS": "Подесувања за известувања",
|
"NOTIFICATIONS": "Подесувања за известувања",
|
||||||
|
"SMTP_PROVIDER": "SMTP провајдер",
|
||||||
|
"SMS_PROVIDER": "СМС/Провајдер на телефон",
|
||||||
"NOTIFICATIONS_DESC": "Подесувања за SMTP и SMS",
|
"NOTIFICATIONS_DESC": "Подесувања за SMTP и SMS",
|
||||||
"MESSAGETEXTS": "Текстови на пораки",
|
"MESSAGETEXTS": "Текстови на пораки",
|
||||||
"IDP": "Identity Providers",
|
"IDP": "Identity Providers",
|
||||||
@ -1982,6 +1984,8 @@
|
|||||||
"CLIENTSECRET_DESCRIPTION": "Чувајте ја вашата клиентска тајна на безбедно место, бидејќи ќе исчезне откако ќе се затвори дијалогот.",
|
"CLIENTSECRET_DESCRIPTION": "Чувајте ја вашата клиентска тајна на безбедно место, бидејќи ќе исчезне откако ќе се затвори дијалогот.",
|
||||||
"REGENERATESECRET": "Генерирај нова клиентска тајна",
|
"REGENERATESECRET": "Генерирај нова клиентска тајна",
|
||||||
"DEVMODE": "Development mode",
|
"DEVMODE": "Development mode",
|
||||||
|
"DEVMODE_ENABLED": "Активиран",
|
||||||
|
"DEVMODE_DISABLED": "Деактивирано",
|
||||||
"DEVMODEDESC": "Внимавајте: Со овозможен Development mode, URIs за пренасочување нема да бидат валидирани.",
|
"DEVMODEDESC": "Внимавајте: Со овозможен Development mode, URIs за пренасочување нема да бидат валидирани.",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE": "Прескокни страница за успешна најава",
|
"SKIPNATIVEAPPSUCCESSPAGE": "Прескокни страница за успешна најава",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Прескокнете ја страницата за успешна најава за оваа нативна апликација.",
|
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Прескокнете ја страницата за успешна најава за оваа нативна апликација.",
|
||||||
|
@ -1019,6 +1019,8 @@
|
|||||||
"LOCKOUT": "Blokada",
|
"LOCKOUT": "Blokada",
|
||||||
"COMPLEXITY": "Złożoność hasła",
|
"COMPLEXITY": "Złożoność hasła",
|
||||||
"NOTIFICATIONS": "Ustawienia powiadomień",
|
"NOTIFICATIONS": "Ustawienia powiadomień",
|
||||||
|
"SMTP_PROVIDER": "Dostawca SMTP",
|
||||||
|
"SMS_PROVIDER": "Dostawca SMS-ów/telefonów",
|
||||||
"NOTIFICATIONS_DESC": "Ustawienia SMTP i SMS",
|
"NOTIFICATIONS_DESC": "Ustawienia SMTP i SMS",
|
||||||
"MESSAGETEXTS": "Teksty wiadomości",
|
"MESSAGETEXTS": "Teksty wiadomości",
|
||||||
"IDP": "Dostawcy tożsamości",
|
"IDP": "Dostawcy tożsamości",
|
||||||
@ -1985,6 +1987,8 @@
|
|||||||
"CLIENTSECRET_DESCRIPTION": "Trzymaj swój sekret klienta w bezpiecznym miejscu, ponieważ zniknie on po zamknięciu okna dialogowego.",
|
"CLIENTSECRET_DESCRIPTION": "Trzymaj swój sekret klienta w bezpiecznym miejscu, ponieważ zniknie on po zamknięciu okna dialogowego.",
|
||||||
"REGENERATESECRET": "Odtwórz sekret klienta",
|
"REGENERATESECRET": "Odtwórz sekret klienta",
|
||||||
"DEVMODE": "Tryb rozwoju",
|
"DEVMODE": "Tryb rozwoju",
|
||||||
|
"DEVMODE_ENABLED": "Aktywowany",
|
||||||
|
"DEVMODE_DISABLED": "Dezaktywowane",
|
||||||
"DEVMODEDESC": "Uwaga: przy włączonym trybie rozwoju adresy URI przekierowania nie będą sprawdzane.",
|
"DEVMODEDESC": "Uwaga: przy włączonym trybie rozwoju adresy URI przekierowania nie będą sprawdzane.",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE": "Pomiń stronę sukcesu po zalogowaniu",
|
"SKIPNATIVEAPPSUCCESSPAGE": "Pomiń stronę sukcesu po zalogowaniu",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Pomiń stronę sukcesu po zalogowaniu dla tej Natywny aplikację",
|
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Pomiń stronę sukcesu po zalogowaniu dla tej Natywny aplikację",
|
||||||
|
@ -1021,6 +1021,8 @@
|
|||||||
"LOCKOUT": "Bloqueio",
|
"LOCKOUT": "Bloqueio",
|
||||||
"COMPLEXITY": "Complexidade de Senha",
|
"COMPLEXITY": "Complexidade de Senha",
|
||||||
"NOTIFICATIONS": "Configurações de Notificação",
|
"NOTIFICATIONS": "Configurações de Notificação",
|
||||||
|
"SMTP_PROVIDER": "Provedor SMTP",
|
||||||
|
"SMS_PROVIDER": "Provedor de SMS/Telefone",
|
||||||
"NOTIFICATIONS_DESC": "Configurações de SMTP e SMS",
|
"NOTIFICATIONS_DESC": "Configurações de SMTP e SMS",
|
||||||
"MESSAGETEXTS": "Textos de Mensagem",
|
"MESSAGETEXTS": "Textos de Mensagem",
|
||||||
"IDP": "Provedores de Identidade",
|
"IDP": "Provedores de Identidade",
|
||||||
@ -1980,6 +1982,8 @@
|
|||||||
"CLIENTSECRET_DESCRIPTION": "Mantenha o segredo do cliente em um local seguro, pois ele desaparecerá assim que o diálogo for fechado.",
|
"CLIENTSECRET_DESCRIPTION": "Mantenha o segredo do cliente em um local seguro, pois ele desaparecerá assim que o diálogo for fechado.",
|
||||||
"REGENERATESECRET": "Regenerar Segredo do Cliente",
|
"REGENERATESECRET": "Regenerar Segredo do Cliente",
|
||||||
"DEVMODE": "Modo de Desenvolvimento",
|
"DEVMODE": "Modo de Desenvolvimento",
|
||||||
|
"DEVMODE_ENABLED": "Ativado",
|
||||||
|
"DEVMODE_DISABLED": "Desativado",
|
||||||
"DEVMODEDESC": "Atenção: Com o modo de desenvolvimento habilitado, as URIs de redirecionamento não serão validadas.",
|
"DEVMODEDESC": "Atenção: Com o modo de desenvolvimento habilitado, as URIs de redirecionamento não serão validadas.",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE": "Pular Página de Sucesso de Login",
|
"SKIPNATIVEAPPSUCCESSPAGE": "Pular Página de Sucesso de Login",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Pule a página de sucesso após o login para este aplicativo nativo.",
|
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Pule a página de sucesso após o login para este aplicativo nativo.",
|
||||||
|
@ -1019,6 +1019,8 @@
|
|||||||
"LOCKOUT": "安全锁策略",
|
"LOCKOUT": "安全锁策略",
|
||||||
"COMPLEXITY": "密码复杂性",
|
"COMPLEXITY": "密码复杂性",
|
||||||
"NOTIFICATIONS": "通知设置",
|
"NOTIFICATIONS": "通知设置",
|
||||||
|
"SMTP_PROVIDER": "SMTP 提供商",
|
||||||
|
"SMS_PROVIDER": "短信/电话提供商",
|
||||||
"NOTIFICATIONS_DESC": "SMTP 和 SMS 设置",
|
"NOTIFICATIONS_DESC": "SMTP 和 SMS 设置",
|
||||||
"MESSAGETEXTS": "消息文本",
|
"MESSAGETEXTS": "消息文本",
|
||||||
"IDP": "身份提供者",
|
"IDP": "身份提供者",
|
||||||
@ -1984,6 +1986,8 @@
|
|||||||
"CLIENTSECRET_DESCRIPTION": "将您的客户保密在一个安全的地方,因为一旦对话框关闭,便无法再次查看。",
|
"CLIENTSECRET_DESCRIPTION": "将您的客户保密在一个安全的地方,因为一旦对话框关闭,便无法再次查看。",
|
||||||
"REGENERATESECRET": "重新生成客户端密钥",
|
"REGENERATESECRET": "重新生成客户端密钥",
|
||||||
"DEVMODE": "开发模式",
|
"DEVMODE": "开发模式",
|
||||||
|
"DEVMODE_ENABLED": "活性",
|
||||||
|
"DEVMODE_DISABLED": "已停用",
|
||||||
"DEVMODEDESC": "注意:启用开发模式的重定向 URI 将不会被验证。",
|
"DEVMODEDESC": "注意:启用开发模式的重定向 URI 将不会被验证。",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE": "登录后跳过成功页面",
|
"SKIPNATIVEAPPSUCCESSPAGE": "登录后跳过成功页面",
|
||||||
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "登录后跳过本机应用的成功页面",
|
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "登录后跳过本机应用的成功页面",
|
||||||
|
69
docs/docs/guides/integrate/identity-providers/keycloak.mdx
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
---
|
||||||
|
title: Configure Keycloak as an Identity Provider in ZITADEL
|
||||||
|
sidebar_label: Keycloak generic OIDC
|
||||||
|
id: keycloak
|
||||||
|
---
|
||||||
|
|
||||||
|
import GeneralConfigDescription from './_general_config_description.mdx';
|
||||||
|
import Intro from './_intro.mdx';
|
||||||
|
import CustomLoginPolicy from './_custom_login_policy.mdx';
|
||||||
|
import IDPsOverview from './_idps_overview.mdx';
|
||||||
|
import GenericOIDC from './_generic_oidc.mdx';
|
||||||
|
import Activate from './_activate.mdx';
|
||||||
|
import TestSetup from './_test_setup.mdx';
|
||||||
|
|
||||||
|
<Intro provider="Keycloak"/>
|
||||||
|
|
||||||
|
## Keycloak Configuration
|
||||||
|
|
||||||
|
### Register a new client
|
||||||
|
|
||||||
|
1. Login to your Keycloak account and go to the clients list: <$KEYCLOAK-DOMAIN/auth/admin/$REALM/console/#/$REALM/clients>
|
||||||
|
2. Click on "Create Client"
|
||||||
|
3. Choose OpenID Connect as Client Type and give your client an ID
|
||||||
|
4. Enable Client authentication and the standard flow and direct access grants as authentication flow
|
||||||
|
5. Add the valid redirect URIs
|
||||||
|
- {your-domain}/ui/login/login/externalidp/callback
|
||||||
|
- Example redirect url for the domain `https://acme-gzoe4x.zitadel.cloud` would look like this: `https://acme-gzoe4x.zitadel.cloud/ui/login/login/externalidp/callback`
|
||||||
|
6. Go to the credentials tab and copy the secret
|
||||||
|
|
||||||
|

|
||||||
|

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

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

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

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

|
@ -9,7 +9,7 @@ import OrgDescription from "../../../concepts/structure/_org_description.mdx";
|
|||||||
import Column from "../../../../src/components/column";
|
import Column from "../../../../src/components/column";
|
||||||
|
|
||||||
An Organization is where your projects and users live. Looking at a B2B use case, an organization represents a business partner who typically has its own branding and has different access settings like additional federated login providers.
|
An Organization is where your projects and users live. Looking at a B2B use case, an organization represents a business partner who typically has its own branding and has different access settings like additional federated login providers.
|
||||||
Users from one organization are seperated from others.
|
Users from one organization are separated from others.
|
||||||
|
|
||||||
## Create a new organization
|
## Create a new organization
|
||||||
|
|
||||||
@ -23,7 +23,7 @@ If you choose your logged in user as organization manager, a membership for the
|
|||||||
alt="Select Organization"
|
alt="Select Organization"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
If you want to enable your customers to create their organization by themselves, we provide a creation form for a organization. `<https://$CUSTOM-DOMAIN/ui/login/register/org`
|
If you want to enable your customers to create their organization by themselves, we provide a creation form for an organization. `<https://$CUSTOM-DOMAIN/ui/login/register/org`
|
||||||
The customer needs to fill in the form with the organization name and the contact details.
|
The customer needs to fill in the form with the organization name and the contact details.
|
||||||
|
|
||||||
<img
|
<img
|
||||||
@ -34,8 +34,8 @@ The customer needs to fill in the form with the organization name and the contac
|
|||||||
|
|
||||||
## How ZITADEL handles usernames
|
## How ZITADEL handles usernames
|
||||||
|
|
||||||
If you domain setting "user loginname must contain orgdomain" is disabled. Your username will be unique withing the whole instance.
|
If your domain setting "user loginname must contain orgdomain" is disabled, your username will be unique within the whole instance.
|
||||||
At the moment the username only allowes e-mail formatted input. (This will be changed soon)
|
At the moment the username only allows e-mail formatted input. (This will be changed soon)
|
||||||
|
|
||||||
### User Loginname must contain orgdomain
|
### User Loginname must contain orgdomain
|
||||||
|
|
||||||
@ -46,9 +46,9 @@ Those loginnames consist of the format `{username}@{domainname}.{zitadeldomain}`
|
|||||||
If your user had the username `john.doe`, the generated loginname would be `john.doe@acme.zitadel.cloud`.
|
If your user had the username `john.doe`, the generated loginname would be `john.doe@acme.zitadel.cloud`.
|
||||||
This also means that only one user with the username `john.doe` can exist in your organization called `ACME`.
|
This also means that only one user with the username `john.doe` can exist in your organization called `ACME`.
|
||||||
|
|
||||||
If you verify your domain name or add additional domains, ZITADEL will generate those additional logonames for you.
|
If you verify your domain name or add additional domains, ZITADEL will generate those additional login names for you.
|
||||||
If the organization would own the domain `acme.ch` and verify it, then the resulting loginname would be `john.doe@acme.ch` in addition to the already generated `john.doe@acme.zitadel.cloud`.
|
If the organization would own the domain `acme.ch` and verify it, then the resulting loginname would be `john.doe@acme.ch` in addition to the already generated `john.doe@acme.zitadel.cloud`.
|
||||||
The user can now use either logonname to authenticate with your application.
|
The user can now use either login name to authenticate with your application.
|
||||||
|
|
||||||
> Note: You can set this setting on your instance as well as your organizations. All available usernames are shown on the top of the user pages.
|
> Note: You can set this setting on your instance as well as your organizations. All available usernames are shown on the top of the user pages.
|
||||||
|
|
||||||
@ -59,12 +59,14 @@ Users that you create within your organization will be suffixed with this domain
|
|||||||
|
|
||||||
You can improve the user experience, by suffixing users with a domain name that is in your control.
|
You can improve the user experience, by suffixing users with a domain name that is in your control.
|
||||||
If the "validate org domains" settings in the [Domain Settings](./instance-settings#domain-settings) is set to true, you have to prove the ownership of your domain, by DNS or HTTP challenge.
|
If the "validate org domains" settings in the [Domain Settings](./instance-settings#domain-settings) is set to true, you have to prove the ownership of your domain, by DNS or HTTP challenge.
|
||||||
If the settings is set to false, the created domain will automatically be set to verifed.
|
If the setting is set to false, the created domain will automatically be set to verifed.
|
||||||
|
|
||||||
An organization can have multiple domain names, but only one domain can be primary.
|
An organization can have multiple domain names, but only one domain can be primary.
|
||||||
The primary domain defines which login name ZITADEL displays to the user, and what information gets asserted in access_tokens (`preferred_username`).
|
The primary domain defines which login name ZITADEL displays to the user, and what information gets asserted in access_tokens (`preferred_username`).
|
||||||
|
|
||||||
Please note that domain verification also removes the logonname from all users, who might have used this combination in the global organization (ie. users not belonging to a specific organization). Relating to our example with acme.ch: If a user ‘coyote’ exists in the global organization with the logonname coyote@acme.ch, then after verification of acme.ch, this logonname will be replaced with `coyote@{randomvalue.tld}`. ZITADEL will notify users affected by this change.
|
Please note that domain verification also removes the login name from all users, who might have used this combination in the global organization (ie. users not belonging to a specific organization).
|
||||||
|
Relating to our example with acme.ch: If a user ‘coyote’ exists in the global organization with the login name coyote@acme.ch, then after verification of acme.ch, this login name will be replaced with `coyote@{randomvalue.tld}`.
|
||||||
|
ZITADEL will notify users affected by this change.
|
||||||
|
|
||||||
## Verify your domain name
|
## Verify your domain name
|
||||||
|
|
||||||
|
306
docs/docs/guides/migrate/sources/keycloak.md
Normal file
@ -0,0 +1,306 @@
|
|||||||
|
---
|
||||||
|
title: Migrate from Keycloak
|
||||||
|
sidebar_label: From Keycloak
|
||||||
|
---
|
||||||
|
|
||||||
|
## Migrating from Keycloak to ZITADEL
|
||||||
|
|
||||||
|
This guide will use [Docker installation](https://www.docker.com/) to run Keycloak and ZITADEL. However, both Keycloak and ZITADEL offer different installation methods. As a result, this guide won't include any required production tuning or security hardening for either system. However, it's advised you follow [recommended guidelines](https://zitadel.com/docs/guides/manage/self-hosted/production) before putting those systems into production. You can skip setting up Keycloak and ZITADEL if you already have running instances.
|
||||||
|
|
||||||
|
## Set up Keycloak
|
||||||
|
### Run Keycloak
|
||||||
|
|
||||||
|
To begin setting up Keycloak, you need to refer to the official [Keycloak Docker image](https://www.keycloak.org/getting-started/getting-started-docker). You'll use it to run a development version of the Keycloak server on your local machine:
|
||||||
|
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run -d -p 8081:8080 -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=admin quay.io/keycloak/keycloak:22.0.1 start-dev
|
||||||
|
```
|
||||||
|
|
||||||
|
In a few seconds, Keycloak will be available at [http://localhost:8081](http://localhost:8081). Access the **Administration Console** via the username `admin` and password `admin`:
|
||||||
|
|
||||||
|
<img src="/docs/img/guides/migrate/keycloak-01.png" alt="Migrating users from Keycloak to ZITADEL"/>
|
||||||
|
|
||||||
|
<img src="/docs/img/guides/migrate/keycloak-02.png" alt="Migrating users from Keycloak to ZITADEL"/>
|
||||||
|
|
||||||
|
|
||||||
|
### Create a realm in Keycloak
|
||||||
|
|
||||||
|
In order to configure Keycloak as the identity provider for your application, you need to create a new realm. This will allow users and authentication resources to be isolated from any other Keycloak usage. Click on the sidebar drop-down menu and select **Create Realm**. Then input the desired realm name and click **Create**:
|
||||||
|
|
||||||
|
<img src="/docs/img/guides/migrate/keycloak-03.png" alt="Migrating users from Keycloak to ZITADEL"/>
|
||||||
|
|
||||||
|
<img src="/docs/img/guides/migrate/keycloak-04.png" alt="Migrating users from Keycloak to ZITADEL"/>
|
||||||
|
|
||||||
|
<img src="/docs/img/guides/migrate/keycloak-05.png" alt="Migrating users from Keycloak to ZITADEL"/>
|
||||||
|
|
||||||
|
|
||||||
|
### Create user in Keycloak
|
||||||
|
|
||||||
|
The last thing you need to do in Keycloak is to create at least one new user. This user will be able to log into your application.
|
||||||
|
|
||||||
|
On the menu on the left, select **Users**, and click **Add user**. Fill in the username, email, and first and last names, and mark the email as verified. Click on **Create** to create a new user:
|
||||||
|
|
||||||
|
<img src="/docs/img/guides/migrate/keycloak-11.png" alt="Migrating users from Keycloak to ZITADEL"/>
|
||||||
|
|
||||||
|
<img src="/docs/img/guides/migrate/keycloak-12.png" alt="Migrating users from Keycloak to ZITADEL"/>
|
||||||
|
|
||||||
|
<img src="/docs/img/guides/migrate/keycloak-13.png" alt="Migrating users from Keycloak to ZITADEL"/>
|
||||||
|
|
||||||
|
Now you should attach a password to this user. Select the **Credentials** tab and click **Set password**.
|
||||||
|
|
||||||
|
<img src="/docs/img/guides/migrate/keycloak-14.png" alt="Migrating users from Keycloak to ZITADEL"/>
|
||||||
|
|
||||||
|
On the new modal panel, input the desired password and select **Save**.
|
||||||
|
|
||||||
|
<img src="/docs/img/guides/migrate/keycloak-15.png" alt="Migrating users from Keycloak to ZITADEL"/>
|
||||||
|
|
||||||
|
<img src="/docs/img/guides/migrate/keycloak-16.png" alt="Migrating users from Keycloak to ZITADEL"/>
|
||||||
|
|
||||||
|
### Export Keycloak users
|
||||||
|
|
||||||
|
Keycloak provides an [export](https://www.keycloak.org/server/importExport) functionality that allows user information to be extracted into JSON files. While it's intended to be used in another Keycloak instance, you can manipulate it to export users to a different user management system.
|
||||||
|
|
||||||
|
For example, in order to generate the export files with Keycloak, you will need to enter the Docker container, run the export command, and copy it outside the container:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Recover the Container ID for Keycloak
|
||||||
|
docker ps
|
||||||
|
|
||||||
|
# Run the export command inside the Keycloak container
|
||||||
|
# use the container ID of Keycloak
|
||||||
|
docker exec <keycloak container ID> /opt/keycloak/bin/kc.sh export --dir /tmp
|
||||||
|
|
||||||
|
# copy generated files from docker container to local machine
|
||||||
|
docker cp <keycloak container ID>:/tmp/my-realm-users-0.json .
|
||||||
|
```
|
||||||
|
|
||||||
|
## Set up ZITADEL
|
||||||
|
|
||||||
|
After creating a sample application that connects to Keycloak, you need to set up ZITADEL in order to migrate the application and users from Keycloak to ZITADEL. For this, ZITADEL offers a [Docker Compose](https://zitadel.com/docs/self-hosting/deploy/compose) installation guide. Follow the instructions under the [Docker compose](https://zitadel.com/docs/self-hosting/deploy/compose#docker-compose) section to run a ZITADEL instance locally.
|
||||||
|
|
||||||
|
Next, the application will be available at [http://localhost:8080/ui/console/](http://localhost:8080/ui/console/).
|
||||||
|
|
||||||
|
<img src="/docs/img/guides/migrate/keycloak-22.png" alt="Migrating users from Keycloak to ZITADEL"/>
|
||||||
|
|
||||||
|
Now you can access the console with the following default credentials:
|
||||||
|
|
||||||
|
* **Username**: `zitadel-admin@zitadel.localhost`
|
||||||
|
* **Password**: `Password1!`
|
||||||
|
|
||||||
|
|
||||||
|
## Import Keycloak users into ZITADEL
|
||||||
|
|
||||||
|
As explained in this [ZITADEL user migration guide](https://zitadel.com/docs/guides/migrate/users), you can import users individually or in bulk. Since we are looking at importing a single user from Keycloak, migrating that individual user to ZITADEL can be done with the [ImportHumanUser](https://zitadel.com/docs/apis/resources/mgmt/management-service-import-human-user) endpoint.
|
||||||
|
|
||||||
|
> With this endpoint, an email will only be sent to the user if the email is marked as not verified or if there's no password set.
|
||||||
|
|
||||||
|
### Create a service user to consume ZITADEL API
|
||||||
|
|
||||||
|
But first of all, in order to use this ZITADEL API, you need to create a [service user](https://zitadel.com/docs/guides/integrate/serviceusers#exercise-create-a-service-user).
|
||||||
|
|
||||||
|
Go to the **Users** menu and select the **Service Users** tab. And click the **+ New** button.
|
||||||
|
|
||||||
|
<img src="/docs/img/guides/migrate/keycloak-39.png" alt="Migrating users from Keycloak to ZITADEL"/>
|
||||||
|
|
||||||
|
Fill in the details of the service user and click **Create**.
|
||||||
|
|
||||||
|
<img src="/docs/img/guides/migrate/keycloak-40.png" alt="Migrating users from Keycloak to ZITADEL"/>
|
||||||
|
|
||||||
|
Your service user is now created and listed.
|
||||||
|
|
||||||
|
<img src="/docs/img/guides/migrate/keycloak-41.png" alt="Migrating users from Keycloak to ZITADEL"/>
|
||||||
|
|
||||||
|
### Provide 'Org Owner' permissions to the service user
|
||||||
|
|
||||||
|
This service user needs to have elevated permissions in order to import users. For this example, you should make the service user an organization owner as explained in [this guide](https://zitadel.com/docs/guides/integrate/access-zitadel-apis#add-org_owner-to-service-user).
|
||||||
|
|
||||||
|
Let's change the permissions as follows:
|
||||||
|
|
||||||
|
Click on the button shown in the image below:
|
||||||
|
|
||||||
|
<img src="/docs/img/guides/migrate/keycloak-42.png" alt="Migrating users from Keycloak to ZITADEL"/>
|
||||||
|
|
||||||
|
Next, select your service user that you created and select the **Org Owner** checkbox to assign the permissions of an organization owner to the service user.
|
||||||
|
|
||||||
|
<img src="/docs/img/guides/migrate/keycloak-43.png" alt="Migrating users from Keycloak to ZITADEL"/>
|
||||||
|
|
||||||
|
### Generate an access token for the service user
|
||||||
|
|
||||||
|
In order for the service user to access the API, they must be able to authenticate themselves. To authenticate the user, you can use either [JWT with Private Key](/docs/guides/integrate/serviceusers#authenticating-a-service-user) flow (recommended for production) or [Personal Access Tokens](/docs/guides/integrate/pat)(PAT). In this guide, we will choose the latter.
|
||||||
|
|
||||||
|
Go to **Users** -> **Service Users** again and click on the service user, then select **Personal Access Tokens** on the left and click the **+ New** button. Copy the generated personal access token to use it later. Click **Close** after copying the PAT.
|
||||||
|
|
||||||
|
<img src="/docs/img/guides/migrate/keycloak-44.png" alt="Migrating users from Keycloak to ZITADEL"/>
|
||||||
|
|
||||||
|
### Import user to ZITADEL via ZITADEL API
|
||||||
|
|
||||||
|
if your Keycloak Realm has a single user, your `my-realm-users-0.json` file, into which you exported your Keycloak user previously, will look like this:
|
||||||
|
|
||||||
|
```js
|
||||||
|
{
|
||||||
|
"realm" : "my-realm",
|
||||||
|
"users" : [ {
|
||||||
|
"id" : "826731b2-bf17-4bd9-b45c-6a26c76ddaae",
|
||||||
|
"createdTimestamp" : 1693887631918,
|
||||||
|
"username" : "test-user",
|
||||||
|
"enabled" : true,
|
||||||
|
"totp" : false,
|
||||||
|
"emailVerified" : true,
|
||||||
|
"firstName" : "John",
|
||||||
|
"lastName" : "Doe",
|
||||||
|
"email" : "test-user@mail.com",
|
||||||
|
"credentials" : [ {
|
||||||
|
"id" : "c3f3759e-9d8a-4628-aad9-09e66f28a4e2",
|
||||||
|
"type" : "password",
|
||||||
|
"userLabel" : "My password",
|
||||||
|
"createdDate" : 1693888572700,
|
||||||
|
"secretData" : "{\"value\":\"ng6oDRung/pBLayd5ro7IU3mL/p86pg3WvQNQc+N1Eg=\",\"salt\":\"RaXjs4RiUKgJGkX6kp277w==\",\"additionalParameters\":{}}",
|
||||||
|
"credentialData" : "{\"hashIterations\":27500,\"algorithm\":\"pbkdf2-sha256\",\"additionalParameters\":{}}"
|
||||||
|
} ],
|
||||||
|
"disableableCredentialTypes" : [ ],
|
||||||
|
"requiredActions" : [ ],
|
||||||
|
"realmRoles" : [ "default-roles-my-realm" ],
|
||||||
|
"notBefore" : 0,
|
||||||
|
"groups" : [ ]
|
||||||
|
} ]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Now, you need to transform the JSON to the ZITADEL data format by adhering to the ZITADEL API [specification](https://zitadel.com/docs/apis/resources/mgmt/management-service-import-human-user) to import a user. The minimal format would be as shown below:
|
||||||
|
|
||||||
|
```js
|
||||||
|
{
|
||||||
|
"userName": "test-user",
|
||||||
|
"profile": {
|
||||||
|
"firstName": "John",
|
||||||
|
"lastName": "Doe"
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"email": "test-user@mail.com",
|
||||||
|
"isEmailVerified": true
|
||||||
|
},
|
||||||
|
"hashedPassword": {
|
||||||
|
"value": "$pbkdf2-sha256$27500$RaXjs4RiUKgJGkX6kp277w==$ng6oDRung/pBLayd5ro7IU3mL/p86pg3WvQNQc+N1Eg="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
Next, you must install [`zitadel-tools`](https://github.com/zitadel/zitadel-tools/tree/main), which is a utility toolset designed to facilitate various interactions with the ZITADEL platform, mainly with tasks related to authentication, authorization, and data migration. We will be using the `migrate` command:
|
||||||
|
|
||||||
|
Purpose: Assists users in transforming exported data from other identity providers to be compatible with Zitadel's import schema.
|
||||||
|
Supported Providers: Currently, migrations from Auth0 and Keycloak are supported.
|
||||||
|
Usage: Users can get a list of available sub-commands and flags with the --help flag.
|
||||||
|
|
||||||
|
Install `zitadel-tools` using the command below. Ensure you have Go already installed on your machine.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go install github.com/zitadel/zitadel-tools@main
|
||||||
|
```
|
||||||
|
|
||||||
|
Now you can run the migration tool for Keycloak as explained in this [guide](https://github.com/zitadel/zitadel-tools/blob/main/cmd/migration/keycloak/readme.md). Let's go through the steps:
|
||||||
|
|
||||||
|
The Keycloak migration tool facilitates the transfer of data to ZITADEL by creating a JSON file tailored to serve as the body for an import request to the ZITADEL API. Note that it's essential that an organization already exists within ZITADEL/
|
||||||
|
|
||||||
|
To perform the migration, you'll need:
|
||||||
|
|
||||||
|
- The organization ID (--org)
|
||||||
|
- A realm.json file (in our case, `my-realm-users-0.json`) that houses your exported Keycloak realm with user details (--realm).
|
||||||
|
- Output path via --output (default: ./importBody.json)
|
||||||
|
- Timeout duration for the data import request using --timeout (default: 30 minutes)
|
||||||
|
- Pretty printing the output JSON with --multiline.
|
||||||
|
|
||||||
|
Execute with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
zitadel-tools migrate keycloak --org=<organisation id> --realm=./realm.json --output=./importBody.json --timeout=1h --multiline
|
||||||
|
```
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
zitadel-tools migrate keycloak --org=233868910057750531 --realm=./my-realm-users-0.json --output=./importBody.json --timeout=1h --multiline
|
||||||
|
```
|
||||||
|
|
||||||
|
Ensure `my-realm-users-0.json` is in the same directory for the tool to process it, or provide the path to the file.
|
||||||
|
|
||||||
|
`importBody.json` will now contain the transformed data as shown below:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
{
|
||||||
|
"dataOrgs": {
|
||||||
|
"orgs": [
|
||||||
|
{
|
||||||
|
"orgId": "233868910057750531",
|
||||||
|
"humanUsers": [
|
||||||
|
{
|
||||||
|
"userId": "826731b2-bf17-4bd9-b45c-6a26c76ddaae",
|
||||||
|
"user": {
|
||||||
|
"userName": "test-user",
|
||||||
|
"profile": {
|
||||||
|
"firstName": "John",
|
||||||
|
"lastName": "Doe"
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"email": "test-user@mail.com",
|
||||||
|
"isEmailVerified": true
|
||||||
|
},
|
||||||
|
"hashedPassword": {
|
||||||
|
"value": "$pbkdf2-sha256$27500$RaXjs4RiUKgJGkX6kp277w==$ng6oDRung/pBLayd5ro7IU3mL/p86pg3WvQNQc+N1Eg="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"timeout": "1h0m0s"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Now copy the following portion to a separate file and name the file `zitadel-users-file.json`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
"userId": "826731b2-bf17-4bd9-b45c-6a26c76ddaae",
|
||||||
|
"user": {
|
||||||
|
"userName": "test-user",
|
||||||
|
"profile": {
|
||||||
|
"firstName": "John",
|
||||||
|
"lastName": "Doe"
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"email": "test-user@mail.com",
|
||||||
|
"isEmailVerified": true
|
||||||
|
},
|
||||||
|
"hashedPassword": {
|
||||||
|
"value": "$pbkdf2-sha256$27500$RaXjs4RiUKgJGkX6kp277w==$ng6oDRung/pBLayd5ro7IU3mL/p86pg3WvQNQc+N1Eg="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Now that we have the user details in the required JSON format, let’s call the ZITADEL API to add the user.
|
||||||
|
|
||||||
|
Run the following cURL command to invoke the API and don't forget to replace `<service user access token>` with the service user's personal access token:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl --request POST \
|
||||||
|
--url http://localhost:8080/management/v1/users/human/_import \
|
||||||
|
--header 'Content-Type: application/json' \
|
||||||
|
--header 'Authorization: Bearer <service user access token>' \
|
||||||
|
--data @zitadel-users-file.json
|
||||||
|
```
|
||||||
|
|
||||||
|
A successful response would be as shown below:
|
||||||
|
|
||||||
|
<img src="/docs/img/guides/migrate/keycloak-46.png" alt="Migrating users from Keycloak to ZITADEL"/>
|
||||||
|
|
||||||
|
> Note that the previous request imports a single user. If you're using ZITADEL Cloud and have a large number of users, you may hit its rate limit or may need to pay the excess number of API requests. If you experience this, reach out to the [ZITADEL support team](https://zitadel.com/contact), as they can provide an alternative migration tools to move a large number of users.
|
||||||
|
|
||||||
|
|
||||||
|
Now you have imported the Keycloak user into ZITADEL. To view your user go to [http://localhost:8080/ui/console/users](http://localhost:8080/ui/console/users) (or go to the **Users** tab to see the users).
|
||||||
|
|
||||||
|
<img src="/docs/img/guides/migrate/keycloak-47.png" alt="Migrating users from Keycloak to ZITADEL"/>
|
||||||
|
|
||||||
|
|
||||||
|
You can now view the Keycloak user's details in ZITADEL. You can see that the password is available too.
|
@ -20,7 +20,7 @@ This means that the users and also their authorizations will be managed within Z
|
|||||||
An organization is the ZITADEL resource which contains users, projects, applications, policies and so on.
|
An organization is the ZITADEL resource which contains users, projects, applications, policies and so on.
|
||||||
In an organization projects and users are managed by the organization.
|
In an organization projects and users are managed by the organization.
|
||||||
You need at least one organization for your own company in our case "The Timing Company".
|
You need at least one organization for your own company in our case "The Timing Company".
|
||||||
As next step grate an organization for each of your costumers.
|
Your next step is to create an organization for each of your customers.
|
||||||
|
|
||||||
## Project
|
## Project
|
||||||
|
|
||||||
@ -39,7 +39,7 @@ You can configure `check roles on authentication` on the project, if you want to
|
|||||||
|
|
||||||
To give a customer permissions to a project, a project grant to the customers organization is needed (search the granted organization by its domain).
|
To give a customer permissions to a project, a project grant to the customers organization is needed (search the granted organization by its domain).
|
||||||
It is also possible to delegate only specific roles of the project to a certain customer.
|
It is also possible to delegate only specific roles of the project to a certain customer.
|
||||||
As soon as a project grant exists, the customer will see the project in the granted projects section of his organization and will be able to authorize his own users to the given project.
|
As soon as a project grant exists, the customer will see the project in the granted projects section of their organization and will be able to authorize their own users to the given project.
|
||||||
|
|
||||||
## Authorizations
|
## Authorizations
|
||||||
|
|
||||||
|
@ -6,14 +6,14 @@ title: Technical Advisory 10002
|
|||||||
|
|
||||||
Version: TBD
|
Version: TBD
|
||||||
|
|
||||||
Date: Calendar week 40/41
|
Date: Calendar week 44
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|
||||||
Since Angular Material v15 many of the UI components have been refactored
|
Since Angular Material v15 many of the UI components have been refactored
|
||||||
to be based on the official Material Design Components for Web (MDC).
|
to be based on the official Material Design Components for Web (MDC).
|
||||||
These refactored components do not support dynamic styling, so in order to keep the library up-to-date,
|
These refactored components do not support dynamic styling, so in order to keep the library up-to-date,
|
||||||
the console UI will loose its dynamic theming capability.
|
the console UI will lose its dynamic theming capability.
|
||||||
|
|
||||||
## Statement
|
## Statement
|
||||||
|
|
||||||
@ -23,7 +23,7 @@ As soon as the release version is published, we will include the version here.
|
|||||||
## Mitigation
|
## Mitigation
|
||||||
|
|
||||||
If you need users to have your branding settings
|
If you need users to have your branding settings
|
||||||
(background-, button-, link and text coloring), you should implemement your
|
(background-, button-, link and text coloring), you should implement your
|
||||||
own user facing UI yourself and not use ZITADELs console UI. Assets like your logo and icons will still be used.
|
own user facing UI yourself and not use ZITADELs console UI. Assets like your logo and icons will still be used.
|
||||||
|
|
||||||
## Impact
|
## Impact
|
||||||
|
46
docs/docs/support/advisory/a10003.md
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
---
|
||||||
|
title: Technical Advisory 10003
|
||||||
|
---
|
||||||
|
|
||||||
|
## Date and Version
|
||||||
|
|
||||||
|
Version: 2.38.0
|
||||||
|
|
||||||
|
Date: Calendar week 41
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
When users are redirected to the ZITADEL Login-UI without any organizational context, they're currently presented a login screen,
|
||||||
|
based on the instance settings, e.g. available IDPs and possible login mechanisms. If the user will then register himself,
|
||||||
|
by the registration form or through an IDP, the user will always be created on the default organization.
|
||||||
|
|
||||||
|
This behaviour led to confusion, e.g. when activating IDPs on default org would not show up in the Login-UI, because they would still be loaded from the instance settings.
|
||||||
|
|
||||||
|
To improve this, we're introducing the following change:
|
||||||
|
If users are redirected to the Login-UI without any organizational context, they will be presented a login screen based on the settings of the default organization (incl. IDPs).
|
||||||
|
|
||||||
|
:::note
|
||||||
|
If the registration (and also authentication) needs to occur on a specified organization, apps can already
|
||||||
|
specify this by providing [an organization scope](https://zitadel.com/docs/apis/openidoauth/scopes#reserved-scopes).
|
||||||
|
:::
|
||||||
|
|
||||||
|
## Statement
|
||||||
|
|
||||||
|
This change was tracked in the following PR:
|
||||||
|
[feat(login): use default org for login without provided org context](https://github.com/zitadel/zitadel/pull/6625), which was released in Version [2.38.0](https://github.com/zitadel/zitadel/releases/tag/v2.38.0)
|
||||||
|
|
||||||
|
## Mitigation
|
||||||
|
|
||||||
|
There's no action needed on your side currently as existing instances are not affected directly and IAM_OWNER can activate the flag at their own pace.
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
Once this update has been released and deployed, newly created instances will always use the default organization and its settings as default context for the login.
|
||||||
|
|
||||||
|
Already existing instances will still use the instance settings by default and can switch to the new default by ["Activating the 'LoginDefaultOrg' feature"](https://zitadel.com/docs/apis/resources/admin/admin-service-activate-feature-login-default-org) through the Admin API.
|
||||||
|
**This change is irreversible!**
|
||||||
|
|
||||||
|
:::note
|
||||||
|
Regardless of the change:
|
||||||
|
If a known username is entered on the first screen, the login switches its context to the organization of that user and settings will be updated to that organization as well.
|
||||||
|
:::
|
37
docs/docs/support/advisory/a10004.md
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
---
|
||||||
|
title: Technical Advisory 10004
|
||||||
|
---
|
||||||
|
|
||||||
|
## Date and Version
|
||||||
|
|
||||||
|
Version: 2.39.0
|
||||||
|
|
||||||
|
Date: 2023-10-14
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
Due to storage optimisations ZITADEL changes the behaviour of sequences.
|
||||||
|
This change improves command (create, update, delete) performance of ZITADEL.
|
||||||
|
|
||||||
|
Sequences are no longer unique inside an instance.
|
||||||
|
From now on sequences are upcounting per aggregate id.
|
||||||
|
For example sequences of newly created users begin at 1.
|
||||||
|
Existing sequences remain untouched.
|
||||||
|
|
||||||
|
## Statement
|
||||||
|
|
||||||
|
This change is tracked in the following PR: [new eventstore framework](https://github.com/zitadel/zitadel/issues/5358).
|
||||||
|
As soon as the release version is published, we will include the version here.
|
||||||
|
|
||||||
|
## Mitigation
|
||||||
|
|
||||||
|
If you use the ListEvents API to scrape events use the creation date instead of the sequence.
|
||||||
|
If you use sequences on a list of objects it's no longer garanteed to have unique sequences across the list.
|
||||||
|
Therefore it's recommended to use the change data of the objects instead.
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
Once this update has been released and deployed, sequences are no longer unique inside an instance.
|
||||||
|
ZITADEL will increase parallel write capabilities, because there is no global sequence to track anymore.
|
||||||
|
Editor service does not respond the different services of ZITADEL anymore, it returns zitadel.
|
||||||
|
As we are switching to resource based API's there is no need for this field anymore.
|
33
docs/docs/support/advisory/a10005.md
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
---
|
||||||
|
title: Technical Advisory 10005
|
||||||
|
---
|
||||||
|
|
||||||
|
## Date and Version
|
||||||
|
|
||||||
|
Version: 2.39.0
|
||||||
|
|
||||||
|
Date: Calendar week 41/42 2023
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
Migrating to version >= 2.39 from < 2.39 will cause down time during setup starts and the new version is started.
|
||||||
|
This is caused by storage optimisations which replace the `eventstore.events` database table with the new `eventstore.events2` table.
|
||||||
|
All existing events are migrated during the execution of the `zitadel setup` command.
|
||||||
|
New events will be inserted into the new `eventstore.events2` table. The old table `evetstore.events` is renamed to `eventstore.events_old` and will be dropped in a future release of ZITADEL.
|
||||||
|
|
||||||
|
## Statement
|
||||||
|
|
||||||
|
This change is tracked in the following PR: [new eventstore framework](https://github.com/zitadel/zitadel/issues/5358).
|
||||||
|
As soon as the release version is published, we will include the version here.
|
||||||
|
|
||||||
|
## Mitigation
|
||||||
|
|
||||||
|
If you use this table for TRIGGERS or Change data capture please check the new table definition and change your code to use `eventstore.events2`.
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
Once the setup step renamed the table. Old versions of ZITADEL are not able to read/write events.
|
||||||
|
|
||||||
|
:::note
|
||||||
|
If the upgrade fails make sure to rename `eventstore.events_old` to `eventstore.events`. This change enables older ZITADEL versions to work properly.
|
||||||
|
:::
|
@ -68,7 +68,55 @@ We understand that these advisories may include breaking changes, and we aim to
|
|||||||
ZITADEL hosted Login-UI is not affected by this change.
|
ZITADEL hosted Login-UI is not affected by this change.
|
||||||
</td>
|
</td>
|
||||||
<td>TBD</td>
|
<td>TBD</td>
|
||||||
<td>Calendar week 40/41</td>
|
<td>Calendar week 44</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a href="./advisory/a10003">A-10003</a>
|
||||||
|
</td>
|
||||||
|
<td>Login-UI - Default Context</td>
|
||||||
|
<td>Breaking Behaviour Change</td>
|
||||||
|
<td>
|
||||||
|
When users are redirected to the ZITADEL Login-UI without any organizational context,
|
||||||
|
they're currently presented a login screen, based on the instance settings,
|
||||||
|
e.g. available IDPs and possible login mechanisms. If the user will then register himself,
|
||||||
|
by the registration form or through an IDP, the user will always be created on the default organization.
|
||||||
|
With the introduced change, the settings will no longer be loaded from the instance, but rather the default organization directly.
|
||||||
|
</td>
|
||||||
|
<td>2.38.0</td>
|
||||||
|
<td>Calendar week 41</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a href="./advisory/a10004">A-10004</a>
|
||||||
|
</td>
|
||||||
|
<td>Sequence uniquenes</td>
|
||||||
|
<td>Breaking Behaviour Change</td>
|
||||||
|
<td>
|
||||||
|
Due to storage optimisations ZITADEL changes the behaviour of sequences.
|
||||||
|
This change improves command (create, update, delete) performance of ZITADEL.
|
||||||
|
Sequences are no longer unique inside an instance.
|
||||||
|
From now on sequences are upcounting per aggregate id.
|
||||||
|
For example sequences of newly created users begin at 1.
|
||||||
|
Existing sequences remain untouched.
|
||||||
|
</td>
|
||||||
|
<td>2.39.0</td>
|
||||||
|
<td>2023-10-14</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a href="./advisory/a10005">A-10005</a>
|
||||||
|
</td>
|
||||||
|
<td>Expected downtime during upgrade</td>
|
||||||
|
<td>Expected downtime during upgrade</td>
|
||||||
|
<td>
|
||||||
|
Migrating to versions >= 2.39 from < 2.39 will cause down time during setup starts and the new version is started.
|
||||||
|
This is caused by storage optimisations which replace the `eventstore.events` database table with the new `eventstore.events2` table.
|
||||||
|
All existing events are migrated during the execution of the `zitadel setup` command.
|
||||||
|
New events will be inserted into the new `eventstore.events2` table. The old table `evetstore.events` is renamed to `eventstore.events_old` and will be dropped in a future release of ZITADEL.
|
||||||
|
</td>
|
||||||
|
<td>2.39.0</td>
|
||||||
|
<td>Calendar week 41/42 2023</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
@ -125,6 +125,7 @@ module.exports = {
|
|||||||
items: [
|
items: [
|
||||||
"guides/migrate/sources/zitadel",
|
"guides/migrate/sources/zitadel",
|
||||||
"guides/migrate/sources/auth0",
|
"guides/migrate/sources/auth0",
|
||||||
|
"guides/migrate/sources/keycloak",
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@ -243,6 +244,7 @@ module.exports = {
|
|||||||
"guides/integrate/identity-providers/openldap",
|
"guides/integrate/identity-providers/openldap",
|
||||||
"guides/integrate/identity-providers/migrate",
|
"guides/integrate/identity-providers/migrate",
|
||||||
"guides/integrate/identity-providers/okta",
|
"guides/integrate/identity-providers/okta",
|
||||||
|
"guides/integrate/identity-providers/keycloak",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
BIN
docs/static/img/guides/console/smtp.png
vendored
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 50 KiB |
BIN
docs/static/img/guides/console/twilio.png
vendored
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 39 KiB |
BIN
docs/static/img/guides/keycloak_add_client.png
vendored
Normal file
After Width: | Height: | Size: 339 KiB |
BIN
docs/static/img/guides/keycloak_client_secret.png
vendored
Normal file
After Width: | Height: | Size: 286 KiB |
BIN
docs/static/img/guides/keycloak_login.png
vendored
Normal file
After Width: | Height: | Size: 148 KiB |
BIN
docs/static/img/guides/migrate/keycloak-01.png
vendored
Normal file
After Width: | Height: | Size: 144 KiB |
BIN
docs/static/img/guides/migrate/keycloak-02.png
vendored
Normal file
After Width: | Height: | Size: 197 KiB |
BIN
docs/static/img/guides/migrate/keycloak-03.png
vendored
Normal file
After Width: | Height: | Size: 221 KiB |
BIN
docs/static/img/guides/migrate/keycloak-04.png
vendored
Normal file
After Width: | Height: | Size: 126 KiB |
BIN
docs/static/img/guides/migrate/keycloak-05.png
vendored
Normal file
After Width: | Height: | Size: 151 KiB |
BIN
docs/static/img/guides/migrate/keycloak-06.png
vendored
Normal file
After Width: | Height: | Size: 242 KiB |
BIN
docs/static/img/guides/migrate/keycloak-07.png
vendored
Normal file
After Width: | Height: | Size: 193 KiB |
BIN
docs/static/img/guides/migrate/keycloak-08.png
vendored
Normal file
After Width: | Height: | Size: 195 KiB |
BIN
docs/static/img/guides/migrate/keycloak-09.png
vendored
Normal file
After Width: | Height: | Size: 213 KiB |
BIN
docs/static/img/guides/migrate/keycloak-10.png
vendored
Normal file
After Width: | Height: | Size: 249 KiB |
BIN
docs/static/img/guides/migrate/keycloak-11.png
vendored
Normal file
After Width: | Height: | Size: 143 KiB |
BIN
docs/static/img/guides/migrate/keycloak-12.png
vendored
Normal file
After Width: | Height: | Size: 156 KiB |
BIN
docs/static/img/guides/migrate/keycloak-13.png
vendored
Normal file
After Width: | Height: | Size: 205 KiB |
BIN
docs/static/img/guides/migrate/keycloak-14.png
vendored
Normal file
After Width: | Height: | Size: 171 KiB |
BIN
docs/static/img/guides/migrate/keycloak-15.png
vendored
Normal file
After Width: | Height: | Size: 170 KiB |
BIN
docs/static/img/guides/migrate/keycloak-16.png
vendored
Normal file
After Width: | Height: | Size: 177 KiB |
BIN
docs/static/img/guides/migrate/keycloak-17.png
vendored
Normal file
After Width: | Height: | Size: 88 KiB |
BIN
docs/static/img/guides/migrate/keycloak-18.png
vendored
Normal file
After Width: | Height: | Size: 220 KiB |
BIN
docs/static/img/guides/migrate/keycloak-19.png
vendored
Normal file
After Width: | Height: | Size: 89 KiB |
BIN
docs/static/img/guides/migrate/keycloak-20.png
vendored
Normal file
After Width: | Height: | Size: 147 KiB |
BIN
docs/static/img/guides/migrate/keycloak-21.png
vendored
Normal file
After Width: | Height: | Size: 60 KiB |
BIN
docs/static/img/guides/migrate/keycloak-22.png
vendored
Normal file
After Width: | Height: | Size: 66 KiB |
BIN
docs/static/img/guides/migrate/keycloak-23.png
vendored
Normal file
After Width: | Height: | Size: 71 KiB |
BIN
docs/static/img/guides/migrate/keycloak-24.png
vendored
Normal file
After Width: | Height: | Size: 70 KiB |
BIN
docs/static/img/guides/migrate/keycloak-25.png
vendored
Normal file
After Width: | Height: | Size: 108 KiB |
BIN
docs/static/img/guides/migrate/keycloak-26.png
vendored
Normal file
After Width: | Height: | Size: 104 KiB |
BIN
docs/static/img/guides/migrate/keycloak-27.png
vendored
Normal file
After Width: | Height: | Size: 68 KiB |
BIN
docs/static/img/guides/migrate/keycloak-28.png
vendored
Normal file
After Width: | Height: | Size: 256 KiB |
BIN
docs/static/img/guides/migrate/keycloak-29.png
vendored
Normal file
After Width: | Height: | Size: 239 KiB |
BIN
docs/static/img/guides/migrate/keycloak-30.png
vendored
Normal file
After Width: | Height: | Size: 313 KiB |
BIN
docs/static/img/guides/migrate/keycloak-31.png
vendored
Normal file
After Width: | Height: | Size: 341 KiB |
BIN
docs/static/img/guides/migrate/keycloak-32.png
vendored
Normal file
After Width: | Height: | Size: 222 KiB |
BIN
docs/static/img/guides/migrate/keycloak-33.png
vendored
Normal file
After Width: | Height: | Size: 204 KiB |
BIN
docs/static/img/guides/migrate/keycloak-34.png
vendored
Normal file
After Width: | Height: | Size: 223 KiB |
BIN
docs/static/img/guides/migrate/keycloak-35.png
vendored
Normal file
After Width: | Height: | Size: 270 KiB |