aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAntoine A <>2024-01-05 12:17:47 +0000
committerAntoine A <>2024-01-05 12:17:47 +0000
commit55006bcb8c3f4fb8986fb93deaa0e2213eeb4832 (patch)
tree12601d38d00fbbb19fc2f8f80745c346335b2bd3
parent9aea450a7173fb10e0bf299eb1685e17c909f705 (diff)
downloadlibeufin-55006bcb8c3f4fb8986fb93deaa0e2213eeb4832.tar.gz
libeufin-55006bcb8c3f4fb8986fb93deaa0e2213eeb4832.tar.bz2
libeufin-55006bcb8c3f4fb8986fb93deaa0e2213eeb4832.zip
Simplify authentification logic and improve documentation
-rw-r--r--Makefile5
-rw-r--r--bank/src/main/kotlin/tech/libeufin/bank/Authentication.kt182
-rw-r--r--bank/src/main/kotlin/tech/libeufin/bank/BankIntegrationApi.kt6
-rw-r--r--bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt71
-rw-r--r--bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt16
-rw-r--r--bank/src/main/kotlin/tech/libeufin/bank/db/AccountDAO.kt11
-rw-r--r--bank/src/main/kotlin/tech/libeufin/bank/db/TokenDAO.kt10
-rw-r--r--bank/src/main/kotlin/tech/libeufin/bank/helpers.kt18
-rw-r--r--build.gradle5
-rw-r--r--util/src/main/kotlin/HTTP.kt42
-rw-r--r--util/src/main/kotlin/strings.kt5
11 files changed, 150 insertions, 221 deletions
diff --git a/Makefile b/Makefile
index 5505c35e..343a6660 100644
--- a/Makefile
+++ b/Makefile
@@ -101,3 +101,8 @@ check: install-nobuild-bank-files
.PHONY: test
test: install-nobuild-bank-files
./gradlew test --tests $(test) -i
+
+.PHONY: doc
+doc:
+ ./gradlew dokkaHtmlMultiModule
+ open build/dokka/htmlMultiModule/index.html
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Authentication.kt b/bank/src/main/kotlin/tech/libeufin/bank/Authentication.kt
index 4a83e94e..7f0fac7b 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/Authentication.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/Authentication.kt
@@ -34,13 +34,35 @@ import tech.libeufin.util.*
private val logger: Logger = LoggerFactory.getLogger("tech.libeufin.bank.Authentication")
+/** Used to store if the currenly authenticated user is admin */
private val AUTH_IS_ADMIN = AttributeKey<Boolean>("is_admin");
+/** Used to store used auth token */
+private val AUTH_TOKEN = AttributeKey<ByteArray>("auth_token");
-/** Restrict route access to admin */
+/** Get username of the request account */
+val ApplicationCall.username: String get() = expectParameter("USERNAME")
+/** Get username of the request account */
+val PipelineContext<Unit, ApplicationCall>.username: String get() = call.username
+
+/** Check if current auth account is admin */
+val ApplicationCall.isAdmin: Boolean get() = attributes.getOrNull(AUTH_IS_ADMIN) ?: false
+/** Check if current auth account is admin */
+val PipelineContext<Unit, ApplicationCall>.isAdmin: Boolean get() = call.isAdmin
+
+/** Check auth token used for authentification */
+val ApplicationCall.authToken: ByteArray? get() = attributes.getOrNull(AUTH_TOKEN)
+
+/**
+ * Create an admin authenticated route for [scope].
+ *
+ * If [enforce], only admin can access this route.
+ *
+ * You can check is the currently authenticated user is admin using [isAdmin].
+ **/
fun Route.authAdmin(db: Database, scope: TokenScope, enforce: Boolean = true, callback: Route.() -> Unit): Route =
intercept(callback) {
if (enforce) {
- val login = context.authenticateBankRequest(db, scope) ?: throw unauthorized("Bad login")
+ val login = context.authenticateBankRequest(db, scope)
if (login != "admin") {
throw unauthorized("Only administrator allowed")
}
@@ -56,10 +78,17 @@ fun Route.authAdmin(db: Database, scope: TokenScope, enforce: Boolean = true, ca
}
-/** Authenticate and check access rights */
+/**
+ * Create an authenticated route for [scope].
+ *
+ * If [allowAdmin], admin is allowed to auth for any user.
+ * If [requireAdmin], only admin can access this route.
+ *
+ * You can check is the currently authenticated user is admin using [isAdmin].
+ **/
fun Route.auth(db: Database, scope: TokenScope, allowAdmin: Boolean = false, requireAdmin: Boolean = false, callback: Route.() -> Unit): Route =
intercept(callback) {
- val authLogin = context.authenticateBankRequest(db, scope) ?: throw unauthorized("Bad login")
+ val authLogin = context.authenticateBankRequest(db, scope)
if (requireAdmin && authLogin != "admin") {
if (authLogin != "admin") {
throw unauthorized("Only administrator allowed")
@@ -73,134 +102,87 @@ fun Route.auth(db: Database, scope: TokenScope, allowAdmin: Boolean = false, req
context.attributes.put(AUTH_IS_ADMIN, authLogin == "admin")
}
-val PipelineContext<Unit, ApplicationCall>.username: String get() = call.username
-val PipelineContext<Unit, ApplicationCall>.isAdmin: Boolean get() = call.isAdmin
-val ApplicationCall.username: String get() = expectUriComponent("USERNAME")
-val ApplicationCall.isAdmin: Boolean get() = attributes.getOrNull(AUTH_IS_ADMIN) ?: false
-
/**
- * This function tries to authenticate the call according
- * to the scheme that is mentioned in the Authorization header.
- * The allowed schemes are either 'HTTP basic auth' or 'bearer token'.
+ * Authenticate an HTTP request for [requiredScope] according to the scheme that is mentioned
+ * in the Authorization header.
+ * The allowed schemes are either 'Basic' or 'Bearer'.
*
- * requiredScope can be either "readonly" or "readwrite".
- *
- * Returns the authenticated customer login, or null if they failed.
+ * Returns the authenticated customer login.
*/
-private suspend fun ApplicationCall.authenticateBankRequest(db: Database, requiredScope: TokenScope): String? {
- // Extracting the Authorization header.
- val header = getAuthorizationRawHeader(this.request)
+private suspend fun ApplicationCall.authenticateBankRequest(db: Database, requiredScope: TokenScope): String {
+ val header = request.headers["Authorization"]
+
+ // Basic auth challenge
if (header == null) {
- // Basic auth challenge
response.header(HttpHeaders.WWWAuthenticate, "Basic")
throw unauthorized(
- "Authorization header not found.",
+ "Authorization header not found",
TalerErrorCode.GENERIC_PARAMETER_MISSING
)
}
-
- val authDetails = getAuthorizationDetails(header) ?: throw badRequest(
- "Authorization is invalid.",
- TalerErrorCode.GENERIC_HTTP_HEADERS_MALFORMED
- )
- return when (authDetails.scheme) {
- "Basic" -> doBasicAuth(db, authDetails.content)
- "Bearer" -> doTokenAuth(db, authDetails.content, requiredScope)
- else -> throw unauthorized("Authorization method wrong or not supported.")
- }
-}
-// Get the auth token (stripped of the bearer-token:-prefix)
-// IF the call was authenticated with it.
-fun ApplicationCall.getAuthToken(): String? {
- val h = getAuthorizationRawHeader(this.request) ?: return null
- val authDetails = getAuthorizationDetails(h) ?: throw badRequest(
- "Authorization header is malformed.", TalerErrorCode.GENERIC_HTTP_HEADERS_MALFORMED
- )
- if (authDetails.scheme == "Bearer") return splitBearerToken(authDetails.content) ?: throw throw badRequest(
- "Authorization header is malformed (could not strip the prefix from Bearer token).",
+ // Parse header
+ val (scheme, content) = header.splitOnce(" ") ?: throw badRequest(
+ "Authorization is invalid",
TalerErrorCode.GENERIC_HTTP_HEADERS_MALFORMED
)
- return null // Not a Bearer token case.
+ return when (scheme) {
+ "Basic" -> doBasicAuth(db, content)
+ "Bearer" -> doTokenAuth(db, content, requiredScope)
+ else -> throw unauthorized("Authorization method wrong or not supported")
+ }
}
-
/**
- * Performs the HTTP basic authentication. Returns the
- * authenticated customer login on success, or null otherwise.
+ * Performs the HTTP Basic Authentication.
+ *
+ * Returns the authenticated customer login
*/
-private suspend fun doBasicAuth(db: Database, encodedCredentials: String): String? {
- val plainUserAndPass = String(base64ToBytes(encodedCredentials), Charsets.UTF_8) // :-separated
- val userAndPassSplit = plainUserAndPass.split(
- ":",
- /**
- * this parameter allows colons to occur in passwords.
- * Without this, passwords that have colons would be split
- * and become meaningless.
- */
- limit = 2
- )
- if (userAndPassSplit.size != 2) throw badRequest(
- "Malformed Basic auth credentials found in the Authorization header.",
+private suspend fun doBasicAuth(db: Database, encoded: String): String {
+ val decoded = String(base64ToBytes(encoded), Charsets.UTF_8)
+ val (login, plainPassword) = decoded.splitOnce(":") ?: throw badRequest(
+ "Malformed Basic auth credentials found in the Authorization header",
TalerErrorCode.GENERIC_HTTP_HEADERS_MALFORMED
)
- val (login, plainPassword) = userAndPassSplit
- val passwordHash = db.account.passwordHash(login) ?: throw unauthorized("Bad password")
- if (!CryptoUtil.checkpw(plainPassword, passwordHash)) return null
+ val hash = db.account.passwordHash(login) ?: throw unauthorized("Unknown account")
+ if (!CryptoUtil.checkpw(plainPassword, hash)) throw unauthorized("Bad password")
return login
}
/**
- * This function takes a prefixed Bearer token, removes the
- * secret-token:-prefix and returns it. Returns null, if the
- * input is invalid.
+ * Performs the secret-token HTTP Bearer Authentication.
+ *
+ * Returns the authenticated customer login
*/
-private fun splitBearerToken(tok: String): String? {
- val tokenSplit = tok.split(":", limit = 2)
- if (tokenSplit.size != 2) return null
- if (tokenSplit[0] != "secret-token") return null
- return tokenSplit[1]
-}
-
-/* Performs the secret-token authentication. Returns the
- * authenticated customer login on success, null otherwise. */
-private suspend fun doTokenAuth(
+private suspend fun ApplicationCall.doTokenAuth(
db: Database,
- token: String,
+ bearer: String,
requiredScope: TokenScope,
-): String? {
- val bareToken = splitBearerToken(token) ?: throw badRequest(
+): String {
+ if (!bearer.startsWith("secret-token:")) throw badRequest(
"Bearer token malformed",
TalerErrorCode.GENERIC_HTTP_HEADERS_MALFORMED
)
- val tokenBytes = try {
- Base32Crockford.decode(bareToken)
+ val decoded = try {
+ Base32Crockford.decode(bearer.slice(13..bearer.length-1))
} catch (e: Exception) {
throw badRequest(
e.message, TalerErrorCode.GENERIC_HTTP_HEADERS_MALFORMED
)
}
- val maybeToken: BearerToken? = db.token.get(tokenBytes)
- if (maybeToken == null) {
- logger.error("Auth token not found")
- return null
- }
- if (maybeToken.expirationTime.isBefore(Instant.now())) {
- logger.error("Auth token is expired")
- return null
- }
- if (maybeToken.scope == TokenScope.readonly && requiredScope == TokenScope.readwrite) {
- logger.error("Auth token has insufficient scope")
- return null
- }
- if (!maybeToken.isRefreshable && requiredScope == TokenScope.refreshable) {
- logger.error("Could not refresh unrefreshable token")
- return null
+ val token: BearerToken = db.token.get(decoded) ?: throw unauthorized("Unknown token")
+ when {
+ token.expirationTime.isBefore(Instant.now())
+ -> throw unauthorized("Expired auth token")
+
+ token.scope == TokenScope.readonly && requiredScope == TokenScope.readwrite
+ -> throw unauthorized("Auth token has insufficient scope")
+
+ !token.isRefreshable && requiredScope == TokenScope.refreshable
+ -> throw unauthorized("Unrefreshable token")
}
- // Getting the related username.
- return db.account.login(maybeToken.bankCustomer) ?: throw libeufinError(
- HttpStatusCode.InternalServerError,
- "Customer not found, despite token mentions it.",
- TalerErrorCode.GENERIC_INTERNAL_INVARIANT_FAILURE
- )
+
+ attributes.put(AUTH_TOKEN, decoded)
+
+ return token.login
} \ No newline at end of file
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/BankIntegrationApi.kt b/bank/src/main/kotlin/tech/libeufin/bank/BankIntegrationApi.kt
index 22c573ae..ff3e9105 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/BankIntegrationApi.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/BankIntegrationApi.kt
@@ -40,7 +40,7 @@ fun Routing.bankIntegrationApi(db: Database, ctx: BankConfig) {
// Note: wopid acts as an authentication token.
get("/taler-integration/withdrawal-operation/{wopid}") {
- val uuid = call.uuidUriComponent("wopid")
+ val uuid = call.uuidParameter("wopid")
val params = StatusParams.extract(call.request.queryParameters)
val op = db.withdrawal.pollStatus(uuid, params) ?: throw notFound(
"Withdrawal operation '$uuid' not found",
@@ -57,7 +57,7 @@ fun Routing.bankIntegrationApi(db: Database, ctx: BankConfig) {
))
}
post("/taler-integration/withdrawal-operation/{wopid}") {
- val opId = call.uuidUriComponent("wopid")
+ val opId = call.uuidParameter("wopid")
val req = call.receive<BankWithdrawalOperationPostRequest>()
val res = db.withdrawal.setDetails(
@@ -100,7 +100,7 @@ fun Routing.bankIntegrationApi(db: Database, ctx: BankConfig) {
}
}
post("/taler-integration/withdrawal-operation/{wopid}/abort") {
- val opId = call.uuidUriComponent("wopid")
+ val opId = call.uuidParameter("wopid")
when (db.withdrawal.abort(opId)) {
AbortResult.UnknownOperation -> throw notFound(
"Withdrawal operation $opId not found",
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt b/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt
index 9966edf5..58fb208a 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt
@@ -77,41 +77,36 @@ private fun Routing.coreBankTokenApi(db: Database) {
val TOKEN_DEFAULT_DURATION: Duration = Duration.ofDays(1L)
auth(db, TokenScope.refreshable) {
post("/accounts/{USERNAME}/token") {
- val maybeAuthToken = call.getAuthToken()
+ val existingToken = call.authToken
val req = call.receive<TokenRequest>()
- /**
- * This block checks permissions ONLY IF the call was authenticated with a token. Basic auth
- * gets always granted.
- */
- if (maybeAuthToken != null) {
- val tokenBytes = Base32Crockford.decode(maybeAuthToken)
- val refreshingToken =
- db.token.get(tokenBytes)
- ?: throw internalServerError(
- "Token used to auth not found in the database!"
- )
+
+ if (existingToken != null) {
+ // This block checks permissions ONLY IF the call was authenticated with a token
+ val refreshingToken = db.token.get(existingToken) ?: throw internalServerError(
+ "Token used to auth not found in the database!"
+ )
if (refreshingToken.scope == TokenScope.readonly && req.scope == TokenScope.readwrite)
- throw forbidden(
- "Cannot generate RW token from RO",
- TalerErrorCode.GENERIC_TOKEN_PERMISSION_INSUFFICIENT
- )
+ throw forbidden(
+ "Cannot generate RW token from RO",
+ TalerErrorCode.GENERIC_TOKEN_PERMISSION_INSUFFICIENT
+ )
}
val tokenBytes = ByteArray(32).apply { Random.nextBytes(this) }
val tokenDuration: Duration = req.duration?.d_us ?: TOKEN_DEFAULT_DURATION
val creationTime = Instant.now()
- val expirationTimestamp =
- if (tokenDuration == ChronoUnit.FOREVER.duration) {
- logger.debug("Creating 'forever' token.")
- Instant.MAX
- } else {
- try {
- logger.debug("Creating token with days duration: ${tokenDuration.toDays()}")
- creationTime.plus(tokenDuration)
- } catch (e: Exception) {
- throw badRequest("Bad token duration: ${e.message}")
- }
+ val expirationTimestamp =
+ if (tokenDuration == ChronoUnit.FOREVER.duration) {
+ logger.debug("Creating 'forever' token.")
+ Instant.MAX
+ } else {
+ try {
+ logger.debug("Creating token with days duration: ${tokenDuration.toDays()}")
+ creationTime.plus(tokenDuration)
+ } catch (e: Exception) {
+ throw badRequest("Bad token duration: ${e.message}")
}
+ }
if (!db.token.create(
login = username,
content = tokenBytes,
@@ -132,8 +127,8 @@ private fun Routing.coreBankTokenApi(db: Database) {
}
auth(db, TokenScope.readonly) {
delete("/accounts/{USERNAME}/token") {
- val token = call.getAuthToken() ?: throw badRequest("Basic auth not supported here.")
- db.token.delete(Base32Crockford.decode(token))
+ val token = call.authToken ?: throw badRequest("Basic auth not supported here.")
+ db.token.delete(token)
call.respond(HttpStatusCode.NoContent)
}
}
@@ -349,7 +344,7 @@ private fun Routing.coreBankTransactionsApi(db: Database, ctx: BankConfig) {
}
}
get("/accounts/{USERNAME}/transactions/{T_ID}") {
- val tId = call.longUriComponent("T_ID")
+ val tId = call.longParameter("T_ID")
val tx = db.transaction.get(tId, username) ?: throw notFound(
"Bank transaction '$tId' not found",
TalerErrorCode.BANK_TRANSACTION_NOT_FOUND
@@ -424,7 +419,7 @@ private fun Routing.coreBankWithdrawalApi(db: Database, ctx: BankConfig) {
}
}
post("/accounts/{USERNAME}/withdrawals/{withdrawal_id}/abort") {
- val opId = call.uuidUriComponent("withdrawal_id")
+ val opId = call.uuidParameter("withdrawal_id")
when (db.withdrawal.abort(opId)) {
AbortResult.UnknownOperation -> throw notFound(
"Withdrawal operation $opId not found",
@@ -438,7 +433,7 @@ private fun Routing.coreBankWithdrawalApi(db: Database, ctx: BankConfig) {
}
}
post("/accounts/{USERNAME}/withdrawals/{withdrawal_id}/confirm") {
- val opId = call.uuidUriComponent("withdrawal_id")
+ val opId = call.uuidParameter("withdrawal_id")
when (db.withdrawal.confirm(opId, Instant.now())) {
WithdrawalConfirmationResult.UnknownOperation -> throw notFound(
"Withdrawal operation $opId not found",
@@ -465,7 +460,7 @@ private fun Routing.coreBankWithdrawalApi(db: Database, ctx: BankConfig) {
}
}
get("/withdrawals/{withdrawal_id}") {
- val uuid = call.uuidUriComponent("withdrawal_id")
+ val uuid = call.uuidParameter("withdrawal_id")
val params = StatusParams.extract(call.request.queryParameters)
val op = db.withdrawal.pollInfo(uuid, params) ?: throw notFound(
"Withdrawal operation '$uuid' not found",
@@ -474,7 +469,7 @@ private fun Routing.coreBankWithdrawalApi(db: Database, ctx: BankConfig) {
call.respond(op)
}
post("/withdrawals/{withdrawal_id}/abort") {
- val opId = call.uuidUriComponent("withdrawal_id")
+ val opId = call.uuidParameter("withdrawal_id")
when (db.withdrawal.abort(opId)) {
AbortResult.UnknownOperation -> throw notFound(
"Withdrawal operation $opId not found",
@@ -488,7 +483,7 @@ private fun Routing.coreBankWithdrawalApi(db: Database, ctx: BankConfig) {
}
}
post("/withdrawals/{withdrawal_id}/confirm") {
- val opId = call.uuidUriComponent("withdrawal_id")
+ val opId = call.uuidParameter("withdrawal_id")
when (db.withdrawal.confirm(opId, Instant.now())) {
WithdrawalConfirmationResult.UnknownOperation -> throw notFound(
"Withdrawal operation $opId not found",
@@ -590,7 +585,7 @@ private fun Routing.coreBankCashoutApi(db: Database, ctx: BankConfig) = conditio
}
}
post("/accounts/{USERNAME}/cashouts/{CASHOUT_ID}/abort") {
- val id = call.longUriComponent("CASHOUT_ID")
+ val id = call.longParameter("CASHOUT_ID")
when (db.cashout.abort(id, username)) {
AbortResult.UnknownOperation -> throw notFound(
"Cashout operation $id not found",
@@ -605,7 +600,7 @@ private fun Routing.coreBankCashoutApi(db: Database, ctx: BankConfig) = conditio
}
post("/accounts/{USERNAME}/cashouts/{CASHOUT_ID}/confirm") {
val req = call.receive<CashoutConfirm>()
- val id = call.longUriComponent("CASHOUT_ID")
+ val id = call.longParameter("CASHOUT_ID")
when (db.cashout.confirm(
id = id,
login = username,
@@ -647,7 +642,7 @@ private fun Routing.coreBankCashoutApi(db: Database, ctx: BankConfig) = conditio
}
auth(db, TokenScope.readonly) {
get("/accounts/{USERNAME}/cashouts/{CASHOUT_ID}") {
- val id = call.longUriComponent("CASHOUT_ID")
+ val id = call.longParameter("CASHOUT_ID")
val cashout = db.cashout.get(id, username) ?: throw notFound(
"Cashout operation $id not found",
TalerErrorCode.BANK_TRANSACTION_NOT_FOUND
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt b/bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt
index 72633c73..c827d406 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt
@@ -247,24 +247,12 @@ enum class TokenScope {
refreshable // Not spec'd as a scope!
}
-/**
- * Convenience type to set/get authentication tokens to/from
- * the database.
- */
data class BearerToken(
- val content: ByteArray,
val scope: TokenScope,
- val isRefreshable: Boolean = false,
+ val isRefreshable: Boolean,
val creationTime: Instant,
val expirationTime: Instant,
- /**
- * Serial ID of the database row that hosts the bank customer
- * that is associated with this token. NOTE: if the token is
- * refreshed by a client that doesn't have a user+password login
- * in the system, the creator remains always the original bank
- * customer that created the very first token.
- */
- val bankCustomer: Long
+ val login: String
)
@Serializable
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/AccountDAO.kt b/bank/src/main/kotlin/tech/libeufin/bank/db/AccountDAO.kt
index 276e5e56..5743eb8a 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/db/AccountDAO.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/db/AccountDAO.kt
@@ -348,17 +348,6 @@ class AccountDAO(private val db: Database) {
}
}
- /** Get login of account [id] */
- suspend fun login(id: Long): String? = db.conn { conn ->
- val stmt = conn.prepareStatement("""
- SELECT login FROM customers WHERE customer_id=?
- """)
- stmt.setLong(1, id)
- stmt.oneOrNull {
- it.getString(1)
- }
- }
-
/** Get bank info of account [login] */
suspend fun bankInfo(login: String): BankInfo? = db.conn { conn ->
val stmt = conn.prepareStatement("""
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/TokenDAO.kt b/bank/src/main/kotlin/tech/libeufin/bank/db/TokenDAO.kt
index 2791d92f..19c214cc 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/db/TokenDAO.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/db/TokenDAO.kt
@@ -63,21 +63,21 @@ class TokenDAO(private val db: Database) {
suspend fun get(token: ByteArray): BearerToken? = db.conn { conn ->
val stmt = conn.prepareStatement("""
SELECT
- expiration_time,
creation_time,
- bank_customer,
+ expiration_time,
+ login,
scope,
is_refreshable
FROM bearer_tokens
+ JOIN customers ON bank_customer=customer_id
WHERE content=?;
""")
stmt.setBytes(1, token)
stmt.oneOrNull {
BearerToken(
- content = token,
- creationTime = it.getLong("creation_time").microsToJavaInstant() ?: throw faultyTimestampByBank(),
+ creationTime = it.getLong("creation_time").microsToJavaInstant() ?: throw faultyDurationByClient(),
expirationTime = it.getLong("expiration_time").microsToJavaInstant() ?: throw faultyDurationByClient(),
- bankCustomer = it.getLong("bank_customer"),
+ login = it.getString("login"),
scope = TokenScope.valueOf(it.getString("scope")),
isRefreshable = it.getBoolean("is_refreshable")
)
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/helpers.kt b/bank/src/main/kotlin/tech/libeufin/bank/helpers.kt
index c8e96e0e..36c49f1f 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/helpers.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/helpers.kt
@@ -41,9 +41,9 @@ import tech.libeufin.util.*
private val logger: Logger = LoggerFactory.getLogger("tech.libeufin.bank.helpers")
-fun ApplicationCall.expectUriComponent(componentName: String) =
- maybeUriComponent(componentName) ?: throw badRequest(
- "No username found in the URI",
+fun ApplicationCall.expectParameter(name: String) =
+ parameters[name] ?: throw badRequest(
+ "Missing '$name' param",
TalerErrorCode.GENERIC_PARAMETER_MISSING
)
@@ -90,17 +90,17 @@ fun getWithdrawalConfirmUrl(
return baseUrl.replace("{woid}", wopId.toString())
}
-fun ApplicationCall.uuidUriComponent(name: String): UUID {
+fun ApplicationCall.uuidParameter(name: String): UUID {
try {
- return UUID.fromString(expectUriComponent(name))
+ return UUID.fromString(expectParameter(name))
} catch (e: Exception) {
throw badRequest("UUID uri component malformed: ${e.message}")
}
}
-fun ApplicationCall.longUriComponent(name: String): Long {
+fun ApplicationCall.longParameter(name: String): Long {
try {
- return expectUriComponent(name).toLong()
+ return expectParameter(name).toLong()
} catch (e: Exception) {
throw badRequest("Long uri component malformed: ${e.message}")
}
@@ -153,4 +153,6 @@ fun Route.conditional(implemented: Boolean, callback: Route.() -> Unit): Route =
if (!implemented) {
throw libeufinError(HttpStatusCode.NotImplemented, "API not implemented", TalerErrorCode.END)
}
- } \ No newline at end of file
+ }
+
+ \ No newline at end of file
diff --git a/build.gradle b/build.gradle
index 632f7c52..01941c05 100644
--- a/build.gradle
+++ b/build.gradle
@@ -4,6 +4,7 @@ import org.apache.tools.ant.filters.ReplaceTokens
plugins {
id("org.jetbrains.kotlin.jvm") version "1.9.20"
+ id("org.jetbrains.dokka") version "1.9.10"
id("idea")
id("java-library")
id("maven-publish")
@@ -33,6 +34,10 @@ allprojects {
}
}
+subprojects {
+ apply plugin: 'org.jetbrains.dokka'
+}
+
idea {
module {
excludeDirs += file("frontend")
diff --git a/util/src/main/kotlin/HTTP.kt b/util/src/main/kotlin/HTTP.kt
index 20ec3817..cedd07b6 100644
--- a/util/src/main/kotlin/HTTP.kt
+++ b/util/src/main/kotlin/HTTP.kt
@@ -45,46 +45,4 @@ fun ApplicationRequest.getBaseUrl(): String? {
encodedPath = "/"
}
}
-}
-
-fun ApplicationCall.maybeUriComponent(name: String): String? {
- val ret: String? = this.parameters[name]
- if (ret == null) {
- logger.error("Component $name not found in URI")
- return null
- }
- return ret
-}
-
-// Extracts the Authorization:-header line, or returns null if not found.
-fun getAuthorizationRawHeader(request: ApplicationRequest): String? {
- return request.headers["Authorization"] ?: run {
- logger.error("Authorization header not found")
- return null
- }
-}
-
-/**
- * Holds the details contained in an Authorization header.
- * The content is held as it was found in the header and supposed
- * to be processed according to the scheme.
- */
-data class AuthorizationDetails(
- val scheme: String,
- val content: String
-)
-
-// Returns the authorization scheme mentioned in the Auth header,
-// or null if that could not be found.
-fun getAuthorizationDetails(authorizationHeader: String): AuthorizationDetails? {
- val split = authorizationHeader.split(" ")
- if (split.isEmpty()) {
- logger.error("malformed Authorization header: contains no space")
- return null
- }
- if (split.size != 2) {
- logger.error("malformed Authorization header: contains more than one space")
- return null
- }
- return AuthorizationDetails(scheme = split[0], content = split[1])
} \ No newline at end of file
diff --git a/util/src/main/kotlin/strings.kt b/util/src/main/kotlin/strings.kt
index 54d6d5a3..1d7b49a4 100644
--- a/util/src/main/kotlin/strings.kt
+++ b/util/src/main/kotlin/strings.kt
@@ -83,3 +83,8 @@ fun getQueryParam(uriQueryString: String, param: String): String? {
return null
}
+fun String.splitOnce(pat: String): Pair<String, String>? {
+ val split = split(pat, limit=2);
+ if (split.size != 2) return null
+ return Pair(split[0], split[1])
+} \ No newline at end of file