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 { body:form-urlencoded {
iccid: 8933201125065160380 iccid: 8933201125065160406
offer: SAVEFAMILY1 offer: SAVEFAMILY1
} }

View File

@@ -7,7 +7,7 @@
"scripts": { "scripts": {
"test": "vitest watch", "test": "vitest watch",
"build": "yarn workspaces foreach -A --exclude sim-consumidor-nos run build && cp .env dist/ && yarn setup:runtime", "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", "start": "yarn setup:runtime && yarn workspaces foreach -Apiv --exclude sim-consumidor-nos run start",
"typecheck": "npx tsc --noEmit", "typecheck": "npx tsc --noEmit",
"dev": "yarn workspaces foreach -Apiv --exclude sim-consumidor-nos run dev ", "dev": "yarn workspaces foreach -Apiv --exclude sim-consumidor-nos run dev ",

View File

@@ -1,5 +1,3 @@
// PEM ?
/** /**
* TODO: * TODO:
* Está demasiado acoplado a objenious, hay que sacar un servicio jwt general para * 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 fs from "fs"
import { import {
JWTToken JWTToken,
JWTHeader,
IJWTService
} from "sim-shared/domain/JWT.js" } from "sim-shared/domain/JWT.js"
import axios, { AxiosError } from "axios"; import axios, { AxiosError } from "axios";
@@ -32,15 +32,6 @@ type TokensRequestResponse = {
"scope": string "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 const PRIVATE_KEY_PATH = env.OBJ_PEM_PATH
@@ -64,7 +55,7 @@ const DEFAULT_HEADERS = {
} }
function addIATHeaders(authHeaders: Object) { function addIATHeaders(authHeaders: Object) {
const headers = <AuthHeaders>{ const headers = <JWTHeader>{
...authHeaders, ...authHeaders,
sub: env.OBJ_CLIENT_ID, sub: env.OBJ_CLIENT_ID,
iss: env.OBJ_CLIENT_ID, iss: env.OBJ_CLIENT_ID,
@@ -76,6 +67,7 @@ function addIATHeaders(authHeaders: Object) {
return headers return headers
} }
export type ObjeniousTokenBody = any
/** /**
* El servicio gestiona un par de tokens auth - refresh para las * 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 * Debe tener un cliente HTTP propio para que no le afecten los
* interceptores, sino puede haber bucles de refresco de token * interceptores, sino puede haber bucles de refresco de token
*/ */
export class JWTService { export class JWTService implements IJWTService<ObjeniousTokenBody> {
public isRefreshing: boolean = false; public isRefreshing: boolean = false;
public authToken: JWTToken<{}> | undefined public authToken: JWTToken<ObjeniousTokenBody> | undefined;
private refreshToken?: JWTToken<{}> private refreshToken?: JWTToken<ObjeniousTokenBody> | undefined;
constructor(args?: { constructor(args?: {
token?: string // si se partiese de un token existente, token?: string // si se partiese de un token existente,
@@ -122,7 +114,7 @@ export class JWTService {
return token return token
} }
public async getNewTokens() { public async getNewAuthToken() {
const bodyWithtoken = { const bodyWithtoken = {
...DEFAULT_BODY, ...DEFAULT_BODY,
client_assertion: this.buildJwtBody() client_assertion: this.buildJwtBody()
@@ -139,8 +131,8 @@ export class JWTService {
let res; let res;
try { try {
res = (await req).data as TokensRequestResponse; res = (await req).data as TokensRequestResponse;
this.authToken = new JWTToken(res.access_token) this.authToken = new JWTToken<ObjeniousTokenBody>(res.access_token)
this.refreshToken = new JWTToken(res.refresh_token) this.refreshToken = new JWTToken<ObjeniousTokenBody>(res.refresh_token)
return this.authToken return this.authToken
} catch (e) { } catch (e) {
const errorString = "No se ha podido conseguir el token de acceso de OBJENIOUS" 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 // 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") if (this.authToken == undefined) throw new Error("Error obteniendo tokens de auth")
@@ -190,8 +182,8 @@ export class JWTService {
let res; let res;
try { try {
res = (await req).data as TokensRequestResponse; res = (await req).data as TokensRequestResponse;
this.authToken = new JWTToken(res.access_token) this.authToken = new JWTToken<ObjeniousTokenBody>(res.access_token)
this.refreshToken = new JWTToken(res.refresh_token) this.refreshToken = new JWTToken<ObjeniousTokenBody>(res.refresh_token)
return this.authToken return this.authToken
} catch (e) { } catch (e) {
const errorString = "No se ha podido conseguir el token de acceso de OBJENIOUS" 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 { HttpClient } from "sim-shared/infrastructure/HTTPClient.js"
import { AxiosError } from "axios" import { AxiosError } from "axios"
import { Result } from "sim-shared/domain/Result.js" 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: // TODO:
// - Pasar a un archivo de DTOs // - 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 { startRMQClient } from "#config/eventBus.config.js"
import { httpInstance } from "#config/httpClient.config.js" import { httpInstance } from "#config/httpClient.config.js"
import { pgPool } from "#config/postgreConfig.js" import { pgPool } from "#config/postgreConfig.js"

View File

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

View File

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

View File

@@ -1,9 +1,9 @@
import { pgPool } from "./config/postgreConfig.js" import { pgPool } from "./config/postgreConfig.js"
import { PgClient } from "sim-shared/infrastructure/PgClient.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 { httpInstance } from "./config/httpClient.config.js"
import { CheckObjeniousRequests } from "./tasks/check_objenious_request.js" import { CheckObjeniousRequests } from "./tasks/check_objenious_request.js"
import { OperationsRepository } from "sim-shared/infrastructure/OperationRepository.js"
async function startCron() { async function startCron() {
const commonSettings = { 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"; import { HttpClient } from "sim-shared/infrastructure/HTTPClient.js";
export class CheckObjeniousRequests { 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" import { sign } from "node:crypto"
export type JWTHeader = { export interface IJWTService<T> {
alg: string, /* Obtener un token de auth sin comprobar nada */
typ: string, getNewAuthToken: () => Promise<JWTToken<T>>,
kid: string /* 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> = { export type JWTPayload<T> = {
/** (Issuer) Quién emitió el token */ /** (Issuer) Quién emitió el token */
iss?: string; iss?: string;
@@ -45,29 +79,33 @@ export type JWT<T> = {
} }
// todo pasar a la clase JWT // todo pasar a la clase JWT
function signJWT(args: { function signJWT(args: SignatureOptions) {
algorythm: "sha256" | string,
data: string,
privateKey: string
}) {
const signature = sign( const signature = sign(
args.algorythm, args.algorythm,
Buffer.from(args.data), Buffer.from(args.data),
args.privateKey args.privateKey
) )
return signature return signature.toString("base64url")
}
export type SignatureOptions = {
algorythm: "sha256" | string,
data: string,
privateKey: string,
} }
export class JWTToken<T> { export class JWTToken<T> {
public rawToken: string public rawToken: string
private decodedPayload: JWTPayload<T> | undefined private decodedPayload: JWTPayload<T> | undefined
private signatureFunc = signJWT
constructor( constructor(
token: string token: string,
externalSignatureFunc?: (args: SignatureOptions) => string
) { ) {
this.rawToken = token this.rawToken = token
this.decodedPayload = this.decodePayload() this.decodedPayload = this.decodePayload()
if (externalSignatureFunc != undefined) this.signatureFunc = externalSignatureFunc
} }
public static fromParts<T>(args: { public static fromParts<T>(args: {
@@ -76,10 +114,13 @@ export class JWTToken<T> {
sigantureData?: { sigantureData?: {
privateKey: string, privateKey: string,
algorythm: string algorythm: string
} },
signatureFunc?: (args: SignatureOptions) => string
}) { }) {
const strHeader = JSON.stringify(args.header) const strHeader = JSON.stringify(args.header)
const base64Header = Buffer.from(strHeader).toString("base64url") const base64Header = Buffer.from(strHeader).toString("base64url")
const signatureFunc = args.signatureFunc ?? signJWT
let token = base64Header let token = base64Header
if (args.payload != undefined) { if (args.payload != undefined) {
@@ -89,11 +130,11 @@ export class JWTToken<T> {
} }
if (args.sigantureData != undefined) { if (args.sigantureData != undefined) {
const base64signature = signJWT({ const base64signature = signatureFunc({
algorythm: args.sigantureData.algorythm, algorythm: args.sigantureData.algorythm,
privateKey: args.sigantureData.privateKey, privateKey: args.sigantureData.privateKey,
data: token data: token,
}).toString("base64url") })
token += ("." + base64signature) token += ("." + base64signature)
} }
@@ -116,4 +157,14 @@ export class JWTToken<T> {
if (expirationDate * 1000 <= now.getTime()) return true if (expirationDate * 1000 <= now.getTime()) return true
return false 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 axios, { AxiosInstance } from "axios"
import { JWTToken } from "../domain/JWT.js" import { IJWTService, JWTToken } from "../domain/JWT.js"
// Cambiar por IJWRGeneralService
export type JWTProvider<T> = { export type JWTProvider<T> = {
/** El servidor está solicitando un token nuevo o refrescando el actual*/ /** El servidor está solicitando un token nuevo o refrescando el actual*/
@@ -13,11 +15,13 @@ export class HttpClient {
public client: AxiosInstance public client: AxiosInstance
private jwtManager: JWTProvider<{}> private jwtManager: JWTProvider<{}>
private jwtService: IJWTService<any> | undefined;
constructor(args: { constructor(args: {
baseURL: string, baseURL: string,
headers: Object, 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({ this.client = axios.create({
...args ...args
@@ -25,6 +29,7 @@ export class HttpClient {
this.jwtManager = args.jwtManager this.jwtManager = args.jwtManager
if (args.jwtService != undefined) this.jwtService = args.jwtService
this.client.interceptors.request.use( this.client.interceptors.request.use(
async (config) => { 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 { Result } from "sim-shared/domain/Result.js";
import { PgClient } from "sim-shared/infrastructure/PgClient.js"; import { PgClient } from "sim-shared/infrastructure/PgClient.js";
export class OperationsRepository implements IOperationsRepository { export class OperationsRepository implements IOperationsRepository {
constructor( constructor(

View File

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

View File

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