Intro
- I have a microservice written in Akka HTTP which communicates with the frontend via an REST API.
- I use Scala's Slick library to communicate with the PostgreSQL DBMS.
- I want secure the API with HTTP's basic authentication mechanism (the frontend sends an username and password to the microservice in each request).
- The extract credentials from each HTTP API call I want to add into Slick's actions in order to ensure via "JDBC Authentication" that the user is authorized to manipulate a given set of data.
Problem
While its obvious for me how to accomplish this with plain Java's DriverManager by creating each new connection with a set of credentials and using this connection in order to perform future operations, the Slick library offers something like this:
val db = Database.forURL(jdbcUrl, superuser, password)
but the documentation states that only one such instance should be created for the whole life of an application:
Note: A Database object usually manages a thread pool and a connection pool. You should always shut it down properly when it is no longer needed (unless the JVM process terminates anyway). Do not create a new Database for every database operation. A single instance is meant to be kept alive for the entire lifetime your your application.
So I am stick to the one db
singleton which always runs all actions with the superuser credentials.
I am new to Slick and I don't know how to add an additional username and password in order to send it together with an action (SQL, DDL statement) to the DBMS that can verify if the user is permit to perform this action. Here is an example of what I want to achieve:
override def removeDatabase(name: String): Future[Unit] =
db.run(sqlu"""DROP DATABASE IF EXISTS #${name.toLowerCase};""").map(_ => ())
To this drop action I would like add the credentials extract from an HTTP request in order to get an JDBC authentication to ensure that the current user performing this action is authorized to delete this database.
What I have did so far?
According to the Slick documentation:
In order to drop down to the JDBC level for functionality that is not available in Slick, you can use a SimpleDBIO action which is run on a database thread and gets access to the JDBC Connection:
val getAutoCommit = SimpleDBIO[Boolean](_.connection.getAutoCommit)
I tried this:
override def removeDatabase(name: String): Future[Unit] = {
val dropStatement = SimpleDBIO[Unit] { session => {
val currentConnection = session.connection
currentConnection.setClientInfo("username", "testUsername")
currentConnection.setClientInfo("password", "invalidPwd")
println(">>> username: " + currentConnection.getClientInfo("username"))
println(">>> password: " + currentConnection.getClientInfo("password"))
sqlu"""DROP DATABASE IF EXISTS #${name.toLowerCase};"""
}
}
db.run(dropStatement).map(_ => ())
}
But unfortunately it doesn't have any impact, the passed credentials to the client info are ignored:
>>> username: null
>>> password: null