Skip to content

Commit

Permalink
Merge pull request #23 from Mattiav8/redirectMiddleware
Browse files Browse the repository at this point in the history
Add HTTPSRedirection Middleware
  • Loading branch information
0xTim committed Jan 5, 2021
2 parents 56b250f + ed920b4 commit 089470c
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 1 deletion.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Expand Up @@ -19,7 +19,7 @@ jobs:
- name: Run Bionic Tests
run: swift test --enable-test-discovery --enable-code-coverage --sanitize=thread
- name: Setup container for codecov upload
run: apt-get update && apt-get install curl
run: apt-get update && apt-get install curl -y
- name: Process coverage file
run: llvm-cov show .build/x86_64-unknown-linux-gnu/debug/VaporSecurityHeadersPackageTests.xctest -instr-profile=.build/debug/codecov/default.profdata > coverage.txt
- name: Upload code coverage
Expand Down
11 changes: 11 additions & 0 deletions README.md
Expand Up @@ -27,6 +27,7 @@ Easily add headers to all your responses for improving the security of your site
* X-Frame-Options
* X-Content-Type-Options
* Strict-Transport-Security (HSTS)
* Redirect HTTP to HTTPS
* Server
* Referrer Policy

Expand Down Expand Up @@ -436,6 +437,16 @@ let securityHeadersFactory = SecurityHeadersFactory().with(strictTransportSecuri
strict-transport-security: max-age=31536000; includeSubDomains; preload
```

## Redirect HTTP to HTTPS

If Strict-Transport-Security is not enough to accomplish a forwarding connection to HTTPS from the browsers, you can opt to add an additional middleware who provides this redirection if clients try to reach your site with an HTTP connection.

To use the HTTPS Redirect Middleware, you can add the following line in **configure.swift** to enable the middleware. This must be done before `securityHeadersFactory.build()` to ensure HSTS works:

```swift
app.middleware.use(HTTPSRedirectMiddleware())
```

## Server

The Server header is usually hidden from responses in order to not give away what type of server you are running and what version you are using. This is to stop attackers from scanning your site and using known vulnerabilities against it easily. By default Vapor does not show the server header in responses for this reason.
Expand Down
@@ -0,0 +1,25 @@
import Vapor

public class HTTPSRedirectMiddleware: Middleware {

public init() {}

public func respond(to request: Request, chainingTo next: Responder) -> EventLoopFuture<Response> {
if request.application.environment == .development {
return next.respond(to: request)
}

let proto = request.headers.first(name: "X-Forwarded-Proto")
?? request.url.scheme
?? "http"

guard proto == "https" else {
guard let host = request.headers.first(name: .host) else {
return request.eventLoop.makeFailedFuture(Abort(.badRequest))
}
let httpsURL = "https://" + host + "\(request.url)"
return request.redirect(to: "\(httpsURL)", type: .permanent).encodeResponse(for: request)
}
return next.respond(to: request)
}
}
86 changes: 86 additions & 0 deletions Tests/VaporSecurityHeadersTests/RedirectionTest.swift
@@ -0,0 +1,86 @@
import XCTest

@testable import Vapor

import VaporSecurityHeaders

class RedirectionTest: XCTestCase {

// MARK: - Properties

private var application: Application!
private var eventLoopGroup: EventLoopGroup!
private var request: Request!

override func setUp() {
eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)
application = Application(.testing, .shared(eventLoopGroup))
request = Request(application: application, method: .GET, on: eventLoopGroup.next())
}

override func tearDownWithError() throws {
application.shutdown()
try eventLoopGroup.syncShutdownGracefully()
}

func testWithRedirectionMiddleware() throws {
let expectedRedirectStatus: HTTPStatus = HTTPResponseStatus(statusCode: 301, reasonPhrase: "Moved permanently")
request.headers.add(name: .host, value: "localhost:8080")
let responseRedirected = try makeTestResponse(for: request, withRedirection: true)
XCTAssertEqual(expectedRedirectStatus, responseRedirected.status)
}
func testWithoutRedirectionMiddleware() throws {
let expectedNoRedirectStatus: HTTPStatus = HTTPResponseStatus(statusCode: 200, reasonPhrase: "Ok")
request.headers.add(name: .host, value: "localhost:8080")
let response = try makeTestResponse(for: request, withRedirection: false)
XCTAssertEqual(expectedNoRedirectStatus, response.status)
}

func testOnDevelopmentEnvironment() throws {
let expectedStatus: HTTPStatus = HTTPResponseStatus(statusCode: 200, reasonPhrase: "Ok")
request.headers.add(name: .host, value: "localhost:8080")
let response = try makeTestResponse(for: request, withRedirection: true, environment: .development)
XCTAssertEqual(expectedStatus, response.status)
}

func testWithoutHost() throws {
let expectedOutcome: String = "Abort.400: Bad Request"
do {
_ = try makeTestResponse(for: request, withRedirection: true)
} catch (let error) {
XCTAssertEqual(expectedOutcome, error.localizedDescription)
}
}

func testWithProtoSet() throws {
let expectedStatus: HTTPStatus = HTTPResponseStatus(statusCode: 200, reasonPhrase: "Ok")
request.headers.add(name: .xForwardedProto, value: "https")
let response = try makeTestResponse(for: request, withRedirection: true)
XCTAssertEqual(expectedStatus, response.status)
}

private func makeTestResponse(for request: Request, withRedirection: Bool, environment: Environment? = nil) throws -> Response {
application.middleware = Middlewares()
if let environment = environment {
application.environment = environment
}
if withRedirection == true {
application.middleware.use(HTTPSRedirectMiddleware())
}
try routes(application)
return try application.responder.respond(to: request).wait()
}

func routes(_ app: Application) throws {
try app.register(collection: RouteController())
}

struct RouteController: RouteCollection {
func boot(routes: RoutesBuilder) throws {
routes.get(use: testing)
}
func testing(req: Request) throws -> String {
return "Test"
}
}
}

0 comments on commit 089470c

Please sign in to comment.