Skip to content

Commit c53b11f

Browse files
committed
password support
1 parent 4d23bd3 commit c53b11f

7 files changed

+85
-14
lines changed

Sources/PostgreSQL/Connection/PostgreSQLConnection+Authenticate.swift

-9
This file was deleted.

Sources/PostgreSQL/Connection/PostgreSQLConnection.swift

+38
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,52 @@ public final class PostgreSQLConnection {
4141

4242
/// Sends `PostgreSQLMessage` to the server.
4343
func send(_ message: [PostgreSQLMessage]) -> Future<[PostgreSQLMessage]> {
44+
print(message)
4445
var responses: [PostgreSQLMessage] = []
4546
return send(message) { response in
47+
print(response)
4648
responses.append(response)
4749
}.map(to: [PostgreSQLMessage].self) {
4850
return responses
4951
}
5052
}
5153

54+
/// Authenticates the `PostgreSQLClient` using a username with no password.
55+
public func authenticate(username: String, database: String? = nil) -> Future<Void> {
56+
let startup = PostgreSQLStartupMessage.versionThree(parameters: [
57+
"user": username,
58+
"database": database ?? username
59+
])
60+
var authRequest: PostgreSQLAuthenticationRequest?
61+
return queueStream.enqueue([.startupMessage(startup)]) { message in
62+
switch message {
63+
case .authenticationRequest(let a):
64+
authRequest = a
65+
return true
66+
default: throw PostgreSQLError(identifier: "auth", reason: "Unsupported message encountered during auth: \(message).")
67+
}
68+
}.flatMap(to: Void.self) {
69+
guard let auth = authRequest else {
70+
throw PostgreSQLError(identifier: "authRequest", reason: "No authorization request / status sent.")
71+
}
72+
73+
switch auth {
74+
case .ok: return .done
75+
case .plaintext: throw PostgreSQLError(identifier: "plaintext", reason: "Plaintext password not supported. Use MD5.")
76+
case .md5(let salt):
77+
/// FIXME: hash password
78+
let password = PostgreSQLPasswordMessage(password: "123")
79+
return self.queueStream.enqueue([.password(password)]) { message in
80+
switch message {
81+
case .error(let error):
82+
throw error
83+
default: return true
84+
}
85+
}
86+
}
87+
}
88+
}
89+
5290
/// Closes this client.
5391
public func close() {
5492
queueStream.close()

Sources/PostgreSQL/Message+Serialize/PostgreSQLMessageEncoder.swift

+3
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ final class PostgreSQLMessageEncoder {
3131
case .execute(let execute):
3232
identifier = .E
3333
try execute.encode(to: encoder)
34+
case .password(let password):
35+
identifier = .p
36+
try password.encode(to: encoder)
3437
default: fatalError("Unsupported encodable type: \(type(of: message))")
3538
}
3639
encoder.updateSize()
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,26 @@
1+
import Foundation
2+
13
/// Authentication request returned by the server.
2-
struct PostgreSQLAuthenticationRequest: Decodable {
3-
/// Requested auth type.
4-
var type: PostgreSQLAuthenticationType
4+
enum PostgreSQLAuthenticationRequest: Decodable {
5+
/// AuthenticationOk
6+
case ok
7+
/// AuthenticationCleartextPassword
8+
case plaintext
9+
/// AuthenticationMD5Password
10+
case md5(Data)
11+
12+
/// See `Decodable.init(from:)`
13+
init(from decoder: Decoder) throws {
14+
let single = try decoder.singleValueContainer()
15+
let type = try single.decode(PostgreSQLAuthenticationType.self)
16+
switch type {
17+
case .ok: self = .ok
18+
case .plaintext: self = .plaintext
19+
case .md5:
20+
let salt = try single.decode(Int32.self)
21+
self = .md5(salt.data)
22+
}
23+
}
524
}
625

726
/// Supported authentication types.
@@ -10,6 +29,8 @@ enum PostgreSQLAuthenticationType: Int32, Decodable {
1029
case ok = 0
1130
/// Specifies that a clear-text password is required.
1231
case plaintext = 3
32+
/// Specifies that an MD5-encrypted password is required.
33+
case md5 = 5
1334
}
1435

1536
extension PostgreSQLAuthenticationType: CustomStringConvertible {
@@ -18,6 +39,7 @@ extension PostgreSQLAuthenticationType: CustomStringConvertible {
1839
switch self {
1940
case .ok: return "none"
2041
case .plaintext: return "plaintext password required"
42+
case .md5: return "md5-hashed password required"
2143
}
2244
}
2345
}

Sources/PostgreSQL/Message/PostgreSQLMessage.swift

+10
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,25 @@ enum PostgreSQLMessage {
77
case error(PostgreSQLDiagnosticResponse)
88
/// Identifies the message as a notice.
99
case notice(PostgreSQLDiagnosticResponse)
10+
/// One of the various authentication request message formats.
1011
case authenticationRequest(PostgreSQLAuthenticationRequest)
12+
/// Identifies the message as a password response.
13+
case password(PostgreSQLPasswordMessage)
14+
/// Identifies the message as a run-time parameter status report.
1115
case parameterStatus(PostgreSQLParameterStatus)
16+
/// Identifies the message as cancellation key data. The frontend must save these values if it wishes to be able to issue CancelRequest messages later.
1217
case backendKeyData(PostgreSQLBackendKeyData)
18+
/// Identifies the message type. ReadyForQuery is sent whenever the backend is ready for a new query cycle.
1319
case readyForQuery(PostgreSQLReadyForQuery)
20+
/// Identifies the message as a simple query.
1421
case query(PostgreSQLQuery)
22+
/// Identifies the message as a row description.
1523
case rowDescription(PostgreSQLRowDescription)
24+
/// Identifies the message as a data row.
1625
case dataRow(PostgreSQLDataRow)
1726
/// Identifies the message as a command-completed response.
1827
case close(PostgreSQLCloseResponse)
28+
/// Identifies the message as a Parse command.
1929
case parse(PostgreSQLParseRequest)
2030
/// Identifies the message as a parameter description.
2131
case parameterDescription(PostgreSQLParameterDescription)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/// Identifies the message as a password response. Note that this is also used for
2+
/// GSSAPI and SSPI response messages (which is really a design error, since the contained
3+
/// data is not a null-terminated string in that case, but can be arbitrary binary data).
4+
struct PostgreSQLPasswordMessage: Encodable {
5+
/// The password (encrypted, if requested).
6+
var password: String
7+
}

Tests/PostgreSQLTests/PostgreSQLConnectionTests.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import TCP
66

77
class PostgreSQLConnectionTests: XCTestCase {
88
func testVersion() throws {
9-
let (client, eventLoop) = try PostgreSQLConnection.makeTest()
9+
let (client, eventLoop) = try! PostgreSQLConnection.makeTest()
1010
let results = try client.simpleQuery("SELECT version();").await(on: eventLoop)
1111
try XCTAssert(results[0]["version"]?.decode(String.self).contains("10.1") == true)
1212
}
@@ -285,7 +285,7 @@ extension PostgreSQLConnection {
285285
static func makeTest() throws -> (PostgreSQLConnection, EventLoop) {
286286
let eventLoop = try DefaultEventLoop(label: "codes.vapor.postgresql.client.test")
287287
let client = try PostgreSQLConnection.connect(on: eventLoop)
288-
_ = try client.authenticate(username: "postgres").await(on: eventLoop)
288+
_ = try client.authenticate(username: "secure", database: "tanner").await(on: eventLoop)
289289
return (client, eventLoop)
290290
}
291291
}

0 commit comments

Comments
 (0)