From 7dfdf9cd82c88a2f3a12c0e39abc05bd9bb45e1f Mon Sep 17 00:00:00 2001 From: kulgg <75735874+kulgg@users.noreply.github.com> Date: Thu, 1 Feb 2024 14:50:38 +0100 Subject: [PATCH] Refactor --- .../clients/typescript-client/README.md | 3 +- .../typescript-client/src/EdcClient.ts | 26 +++++++++------ .../src/oauth2/AccessTokenFetcher.ts | 28 +++++++--------- .../src/oauth2/AccessTokenInjector.ts | 30 ++++------------- .../src/oauth2/AccessTokenStore.ts | 33 ++++++------------- .../typescript-client/src/oauth2/HttpUtils.ts | 14 +++----- .../src/oauth2/OAuthMiddleware.ts | 12 ++----- .../oauth2/{TypeUtils.ts => ObjectUtils.ts} | 0 .../src/oauth2/RequestUtils.ts | 32 ++++++++++++++++++ 9 files changed, 84 insertions(+), 94 deletions(-) rename extensions/wrapper/clients/typescript-client/src/oauth2/{TypeUtils.ts => ObjectUtils.ts} (100%) create mode 100644 extensions/wrapper/clients/typescript-client/src/oauth2/RequestUtils.ts diff --git a/extensions/wrapper/clients/typescript-client/README.md b/extensions/wrapper/clients/typescript-client/README.md index 0eac452d0..c8f34a73d 100644 --- a/extensions/wrapper/clients/typescript-client/README.md +++ b/extensions/wrapper/clients/typescript-client/README.md @@ -54,8 +54,7 @@ A minimal example project using the typescript API client can be found const edcClient: EdcClient = buildEdcClient({ managementApiUrl: 'http://localhost:11002/api/management/v2', oAuth2ClientCredentials: { - serverUrl: 'http://localhost:11002', - tokenEndpoint: '/token', + tokenUrl: 'http://localhost:11002/token', clientId: '{{your-connector}}-app', clientSecret: '...', }, diff --git a/extensions/wrapper/clients/typescript-client/src/EdcClient.ts b/extensions/wrapper/clients/typescript-client/src/EdcClient.ts index 3a8924ede..0667f6c0b 100644 --- a/extensions/wrapper/clients/typescript-client/src/EdcClient.ts +++ b/extensions/wrapper/clients/typescript-client/src/EdcClient.ts @@ -26,21 +26,21 @@ export interface EdcClient { * @param opts opts */ export function buildEdcClient(opts: EdcClientOptions): EdcClient { - let oAuthMiddleware: Middleware[] | undefined; + let middleware: Middleware[] = []; + let headers: Record = {}; if (opts.oAuth2ClientCredentials) { - oAuthMiddleware = buildOAuthMiddleware(opts.oAuth2ClientCredentials); + middleware.push(buildOAuthMiddleware(opts.oAuth2ClientCredentials)); + } + if (opts.managementApiKey) { + headers = buildApiKeyHeader(opts.managementApiKey); } const config = new Configuration({ basePath: opts.managementApiUrl, - headers: opts.managementApiKey - ? { - 'X-Api-Key': opts.managementApiKey, - } - : undefined, + headers, credentials: 'same-origin', - middleware: oAuthMiddleware ? oAuthMiddleware : undefined, + middleware, ...opts.configOverrides, }); @@ -53,12 +53,18 @@ export function buildEdcClient(opts: EdcClientOptions): EdcClient { function buildOAuthMiddleware( clientCredentials: OAuth2ClientCredentials, -): Middleware[] { +): Middleware { const accessTokenFetcher = new AccessTokenFetcher(clientCredentials); const accessTokenStore = new AccessTokenStore(accessTokenFetcher); const accessTokenInjector = new AccessTokenInjector(); - return [new OAuthMiddleware(accessTokenInjector, accessTokenStore).build()]; + return new OAuthMiddleware(accessTokenInjector, accessTokenStore).build(); +} + +function buildApiKeyHeader(key: string) { + return { + 'X-Api-Key': key, + }; } /** diff --git a/extensions/wrapper/clients/typescript-client/src/oauth2/AccessTokenFetcher.ts b/extensions/wrapper/clients/typescript-client/src/oauth2/AccessTokenFetcher.ts index 9a19e1d23..ac10e4a34 100644 --- a/extensions/wrapper/clients/typescript-client/src/oauth2/AccessTokenFetcher.ts +++ b/extensions/wrapper/clients/typescript-client/src/oauth2/AccessTokenFetcher.ts @@ -10,23 +10,19 @@ export class AccessTokenFetcher { this.credentialsBody = this.buildFormData(); } - async fetch(): Promise { - try { - let response = await fetch(this.clientCredentials.tokenUrl, { - method: 'POST', - body: this.credentialsBody, - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - }, - }); - let json: any = await response.json(); - if (json && 'access_token' in json) { - return json['access_token']; - } - return undefined; - } catch (err) { - return undefined; + async fetch(): Promise { + let response = await fetch(this.clientCredentials.tokenUrl, { + method: 'POST', + body: this.credentialsBody, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + }); + let json: any = await response.json(); + if (json && 'access_token' in json) { + return json['access_token']; } + throw new Error('access_token'); } private buildFormData(): string { diff --git a/extensions/wrapper/clients/typescript-client/src/oauth2/AccessTokenInjector.ts b/extensions/wrapper/clients/typescript-client/src/oauth2/AccessTokenInjector.ts index 1fd33caee..ea8257b75 100644 --- a/extensions/wrapper/clients/typescript-client/src/oauth2/AccessTokenInjector.ts +++ b/extensions/wrapper/clients/typescript-client/src/oauth2/AccessTokenInjector.ts @@ -1,32 +1,14 @@ -import {isHeaders} from './TypeUtils'; +import {withHeader} from './RequestUtils'; export class AccessTokenInjector { // init fields are passed by reference inject(init: RequestInit, token: string) { const authorizationValue = this.buildBearerValueString(token); - - if (!init.headers) { - init.headers = {Authorization: authorizationValue}; - return; - } - - if (Array.isArray(init.headers)) { - init.headers = init.headers.filter( - ([a, b]) => a !== 'Authorization', - ); - init.headers.push(['Authorization', authorizationValue]); - } else if (isHeaders(init.headers)) { - if (init.headers.has('Authorization')) { - init.headers.set('Authorization', authorizationValue); - } else { - init.headers.append('Authorization', authorizationValue); - } - } else { - init.headers = { - ...(init.headers as Record), - Authorization: authorizationValue, - }; - } + init.headers = withHeader( + 'Authorization', + authorizationValue, + init.headers, + ); } private buildBearerValueString(token: string): string { diff --git a/extensions/wrapper/clients/typescript-client/src/oauth2/AccessTokenStore.ts b/extensions/wrapper/clients/typescript-client/src/oauth2/AccessTokenStore.ts index 1b6cb4656..fc7efee61 100644 --- a/extensions/wrapper/clients/typescript-client/src/oauth2/AccessTokenStore.ts +++ b/extensions/wrapper/clients/typescript-client/src/oauth2/AccessTokenStore.ts @@ -1,38 +1,25 @@ import {AccessTokenFetcher} from './AccessTokenFetcher'; export class AccessTokenStore { - _accessToken?: string; - accessTokenFetcher: AccessTokenFetcher; - private isRefreshing = false; - private currentFetchPromise: Promise = - Promise.resolve(undefined); + private activeRequest: Promise | null = null; - constructor(accessTokenFetcher: AccessTokenFetcher, token?: string) { - this.accessToken = token; - this.accessTokenFetcher = accessTokenFetcher; - } - - get accessToken(): string | undefined { - return this._accessToken; - } - - set accessToken(token: string | undefined) { - this._accessToken = token; - } + constructor( + private accessTokenFetcher: AccessTokenFetcher, + public accessToken?: string, + ) {} // Synchronized refreshing of the access token async refreshToken() { - if (this.isRefreshing) { - await this.currentFetchPromise; + if (this.activeRequest) { + await this.activeRequest; return; } - this.isRefreshing = true; - this.currentFetchPromise = this.accessTokenFetcher.fetch(); - const newToken = await this.currentFetchPromise; + this.activeRequest = this.accessTokenFetcher.fetch(); + const newToken = await this.activeRequest; if (newToken) { this.accessToken = newToken; } - this.isRefreshing = false; + this.activeRequest = null; } } diff --git a/extensions/wrapper/clients/typescript-client/src/oauth2/HttpUtils.ts b/extensions/wrapper/clients/typescript-client/src/oauth2/HttpUtils.ts index 0efefd061..cb10216aa 100644 --- a/extensions/wrapper/clients/typescript-client/src/oauth2/HttpUtils.ts +++ b/extensions/wrapper/clients/typescript-client/src/oauth2/HttpUtils.ts @@ -2,14 +2,8 @@ export function needsAuthentication(httpStatus: number) { return httpStatus === 401 || httpStatus === 403; } -export function createUrlEncodedParamsString(obj: any) { - var formBody = []; - for (var property in obj) { - formBody.push( - `${encodeURIComponent(property)}=${encodeURIComponent( - obj[property], - )}`, - ); - } - return formBody.join('&'); +export function createUrlEncodedParamsString(obj: Record) { + return Object.entries(obj) + .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`) + .join('&'); } diff --git a/extensions/wrapper/clients/typescript-client/src/oauth2/OAuthMiddleware.ts b/extensions/wrapper/clients/typescript-client/src/oauth2/OAuthMiddleware.ts index 94d8d74d3..a62d7fc4d 100644 --- a/extensions/wrapper/clients/typescript-client/src/oauth2/OAuthMiddleware.ts +++ b/extensions/wrapper/clients/typescript-client/src/oauth2/OAuthMiddleware.ts @@ -4,16 +4,10 @@ import {AccessTokenStore} from './AccessTokenStore'; import {needsAuthentication} from './HttpUtils'; export class OAuthMiddleware { - accessTokenInjector: AccessTokenInjector; - accessTokenStore: AccessTokenStore; - constructor( - accessTokenInjector: AccessTokenInjector, - accessTokenStore: AccessTokenStore, - ) { - this.accessTokenInjector = accessTokenInjector; - this.accessTokenStore = accessTokenStore; - } + private accessTokenInjector: AccessTokenInjector, + private accessTokenStore: AccessTokenStore, + ) {} build(): Middleware { return { diff --git a/extensions/wrapper/clients/typescript-client/src/oauth2/TypeUtils.ts b/extensions/wrapper/clients/typescript-client/src/oauth2/ObjectUtils.ts similarity index 100% rename from extensions/wrapper/clients/typescript-client/src/oauth2/TypeUtils.ts rename to extensions/wrapper/clients/typescript-client/src/oauth2/ObjectUtils.ts diff --git a/extensions/wrapper/clients/typescript-client/src/oauth2/RequestUtils.ts b/extensions/wrapper/clients/typescript-client/src/oauth2/RequestUtils.ts new file mode 100644 index 000000000..a971acd8e --- /dev/null +++ b/extensions/wrapper/clients/typescript-client/src/oauth2/RequestUtils.ts @@ -0,0 +1,32 @@ +import {isHeaders} from './ObjectUtils'; + +export function withHeader( + headerName: string, + headerValue: string, + headers?: HeadersInit, +): HeadersInit { + if (!headers) { + headers = {headerName: headerValue}; + return headers; + } + + if (Array.isArray(headers)) { + return headers.map(([a, b]) => + a !== headerName ? [a, b] : [headerName, headerValue], + ); + } + + if (isHeaders(headers)) { + if (headers.has(headerName)) { + headers.set(headerName, headerValue); + } else { + headers.append(headerName, headerValue); + } + return headers; + } + + return { + ...(headers as Record), + headerName: headerValue, + }; +}