import * as crypto from 'crypto' ; import { ChallengeService } from './ChallengeService' ; export interface JWT { header : Record < string , string | number | boolean > ; payload : Record < string , string | number | boolean > ; signature : string ; } export class AlgorithmNegotiationService extends ChallengeService { private static JWT_SIGNING_KEY = "kd8ehais9)i3n!na" ; public static generateJWT ( username , admin = false , timestamp = - 1 ) { if ( timestamp === - 1 ) { const oneYearFromNow = new Date ( ) ; timestamp = oneYearFromNow . setFullYear ( oneYearFromNow . getFullYear ( ) + 1 ) ; } const jwt = { header : { alg : "HS256" , typ : "JWT" } , payload : { sub : username , isAdmin : admin , iat : Math . round ( timestamp / 1000 ) } , signature : "" } ; jwt . signature = this . signJwt ( jwt , AlgorithmNegotiationService . JWT_SIGNING_KEY ) ; return jwt ; } public static parseToken ( jwt ) { let parts = jwt . split ( "." ) ; parts = parts . map ( ( p , i ) => { return i === 2 ? p : JSON . parse ( AlgorithmNegotiationService . fromBase64Url ( p ) ) ; } ) ; const parsedJWT = { header : parts [ 0 ] , payload : parts [ 1 ] , signature : parts [ 2 ] } ; switch ( parsedJWT . header . alg . toString ( ) . toLowerCase ( ) ) { case 'hs256' : if ( AlgorithmNegotiationService . signJwt ( parsedJWT , AlgorithmNegotiationService . JWT_SIGNING_KEY ) !== parsedJWT . signature ) throw new Error ( "Invalid Signature" ) ; break ; case 'none' : break ; default : throw new Error ( `JWT PARSING: ${ parts [ 0 ] . alg } algorithm not supported yet` ) ; } return parsedJWT ; } public static JWTToString ( jwt ) { const header = AlgorithmNegotiationService . toBase64URL ( JSON . stringify ( jwt . header ) ) ; const payload = AlgorithmNegotiationService . toBase64URL ( JSON . stringify ( jwt . payload ) ) ; return `${ header }.${ payload }.${ jwt . signature }` ; } public static signJwt ( jwt , signingKey ) { const header = AlgorithmNegotiationService . toBase64URL ( JSON . stringify ( jwt . header ) ) ; const payload = AlgorithmNegotiationService . toBase64URL ( JSON . stringify ( jwt . payload ) ) ; const toSign = `${ header }.${ payload }` ; const signedBase64 = crypto . createHmac ( 'sha256' , signingKey ) . update ( toSign ) . digest ( "base64" ) ; const signedbase64Url = AlgorithmNegotiationService . base64ToBase64Url ( signedBase64 ) ; return signedbase64Url ; } private static base64ToBase64Url ( content ) { return content . replace ( /\+/g , "-" ) . replace ( /\//g , "_" ) . replace ( /=/g , "" ) ; } private static toBase64URL ( content ) { content = this . toBase64 ( content ) ; return this . base64ToBase64Url ( content ) ; } private static fromBase64Url ( content ) { content = content . replace ( /-/g , "+" ) . replace ( /_/g , "/" ) ; const r = content . length % 4 ; content += "=" . repeat ( ( 4 - r ) % 4 ) ; content = this . fromBase64 ( content ) ; return content ; } private static toBase64 ( content ) { return Buffer . from ( content ) . toString ( 'base64' ) ; } private static fromBase64 ( content ) { return Buffer . from ( content , 'base64' ) . toString ( ) ; } }