Skip to content

Commit 089470c

Browse files
authored
Merge pull request #23 from Mattiav8/redirectMiddleware
Add HTTPSRedirection Middleware
2 parents 56b250f + ed920b4 commit 089470c

File tree

4 files changed

+123
-1
lines changed

4 files changed

+123
-1
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919
- name: Run Bionic Tests
2020
run: swift test --enable-test-discovery --enable-code-coverage --sanitize=thread
2121
- name: Setup container for codecov upload
22-
run: apt-get update && apt-get install curl
22+
run: apt-get update && apt-get install curl -y
2323
- name: Process coverage file
2424
run: llvm-cov show .build/x86_64-unknown-linux-gnu/debug/VaporSecurityHeadersPackageTests.xctest -instr-profile=.build/debug/codecov/default.profdata > coverage.txt
2525
- name: Upload code coverage

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ Easily add headers to all your responses for improving the security of your site
2727
* X-Frame-Options
2828
* X-Content-Type-Options
2929
* Strict-Transport-Security (HSTS)
30+
* Redirect HTTP to HTTPS
3031
* Server
3132
* Referrer Policy
3233

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

440+
## Redirect HTTP to HTTPS
441+
442+
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.
443+
444+
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:
445+
446+
```swift
447+
app.middleware.use(HTTPSRedirectMiddleware())
448+
```
449+
439450
## Server
440451

441452
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.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import Vapor
2+
3+
public class HTTPSRedirectMiddleware: Middleware {
4+
5+
public init() {}
6+
7+
public func respond(to request: Request, chainingTo next: Responder) -> EventLoopFuture<Response> {
8+
if request.application.environment == .development {
9+
return next.respond(to: request)
10+
}
11+
12+
let proto = request.headers.first(name: "X-Forwarded-Proto")
13+
?? request.url.scheme
14+
?? "http"
15+
16+
guard proto == "https" else {
17+
guard let host = request.headers.first(name: .host) else {
18+
return request.eventLoop.makeFailedFuture(Abort(.badRequest))
19+
}
20+
let httpsURL = "https://" + host + "\(request.url)"
21+
return request.redirect(to: "\(httpsURL)", type: .permanent).encodeResponse(for: request)
22+
}
23+
return next.respond(to: request)
24+
}
25+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import XCTest
2+
3+
@testable import Vapor
4+
5+
import VaporSecurityHeaders
6+
7+
class RedirectionTest: XCTestCase {
8+
9+
// MARK: - Properties
10+
11+
private var application: Application!
12+
private var eventLoopGroup: EventLoopGroup!
13+
private var request: Request!
14+
15+
override func setUp() {
16+
eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)
17+
application = Application(.testing, .shared(eventLoopGroup))
18+
request = Request(application: application, method: .GET, on: eventLoopGroup.next())
19+
}
20+
21+
override func tearDownWithError() throws {
22+
application.shutdown()
23+
try eventLoopGroup.syncShutdownGracefully()
24+
}
25+
26+
func testWithRedirectionMiddleware() throws {
27+
let expectedRedirectStatus: HTTPStatus = HTTPResponseStatus(statusCode: 301, reasonPhrase: "Moved permanently")
28+
request.headers.add(name: .host, value: "localhost:8080")
29+
let responseRedirected = try makeTestResponse(for: request, withRedirection: true)
30+
XCTAssertEqual(expectedRedirectStatus, responseRedirected.status)
31+
}
32+
func testWithoutRedirectionMiddleware() throws {
33+
let expectedNoRedirectStatus: HTTPStatus = HTTPResponseStatus(statusCode: 200, reasonPhrase: "Ok")
34+
request.headers.add(name: .host, value: "localhost:8080")
35+
let response = try makeTestResponse(for: request, withRedirection: false)
36+
XCTAssertEqual(expectedNoRedirectStatus, response.status)
37+
}
38+
39+
func testOnDevelopmentEnvironment() throws {
40+
let expectedStatus: HTTPStatus = HTTPResponseStatus(statusCode: 200, reasonPhrase: "Ok")
41+
request.headers.add(name: .host, value: "localhost:8080")
42+
let response = try makeTestResponse(for: request, withRedirection: true, environment: .development)
43+
XCTAssertEqual(expectedStatus, response.status)
44+
}
45+
46+
func testWithoutHost() throws {
47+
let expectedOutcome: String = "Abort.400: Bad Request"
48+
do {
49+
_ = try makeTestResponse(for: request, withRedirection: true)
50+
} catch (let error) {
51+
XCTAssertEqual(expectedOutcome, error.localizedDescription)
52+
}
53+
}
54+
55+
func testWithProtoSet() throws {
56+
let expectedStatus: HTTPStatus = HTTPResponseStatus(statusCode: 200, reasonPhrase: "Ok")
57+
request.headers.add(name: .xForwardedProto, value: "https")
58+
let response = try makeTestResponse(for: request, withRedirection: true)
59+
XCTAssertEqual(expectedStatus, response.status)
60+
}
61+
62+
private func makeTestResponse(for request: Request, withRedirection: Bool, environment: Environment? = nil) throws -> Response {
63+
application.middleware = Middlewares()
64+
if let environment = environment {
65+
application.environment = environment
66+
}
67+
if withRedirection == true {
68+
application.middleware.use(HTTPSRedirectMiddleware())
69+
}
70+
try routes(application)
71+
return try application.responder.respond(to: request).wait()
72+
}
73+
74+
func routes(_ app: Application) throws {
75+
try app.register(collection: RouteController())
76+
}
77+
78+
struct RouteController: RouteCollection {
79+
func boot(routes: RoutesBuilder) throws {
80+
routes.get(use: testing)
81+
}
82+
func testing(req: Request) throws -> String {
83+
return "Test"
84+
}
85+
}
86+
}

0 commit comments

Comments
 (0)