import { computeHash , verifyHash } from '../fact/hash' ; import { TopologicalSorter } from '../fact/sorter' ; import { Feed } from '../feed/feed' ; import { FactRecord } from '../storage' ; import { distinct , mapAsync } from '../util/fn' ; import { Trace } from '../util/trace' ; import { AuthorizationRules } from './authorizationRules' ; export class Forbidden extends Error { __proto__ : Error ; constructor ( message ? ) { const trueProto = new . target . prototype ; super ( message ) ; this . __proto__ = trueProto ; } } type AuthorizationVerdict = "New" | "Signed" | "Existing" | "Forbidden" ; type AuthorizationResult = { fact : FactRecord ; verdict : AuthorizationVerdict ; } ; export class AuthorizationEngine { constructor ( private authorizationRules , private feed ) { } async authorizeFacts ( facts , userFact ) { const sorter = new TopologicalSorter < Promise < AuthorizationResult > > ( ) ; const results = await mapAsync ( sorter . sort ( facts , ( p , f ) => this . visit ( p , f , userFact , facts ) ) , x => x ) ; const rejected = results . filter ( r => r . verdict === "Forbidden" ) ; if ( rejected . length > 0 ) { const distinctTypes = rejected . map ( r => r . fact . type ) . filter ( distinct ) . join ( ", " ) ; const count = rejected . length === 1 ? "1 fact" : `${ rejected . length } facts` ; const message = `Rejected ${ count } of type ${ distinctTypes }.` ; throw new Forbidden ( message ) ; } const authorizedFacts = results . filter ( r => r . verdict === "New" || r . verdict === "Signed" ) . map ( r => r . fact ) ; return authorizedFacts ; } private async visit ( predecessors , fact , userFact , factRecords ) { const predecessorResults = await mapAsync ( predecessors , p => p ) ; const verdict = await this . authorize ( predecessorResults , userFact , fact , factRecords ) ; return { fact , verdict } ; } private async authorize ( predecessors , userFact , fact , factRecords ) { if ( predecessors . some ( p => p . verdict === "Forbidden" ) ) { const predecessor = predecessors . filter ( p => p . verdict === 'Forbidden' ) . map ( p => p . fact . type ) . join ( ', ' ) ; Trace . warn ( `The fact ${ fact . type } cannot be authorized because its predecessor ${ predecessor } is not authorized.` ) ; return "Forbidden" ; } if ( ! verifyHash ( fact ) ) { const computedHash = computeHash ( fact . fields , fact . predecessors ) ; Trace . warn ( `The hash of ${ fact . type } does not match: computed ${ computedHash }, provided ${ fact . hash }.` ) ; return "Forbidden" ; } const isAuthorized = await this . authorizationRules . isAuthorized ( userFact , fact , factRecords , this . feed ) ; if ( predecessors . some ( p => p . verdict === "New" ) || ! ( await this . feed . exists ( fact ) ) ) { if ( ! isAuthorized ) { Trace . warn ( `The fact of type ${ fact . type } is not authorized.` ) ; } return isAuthorized ? "New" : "Forbidden" ; } return isAuthorized ? "Signed" : "Existing" ; } }