Files
sf-sim/packages/sim-shared/domain/JWT.ts

171 lines
4.3 KiB
TypeScript

/**
* 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<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;
/** (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<T> = {
header: JWTHeader,
payload?: JWTPayload<T>,
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<T> {
public rawToken: string
private decodedPayload: JWTPayload<T> | 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<T>(args: {
header: JWTHeader,
payload?: JWTPayload<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) {
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<T> {
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")
}
}