Skip to content

Commit c7a419b

Browse files
jimmyaJimmy Arts
and
Jimmy Arts
authoredApr 21, 2020
Update CSRF to support Vapor 4 (vapor-community#14)
* Work on converting to use with Vapor 4 * Update to latest vapor rc * Change package name * Rename csrf to CSRF Co-authored-by: Jimmy Arts <Jimmy.Arts@ahold.com>
1 parent 4e2f27a commit c7a419b

File tree

4 files changed

+56
-65
lines changed

4 files changed

+56
-65
lines changed
 

‎.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata

+7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Package.swift

+10-6
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
1-
// swift-tools-version:4.0
2-
1+
// swift-tools-version:5.2
32
import PackageDescription
43

54
let package = Package(
65
name: "CSRF",
6+
platforms: [
7+
.macOS(.v10_15),
8+
],
79
products: [
8-
.library(name: "CSRF", targets: ["CSRF"])
10+
.library(name: "CSRF", targets: ["CSRF"]),
911
],
1012
dependencies: [
11-
.package(url: "https://github.com/vapor/vapor.git", from: "3.1.0")
13+
// 💧 A server-side Swift web framework.
14+
.package(url: "https://github.com/vapor/vapor.git", from: "4.0.0-rc.1"),
1215
],
1316
targets: [
14-
.target(name: "CSRF", dependencies: ["Vapor"], path: "./Sources"),
15-
.testTarget(name: "CSRFTests", dependencies: ["CSRF", "Vapor"])
17+
.target(name: "CSRF", dependencies: [
18+
.product(name: "Vapor", package: "vapor"),
19+
]),
1620
]
1721
)
+39-39
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
import Crypto
21
import Vapor
32

4-
public typealias TokenRetrievalHandler = ((Request) throws -> Future<String>)
3+
public typealias TokenRetrievalHandler = ((Request) -> EventLoopFuture<String>)
54

65
/// Middleware to protect against cross-site request forgery attacks.
7-
public struct CSRF: Middleware, Service {
6+
public struct CSRF: Middleware {
87
private let ignoredMethods: [HTTPMethod]
98
private var tokenRetrieval: TokenRetrievalHandler
109

@@ -18,77 +17,78 @@ public struct CSRF: Middleware, Service {
1817
self.tokenRetrieval = tokenRetrieval ?? CSRF.defaultTokenRetrieval
1918
}
2019

21-
public func respond(to request: Request, chainingTo next: Responder) throws -> Future<Response> {
22-
let method = request.http.method
20+
public func respond(to request: Request, chainingTo next: Responder) -> EventLoopFuture<Response> {
21+
let method = request.method
2322

2423
if ignoredMethods.contains(method) {
25-
return try next.respond(to: request)
24+
return next.respond(to: request)
2625
}
2726

28-
let secret = try createSecret(from: request)
27+
let secret = createSecret(from: request)
2928

30-
return try tokenRetrieval(request).flatMap(to: Response.self) { token in
31-
let valid = try self.validate(token, with: secret)
32-
guard valid else {
33-
throw Abort(.forbidden, reason: "Invalid CSRF token.")
29+
return tokenRetrieval(request).flatMap { token in
30+
do {
31+
let valid = try self.validate(token, with: secret)
32+
guard valid else {
33+
return request.eventLoop.makeFailedFuture(Abort(.forbidden, reason: "Invalid CSRF token."))
34+
}
35+
return next.respond(to: request)
36+
} catch {
37+
return request.eventLoop.makeFailedFuture(error)
3438
}
35-
return try next.respond(to: request)
3639
}
3740
}
3841

3942
/// Creates a token from a given `Request`. Call this method to generate a CSRF token to assign to your key of choice in the header and pass the token back to the caller via the response.
4043
/// - parameter request: The `Request` used to either find the secret in, or the request used to generate the secret.
4144
/// - returns: `Bytes` representing the generated token.
4245
/// - throws: An error that may arise from either creating the secret from the request or from generating the token.
43-
public func createToken(from request: Request) throws -> String {
44-
let secret = try createSecret(from: request)
45-
let saltBytes = try CryptoRandom().generateData(count: 8)
46-
let saltString = saltBytes.hexEncodedString()
47-
return try generateToken(from: secret, with: saltString)
46+
public func createToken(from request: Request) -> String {
47+
let secret = createSecret(from: request)
48+
let saltBytes = [UInt8].random(count: 8)
49+
let saltString = saltBytes.description
50+
return generateToken(from: secret, with: saltString)
4851
}
4952

50-
private func generateToken(from secret: String, with salt: String) throws -> String {
51-
let saltPlusSecret = salt + "-" + secret
52-
let token = try MD5.hash(saltPlusSecret).hexEncodedString()
53+
private func generateToken(from secret: String, with salt: String) -> String {
54+
let saltPlusSecret = (salt + "-" + secret)
55+
let digest = Insecure.MD5.hash(data: [UInt8](saltPlusSecret.utf8))
56+
let token = digest.description
5357
return salt + "-" + token
5458
}
5559

5660
private func validate(_ token: String, with secret: String) throws -> Bool {
5761
guard let salt = token.components(separatedBy: "-").first else {
5862
throw Abort(.forbidden, reason: "The provided CSRF token is in the wrong format.")
5963
}
60-
let expectedToken = try generateToken(from: secret, with: salt)
64+
let expectedToken = generateToken(from: secret, with: salt)
6165
return expectedToken == token
6266
}
6367

64-
private func createSecret(from request: Request) throws -> String {
65-
66-
let session = try request.session()
67-
68-
guard let secret = session["CSRFSecret"] else {
69-
let random = CryptoRandom()
70-
let secretData = try random.generateData(count: 16)
71-
let secret = secretData.hexEncodedString()
72-
session["CSRFSecret"] = secret
68+
private func createSecret(from request: Request) -> String {
69+
guard let secret = request.session.data["CSRFSecret"] else {
70+
let secretData = [UInt8].random(count: 16)
71+
let secret = secretData.description
72+
request.session.data["CSRFSecret"] = secret
7373
return secret
7474
}
75-
7675
return secret
7776
}
7877

79-
private static func defaultTokenRetrieval(from request: Request) throws -> Future<String> {
78+
private static func defaultTokenRetrieval(from request: Request) -> EventLoopFuture<String> {
8079

8180
let csrfKeys: Set<String> = ["_csrf", "csrf-token", "xsrf-token", "x-csrf-token", "x-xsrf-token", "x-csrftoken"]
82-
let requestHeaderKeys = Set(request.http.headers.map { $0.name })
81+
let requestHeaderKeys = Set(request.headers.map { $0.name })
8382
let intersection = csrfKeys.intersection(requestHeaderKeys)
8483

85-
if let matchingKey = intersection.first, let token = request.http.headers[matchingKey].first {
86-
return request.future(token)
84+
if let matchingKey = intersection.first, let token = request.headers[matchingKey].first {
85+
return request.eventLoop.makeSucceededFuture(token)
8786
}
8887

89-
return request.content.get(at: "_csrf")
90-
.catchMap { error in
91-
throw Abort(.forbidden, reason: "No CSRF token provided.")
92-
}
88+
do {
89+
return try request.eventLoop.makeSucceededFuture(request.content.get(String.self, at: "_csrf"))
90+
} catch {
91+
return request.eventLoop.makeFailedFuture(Abort(.forbidden, reason: "No CSRF token provided."))
92+
}
9393
}
9494
}

‎Sources/CSRFFormFieldTag.swift

-20
This file was deleted.

0 commit comments

Comments
 (0)
Failed to load comments.