Debug para tokens de Alai

This commit is contained in:
2026-05-04 13:39:54 +02:00
parent 331d920379
commit 113d9f3786
19 changed files with 272 additions and 45 deletions

View File

@@ -7,6 +7,7 @@ ALAI_API_URL=https://wsaccess.alaisecure.com/bssrest
ALAI_CERTIFICATES_DIR=./certificates/
ALAI_CERTIFICATE_NAME=wsaccess_alaisecure_com_cert_client_new.p12
ALAI_CERTIFICATE_PASSWORD=iHaaek+zyzWz6cH6rg==
ALAI_USERNAME=palomaibanez
ALAI_PASSWORD=palomaibanez1234
ALAI_BRANDID=savefamily

View File

@@ -15,6 +15,8 @@ export class AlaiTokenManager implements JWTProvider<{}> {
if (res.error != undefined) {
console.error("Error obteniendo el token de ALAI", res.error)
} else {
this.authToken = new JWTToken(res.data.accessToken)
}
}

View File

@@ -0,0 +1,50 @@
import { JWTToken } from "sim-shared/domain/JWT.js";
import { JWTProvider } from "sim-shared/infrastructure/HTTPClient.js";
import { LegacyJWTTokenRepository } from "#infrastructure/LegacyJWTTokensRepository.js";
import { env } from "#config/env/env.js";
const tokenDir = String(env.ALAI_CERTIFICATES_DIR)
const tokenFile = ".debugToken"
/**
* Usa un token guardado a mano en archivo para no gastar tokens de Alai
*/
export class DebugTokenManager implements JWTProvider<{}> {
isRefreshing: boolean = false;
authToken: JWTToken<{}> | undefined;
private async getNewAuthToken() {
// TODO: Si no funcionase hay que reprogramar los mensajes para ser
// consumidos mas tarde.
const res = LegacyJWTTokenRepository.getTokenFromFile(tokenDir, tokenFile)
if (res.error != undefined) {
console.error("Error obteniendo el token de ALAI", res.error)
} else {
this.authToken = new JWTToken(res.data)
console.log("[d] Token DEBUG: ", this.authToken)
}
}
public tryRefreshToken(): Promise<JWTToken<{}>> {
// En Alai no existe el concepto de refresh, se solicita otro token nuevo
return this.getAccessToken()
};
public async getAccessToken(): Promise<JWTToken<{}>> {
// Caso 1: El token actual es valido
if (this.authToken != undefined && !this.authToken.isExpired()) {
return this.authToken
} else {
// Caso 2: El token actual no existe o ha expirado
await this.getNewAuthToken()
}
// Si después de todo no se ha generado el token es un error catastrofico
if (this.authToken == undefined) throw new Error("Error obteniendo tokens de auth")
return this.authToken
};
}

View File

@@ -74,7 +74,6 @@ export class SimAlaiController {
iccid: iccid,
correlation_id: correlation_id
}))
return res;
}
}
@@ -99,6 +98,7 @@ export class SimAlaiController {
}
}
/*
public reActivate() {
return async (msg: ConsumeMessage) => {
console.log("Evento reActivate ", msg.fields)
@@ -113,6 +113,7 @@ export class SimAlaiController {
return res;
}
}
*/
/**
* Select especificamente por REST para evitar pasar por las colas.
@@ -136,7 +137,7 @@ export class SimAlaiController {
// TODO: Automatizar la paginacion
//const usecaseRes = this.uscases.selectMany({ iccid })
} else {
const usecaseRes = await this.uscases.selectOne({ iccid })
const usecaseRes = await this.uscases.selectOne(iccid)
if (usecaseRes.error != undefined) {
res.status(500).json(usecaseRes)
return;
@@ -150,28 +151,29 @@ export class SimAlaiController {
}
}
public selectPageREST() {
return async (req: Request, res: Response) => {
const { offset, limit, filter, orderBy } = req.query
const params = {
offset: (offset != undefined) ? Number(offset) : undefined,
limit: (limit != undefined) ? Number(limit) : undefined,
filter: (filter != undefined) ? String(filter) : undefined,
orderBy: (orderBy != undefined) ? String(orderBy) : undefined
}
const usecaseRes = await this.uscases.selectPage(params)
if (usecaseRes.error != undefined) {
res.status(500).json(usecaseRes)
return;
} else {
res.status(200).send(usecaseRes.data)
return;
/**
public selectPageREST() {
return async (req: Request, res: Response) => {
const { offset, limit, filter, orderBy } = req.query
const params = {
offset: (offset != undefined) ? Number(offset) : undefined,
limit: (limit != undefined) ? Number(limit) : undefined,
filter: (filter != undefined) ? String(filter) : undefined,
orderBy: (orderBy != undefined) ? String(orderBy) : undefined
}
const usecaseRes = await this.uscases.selectPage(params)
if (usecaseRes.error != undefined) {
res.status(500).json(usecaseRes)
return;
} else {
res.status(200).send(usecaseRes.data)
return;
}
}
}
}
**/
}

View File

@@ -14,15 +14,15 @@ type FuncType = ((m: ConsumeMessage) => Promise<Result<string, any>>)
export class SimAlaiRouter {
private readonly routes: Map<string, FuncType>;
// WIP
constructor(
private readonly simController: SimAlaiController,
private readonly eventBus: EventBus
) {
this.routes = new Map<string, FuncType>([
//["select", undefined],
["activate", this.simController.activate()],
["pause", this.simController.suspend()],
["reactivate", this.simController.reActivate()],
//["reactivate", this.simController.reActivate()],
//["cancel", this.simController.terminate()],
//["preActivate", this.simController.preActivate()]
]);

View File

@@ -59,6 +59,10 @@ export class SimAlaiUsecases {
return order
}
/**
* Gestiona el ciclo de vida de una petición. No aplica
* a peticiones de lectura (no pasan por la cola y no generan un order)
*/
public usecaseTemplate<T, R>(
func: (_: T) => Promise<Result<string, R>>,
args: T,
@@ -70,6 +74,8 @@ export class SimAlaiUsecases {
this.setRunning(correlation_id)
.then()
.catch(e => console.error("Error actualizando el order", e))
else
console.warn("[!] Se ha lanzado una caso de uso sin correlation_id")
try {
const res = await func(args)
@@ -129,4 +135,9 @@ export class SimAlaiUsecases {
return reserved
}, args.iccid, args.correlation_id)
}
public async selectOne(iccid: string) {
const sim = await this.alaiRepository.getSimByICCID(iccid)
return sim
}
}

View File

@@ -13,6 +13,10 @@ export type SSLCert = {
keypem: string
}
/**
* TODO:
* - Se ha usado https.Agent en su lugar, eliminar si no se usa
*/
export class SSLCertificateLoader {
constructor(

View File

@@ -0,0 +1 @@
eyJhbGciOiJIUzM4NCJ9.eyJiciI6InNhdmVmYW1pbHkiLCJpcCI6IjUyLjIxNC4xMTIuMTgxIiwic3ViIjoiaW5mb3NhdmVmYW1pbHkiLCJzIjoiRVdTMTY0NjI4YjE0MmRlZWI3IiwicG9zIjoic2F2ZWZhbWlseUNhYyIsImlkV3NVc2VyIjoiODkiLCJpc012bmEiOmZhbHNlLCJkb21haW4iOiJBbGFpfHNhdmVmYW1pbHkiLCJpYXQiOjE3Nzc4OTA0ODEsImV4cCI6MTc3NzkwMTI4MX0.SNO14ONoayy7MEnauSsT7H4To7bbW_GYTq1ZvC2IdcdHZq8oOLlVPAJyu3uMXHRk

View File

@@ -38,6 +38,8 @@ export const env = {
// ESPECIFICO ALAI
ALAI_API_URL: process.env.ALAI_API_URL,
ALAI_CERTIFICATES_DIR: process.env.ALAI_CERTIFICATES_DIR,
ALAI_CERTIFICATE_NAME: process.env.ALAI_CERTIFICATE_NAME,
ALAI_CERTIFICATE_PASSWORD: process.env.ALAI_CERTIFICATE_PASSWORD,
ALAI_USERNAME: process.env.ALAI_USERNAME,
ALAI_PASSWORD: process.env.ALAI_PASSWORD,
ALAI_BRANDID: process.env.ALAI_BRANDID,
@@ -52,3 +54,7 @@ assert.ok(env.ALAI_USERNAME != undefined, "ALAI_USERNAME no definido")
assert.ok(env.ALAI_PASSWORD != undefined, "ALAI_PASSWORD no definido")
assert.ok(env.ALAI_BRANDID != undefined, "ALAI_BRANDID no definido")
assert.ok(env.ALAI_API_URL != undefined, "ALAI_API_URL no definido")
assert.ok(env.ALAI_CERTIFICATE_NAME != undefined, "ALAI_CERTIFICATE_NAME no definido")
assert.ok(env.ALAI_CERTIFICATES_DIR != undefined, "ALAI_CERTIFICATES_DIR no definido")
assert.ok(env.ALAI_CERTIFICATE_PASSWORD != undefined, "ALAI_CERTIFICATE_PASSWORD no definido")

View File

@@ -1,11 +1,17 @@
import { HttpClient } from "sim-shared/infrastructure/HTTPClient.js"
import { AlaiTokenManager } from "#aplication/AlaiTokenManager.js"
import { env } from "#config/env/env.js";
import { httpsAgent } from "./httpsAgent.js"
import { DebugTokenManager } from "#aplication/DebugTokenManager.js";
const tokenManager = new AlaiTokenManager()
const debugTokenManagr = new DebugTokenManager()
console.error("USANDO DebugTokenManager! Eliminar en prod")
export const alaiHttp = new HttpClient({
baseURL: env.ALAI_API_URL as string,
headers: {},
jwtManager: tokenManager
jwtManager: debugTokenManagr,
httpsAgent: httpsAgent
})

View File

@@ -1,19 +1,14 @@
import fs from 'fs';
import https from 'https';
import axios from 'axios';
import { env } from './env/env.js';
import path from 'path';
const certificatesDir = env.ALAI_CERTIFICATES_DIR
const certificateName = env.ALAI_CERTIFICATE_NAME
// ...
const httpsAgent = new https.Agent({
pfx: fs.readFileSync(path.join(certificatesDir, 'keystore.p12')),
passphrase: '<your_keystore_passphrase_here>'
const certificatesDir = String(env.ALAI_CERTIFICATES_DIR)
const certificateName = String(env.ALAI_CERTIFICATE_NAME)
const certificatePassword = String(env.ALAI_CERTIFICATE_PASSWORD)
export const httpsAgent = new https.Agent({
pfx: fs.readFileSync(path.join(certificatesDir, certificateName)),
passphrase: certificatePassword
});
// TODO: EJEMPLO, METER EN EL HTTP CLIENT
const result = await axios.get('https://myserver.internal.net:9443', { httpsAgent });
// do something with the result
// ...

View File

@@ -52,11 +52,11 @@ async function startWorker() {
// WIP
app.get("/select", alaiController.selectREST())
app.get("/selectPage", alaiController.selectPageREST())
//app.get("/selectPage", alaiController.selectPageREST())
app.listen(PORT, HOSTNAME, (e) => {
if (e == undefined) {
console.log("[o] Servidor iniciado en el puerto %d", PORT)
console.log("[o] Servidor (Alai) iniciado en el puerto %d", PORT)
} else {
console.error("Error express ", e)
}
@@ -66,10 +66,10 @@ async function startWorker() {
startWorker()
.then(e => {
console.log("[o] Worker de SIM de NOS iniciado")
console.log("[o] Worker de SIM de Alai iniciado")
})
.catch(e => {
console.log("[x] Error iniciando worker de SIM de NOS")
console.log("[x] Error iniciando worker de SIM de Alai")
})
export default {}

View File

@@ -108,7 +108,7 @@ export class AlaiRepository {
public async getSimByICCID(iccid: string) {
const endpoint = `/v1/sim/${iccid}`
const promReq = this.httpClient.post<AlaiAPI.Sim>(endpoint, undefined)
const promReq = this.httpClient.post<AlaiAPI.Sim | undefined>(endpoint, undefined)
const res = await this.manageRequest(promReq)
return res
}

View File

@@ -0,0 +1,65 @@
import path from "path";
import fs from 'fs';
import { Result } from "sim-shared/domain/Result.js";
import { PgClient } from "sim-shared/infrastructure/PgClient.js";
type TokenLine = {
id: number,
url: string,
user_name: string,
pass: string,
brand_id: string,
cert_file: string,
key_file: string,
ca_file: string,
p12_file: string,
cert_password: string,
token: string
}
/**
* Repositorio para usar los tokens guardados en la bdd de intranet o en un archivo
*/
export class LegacyJWTTokenRepository {
constructor(
// En prod (no deberia usarse) tiene que apuntar a intranet
private pgClient: PgClient
) {
}
public async getTokenFromDB(): Promise<Result<string, string>> {
const query = "SELECT * FROM alai_api_credentials;"
try {
const res = await this.pgClient.query<TokenLine>(query)
if (res.rowCount == 0) {
return {
error: "Error recuperando el token actual"
}
} else {
return {
data: res.rows[0].token
}
}
} catch (e) {
return {
error: String(e)
}
}
}
public static getTokenFromFile(dir: string, file: string): Result<string, string> {
try {
const tokenPath = path.join(dir, file)
const fileContent = fs.readFileSync(tokenPath).toString()
return {
data: fileContent
}
} catch (e) {
return {
error: "[d!] No se ha podido leer el archivo del token de debug" + String(e)
}
}
}
}

View File

@@ -1,5 +1,6 @@
import axios, { AxiosInstance } from "axios"
import { IJWTService, JWTToken } from "../domain/JWT.js"
import https from "https"
// Cambiar por IJWRGeneralService
@@ -20,11 +21,14 @@ export class HttpClient {
constructor(args: {
baseURL: string,
headers: Record<string, string>,
jwtManager: JWTProvider<{}> // todo: asociar el tipo de token,
jwtService?: IJWTService<any>
jwtManager: JWTProvider<{}>, // todo: asociar el tipo de token
jwtService?: IJWTService<any>,
httpsAgent: https.Agent
}) {
this.client = axios.create({
...args
baseURL: args.baseURL,
headers: args.headers,
httpsAgent: args.httpsAgent
})
this.jwtManager = args.jwtManager