// PEM ? import { env } from "#config/env"; import fs from "fs" import { JWTToken } from "#shared/domain/JWT" import axios, { AxiosError } from "axios"; import { sign } from "node:crypto" import { ClientRequest } from "node:http"; type GrantAccessRequestBody = { grant_type: string, client_id: string, client_assertion_type: string, client_assertion: string } type TokensRequestResponse = { "access_token": string, "expires_in": number, "refresh_token": string "refresh_expires_in": number, "token_type": "Bearer" | string, "not-before-policy": number, "session_state": string, "scope": string } type AuthHeaders = { content_type: string, sub: string, iss: string, aud: string, jti: string, iat: number, exp: number, } const PRIVATE_KEY_PATH = __dirname + "/../obj.pem" const GET_TOKEN_URL = "https://idp.docapost.io/auth/realms/GETWAY/protocol/openid-connect/token" const REFRESH_TOKEN_URL = GET_TOKEN_URL const DEFAULT_BODY: GrantAccessRequestBody = { grant_type: "client_credentials", client_id: env.OBJ_CLIENT_ID, client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer", client_assertion: env.OBJ_CLI_ASSERTION } const REFRESH_BODY = { ...DEFAULT_BODY, grant_type: "refresh_token", } const DEFAULT_HEADERS = { "content-type": "application/x-www-form-urlencoded" } function addIATHeaders(authHeaders: Object) { const headers = { ...authHeaders, sub: env.OBJ_CLIENT_ID, iss: env.OBJ_CLIENT_ID, aud: GET_TOKEN_URL, jti: Date.now().toString(), iat: Math.floor(Date.now() / 1000), exp: Math.floor(Date.now() / 1000) + 5 * 60, } return headers } /** * El servicio gestiona un par de tokens auth - refresh para las * operaciones de Objenious. * Se puede partir de tokens existentes. * * Debe tener un cliente HTTP propio para que no le afecten los * interceptores, sino puede haber bucles de refresco de token */ export class JWTService { public isRefreshing: boolean = false; public authToken: JWTToken<{}> | undefined private refreshToken?: JWTToken<{}> constructor(args?: { token?: string // si se partiese de un token existente, refreshToken?: string }) { if (args?.token != undefined) this.authToken = new JWTToken(args.token) if (args?.refreshToken != undefined) this.refreshToken = new JWTToken(args.refreshToken) } private buildJwtBody() { const jwtHeaders = { alg: "RS256", typ: "JWT", kid: env.OBJ_KID } const jwtData = addIATHeaders({ sub: env.OBJ_CLIENT_ID, iss: env.OBJ_CLIENT_ID, aud: "https://idp.docapost.io/auth/realms/GETWAY", jti: Date.now().toString(), }) const key = fs.readFileSync(PRIVATE_KEY_PATH, "utf8") const token = JWTToken.fromParts({ header: jwtHeaders, payload: jwtData, sigantureData: { algorythm: "sha256", privateKey: key } }) return token } public async getAccessToken() { if (this.authToken != undefined && !this.authToken.isExpired()) { console.warn("Se está intentado conseguir un token sin expirar el anterior") } const bodyWithtoken = { ...DEFAULT_BODY, client_assertion: this.buildJwtBody() } const req = axios.post(GET_TOKEN_URL, bodyWithtoken, { headers: addIATHeaders(DEFAULT_HEADERS) } ) this.isRefreshing = true; let res; try { res = (await req).data as TokensRequestResponse; this.authToken = new JWTToken(res.access_token) this.refreshToken = new JWTToken(res.refresh_token) return this.authToken } catch (e) { const errorString = "No se ha podido conseguir el token de acceso de OBJENIOUS" console.error(errorString, (e as AxiosError).response?.data) throw new Error(errorString) } finally { this.isRefreshing = false; } } public async tryRefreshToken() { if (this.refreshToken == undefined) throw new Error("El refreshToken no está definido") if (this.refreshToken.isExpired()) throw new Error("El refreshToken ha expirado") const body = { ...REFRESH_BODY, client_assertion: this.buildJwtBody(), refresh_token: this.refreshToken.rawToken } const req = axios.post(REFRESH_TOKEN_URL, body, { headers: DEFAULT_HEADERS } ) let res; try { res = (await req).data as TokensRequestResponse; this.authToken = new JWTToken(res.access_token) this.refreshToken = new JWTToken(res.refresh_token) return this.authToken } catch (e) { const errorString = "No se ha podido conseguir el token de acceso de OBJENIOUS" console.error(errorString, e) throw new Error(errorString) } } }