commit 99b1c08ebed2acd2efd0f907a5adcf57900bd075
parent 9a4be2728ea0c40451e2b0ce9c6e97d85b50a729
Author: Antoine A <>
Date: Wed, 25 Jun 2025 13:42:52 +0200
bank: add missing token delete by id endpoint
Diffstat:
4 files changed, 49 insertions(+), 5 deletions(-)
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt b/bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt
@@ -328,7 +328,8 @@ data class TokenInfo(
val isRefreshable: Boolean,
val description: String? = null,
val last_access: TalerProtocolTimestamp,
- val row_id: Long
+ val row_id: Long,
+ val token_id: Long
)
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/api/CoreBankApi.kt b/bank/src/main/kotlin/tech/libeufin/bank/api/CoreBankApi.kt
@@ -137,15 +137,30 @@ private fun Routing.coreBankTokenApi(db: Database, cfg: BankConfig) {
}
}
}
+ auth(db, cfg.pwCrypto, TokenLogicalScope.readwrite, cfg.basicAuthCompat, allowAdmin = true) {
+ delete("/accounts/{USERNAME}/tokens/{TOKEN_ID}") {
+ val id = call.longPath("TOKEN_ID")
+ if (db.token.deleteById(id)) {
+ call.respond(HttpStatusCode.NoContent)
+ } else {
+ throw notFound(
+ "Token '$id' not found",
+ TalerErrorCode.BANK_TRANSACTION_NOT_FOUND
+ )
+ }
+ }
+ }
auth(db, cfg.pwCrypto, TokenLogicalScope.readonly, cfg.basicAuthCompat) {
delete("/accounts/{USERNAME}/token") {
val token = call.authToken ?: throw badRequest("Basic auth not supported here.")
db.token.delete(token)
call.respond(HttpStatusCode.NoContent)
}
+ }
+ auth(db, cfg.pwCrypto, TokenLogicalScope.readonly, cfg.basicAuthCompat, allowAdmin = true) {
get("/accounts/{USERNAME}/tokens") {
val params = PageParams.extract(call.request.queryParameters)
- val tokens = db.token.page(params, call.username)
+ val tokens = db.token.page(params, call.username, Instant.now())
if (tokens.isEmpty()) {
call.respond(HttpStatusCode.NoContent)
} else {
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/TokenDAO.kt b/bank/src/main/kotlin/tech/libeufin/bank/db/TokenDAO.kt
@@ -105,8 +105,16 @@ class TokenDAO(private val db: Database) {
executeUpdate()
}
+ /** Delete token [id] */
+ suspend fun deleteById(id: Long) = db.serializable(
+ "DELETE FROM bearer_tokens WHERE bearer_token_id = ?"
+ ) {
+ bind(id)
+ executeUpdateCheck()
+ }
+
/** Get a page of all tokens of [username] accounts */
- suspend fun page(params: PageParams, username: String): List<TokenInfo>
+ suspend fun page(params: PageParams, username: String, timestamp: Instant): List<TokenInfo>
= db.page(
params,
"bearer_token_id",
@@ -121,10 +129,12 @@ class TokenDAO(private val db: Database) {
bearer_token_id
FROM bearer_tokens
WHERE
+ expiration_time > ? AND
bank_customer=(SELECT customer_id FROM customers WHERE deleted_at IS NULL AND username = ?)
AND
""",
{
+ bind(timestamp.micros())
bind(username)
}
) {
@@ -135,7 +145,8 @@ class TokenDAO(private val db: Database) {
isRefreshable = it.getBoolean("is_refreshable"),
description = it.getString("description"),
last_access = it.getTalerTimestamp("last_access"),
- row_id = it.getLong("bearer_token_id")
+ row_id = it.getLong("bearer_token_id"),
+ token_id = it.getLong("bearer_token_id")
)
}
}
\ No newline at end of file
diff --git a/bank/src/test/kotlin/CoreBankApiTest.kt b/bank/src/test/kotlin/CoreBankApiTest.kt
@@ -334,7 +334,24 @@ class CoreBankTokenApiTest {
}.assertUnauthorized(TalerErrorCode.GENERIC_TOKEN_UNKNOWN)
}
- // GET /accounts/USERNAME/token
+ // DELETE /accounts/USERNAME/tokens/TOKEN_ID
+ @Test
+ fun deleteById() = bankSetup {
+ authRoutine(HttpMethod.Delete, "/accounts/merchant/tokens/1t", allowAdmin = true)
+
+ val token = client.postPw("/accounts/merchant/token") {
+ json { "scope" to "readonly" }
+ }.assertOkJson<TokenSuccessResponse>().access_token
+ // Check OK
+ client.deleteA("/accounts/merchant/tokens/2").assertNoContent()
+ client.deleteA("/accounts/merchant/tokens/2").assertNotFound(TalerErrorCode.BANK_TRANSACTION_NOT_FOUND)
+ // Check token no longer work
+ client.delete("/accounts/merchant/token") {
+ headers[HttpHeaders.Authorization] = "Bearer $token"
+ }.assertUnauthorized(TalerErrorCode.GENERIC_TOKEN_UNKNOWN)
+ }
+
+ // GET /accounts/USERNAME/tokens
@Test
fun get() = bankSetup {
// Check OK