import qs from "querystring"; import https from "https"; import axios, { AxiosHeaders, AxiosRequestConfig } from "axios"; import { AccessToken, ContentType, defaultAuthContentType, defaultAuthHeaderName, defaultGrantType, defaultTokenHeaderName, ExecutarRequestParams, LhispOauthClientConstructorParams, } from "./lhisp-oauth-client.t"; import logger from "lhisp-logger"; export class LhispOauthClient { protected authUrl: string; protected apiUrl: string; protected clientId: string; protected clientSecret: string; protected authHeaderName: string; protected tokenHeaderName: string; protected authContentType: ContentType; protected certificado?: string | Buffer; protected senhaCertificado?: string; protected authScope?: string; protected headers?: AxiosHeaders; protected grantType?: string; protected agent: https.Agent; protected accessToken?: iAccessToken; protected refreshToken?: iAccessToken; protected tokenCreatedAt = 0; protected tokenExpiresIn = 0; protected sendAuthCredentialsOnRequestBody?: boolean; protected formatAccessToken?: (accessToken?: iAccessToken) => string; constructor(params: LhispOauthClientConstructorParams) { if (params.certificado) { this.agent = new https.Agent({ pfx: Buffer.isBuffer(params.certificado) ? params.certificado : Buffer.from(params.certificado, "base64"), passphrase: params.senhaCertificado, rejectUnauthorized: false, }); } else { this.agent = new https.Agent({ rejectUnauthorized: false, }); } this.certificado = params.certificado; this.headers = (params.headers ? params.headers : {}) as any as AxiosHeaders; this.apiUrl = params.apiUrl; this.authUrl = params.authUrl; this.authScope = params.authScope; this.grantType = params.grantType || defaultGrantType; this.authContentType = params.authContentType || defaultAuthContentType; this.clientId = params.clientId; this.clientSecret = params.clientSecret; this.authHeaderName = params.authHeaderName || defaultAuthHeaderName; this.tokenHeaderName = params.tokenHeaderName || defaultTokenHeaderName; this.sendAuthCredentialsOnRequestBody = params.sendAuthCredentialsOnRequestBody; this.formatAccessToken = params.formatAccessToken; } getAuthHeaderValue(): string { return `Basic ${Buffer.from(`${this.clientId}:${this.clientSecret}`).toString("base64")}`; } parseData({ data, contentType = ContentType.APPLICATION_JSON }: { data: any; contentType: string }) { if (!data || Object.keys(data).length === 0) return undefined; switch (contentType) { case ContentType.APPLICATION_JSON: return JSON.stringify(data); case ContentType.APPLICATION_X_WWW_FORM_URLENCODED: return qs.stringify(data); default: throw new Error(`Content Type Inválido: [${contentType}]`); } } 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 { try { if (this.accessToken && this.isTokenValid()) { return this.accessToken; } // TODO: Implementar Refresh Token. let authRequestOpt: AxiosRequestConfig = { method: "POST", url: this.authUrl, httpsAgent: this.agent, headers: { [this.authHeaderName]: this.getAuthHeaderValue(), "Content-Type": this.authContentType, }, data: {}, }; if (this.grantType) authRequestOpt.data.grant_type = this.grantType; if (this.authScope) authRequestOpt.data.scope = this.authScope; if (this.sendAuthCredentialsOnRequestBody) { if (this.clientId) authRequestOpt.data.client_id = this.clientId; if (this.clientSecret) authRequestOpt.data.client_secret = this.clientSecret; } authRequestOpt.data = this.parseData({ data: authRequestOpt.data, contentType: this.authContentType, }); 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: any) { return data as iAccessToken; } getAuthToken() { if (this.formatAccessToken) { return this.formatAccessToken(this.accessToken); } return `${this.accessToken?.token_type} ${this.accessToken?.access_token}`; } async executarRequest({ method, path, data, params, contentType = ContentType.APPLICATION_JSON, }: ExecutarRequestParams): Promise { try { await this.getAccessToken(); const headers = { "Content-Type": contentType, [this.tokenHeaderName]: this.getAuthToken(), ...(this.headers || {}), }; const response = await axios.request({ method, url: `${this.apiUrl}${path}`, httpsAgent: this.agent, headers, data, params, }); return response.data; } catch (error) { logger.error({ message: "LhispOauthClient.executarRequest", method, url: `${this.apiUrl}${path}`, data, params, contentType, error, }); throw error; } } async get(opt: ExecutarRequestParams) { return this.executarRequest({ method: "GET", contentType: opt.contentType || ContentType.APPLICATION_JSON, ...opt, }); } async put(opt: ExecutarRequestParams) { return this.executarRequest({ method: "PUT", contentType: opt.contentType || ContentType.APPLICATION_JSON, ...opt, }); } async patch(opt: ExecutarRequestParams) { return this.executarRequest({ method: "PATCH", contentType: opt.contentType || ContentType.APPLICATION_JSON, ...opt, }); } async post(opt: ExecutarRequestParams) { return this.executarRequest({ method: "POST", contentType: opt.contentType || ContentType.APPLICATION_JSON, ...opt, }); } async delete(opt: ExecutarRequestParams) { return this.executarRequest({ method: "DELETE", contentType: opt.contentType || ContentType.APPLICATION_JSON, ...opt, }); } }