Merge branch 'main' of git.savefamilygps.net:SaveFamily/sf-sim

This commit is contained in:
2026-02-10 16:11:59 +01:00
15 changed files with 100 additions and 52 deletions

View File

@@ -11,7 +11,7 @@ post {
}
body:form-urlencoded {
iccid: 8933201125065160380
iccid: 8933201125065160406
offer: SAVEFAMILY1
}

View File

@@ -7,7 +7,7 @@
"scripts": {
"test": "vitest watch",
"build": "yarn workspaces foreach -A --exclude sim-consumidor-nos run build && cp .env dist/ && yarn setup:runtime",
"setup:runtime": "mkdir -p dist/packages/node_modules && ln -sf ../sim-shared dist/packages/node_modules/sim-shared && ln -sf ../sim-consumidor-objenious dist/packages/node_modules/sim-consumidor-objenious && ln -sf ../sim-entrada-eventos dist/packages/node_modules/sim-entrada-eventos && ln -sf ../sim-objenious-cron dist/packages/node_modules/sim-objenious-cron",
"setup:runtime": "mkdir -p dist/packages/node_modules && ln -sf ../sim-shared dist/packages/node_modules/sim-shared && ln -sf ../sf-consumidor-objenious dist/packages/node_modules/sim-consumidor-objenious",
"start": "yarn setup:runtime && yarn workspaces foreach -Apiv --exclude sim-consumidor-nos run start",
"typecheck": "npx tsc --noEmit",
"dev": "yarn workspaces foreach -Apiv --exclude sim-consumidor-nos run dev ",

View File

@@ -1,5 +1,3 @@
// PEM ?
/**
* TODO:
* Está demasiado acoplado a objenious, hay que sacar un servicio jwt general para
@@ -10,7 +8,9 @@ import { env } from "#config/env/index.js";
import fs from "fs"
import {
JWTToken
JWTToken,
JWTHeader,
IJWTService
} from "sim-shared/domain/JWT.js"
import axios, { AxiosError } from "axios";
@@ -32,15 +32,6 @@ type TokensRequestResponse = {
"scope": string
}
type AuthHeaders = {
content_type: string,
sub: string,
iss: string,
aud: string,
jti: string,
iat: number,
exp: number,
}
const PRIVATE_KEY_PATH = env.OBJ_PEM_PATH
@@ -64,7 +55,7 @@ const DEFAULT_HEADERS = {
}
function addIATHeaders(authHeaders: Object) {
const headers = <AuthHeaders>{
const headers = <JWTHeader>{
...authHeaders,
sub: env.OBJ_CLIENT_ID,
iss: env.OBJ_CLIENT_ID,
@@ -76,6 +67,7 @@ function addIATHeaders(authHeaders: Object) {
return headers
}
export type ObjeniousTokenBody = any
/**
* El servicio gestiona un par de tokens auth - refresh para las
@@ -85,10 +77,10 @@ function addIATHeaders(authHeaders: Object) {
* Debe tener un cliente HTTP propio para que no le afecten los
* interceptores, sino puede haber bucles de refresco de token
*/
export class JWTService {
export class JWTService implements IJWTService<ObjeniousTokenBody> {
public isRefreshing: boolean = false;
public authToken: JWTToken<{}> | undefined
private refreshToken?: JWTToken<{}>
public authToken: JWTToken<ObjeniousTokenBody> | undefined;
private refreshToken?: JWTToken<ObjeniousTokenBody> | undefined;
constructor(args?: {
token?: string // si se partiese de un token existente,
@@ -122,7 +114,7 @@ export class JWTService {
return token
}
public async getNewTokens() {
public async getNewAuthToken() {
const bodyWithtoken = {
...DEFAULT_BODY,
client_assertion: this.buildJwtBody()
@@ -139,8 +131,8 @@ export class JWTService {
let res;
try {
res = (await req).data as TokensRequestResponse;
this.authToken = new JWTToken(res.access_token)
this.refreshToken = new JWTToken(res.refresh_token)
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"
@@ -163,7 +155,7 @@ export class JWTService {
}
// Caso 3: Ningún token es valido
await this.getNewTokens()
await this.getNewAuthToken()
if (this.authToken == undefined) throw new Error("Error obteniendo tokens de auth")
@@ -190,8 +182,8 @@ export class JWTService {
let res;
try {
res = (await req).data as TokensRequestResponse;
this.authToken = new JWTToken(res.access_token)
this.refreshToken = new JWTToken(res.refresh_token)
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"

View File

@@ -2,7 +2,7 @@ import { ActionData, ActivationData } from "#domain/DTOs/objeniousapi.js"
import { HttpClient } from "sim-shared/infrastructure/HTTPClient.js"
import { AxiosError } from "axios"
import { Result } from "sim-shared/domain/Result.js"
import { ObjeniousOperation, IOperationsRepository as OperationsRepositoryPort } from "#domain/operationsRepository.port.js"
import { ObjeniousOperation, IOperationsRepository as OperationsRepositoryPort } from "sim-shared/domain/operationsRepository.port.js"
// TODO:
// - Pasar a un archivo de DTOs

View File

@@ -1,5 +1,5 @@
import { OperationsRepository } from "#adapters/OperationRepository.js"
import { OperationsRepository } from "sim-shared/infrastructure/OperationRepository.js"
import { startRMQClient } from "#config/eventBus.config.js"
import { httpInstance } from "#config/httpClient.config.js"
import { pgPool } from "#config/postgreConfig.js"

View File

@@ -68,6 +68,7 @@
"cors": "*",
"dotenv": "*",
"express": "*",
"sim-consumidor-objenious": "sim-consumidor-objenious:*",
"sim-shared": "sim-shared:*",
"typescript": "*"
},

View File

@@ -1,6 +1,6 @@
import { HttpClient } from "sim-shared/infrastructure/HTTPClient.js"
import { JWTService } from "sim-consumidor-objenious/aplication/JWT.service.js"
import { env } from "./env/index.js"
import { JWTService } from "packages/sim-consumidor-objenious/aplication/JWT.service.js"
const OBJ_BASE_URL = env.OBJ_BASE_URL

View File

@@ -1,9 +1,9 @@
import { pgPool } from "./config/postgreConfig.js"
import { PgClient } from "sim-shared/infrastructure/PgClient.js"
import { OperationsRepository } from "../sim-consumidor-objenious/infrastructure/OperationRepository.js"
import { httpInstance } from "./config/httpClient.config.js"
import { CheckObjeniousRequests } from "./tasks/check_objenious_request.js"
import { OperationsRepository } from "sim-shared/infrastructure/OperationRepository.js"
async function startCron() {
const commonSettings = {

View File

@@ -1,4 +1,4 @@
import { IOperationsRepository, Objenious, ObjeniousOperation, ObjeniousOperationChange, StatusEnum } from "sim-consumidor-objenious/domain/operationsRepository.port.js"
import { IOperationsRepository, Objenious, ObjeniousOperation, ObjeniousOperationChange, StatusEnum } from "sim-shared/domain/operationsRepository.port.js";
import { HttpClient } from "sim-shared/infrastructure/HTTPClient.js";
export class CheckObjeniousRequests {

View File

@@ -1,11 +1,45 @@
/**
* Herramientas para gestionar todos los trabajos con JWT, los tipos
* definidos aquí deben de ser sufcientemente generales para usarse
* en todos los proyectos.
*
* Las cabeceras de un token y la firma son standard para todos, pero
* el body puede variar con contenido nuevo.
*/
import { sign } from "node:crypto"
export type JWTHeader = {
alg: string,
typ: string,
kid: string
export interface IJWTService<T> {
/* Obtener un token de auth sin comprobar nada */
getNewAuthToken: () => Promise<JWTToken<T>>,
/* Obtener un token valido -> sino refrescar -> sin pillar un token nuevo */
getAccessToken: () => Promise<JWTToken<T>>,
/* Intenta refrescar el token actual */
tryRefreshToken: () => Promise<JWTToken<T>>,
}
export type JWTHeader = {
/* algoritmo de firma */
alg: string,
/* tipo de token */
typ: string,
/* key ID */
kid?: string
/* content type */
cty?: string
/**/
iss?: string,
sub?: string,
aud?: string,
jti?: string,
iat?: number,
exp?: number,
}
/*
* Define los campos basicos del payload de un token, en <T> se
* añaden los campos espeficos de la comunicacion
* */
export type JWTPayload<T> = {
/** (Issuer) Quién emitió el token */
iss?: string;
@@ -45,29 +79,33 @@ export type JWT<T> = {
}
// todo pasar a la clase JWT
function signJWT(args: {
algorythm: "sha256" | string,
data: string,
privateKey: string
}) {
function signJWT(args: SignatureOptions) {
const signature = sign(
args.algorythm,
Buffer.from(args.data),
args.privateKey
)
return signature
return signature.toString("base64url")
}
export type SignatureOptions = {
algorythm: "sha256" | string,
data: string,
privateKey: string,
}
export class JWTToken<T> {
public rawToken: string
private decodedPayload: JWTPayload<T> | undefined
private signatureFunc = signJWT
constructor(
token: string
token: string,
externalSignatureFunc?: (args: SignatureOptions) => string
) {
this.rawToken = token
this.decodedPayload = this.decodePayload()
if (externalSignatureFunc != undefined) this.signatureFunc = externalSignatureFunc
}
public static fromParts<T>(args: {
@@ -76,10 +114,13 @@ export class JWTToken<T> {
sigantureData?: {
privateKey: string,
algorythm: string
}
},
signatureFunc?: (args: SignatureOptions) => string
}) {
const strHeader = JSON.stringify(args.header)
const base64Header = Buffer.from(strHeader).toString("base64url")
const signatureFunc = args.signatureFunc ?? signJWT
let token = base64Header
if (args.payload != undefined) {
@@ -89,11 +130,11 @@ export class JWTToken<T> {
}
if (args.sigantureData != undefined) {
const base64signature = signJWT({
const base64signature = signatureFunc({
algorythm: args.sigantureData.algorythm,
privateKey: args.sigantureData.privateKey,
data: token
}).toString("base64url")
data: token,
})
token += ("." + base64signature)
}
@@ -116,4 +157,14 @@ export class JWTToken<T> {
if (expirationDate * 1000 <= now.getTime()) return true
return false
}
public isValid() {
throw new Error("No implementado")
}
public verifySignature() {
throw new Error("No implementado")
}
}

View File

@@ -1,5 +1,7 @@
import axios, { AxiosInstance } from "axios"
import { JWTToken } from "../domain/JWT.js"
import { IJWTService, JWTToken } from "../domain/JWT.js"
// Cambiar por IJWRGeneralService
export type JWTProvider<T> = {
/** El servidor está solicitando un token nuevo o refrescando el actual*/
@@ -13,11 +15,13 @@ export class HttpClient {
public client: AxiosInstance
private jwtManager: JWTProvider<{}>
private jwtService: IJWTService<any> | undefined;
constructor(args: {
baseURL: string,
headers: Object,
jwtManager: JWTProvider<{}> // todo: asociar el tipo de token
jwtManager: JWTProvider<{}> // todo: asociar el tipo de token,
jwtService?: IJWTService<any>
}) {
this.client = axios.create({
...args
@@ -25,6 +29,7 @@ export class HttpClient {
this.jwtManager = args.jwtManager
if (args.jwtService != undefined) this.jwtService = args.jwtService
this.client.interceptors.request.use(
async (config) => {

View File

@@ -1,9 +1,7 @@
import { IOperationsRepository, ObjeniousOperation, ObjeniousOperationChange } from "#domain/operationsRepository.port.js";
import { IOperationsRepository, ObjeniousOperation, ObjeniousOperationChange } from "sim-shared/domain/operationsRepository.port.js";
import { Result } from "sim-shared/domain/Result.js";
import { PgClient } from "sim-shared/infrastructure/PgClient.js";
export class OperationsRepository implements IOperationsRepository {
constructor(

View File

@@ -9,6 +9,6 @@
],
"include": [
"**/*.ts",
"../../packages/sim-shared/**/*.ts"
"../../packages/sim-shared/**/*.ts",
]
}

View File

@@ -2769,7 +2769,7 @@ __metadata:
languageName: unknown
linkType: soft
"sim-consumidor-objenious@workspace:packages/sim-consumidor-objenious":
"sim-consumidor-objenious@sim-consumidor-objenious:*, sim-consumidor-objenious@workspace:packages/sim-consumidor-objenious":
version: 0.0.0-use.local
resolution: "sim-consumidor-objenious@workspace:packages/sim-consumidor-objenious"
dependencies:
@@ -2784,6 +2784,7 @@ __metadata:
dotenv: "npm:*"
express: "npm:*"
prettier: "npm:*"
sim-consumidor-objenious: "sim-consumidor-objenious:*"
sim-shared: "sim-shared:*"
supertest: "npm:*"
tsc-alias: "npm:^1.8.16"