Merged in development (pull request #8)

Development
This commit is contained in:
Leandro Costa 2023-06-17 00:12:53 +00:00
commit f1b651f5a2
8 changed files with 282 additions and 2647 deletions

View file

@ -1,9 +1,9 @@
import axios from 'axios';
import axios from "axios";
import { LhispOauthClient } from "../src/lhisp-oauth-client";
import { ContentType, LhispOauthClientConstructorParams } from '../src/lhisp-oauth-client.t';
import { ContentType, LhispOauthClientConstructorParams, defaultAuthContentType } from "../src/lhisp-oauth-client.t";
// Mock jest and set the type
jest.mock('axios');
jest.mock("axios");
const mockedAxios = axios as jest.Mocked<typeof axios>;
const apiUrl = "https://myapi.com";
@ -11,11 +11,11 @@ const authUrl = "https://auth.myapi.com/oauth/token";
const clientId = "testClientdId";
const clientSecret = "testClientSecret";
const baseClientParams = { apiUrl, authUrl, clientId, clientSecret };
const basicAuth = `Basic ${Buffer.from(`${clientId}:${clientSecret}`).toString('base64')}`;
const contentTypeApplicationJson = "application/json";
const contentTypeApplicationXFormUrlEncoded = "application/x-www-form-urlencoded";
const defaultGrantValue='client_credentials';
const defaultGrantType=`{"grant_type":"${defaultGrantValue}"}`;
const basicAuth = `Basic ${Buffer.from(`${clientId}:${clientSecret}`).toString("base64")}`;
const contentTypeApplicationJson = ContentType.APPLICATION_JSON;
const contentTypeApplicationXFormUrlEncoded = ContentType.APPLICATION_X_WWW_FORM_URLENCODED;
const defaultGrantValue = "client_credentials";
const defaultGrantType = `grant_type=${defaultGrantValue}`;
describe("Get Access Token", () => {
beforeEach(() => {
@ -32,69 +32,78 @@ describe("Get Access Token", ()=>{
it("Shoud Get with Custom Auth Header", async () => {
const cli = getOauthClient({
...baseClientParams,
authHeaderName: 'CustomAuthorizationHeader',
authHeaderName: "CustomAuthorizationHeader",
});
await accessTokenValidator(cli);
expect(mockedAxios.request).toBeCalledWith(expect.objectContaining({
expect(mockedAxios.request).toBeCalledWith(
expect.objectContaining({
url: authUrl,
method: "POST",
headers: {
CustomAuthorizationHeader: basicAuth,
'Content-Type': contentTypeApplicationJson,
"Content-Type": contentTypeApplicationXFormUrlEncoded,
},
data: defaultGrantType,
}));
})
);
});
it("Shoud Get with Custom Grant Type", async () => {
const cli = getOauthClient({
...baseClientParams,
grantType: 'PermissaoCustom',
grantType: "PermissaoCustom",
});
await accessTokenValidator(cli);
expect(mockedAxios.request).toBeCalledWith(expect.objectContaining({
expect(mockedAxios.request).toBeCalledWith(
expect.objectContaining({
url: authUrl,
method: "POST",
headers: {
Authorization: basicAuth,
'Content-Type': contentTypeApplicationJson,
"Content-Type": contentTypeApplicationXFormUrlEncoded,
},
data: '{"grant_type":"PermissaoCustom"}',
}));
data: "grant_type=PermissaoCustom",
})
);
});
it("Shoud Get with Custom Auth Scope", async () => {
const cli = getOauthClient({
...baseClientParams,
authScope: 'EscopoCustom',
authScope: "EscopoCustom",
});
await accessTokenValidator(cli);
expect(mockedAxios.request).toBeCalledWith(expect.objectContaining({
expect(mockedAxios.request).toBeCalledWith(
expect.objectContaining({
url: authUrl,
method: "POST",
headers: {
Authorization: basicAuth,
'Content-Type': contentTypeApplicationJson,
"Content-Type": contentTypeApplicationXFormUrlEncoded,
},
data: `{"grant_type":"${defaultGrantValue}","scope":"EscopoCustom"}`,
}));
data: `grant_type=${defaultGrantValue}&scope=EscopoCustom`,
})
);
});
it("Shoud Get with Credentials on Request body", async () => {
const cli = getOauthClient({
...baseClientParams,
authContentType: contentTypeApplicationJson,
sendAuthCredentialsOnRequestBody: true,
});
await accessTokenValidator(cli);
expect(mockedAxios.request).toBeCalledWith(expect.objectContaining({
expect(mockedAxios.request).toBeCalledWith(
expect.objectContaining({
url: authUrl,
method: "POST",
headers: {
Authorization: basicAuth,
'Content-Type': contentTypeApplicationJson,
"Content-Type": contentTypeApplicationJson,
},
data: `{"grant_type":"${defaultGrantValue}","client_id":"${clientId}","client_secret":"${clientSecret}"}`,
}));
})
);
});
});
@ -102,76 +111,84 @@ describe("Request", ()=>{
beforeEach(() => {
mockedAxios.request.mockReset();
mockedAxios.request.mockResolvedValueOnce({ data: mockedAccessToken });
mockedAxios.request.mockResolvedValueOnce({ data: {"status": "ok"} });
mockedAxios.request.mockResolvedValueOnce({ data: { status: "ok" } });
});
it("Get without Params", async () => {
const cli = getOauthClient();
const resp = await cli.get({ path: '/my-test-url' });
const resp = await cli.get({ path: "/my-test-url" });
validateDefaultGetAccessToken();
expect(mockedAxios.request).toBeCalledWith(expect.objectContaining({
expect(mockedAxios.request).toBeCalledWith(
expect.objectContaining({
url: `${apiUrl}/my-test-url`,
method: "GET",
headers: {
Authorization: `Bearer SomeAccessToken`,
'Content-Type': contentTypeApplicationJson,
"Content-Type": contentTypeApplicationJson,
},
data: undefined
}));
expect(resp).toStrictEqual({"status": "ok"});
data: undefined,
})
);
expect(resp).toStrictEqual({ status: "ok" });
});
it("Get with Params", async () => {
const cli = getOauthClient();
const resp = await cli.get({ path: '/my-test-url', params: { id: 1 } });
const resp = await cli.get({ path: "/my-test-url", params: { id: 1 } });
validateDefaultGetAccessToken();
expect(mockedAxios.request).toBeCalledWith(expect.objectContaining({
expect(mockedAxios.request).toBeCalledWith(
expect.objectContaining({
url: `${apiUrl}/my-test-url`,
method: "GET",
headers: {
Authorization: `Bearer SomeAccessToken`,
'Content-Type': contentTypeApplicationJson,
"Content-Type": contentTypeApplicationJson,
},
params: { id: 1 },
data: undefined
}));
expect(resp).toStrictEqual({"status": "ok"});
data: undefined,
})
);
expect(resp).toStrictEqual({ status: "ok" });
});
it("Post", async () => {
const cli = getOauthClient();
const resp = await cli.post({ path: '/my-test-url-post', data: { id: 1, user: 'test' } });
const resp = await cli.post({ path: "/my-test-url-post", data: { id: 1, user: "test" } });
validateDefaultGetAccessToken();
expect(mockedAxios.request).toBeCalledWith(expect.objectContaining({
expect(mockedAxios.request).toBeCalledWith(
expect.objectContaining({
url: `${apiUrl}/my-test-url-post`,
method: "POST",
headers: {
Authorization: `Bearer SomeAccessToken`,
'Content-Type': contentTypeApplicationJson,
"Content-Type": contentTypeApplicationJson,
},
data: { id: 1, user: 'test' }
}));
expect(resp).toStrictEqual({"status": "ok"});
data: { id: 1, user: "test" },
})
);
expect(resp).toStrictEqual({ status: "ok" });
});
it("Post with different contentType", async () => {
const cli = getOauthClient();
const resp = await cli.post({
path: '/my-test-url-post',
data: { id: 1, user: 'test' },
contentType: ContentType.APPLICATION_X_WWW_FORM_URLENCODED
path: "/my-test-url-post",
data: { id: 1, user: "test" },
contentType: ContentType.APPLICATION_X_WWW_FORM_URLENCODED,
});
validateDefaultGetAccessToken();
expect(mockedAxios.request).toBeCalledWith(expect.objectContaining({
expect(mockedAxios.request).toBeCalledWith(
expect.objectContaining({
url: `${apiUrl}/my-test-url-post`,
method: "POST",
headers: {
Authorization: `Bearer SomeAccessToken`,
'Content-Type': contentTypeApplicationXFormUrlEncoded,
"Content-Type": contentTypeApplicationXFormUrlEncoded,
},
data: { id: 1, user: 'test' }
}));
expect(resp).toStrictEqual({"status": "ok"});
data: { id: 1, user: "test" },
})
);
expect(resp).toStrictEqual({ status: "ok" });
});
it("Post with Different Token Header Name", async () => {
@ -180,34 +197,38 @@ describe("Request", ()=>{
tokenHeaderName: "x-token",
});
const resp = await cli.post({
path: '/my-test-url-post',
data: { id: 1, user: 'test' },
contentType: ContentType.APPLICATION_X_WWW_FORM_URLENCODED
path: "/my-test-url-post",
data: { id: 1, user: "test" },
contentType: ContentType.APPLICATION_X_WWW_FORM_URLENCODED,
});
validateDefaultGetAccessToken();
expect(mockedAxios.request).toBeCalledWith(expect.objectContaining({
expect(mockedAxios.request).toBeCalledWith(
expect.objectContaining({
url: `${apiUrl}/my-test-url-post`,
method: "POST",
headers: {
'x-token': `Bearer SomeAccessToken`,
'Content-Type': contentTypeApplicationXFormUrlEncoded,
"x-token": `Bearer SomeAccessToken`,
"Content-Type": contentTypeApplicationXFormUrlEncoded,
},
data: { id: 1, user: 'test' }
}));
expect(resp).toStrictEqual({"status": "ok"});
data: { id: 1, user: "test" },
})
);
expect(resp).toStrictEqual({ status: "ok" });
});
});
function validateDefaultGetAccessToken() {
expect(mockedAxios.request).toBeCalledWith(expect.objectContaining({
expect(mockedAxios.request).toBeCalledWith(
expect.objectContaining({
url: authUrl,
method: "POST",
headers: {
Authorization: basicAuth,
'Content-Type': contentTypeApplicationJson,
"Content-Type": contentTypeApplicationXFormUrlEncoded,
},
data: defaultGrantType,
}));
})
);
}
async function accessTokenValidator(cli: LhispOauthClient) {
@ -218,7 +239,6 @@ async function accessTokenValidator(cli: LhispOauthClient){
expect(accessToken.access_token).toBe(mockedAccessToken.access_token);
expect(accessToken.expires_in).toBe(mockedAccessToken.expires_in);
expect(accessToken.scope).toBe(mockedAccessToken.scope);
expect(accessToken.created_at).toBeGreaterThanOrEqual(now);
}
function getOauthClient(opt: LhispOauthClientConstructorParams = baseClientParams) {
@ -230,4 +250,4 @@ const mockedAccessToken = {
access_token: "SomeAccessToken",
expires_in: 600,
scope: "cobrancas.boletos-requisicao cobrancas.boletos-info",
}
};

View file

@ -6,8 +6,9 @@ definitions:
caches:
- node
script:
- yarn install
- yarn build
- npm install
- npm run test
- npm run build
artifacts:
- dist/**
pipelines:

4
package-lock.json generated
View file

@ -1,12 +1,12 @@
{
"name": "lhisp-oauth-client",
"version": "1.0.11",
"version": "1.0.13",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "lhisp-oauth-client",
"version": "1.0.11",
"version": "1.0.13",
"license": "MIT",
"dependencies": {
"axios": "^1.4.0",

View file

@ -1,6 +1,6 @@
{
"name": "lhisp-oauth-client",
"version": "1.0.11",
"version": "1.0.13",
"main": "src/index",
"types": "src/index.d.ts",
"repository": "git@bitbucket.org:leandro_costa/lhisp-oauth-client.git",

View file

@ -1,2 +1,2 @@
export * from './lhisp-oauth-client';
export * from './lhisp-oauth-client.t';
export * from "./lhisp-oauth-client";
export * from "./lhisp-oauth-client.t";

View file

@ -23,7 +23,7 @@ export interface LhispOauthClientConstructorParams {
export interface ExecutarRequestParams extends AxiosRequestConfig {
path: string;
contentType?: ContentType,
contentType?: ContentType;
}
export interface AccessToken {
@ -31,15 +31,14 @@ export interface AccessToken {
access_token: string;
expires_in: number;
scope?: string;
created_at?: number;
}
export enum ContentType {
APPLICATION_JSON='application/json',
APPLICATION_X_WWW_FORM_URLENCODED='application/x-www-form-urlencoded',
APPLICATION_JSON = "application/json",
APPLICATION_X_WWW_FORM_URLENCODED = "application/x-www-form-urlencoded",
}
export const defaultGrantType = 'client_credentials';
export const defaultGrantType = "client_credentials";
export const defaultAuthContentType = ContentType.APPLICATION_X_WWW_FORM_URLENCODED;
export const defaultAuthHeaderName = 'Authorization';
export const defaultTokenHeaderName = 'Authorization';
export const defaultAuthHeaderName = "Authorization";
export const defaultTokenHeaderName = "Authorization";

View file

@ -13,7 +13,7 @@ import {
} from "./lhisp-oauth-client.t";
import logger from "lhisp-logger";
export class LhispOauthClient {
export class LhispOauthClient<iAccessToken extends AccessToken = AccessToken> {
protected authUrl: string;
protected apiUrl: string;
protected clientId: string;
@ -27,8 +27,10 @@ export class LhispOauthClient {
protected headers?: Headers;
protected grantType?: string;
protected agent: https.Agent;
protected accessToken?: AccessToken;
protected refreshToken?: AccessToken;
protected accessToken?: iAccessToken;
protected refreshToken?: iAccessToken;
protected tokenCreatedAt = 0;
protected tokenExpiresIn = 0;
protected sendAuthCredentialsOnRequestBody?: boolean;
constructor(params: LhispOauthClientConstructorParams) {
@ -75,16 +77,16 @@ export class LhispOauthClient {
}
}
isTokenValid(token: AccessToken) {
if (!token) return false;
if (!token.created_at) return false;
const timeDiff = (Date.now() - token.created_at) / 1000;
return timeDiff < token.expires_in - 10;
isTokenValid() {
if (!this.accessToken) return false;
if (!this.tokenCreatedAt) return false;
const timeDiff = (Date.now() - this.tokenCreatedAt) / 1000;
return timeDiff < this.tokenExpiresIn - 10;
}
async getAccessToken(): Promise<AccessToken> {
async getAccessToken(): Promise<iAccessToken> {
try {
if (this.accessToken && this.isTokenValid(this.accessToken)) {
if (this.accessToken && this.isTokenValid()) {
return this.accessToken;
}
@ -113,21 +115,19 @@ export class LhispOauthClient {
data: authRequestOpt.data,
contentType: this.authContentType,
});
const response = await axios.request(authRequestOpt);
return this.buildAccessToken(response.data);
const resp = await axios.request(authRequestOpt);
this.accessToken = this.buildAccessToken(resp.data);
this.tokenCreatedAt = new Date().getTime();
this.tokenExpiresIn = this.accessToken?.expires_in || this.tokenCreatedAt + 60000;
return this.accessToken;
} catch (error) {
logger.error({ message: "LhispOauthClient.getAccessToken", error });
throw error;
}
}
buildAccessToken(data: Omit<AccessToken, "created_at">): AccessToken {
this.accessToken = {
...data,
created_at: Date.now(),
};
return this.accessToken;
buildAccessToken(data: any) {
return data as iAccessToken;
}
getAuthToken() {
@ -143,14 +143,10 @@ export class LhispOauthClient {
}: ExecutarRequestParams): Promise<ResponseType> {
try {
await this.getAccessToken();
if (!this.accessToken?.token_type) {
console.log("## LHOAUTH2 NO TOKEN ?:", this.accessToken);
}
let headers = {
"Content-Type": contentType,
[this.tokenHeaderName]: this.getAuthToken(),
// ...this.headers
};
const response = await axios.request<ResponseType>({

2381
yarn.lock

File diff suppressed because it is too large Load diff