From 9b1d0c000afdc3c57940996cd2101fa10c74c4cb Mon Sep 17 00:00:00 2001 From: Ivan Kovarin Date: Wed, 29 Oct 2025 16:54:08 +0100 Subject: [PATCH 1/4] [APIM-11537] Enable SSO on next-gen portal. --- gravitee-apim-portal-webui-next/package.json | 1 + .../src/app/app.component.html | 7 +- .../src/app/app.component.ts | 27 +++++- .../src/app/app.config.ts | 6 ++ .../src/app/log-in/log-in.component.html | 84 ++++++++++------- .../src/app/log-in/log-in.component.scss | 58 ++++++++++++ .../src/app/log-in/log-in.component.ts | 62 ++++++++++--- .../src/assets/images/idp/github.svg | 3 + .../src/assets/images/idp/google.svg | 1 + .../src/assets/images/idp/graviteeio_am.svg | 9 ++ .../src/assets/images/idp/oidc.svg | 9 ++ .../desktop-nav-bar.component.html | 30 ++++--- .../desktop-nav-bar.component.ts | 1 + .../mobile-nav-bar.component.html | 12 +-- .../mobile-nav-bar.component.ts | 1 + .../components/nav-bar/nav-bar.component.html | 4 +- .../components/nav-bar/nav-bar.component.ts | 1 + .../src/entities/common/data-response.ts | 22 +++++ .../src/entities/common/links.ts | 22 +++++ .../configuration/identity-provider-type.ts | 23 +++++ .../configuration/identity-provider.ts | 41 +++++++++ .../src/services/auth.service.ts | 90 +++++++++++++++++-- .../src/services/identity-provider.service.ts | 42 +++++++++ gravitee-apim-portal-webui-next/yarn.lock | 15 +++- 24 files changed, 502 insertions(+), 69 deletions(-) create mode 100644 gravitee-apim-portal-webui-next/src/assets/images/idp/github.svg create mode 100644 gravitee-apim-portal-webui-next/src/assets/images/idp/google.svg create mode 100644 gravitee-apim-portal-webui-next/src/assets/images/idp/graviteeio_am.svg create mode 100644 gravitee-apim-portal-webui-next/src/assets/images/idp/oidc.svg create mode 100644 gravitee-apim-portal-webui-next/src/entities/common/data-response.ts create mode 100644 gravitee-apim-portal-webui-next/src/entities/common/links.ts create mode 100644 gravitee-apim-portal-webui-next/src/entities/configuration/identity-provider-type.ts create mode 100644 gravitee-apim-portal-webui-next/src/entities/configuration/identity-provider.ts create mode 100644 gravitee-apim-portal-webui-next/src/services/identity-provider.service.ts diff --git a/gravitee-apim-portal-webui-next/package.json b/gravitee-apim-portal-webui-next/package.json index 1cc157abcd8..b36ca9b61b9 100644 --- a/gravitee-apim-portal-webui-next/package.json +++ b/gravitee-apim-portal-webui-next/package.json @@ -61,6 +61,7 @@ "@fontsource/roboto": "5.2.5", "@types/swagger-ui": "3.52.4", "angular-gridster2": "20.2.3", + "angular-oauth2-oidc": "^20.0.2", "asciidoctor": "3.0.4", "chart.js": "4.3.0", "dompurify": "3.2.7", diff --git a/gravitee-apim-portal-webui-next/src/app/app.component.html b/gravitee-apim-portal-webui-next/src/app/app.component.html index 2f3f7d95e10..40f4d073c64 100644 --- a/gravitee-apim-portal-webui-next/src/app/app.component.html +++ b/gravitee-apim-portal-webui-next/src/app/app.component.html @@ -15,7 +15,12 @@ limitations under the License. --> - +
diff --git a/gravitee-apim-portal-webui-next/src/app/app.component.ts b/gravitee-apim-portal-webui-next/src/app/app.component.ts index ce882154f2a..675e9358a59 100644 --- a/gravitee-apim-portal-webui-next/src/app/app.component.ts +++ b/gravitee-apim-portal-webui-next/src/app/app.component.ts @@ -15,7 +15,8 @@ */ import { Component, effect, inject } from '@angular/core'; import { Title } from '@angular/platform-browser'; -import { RouterOutlet } from '@angular/router'; +import { NavigationStart, Router, RouterOutlet } from '@angular/router'; +import { isEmpty } from 'lodash'; import { BreadcrumbService } from 'xng-breadcrumb'; import { FooterComponent } from '../components/footer/footer.component'; @@ -25,6 +26,12 @@ import { CurrentUserService } from '../services/current-user.service'; import { PortalMenuLinksService } from '../services/portal-menu-links.service'; import { ThemeService } from '../services/theme.service'; +const PATHS = { + LOGIN: '/log-in', + SIGNUP: '/sign-up', + RESET_PASSWORD: '/log-in/reset-password', +}; + @Component({ selector: 'app-root', imports: [RouterOutlet, NavBarComponent, FooterComponent], @@ -43,6 +50,7 @@ export class AppComponent { // Don't delete BreadcrumbService from here - it's responsible for correct breadcrumb navigation private breadcrumbService: BreadcrumbService, private title: Title, + private router: Router, ) { this.siteTitle = configService.configuration?.portalNext?.siteTitle ?? 'Developer Portal'; this.title.setTitle(this.siteTitle); @@ -51,6 +59,23 @@ export class AppComponent { this.updateFavicon(); } }); + + this.router.events.subscribe(event => { + if (event instanceof NavigationStart) { + if (isEmpty(this.currentUser()) && !this.isInLoginOrRegistration(event.url) && this.forceLogin()) { + const redirectUrl = event.url; + this.router.navigate([PATHS.LOGIN], { replaceUrl: true, queryParams: { redirectUrl } }); + } + } + }); + } + + isInLoginOrRegistration(url: string = this.router.routerState.snapshot.url): boolean { + return url.startsWith(PATHS.LOGIN) || url.startsWith(PATHS.SIGNUP) || url.startsWith(PATHS.RESET_PASSWORD); + } + + forceLogin(): boolean { + return this.configService.configuration.authentication?.forceLogin?.enabled ?? false; } private updateFavicon() { diff --git a/gravitee-apim-portal-webui-next/src/app/app.config.ts b/gravitee-apim-portal-webui-next/src/app/app.config.ts index 8cde9467ba5..1974dbade14 100644 --- a/gravitee-apim-portal-webui-next/src/app/app.config.ts +++ b/gravitee-apim-portal-webui-next/src/app/app.config.ts @@ -20,18 +20,21 @@ import { MAT_RIPPLE_GLOBAL_OPTIONS } from '@angular/material/core'; import { provideAnimations } from '@angular/platform-browser/animations'; import { provideRouter, Router, withComponentInputBinding, withRouterConfig } from '@angular/router'; import { provideCharts, withDefaultRegisterables } from 'ng2-charts'; +import { provideOAuthClient } from 'angular-oauth2-oidc'; import { catchError, combineLatest, Observable, switchMap } from 'rxjs'; import { of } from 'rxjs/internal/observable/of'; import { routes } from './app.routes'; import { csrfInterceptor } from '../interceptors/csrf.interceptor'; import { httpRequestInterceptor } from '../interceptors/http-request.interceptor'; +import { AuthService } from '../services/auth.service'; import { ConfigService } from '../services/config.service'; import { CurrentUserService } from '../services/current-user.service'; import { PortalMenuLinksService } from '../services/portal-menu-links.service'; import { ThemeService } from '../services/theme.service'; function initApp( + authService: AuthService, configService: ConfigService, themeService: ThemeService, currentUserService: CurrentUserService, @@ -40,6 +43,7 @@ function initApp( ): () => Observable { return () => configService.initBaseURL().pipe( + switchMap(_ => authService.load()), switchMap(_ => combineLatest([ themeService.loadTheme(), @@ -60,8 +64,10 @@ export const appConfig: ApplicationConfig = { provideRouter(routes, withComponentInputBinding(), withRouterConfig({ paramsInheritanceStrategy: 'always' })), provideHttpClient(withInterceptors([httpRequestInterceptor, csrfInterceptor])), provideAnimations(), + provideOAuthClient(), provideAppInitializer(() => { const initializerFn = initApp( + inject(AuthService), inject(ConfigService), inject(ThemeService), inject(CurrentUserService), diff --git a/gravitee-apim-portal-webui-next/src/app/log-in/log-in.component.html b/gravitee-apim-portal-webui-next/src/app/log-in/log-in.component.html index a87f07241c7..41ab59fce69 100644 --- a/gravitee-apim-portal-webui-next/src/app/log-in/log-in.component.html +++ b/gravitee-apim-portal-webui-next/src/app/log-in/log-in.component.html @@ -20,37 +20,61 @@ Log in -