feat: specify login UI version on instance and apps (#9071)

# Which Problems Are Solved

To be able to migrate or test the new login UI, admins might want to
(temporarily) switch individual apps.
At a later point admin might want to make sure all applications use the
new login UI.

# How the Problems Are Solved

- Added a feature flag `` on instance level to require all apps to use
the new login and provide an optional base url.
- if the flag is enabled, all (OIDC) applications will automatically use
the v2 login.
  - if disabled, applications can decide based on their configuration
- Added an option on OIDC apps to use the new login UI and an optional
base url.
- Removed the requirement to use `x-zitadel-login-client` to be
redirected to the login V2 and retrieve created authrequest and link
them to SSO sessions.
- Added a new "IAM_LOGIN_CLIENT" role to allow management of users,
sessions, grants and more without `x-zitadel-login-client`.

# Additional Changes

None

# Additional Context

closes https://github.com/zitadel/zitadel/issues/8702
This commit is contained in:
Livio Spring
2024-12-19 10:37:46 +01:00
committed by GitHub
parent b5e92a6144
commit 50d2b26a28
89 changed files with 1670 additions and 321 deletions
@@ -147,6 +147,22 @@
>
{{ 'APP.OIDC.REFRESHTOKEN' | translate }}
</mat-checkbox>
<mat-checkbox
*ngIf="loginV2"
color="primary"
class="rt"
[formControl]="loginV2"
name="loginV2"
matTooltip="{{ 'APP.LOGINV2DESC' | translate }}"
>
{{ 'APP.LOGINV2.USEV2' | translate }}
</mat-checkbox>
<cnsl-form-field class="app-formfield">
<cnsl-label>{{ 'APP.LOGINV2.BASEURL' | translate }}</cnsl-label>
<input cnslInput formControlName="loginV2BaseURL" />
</cnsl-form-field>
</div>
</div>
@@ -1,6 +1,6 @@
import { COMMA, ENTER, SPACE } from '@angular/cdk/keycodes';
import { Location } from '@angular/common';
import { Component, OnDestroy, OnInit, ViewEncapsulation, signal } from '@angular/core';
import { Component, OnDestroy, OnInit, signal } from '@angular/core';
import { AbstractControl, FormControl, UntypedFormBuilder, UntypedFormControl, UntypedFormGroup } from '@angular/forms';
import { MatCheckboxChange } from '@angular/material/checkbox';
import { MatDialog } from '@angular/material/dialog';
@@ -21,6 +21,9 @@ import {
APIConfig,
App,
AppState,
LoginV1,
LoginV2,
LoginVersion,
OIDCAppType,
OIDCAuthMethodType,
OIDCConfig,
@@ -50,8 +53,8 @@ import {
getAuthMethodFromPartialConfig,
getPartialConfigFromAuthMethod,
IMPLICIT_METHOD,
PKCE_METHOD,
PK_JWT_METHOD,
PKCE_METHOD,
POST_METHOD,
} from '../authmethods';
import { AuthMethodDialogComponent } from './auth-method-dialog/auth-method-dialog.component';
@@ -182,6 +185,7 @@ export class AppDetailComponent implements OnInit, OnDestroy {
public currentSetting: string | undefined = this.settingsList[0].id;
public isNew = signal<boolean>(false);
constructor(
private envSvc: EnvironmentService,
public translate: TranslateService,
@@ -203,6 +207,8 @@ export class AppDetailComponent implements OnInit, OnDestroy {
grantTypesList: [{ value: [], disabled: true }],
appType: [{ value: '', disabled: true }],
authMethodType: [{ value: '', disabled: true }],
loginV2: [{ value: false, disabled: true }],
loginV2BaseURL: [{ value: '', disabled: true }],
});
this.oidcTokenForm = this.fb.group({
@@ -430,6 +436,12 @@ export class AppDetailComponent implements OnInit, OnDestroy {
const inSecs = this.app.oidcConfig?.clockSkew.seconds + this.app.oidcConfig?.clockSkew.nanos / 100000;
this.oidcTokenForm.controls['clockSkewSeconds'].setValue(inSecs);
}
if (this.app.oidcConfig?.loginVersion?.loginV1) {
this.oidcForm.controls['loginV2'].setValue(false);
} else if (this.app.oidcConfig?.loginVersion?.loginV2) {
this.oidcForm.controls['loginV2'].setValue(true);
this.oidcForm.controls['loginV2BaseURL'].setValue(this.app.oidcConfig.loginVersion.loginV2.baseUri);
}
if (this.app.oidcConfig) {
this.oidcForm.patchValue(this.app.oidcConfig);
this.oidcTokenForm.patchValue(this.app.oidcConfig);
@@ -655,6 +667,15 @@ export class AppDetailComponent implements OnInit, OnDestroy {
req.setAuthMethodType(this.app.oidcConfig.authMethodType);
req.setGrantTypesList(this.app.oidcConfig.grantTypesList);
req.setAppType(this.app.oidcConfig.appType);
const login = new LoginVersion();
if (this.loginV2?.value) {
const loginV2 = new LoginV2();
loginV2.setBaseUri(this.loginV2BaseURL?.value);
login.setLoginV2(loginV2);
} else {
login.setLoginV1(new LoginV1());
}
req.setLoginVersion(login);
// token
req.setAccessTokenType(this.app.oidcConfig.accessTokenType);
@@ -839,6 +860,14 @@ export class AppDetailComponent implements OnInit, OnDestroy {
return this.oidcForm.get('authMethodType');
}
public get loginV2(): FormControl<boolean> | null {
return this.oidcForm.get('loginV2') as FormControl<boolean>;
}
public get loginV2BaseURL(): AbstractControl | null {
return this.oidcForm.get('loginV2BaseURL');
}
public get apiAuthMethodType(): AbstractControl | null {
return this.apiForm.get('authMethodType') as UntypedFormControl;
}