Refactor de jwt y base de la bdd de pausas-cancelaciones
This commit is contained in:
@@ -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)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user