sourceafSleepSafe::SleepSafeMiddleware.fan

using afIoc::Inject
using afIocConfig::Config
using afBedSheet

** BedSheet middleware that invokes the Guards.
** 
** 'SleepSafeMiddleware' is contributed with the following:
** 
** pre>
** syntax: fantom
** 
** @Contribute { serviceType=MiddlewarePipeline# }
** Void contributeMiddleware(Configuration config, SleepSafeMiddleware middleware) {
**     config.set("afSleepSafe.guards", middleware).after("afBedSheet.assets").before("afBedSheet.routes")
** }
** <pre
** 
** Assets are not purposely not protected to prevent HTTP sessions being loaded (potentially from a database) on *every* 
** request. Override Middleware ordering to change this behaviour.
**
const class SleepSafeMiddleware : Middleware {
    
    @Inject private const Log                   log
    @Inject private const HttpRequest           req
    @Inject private const HttpResponse          res
    @Inject private const ResponseProcessors    resPros
    @Config private const Int                   rejectedStatusCode
    
    ** The Guards used to protect / reject each HTTP request.
                    const Guard[]               guards
    
    @NoDoc
    new make(Type:Guard guards, |This| f) {
        f(this)     
        // note: we can inject just "Guard[]" when we upgrade to afIoc 3.0.8  
        this.guards = guards.vals
    }
    
    @NoDoc
    override Void service(MiddlewarePipeline pipeline) {
        if (shouldGuardRequest(req)) {
            denied := guards.eachWhile { it.guard(req, res) }       
            if (denied != null)
                return rejectSuspectedAttack(denied)
        }
        pipeline.service
    }
    
    ** Override hook to optionally exempt HTTP requests from being guarded by SleepSafe.
    ** Sometimes you want to exclude URLs from being processed, this override hook lets you do that by optionally returning 'false'.
    ** 
    ** This method defaults to returning 'true'.
    virtual Bool shouldGuardRequest(HttpRequest httpReq) { true }
    
    ** Override hook to respond to failed Guard checks. 
    ** Defaults to logging the msg (at warn level) and processes a 403 status.
    virtual Void rejectSuspectedAttack(Obj errObj) {
        msg := errObj is Err ? ((Err) errObj).msg : errObj.toStr
        log.warn(msg)
        resPros.processResponse(HttpStatus(rejectedStatusCode, msg))
    }
}