From 5cde52148f38c58e1963f665e3c8bcb8d801425d Mon Sep 17 00:00:00 2001 From: Ramon Date: Wed, 10 Sep 2025 09:05:55 +0200 Subject: [PATCH] fix(console): Add login v2 url to identity providers (#10583) # Which Problems Are Solved When using login V2 the Callback URL for an Identity Provider is different. When following the guideance in the console and using Login V2 users will use the wrong callback url. grafik # How the Problems Are Solved I have added the correct Login V2 url to the identity providers and updated our docs. grafik # Additional Changes Small refactorings and porting some components over to ChangeDetection OnPush # Additional Context Replace this example with links to related issues, discussions, discord threads, or other sources with more context. Use the Closing #issue syntax for issues that are resolved with this PR. - Closes #10461 --------- Co-authored-by: Max Peintner --- .../copy-row/copy-row.component.html | 6 +- .../components/copy-row/copy-row.component.ts | 11 ++-- .../src/app/modules/card/card.component.html | 4 +- .../src/app/modules/card/card.component.scss | 4 -- .../src/app/modules/card/card.component.ts | 21 ++----- .../info-section/info-section.component.html | 14 ++--- .../info-section/info-section.component.scss | 9 --- .../info-section/info-section.component.ts | 6 +- .../provider-apple.component.ts | 2 +- .../provider-next/provider-next.component.ts | 19 +++---- .../provider-next/provider-next.service.ts | 56 ++++++++++++++----- .../provider-saml-sp.component.html | 2 +- .../pages/org-create/org-create.component.ts | 10 ---- .../identity-providers/linkedin_oauth.mdx | 4 +- 14 files changed, 81 insertions(+), 87 deletions(-) diff --git a/console/src/app/components/copy-row/copy-row.component.html b/console/src/app/components/copy-row/copy-row.component.html index e8a2e34f136..0d8b11eb998 100644 --- a/console/src/app/components/copy-row/copy-row.component.html +++ b/console/src/app/components/copy-row/copy-row.component.html @@ -2,11 +2,11 @@
{{ label }}
diff --git a/console/src/app/components/copy-row/copy-row.component.ts b/console/src/app/components/copy-row/copy-row.component.ts index 373958908ec..a4d29a07902 100644 --- a/console/src/app/components/copy-row/copy-row.component.ts +++ b/console/src/app/components/copy-row/copy-row.component.ts @@ -1,9 +1,9 @@ import { CommonModule } from '@angular/common'; -import { Component, Input } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Input, signal } from '@angular/core'; import { TranslateModule } from '@ngx-translate/core'; import { MatButtonModule } from '@angular/material/button'; import { MatTooltipModule } from '@angular/material/tooltip'; -import { CopyToClipboardModule } from '../../directives/copy-to-clipboard/copy-to-clipboard.module'; +import { CopyToClipboardModule } from 'src/app/directives/copy-to-clipboard/copy-to-clipboard.module'; @Component({ standalone: true, @@ -11,11 +11,12 @@ import { CopyToClipboardModule } from '../../directives/copy-to-clipboard/copy-t templateUrl: './copy-row.component.html', styleUrls: ['./copy-row.component.scss'], imports: [CommonModule, TranslateModule, MatButtonModule, MatTooltipModule, CopyToClipboardModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class CopyRowComponent { - @Input({ required: true }) public label = ''; - @Input({ required: true }) public value = ''; + @Input({ required: true }) public label!: string; + @Input({ required: true }) public value!: string; @Input() public labelMinWidth = ''; - public copied = ''; + protected readonly copied = signal(''); } diff --git a/console/src/app/modules/card/card.component.html b/console/src/app/modules/card/card.component.html index e7052c3dfa0..94d06abede0 100644 --- a/console/src/app/modules/card/card.component.html +++ b/console/src/app/modules/card/card.component.html @@ -1,4 +1,4 @@ -
+

{{ title }}

@@ -13,7 +13,7 @@ {{ description }}

-
+
diff --git a/console/src/app/modules/card/card.component.scss b/console/src/app/modules/card/card.component.scss index 97beb36b840..d847a28c8a0 100644 --- a/console/src/app/modules/card/card.component.scss +++ b/console/src/app/modules/card/card.component.scss @@ -46,10 +46,6 @@ } } - &.stretch { - height: 100%; - } - .card-content { display: flex; flex-direction: column; diff --git a/console/src/app/modules/card/card.component.ts b/console/src/app/modules/card/card.component.ts index 2667a37da0b..d0c42b88893 100644 --- a/console/src/app/modules/card/card.component.ts +++ b/console/src/app/modules/card/card.component.ts @@ -1,26 +1,15 @@ -import { animate, style, transition, trigger } from '@angular/animations'; -import { Component, Input } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; @Component({ selector: 'cnsl-card', templateUrl: './card.component.html', styleUrls: ['./card.component.scss'], - animations: [ - trigger('openClose', [ - transition(':enter', [ - style({ height: '0', opacity: 0 }), - animate('150ms ease-in-out', style({ height: '*', opacity: 1 })), - ]), - transition(':leave', [animate('150ms ease-in-out', style({ height: '0', opacity: 0 }))]), - ]), - ], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class CardComponent { - @Input() public expanded: boolean = true; - @Input() public warn: boolean = false; @Input() public title: string = ''; @Input() public description: string = ''; - @Input() public animate: boolean = false; - @Input() public nomargin?: boolean = false; - @Input() public stretch: boolean = false; + @Input() public expanded: boolean = true; + @Input() public warn: boolean = false; + @Input() public nomargin: boolean = false; } diff --git a/console/src/app/modules/info-section/info-section.component.html b/console/src/app/modules/info-section/info-section.component.html index b27fdea148a..786554fbd65 100644 --- a/console/src/app/modules/info-section/info-section.component.html +++ b/console/src/app/modules/info-section/info-section.component.html @@ -1,17 +1,15 @@
- - - - + + +
diff --git a/console/src/app/modules/info-section/info-section.component.scss b/console/src/app/modules/info-section/info-section.component.scss index 8399226662c..daec4377163 100644 --- a/console/src/app/modules/info-section/info-section.component.scss +++ b/console/src/app/modules/info-section/info-section.component.scss @@ -40,15 +40,6 @@ } } - &.success { - background-color: map-get($background, successinfosection); - color: map-get($foreground, successinfosection); - - .icon { - color: map-get($foreground, successinfosection); - } - } - &.warn { background-color: map-get($background, warninfosection); color: map-get($foreground, warninfosection); diff --git a/console/src/app/modules/info-section/info-section.component.ts b/console/src/app/modules/info-section/info-section.component.ts index 0ae60d290a8..3618b43b9ba 100644 --- a/console/src/app/modules/info-section/info-section.component.ts +++ b/console/src/app/modules/info-section/info-section.component.ts @@ -1,8 +1,7 @@ -import { Component, Input } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; export enum InfoSectionType { INFO = 'INFO', - SUCCESS = 'SUCCESS', WARN = 'WARN', ALERT = 'ALERT', } @@ -11,8 +10,11 @@ export enum InfoSectionType { selector: 'cnsl-info-section', templateUrl: './info-section.component.html', styleUrls: ['./info-section.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class InfoSectionComponent { @Input() type: InfoSectionType = InfoSectionType.INFO; @Input() fitWidth: boolean = false; + + protected readonly infoSectionType = InfoSectionType; } diff --git a/console/src/app/modules/providers/provider-apple/provider-apple.component.ts b/console/src/app/modules/providers/provider-apple/provider-apple.component.ts index 461ab90e34b..8e62825b1b4 100644 --- a/console/src/app/modules/providers/provider-apple/provider-apple.component.ts +++ b/console/src/app/modules/providers/provider-apple/provider-apple.component.ts @@ -4,7 +4,7 @@ import { Component, Injector, Type } from '@angular/core'; import { AbstractControl, FormControl, FormGroup } from '@angular/forms'; import { MatChipInputEvent } from '@angular/material/chips'; import { ActivatedRoute } from '@angular/router'; -import { BehaviorSubject, Observable, take } from 'rxjs'; +import { BehaviorSubject, take } from 'rxjs'; import { AddAppleProviderRequest as AdminAddAppleProviderRequest, GetProviderByIDRequest as AdminGetProviderByIDRequest, diff --git a/console/src/app/modules/providers/provider-next/provider-next.component.ts b/console/src/app/modules/providers/provider-next/provider-next.component.ts index 28681198f2d..87f2e90bd57 100644 --- a/console/src/app/modules/providers/provider-next/provider-next.component.ts +++ b/console/src/app/modules/providers/provider-next/provider-next.component.ts @@ -1,4 +1,4 @@ -import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; export interface CopyUrl { label: string; @@ -10,17 +10,16 @@ export interface CopyUrl { selector: 'cnsl-provider-next', templateUrl: './provider-next.component.html', styleUrls: ['./provider-next.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class ProviderNextComponent { - @Input() copyUrls?: CopyUrl[] | null; - @Input() autofillLink?: string | null; - @Input() activateLink?: string | null; - @Input() configureProvider?: boolean | null; - @Input() configureTitle?: string; - @Input() configureDescription?: string; + @Input() copyUrls: CopyUrl[] | null = null; + @Input() autofillLink: string | null = null; + @Input({ required: true }) activateLink!: string | null; + @Input({ required: true }) configureProvider!: boolean; + @Input({ required: true }) configureTitle!: string; + @Input({ required: true }) configureDescription!: string; @Input() configureLink?: string; - @Input() expanded?: boolean; + @Input({ required: true }) expanded!: boolean; @Output() activate = new EventEmitter(); - - constructor() {} } diff --git a/console/src/app/modules/providers/provider-next/provider-next.service.ts b/console/src/app/modules/providers/provider-next/provider-next.service.ts index 245571e6992..f2f03c4d048 100644 --- a/console/src/app/modules/providers/provider-next/provider-next.service.ts +++ b/console/src/app/modules/providers/provider-next/provider-next.service.ts @@ -1,26 +1,45 @@ import { Injectable, Injector, Type } from '@angular/core'; -import { BehaviorSubject, combineLatestWith, from, Observable, of, shareReplay, switchMap, take } from 'rxjs'; -import { filter, map, tap } from 'rxjs/operators'; -import { EnvironmentService } from '../../../services/environment.service'; +import { BehaviorSubject, combineLatestWith, defer, from, Observable, of, shareReplay, switchMap, take } from 'rxjs'; +import { catchError, filter, map, timeout } from 'rxjs/operators'; +import { EnvironmentService } from 'src/app/services/environment.service'; import { CopyUrl } from './provider-next.component'; -import { ManagementService } from '../../../services/mgmt.service'; -import { AdminService } from '../../../services/admin.service'; -import { IDPOwnerType } from '../../../proto/generated/zitadel/idp_pb'; -import { ToastService } from '../../../services/toast.service'; +import { ManagementService } from 'src/app/services/mgmt.service'; +import { AdminService } from 'src/app/services/admin.service'; +import { IDPOwnerType } from 'src/app/proto/generated/zitadel/idp_pb'; +import { ToastService } from 'src/app/services/toast.service'; import { Data, ParamMap } from '@angular/router'; -import { LoginPolicyService } from '../../../services/login-policy.service'; +import { LoginPolicyService } from 'src/app/services/login-policy.service'; import { PolicyComponentServiceType } from '../../policies/policy-component-types.enum'; +import { NewFeatureService } from 'src/app/services/new-feature.service'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; @Injectable({ providedIn: 'root', }) export class ProviderNextService { + private readonly loginV2BaseUri$: Observable; constructor( - private env: EnvironmentService, - private toast: ToastService, - private loginPolicySvc: LoginPolicyService, - private injector: Injector, - ) {} + private readonly env: EnvironmentService, + private readonly toast: ToastService, + private readonly loginPolicySvc: LoginPolicyService, + private readonly injector: Injector, + private readonly featureService: NewFeatureService, + ) { + this.loginV2BaseUri$ = this.getLoginV2BaseUri(); + } + + private getLoginV2BaseUri(): Observable { + return defer(() => this.featureService.getInstanceFeatures()).pipe( + timeout(1000), + // we try to load the features if this fails or takes too long we just assume loginV2 is not available + catchError(() => of({ loginV2: undefined })), + map((features) => features?.loginV2?.baseUri), + // we only try this once as the backup plan is not too bad + // and in most cases this will work + shareReplay({ refCount: false, bufferSize: 1 }), + takeUntilDestroyed(), + ); + } service(routeData: Observable): Observable { return routeData.pipe( @@ -82,11 +101,18 @@ export class ProviderNextService { callbackUrls(): Observable { return this.env.env.pipe( - map((env) => [ + combineLatestWith(this.loginV2BaseUri$), + map(([env, loginV2BaseUri]) => [ { - label: 'ZITADEL Callback URL', + label: 'Login V1 Callback URL', url: `${env.issuer}/ui/login/login/externalidp/callback`, }, + { + label: 'Login V2 Callback URL', + // if we don't have a loginV2BaseUri we provide a placeholder url so the user knows what to fill in + // this is not ideal but better than nothing + url: loginV2BaseUri ? `${loginV2BaseUri}idps/callback` : '{LOGIN V2 Hostname}/idps/callback', + }, ]), ); } diff --git a/console/src/app/modules/providers/provider-saml-sp/provider-saml-sp.component.html b/console/src/app/modules/providers/provider-saml-sp/provider-saml-sp.component.html index 833a44231de..602b72c72eb 100644 --- a/console/src/app/modules/providers/provider-saml-sp/provider-saml-sp.component.html +++ b/console/src/app/modules/providers/provider-saml-sp/provider-saml-sp.component.html @@ -18,7 +18,7 @@