/** * 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 interface IJWTService { /* Obtener un token de auth sin comprobar nada */ getNewAuthToken: () => Promise>, /* Obtener un token valido -> sino refrescar -> sin pillar un token nuevo */ getAccessToken: () => Promise>, /* Intenta refrescar el token actual */ tryRefreshToken: () => Promise>, } 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 se * añaden los campos espeficos de la comunicacion * */ export type JWTPayload = { /** (Issuer) Quién emitió el token */ iss?: string; /** (Subject) De quién trata el token (ej. user_id) */ sub?: string; /** (Audience) Destinatarios del token */ aud?: string | string[]; /** (Expiration Time) Fecha de expiración (Unix timestamp) */ exp?: number; /** (Not Before) No válido antes de esta fecha (Unix timestamp) */ nbf?: number; /** (Issued At) Cuándo fue emitido (Unix timestamp) */ iat?: number; /** (JWT ID) Identificador único para este token */ jti?: string; /** (Authentication Context Class) */ acr?: string } & T export type JWTSignature = { e?: string, ktv?: string, n?: string } export type JWT = { header: JWTHeader, payload?: JWTPayload, signature: JWTSignature } // todo pasar a la clase JWT function signJWT(args: SignatureOptions) { const signature = sign( args.algorythm, Buffer.from(args.data), args.privateKey ) return signature.toString("base64url") } export type SignatureOptions = { algorythm: "sha256" | string, data: string, privateKey: string, } export class JWTToken { public rawToken: string private decodedPayload: JWTPayload | undefined private signatureFunc = signJWT constructor( token: string, externalSignatureFunc?: (args: SignatureOptions) => string ) { this.rawToken = token this.decodedPayload = this.decodePayload() if (externalSignatureFunc != undefined) this.signatureFunc = externalSignatureFunc } public static fromParts(args: { header: JWTHeader, payload?: JWTPayload, 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) { const strPayload = JSON.stringify(args.payload) const base64payload = Buffer.from(strPayload).toString("base64url") token += ("." + base64payload) } if (args.sigantureData != undefined) { const base64signature = signatureFunc({ algorythm: args.sigantureData.algorythm, privateKey: args.sigantureData.privateKey, data: token, }) token += ("." + base64signature) } return token } private decodePayload(): JWTPayload { if (this.rawToken == undefined) throw new Error("La clase no tiene un token definido") const rawTokenPayload = this.rawToken.split(".")[1] if (rawTokenPayload == undefined) throw new Error("El token no tiene payload") return JSON.parse(Buffer.from(rawTokenPayload, "base64").toString("utf8")); } public isExpired() { if (this.decodedPayload == undefined) throw new Error("Error leyendo el payload del token") const now = new Date() const expirationDate = this.decodedPayload.exp if (expirationDate == undefined) return false // un token sin fecha de expiracion no expira if (expirationDate * 1000 <= now.getTime()) return true return false } public isValid() { throw new Error("No implementado") } public verifySignature() { throw new Error("No implementado") } }