Last active
April 15, 2020 23:21
-
-
Save agemooij/0e947780f73d55bc1baa to your computer and use it in GitHub Desktop.
Spray HttpDirectives
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package scalapenos.spray.auth | |
import spray.routing._ | |
import spray.routing.Directives._ | |
import spray.http.HttpHeaders._ | |
import spray.http.StatusCodes._ | |
trait HttpsDirectives { | |
import HttpsDirectives._ | |
def enforceHttpsIf(yes: => Boolean): Directive0 = { | |
if (yes) enforceHttps | |
else pass | |
} | |
def enforceHttps: Directive0 = { | |
respondWithHeader(StrictTransportSecurity) & | |
extract(isHttpsRequest).flatMap( | |
if (_) pass | |
else redirectToHttps | |
) | |
} | |
def redirectToHttps: Directive0 = { | |
requestUri.flatMap { uri => | |
redirect(uri.copy(scheme = "https"), MovedPermanently) | |
} | |
} | |
} | |
object HttpsDirectives { | |
/** Hardcoded max-age of one year (31536000 seconds) for now. */ | |
val StrictTransportSecurity = RawHeader("Strict-Transport-Security", "max-age=31536000") | |
val isHttpsRequest: RequestContext => Boolean = { ctx => | |
ctx.request.uri.scheme == "https" || ctx.request.headers.exists(h => h.is("x-forwarded-proto") && h.value == "https") | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package scalapenos.spray.auth | |
import org.specs2.mutable.Specification | |
import spray.http._ | |
import spray.http.HttpHeaders._ | |
import spray.http.StatusCodes._ | |
import spray.routing._ | |
import spray.routing.Directives._ | |
import spray.testkit.Specs2RouteTest | |
import HttpsDirectives._ | |
class HttpsDirectivesSpec extends Specification | |
with Specs2RouteTest | |
with HttpsDirectives { | |
val httpUri = Uri("http://example.com/api/awesome") | |
val httpsUri = Uri("https://example.com/api/awesome") | |
"The enforceHttps directive" should { | |
val route = enforceHttps { | |
complete(OK) | |
} | |
"allow https requests and respond with the HSTS header" in { | |
Get(httpsUri) ~> route ~> check { | |
status === OK | |
header(StrictTransportSecurity.name) must beSome(StrictTransportSecurity) | |
} | |
} | |
"allow terminated https requests containing a 'X-Forwarded-Proto' header and respond with the HSTS header" in { | |
Get(httpUri) ~> addHeader(RawHeader("X-Forwarded-Proto", "https")) ~> route ~> check { | |
status === OK | |
header(StrictTransportSecurity.name) must beSome(StrictTransportSecurity) | |
} | |
} | |
"redirect plain http requests to the matching https URI" in { | |
Get(httpUri) ~> route ~> check { | |
status === MovedPermanently | |
header[Location].map(l => Uri(l.value)) must beSome(httpsUri) | |
header(StrictTransportSecurity.name) must beSome(StrictTransportSecurity) | |
} | |
} | |
"redirect terminated http requests to the matching https URI" in { | |
Get(httpUri) ~> addHeader(RawHeader("X-Forwarded-Proto", "http")) ~> route ~> check { | |
status === MovedPermanently | |
header[Location].map(l => Uri(l.value)) must beSome(httpsUri) | |
header(StrictTransportSecurity.name) must beSome(StrictTransportSecurity) | |
} | |
} | |
} | |
"The enforceHttpsIf directive" should { | |
"enforce https when the argument resolves to true" in { | |
val route = enforceHttpsIf(true) { | |
complete(OK) | |
} | |
Get(httpsUri) ~> route ~> check { | |
status === OK | |
header(StrictTransportSecurity.name) must beSome(StrictTransportSecurity) | |
} | |
Get(httpUri) ~> route ~> check { | |
status === MovedPermanently | |
header[Location].map(l => Uri(l.value)) must beSome(httpsUri) | |
header(StrictTransportSecurity.name) must beSome(StrictTransportSecurity) | |
} | |
} | |
"not enforce https when the argument resolves to false" in { | |
val route = enforceHttpsIf(false) { | |
complete(OK) | |
} | |
Get(httpsUri) ~> route ~> check { | |
status === OK | |
header(StrictTransportSecurity.name) must beNone | |
} | |
Get(httpUri) ~> route ~> check { | |
status === OK | |
header(StrictTransportSecurity.name) must beNone | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment