diff --git a/accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/accountant/ports/postgres/dao/UserTradeVolumeRepository.kt b/accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/accountant/ports/postgres/dao/UserTradeVolumeRepository.kt index 730452f0e..d52675cb0 100644 --- a/accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/accountant/ports/postgres/dao/UserTradeVolumeRepository.kt +++ b/accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/accountant/ports/postgres/dao/UserTradeVolumeRepository.kt @@ -63,12 +63,12 @@ interface UserTradeVolumeRepository : ReactiveCrudRepository= :startDate and quote_currency = :quoteCurrency - order by date desc + group by date """ ) fun findDailyTradeVolume( diff --git a/api/api-app/src/main/kotlin/co/nilin/opex/api/app/config/RateLimitConfig.kt b/api/api-app/src/main/kotlin/co/nilin/opex/api/app/config/RateLimitConfig.kt index fa67d5e65..e11c77efa 100644 --- a/api/api-app/src/main/kotlin/co/nilin/opex/api/app/config/RateLimitConfig.kt +++ b/api/api-app/src/main/kotlin/co/nilin/opex/api/app/config/RateLimitConfig.kt @@ -108,12 +108,13 @@ class RateLimitConfig( retryAfterSeconds: Int ): Mono { logger.info("Rate limit exceeded ($identity) -- $method:$url") - exchange.response.statusCode = HttpStatus.TOO_MANY_REQUESTS - return exchange.response.writeWith( - Mono.just( - exchange.response.bufferFactory() - .wrap("Rate limit exceeded ($identity) -- $method:$url -- Retry-After, $retryAfterSeconds".toByteArray()) - ) - ) +// exchange.response.statusCode = HttpStatus.TOO_MANY_REQUESTS + throw OpexError.RateLimit.exception() +// return exchange.response.writeWith( +// Mono.just( +// exchange.response.bufferFactory() +// .wrap("Rate limit exceeded ($identity) -- $method:$url -- Retry-After, $retryAfterSeconds".toByteArray()) +// ) +// ) } } \ No newline at end of file diff --git a/api/api-app/src/main/kotlin/co/nilin/opex/api/app/interceptor/APIKeyFilterImpl.kt b/api/api-app/src/main/kotlin/co/nilin/opex/api/app/interceptor/APIKeyFilterImpl.kt index a02319314..6b505346d 100644 --- a/api/api-app/src/main/kotlin/co/nilin/opex/api/app/interceptor/APIKeyFilterImpl.kt +++ b/api/api-app/src/main/kotlin/co/nilin/opex/api/app/interceptor/APIKeyFilterImpl.kt @@ -4,8 +4,10 @@ import co.nilin.opex.api.app.security.ClientCredentialsTokenService import co.nilin.opex.api.app.security.HmacVerifier import co.nilin.opex.api.core.spi.APIKeyService import co.nilin.opex.api.core.spi.APIKeyFilter +import co.nilin.opex.common.OpexError import kotlinx.coroutines.reactor.mono import org.slf4j.LoggerFactory +import org.springframework.http.HttpStatus import org.springframework.stereotype.Component import org.springframework.web.server.ServerWebExchange import org.springframework.web.server.WebFilter @@ -42,17 +44,17 @@ class APIKeyFilterImpl( val sourceIp = request.remoteAddress?.address?.hostAddress if (!entry.allowedIps.isNullOrEmpty() && (sourceIp == null || !entry.allowedIps!!.contains(sourceIp))) { logger.warn("API key {} request from disallowed IP {}", apiKeyId, sourceIp) - null + throw OpexError.Forbidden.exception() } if (!entry.allowedEndpoints.isNullOrEmpty() && ( !entry.allowedEndpoints!!.contains(path))) { logger.warn("API key {} request to unauthorized resource {}", apiKeyId, path) - null + throw OpexError.Forbidden.exception() } else { val ts = tsHeader.toLongOrNull() val bodyHash = request.headers["X-API-BODY-SHA256"]?.firstOrNull() if (ts == null) { logger.warn("Invalid timestamp header for bot {}", apiKeyId) - null + throw OpexError.InvalidTime.exception() } else { val ok = hmacVerifier.verify( entry.secret, @@ -67,12 +69,12 @@ class APIKeyFilterImpl( ) if (!ok) { logger.warn("Invalid signature for apiKey {}", apiKeyId) - null + throw OpexError.InvalidSignature.exception() } else { val userId = entry.keycloakUserId if (userId.isNullOrBlank()) { logger.warn("API key {} has no mapped Keycloak userId; rejecting", apiKeyId) - null + throw OpexError.UnAuthorized.exception() } else { val bearer = clientTokenService.exchangeToUserToken(userId) val req = request.mutate() diff --git a/api/api-app/src/main/kotlin/co/nilin/opex/api/app/service/OpexFilterExceptionHandler.kt b/api/api-app/src/main/kotlin/co/nilin/opex/api/app/service/OpexFilterExceptionHandler.kt new file mode 100644 index 000000000..131fb0522 --- /dev/null +++ b/api/api-app/src/main/kotlin/co/nilin/opex/api/app/service/OpexFilterExceptionHandler.kt @@ -0,0 +1,36 @@ +package co.nilin.opex.api.app.service + +import co.nilin.opex.utility.error.data.OpexException +import co.nilin.opex.utility.error.spi.ErrorTranslator +import com.fasterxml.jackson.databind.ObjectMapper +import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler +import org.springframework.core.annotation.Order +import org.springframework.http.HttpStatusCode +import org.springframework.http.MediaType +import org.springframework.stereotype.Component +import org.springframework.web.server.ServerWebExchange +import reactor.core.publisher.Mono + +@Component +@Order(-2) +class OpexFilterExceptionHandler( + private val translator: ErrorTranslator, + private val objectMapper: ObjectMapper +) : ErrorWebExceptionHandler { + + override fun handle(exchange: ServerWebExchange, ex: Throwable): Mono { + + if (ex is OpexException) { + return translator.translate(ex).flatMap { error -> + exchange.response.statusCode = HttpStatusCode.valueOf(error.status.value()) + exchange.response.headers.contentType = MediaType.APPLICATION_JSON + + val bytes = objectMapper.writeValueAsBytes(error) + val buffer = exchange.response.bufferFactory().wrap(bytes) + + exchange.response.writeWith(Mono.just(buffer)) + } + } + return Mono.error(ex) + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/co/nilin/opex/common/OpexError.kt b/common/src/main/kotlin/co/nilin/opex/common/OpexError.kt index 2e44f81fa..8fab4bbe5 100644 --- a/common/src/main/kotlin/co/nilin/opex/common/OpexError.kt +++ b/common/src/main/kotlin/co/nilin/opex/common/OpexError.kt @@ -18,6 +18,10 @@ enum class OpexError(val code: Int, val message: String?, val status: HttpStatus InvalidRequestBody(1021, "Request body is invalid", HttpStatus.BAD_REQUEST), NoRecordFound(1022, "No record found for this service", HttpStatus.NOT_FOUND), ServiceDeprecated(1023, "Service deprecated", HttpStatus.SERVICE_UNAVAILABLE), + RateLimit(1024, null, HttpStatus.TOO_MANY_REQUESTS), + InvalidSignature(1025, null, HttpStatus.BAD_REQUEST), + InvalidTime(1026, null, HttpStatus.BAD_REQUEST), + // code 2000: accountant InvalidPair(2001, "%s is not available", HttpStatus.BAD_REQUEST), diff --git a/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/controller/WalletStatController.kt b/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/controller/WalletStatController.kt index 019da597c..6af632f02 100644 --- a/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/controller/WalletStatController.kt +++ b/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/controller/WalletStatController.kt @@ -74,4 +74,3 @@ class WalletStatController( } - diff --git a/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/service/DepositService.kt b/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/service/DepositService.kt index 79d6bebb2..bc9500e14 100644 --- a/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/service/DepositService.kt +++ b/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/service/DepositService.kt @@ -165,12 +165,9 @@ class DepositService( depositCommand.status = DepositStatus.INVALID } - // todo add statusReason field - if (isValid || depositCommand.depositType == DepositType.ON_CHAIN) { - traceDepositService.saveDepositInNewTransaction(depositCommand) - } + traceDepositService.saveDepositInNewTransaction(depositCommand) - if (!isValid && depositCommand.depositType != DepositType.OFF_CHAIN) { + if (!isValid) { return null } @@ -381,7 +378,7 @@ class DepositService( depositType = co.nilin.opex.wallet.core.model.DepositType.OFF_CHAIN, network = null, attachment = null, - transferMethod = if (request.transferMethod == TransferMethod.REWARD) TransferMethod.REWARD else { + transferMethod = if (request.transferMethod == TransferMethod.REWARD) TransferMethod.REWARD else { if (request.isIPG == true) TransferMethod.IPG else TransferMethod.MPG } ) diff --git a/wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/wallet/ports/postgres/dao/WalletRepository.kt b/wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/wallet/ports/postgres/dao/WalletRepository.kt index 20c81d23a..ee560ff6d 100644 --- a/wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/wallet/ports/postgres/dao/WalletRepository.kt +++ b/wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/wallet/ports/postgres/dao/WalletRepository.kt @@ -133,7 +133,7 @@ interface WalletRepository : ReactiveCrudRepository { """ select currency, sum(balance) as balance from wallet w join wallet_owner wo on w.owner = wo.id - where wallet_type in ('MAIN', 'EXCHANGE') + where wallet_type in ('MAIN', 'EXCHANGE', 'CASHOUT') and wo.uuid != '1' and w.id not in (select wallet_id from wallet_stat_exclusion) group by currency