sourceafSleepSafe::SameOriginGuard.fan
using afIoc::Inject using afIocConfig::ConfigSource using afBedSheet::HttpRequest using afBedSheet::HttpResponse using afBedSheet::BedSheetServer** Guards against CSRF attacks by checking that the 'Referer' or 'Origin' HTTP header matches the 'Host'.**** The idea behind the same origin check is that standard form POST requests should originate from the same server.** So the 'Referer' and 'Origin' HTTP headers are checked to ensure they match the server host.** The 'Host' parameter is determined from [BedSheetServer.host()]`afBedSheet::BedSheetServer.host` and is usually picked up** from the 'BedSheetConfigIds.host' config value.**** Requests are also denied if neither the 'Referer' nor 'Origin' HTTP header are present.**** See [Cross-Site Request Forgery (CSRF) Prevention Cheat Sheet]`https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet#Verifying_Same_Origin_with_Standard_Headers` for details.******** Ioc Configuration** ******************* 'SameOriginGuard' is disabled by default as a referrer policy is preferred.** For if a 'no-referrer' policy is enforced (either explicitly or as an older browser fall back) then, more than likely, this** guard will fail!**** To enable, contribute this class to the 'SleepSafeMiddleware' configuration:**** syntax: fantom** @Contribute { serviceType=SleepSafeMiddleware# }** Void contributeSleepSafeMiddleware(Configuration config) {** config[SameOriginGuard#] = config.build(SameOriginGuard#)** }**** Then to configure an origin whitelist:**** table:** afIocConfig Key Value** --------------------------------- ------------** 'afSleepSafe.sameOriginWhitelist' A CSV of alternative allowed origins.**** Example:**** syntax: fantom** @Contribute { serviceType=ApplicationDefaults# }** Void contributeAppDefaults(Configuration config) {** config["afSleepSafe.sameOriginWhitelist"] = "http://domain1.com, http://domain2.com"** }**** To configure the BedSheet host:**** syntax: fantom** @Contribute { serviceType=ApplicationDefaults# }** Void contributeAppDefaults(Configuration config) {** config["afBedSheet.host"] = `https://example.com`** }**const class SameOriginGuard : Guard { @Inject private const BedSheetServer bedServer private const Uri[] whitelist private new make(ConfigSource configSrc, |This| f) { f(this) csv := (Str) configSrc.get("afSleepSafe.sameOriginWhitelist", Str#) whitelist = csv.split(',').map { Uri(it, false) }.exclude { it == null || it.toStr.isEmpty } } @NoDoc override const Str protectsAgainst := "CSRF" @NoDoc override Str? guard(HttpRequest httpReq, HttpResponse httpRes) { if (CsrfTokenGuard.fromVunerableUrl(httpReq)) { host := bedServer.host referrer := httpReq.headers.referrer?.plus(`/`)// delete the path component// referrer is optional and may be relative - see https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.36if (referrer != null && referrer.isAbs) { if (host != referrer && !whitelist.contains(referrer)) return csrfErr("Referrer does not match Host: ${referrer} != ${host}" + (whitelist.isEmpty ? "" : ", " + whitelist.join(", "))) } origin := httpReq.headers.origin?.plus(`/`)// delete any path component (not that there should be any)if (origin != null) { if (host != origin && !whitelist.contains(origin)) return csrfErr("Origin does not match Host: ${origin} != ${host}" + (whitelist.isEmpty ? "" : ", " + whitelist.join(", "))) } if (referrer == null && origin == null) return csrfErr("HTTP request contains neither a Referrer nor an Origin header") } return null } private Str csrfErr(Str msg) { "Suspected CSRF attack - $msg" } }