Skip to content

Commit

Permalink
✨ Add support for cleartext password authentication
Browse files Browse the repository at this point in the history
  • Loading branch information
giacomocavalieri committed Aug 11, 2024
1 parent 5ead037 commit 2b2e3f9
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 7 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## Unreleased

- Squirrel now supports plaintext password authentication.
([Giacomo Cavalieri](https://github.com/giacomocavalieri))

- Squirrel now uses `"postgres"` as the default user in case the `PGUSER`
environment variable is not set.
([Giacomo Cavalieri](https://github.com/giacomocavalieri))
Expand Down
51 changes: 46 additions & 5 deletions src/squirrel/internal/database/postgres.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,11 @@ import gleam/string
import squirrel/internal/database/postgres_protocol as pg
import squirrel/internal/error.{
type Error, type Pointer, type ValueIdentifierError, ByteIndex,
CannotParseQuery, PgCannotAuthenticate, PgCannotConnectUserDatabase,
PgCannotDecodeReceivedMessage, PgCannotDescribeQuery, PgCannotReceiveMessage,
PgCannotSendMessage, Pointer, QueryHasInvalidColumn, QueryHasUnsupportedType,
CannotParseQuery, PgCannotConnectUserDatabase, PgCannotDecodeReceivedMessage,
PgCannotDescribeQuery, PgCannotReceiveMessage, PgCannotSendMessage,
PgFailedCleartextAuthentication, PgUnexpectedAuthMessage,
PgUnsupportedAuthentication, Pointer, QueryHasInvalidColumn,
QueryHasUnsupportedType,
}
import squirrel/internal/eval_extra
import squirrel/internal/gleam
Expand Down Expand Up @@ -245,13 +247,23 @@ pub fn main(
}

fn authenticate(connection: ConnectionOptions) -> Db(Nil) {
let params = [#("user", connection.user), #("database", connection.database)]
let ConnectionOptions(user: user, database: database, password: password, ..) =
connection

let params = [#("user", user), #("database", database)]
use _ <- eval.try(send(pg.FeStartupMessage(params)))

use msg <- eval.try(receive())
use _ <- eval.try(case msg {
pg.BeAuthenticationOk -> eval.return(Nil)
_ -> unexpected_message(PgCannotAuthenticate, "AuthenticationOk", msg)
pg.BeAuthenticationCleartextPassword ->
cleartext_authenticate(user, password)
pg.BeAuthenticationMD5Password(salt) -> md5_authenticate(password, salt)
pg.BeAuthenticationGSS -> unsupported_authentication("GSS")
pg.BeAuthenticationSASL(_) -> unsupported_authentication("SASL")
pg.BeAuthenticationSSPI -> unsupported_authentication("SSPI")
pg.BeAuthenticationKerberosV5 -> unsupported_authentication("KerberosV5")
_ -> unexpected_message(PgUnexpectedAuthMessage, "Auth method", msg)
})

use _ <- eval.try(
Expand All @@ -268,6 +280,29 @@ fn authenticate(connection: ConnectionOptions) -> Db(Nil) {
eval.return(Nil)
}

fn cleartext_authenticate(user: String, password: String) -> Db(Nil) {
use _ <- eval.try(send(pg.FeAmbigous(pg.FePasswordMessage(password))))
use msg <- eval.try(receive())
case msg {
pg.BeAuthenticationOk -> eval.return(Nil)
pg.BeErrorResponse(_) ->
eval.throw(PgFailedCleartextAuthentication(user: user, password: password))

// The response should only ever be Ok or Error (in case the password is
// not correct).
_ ->
unexpected_message(
PgUnexpectedAuthMessage,
"AuthenticationOk ok ErrorRespose",
msg,
)
}
}

fn md5_authenticate(_password: String, _salt: BitArray) -> Db(Nil) {
unsupported_authentication("md5")
}

/// Returns type information about a query.
///
fn infer_types(query: UntypedQuery) -> Db(TypedQuery) {
Expand Down Expand Up @@ -736,6 +771,12 @@ fn unexpected_message(
builder(expected, string.inspect(got)) |> eval.throw
}

/// Throws a `PgUnexpectedAuthentication` error.
///
fn unsupported_authentication(auth: String) {
eval.throw(PgUnsupportedAuthentication(auth))
}

/// Looks up for a type with the given id in a global cache.
/// If the type is present it immediately returns it.
/// Otherwise it runs the database action to fetch it and then caches it to be
Expand Down
37 changes: 35 additions & 2 deletions src/squirrel/internal/error.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,16 @@ pub type Error {

/// When authentication workflow goes wrong.
///
PgCannotAuthenticate(expected: String, got: String)
PgUnexpectedAuthMessage(expected: String, got: String)

/// When cleartext authentication fails for a given user.
///
PgFailedCleartextAuthentication(user: String, password: String)

/// When the server is expecting an authentication method that is not
/// currently supported.
///
PgUnsupportedAuthentication(auth: String)

/// When there's an error with the underlying socket and I cannot send
/// messages to the server.
Expand Down Expand Up @@ -147,14 +156,38 @@ pub fn to_doc(error: Error) -> Document {
<> " environment variables.",
)

PgCannotAuthenticate(expected: expected, got: got) ->
PgFailedCleartextAuthentication(user: user, password: password) -> {
let error =
printable_error("Cannot authenticate")
|> add_paragraph(
"Invalid password for user " <> style_inline_code(user) <> ".",
)
case password {
"" ->
error
|> hint("You can change the default password used to
authenticate by setting the " <> style_inline_code("PGPASSWORD") <> " environment variable.")
_ -> error
}
}

PgUnexpectedAuthMessage(expected: expected, got: got) ->
printable_error("Cannot authenticate")
|> add_paragraph(
"I ran into an unexpected problem while trying to authenticate with the
Postgres server. This is most definitely a bug!",
)
|> report_bug("Expected: " <> expected <> ", Got: " <> got)

PgUnsupportedAuthentication(auth: auth) ->
printable_error("Unsupported authentication method")
|> add_paragraph(
"The Postgres server is asking to authenticate using the "
<> auth
<> " authentication mathod, which I currently do not support.",
)
|> call_to_action(for: "this authentication method to be supported")

PgCannotSendMessage(reason: reason) ->
printable_error("Cannot send message")
|> add_paragraph(
Expand Down

0 comments on commit 2b2e3f9

Please sign in to comment.