fileoverview import { debug as d } from '@hint/utils-debug' ; import { FetchEnd , HintContext , IHint , ScanEnd } from 'hint' ; import { Severity } from '@hint/utils-types' ; import { analyze as ssllabs } from './api' ; import { Grades , SSLLabsEndpoint , SSLLabsOptions , SSLLabsResult } from './types' ; import meta from './meta' ; import { getMessage } from './i18n.import' ; const debug = d ( __filename ) ; export default class SSLLabsHint implements IHint { public static readonly meta = meta ; public constructor ( context ) { /** The promise that represents the scan by SSL Labs. */ let promise ; /** The minimum grade required to pass. */ let minimumGrade = 'A-' ; /** The options to pass to the SSL Labs scanner. */ let scanOptions = { all : 'done' , fromCache : true , host : '' , maxAge : 2 } ; /** Error processing the request if any. */ let failed = false ; const loadHintConfig = ( ) => { minimumGrade = ( context . hintOptions && context . hintOptions . grade ) || 'A-' ; const userSslOptions = ( context . hintOptions && context . hintOptions . ssllabs ) || { } ; scanOptions = { ... scanOptions , ... userSslOptions } ; } ; const verifyEndpoint = ( resource , { grade , serverName = resource , details } ) => { if ( ! grade && details . protocols . length === 0 ) { const message = getMessage ( 'doesNotSupportHTTPS' , context . language , resource ) ; debug ( message ) ; context . report ( resource , message , { severity : Severity . error } ) ; return ; } const calculatedGrade = Grades [ grade ] ; const calculatedMiniumGrade = Grades [ minimumGrade ] ; if ( calculatedGrade > calculatedMiniumGrade ) { const message = getMessage ( 'gradeNotMeetTheMinimum' , context . language , [ serverName , grade , minimumGrade ] ) ; debug ( message ) ; context . report ( resource , message , { severity : Severity . error } ) ; } else { debug ( `Grade ${ grade } for ${ resource } is ok.` ) ; } } ; const notifyError = ( resource , error ) => { debug ( `Error getting data for ${ resource } %O` , error ) ; context . report ( resource , getMessage ( 'couldNotGetResults' , context . language , resource ) , { severity : Severity . warning } ) ; } ; const start = ( { resource } ) => { if ( ! resource . startsWith ( 'https://' ) ) { const message = getMessage ( 'doesNotSupportHTTPS' , context . language , resource ) ; debug ( message ) ; context . report ( resource , message , { severity : Severity . error } ) ; return ; } debug ( `Starting SSL Labs scan for ${ resource }` ) ; scanOptions . host = resource ; promise = ssllabs ( scanOptions ) ; promise . catch ( ( error ) => { failed = true ; notifyError ( resource , error ) ; } ) ; } ; const end = async ( { resource } ) => { if ( ! promise || failed ) { return ; } debug ( `Waiting for SSL Labs results for ${ resource }` ) ; let host ; try { host = await promise ; } catch ( e ) { notifyError ( resource , e ) ; return ; } debug ( `Received SSL Labs results for ${ resource }` ) ; if ( ! host || ! host . endpoints || host . endpoints . length === 0 ) { const msg = getMessage ( 'noResults' , context . language , resource ) ; debug ( msg ) ; context . report ( resource , msg , { severity : Severity . warning } ) ; return ; } host . endpoints . forEach ( ( endpoint ) => { verifyEndpoint ( resource , endpoint ) ; } ) ; } ; loadHintConfig ( ) ; context . on ( 'fetch::end::html' , start ) ; context . on ( 'scan::end' , end ) ; } }