Skip to content

Commit

Permalink
chore: add token auth strategy and client credential provider
Browse files Browse the repository at this point in the history
  • Loading branch information
tiwarishubham635 committed Dec 4, 2024
1 parent a9826ba commit 4916098
Show file tree
Hide file tree
Showing 4 changed files with 172 additions and 0 deletions.
67 changes: 67 additions & 0 deletions src/auth_strategy/TokenAuthStrategy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import AuthStrategy from "./AuthStrategy";
import TokenManager from "../http/bearer_token/TokenManager";
import jwt, { JwtPayload } from "jsonwebtoken";

export default class TokenAuthStrategy extends AuthStrategy {
private token: string;
private tokenManager: TokenManager;

constructor(tokenManager: TokenManager) {
super("token");
this.token = "";
this.tokenManager = tokenManager;
}

async getAuthString(): Promise<string> {
return this.fetchToken()
.then((token) => {
this.token = token;
return `Bearer ${this.token}`;
})
.catch((error) => {
throw new Error(`Failed to fetch access token: ${error}`);
});
}

requiresAuthentication(): boolean {
return true;
}

async fetchToken(): Promise<string> {
if (
this.token == null ||
this.token.length === 0 ||
this.isTokenExpired(this.token)
) {
return this.tokenManager.fetchToken();
}
return Promise.resolve(this.token);
}

/**
* Function to check if the token is expired with a buffer of 30 seconds.
* @param token - The JWT token as a string.
* @returns Boolean indicating if the token is expired.
*/
isTokenExpired(token: string): boolean {
try {
// Decode the token without verifying the signature, as we only want to read the expiration for this check
const decoded = jwt.decode(token) as JwtPayload;

if (!decoded || !decoded.exp) {
// If the token doesn't have an expiration, consider it expired
return true;
}

const expiresAt = decoded.exp * 1000;
const bufferMilliseconds = 30 * 1000;
const bufferExpiresAt = expiresAt - bufferMilliseconds;

// Return true if the current time is after the expiration time with buffer
return Date.now() > bufferExpiresAt;
} catch (error) {
// If there's an error decoding the token, consider it expired
return true;
}
}
}
66 changes: 66 additions & 0 deletions src/credential_provider/ClientCredentialProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import CredentialProvider from "./CredentialProvider";
import TokenManager from "../http/bearer_token/TokenManager";
import AuthStrategy from "../auth_strategy/AuthStrategy";
import ApiTokenManager from "../http/bearer_token/ApiTokenManager";
import TokenAuthStrategy from "../auth_strategy/TokenAuthStrategy";

class ClientCredentialProvider extends CredentialProvider {
grantType: string;
clientId: string;
clientSecret: string;
tokenManager: TokenManager | null;

constructor() {
super("client-credentials");
this.grantType = "client_credentials";
this.clientId = "";
this.clientSecret = "";
this.tokenManager = null;
}

public toAuthStrategy(): AuthStrategy {
if (this.tokenManager == null) {
this.tokenManager = new ApiTokenManager({
grantType: this.grantType,
clientId: this.clientId,
clientSecret: this.clientSecret,
});
}
return new TokenAuthStrategy(this.tokenManager);
}
}

namespace ClientCredentialProvider {
export class ClientCredentialProviderBuilder {
private readonly instance: ClientCredentialProvider;

constructor() {
this.instance = new ClientCredentialProvider();
}

public setClientId(clientId: string): ClientCredentialProviderBuilder {
this.instance.clientId = clientId;
return this;
}

public setClientSecret(
clientSecret: string
): ClientCredentialProviderBuilder {
this.instance.clientSecret = clientSecret;
return this;
}

public setTokenManager(
tokenManager: TokenManager
): ClientCredentialProviderBuilder {
this.instance.tokenManager = tokenManager;
return this;
}

public build(): ClientCredentialProvider {
return this.instance;
}
}
}

export = ClientCredentialProvider;
36 changes: 36 additions & 0 deletions src/http/bearer_token/ApiTokenManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import TokenManager from "./TokenManager";
import {
TokenListInstance,
TokenListInstanceCreateOptions,
} from "../../rest/previewIam/v1/token";
import PreviewIamBase from "../../rest/PreviewIamBase";
import V1 from "../../rest/previewIam/V1";
import NoAuthCredentialProvider from "../../credential_provider/NoAuthCredentialProvider";
import { Client } from "../../base/BaseTwilio";

export default class ApiTokenManager implements TokenManager {
private params: TokenListInstanceCreateOptions;

constructor(params: TokenListInstanceCreateOptions) {
this.params = params;
}

async fetchToken(): Promise<string> {
const noAuthCredentialProvider =
new NoAuthCredentialProvider.NoAuthCredentialProvider();
const client = new Client();
client.setCredentialProvider(noAuthCredentialProvider);

const tokenListInstance = TokenListInstance(
new V1(new PreviewIamBase(client))
);
return tokenListInstance
.create(this.params)
.then((token) => {
return token.accessToken;
})
.catch((error) => {
throw new Error(`Failed to fetch access token: ${error}`);
});
}
}
3 changes: 3 additions & 0 deletions src/http/bearer_token/TokenManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default abstract class TokenManager {
abstract fetchToken(): Promise<string>;
}

0 comments on commit 4916098

Please sign in to comment.