From 0d957dfb1cb3018631e1a4ad778db2448eff2763 Mon Sep 17 00:00:00 2001 From: lonelyhentxi Date: Fri, 21 Feb 2025 03:53:46 +0800 Subject: [PATCH] feat: support client_secret --- package.json | 2 +- src/adapters/tanstack-router/index.ts | 4 +-- .../auto-login-partial-routes.guard.ts | 17 ++++++----- src/config/openid-configuration.ts | 6 ++++ src/login/login.service.ts | 4 ++- src/router/index.ts | 2 +- src/utils/url/url.service.ts | 28 +++++++++++++++++++ 7 files changed, 51 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index 605c74c..6e50d78 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "oidc-client-rx", - "version": "0.1.0-alpha.4", + "version": "0.1.0-alpha.5", "homepage": "https://github.com/lonelyhentxi/oidc-client-rx", "author": "lonelyhentxi", "description": "ReactiveX enhanced OIDC and OAuth2 protocol support for browser-based JavaScript applications", diff --git a/src/adapters/tanstack-router/index.ts b/src/adapters/tanstack-router/index.ts index 03a5467..ffe3f86 100644 --- a/src/adapters/tanstack-router/index.ts +++ b/src/adapters/tanstack-router/index.ts @@ -1,9 +1,9 @@ import { InjectionToken, inject } from '@outposts/injection-js'; -import type { Router } from '@tanstack/react-router'; +import type { AnyRouter } from '@tanstack/react-router'; import type { AuthFeature } from '../../features'; import { AbstractRouter } from '../../router'; -export type TanStackRouter = Router; +export type TanStackRouter = AnyRouter; export const TANSTACK_ROUTER = new InjectionToken( 'TANSTACK_ROUTER' diff --git a/src/auto-login/auto-login-partial-routes.guard.ts b/src/auto-login/auto-login-partial-routes.guard.ts index 9e1c926..7e7f0cb 100644 --- a/src/auto-login/auto-login-partial-routes.guard.ts +++ b/src/auto-login/auto-login-partial-routes.guard.ts @@ -1,6 +1,6 @@ import { Injectable, inject } from '@outposts/injection-js'; -import type { Observable } from 'rxjs'; -import { map } from 'rxjs/operators'; +import { type Observable, of } from 'rxjs'; +import { map, switchMap } from 'rxjs/operators'; import type { AuthOptions } from '../auth-options'; import { AuthStateService } from '../auth-state/auth-state.service'; import { ConfigurationService } from '../config/config.service'; @@ -126,7 +126,7 @@ function checkAuth( configId?: string ): Observable { return configurationService.getOpenIDConfiguration(configId).pipe( - map((configuration) => { + switchMap((configuration) => { const isAuthenticated = authStateService.areAuthStorageTokensValid(configuration); @@ -137,13 +137,16 @@ function checkAuth( if (!isAuthenticated) { autoLoginService.saveRedirectRoute(configuration, url); if (authOptions) { - loginService.login(configuration, authOptions); - } else { - loginService.login(configuration); + return loginService + .login(configuration, authOptions) + .pipe(switchMap(() => of(isAuthenticated))); } + return loginService + .login(configuration) + .pipe(switchMap(() => of(isAuthenticated))); } - return isAuthenticated; + return of(isAuthenticated); }) ); } diff --git a/src/config/openid-configuration.ts b/src/config/openid-configuration.ts index b1bb650..f8e611f 100644 --- a/src/config/openid-configuration.ts +++ b/src/config/openid-configuration.ts @@ -40,6 +40,12 @@ export interface OpenIdConfiguration { * or if it contains additional audiences not trusted by the Client. */ clientId?: string; + /** + * @dangerous + * @see [client secret is missing](https://github.com/damienbod/angular-auth-oidc-client/issues/399) + * The client secret. For some oidc service the must provide this. + */ + clientSecret?: string; /** * `code`, `id_token token` or `id_token`. * Name of the flow which can be configured. diff --git a/src/login/login.service.ts b/src/login/login.service.ts index ebcbae2..e55e22e 100644 --- a/src/login/login.service.ts +++ b/src/login/login.service.ts @@ -1,5 +1,6 @@ import { Injectable, inject } from '@outposts/injection-js'; -import { type Observable, of, throwError } from 'rxjs'; +import { BehaviorSubject, type Observable, of, throwError } from 'rxjs'; +import { MockUtil } from 'src/utils/reflect'; import type { AuthOptions } from '../auth-options'; import type { OpenIdConfiguration } from '../config/openid-configuration'; import { StoragePersistenceService } from '../storage/storage-persistence.service'; @@ -24,6 +25,7 @@ export class LoginService { private readonly popupService = inject(PopUpService); + @MockUtil({ implementation: () => new BehaviorSubject(undefined) }) login( configuration: OpenIdConfiguration | null, authOptions?: AuthOptions diff --git a/src/router/index.ts b/src/router/index.ts index 540d32f..1a1ba62 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -30,7 +30,7 @@ export abstract class AbstractRouter< abstract getCurrentNavigation(): NAVIGATION; } -export class VanillaLocationRouter extends AbstractRouter { +export class VanillaLocationRouter extends AbstractRouter { protected document = inject(DOCUMENT); private get location(): Location { diff --git a/src/utils/url/url.service.ts b/src/utils/url/url.service.ts index 7d90cb0..758ba64 100644 --- a/src/utils/url/url.service.ts +++ b/src/utils/url/url.service.ts @@ -249,6 +249,7 @@ export class UrlService { configuration: OpenIdConfiguration ): string | null { const clientId = this.getClientId(configuration); + const clientSecret = this.getClientSecret(configuration); if (!clientId) { return null; @@ -259,6 +260,9 @@ export class UrlService { params = params.set('client_id', clientId); params = params.set('token', token); params = params.set('token_type_hint', 'access_token'); + if (clientSecret) { + params = params.set('client_secret', clientSecret); + } return params.toString(); } @@ -268,6 +272,7 @@ export class UrlService { configuration: OpenIdConfiguration ): string | null { const clientId = this.getClientId(configuration); + const clientSecret = this.getClientSecret(configuration); if (!clientId) { return null; @@ -278,6 +283,9 @@ export class UrlService { params = params.set('client_id', clientId); params = params.set('token', token); params = params.set('token_type_hint', 'refresh_token'); + if (clientSecret) { + params = params.set('client_secret', clientSecret); + } return params.toString(); } @@ -304,6 +312,7 @@ export class UrlService { customTokenParams?: { [p: string]: string | number | boolean } ): string | null { const clientId = this.getClientId(configuration); + const clientSecret = this.getClientSecret(configuration); if (!clientId) { return null; @@ -313,6 +322,9 @@ export class UrlService { params = params.set('grant_type', 'authorization_code'); params = params.set('client_id', clientId); + if (clientSecret) { + params = params.set('client_secret', clientSecret); + } if (!configuration.disablePkce) { const codeVerifier = this.flowsDataService.getCodeVerifier(configuration); @@ -364,6 +376,7 @@ export class UrlService { customParamsRefresh?: { [key: string]: string | number | boolean } ): string | null { const clientId = this.getClientId(configuration); + const clientSecret = this.getClientSecret(configuration); if (!clientId) { return null; @@ -374,6 +387,9 @@ export class UrlService { params = params.set('grant_type', 'refresh_token'); params = params.set('client_id', clientId); params = params.set('refresh_token', refreshToken); + if (clientSecret) { + params = params.set('client_secret', clientSecret); + } if (customParamsRefresh) { params = this.appendCustomParams({ ...customParamsRefresh }, params); @@ -851,6 +867,18 @@ export class UrlService { return clientId; } + private getClientSecret(configuration: OpenIdConfiguration): string | null { + const { clientSecret } = configuration; + + if (!clientSecret) { + this.loggerService.logDebug(configuration, 'could not get clientSecret'); + + return null; + } + + return clientSecret; + } + private appendCustomParams( customParams: { [key: string]: string | number | boolean }, params: HttpParams