/** * TODO: * Está demasiado acoplado a objenious, hay que sacar un servicio jwt general para * el cliente HTTP */ import fs from "fs" import { JWTToken, JWTHeader, IJWTService, JWTPayload } from "sim-shared/domain/JWT.js" import axios, { AxiosError } from "axios"; export type GrantAccessRequestBody = { grant_type: string, client_id: string, client_assertion_type: string, client_assertion: string } export 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 } export type ObjeniousTokenBody = any /** * 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 implements IJWTService { public isRefreshing: boolean = false; public authToken: JWTToken | undefined; private refreshToken?: JWTToken | undefined; // http private transformHeaders?: (_: Object) => JWTHeader; private defaultHttpHeaders: Record; private defaultBody: Record; // jwt private defaultJWTHeaders: JWTHeader; private defaultJWTPayload: JWTPayload; private privateKeyPath: string; private tokenUrl: string; private refreshTokenUrl: string; constructor(args: { token?: string // si se partiese de un token existente, refreshToken?: string, transformJWTHeaders?: (_: Object) => JWTHeader, defaultHeaders: Record, defaultBody: Record, defaultJWTHeaders: JWTHeader, defaultJWTPayload: JWTPayload, privateKeyPath: string, tokenUrl: string, refreshTokenUrl: string }) { if (args?.token != undefined) this.authToken = new JWTToken(args.token) if (args?.refreshToken != undefined) this.refreshToken = new JWTToken(args.refreshToken) if (args?.transformJWTHeaders != undefined) this.transformHeaders = args.transformJWTHeaders this.defaultHttpHeaders = args.defaultHeaders; this.defaultBody = args.defaultBody; this.defaultJWTHeaders = args.defaultJWTHeaders; this.defaultJWTPayload = args.defaultJWTPayload; this.privateKeyPath = args.privateKeyPath; this.tokenUrl = args.tokenUrl; this.refreshTokenUrl = args.refreshTokenUrl; } private buildJwtBody() { const jwtHeaders = this.defaultJWTHeaders const jwtData = (this.transformHeaders) ? this.transformHeaders(this.defaultJWTPayload) : this.defaultJWTPayload; const key = fs.readFileSync(this.privateKeyPath, "utf8") const token = JWTToken.fromParts({ header: jwtHeaders, payload: jwtData, sigantureData: { algorythm: "sha256", privateKey: key } }) return token } public async getNewAuthToken() { const bodyWithtoken = { ...this.defaultBody, client_assertion: this.buildJwtBody() } const headers = (this.transformHeaders) ? this.transformHeaders(this.defaultHttpHeaders) : this.defaultHttpHeaders; const req = axios.post(this.tokenUrl, bodyWithtoken, { headers: 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 getAccessToken() { // Caso 1: El token actual es valido if (this.authToken != undefined && !this.authToken.isExpired()) { return this.authToken } // Caso 2: El token ha expirado pero se puede refrescar if (this.authToken?.isExpired() && this.refreshToken != undefined && !this.refreshToken.isExpired()) { await this.tryRefreshToken() } // Caso 3: Ningún token es valido await this.getNewAuthToken() if (this.authToken == undefined) throw new Error("Error obteniendo tokens de auth") return this.authToken } 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 refreshBody = { ...this.defaultBody, grant_type: "refresh_token", } const body = { ...refreshBody, client_assertion: this.buildJwtBody(), refresh_token: this.refreshToken.rawToken } const req = axios.post(this.refreshTokenUrl, body, { headers: this.defaultHttpHeaders } ) 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) } } }