Refactor de jwt y base de la bdd de pausas-cancelaciones

This commit is contained in:
2026-04-07 13:20:31 +02:00
parent 1b6da651a6
commit 7d88359263
18 changed files with 465 additions and 164 deletions

View File

@@ -1,17 +0,0 @@
import { test, describe } from "vitest"
import { JWTService } from "./JWT.service.js"
describe("Tokens Objenious", () => {
const jwtService = new JWTService()
test("Solicicitud normal de auth", async () => {
const token = await jwtService.getAccessToken()
console.log("acceso objenious", token)
}),
test("Solicicitud de refresh de auth", async () => {
const token = await jwtService.tryRefreshToken()
console.log("acceso refresh objenious", token)
})
})

View File

@@ -1,195 +0,0 @@
/**
* TODO:
* Está demasiado acoplado a objenious, hay que sacar un servicio jwt general para
* el cliente HTTP
*/
import { env } from "#config/env/index.js";
import fs from "fs"
import {
JWTToken,
JWTHeader,
IJWTService
} from "sim-shared/domain/JWT.js"
import axios, { AxiosError } from "axios";
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
}
const PRIVATE_KEY_PATH = env.OBJ_PEM_PATH
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 = <JWTHeader>{
...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
}
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<ObjeniousTokenBody> {
public isRefreshing: boolean = false;
public authToken: JWTToken<ObjeniousTokenBody> | undefined;
private refreshToken?: JWTToken<ObjeniousTokenBody> | undefined;
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 getNewAuthToken() {
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<ObjeniousTokenBody>(res.access_token)
this.refreshToken = new JWTToken<ObjeniousTokenBody>(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 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<ObjeniousTokenBody>(res.access_token)
this.refreshToken = new JWTToken<ObjeniousTokenBody>(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)
}
}
}

View File

@@ -3,7 +3,6 @@ import { ConsumeMessage } from "amqplib";
import { SimUseCases } from "./Sim.usecases.js";
import { SimEvents } from "sim-shared/domain/SimEvents.js";
import { Result } from "sim-shared/domain/Result.js";
import { env } from "#config/env/index.js";
/**
* La clase usa generadores de funciones para mantener el contexto
@@ -157,6 +156,9 @@ export class SimController {
}
}
/**
* Lo mismo que pause
*/
public suspend() {
return async (msg: ConsumeMessage) => {
let msgData;