import { QueueClient } from "@azure/storage-queue" ; import { format as dateFnsFormat } from "date-fns" ; import { isLeft , tryCatch2v } from "fp-ts/lib/Either" ; import { fromEither , fromNullable , isNone , Option } from "fp-ts/lib/Option" ; import * as t from "io-ts" ; import { UTCISODateFromString } from "italia-ts-commons/lib/dates" ; import { readableReport } from "italia-ts-commons/lib/reporters" ; import { FiscalCode , IPString , NonEmptyString , PatternString } from "italia-ts-commons/lib/strings" ; import { DOMParser } from "xmldom" ; import { log } from "./logger" ; import { base64EncodeObject } from "./messages" ; const SAML_NAMESPACE = { ASSERTION : "urn:oasis:names:tc:SAML:2.0:assertion" , PROTOCOL : "urn:oasis:names:tc:SAML:2.0:protocol" } ; export const getFiscalNumberFromPayload = ( doc ) : Option < FiscalCode > => fromNullable ( doc . getElementsByTagNameNS ( SAML_NAMESPACE . ASSERTION , "Attribute" ) ) . mapNullable ( collection => Array . from ( collection ) . find ( elem => elem . getAttribute ( "Name" ) === "fiscalNumber" ) ) . mapNullable ( _ => _ . textContent ?. trim ( ) . replace ( "TINIT-" , "" ) ) . chain ( _ => fromEither ( FiscalCode . decode ( _ ) ) ) ; const getRequestIDFromPayload = ( tagName , attrName ) => ( doc ) : Option < string > => fromNullable ( doc . getElementsByTagNameNS ( SAML_NAMESPACE . PROTOCOL , tagName ) . item ( 0 ) ) . chain ( element => fromEither ( NonEmptyString . decode ( element . getAttribute ( attrName ) ) ) ) ; export const getRequestIDFromRequest = getRequestIDFromPayload ( "AuthnRequest" , "ID" ) ; export const getRequestIDFromResponse = getRequestIDFromPayload ( "Response" , "InResponseTo" ) ; const SpidMsg = t . interface ( { createdAt : UTCISODateFromString , createdAtDay : PatternString ( "^[0-9]{4}-[0-9]{2}-[0-9]{2}$" ) , fiscalCode : t . union ( [ t . undefined , FiscalCode ] ) , ip : IPString , requestPayload : t . string , responsePayload : t . string , spidRequestId : t . union ( [ t . undefined , t . string ] ) } ) ; type SpidMsg = t . TypeOf < typeof SpidMsg > ; export const makeSpidLogCallback = ( queueClient ) => ( sourceIp , requestPayload , responsePayload ) : void => { const logPrefix = `SpidLogCallback` ; tryCatch2v ( ( ) => { const responseXML = new DOMParser ( ) . parseFromString ( responsePayload , "text/xml" ) ; if ( ! responseXML ) { log . error ( `${ logPrefix }|ERROR=Cannot parse SPID XML` ) ; return ; } const maybeRequestId = getRequestIDFromResponse ( responseXML ) ; if ( isNone ( maybeRequestId ) ) { log . error ( `${ logPrefix }|ERROR=Cannot get Request ID from SPID XML` ) ; return ; } const requestId = maybeRequestId . value ; const maybeFiscalCode = getFiscalNumberFromPayload ( responseXML ) ; if ( isNone ( maybeFiscalCode ) ) { log . error ( `${ logPrefix }|ERROR=Cannot get user's fiscal Code from SPID XML` ) ; return ; } const fiscalCode = maybeFiscalCode . value ; const errorOrSpidMsg = SpidMsg . decode ( { createdAt : new Date ( ) , createdAtDay : dateFnsFormat ( new Date ( ) , "YYYY-MM-DD" ) , fiscalCode , ip : sourceIp as IPString , requestPayload , responsePayload , spidRequestId : requestId } as SpidMsg ) ; if ( isLeft ( errorOrSpidMsg ) ) { log . error ( `${ logPrefix }|ERROR=Invalid format for SPID log payload` ) ; log . debug ( `${ logPrefix }|ERROR_DETAILS=${ readableReport ( errorOrSpidMsg . value ) }` ) ; return ; } const spidMsg = errorOrSpidMsg . value ; const spidMsgBase64 = base64EncodeObject ( spidMsg ) ; queueClient . sendMessage ( spidMsgBase64 ) . catch ( err => { log . error ( `${ logPrefix }|ERROR=Cannot enqueue SPID log` ) ; log . debug ( `${ logPrefix }|ERROR_DETAILS=${ err }` ) ; } ) ; } , err => { log . error ( `${ logPrefix }|ERROR=${ err }` ) ; } ) ; } ;