Skip to content

Commit 26c4460

Browse files
authored
Add asyncCredentialsAuthenticator to ModelCredentialsAuthenticatable (#744)
* Extend ModelCredentialsAuthenticatable with asyncCredentialsAuthenticator #743 * compiler guards for test
1 parent 469310c commit 26c4460

File tree

2 files changed

+93
-0
lines changed

2 files changed

+93
-0
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#if compiler(>=5.5) && canImport(_Concurrency)
2+
import NIOCore
3+
import Vapor
4+
5+
@available(macOS 12, iOS 15, watchOS 8, tvOS 15, *)
6+
extension ModelCredentialsAuthenticatable {
7+
public static func asyncCredentialsAuthenticator(
8+
_ database: DatabaseID? = nil
9+
) -> AsyncAuthenticator {
10+
AsyncModelCredentialsAuthenticator<Self>(database: database)
11+
}
12+
}
13+
14+
@available(macOS 12, iOS 15, watchOS 8, tvOS 15, *)
15+
private struct AsyncModelCredentialsAuthenticator<User>: AsyncCredentialsAuthenticator
16+
where User: ModelCredentialsAuthenticatable
17+
{
18+
typealias Credentials = ModelCredentials
19+
20+
public let database: DatabaseID?
21+
22+
func authenticate(credentials: ModelCredentials, for request: Request) async throws {
23+
if let user = try await User.query(on: request.db(self.database)).filter(\._$username == credentials.username).first() {
24+
guard try user.verify(password: credentials.password) else {
25+
return
26+
}
27+
request.auth.login(user)
28+
}
29+
}
30+
}
31+
32+
#endif
33+

Tests/FluentTests/CredentialTests.swift

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,69 @@ final class CredentialTests: XCTestCase {
6060
XCTAssertEqual(res.status, .ok)
6161
}
6262
}
63+
}
64+
65+
#if compiler(>=5.5) && canImport(_Concurrency)
66+
@available(macOS 12, iOS 15, watchOS 8, tvOS 15, *)
67+
func testAsyncCredentialsAuthentication() async throws {
68+
let app = Application(.testing)
69+
defer { app.shutdown() }
70+
71+
72+
// Setup test db.
73+
let testDB = ArrayTestDatabase()
74+
75+
app.databases.use(testDB.configuration, as: .test)
76+
77+
// Configure sessions.
78+
app.middleware.use(app.sessions.middleware)
79+
80+
// Setup routes.
81+
let sessionRoutes = app.grouped(CredentialsUser.sessionAuthenticator())
82+
83+
let credentialRoutes = sessionRoutes.grouped(CredentialsUser.asyncCredentialsAuthenticator())
84+
credentialRoutes.post("login") { req -> Response in
85+
guard req.auth.has(CredentialsUser.self) else {
86+
throw Abort(.unauthorized)
87+
}
88+
return req.redirect(to: "/protected")
89+
}
90+
91+
let protectedRoutes = sessionRoutes.grouped(CredentialsUser.redirectMiddleware(path: "/login"))
92+
protectedRoutes.get("protected") { req -> HTTPStatus in
93+
_ = try req.auth.require(CredentialsUser.self)
94+
return .ok
95+
}
6396

97+
// Create user
98+
let password = "password-\(Int.random())"
99+
let passwordHash = try Bcrypt.hash(password)
100+
let testUser = CredentialsUser(id: UUID(), username: "user-\(Int.random())", password: passwordHash)
101+
testDB.append([TestOutput(testUser)])
102+
testDB.append([TestOutput(testUser)])
103+
testDB.append([TestOutput(testUser)])
104+
testDB.append([TestOutput(testUser)])
105+
106+
// Test login
107+
let loginData = ModelCredentials(username: testUser.username, password: password)
108+
try app.test(.POST, "/login", beforeRequest: { req in
109+
try req.content.encode(loginData, as: .urlEncodedForm)
110+
}) { res in
111+
XCTAssertEqual(res.status, .seeOther)
112+
XCTAssertEqual(res.headers[.location].first, "/protected")
113+
let sessionID = try XCTUnwrap(res.headers.setCookie?["vapor-session"]?.string)
64114

115+
// Test accessing protected route
116+
try app.test(.GET, "/protected", beforeRequest: { req in
117+
var cookies = HTTPCookies()
118+
cookies["vapor-session"] = .init(string: sessionID)
119+
req.headers.cookie = cookies
120+
}) { res in
121+
XCTAssertEqual(res.status, .ok)
122+
}
123+
}
65124
}
125+
#endif
66126
}
67127

68128
final class CredentialsUser: Model {

0 commit comments

Comments
 (0)