[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[libeufin] 07/07: Serve Sandbox via Unix domain socket.
From: |
gnunet |
Subject: |
[libeufin] 07/07: Serve Sandbox via Unix domain socket. |
Date: |
Wed, 06 Oct 2021 16:17:58 +0200 |
This is an automated email from the git hooks/post-receive script.
ms pushed a commit to branch master
in repository libeufin.
commit 6144c4a6203549a16390356ea8deb9b5b6a464a9
Author: ms <ms@taler.net>
AuthorDate: Wed Oct 6 16:17:25 2021 +0200
Serve Sandbox via Unix domain socket.
---
.../src/main/kotlin/tech/libeufin/sandbox/Main.kt | 1248 ++++++++++----------
1 file changed, 629 insertions(+), 619 deletions(-)
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
index f70f7ed..fbde053 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
@@ -38,35 +38,21 @@ package tech.libeufin.sandbox
import UtilError
import com.fasterxml.jackson.core.JsonParseException
-import io.ktor.application.ApplicationCallPipeline
-import io.ktor.application.call
-import io.ktor.application.install
-import io.ktor.features.CallLogging
-import io.ktor.features.ContentNegotiation
-import io.ktor.features.StatusPages
-import io.ktor.response.respond
-import io.ktor.response.respondText
import io.ktor.server.engine.embeddedServer
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.transactions.transaction
-import io.ktor.jackson.jackson
import org.slf4j.Logger
import org.slf4j.LoggerFactory
-import org.slf4j.event.Level
import org.w3c.dom.Document
+import io.ktor.jackson.*
import tech.libeufin.util.CryptoUtil
import tech.libeufin.util.RawPayment
import java.lang.ArithmeticException
import java.math.BigDecimal
import java.security.interfaces.RSAPublicKey
import javax.xml.bind.JAXBContext
-import com.fasterxml.jackson.core.util.DefaultIndenter
-import com.fasterxml.jackson.core.util.DefaultPrettyPrinter
-import com.fasterxml.jackson.databind.SerializationFeature
import com.fasterxml.jackson.databind.exc.MismatchedInputException
-import com.fasterxml.jackson.module.kotlin.KotlinModule
import com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException
-import org.jetbrains.exposed.sql.statements.api.ExposedBlob
import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.core.ProgramResult
import com.github.ajalt.clikt.core.context
@@ -77,21 +63,33 @@ import com.github.ajalt.clikt.parameters.options.*
import com.github.ajalt.clikt.parameters.types.int
import execThrowableOrTerminate
import io.ktor.application.ApplicationCall
+import io.ktor.application.ApplicationCallPipeline
+import io.ktor.application.call
+import io.ktor.application.install
+import io.ktor.features.CallLogging
+import io.ktor.features.ContentNegotiation
+import org.jetbrains.exposed.sql.statements.api.ExposedBlob
+import com.fasterxml.jackson.core.util.DefaultIndenter
+import com.fasterxml.jackson.core.util.DefaultPrettyPrinter
+import com.fasterxml.jackson.module.kotlin.KotlinModule
+import com.fasterxml.jackson.databind.SerializationFeature
+import
org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction
+import io.ktor.features.StatusPages
+import io.ktor.response.respond
+import io.ktor.response.respondText
import io.ktor.auth.*
import io.ktor.http.*
import io.ktor.request.*
import io.ktor.routing.*
import io.ktor.server.netty.*
import io.ktor.util.date.*
+import io.ktor.application.*
import kotlinx.coroutines.newSingleThreadContext
-import
org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction
+import startServer
import tech.libeufin.util.*
-import tech.libeufin.util.ebics_h004.EbicsResponse
-import tech.libeufin.util.ebics_h004.EbicsTypes
import validatePlainAmount
import java.net.BindException
import java.util.*
-import kotlin.random.Random
import kotlin.system.exitProcess
private val logger: Logger = LoggerFactory.getLogger("tech.libeufin.sandbox")
@@ -290,9 +288,22 @@ class Serve : CliktCommand("Run sandbox HTTP server") {
private val logLevel by option()
private val port by option().int().default(5000)
+ private val withUnixSocket by option(
+ help = "Bind the Sandbox to the Unix domain socket at PATH.
Overrides" +
+ "--port, when both are given", metavar = "PATH"
+ )
override fun run() {
setLogLevel(logLevel)
- serverMain(getDbConnFromEnv(SANDBOX_DB_ENV_VAR_NAME), port)
+ val dbName = getDbConnFromEnv(SANDBOX_DB_ENV_VAR_NAME)
+ if (withUnixSocket != null) {
+ execThrowableOrTerminate { dbCreateTables(dbName) }
+ startServer(
+ withUnixSocket ?: throw Exception("Could not use the Unix
domain socket path value!"),
+ app = sandboxApp
+ )
+ exitProcess(0)
+ }
+ serverMain(dbName, port)
}
}
@@ -384,703 +395,702 @@ suspend inline fun <reified T : Any>
ApplicationCall.receiveJson(): T {
}
val singleThreadContext = newSingleThreadContext("DB")
-
-fun serverMain(dbName: String, port: Int) {
- execThrowableOrTerminate { dbCreateTables(dbName) }
- val myLogger = logger
- val server = embeddedServer(Netty, port = port) {
- install(CallLogging) {
- this.level = Level.DEBUG
- this.logger = myLogger
- }
- install(Authentication) {
- // Web-based authentication for Bank customers.
- form("auth-form") {
- userParamName = "username"
- passwordParamName = "password"
- validate { credentials ->
- if (credentials.name == "test") {
- UserIdPrincipal(credentials.name)
- } else {
- null
- }
+val sandboxApp: Application.() -> Unit = {
+ install(io.ktor.features.CallLogging) {
+ this.level = org.slf4j.event.Level.DEBUG
+ this.logger = logger
+ }
+ install(Authentication) {
+ // Web-based authentication for Bank customers.
+ form("auth-form") {
+ userParamName = "username"
+ passwordParamName = "password"
+ validate { credentials ->
+ if (credentials.name == "test") {
+ UserIdPrincipal(credentials.name)
+ } else {
+ null
}
}
}
- install(ContentNegotiation) {
- jackson {
- enable(SerializationFeature.INDENT_OUTPUT)
- setDefaultPrettyPrinter(DefaultPrettyPrinter().apply {
-
indentArraysWith(DefaultPrettyPrinter.FixedSpaceIndenter.instance)
- indentObjectsWith(DefaultIndenter(" ", "\n"))
- })
- registerModule(KotlinModule(nullisSameAsDefault = true))
- //registerModule(JavaTimeModule())
- }
+ }
+ install(io.ktor.features.ContentNegotiation) {
+ jackson {
+
enable(com.fasterxml.jackson.databind.SerializationFeature.INDENT_OUTPUT)
+ setDefaultPrettyPrinter(DefaultPrettyPrinter().apply {
+
indentArraysWith(com.fasterxml.jackson.core.util.DefaultPrettyPrinter.FixedSpaceIndenter.instance)
+ indentObjectsWith(DefaultIndenter(" ", "\n"))
+ })
+ registerModule(KotlinModule(nullisSameAsDefault = true))
+ //registerModule(JavaTimeModule())
}
- install(StatusPages) {
- exception<ArithmeticException> { cause ->
- logger.error("Exception while handling '${call.request.uri}'",
cause)
- call.respondText(
- "Invalid arithmetic attempted.",
- ContentType.Text.Plain,
- // here is always the bank's fault, as it should always
check
- // the operands.
- HttpStatusCode.InternalServerError
+ }
+ install(StatusPages) {
+ exception<ArithmeticException> { cause ->
+ logger.error("Exception while handling '${call.request.uri}'",
cause)
+ call.respondText(
+ "Invalid arithmetic attempted.",
+ io.ktor.http.ContentType.Text.Plain,
+ // here is always the bank's fault, as it should always check
+ // the operands.
+ io.ktor.http.HttpStatusCode.InternalServerError
+ )
+ }
+ exception<EbicsRequestError> { cause ->
+ val resp =
tech.libeufin.util.ebics_h004.EbicsResponse.createForUploadWithError(
+ cause.errorText,
+ cause.errorCode,
+ // assuming that the phase is always transfer,
+ // as errors during initialization should have
+ // already been caught by the chunking logic.
+
tech.libeufin.util.ebics_h004.EbicsTypes.TransactionPhaseType.TRANSFER
+ )
+
+ val hostAuthPriv = transaction {
+ val host = tech.libeufin.sandbox.EbicsHostEntity.find {
+ tech.libeufin.sandbox.EbicsHostsTable.hostID.upperCase()
eq call.attributes.get(tech.libeufin.sandbox.EbicsHostIdAttribute).uppercase()
+ }.firstOrNull() ?: throw SandboxError(
+ io.ktor.http.HttpStatusCode.InternalServerError,
+ "Requested Ebics host ID not found."
)
+
tech.libeufin.util.CryptoUtil.loadRsaPrivateKey(host.authenticationPrivateKey.bytes)
}
- exception<EbicsRequestError> { cause ->
- val resp = EbicsResponse.createForUploadWithError(
- cause.errorText,
- cause.errorCode,
- // assuming that the phase is always transfer,
- // as errors during initialization should have
- // already been caught by the chunking logic.
- EbicsTypes.TransactionPhaseType.TRANSFER
- )
-
- val hostAuthPriv = transaction {
- val host = EbicsHostEntity.find {
- EbicsHostsTable.hostID.upperCase() eq
call.attributes.get(EbicsHostIdAttribute).uppercase()
- }.firstOrNull() ?: throw SandboxError(
- HttpStatusCode.InternalServerError,
- "Requested Ebics host ID not found."
+ call.respondText(
+ tech.libeufin.util.XMLUtil.signEbicsResponse(resp,
hostAuthPriv),
+ io.ktor.http.ContentType.Application.Xml,
+ io.ktor.http.HttpStatusCode.OK
+ )
+ }
+ exception<SandboxError> { cause ->
+ logger.error("Exception while handling '${call.request.uri}',
${cause.reason}")
+ call.respond(
+ cause.statusCode,
+ SandboxErrorJson(
+ error = SandboxErrorDetailJson(
+ type = "sandbox-error",
+ description = cause.reason
)
-
CryptoUtil.loadRsaPrivateKey(host.authenticationPrivateKey.bytes)
- }
- call.respondText(
- XMLUtil.signEbicsResponse(resp, hostAuthPriv),
- ContentType.Application.Xml,
- HttpStatusCode.OK
)
- }
- exception<SandboxError> { cause ->
- logger.error("Exception while handling '${call.request.uri}',
${cause.reason}")
- call.respond(
- cause.statusCode,
- SandboxErrorJson(
- error = SandboxErrorDetailJson(
- type = "sandbox-error",
- description = cause.reason
- )
+ )
+ }
+ exception<UtilError> { cause ->
+ logger.error("Exception while handling '${call.request.uri}'",
cause)
+ call.respond(
+ cause.statusCode,
+ SandboxErrorJson(
+ error = SandboxErrorDetailJson(
+ type = "util-error",
+ description = cause.reason
)
)
- }
- exception<UtilError> { cause ->
- logger.error("Exception while handling '${call.request.uri}'",
cause)
- call.respond(
- cause.statusCode,
- SandboxErrorJson(
- error = SandboxErrorDetailJson(
- type = "util-error",
- description = cause.reason
- )
- )
+ )
+ }
+ exception<Throwable> { cause ->
+ logger.error("Exception while handling '${call.request.uri}'",
cause)
+ call.respondText("Internal server error.",
io.ktor.http.ContentType.Text.Plain,
io.ktor.http.HttpStatusCode.InternalServerError)
+ }
+ }
+ intercept(io.ktor.application.ApplicationCallPipeline.Fallback) {
+ if (this.call.response.status() == null) {
+ call.respondText("Not found (no route matched).\n",
io.ktor.http.ContentType.Text.Plain, io.ktor.http.HttpStatusCode.NotFound)
+ return@intercept finish()
+ }
+ }
+ routing {
+
+ get("/") {
+ call.respondText("Hello, this is Sandbox\n",
io.ktor.http.ContentType.Text.Plain)
+ }
+ get("/config") {
+ call.respond(object {
+ val name = "libeufin-sandbox"
+
+ // FIXME: use actual version here!
+ val version = "0.0.0-dev.0"
+ })
+ }
+ /**
+ * For now, only returns the last statement of the
+ * requesting account.
+ */
+ post("/admin/payments/camt") {
+ requireSuperuser(call.request)
+ val body = call.receiveJson<CamtParams>()
+ val bankaccount = getAccountFromLabel(body.bankaccount)
+ if (body.type != 53) throw SandboxError(
+ io.ktor.http.HttpStatusCode.NotFound,
+ "Only Camt.053 documents can be generated."
+ )
+ val camtMessage = transaction {
+ tech.libeufin.sandbox.BankAccountStatementEntity.find {
+
tech.libeufin.sandbox.BankAccountStatementsTable.bankAccount eq bankaccount.id
+ }.lastOrNull()?.xmlMessage ?: throw SandboxError(
+ io.ktor.http.HttpStatusCode.NotFound,
+ "Could not find any statements; please wait next tick"
)
}
- exception<Throwable> { cause ->
- logger.error("Exception while handling '${call.request.uri}'",
cause)
- call.respondText("Internal server error.",
ContentType.Text.Plain, HttpStatusCode.InternalServerError)
- }
+ call.respondText(
+ camtMessage, io.ktor.http.ContentType.Text.Xml,
io.ktor.http.HttpStatusCode.OK
+ )
+ return@post
}
- intercept(ApplicationCallPipeline.Fallback) {
- if (this.call.response.status() == null) {
- call.respondText("Not found (no route matched).\n",
ContentType.Text.Plain, HttpStatusCode.NotFound)
- return@intercept finish()
+
+ post("/admin/bank-accounts/{label}") {
+ requireSuperuser(call.request)
+ val body = call.receiveJson<BankAccountInfo>()
+ transaction {
+ tech.libeufin.sandbox.BankAccountEntity.new {
+ iban = body.iban
+ bic = body.bic
+ name = body.name
+ label = body.label
+ currency = body.currency ?: "EUR"
+ }
}
+ call.respond(object {})
+ return@post
}
- routing {
- get("/") {
- call.respondText("Hello, this is Sandbox\n",
ContentType.Text.Plain)
+ get("/admin/bank-accounts/{label}") {
+ requireSuperuser(call.request)
+ val label = ensureNonNull(call.parameters["label"])
+ val ret = transaction {
+ val bankAccount = tech.libeufin.sandbox.BankAccountEntity.find
{
+ tech.libeufin.sandbox.BankAccountsTable.label eq label
+ }.firstOrNull() ?: throw SandboxError(
+ io.ktor.http.HttpStatusCode.NotFound,
+ "Account '$label' not found"
+ )
+ val balance = balanceForAccount(bankAccount)
+ object {
+ val balance = "${bankAccount.currency}:${balance}"
+ val iban = bankAccount.iban
+ val bic = bankAccount.bic
+ val name = bankAccount.name
+ val label = bankAccount.label
+ }
}
- get("/config") {
- call.respond(object {
- val name = "libeufin-sandbox"
+ call.respond(ret)
+ return@get
+ }
- // FIXME: use actual version here!
- val version = "0.0.0-dev.0"
- })
+ post("/admin/bank-accounts/{label}/simulate-incoming-transaction") {
+ requireSuperuser(call.request)
+ val body = call.receiveJson<IncomingPaymentInfo>()
+ // FIXME: generate nicer UUID!
+ val accountLabel = ensureNonNull(call.parameters["label"])
+ if (!validatePlainAmount(body.amount)) {
+ throw SandboxError(
+ io.ktor.http.HttpStatusCode.BadRequest,
+ "invalid amount (should be plain amount without currency)"
+ )
}
- /**
- * For now, only returns the last statement of the
- * requesting account.
- */
- post("/admin/payments/camt") {
- requireSuperuser(call.request)
- val body = call.receiveJson<CamtParams>()
- val bankaccount = getAccountFromLabel(body.bankaccount)
- if (body.type != 53) throw SandboxError(
- HttpStatusCode.NotFound,
- "Only Camt.053 documents can be generated."
+ val reqDebtorBic = body.debtorBic
+ if (reqDebtorBic != null && !validateBic(reqDebtorBic)) {
+ throw SandboxError(
+ io.ktor.http.HttpStatusCode.BadRequest,
+ "invalid BIC"
)
- val camtMessage = transaction {
- BankAccountStatementEntity.find {
- BankAccountStatementsTable.bankAccount eq
bankaccount.id
- }.lastOrNull()?.xmlMessage ?: throw SandboxError(
- HttpStatusCode.NotFound,
- "Could not find any statements; please wait next tick"
- )
+ }
+ transaction {
+ val account = getBankAccountFromLabel(accountLabel)
+ val randId = getRandomString(16)
+ tech.libeufin.sandbox.BankAccountTransactionEntity.new {
+ creditorIban = account.iban
+ creditorBic = account.bic
+ creditorName = account.name
+ debtorIban = body.debtorIban
+ debtorBic = reqDebtorBic
+ debtorName = body.debtorName
+ subject = body.subject
+ amount = body.amount
+ currency = account.currency
+ date = getUTCnow().toInstant().toEpochMilli()
+ accountServicerReference = "sandbox-$randId"
+ this.account = account
+ direction = "CRDT"
}
- call.respondText(
- camtMessage, ContentType.Text.Xml, HttpStatusCode.OK
- )
- return@post
}
+ call.respond(object {})
+ }
- post("/admin/bank-accounts/{label}") {
- requireSuperuser(call.request)
- val body = call.receiveJson<BankAccountInfo>()
- transaction {
- BankAccountEntity.new {
- iban = body.iban
- bic = body.bic
- name = body.name
- label = body.label
- currency = body.currency ?: "EUR"
- }
+ /**
+ * Associates a new bank account with an existing Ebics subscriber.
+ */
+ post("/admin/ebics/bank-accounts") {
+ requireSuperuser(call.request)
+ val body = call.receiveJson<BankAccountRequest>()
+ if (!validateBic(body.bic)) {
+ throw SandboxError(io.ktor.http.HttpStatusCode.BadRequest,
"invalid BIC (${body.bic})")
+ }
+ transaction {
+ val subscriber = getEbicsSubscriberFromDetails(
+ body.subscriber.userID,
+ body.subscriber.partnerID,
+ body.subscriber.hostID
+ )
+ val check = tech.libeufin.sandbox.BankAccountEntity.find {
+ tech.libeufin.sandbox.BankAccountsTable.iban eq body.iban
or (tech.libeufin.sandbox.BankAccountsTable.label eq body.label)
+ }.count()
+ if (check > 0) throw SandboxError(
+ io.ktor.http.HttpStatusCode.BadRequest,
+ "Either IBAN or account label were already taken; please
choose fresh ones"
+ )
+ subscriber.bankAccount =
tech.libeufin.sandbox.BankAccountEntity.new {
+ iban = body.iban
+ bic = body.bic
+ name = body.name
+ label = body.label
+ currency = body.currency.uppercase(java.util.Locale.ROOT)
}
- call.respond(object {})
- return@post
}
-
- get("/admin/bank-accounts/{label}") {
- requireSuperuser(call.request)
- val label = ensureNonNull(call.parameters["label"])
- val ret = transaction {
- val bankAccount = BankAccountEntity.find {
- BankAccountsTable.label eq label
- }.firstOrNull() ?: throw SandboxError(
- HttpStatusCode.NotFound,
- "Account '$label' not found"
+ call.respondText("Bank account created")
+ return@post
+ }
+ get("/admin/bank-accounts") {
+ requireSuperuser(call.request)
+ val accounts = mutableListOf<BankAccountInfo>()
+ transaction {
+ tech.libeufin.sandbox.BankAccountEntity.all().forEach {
+ accounts.add(
+ BankAccountInfo(
+ label = it.label,
+ name = it.name,
+ bic = it.bic,
+ iban = it.iban,
+ currency = it.currency
+ )
)
- val balance = balanceForAccount(bankAccount)
- object {
- val balance = "${bankAccount.currency}:${balance}"
- val iban = bankAccount.iban
- val bic = bankAccount.bic
- val name = bankAccount.name
- val label = bankAccount.label
- }
}
- call.respond(ret)
- return@get
}
-
- post("/admin/bank-accounts/{label}/simulate-incoming-transaction")
{
- requireSuperuser(call.request)
- val body = call.receiveJson<IncomingPaymentInfo>()
- // FIXME: generate nicer UUID!
+ call.respond(accounts)
+ }
+ get("/admin/bank-accounts/{label}/transactions") {
+ requireSuperuser(call.request)
+ val ret = AccountTransactions()
+ transaction {
val accountLabel = ensureNonNull(call.parameters["label"])
- if (!validatePlainAmount(body.amount)) {
- throw SandboxError(
- HttpStatusCode.BadRequest,
- "invalid amount (should be plain amount without
currency)"
- )
- }
- val reqDebtorBic = body.debtorBic
- if (reqDebtorBic != null && !validateBic(reqDebtorBic)) {
- throw SandboxError(
- HttpStatusCode.BadRequest,
- "invalid BIC"
- )
- }
transaction {
val account = getBankAccountFromLabel(accountLabel)
- val randId = getRandomString(16)
- BankAccountTransactionEntity.new {
+ tech.libeufin.sandbox.BankAccountTransactionEntity.find {
+
tech.libeufin.sandbox.BankAccountTransactionsTable.account eq account.id
+ }.forEach {
+ ret.payments.add(
+ PaymentInfo(
+ accountLabel = account.label,
+ creditorIban = it.creditorIban,
+ // FIXME: We need to modify the transactions
table to have an actual
+ // account servicer reference here.
+ accountServicerReference =
it.accountServicerReference,
+ paymentInformationId = it.pmtInfId,
+ debtorIban = it.debtorIban,
+ subject = it.subject,
+ date = GMTDate(it.date).toHttpDate(),
+ amount = it.amount,
+ creditorBic = it.creditorBic,
+ creditorName = it.creditorName,
+ debtorBic = it.debtorBic,
+ debtorName = it.debtorName,
+ currency = it.currency,
+ creditDebitIndicator = when (it.direction) {
+ "CRDT" -> "credit"
+ "DBIT" -> "debit"
+ else -> throw Error("invalid direction")
+ }
+ )
+ )
+ }
+ }
+ }
+ call.respond(ret)
+ }
+ post("/admin/bank-accounts/{label}/generate-transactions") {
+ requireSuperuser(call.request)
+ transaction {
+ val accountLabel = ensureNonNull(call.parameters["label"])
+ val account = getBankAccountFromLabel(accountLabel)
+ val transactionReferenceCrdt = getRandomString(8)
+ val transactionReferenceDbit = getRandomString(8)
+
+ run {
+ val amount = kotlin.random.Random.nextLong(5, 25)
+ tech.libeufin.sandbox.BankAccountTransactionEntity.new {
creditorIban = account.iban
creditorBic = account.bic
creditorName = account.name
- debtorIban = body.debtorIban
- debtorBic = reqDebtorBic
- debtorName = body.debtorName
- subject = body.subject
- amount = body.amount
+ debtorIban = "DE64500105178797276788"
+ debtorBic = "DEUTDEBB101"
+ debtorName = "Max Mustermann"
+ subject = "sample transaction
$transactionReferenceCrdt"
+ this.amount = amount.toString()
currency = account.currency
date = getUTCnow().toInstant().toEpochMilli()
- accountServicerReference = "sandbox-$randId"
+ accountServicerReference = transactionReferenceCrdt
this.account = account
direction = "CRDT"
}
}
- call.respond(object {})
- }
- /**
- * Associates a new bank account with an existing Ebics subscriber.
- */
- post("/admin/ebics/bank-accounts") {
- requireSuperuser(call.request)
- val body = call.receiveJson<BankAccountRequest>()
- if (!validateBic(body.bic)) {
- throw SandboxError(HttpStatusCode.BadRequest, "invalid BIC
(${body.bic})")
- }
- transaction {
- val subscriber = getEbicsSubscriberFromDetails(
- body.subscriber.userID,
- body.subscriber.partnerID,
- body.subscriber.hostID
- )
- val check = BankAccountEntity.find {
- BankAccountsTable.iban eq body.iban or
(BankAccountsTable.label eq body.label)
- }.count()
- if (check > 0) throw SandboxError(
- HttpStatusCode.BadRequest,
- "Either IBAN or account label were already taken;
please choose fresh ones"
- )
- subscriber.bankAccount = BankAccountEntity.new {
- iban = body.iban
- bic = body.bic
- name = body.name
- label = body.label
- currency = body.currency.uppercase(Locale.ROOT)
- }
- }
- call.respondText("Bank account created")
- return@post
- }
- get("/admin/bank-accounts") {
- requireSuperuser(call.request)
- val accounts = mutableListOf<BankAccountInfo>()
- transaction {
- BankAccountEntity.all().forEach {
- accounts.add(
- BankAccountInfo(
- label = it.label,
- name = it.name,
- bic = it.bic,
- iban = it.iban,
- currency = it.currency
- )
- )
- }
- }
- call.respond(accounts)
- }
- get("/admin/bank-accounts/{label}/transactions") {
- requireSuperuser(call.request)
- val ret = AccountTransactions()
- transaction {
- val accountLabel = ensureNonNull(call.parameters["label"])
- transaction {
- val account = getBankAccountFromLabel(accountLabel)
- BankAccountTransactionEntity.find {
- BankAccountTransactionsTable.account eq account.id
- }.forEach {
- ret.payments.add(
- PaymentInfo(
- accountLabel = account.label,
- creditorIban = it.creditorIban,
- // FIXME: We need to modify the
transactions table to have an actual
- // account servicer reference here.
- accountServicerReference =
it.accountServicerReference,
- paymentInformationId = it.pmtInfId,
- debtorIban = it.debtorIban,
- subject = it.subject,
- date = GMTDate(it.date).toHttpDate(),
- amount = it.amount,
- creditorBic = it.creditorBic,
- creditorName = it.creditorName,
- debtorBic = it.debtorBic,
- debtorName = it.debtorName,
- currency = it.currency,
- creditDebitIndicator = when (it.direction)
{
- "CRDT" -> "credit"
- "DBIT" -> "debit"
- else -> throw Error("invalid
direction")
- }
- )
- )
- }
- }
- }
- call.respond(ret)
- }
- post("/admin/bank-accounts/{label}/generate-transactions") {
- requireSuperuser(call.request)
- transaction {
- val accountLabel = ensureNonNull(call.parameters["label"])
- val account = getBankAccountFromLabel(accountLabel)
- val transactionReferenceCrdt = getRandomString(8)
- val transactionReferenceDbit = getRandomString(8)
-
- run {
- val amount = Random.nextLong(5, 25)
- BankAccountTransactionEntity.new {
- creditorIban = account.iban
- creditorBic = account.bic
- creditorName = account.name
- debtorIban = "DE64500105178797276788"
- debtorBic = "DEUTDEBB101"
- debtorName = "Max Mustermann"
- subject = "sample transaction
$transactionReferenceCrdt"
- this.amount = amount.toString()
- currency = account.currency
- date = getUTCnow().toInstant().toEpochMilli()
- accountServicerReference = transactionReferenceCrdt
- this.account = account
- direction = "CRDT"
- }
- }
-
- run {
- val amount = Random.nextLong(5, 25)
-
- BankAccountTransactionEntity.new {
- debtorIban = account.iban
- debtorBic = account.bic
- debtorName = account.name
- creditorIban = "DE64500105178797276788"
- creditorBic = "DEUTDEBB101"
- creditorName = "Max Mustermann"
- subject = "sample transaction
$transactionReferenceDbit"
- this.amount = amount.toString()
- currency = account.currency
- date = getUTCnow().toInstant().toEpochMilli()
- accountServicerReference = transactionReferenceDbit
- this.account = account
- direction = "DBIT"
- }
+ run {
+ val amount = kotlin.random.Random.nextLong(5, 25)
+
+ tech.libeufin.sandbox.BankAccountTransactionEntity.new {
+ debtorIban = account.iban
+ debtorBic = account.bic
+ debtorName = account.name
+ creditorIban = "DE64500105178797276788"
+ creditorBic = "DEUTDEBB101"
+ creditorName = "Max Mustermann"
+ subject = "sample transaction
$transactionReferenceDbit"
+ this.amount = amount.toString()
+ currency = account.currency
+ date = getUTCnow().toInstant().toEpochMilli()
+ accountServicerReference = transactionReferenceDbit
+ this.account = account
+ direction = "DBIT"
}
}
- call.respond(object {})
}
- /**
- * Creates a new Ebics subscriber.
- */
- post("/admin/ebics/subscribers") {
- requireSuperuser(call.request)
- val body = call.receiveJson<EbicsSubscriberElement>()
- transaction {
- EbicsSubscriberEntity.new {
- partnerId = body.partnerID
- userId = body.userID
- systemId = null
- hostId = body.hostID
- state = SubscriberState.NEW
- nextOrderID = 1
- }
+ call.respond(object {})
+ }
+ /**
+ * Creates a new Ebics subscriber.
+ */
+ post("/admin/ebics/subscribers") {
+ requireSuperuser(call.request)
+ val body = call.receiveJson<EbicsSubscriberElement>()
+ transaction {
+ tech.libeufin.sandbox.EbicsSubscriberEntity.new {
+ partnerId = body.partnerID
+ userId = body.userID
+ systemId = null
+ hostId = body.hostID
+ state = tech.libeufin.sandbox.SubscriberState.NEW
+ nextOrderID = 1
}
- call.respondText(
- "Subscriber created.",
- ContentType.Text.Plain, HttpStatusCode.OK
- )
- return@post
}
- /**
- * Shows all the Ebics subscribers' details.
- */
- get("/admin/ebics/subscribers") {
- requireSuperuser(call.request)
- val ret = AdminGetSubscribers()
- transaction {
- EbicsSubscriberEntity.all().forEach {
- ret.subscribers.add(
- EbicsSubscriberElement(
- userID = it.userId,
- partnerID = it.partnerId,
- hostID = it.hostId
- )
+ call.respondText(
+ "Subscriber created.",
+ io.ktor.http.ContentType.Text.Plain,
io.ktor.http.HttpStatusCode.OK
+ )
+ return@post
+ }
+ /**
+ * Shows all the Ebics subscribers' details.
+ */
+ get("/admin/ebics/subscribers") {
+ requireSuperuser(call.request)
+ val ret = AdminGetSubscribers()
+ transaction {
+ tech.libeufin.sandbox.EbicsSubscriberEntity.all().forEach {
+ ret.subscribers.add(
+ EbicsSubscriberElement(
+ userID = it.userId,
+ partnerID = it.partnerId,
+ hostID = it.hostId
)
- }
- }
- call.respond(ret)
- return@get
- }
- post("/admin/ebics/hosts/{hostID}/rotate-keys") {
- requireSuperuser(call.request)
- val hostID: String = call.parameters["hostID"] ?: throw
SandboxError(
- HttpStatusCode.BadRequest, "host ID missing in URL"
- )
- transaction {
- val host = EbicsHostEntity.find {
- EbicsHostsTable.hostID eq hostID
- }.firstOrNull() ?: throw SandboxError(
- HttpStatusCode.NotFound, "Host $hostID not found"
)
- val pairA = CryptoUtil.generateRsaKeyPair(2048)
- val pairB = CryptoUtil.generateRsaKeyPair(2048)
- val pairC = CryptoUtil.generateRsaKeyPair(2048)
- host.authenticationPrivateKey =
ExposedBlob(pairA.private.encoded)
- host.encryptionPrivateKey =
ExposedBlob(pairB.private.encoded)
- host.signaturePrivateKey =
ExposedBlob(pairC.private.encoded)
}
- call.respondText(
- "Keys of '${hostID}' rotated.",
- ContentType.Text.Plain,
- HttpStatusCode.OK
+ }
+ call.respond(ret)
+ return@get
+ }
+ post("/admin/ebics/hosts/{hostID}/rotate-keys") {
+ requireSuperuser(call.request)
+ val hostID: String = call.parameters["hostID"] ?: throw
SandboxError(
+ io.ktor.http.HttpStatusCode.BadRequest, "host ID missing in
URL"
+ )
+ transaction {
+ val host = tech.libeufin.sandbox.EbicsHostEntity.find {
+ tech.libeufin.sandbox.EbicsHostsTable.hostID eq hostID
+ }.firstOrNull() ?: throw SandboxError(
+ io.ktor.http.HttpStatusCode.NotFound, "Host $hostID not
found"
)
- return@post
+ val pairA =
tech.libeufin.util.CryptoUtil.generateRsaKeyPair(2048)
+ val pairB =
tech.libeufin.util.CryptoUtil.generateRsaKeyPair(2048)
+ val pairC =
tech.libeufin.util.CryptoUtil.generateRsaKeyPair(2048)
+ host.authenticationPrivateKey =
ExposedBlob(pairA.private.encoded)
+ host.encryptionPrivateKey = ExposedBlob(pairB.private.encoded)
+ host.signaturePrivateKey = ExposedBlob(pairC.private.encoded)
}
+ call.respondText(
+ "Keys of '${hostID}' rotated.",
+ io.ktor.http.ContentType.Text.Plain,
+ io.ktor.http.HttpStatusCode.OK
+ )
+ return@post
+ }
- /**
- * Creates a new EBICS host.
- */
- post("/admin/ebics/hosts") {
- requireSuperuser(call.request)
- val req = call.receiveJson<EbicsHostCreateRequest>()
- val pairA = CryptoUtil.generateRsaKeyPair(2048)
- val pairB = CryptoUtil.generateRsaKeyPair(2048)
- val pairC = CryptoUtil.generateRsaKeyPair(2048)
- transaction {
- EbicsHostEntity.new {
- this.ebicsVersion = req.ebicsVersion
- this.hostId = req.hostID
- this.authenticationPrivateKey =
ExposedBlob(pairA.private.encoded)
- this.encryptionPrivateKey =
ExposedBlob(pairB.private.encoded)
- this.signaturePrivateKey =
ExposedBlob(pairC.private.encoded)
- }
+ /**
+ * Creates a new EBICS host.
+ */
+ post("/admin/ebics/hosts") {
+ requireSuperuser(call.request)
+ val req = call.receiveJson<EbicsHostCreateRequest>()
+ val pairA = tech.libeufin.util.CryptoUtil.generateRsaKeyPair(2048)
+ val pairB = tech.libeufin.util.CryptoUtil.generateRsaKeyPair(2048)
+ val pairC = tech.libeufin.util.CryptoUtil.generateRsaKeyPair(2048)
+ transaction {
+ tech.libeufin.sandbox.EbicsHostEntity.new {
+ this.ebicsVersion = req.ebicsVersion
+ this.hostId = req.hostID
+ this.authenticationPrivateKey =
ExposedBlob(pairA.private.encoded)
+ this.encryptionPrivateKey =
ExposedBlob(pairB.private.encoded)
+ this.signaturePrivateKey =
ExposedBlob(pairC.private.encoded)
}
- call.respondText(
- "Host '${req.hostID}' created.",
- ContentType.Text.Plain,
- HttpStatusCode.OK
- )
- return@post
}
+ call.respondText(
+ "Host '${req.hostID}' created.",
+ io.ktor.http.ContentType.Text.Plain,
+ io.ktor.http.HttpStatusCode.OK
+ )
+ return@post
+ }
+ /**
+ * Show the names of all the Ebics hosts
+ */
+ get("/admin/ebics/hosts") {
+ requireSuperuser(call.request)
+ val ebicsHosts = transaction {
+ tech.libeufin.sandbox.EbicsHostEntity.all().map { it.hostId }
+ }
+ call.respond(EbicsHostsResponse(ebicsHosts))
+ }
+ /**
+ * Serves all the Ebics requests.
+ */
+ post("/ebicsweb") {
+ try {
+ call.ebicsweb()
+ }
/**
- * Show the names of all the Ebics hosts
+ * Those errors were all detected by the bank's logic.
*/
- get("/admin/ebics/hosts") {
- requireSuperuser(call.request)
- val ebicsHosts = transaction {
- EbicsHostEntity.all().map { it.hostId }
+ catch (e: SandboxError) {
+ // Should translate to EBICS error code.
+ when (e.errorCode) {
+
tech.libeufin.util.LibeufinErrorCode.LIBEUFIN_EC_INVALID_STATE -> throw
EbicsProcessingError("Invalid bank state.")
+
tech.libeufin.util.LibeufinErrorCode.LIBEUFIN_EC_INCONSISTENT_STATE -> throw
EbicsProcessingError("Inconsistent bank state.")
+ else -> throw EbicsProcessingError("Unknown LibEuFin error
code: ${e.errorCode}.")
}
- call.respond(EbicsHostsResponse(ebicsHosts))
+
}
/**
- * Serves all the Ebics requests.
+ * An error occurred, but it wasn't explicitly thrown by the bank.
*/
- post("/ebicsweb") {
- try {
- call.ebicsweb()
- }
- /**
- * Those errors were all detected by the bank's logic.
- */
- catch (e: SandboxError) {
- // Should translate to EBICS error code.
- when (e.errorCode) {
- LibeufinErrorCode.LIBEUFIN_EC_INVALID_STATE -> throw
EbicsProcessingError("Invalid bank state.")
- LibeufinErrorCode.LIBEUFIN_EC_INCONSISTENT_STATE ->
throw EbicsProcessingError("Inconsistent bank state.")
- else -> throw EbicsProcessingError("Unknown LibEuFin
error code: ${e.errorCode}.")
- }
+ catch (e: Exception) {
+ throw EbicsProcessingError("Unmanaged error: $e")
+ }
- }
- /**
- * An error occurred, but it wasn't explicitly thrown by the
bank.
- */
- catch (e: Exception) {
- throw EbicsProcessingError("Unmanaged error: $e")
- }
+ }
+ /**
+ * Activates a withdraw operation of 1 currency unit with
+ * the default exchange, from a designated/constant customer.
+ */
+ get("/taler") {
+ requireSuperuser(call.request)
+ SandboxAssert(
+ hostName != null,
+ "Own hostname not found. Logs should have warned"
+ )
+ SandboxAssert(
+ currencyEnv != null,
+ "Currency not found. Logs should have warned"
+ )
+ // check that the three canonical accounts exist
+ val wo = transaction {
+ val exchange = tech.libeufin.sandbox.BankAccountEntity.find {
+ tech.libeufin.sandbox.BankAccountsTable.label eq
"sandbox-account-exchange"
+ }.firstOrNull()
+ val customer = tech.libeufin.sandbox.BankAccountEntity.find {
+ tech.libeufin.sandbox.BankAccountsTable.label eq
"sandbox-account-customer"
+ }.firstOrNull()
+ val merchant = tech.libeufin.sandbox.BankAccountEntity.find {
+ tech.libeufin.sandbox.BankAccountsTable.label eq
"sandbox-account-merchant"
+ }.firstOrNull()
+
+ SandboxAssert(exchange != null, "exchange has no bank account")
+ SandboxAssert(customer != null, "customer has no bank account")
+ SandboxAssert(merchant != null, "merchant has no bank account")
+
+ // At this point, the three actors exist and a new withdraw
operation can be created.
+ tech.libeufin.sandbox.TalerWithdrawalEntity.new {
+ // wopid is autogenerated, and momentarily the only column
+ }
}
/**
- * Activates a withdraw operation of 1 currency unit with
- * the default exchange, from a designated/constant customer.
+ * Future versions will include the QR code in this response.
*/
- get("/taler") {
- requireSuperuser(call.request)
- SandboxAssert(
- hostName != null,
- "Own hostname not found. Logs should have warned"
- )
- SandboxAssert(
- currencyEnv != null,
- "Currency not found. Logs should have warned"
+ call.respondText("taler://withdraw/${hostName}/api/${wo.wopid}")
+ return@get
+ }
+ get("/api/config") {
+ SandboxAssert(
+ currencyEnv != null,
+ "Currency not found. Logs should have warned"
+ )
+ call.respond(object {
+ val name = "taler-bank-integration"
+
+ // FIXME: use actual version here!
+ val version = "0:0:0"
+ val currency = currencyEnv
+ })
+ }
+ /**
+ * not regulating the access here, as the wopid was only granted
+ * to logged-in users before (at the /taler endpoint) and has enough
+ * entropy to prevent guesses.
+ */
+ get("/api/withdrawal-operation/{wopid}") {
+ val wopid: String = ensureNonNull(call.parameters["wopid"])
+ val wo = transaction {
+
+ tech.libeufin.sandbox.TalerWithdrawalEntity.find {
+ tech.libeufin.sandbox.TalerWithdrawalsTable.wopid eq
java.util.UUID.fromString(wopid)
+ }.firstOrNull() ?: throw SandboxError(
+ io.ktor.http.HttpStatusCode.NotFound,
+ "Withdrawal operation: $wopid not found"
)
- // check that the three canonical accounts exist
- val wo = transaction {
- val exchange = BankAccountEntity.find {
- BankAccountsTable.label eq "sandbox-account-exchange"
- }.firstOrNull()
- val customer = BankAccountEntity.find {
- BankAccountsTable.label eq "sandbox-account-customer"
- }.firstOrNull()
- val merchant = BankAccountEntity.find {
- BankAccountsTable.label eq "sandbox-account-merchant"
- }.firstOrNull()
-
- SandboxAssert(exchange != null, "exchange has no bank
account")
- SandboxAssert(customer != null, "customer has no bank
account")
- SandboxAssert(merchant != null, "merchant has no bank
account")
-
- // At this point, the three actors exist and a new
withdraw operation can be created.
- TalerWithdrawalEntity.new {
- // wopid is autogenerated, and momentarily the only
column
-
- }
- }
- /**
- * Future versions will include the QR code in this response.
- */
-
call.respondText("taler://withdraw/${hostName}/api/${wo.wopid}")
- return@get
}
- get("/api/config") {
- SandboxAssert(
- currencyEnv != null,
- "Currency not found. Logs should have warned"
+ SandboxAssert(
+ envName != null,
+ "Env name not found, cannot suggest Exchange."
+ )
+ val ret = TalerWithdrawalStatus(
+ selection_done = wo.selectionDone,
+ transfer_done = wo.transferDone,
+ amount = "${currencyEnv}:5",
+ suggested_exchange = "https://exchange.${envName}.taler.net/"
+ )
+ call.respond(ret)
+ return@get
+ }
+ /**
+ * Here Sandbox collects the reserve public key to be used
+ * as the wire transfer subject, and pays the exchange - which
+ * is as well collected in this request.
+ */
+ post("/api/withdrawal-operation/{wopid}") {
+
+ val wopid: String = ensureNonNull(call.parameters["wopid"])
+ val body = call.receiveJson<TalerWithdrawalConfirmation>()
+
+ newSuspendedTransaction(context = singleThreadContext) {
+ var wo = tech.libeufin.sandbox.TalerWithdrawalEntity.find {
+ tech.libeufin.sandbox.TalerWithdrawalsTable.wopid eq
java.util.UUID.fromString(wopid)
+ }.firstOrNull() ?: throw SandboxError(
+ io.ktor.http.HttpStatusCode.NotFound, "Withdrawal
operation $wopid not found."
)
- call.respond(object {
- val name = "taler-bank-integration"
-
- // FIXME: use actual version here!
- val version = "0:0:0"
- val currency = currencyEnv
- })
- }
- /**
- * not regulating the access here, as the wopid was only granted
- * to logged-in users before (at the /taler endpoint) and has
enough
- * entropy to prevent guesses.
- */
- get("/api/withdrawal-operation/{wopid}") {
- val wopid: String = ensureNonNull(call.parameters["wopid"])
- val wo = transaction {
-
- TalerWithdrawalEntity.find {
- TalerWithdrawalsTable.wopid eq UUID.fromString(wopid)
- }.firstOrNull() ?: throw SandboxError(
- HttpStatusCode.NotFound,
- "Withdrawal operation: $wopid not found"
+ if (wo.selectionDone) {
+ if (wo.transferDone) {
+ logger.info("Wallet performs again this operation that
was paid out earlier: idempotent")
+ return@newSuspendedTransaction
+ }
+ // reservePub+exchange selected but not payed: check
consistency
+ if (body.reserve_pub != wo.reservePub) throw SandboxError(
+ io.ktor.http.HttpStatusCode.Conflict,
+ "Selecting a different reserve from the one already
selected"
+ )
+ if (body.selected_exchange != wo.selectedExchangePayto)
throw SandboxError(
+ io.ktor.http.HttpStatusCode.Conflict,
+ "Selecting a different exchange from the one already
selected"
)
}
- SandboxAssert(
- envName != null,
- "Env name not found, cannot suggest Exchange."
- )
- val ret = TalerWithdrawalStatus(
- selection_done = wo.selectionDone,
- transfer_done = wo.transferDone,
- amount = "${currencyEnv}:5",
- suggested_exchange =
"https://exchange.${envName}.taler.net/"
+ // here only if (1) no selection done or (2) _only_ selection
done:
+ // both ways no transfer must have happened.
+ SandboxAssert(!wo.transferDone, "Sandbox allowed paid but
unselected reserve")
+
+ wireTransfer(
+ "sandbox-account-customer",
+ "sandbox-account-exchange",
+ "$currencyEnv:5",
+ body.reserve_pub
)
- call.respond(ret)
- return@get
+ wo.reservePub = body.reserve_pub
+ wo.selectedExchangePayto = body.selected_exchange
+ wo.selectionDone = true
+ wo.transferDone = true
}
/**
- * Here Sandbox collects the reserve public key to be used
- * as the wire transfer subject, and pays the exchange - which
- * is as well collected in this request.
+ * NOTE: is this always guaranteed to run AFTER the suspended
+ * transaction block above?
*/
- post("/api/withdrawal-operation/{wopid}") {
+ call.respond(object {
+ val transfer_done = true
+ })
+ return@post
+ }
- val wopid: String = ensureNonNull(call.parameters["wopid"])
- val body = call.receiveJson<TalerWithdrawalConfirmation>()
+ // Create a new demobank instance with a particular currency,
+ // debt limit and possibly other configuration
+ // (could also be a CLI command for now)
+ post("/demobanks") {
- newSuspendedTransaction(context = singleThreadContext) {
- var wo = TalerWithdrawalEntity.find {
- TalerWithdrawalsTable.wopid eq UUID.fromString(wopid)
- }.firstOrNull() ?: throw SandboxError(
- HttpStatusCode.NotFound, "Withdrawal operation $wopid
not found."
- )
- if (wo.selectionDone) {
- if (wo.transferDone) {
- logger.info("Wallet performs again this operation
that was paid out earlier: idempotent")
- return@newSuspendedTransaction
- }
- // reservePub+exchange selected but not payed: check
consistency
- if (body.reserve_pub != wo.reservePub) throw
SandboxError(
- HttpStatusCode.Conflict,
- "Selecting a different reserve from the one
already selected"
- )
- if (body.selected_exchange !=
wo.selectedExchangePayto) throw SandboxError(
- HttpStatusCode.Conflict,
- "Selecting a different exchange from the one
already selected"
- )
- }
- // here only if (1) no selection done or (2) _only_
selection done:
- // both ways no transfer must have happened.
- SandboxAssert(!wo.transferDone, "Sandbox allowed paid but
unselected reserve")
-
- wireTransfer(
- "sandbox-account-customer",
- "sandbox-account-exchange",
- "$currencyEnv:5",
- body.reserve_pub
- )
- wo.reservePub = body.reserve_pub
- wo.selectedExchangePayto = body.selected_exchange
- wo.selectionDone = true
- wo.transferDone = true
- }
- /**
- * NOTE: is this always guaranteed to run AFTER the suspended
- * transaction block above?
- */
- call.respond(object {
- val transfer_done = true
- })
- return@post
- }
+ }
- // Create a new demobank instance with a particular currency,
- // debt limit and possibly other configuration
- // (could also be a CLI command for now)
- post("/demobanks") {
+ // List configured demobanks
+ get("/demobanks") {
- }
+ }
- // List configured demobanks
- get("/demobanks") {
+ delete("/demobank/{demobankid") {
- }
+ }
- delete("/demobank/{demobankid") {
+ get("/demobank/{demobankid") {
- }
+ }
- get("/demobank/{demobankid") {
+ route("/demobank/{demobankid}") {
+ // Note: Unlike the old pybank, the sandbox does *not* actually
expose the
+ // taler wire gateway API, because the exchange uses the nexus.
- }
+ // Endpoint(s) for making arbitrary payments in the sandbox for
integration tests
+ // FIXME: Do we actually need this, or can we just use the sandbox
admin APIs?
+ route("/testing-api") {
- route("/demobank/{demobankid}") {
- // Note: Unlike the old pybank, the sandbox does *not*
actually expose the
- // taler wire gateway API, because the exchange uses the nexus.
+ }
- // Endpoint(s) for making arbitrary payments in the sandbox
for integration tests
- // FIXME: Do we actually need this, or can we just use the
sandbox admin APIs?
- route("/testing-api") {
+ route("/access-api") {
+ get("/accounts/{account_name}") {
+ // Authenticated. Accesses basic information (balance)
+ // about an account. (see docs)
+ // FIXME: Since we now use IBANs everywhere, maybe the
account should also be assigned an IBAN
}
- route("/access-api") {
- get("/accounts/{account_name}") {
- // Authenticated. Accesses basic information (balance)
- // about an account. (see docs)
-
- // FIXME: Since we now use IBANs everywhere, maybe the
account should also be assigned an IBAN
- }
-
- get("/accounts/{account_name}/history") {
- // New endpoint, access account history to display in
the SPA
- // (could be merged with GET /accounts/{account_name}
- }
+ get("/accounts/{account_name}/history") {
+ // New endpoint, access account history to display in the
SPA
+ // (could be merged with GET /accounts/{account_name}
+ }
- // [...]
+ // [...]
- get("/public-accounts") {
- // List public accounts. Does not require any
authentication.
- // XXX: New!
- }
-
- get("/public-accounts/{account_name}/history") {
- // Get transaction history of a public account
- }
+ get("/public-accounts") {
+ // List public accounts. Does not require any
authentication.
+ // XXX: New!
+ }
- post("/testing/register") {
- // Register a new account.
- // No authentication is required to register a new
user.
- // FIXME: Should probably not use "testing" as the
prefix, since it's used "in production" in the demobank SPA
- }
+ get("/public-accounts/{account_name}/history") {
+ // Get transaction history of a public account
}
+ post("/testing/register") {
+ // Register a new account.
+ // No authentication is required to register a new user.
+ // FIXME: Should probably not use "testing" as the
prefix, since it's used "in production" in the demobank SPA
+ }
}
+
}
}
+}
+fun serverMain(dbName: String, port: Int) {
+ execThrowableOrTerminate { dbCreateTables(dbName) }
+ val server = embeddedServer(Netty, port = port, module = sandboxApp)
logger.info("LibEuFin Sandbox running on port $port")
try {
server.start(wait = true)
--
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.
- [libeufin] branch master updated (fe31400 -> 6144c4a), gnunet, 2021/10/06
- [libeufin] 01/07: Unix domain socket server., gnunet, 2021/10/06
- [libeufin] 02/07: Unix domain sockets dependencies, gnunet, 2021/10/06
- [libeufin] 03/07: adapt test to new API, gnunet, 2021/10/06
- [libeufin] 06/07: Unix domain socket proxy: fix payload read/write., gnunet, 2021/10/06
- [libeufin] 04/07: test for Unix domain socket, gnunet, 2021/10/06
- [libeufin] 05/07: test dep, gnunet, 2021/10/06
- [libeufin] 07/07: Serve Sandbox via Unix domain socket.,
gnunet <=