Skip to content

Commit

Permalink
Refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
kulgg committed Feb 1, 2024
1 parent 5d7c1c4 commit 7dfdf9c
Show file tree
Hide file tree
Showing 9 changed files with 84 additions and 94 deletions.
3 changes: 1 addition & 2 deletions extensions/wrapper/clients/typescript-client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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: '...',
},
Expand Down
26 changes: 16 additions & 10 deletions extensions/wrapper/clients/typescript-client/src/EdcClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, string> = {};

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,
});

Expand All @@ -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,
};
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,19 @@ export class AccessTokenFetcher {
this.credentialsBody = this.buildFormData();
}

async fetch(): Promise<string | undefined> {
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<string> {
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 {
Expand Down
Original file line number Diff line number Diff line change
@@ -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<string, string>),
Authorization: authorizationValue,
};
}
init.headers = withHeader(
'Authorization',
authorizationValue,
init.headers,
);
}

private buildBearerValueString(token: string): string {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,38 +1,25 @@
import {AccessTokenFetcher} from './AccessTokenFetcher';

export class AccessTokenStore {
_accessToken?: string;
accessTokenFetcher: AccessTokenFetcher;
private isRefreshing = false;
private currentFetchPromise: Promise<string | undefined> =
Promise.resolve(undefined);
private activeRequest: Promise<string> | 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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, string>) {
return Object.entries(obj)
.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
.join('&');
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
@@ -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<string, string>),
headerName: headerValue,
};
}

0 comments on commit 7dfdf9c

Please sign in to comment.