Skip to content

Commit

Permalink
Add supporting static files
Browse files Browse the repository at this point in the history
  • Loading branch information
frederikprijck committed Apr 3, 2023
1 parent e1bf9f3 commit d5902d7
Show file tree
Hide file tree
Showing 8 changed files with 663 additions and 0 deletions.
3 changes: 3 additions & 0 deletions src/management/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './management-client.options';
export * from './management-client';
export * from './token-provider.middleware';
22 changes: 22 additions & 0 deletions src/management/management-client.options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
export interface ManagementClientOptions {
domain: string;
audience?: string;
}

export interface ManagementClientOptionsWithToken extends ManagementClientOptions {
token: string;
}

export interface ManagementClientOptionsWithClientSecret extends ManagementClientOptions {
clientId: string;
clientSecret: string;
}

export interface ManagementClientOptionsWithClientAssertion extends ManagementClientOptions {
clientId: string;
clientAssertionSigningKey: string;
}

export type ManagementClientOptionsWithClientCredentials =
| ManagementClientOptionsWithClientSecret
| ManagementClientOptionsWithClientAssertion;
27 changes: 27 additions & 0 deletions src/management/management-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import fetch, { RequestInfo as NFRequestInfo, RequestInit as NFRequestInit } from 'node-fetch';
import { Configuration } from './runtime';
import { ManagementClientBase } from './__generated';
import {
ManagementClientOptionsWithToken,
ManagementClientOptionsWithClientCredentials,
} from './management-client.options';
import { tokenProviderFactory } from './management-client.utils';
import { TokenProviderMiddleware } from './token-provider.middleware';

export class ManagementClient extends ManagementClientBase {
constructor(
options: ManagementClientOptionsWithToken | ManagementClientOptionsWithClientCredentials
) {
super(
new Configuration({
baseUrl: 'https://' + options.domain + '/api/v2',
fetchApi: (url: RequestInfo, init: RequestInit) => {
return fetch(url as NFRequestInfo, init as NFRequestInit) as unknown as Promise<Response>;
},
middleware: [],
})
);

this.configuration.middleware.push(new TokenProviderMiddleware(tokenProviderFactory(options)));
}
}
42 changes: 42 additions & 0 deletions src/management/management-client.utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import {
ManagementClientOptionsWithClientCredentials,
ManagementClientOptionsWithToken,
} from './management-client.options';
import { TokenProviderOptions, TokenProvider } from './token-provider';

export function toTokenProviderOptions(
options: ManagementClientOptionsWithClientCredentials
): TokenProviderOptions {
const base = {
domain: options.domain,
audience: options.audience ?? `https://${options.domain}/api/v2/`,
};

if ('clientAssertionSigningKey' in options) {
return {
...base,
clientId: options.clientId,
clientAssertionSigningKey: options.clientAssertionSigningKey,
};
} else {
return {
...base,
clientId: options.clientId,
clientSecret: options.clientSecret,
};
}
}

export function tokenProviderFactory(
options: ManagementClientOptionsWithToken | ManagementClientOptionsWithClientCredentials
) {
if ('token' in options) {
return {
getAccessToken: () => {
return Promise.resolve(options.token);
},
} as TokenProvider;
} else {
return new TokenProvider(toTokenProviderOptions(options));
}
}
1 change: 1 addition & 0 deletions src/management/runtime.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './../runtime';
18 changes: 18 additions & 0 deletions src/management/token-provider.middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { FetchParams, Middleware, RequestContext } from './__generated';
import { TokenProvider } from './token-provider';

export class TokenProviderMiddleware implements Middleware {
constructor(private tokenProvider: TokenProvider) {}

async pre?(context: RequestContext): Promise<FetchParams | void> {
const token = await this.tokenProvider.getAccessToken();
context.init.headers = {
...context.init.headers,
Authorization: `Bearer ${token}`,
};
return {
url: context.url,
init: context.init,
};
}
}
155 changes: 155 additions & 0 deletions src/management/token-provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import memoizer from 'lru-memoizer';
import { promisify } from 'util';

import AuthenticationClient from '../auth';

interface TokenResponse {
access_token: string;
expires_in: number;
}
export interface BaseTokenProviderOptions {
domain: string;
audience: string;
clientId: string;
scope?: string;

enableCache?: boolean;
cacheTTLInSeconds?: number;
}

export interface TokenProviderOptionsWithClientSecret extends BaseTokenProviderOptions {
clientSecret: string;
}

export interface TokenProviderOptionsWithClientAssertion extends BaseTokenProviderOptions {
clientAssertionSigningKey: string;
}

export type TokenProviderOptions =
| TokenProviderOptionsWithClientSecret
| TokenProviderOptionsWithClientAssertion;

export class TokenProvider {
private options: TokenProviderOptions;

// Due to lack of ESM support, using any for now.
private authenticationClient: any;

constructor(options: TokenProviderOptions) {
if (!options || typeof options !== 'object') {
throw new Error('Options must be an object');
}

const params = { enableCache: true, ...options };

if (!params.domain || params.domain.length === 0) {
throw new Error('Must provide a domain');
}

if (!params.clientId || params.clientId.length === 0) {
throw new Error('Must provide a clientId');
}

if (!('clientSecret' in params) && !('clientAssertionSigningKey' in params)) {
throw new Error('Must provide a clientSecret or a clientAssertionSigningKey');
} else if (
('clientSecret' in params && params.clientSecret.length === 0) ||
('clientAssertionSigningKey' in params && params.clientAssertionSigningKey.length === 0)
) {
throw new Error('Must provide a clientSecret or a clientAssertionSigningKey');
}

if (!params.audience || params.audience.length === 0) {
throw new Error('Must provide a audience');
}

if (typeof params.enableCache !== 'boolean') {
throw new Error('enableCache must be a boolean');
}

if (params.enableCache && params.cacheTTLInSeconds) {
if (typeof params.cacheTTLInSeconds !== 'number') {
throw new Error('cacheTTLInSeconds must be a number');
}

if (params.cacheTTLInSeconds <= 0) {
throw new Error('cacheTTLInSeconds must be a greater than 0');
}
}

if (params.scope && typeof params.scope !== 'string') {
throw new Error('scope must be a string');
}

this.options = params;

const { scope, audience, enableCache, cacheTTLInSeconds, ...authenticationClientOptions } =
this.options;

this.authenticationClient = new AuthenticationClient(authenticationClientOptions);
}

private getCachedAccessToken = promisify<TokenProviderOptions, TokenResponse>(
memoizer<TokenProviderOptions, TokenResponse>({
load: (options: TokenProviderOptions, callback: (err: any, data: TokenResponse) => void) => {
this.clientCredentialsGrant(options.domain, options.scope, options.audience)
.then((data: TokenResponse) => {
callback(null, data);
})
.catch((err: any) => {
callback(err, null);
});
},
hash(options: TokenProviderOptions) {
return `${options.domain}-${options.clientId}-${options.scope}`;
},
itemMaxAge(options: TokenProviderOptions, data: TokenResponse) {
if (options.cacheTTLInSeconds) {
return options.cacheTTLInSeconds * 1000;
}

// if the expires_in is lower or equal to than 10 seconds, do not subtract 10 additional seconds.
if (data.expires_in && data.expires_in <= 10 /* seconds */) {
return data.expires_in * 1000;
} else if (data.expires_in) {
// Subtract 10 seconds from expires_in to fetch a new one, before it expires.
return data.expires_in * 1000 - 10000 /* milliseconds */;
}
return 60 * 60 * 1000; //1h
},

// TODO: Need to patch lru-memoizer to accept a max on its types.
// max: 100,
})
);

/**
* Returns the access_token.
*
* @returns {Promise} Promise returning an access_token.
*/
async getAccessToken() {
if (this.options.enableCache) {
const data = await this.getCachedAccessToken(this.options);
return data.access_token;
} else {
const data = await this.clientCredentialsGrant(
this.options.domain,
this.options.scope,
this.options.audience
);
return data.access_token;
}
}

private clientCredentialsGrant(
domain: string,
scope: string,
audience: string
): Promise<TokenResponse> {
return this.authenticationClient.clientCredentialsGrant({
audience,
scope,
});
}
}
Loading

0 comments on commit d5902d7

Please sign in to comment.