import WebSocket from 'ws' ; import { WebSocketAuthorizer } from '@mds-core/mds-api-authorizer' ; import { AuthorizationError } from '@mds-core/mds-utils' ; import logger from '@mds-core/mds-logger' ; import jwt from 'jsonwebtoken' ; import jwks from 'jwks-rsa' ; import { promisify } from 'util' ; export class Clients { authenticatedClients : WebSocket [ ] ; subList : { [ key ] : WebSocket [ ] ; } ; public static getKey = async ( header ) => { const { JWKS_URI } = process . env ; if ( ! JWKS_URI ) throw new Error ( 'No JWKS_URI defined!' ) ; const client = jwks ( { jwksUri : JWKS_URI } ) ; const key = await promisify ( client . getSigningKey ) ( header . kid ?? 'null' ) ; return key . publicKey || key . rsaPublicKey ; } ; public constructor ( supportedEntities ) { this . subList = Object . fromEntries ( supportedEntities . map ( e => [ e , [ ] ] ) ) ; this . authenticatedClients = [ ] ; this . saveClient = this . saveClient . bind ( this ) ; } public isAuthenticated ( client ) { return this . authenticatedClients . includes ( client ) ; } public saveClient ( entities , client ) { if ( ! this . authenticatedClients . includes ( client ) ) { return ; } const trimmedEntities = entities . map ( entity => entity . trim ( ) ) ; return Promise . all ( trimmedEntities . map ( entity => { try { this . subList [ entity ] . push ( client ) ; client . send ( `SUB%${ JSON . stringify ( { status : 'Success' } ) }` ) ; } catch { client . send ( `SUB%${ JSON . stringify ( { status : 'Failure' } ) }` ) ; return logger . error ( `failed to push ${ entity }` ) ; } } ) ) ; } public async saveAuth ( authorizer , client ) { try { const [ , token ] = authorizer . split ( ' ' ) ; const auth = WebSocketAuthorizer ( authorizer ) ; const validateAuth = await Clients . checkAuth ( token ) ; if ( ! validateAuth ) { client . send ( JSON . stringify ( { err : new AuthorizationError ( ) } ) ) ; return ; } const scopes = auth ?. scope . split ( ' ' ) ?? [ ] ; if ( scopes . includes ( 'admin:all' ) ) { this . authenticatedClients . push ( client ) ; client . send ( `AUTH%${ JSON . stringify ( { status : 'Success' } ) }` ) ; } else { client . send ( `AUTH%${ JSON . stringify ( { status : 'Failure' } ) }` ) ; } } catch ( err ) { logger . warn ( err ) ; client . send ( JSON . stringify ( err ) ) ; } } public static async checkAuth ( token ) { try { const { JWT_ISSUER , JWT_AUDIENCE } = process . env ; const { header } = jwt . decode ( token , { complete : true , json : true } ) as { header : { kid : string ; } ; } ; const key = ( await this . getKey ( header ) ) as string ; return jwt . verify ( token , key , { audience : JWT_AUDIENCE , issuer : JWT_ISSUER } ) ; } catch ( err ) { logger . warn ( err ) ; return false ; } } }