From 4296ce014cca7c29c4be7f975622c4d41252188d Mon Sep 17 00:00:00 2001 From: Elaman Nazarkulov Date: Thu, 18 Jul 2024 20:35:56 +0600 Subject: [PATCH 1/6] Added crypto wallet generate --- build.gradle | 8 + .../openmessenger/assistant/model/Reminder.kt | 6 +- .../openmessenger/assistant/model/Todos.kt | 2 +- .../repository/TaskRepository.kt | 11 + .../repository/WalletRepository.kt | 11 + .../repository/entity/BlockchainType.kt | 20 ++ .../repository/entity/TaskEntity.kt | 37 +++ .../repository/entity/WalletEntity.kt | 38 +++ .../repository/entity/base/Dictionary.kt | 7 + .../openmessenger/service/TaskService.kt | 9 + .../service/WalletManagementService.kt | 236 ++++++++++++++++++ .../openmessenger/service/WalletService.kt | 9 + .../service/dto/CreateWalletRequest.kt | 8 + .../service/dto/DecryptWalletRequest.kt | 6 + .../openmessenger/service/dto/KeyResponse.kt | 8 + .../openmessenger/service/dto/TaskRequest.kt | 11 + .../service/impl/AssistantServiceImpl.kt | 4 + .../service/impl/CognitoUserServiceImpl.kt | 6 +- .../service/impl/TaskServiceImpl.kt | 30 +++ .../service/impl/UserServiceImpl.kt | 11 +- .../service/response/WalletResponse.kt | 11 + .../openmessenger/util/WalletHelper.kt | 11 + .../web/controller/PromptController.kt | 6 +- .../web/controller/TaskController.kt | 32 +++ .../web/controller/WalletController.kt | 38 +++ .../web/request/user/UserDetailsRequest.kt | 2 +- .../db/migration/V4__create-tasks-table.sql | 11 + .../db/migration/V5__create-wallet-table.sql | 12 + 28 files changed, 590 insertions(+), 11 deletions(-) create mode 100644 src/main/kotlin/io/openfuture/openmessenger/repository/TaskRepository.kt create mode 100644 src/main/kotlin/io/openfuture/openmessenger/repository/WalletRepository.kt create mode 100644 src/main/kotlin/io/openfuture/openmessenger/repository/entity/BlockchainType.kt create mode 100644 src/main/kotlin/io/openfuture/openmessenger/repository/entity/TaskEntity.kt create mode 100644 src/main/kotlin/io/openfuture/openmessenger/repository/entity/WalletEntity.kt create mode 100644 src/main/kotlin/io/openfuture/openmessenger/repository/entity/base/Dictionary.kt create mode 100644 src/main/kotlin/io/openfuture/openmessenger/service/TaskService.kt create mode 100644 src/main/kotlin/io/openfuture/openmessenger/service/WalletManagementService.kt create mode 100644 src/main/kotlin/io/openfuture/openmessenger/service/WalletService.kt create mode 100644 src/main/kotlin/io/openfuture/openmessenger/service/dto/CreateWalletRequest.kt create mode 100644 src/main/kotlin/io/openfuture/openmessenger/service/dto/DecryptWalletRequest.kt create mode 100644 src/main/kotlin/io/openfuture/openmessenger/service/dto/KeyResponse.kt create mode 100644 src/main/kotlin/io/openfuture/openmessenger/service/dto/TaskRequest.kt create mode 100644 src/main/kotlin/io/openfuture/openmessenger/service/impl/TaskServiceImpl.kt create mode 100644 src/main/kotlin/io/openfuture/openmessenger/service/response/WalletResponse.kt create mode 100644 src/main/kotlin/io/openfuture/openmessenger/util/WalletHelper.kt create mode 100644 src/main/kotlin/io/openfuture/openmessenger/web/controller/TaskController.kt create mode 100644 src/main/kotlin/io/openfuture/openmessenger/web/controller/WalletController.kt create mode 100644 src/main/resources/db/migration/V4__create-tasks-table.sql create mode 100644 src/main/resources/db/migration/V5__create-wallet-table.sql diff --git a/build.gradle b/build.gradle index 3f0dcff..59df67e 100644 --- a/build.gradle +++ b/build.gradle @@ -21,6 +21,7 @@ configurations { repositories { mavenCentral() + jcenter() } dependencies { @@ -65,6 +66,13 @@ dependencies { testImplementation 'org.testcontainers:postgresql:1.19.7' + // Web3j + implementation("org.web3j:core:5.0.0") + // Bitcoinj + implementation("org.bitcoinj:bitcoinj-core:0.16.1") + // Bip39 Mnemonic generator + implementation("io.github.novacrypto:BIP39:2019.01.27") + } tasks.named('test') { diff --git a/src/main/kotlin/io/openfuture/openmessenger/assistant/model/Reminder.kt b/src/main/kotlin/io/openfuture/openmessenger/assistant/model/Reminder.kt index a2811a6..a92524f 100644 --- a/src/main/kotlin/io/openfuture/openmessenger/assistant/model/Reminder.kt +++ b/src/main/kotlin/io/openfuture/openmessenger/assistant/model/Reminder.kt @@ -16,6 +16,8 @@ data class Reminder( ): BaseModel data class ReminderItem( - val remindAt: LocalDateTime?, + val remindAt: String?, val description: String? -) \ No newline at end of file +){ + +} \ No newline at end of file diff --git a/src/main/kotlin/io/openfuture/openmessenger/assistant/model/Todos.kt b/src/main/kotlin/io/openfuture/openmessenger/assistant/model/Todos.kt index cd18322..28bafaa 100644 --- a/src/main/kotlin/io/openfuture/openmessenger/assistant/model/Todos.kt +++ b/src/main/kotlin/io/openfuture/openmessenger/assistant/model/Todos.kt @@ -18,6 +18,6 @@ data class Todos( data class Todo( val executor: String?, val description: String?, - val dueDate: LocalDateTime?, + val dueDate: String?, val context: String? ) \ No newline at end of file diff --git a/src/main/kotlin/io/openfuture/openmessenger/repository/TaskRepository.kt b/src/main/kotlin/io/openfuture/openmessenger/repository/TaskRepository.kt new file mode 100644 index 0000000..0859918 --- /dev/null +++ b/src/main/kotlin/io/openfuture/openmessenger/repository/TaskRepository.kt @@ -0,0 +1,11 @@ +package io.openfuture.openmessenger.repository + +import io.openfuture.openmessenger.repository.entity.TaskEntity +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.data.jpa.repository.Query + +interface TaskRepository : JpaRepository { + @Query("SELECT t from TaskEntity t where t.assignee = :emailAddress or t.assignor =:emailAddress ") + fun findAllByAssigneeOrAssignor(emailAddress: String) : List + +} \ No newline at end of file diff --git a/src/main/kotlin/io/openfuture/openmessenger/repository/WalletRepository.kt b/src/main/kotlin/io/openfuture/openmessenger/repository/WalletRepository.kt new file mode 100644 index 0000000..658712e --- /dev/null +++ b/src/main/kotlin/io/openfuture/openmessenger/repository/WalletRepository.kt @@ -0,0 +1,11 @@ +package io.openfuture.openmessenger.repository + +import io.openfuture.openmessenger.repository.entity.WalletEntity +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.data.jpa.repository.Query + +interface WalletRepository : JpaRepository { + @Query("SELECT t from WalletEntity t where t.userId =:userId ") + fun findAllByUserId(userId: String) : List + +} \ No newline at end of file diff --git a/src/main/kotlin/io/openfuture/openmessenger/repository/entity/BlockchainType.kt b/src/main/kotlin/io/openfuture/openmessenger/repository/entity/BlockchainType.kt new file mode 100644 index 0000000..6a23542 --- /dev/null +++ b/src/main/kotlin/io/openfuture/openmessenger/repository/entity/BlockchainType.kt @@ -0,0 +1,20 @@ +package io.openfuture.openmessenger.repository.entity + +import io.openfuture.openmessenger.repository.entity.base.Dictionary + + +enum class BlockchainType( + private val id: Int, + private val value: String +) : Dictionary { + + ETH(1, "ETH"), + BTC(2, "BTC"), + BNB(3, "BNB") + ; + + override fun getId(): Int = id + + fun getValue(): String = value + +} \ No newline at end of file diff --git a/src/main/kotlin/io/openfuture/openmessenger/repository/entity/TaskEntity.kt b/src/main/kotlin/io/openfuture/openmessenger/repository/entity/TaskEntity.kt new file mode 100644 index 0000000..cc6474e --- /dev/null +++ b/src/main/kotlin/io/openfuture/openmessenger/repository/entity/TaskEntity.kt @@ -0,0 +1,37 @@ +package io.openfuture.openmessenger.repository.entity + +import jakarta.persistence.* +import java.time.LocalDate +import java.time.LocalDateTime +import java.time.LocalDateTime.now + +@Entity +@Table(name = "open_tasks") +class TaskEntity() { + constructor( + assignor: String?, + assignee: String?, + taskTitle: String?, + taskDescription: String?, + taskDate: LocalDate? + ): this() { + this.assignee = assignee + this.assignor = assignor + this.taskTitle = taskTitle + this.taskDescription = taskDescription + this.taskDate = taskDate + this.createdAt = now() + this.updatedAt = now() + } + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + var id: Long? = null + var createdAt: LocalDateTime? = now() + var updatedAt: LocalDateTime? = now() + var assignor: String? = null + var assignee: String? = null + var taskTitle: String? = null + var taskDescription: String? = null + var taskDate: LocalDate? = LocalDate.now() +} diff --git a/src/main/kotlin/io/openfuture/openmessenger/repository/entity/WalletEntity.kt b/src/main/kotlin/io/openfuture/openmessenger/repository/entity/WalletEntity.kt new file mode 100644 index 0000000..81414b1 --- /dev/null +++ b/src/main/kotlin/io/openfuture/openmessenger/repository/entity/WalletEntity.kt @@ -0,0 +1,38 @@ +package io.openfuture.openmessenger.repository.entity + +import jakarta.persistence.* +import java.time.LocalDate +import java.time.LocalDateTime +import java.time.LocalDateTime.now + +@Entity +@Table(name = "open_wallets") +class WalletEntity() { + constructor( + address: String?, + privateKey: String?, + blockchainType: BlockchainType, + seedPhrases: String?, + userId: String? + ): this() { + this.address = address + this.privateKey = privateKey + this.seedPhrases = seedPhrases + this.userId = userId + this.blockchainType = blockchainType + this.createdAt = now() + this.updatedAt = now() + } + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + var id: Long? = null + var createdAt: LocalDateTime? = now() + var updatedAt: LocalDateTime? = now() + var address: String? = null + var privateKey: String? = null + @Enumerated(EnumType.STRING) + var blockchainType: BlockchainType = BlockchainType.BTC + var seedPhrases: String? = null + var userId: String? = null +} diff --git a/src/main/kotlin/io/openfuture/openmessenger/repository/entity/base/Dictionary.kt b/src/main/kotlin/io/openfuture/openmessenger/repository/entity/base/Dictionary.kt new file mode 100644 index 0000000..78d592a --- /dev/null +++ b/src/main/kotlin/io/openfuture/openmessenger/repository/entity/base/Dictionary.kt @@ -0,0 +1,7 @@ +package io.openfuture.openmessenger.repository.entity.base + +interface Dictionary { + + fun getId(): Int + +} \ No newline at end of file diff --git a/src/main/kotlin/io/openfuture/openmessenger/service/TaskService.kt b/src/main/kotlin/io/openfuture/openmessenger/service/TaskService.kt new file mode 100644 index 0000000..9cf2b9f --- /dev/null +++ b/src/main/kotlin/io/openfuture/openmessenger/service/TaskService.kt @@ -0,0 +1,9 @@ +package io.openfuture.openmessenger.service + +import io.openfuture.openmessenger.repository.entity.TaskEntity +import io.openfuture.openmessenger.service.dto.TaskRequest + +interface TaskService { + fun save(taskRequest: TaskRequest): TaskEntity + fun get(email: String): List +} \ No newline at end of file diff --git a/src/main/kotlin/io/openfuture/openmessenger/service/WalletManagementService.kt b/src/main/kotlin/io/openfuture/openmessenger/service/WalletManagementService.kt new file mode 100644 index 0000000..9e2d29c --- /dev/null +++ b/src/main/kotlin/io/openfuture/openmessenger/service/WalletManagementService.kt @@ -0,0 +1,236 @@ +package io.openfuture.openmessenger.service + + +import io.github.novacrypto.bip39.MnemonicGenerator +import io.github.novacrypto.bip39.Words +import io.github.novacrypto.bip39.wordlists.English +import io.openfuture.openmessenger.repository.WalletRepository +import io.openfuture.openmessenger.repository.entity.BlockchainType +import io.openfuture.openmessenger.repository.entity.WalletEntity +import io.openfuture.openmessenger.service.dto.CreateWalletRequest +import io.openfuture.openmessenger.service.dto.DecryptWalletRequest +import io.openfuture.openmessenger.service.dto.KeyResponse +import io.openfuture.openmessenger.service.response.WalletResponse +import io.openfuture.openmessenger.util.getDerivedKey +import org.apache.commons.codec.binary.Base64 +import org.apache.commons.codec.binary.Hex +import org.bitcoinj.core.Address +import org.bitcoinj.crypto.ChildNumber +import org.bitcoinj.params.MainNetParams +import org.bitcoinj.script.Script +import org.bitcoinj.wallet.DeterministicSeed +import org.springframework.stereotype.Service +import org.web3j.crypto.Bip32ECKeyPair +import org.web3j.crypto.Credentials +import java.nio.charset.StandardCharsets +import java.security.NoSuchAlgorithmException +import java.security.SecureRandom +import java.security.spec.InvalidKeySpecException +import javax.crypto.Cipher +import javax.crypto.SecretKey +import javax.crypto.SecretKeyFactory +import javax.crypto.spec.IvParameterSpec +import javax.crypto.spec.PBEKeySpec +import javax.crypto.spec.SecretKeySpec + + +@Service +class WalletManagementService( + private val walletRepository: WalletRepository +) { + + val salt = "NaCl" + val initVector = "IIIIIIIIIIIIIIII" + val iterations = 1000 + val keyLength = 256 + fun generate(request: CreateWalletRequest, username: String): WalletResponse { + // Generate mnemonic words + val seedCode = generateSeedCode() + + // Generate address for blockchain + val blockchain = generateAddress(seedCode, request.blockchainType) + + val encryptedPrivateKey = encrypt(blockchain.privateKey, request.password) + val encryptedSeedPhrases = encrypt(seedCode, request.password) + + // Save address on db + val wallet = walletRepository.save(WalletEntity( + blockchain.address, + encryptedPrivateKey, + request.blockchainType, + encryptedSeedPhrases, + username + )) + + return WalletResponse(wallet.address, blockchain.privateKey, request.blockchainType, seedCode) + } + + + fun getByUserId(userId: String): List { + return walletRepository.findAllByUserId(userId) + .map { WalletResponse(it.address, it.privateKey, it.blockchainType, it.seedPhrases) } + } + + fun decryptWallet(decryptWalletRequest: DecryptWalletRequest): String{ + return decrypt(decryptWalletRequest.encryptedText, decryptWalletRequest.password) + } + + fun generateSeedCode(): String { + val seedCode = StringBuilder() + val entropy = ByteArray(Words.TWELVE.byteLength()) + SecureRandom().nextBytes(entropy) + MnemonicGenerator(English.INSTANCE) + .createMnemonic(entropy, seedCode::append) + + return seedCode.toString() + } + + fun generateAddress(seedCode: String, blockchainType: BlockchainType): KeyResponse { + + return when (blockchainType){ + BlockchainType.BNB, BlockchainType.ETH -> generateEthAddress(seedCode, blockchainType) + BlockchainType.BTC -> generateBtcAddress(seedCode) + } + + } + + fun generateEthAddress(seedCode: String, blockchainType: BlockchainType): KeyResponse { + + // Ex. m/44'/60'/0'/0 derivation path + val derivationPath = getDerivationPath(blockchainType) + + val derivedKeyPair = getDerivedKey(derivationPath, seedCode) + + // Load the wallet for the derived keypair + val credential = Credentials.create(derivedKeyPair) + + return KeyResponse( + BlockchainType.ETH.getValue(), + credential.ecKeyPair.privateKey.toString(16), + credential.address + ) + } + + fun generateBtcAddress(seedCode: String): KeyResponse { + + val creationTime = System.currentTimeMillis() / 1000L + + val seed = DeterministicSeed(seedCode, null, "", creationTime) + + val params = MainNetParams.get() //TestNet3Params.get() + val wallet = org.bitcoinj.wallet.Wallet.fromSeed( + params, + seed, + Script.ScriptType.P2PKH, + listOf( + ChildNumber(44, true), + ChildNumber(0, true), + ChildNumber.ZERO_HARDENED, + ChildNumber.ZERO, + ChildNumber.ZERO + ) + ) + val privateKey = wallet.watchingKey.getPrivateKeyEncoded(params) + + return KeyResponse( + BlockchainType.BTC.getValue(), + privateKey.toString(), + Address.fromKey(params, wallet.watchingKey, Script.ScriptType.P2PKH).toString() + ) + } + + fun getDerivationPath(blockchainType: BlockchainType): IntArray { + + val derivationPath = when (blockchainType) { + BlockchainType.ETH -> intArrayOf( + 44 or Bip32ECKeyPair.HARDENED_BIT, + 60 or Bip32ECKeyPair.HARDENED_BIT, + 0 or Bip32ECKeyPair.HARDENED_BIT, + 0, + 0 + ) + + BlockchainType.BTC -> intArrayOf( + 44 or Bip32ECKeyPair.HARDENED_BIT, + 0 or Bip32ECKeyPair.HARDENED_BIT, + 0 or Bip32ECKeyPair.HARDENED_BIT, + 0, + 0 + ) + + else -> intArrayOf( + 44 or Bip32ECKeyPair.HARDENED_BIT, + 714 or Bip32ECKeyPair.HARDENED_BIT, + 0 or Bip32ECKeyPair.HARDENED_BIT, + 0, + 0 + ) + } + + return derivationPath + } + + fun encrypt(plainText: String, password: String): String{ + + val passwordChars = password.toCharArray() + val saltBytes = salt.toByteArray() + + val secretKey: SecretKey = getKey(passwordChars, saltBytes, iterations, keyLength) + val key = secretKey.encoded + + return encrypt(key, initVector, plainText)!! + } + + fun decrypt(encryptedText: String, password: String): String{ + val passwordChars = password.toCharArray() + val saltBytes = salt.toByteArray() + val secretKey: SecretKey = getKey(passwordChars, saltBytes, iterations, keyLength) + val key = secretKey.encoded + val decrypted: String = decrypt(key, initVector, encryptedText)!! + println("Decrypted string: $decrypted") + return decrypted + } + + fun getKey(password: CharArray?, salt: ByteArray?, iterations: Int, keyLength: Int): SecretKey { + return try { + val skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256") + val spec = PBEKeySpec(password, salt, iterations, keyLength) + skf.generateSecret(spec) + } catch (e: NoSuchAlgorithmException) { + throw RuntimeException(e) + } catch (e: InvalidKeySpecException) { + throw RuntimeException(e) + } + } + + fun encrypt(key: ByteArray?, initVector: String, value: String): String? { + try { + val iv = IvParameterSpec(initVector.toByteArray()) + val skeySpec = SecretKeySpec(key, "AES") + val cipher = Cipher.getInstance("AES/CTR/NoPadding") + cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv) + val encrypted = cipher.doFinal(value.toByteArray()) + println("encrypted string base64: " + Base64.encodeBase64String(encrypted)) + println("encrypted string hex: " + Hex.encodeHexString(encrypted)) + return Base64.encodeBase64String(encrypted) + } catch (ex: Exception) { + ex.printStackTrace() + } + return null + } + + fun decrypt(key: ByteArray?, initVector: String, encrypted: String?): String? { + try { + val iv = IvParameterSpec(initVector.toByteArray(StandardCharsets.UTF_8)) + val skeySpec = SecretKeySpec(key, "AES") + val cipher = Cipher.getInstance("AES/CTR/NoPadding") + cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv) + val original = cipher.doFinal(Base64.decodeBase64(encrypted)) + return String(original) + } catch (ex: Exception) { + ex.printStackTrace() + } + return null + } + +} \ No newline at end of file diff --git a/src/main/kotlin/io/openfuture/openmessenger/service/WalletService.kt b/src/main/kotlin/io/openfuture/openmessenger/service/WalletService.kt new file mode 100644 index 0000000..ce817bc --- /dev/null +++ b/src/main/kotlin/io/openfuture/openmessenger/service/WalletService.kt @@ -0,0 +1,9 @@ +package io.openfuture.openmessenger.service + +import io.openfuture.openmessenger.repository.entity.TaskEntity +import io.openfuture.openmessenger.service.dto.TaskRequest + +interface WalletService { + fun save(taskRequest: TaskRequest): TaskEntity + fun get(email: String): List +} \ No newline at end of file diff --git a/src/main/kotlin/io/openfuture/openmessenger/service/dto/CreateWalletRequest.kt b/src/main/kotlin/io/openfuture/openmessenger/service/dto/CreateWalletRequest.kt new file mode 100644 index 0000000..2a9615b --- /dev/null +++ b/src/main/kotlin/io/openfuture/openmessenger/service/dto/CreateWalletRequest.kt @@ -0,0 +1,8 @@ +package io.openfuture.openmessenger.service.dto + +import io.openfuture.openmessenger.repository.entity.BlockchainType + +data class CreateWalletRequest( + var blockchainType: BlockchainType, + var password: String +) diff --git a/src/main/kotlin/io/openfuture/openmessenger/service/dto/DecryptWalletRequest.kt b/src/main/kotlin/io/openfuture/openmessenger/service/dto/DecryptWalletRequest.kt new file mode 100644 index 0000000..0ba9ced --- /dev/null +++ b/src/main/kotlin/io/openfuture/openmessenger/service/dto/DecryptWalletRequest.kt @@ -0,0 +1,6 @@ +package io.openfuture.openmessenger.service.dto + +data class DecryptWalletRequest( + var encryptedText: String, + var password: String +) diff --git a/src/main/kotlin/io/openfuture/openmessenger/service/dto/KeyResponse.kt b/src/main/kotlin/io/openfuture/openmessenger/service/dto/KeyResponse.kt new file mode 100644 index 0000000..c6e0bf4 --- /dev/null +++ b/src/main/kotlin/io/openfuture/openmessenger/service/dto/KeyResponse.kt @@ -0,0 +1,8 @@ +package io.openfuture.openmessenger.service.dto + + +data class KeyResponse( + val blockchain: String, + var privateKey: String, + var address: String +) diff --git a/src/main/kotlin/io/openfuture/openmessenger/service/dto/TaskRequest.kt b/src/main/kotlin/io/openfuture/openmessenger/service/dto/TaskRequest.kt new file mode 100644 index 0000000..a97864f --- /dev/null +++ b/src/main/kotlin/io/openfuture/openmessenger/service/dto/TaskRequest.kt @@ -0,0 +1,11 @@ +package io.openfuture.openmessenger.service.dto + +import java.time.LocalDate + +data class TaskRequest( + val assignor: String? , + var assignee: String? , + var taskTitle: String? , + var taskDescription: String? , + var taskDate: LocalDate? +) diff --git a/src/main/kotlin/io/openfuture/openmessenger/service/impl/AssistantServiceImpl.kt b/src/main/kotlin/io/openfuture/openmessenger/service/impl/AssistantServiceImpl.kt index 49b1aee..2cd8f56 100644 --- a/src/main/kotlin/io/openfuture/openmessenger/service/impl/AssistantServiceImpl.kt +++ b/src/main/kotlin/io/openfuture/openmessenger/service/impl/AssistantServiceImpl.kt @@ -1,7 +1,11 @@ package io.openfuture.openmessenger.service.impl +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule +import com.fasterxml.jackson.module.kotlin.KotlinModule import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.fasterxml.jackson.module.kotlin.readValue +import com.fasterxml.jackson.module.kotlin.registerKotlinModule import io.openfuture.openmessenger.assistant.gemini.GeminiService import io.openfuture.openmessenger.assistant.model.* import io.openfuture.openmessenger.repository.MessageJdbcRepository diff --git a/src/main/kotlin/io/openfuture/openmessenger/service/impl/CognitoUserServiceImpl.kt b/src/main/kotlin/io/openfuture/openmessenger/service/impl/CognitoUserServiceImpl.kt index fb5cbbf..dea622a 100644 --- a/src/main/kotlin/io/openfuture/openmessenger/service/impl/CognitoUserServiceImpl.kt +++ b/src/main/kotlin/io/openfuture/openmessenger/service/impl/CognitoUserServiceImpl.kt @@ -61,7 +61,11 @@ class CognitoUserServiceImpl( setUserPassword(request.email, request.password) createUserResult.user } catch (e: UsernameExistsException) { - throw UsernameExistsException("User name that already exists") + //throw UsernameExistsException("User name that already exists") + log.info("User Exists: {}", e.errorMessage) + val userType = UserType() + userType.username = request.email + return userType } catch (e: com.amazonaws.services.cognitoidp.model.InvalidPasswordException) { throw InvalidPasswordException("Invalid password.", e) } diff --git a/src/main/kotlin/io/openfuture/openmessenger/service/impl/TaskServiceImpl.kt b/src/main/kotlin/io/openfuture/openmessenger/service/impl/TaskServiceImpl.kt new file mode 100644 index 0000000..9440225 --- /dev/null +++ b/src/main/kotlin/io/openfuture/openmessenger/service/impl/TaskServiceImpl.kt @@ -0,0 +1,30 @@ +package io.openfuture.openmessenger.service.impl + +import io.openfuture.openmessenger.repository.TaskRepository +import io.openfuture.openmessenger.repository.entity.TaskEntity +import io.openfuture.openmessenger.service.TaskService +import io.openfuture.openmessenger.service.dto.TaskRequest +import lombok.RequiredArgsConstructor +import org.springframework.stereotype.Service + +@Service +@RequiredArgsConstructor +class TaskServiceImpl( + val taskRepository: TaskRepository +) : TaskService { + + override fun save(taskRequest: TaskRequest): TaskEntity { + val taskEntity = TaskEntity( + taskRequest.assignor, + taskRequest.assignee, + taskRequest.taskTitle, + taskRequest.taskDescription, + taskRequest.taskDate + ) + return taskRepository.save(taskEntity) + } + + override fun get(email: String): List { + return taskRepository.findAllByAssigneeOrAssignor(email) + } +} \ No newline at end of file diff --git a/src/main/kotlin/io/openfuture/openmessenger/service/impl/UserServiceImpl.kt b/src/main/kotlin/io/openfuture/openmessenger/service/impl/UserServiceImpl.kt index 5e110f3..8b7f766 100644 --- a/src/main/kotlin/io/openfuture/openmessenger/service/impl/UserServiceImpl.kt +++ b/src/main/kotlin/io/openfuture/openmessenger/service/impl/UserServiceImpl.kt @@ -5,11 +5,13 @@ import io.openfuture.openmessenger.repository.UserJpaRepository import io.openfuture.openmessenger.repository.entity.GroupChat import io.openfuture.openmessenger.repository.entity.User import io.openfuture.openmessenger.service.GroupChatService +import io.openfuture.openmessenger.service.UserAuthService import io.openfuture.openmessenger.service.UserService import io.openfuture.openmessenger.web.request.user.UserDetailsRequest import io.openfuture.openmessenger.web.response.GroupInfo import io.openfuture.openmessenger.web.response.UserDetailsResponse import lombok.RequiredArgsConstructor +import org.springframework.security.core.context.SecurityContextHolder import org.springframework.stereotype.Service @Service @@ -17,7 +19,8 @@ import org.springframework.stereotype.Service class UserServiceImpl( val userJpaRepository: UserJpaRepository, val messageRepository: MessageRepository, - val groupChatService: GroupChatService + val groupChatService: GroupChatService, + val userAuthService: UserAuthService ) : UserService { override fun getAllRecipientsBySender(username: String?): Collection? { @@ -34,10 +37,12 @@ class UserServiceImpl( } override fun getUserDetails(request: UserDetailsRequest): UserDetailsResponse { - val commonGroups = groupChatService.findCommonGroups(request.email, request.username) + val userDetails = userAuthService.current() + println("Current user: $userDetails") + val commonGroups = groupChatService.findCommonGroups(request.email, userDetails.email) val user = userJpaRepository.findByEmail(request.email) val groups = commonGroups!!.stream().map { groupChat: GroupChat? -> GroupInfo(groupChat?.id, groupChat?.name) } .toList() - return UserDetailsResponse(request.email, "", groups) + return UserDetailsResponse(request.email, user?.lastName + " " + user?.lastName, groups) } } \ No newline at end of file diff --git a/src/main/kotlin/io/openfuture/openmessenger/service/response/WalletResponse.kt b/src/main/kotlin/io/openfuture/openmessenger/service/response/WalletResponse.kt new file mode 100644 index 0000000..0ec8659 --- /dev/null +++ b/src/main/kotlin/io/openfuture/openmessenger/service/response/WalletResponse.kt @@ -0,0 +1,11 @@ +package io.openfuture.openmessenger.service.response + +import io.openfuture.openmessenger.repository.entity.BlockchainType + + +data class WalletResponse( + var address: String? = null, + var privateKey: String? = null, + var blockchainType: BlockchainType, + var seedPhrases: String? = null, +) \ No newline at end of file diff --git a/src/main/kotlin/io/openfuture/openmessenger/util/WalletHelper.kt b/src/main/kotlin/io/openfuture/openmessenger/util/WalletHelper.kt new file mode 100644 index 0000000..297c545 --- /dev/null +++ b/src/main/kotlin/io/openfuture/openmessenger/util/WalletHelper.kt @@ -0,0 +1,11 @@ +package io.openfuture.openmessenger.util + + +import org.web3j.crypto.* + +fun getDerivedKey(derivationPath: IntArray, seedCode: String): Bip32ECKeyPair { + // Generate a BIP32 master keypair from the mnemonic phrase + val masterKeypair = Bip32ECKeyPair.generateKeyPair(MnemonicUtils.generateSeed(seedCode, "")) + // Derive the keypair using the derivation path + return Bip32ECKeyPair.deriveKeyPair(masterKeypair, derivationPath) +} diff --git a/src/main/kotlin/io/openfuture/openmessenger/web/controller/PromptController.kt b/src/main/kotlin/io/openfuture/openmessenger/web/controller/PromptController.kt index 7033af9..5f0f43f 100644 --- a/src/main/kotlin/io/openfuture/openmessenger/web/controller/PromptController.kt +++ b/src/main/kotlin/io/openfuture/openmessenger/web/controller/PromptController.kt @@ -42,17 +42,17 @@ class PromptController( @PostMapping("/notes") fun getAllNotes(@RequestBody request: GetAllNotesRequest): List { - return assistantService.getAllNotes(request) + return assistantService.getAllNotes(request).sortedByDescending { it.generatedAt } } @PostMapping("/todos") fun getAllTodos(@RequestBody request: GetAllTodosRequest): List { - return assistantService.getAllTodos(request) + return assistantService.getAllTodos(request).sortedByDescending { it.generatedAt } } @PostMapping("/reminders") fun getAllReminders(@RequestBody request: GetAllRemindersRequest): List { - return assistantService.getAllReminders(request) + return assistantService.getAllReminders(request).sortedByDescending { it.generatedAt } } } diff --git a/src/main/kotlin/io/openfuture/openmessenger/web/controller/TaskController.kt b/src/main/kotlin/io/openfuture/openmessenger/web/controller/TaskController.kt new file mode 100644 index 0000000..ff05aa6 --- /dev/null +++ b/src/main/kotlin/io/openfuture/openmessenger/web/controller/TaskController.kt @@ -0,0 +1,32 @@ +package io.openfuture.openmessenger.web.controller + +import io.openfuture.openmessenger.repository.entity.TaskEntity +import io.openfuture.openmessenger.service.TaskService +import io.openfuture.openmessenger.service.UserAuthService +import io.openfuture.openmessenger.service.dto.TaskRequest +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController + +@RequestMapping("/api/v1/tasks") +@RestController +class TaskController( + val userAuthService: UserAuthService, + val taskService: TaskService +) { + + @PostMapping + fun create( + @RequestBody request: TaskRequest + ): TaskEntity { + return taskService.save(request) + } + + @GetMapping + fun get(): List { + val currentUser = userAuthService.current() + return taskService.get(currentUser.email!!) + } +} diff --git a/src/main/kotlin/io/openfuture/openmessenger/web/controller/WalletController.kt b/src/main/kotlin/io/openfuture/openmessenger/web/controller/WalletController.kt new file mode 100644 index 0000000..a079787 --- /dev/null +++ b/src/main/kotlin/io/openfuture/openmessenger/web/controller/WalletController.kt @@ -0,0 +1,38 @@ +package io.openfuture.openmessenger.web.controller + +import io.openfuture.openmessenger.repository.entity.WalletEntity +import io.openfuture.openmessenger.service.UserAuthService +import io.openfuture.openmessenger.service.WalletManagementService +import io.openfuture.openmessenger.service.dto.CreateWalletRequest +import io.openfuture.openmessenger.service.dto.DecryptWalletRequest +import io.openfuture.openmessenger.service.response.WalletResponse +import org.springframework.web.bind.annotation.* + +@RequestMapping("/api/v1/wallets") +@RestController +class WalletController( + val userAuthService: UserAuthService, + val walletManagementService: WalletManagementService +) { + + @PostMapping("/generate") + fun create( + @RequestBody request: CreateWalletRequest + ): WalletResponse { + val currentUser = userAuthService.current() + return walletManagementService.generate(request, currentUser.email!!) + } + + @PostMapping("/decrypt") + fun create( + @RequestBody request: DecryptWalletRequest + ): String { + return walletManagementService.decryptWallet(request) + } + + @GetMapping + fun get(): List { + val currentUser = userAuthService.current() + return walletManagementService.getByUserId(currentUser.email!!) + } +} diff --git a/src/main/kotlin/io/openfuture/openmessenger/web/request/user/UserDetailsRequest.kt b/src/main/kotlin/io/openfuture/openmessenger/web/request/user/UserDetailsRequest.kt index 2bf46ba..1dcd3d1 100644 --- a/src/main/kotlin/io/openfuture/openmessenger/web/request/user/UserDetailsRequest.kt +++ b/src/main/kotlin/io/openfuture/openmessenger/web/request/user/UserDetailsRequest.kt @@ -1,3 +1,3 @@ package io.openfuture.openmessenger.web.request.user -data class UserDetailsRequest(val username: String, val email: String) \ No newline at end of file +data class UserDetailsRequest(val email: String) \ No newline at end of file diff --git a/src/main/resources/db/migration/V4__create-tasks-table.sql b/src/main/resources/db/migration/V4__create-tasks-table.sql new file mode 100644 index 0000000..4def696 --- /dev/null +++ b/src/main/resources/db/migration/V4__create-tasks-table.sql @@ -0,0 +1,11 @@ +create table open_tasks +( + id serial primary key, + created_at timestamp, + updated_at timestamp, + assignor varchar(255), + assignee varchar(255), + task_title varchar(255), + task_description text, + task_date date +); \ No newline at end of file diff --git a/src/main/resources/db/migration/V5__create-wallet-table.sql b/src/main/resources/db/migration/V5__create-wallet-table.sql new file mode 100644 index 0000000..ed74088 --- /dev/null +++ b/src/main/resources/db/migration/V5__create-wallet-table.sql @@ -0,0 +1,12 @@ +create table open_wallets +( + id serial primary key, + created_at timestamp, + updated_at timestamp, + user_id varchar, + blockchain_type varchar, + address text, + private_key text, + seed_phrases text, + is_encrypted boolean +); \ No newline at end of file From f4c2a984813e9fd9df8c25af8e1aad5a885c774b Mon Sep 17 00:00:00 2001 From: Elaman Nazarkulov Date: Fri, 6 Sep 2024 10:35:40 +0600 Subject: [PATCH 2/6] Added FCM services --- build.gradle | 8 +- .../component/state/DefaultStateApi.kt | 40 ++++ .../openmessenger/component/state/StateApi.kt | 8 + .../configuration/SecurityConfig.kt | 1 + .../configuration/StateConfig.kt | 18 ++ .../configuration/property/StateProperties.kt | 12 + .../repository/UserFirebaseTokenRepository.kt | 9 + .../repository/entity/BlockchainType.kt | 3 +- .../repository/entity/UserFireBaseToken.kt | 27 +++ .../repository/entity/WalletEntity.kt | 7 +- .../service/PushNotificationService.kt | 63 ++++++ .../service/WalletManagementService.kt | 213 +----------------- .../openmessenger/service/WalletService.kt | 9 - .../service/dto/PushNotificationRequest.kt | 8 + ...eWalletRequest.kt => SaveWalletRequest.kt} | 5 +- .../service/dto/StateWalletDto.kt | 11 + .../service/firebase/FCMInitializer.kt | 38 ++++ .../service/firebase/FCMService.kt | 99 ++++++++ .../service/firebase/NotificationParameter.kt | 11 + .../service/response/WalletResponse.kt | 5 +- .../openmessenger/util/WalletHelper.kt | 11 - .../controller/UserFirebaseTokenController.kt | 32 +++ .../web/controller/WalletController.kt | 59 ++++- .../web/request/FirebaseTokenRequest.kt | 6 + src/main/resources/application.yml | 14 +- .../db/migration/V5__create-wallet-table.sql | 16 +- .../V6__create-firebase-token-table.sql | 8 + ...ba-firebase-adminsdk-1523p-0a596434ea.json | 13 ++ 28 files changed, 490 insertions(+), 264 deletions(-) create mode 100644 src/main/kotlin/io/openfuture/openmessenger/component/state/DefaultStateApi.kt create mode 100644 src/main/kotlin/io/openfuture/openmessenger/component/state/StateApi.kt create mode 100644 src/main/kotlin/io/openfuture/openmessenger/configuration/StateConfig.kt create mode 100644 src/main/kotlin/io/openfuture/openmessenger/configuration/property/StateProperties.kt create mode 100644 src/main/kotlin/io/openfuture/openmessenger/repository/UserFirebaseTokenRepository.kt create mode 100644 src/main/kotlin/io/openfuture/openmessenger/repository/entity/UserFireBaseToken.kt create mode 100644 src/main/kotlin/io/openfuture/openmessenger/service/PushNotificationService.kt delete mode 100644 src/main/kotlin/io/openfuture/openmessenger/service/WalletService.kt create mode 100644 src/main/kotlin/io/openfuture/openmessenger/service/dto/PushNotificationRequest.kt rename src/main/kotlin/io/openfuture/openmessenger/service/dto/{CreateWalletRequest.kt => SaveWalletRequest.kt} (67%) create mode 100644 src/main/kotlin/io/openfuture/openmessenger/service/dto/StateWalletDto.kt create mode 100644 src/main/kotlin/io/openfuture/openmessenger/service/firebase/FCMInitializer.kt create mode 100644 src/main/kotlin/io/openfuture/openmessenger/service/firebase/FCMService.kt create mode 100644 src/main/kotlin/io/openfuture/openmessenger/service/firebase/NotificationParameter.kt delete mode 100644 src/main/kotlin/io/openfuture/openmessenger/util/WalletHelper.kt create mode 100644 src/main/kotlin/io/openfuture/openmessenger/web/controller/UserFirebaseTokenController.kt create mode 100644 src/main/kotlin/io/openfuture/openmessenger/web/request/FirebaseTokenRequest.kt create mode 100644 src/main/resources/db/migration/V6__create-firebase-token-table.sql create mode 100644 src/main/resources/open-chat-5f7ba-firebase-adminsdk-1523p-0a596434ea.json diff --git a/build.gradle b/build.gradle index 59df67e..1b43544 100644 --- a/build.gradle +++ b/build.gradle @@ -66,12 +66,8 @@ dependencies { testImplementation 'org.testcontainers:postgresql:1.19.7' - // Web3j - implementation("org.web3j:core:5.0.0") - // Bitcoinj - implementation("org.bitcoinj:bitcoinj-core:0.16.1") - // Bip39 Mnemonic generator - implementation("io.github.novacrypto:BIP39:2019.01.27") + implementation("org.springframework.boot:spring-boot-starter-webflux") + implementation 'com.google.firebase:firebase-admin:9.2.0' } diff --git a/src/main/kotlin/io/openfuture/openmessenger/component/state/DefaultStateApi.kt b/src/main/kotlin/io/openfuture/openmessenger/component/state/DefaultStateApi.kt new file mode 100644 index 0000000..5578af1 --- /dev/null +++ b/src/main/kotlin/io/openfuture/openmessenger/component/state/DefaultStateApi.kt @@ -0,0 +1,40 @@ +package io.openfuture.openmessenger.component.state + +import io.openfuture.openmessenger.repository.entity.BlockchainType +import io.openfuture.openmessenger.service.dto.StateWalletDto +import org.springframework.http.MediaType +import org.springframework.stereotype.Component +import org.springframework.web.reactive.function.BodyInserters +import org.springframework.web.reactive.function.client.WebClient + + +@Component +class DefaultStateApi( + private val stateWebClient: WebClient +) : StateApi { + + override fun createWallet( + address: String, + webHook: String, + blockchain: BlockchainType, + applicationId: String + ): StateWalletDto? { + val request = CreateStateWalletRequest(address, applicationId, blockchain.getValue(), webHook) + println("State save request $request") + return stateWebClient + .post() + .uri("http://localhost:8545/api/wallets/single") + .accept(MediaType.APPLICATION_JSON) + .body(BodyInserters.fromValue(request)) + .retrieve() + .bodyToMono(StateWalletDto::class.java) + .block() + } + + data class CreateStateWalletRequest( + val address: String, + val applicationId: String, + val blockchain: String, + val webhook: String + ) +} diff --git a/src/main/kotlin/io/openfuture/openmessenger/component/state/StateApi.kt b/src/main/kotlin/io/openfuture/openmessenger/component/state/StateApi.kt new file mode 100644 index 0000000..ce28d15 --- /dev/null +++ b/src/main/kotlin/io/openfuture/openmessenger/component/state/StateApi.kt @@ -0,0 +1,8 @@ +package io.openfuture.openmessenger.component.state + +import io.openfuture.openmessenger.repository.entity.BlockchainType +import io.openfuture.openmessenger.service.dto.StateWalletDto + +interface StateApi { + fun createWallet(address: String, webHook: String, blockchain: BlockchainType, applicationId: String): StateWalletDto? +} diff --git a/src/main/kotlin/io/openfuture/openmessenger/configuration/SecurityConfig.kt b/src/main/kotlin/io/openfuture/openmessenger/configuration/SecurityConfig.kt index 743dffd..ed0567d 100644 --- a/src/main/kotlin/io/openfuture/openmessenger/configuration/SecurityConfig.kt +++ b/src/main/kotlin/io/openfuture/openmessenger/configuration/SecurityConfig.kt @@ -32,6 +32,7 @@ class SecurityConfig( .authorizeHttpRequests { it.requestMatchers("/api/v1/public/login").permitAll() it.requestMatchers("/api/v1/public/signup").permitAll() + it.requestMatchers("/api/v1/wallets/webhook").permitAll() it.requestMatchers("/api/v1/attachments/download/**").permitAll() //.requestMatchers("/**").permitAll() it.anyRequest().authenticated() diff --git a/src/main/kotlin/io/openfuture/openmessenger/configuration/StateConfig.kt b/src/main/kotlin/io/openfuture/openmessenger/configuration/StateConfig.kt new file mode 100644 index 0000000..1f1ea27 --- /dev/null +++ b/src/main/kotlin/io/openfuture/openmessenger/configuration/StateConfig.kt @@ -0,0 +1,18 @@ +package io.openfuture.openmessenger.configuration + +import io.openfuture.openmessenger.configuration.property.StateProperties +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.web.reactive.function.client.WebClient + + +@Configuration +class StateConfig { + + @Bean + fun stateClient(stateProperties: StateProperties): WebClient = + WebClient.builder() + .baseUrl(stateProperties.baseUrl!!) + .build() + +} diff --git a/src/main/kotlin/io/openfuture/openmessenger/configuration/property/StateProperties.kt b/src/main/kotlin/io/openfuture/openmessenger/configuration/property/StateProperties.kt new file mode 100644 index 0000000..a340b36 --- /dev/null +++ b/src/main/kotlin/io/openfuture/openmessenger/configuration/property/StateProperties.kt @@ -0,0 +1,12 @@ +package io.openfuture.openmessenger.configuration.property + +import org.springframework.boot.context.properties.ConfigurationProperties +import org.springframework.stereotype.Component +import org.springframework.validation.annotation.Validated +import javax.validation.constraints.NotBlank +import javax.validation.constraints.NotNull + +@ConfigurationProperties(prefix = "state") +@Validated +@Component +data class StateProperties(@field:NotNull @field:NotBlank var baseUrl: String?) diff --git a/src/main/kotlin/io/openfuture/openmessenger/repository/UserFirebaseTokenRepository.kt b/src/main/kotlin/io/openfuture/openmessenger/repository/UserFirebaseTokenRepository.kt new file mode 100644 index 0000000..2995615 --- /dev/null +++ b/src/main/kotlin/io/openfuture/openmessenger/repository/UserFirebaseTokenRepository.kt @@ -0,0 +1,9 @@ +package io.openfuture.openmessenger.repository + +import io.openfuture.openmessenger.repository.entity.UserFireBaseToken +import org.springframework.data.jpa.repository.JpaRepository + +interface UserFirebaseTokenRepository : JpaRepository { + fun findAllByUserId(userId: String): List + fun existsByUserIdAndFirebaseToken(userId: String, fireBaseToken: String) : Boolean +} \ No newline at end of file diff --git a/src/main/kotlin/io/openfuture/openmessenger/repository/entity/BlockchainType.kt b/src/main/kotlin/io/openfuture/openmessenger/repository/entity/BlockchainType.kt index 6a23542..cb8b41f 100644 --- a/src/main/kotlin/io/openfuture/openmessenger/repository/entity/BlockchainType.kt +++ b/src/main/kotlin/io/openfuture/openmessenger/repository/entity/BlockchainType.kt @@ -10,7 +10,8 @@ enum class BlockchainType( ETH(1, "ETH"), BTC(2, "BTC"), - BNB(3, "BNB") + BNB(3, "BNB"), + TRX(4, "TRX") ; override fun getId(): Int = id diff --git a/src/main/kotlin/io/openfuture/openmessenger/repository/entity/UserFireBaseToken.kt b/src/main/kotlin/io/openfuture/openmessenger/repository/entity/UserFireBaseToken.kt new file mode 100644 index 0000000..255019b --- /dev/null +++ b/src/main/kotlin/io/openfuture/openmessenger/repository/entity/UserFireBaseToken.kt @@ -0,0 +1,27 @@ +package io.openfuture.openmessenger.repository.entity + +import jakarta.persistence.* +import java.time.LocalDateTime + +@Entity +@Table(name = "user_firebase_tokens") +class UserFireBaseToken() { + constructor( + userId: String, + token: String + ) : this() { + this.userId = userId + this.firebaseToken = token + this.createdAt = LocalDateTime.now() + this.updatedAt = LocalDateTime.now() + } + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + var id: Long? = null + var createdAt: LocalDateTime = LocalDateTime.now() + var updatedAt: LocalDateTime = LocalDateTime.now() + var userId: String? = null + var firebaseToken: String? = null + +} \ No newline at end of file diff --git a/src/main/kotlin/io/openfuture/openmessenger/repository/entity/WalletEntity.kt b/src/main/kotlin/io/openfuture/openmessenger/repository/entity/WalletEntity.kt index 81414b1..db7627a 100644 --- a/src/main/kotlin/io/openfuture/openmessenger/repository/entity/WalletEntity.kt +++ b/src/main/kotlin/io/openfuture/openmessenger/repository/entity/WalletEntity.kt @@ -10,14 +10,10 @@ import java.time.LocalDateTime.now class WalletEntity() { constructor( address: String?, - privateKey: String?, blockchainType: BlockchainType, - seedPhrases: String?, userId: String? ): this() { this.address = address - this.privateKey = privateKey - this.seedPhrases = seedPhrases this.userId = userId this.blockchainType = blockchainType this.createdAt = now() @@ -30,9 +26,8 @@ class WalletEntity() { var createdAt: LocalDateTime? = now() var updatedAt: LocalDateTime? = now() var address: String? = null - var privateKey: String? = null @Enumerated(EnumType.STRING) var blockchainType: BlockchainType = BlockchainType.BTC - var seedPhrases: String? = null + var balance: String? = null var userId: String? = null } diff --git a/src/main/kotlin/io/openfuture/openmessenger/service/PushNotificationService.kt b/src/main/kotlin/io/openfuture/openmessenger/service/PushNotificationService.kt new file mode 100644 index 0000000..851539f --- /dev/null +++ b/src/main/kotlin/io/openfuture/openmessenger/service/PushNotificationService.kt @@ -0,0 +1,63 @@ +package io.openfuture.openmessenger.service + + +import io.openfuture.openmessenger.service.dto.PushNotificationRequest +import io.openfuture.openmessenger.service.firebase.FCMService +import org.slf4j.LoggerFactory +import org.springframework.stereotype.Service +import java.util.concurrent.ExecutionException + + +@Service +class PushNotificationService( + private val fcmService: FCMService +) { + private val logger = LoggerFactory.getLogger(PushNotificationService::class.java) + + fun subscribe(tokens: List, topic: String) { + try { + fcmService.subscribeTopic(tokens, topic) + } catch (e: InterruptedException) { + logger.error(e.message) + } catch (e: ExecutionException) { + logger.error(e.message) + } + } + + fun sendPushNotificationWithoutData(request: PushNotificationRequest?) { + try { + fcmService.sendMessageWithoutData(request!!) + } catch (e: InterruptedException) { + logger.error(e.message) + } catch (e: ExecutionException) { + logger.error(e.message) + } + } + + fun sendPushNotificationToToken( + request: PushNotificationRequest + ) { + try { + fcmService.sendMessageToToken(mapOf(), request) + } catch (e: InterruptedException) { + logger.error(e.message) + } catch (e: ExecutionException) { + logger.error(e.message) + } + } + +// private fun toPushData(notificationCreateRequest: NotificationCreateRequest): Map { +// val pushData: MutableMap = HashMap() +// val random = Random() +// pushData["messageId"] = abs(random.nextInt().toDouble()).toString() +// pushData["text"] = LocalDateTime.now().toString() +// pushData["title"] = notificationCreateRequest.getTitle() +// pushData["url"] = notificationCreateRequest.getUrl() +// pushData["click_action"] = "KMP_NOTIFICATION_CLICK" +// pushData["status"] = notificationCreateRequest.getStatus().toString() +// pushData["notificationType"] = notificationCreateRequest.getNotificationType().toString() +// return pushData +// } + + +} \ No newline at end of file diff --git a/src/main/kotlin/io/openfuture/openmessenger/service/WalletManagementService.kt b/src/main/kotlin/io/openfuture/openmessenger/service/WalletManagementService.kt index 9e2d29c..e4f3187 100644 --- a/src/main/kotlin/io/openfuture/openmessenger/service/WalletManagementService.kt +++ b/src/main/kotlin/io/openfuture/openmessenger/service/WalletManagementService.kt @@ -1,236 +1,33 @@ package io.openfuture.openmessenger.service -import io.github.novacrypto.bip39.MnemonicGenerator -import io.github.novacrypto.bip39.Words -import io.github.novacrypto.bip39.wordlists.English import io.openfuture.openmessenger.repository.WalletRepository -import io.openfuture.openmessenger.repository.entity.BlockchainType import io.openfuture.openmessenger.repository.entity.WalletEntity -import io.openfuture.openmessenger.service.dto.CreateWalletRequest -import io.openfuture.openmessenger.service.dto.DecryptWalletRequest -import io.openfuture.openmessenger.service.dto.KeyResponse +import io.openfuture.openmessenger.service.dto.SaveWalletRequest import io.openfuture.openmessenger.service.response.WalletResponse -import io.openfuture.openmessenger.util.getDerivedKey -import org.apache.commons.codec.binary.Base64 -import org.apache.commons.codec.binary.Hex -import org.bitcoinj.core.Address -import org.bitcoinj.crypto.ChildNumber -import org.bitcoinj.params.MainNetParams -import org.bitcoinj.script.Script -import org.bitcoinj.wallet.DeterministicSeed import org.springframework.stereotype.Service -import org.web3j.crypto.Bip32ECKeyPair -import org.web3j.crypto.Credentials -import java.nio.charset.StandardCharsets -import java.security.NoSuchAlgorithmException -import java.security.SecureRandom -import java.security.spec.InvalidKeySpecException -import javax.crypto.Cipher -import javax.crypto.SecretKey -import javax.crypto.SecretKeyFactory -import javax.crypto.spec.IvParameterSpec -import javax.crypto.spec.PBEKeySpec -import javax.crypto.spec.SecretKeySpec @Service class WalletManagementService( private val walletRepository: WalletRepository ) { - - val salt = "NaCl" - val initVector = "IIIIIIIIIIIIIIII" - val iterations = 1000 - val keyLength = 256 - fun generate(request: CreateWalletRequest, username: String): WalletResponse { - // Generate mnemonic words - val seedCode = generateSeedCode() - - // Generate address for blockchain - val blockchain = generateAddress(seedCode, request.blockchainType) - - val encryptedPrivateKey = encrypt(blockchain.privateKey, request.password) - val encryptedSeedPhrases = encrypt(seedCode, request.password) + fun saveWallet(request: SaveWalletRequest, username: String): WalletResponse { // Save address on db val wallet = walletRepository.save(WalletEntity( - blockchain.address, - encryptedPrivateKey, + request.address, request.blockchainType, - encryptedSeedPhrases, username )) - return WalletResponse(wallet.address, blockchain.privateKey, request.blockchainType, seedCode) + return WalletResponse(wallet.address, wallet.balance, request.blockchainType) } fun getByUserId(userId: String): List { return walletRepository.findAllByUserId(userId) - .map { WalletResponse(it.address, it.privateKey, it.blockchainType, it.seedPhrases) } - } - - fun decryptWallet(decryptWalletRequest: DecryptWalletRequest): String{ - return decrypt(decryptWalletRequest.encryptedText, decryptWalletRequest.password) - } - - fun generateSeedCode(): String { - val seedCode = StringBuilder() - val entropy = ByteArray(Words.TWELVE.byteLength()) - SecureRandom().nextBytes(entropy) - MnemonicGenerator(English.INSTANCE) - .createMnemonic(entropy, seedCode::append) - - return seedCode.toString() - } - - fun generateAddress(seedCode: String, blockchainType: BlockchainType): KeyResponse { - - return when (blockchainType){ - BlockchainType.BNB, BlockchainType.ETH -> generateEthAddress(seedCode, blockchainType) - BlockchainType.BTC -> generateBtcAddress(seedCode) - } - - } - - fun generateEthAddress(seedCode: String, blockchainType: BlockchainType): KeyResponse { - - // Ex. m/44'/60'/0'/0 derivation path - val derivationPath = getDerivationPath(blockchainType) - - val derivedKeyPair = getDerivedKey(derivationPath, seedCode) - - // Load the wallet for the derived keypair - val credential = Credentials.create(derivedKeyPair) - - return KeyResponse( - BlockchainType.ETH.getValue(), - credential.ecKeyPair.privateKey.toString(16), - credential.address - ) - } - - fun generateBtcAddress(seedCode: String): KeyResponse { - - val creationTime = System.currentTimeMillis() / 1000L - - val seed = DeterministicSeed(seedCode, null, "", creationTime) - - val params = MainNetParams.get() //TestNet3Params.get() - val wallet = org.bitcoinj.wallet.Wallet.fromSeed( - params, - seed, - Script.ScriptType.P2PKH, - listOf( - ChildNumber(44, true), - ChildNumber(0, true), - ChildNumber.ZERO_HARDENED, - ChildNumber.ZERO, - ChildNumber.ZERO - ) - ) - val privateKey = wallet.watchingKey.getPrivateKeyEncoded(params) - - return KeyResponse( - BlockchainType.BTC.getValue(), - privateKey.toString(), - Address.fromKey(params, wallet.watchingKey, Script.ScriptType.P2PKH).toString() - ) - } - - fun getDerivationPath(blockchainType: BlockchainType): IntArray { - - val derivationPath = when (blockchainType) { - BlockchainType.ETH -> intArrayOf( - 44 or Bip32ECKeyPair.HARDENED_BIT, - 60 or Bip32ECKeyPair.HARDENED_BIT, - 0 or Bip32ECKeyPair.HARDENED_BIT, - 0, - 0 - ) - - BlockchainType.BTC -> intArrayOf( - 44 or Bip32ECKeyPair.HARDENED_BIT, - 0 or Bip32ECKeyPair.HARDENED_BIT, - 0 or Bip32ECKeyPair.HARDENED_BIT, - 0, - 0 - ) - - else -> intArrayOf( - 44 or Bip32ECKeyPair.HARDENED_BIT, - 714 or Bip32ECKeyPair.HARDENED_BIT, - 0 or Bip32ECKeyPair.HARDENED_BIT, - 0, - 0 - ) - } - - return derivationPath - } - - fun encrypt(plainText: String, password: String): String{ - - val passwordChars = password.toCharArray() - val saltBytes = salt.toByteArray() - - val secretKey: SecretKey = getKey(passwordChars, saltBytes, iterations, keyLength) - val key = secretKey.encoded - - return encrypt(key, initVector, plainText)!! - } - - fun decrypt(encryptedText: String, password: String): String{ - val passwordChars = password.toCharArray() - val saltBytes = salt.toByteArray() - val secretKey: SecretKey = getKey(passwordChars, saltBytes, iterations, keyLength) - val key = secretKey.encoded - val decrypted: String = decrypt(key, initVector, encryptedText)!! - println("Decrypted string: $decrypted") - return decrypted - } - - fun getKey(password: CharArray?, salt: ByteArray?, iterations: Int, keyLength: Int): SecretKey { - return try { - val skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256") - val spec = PBEKeySpec(password, salt, iterations, keyLength) - skf.generateSecret(spec) - } catch (e: NoSuchAlgorithmException) { - throw RuntimeException(e) - } catch (e: InvalidKeySpecException) { - throw RuntimeException(e) - } - } - - fun encrypt(key: ByteArray?, initVector: String, value: String): String? { - try { - val iv = IvParameterSpec(initVector.toByteArray()) - val skeySpec = SecretKeySpec(key, "AES") - val cipher = Cipher.getInstance("AES/CTR/NoPadding") - cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv) - val encrypted = cipher.doFinal(value.toByteArray()) - println("encrypted string base64: " + Base64.encodeBase64String(encrypted)) - println("encrypted string hex: " + Hex.encodeHexString(encrypted)) - return Base64.encodeBase64String(encrypted) - } catch (ex: Exception) { - ex.printStackTrace() - } - return null - } - - fun decrypt(key: ByteArray?, initVector: String, encrypted: String?): String? { - try { - val iv = IvParameterSpec(initVector.toByteArray(StandardCharsets.UTF_8)) - val skeySpec = SecretKeySpec(key, "AES") - val cipher = Cipher.getInstance("AES/CTR/NoPadding") - cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv) - val original = cipher.doFinal(Base64.decodeBase64(encrypted)) - return String(original) - } catch (ex: Exception) { - ex.printStackTrace() - } - return null + .map { WalletResponse(it.address, it.balance, it.blockchainType) } } } \ No newline at end of file diff --git a/src/main/kotlin/io/openfuture/openmessenger/service/WalletService.kt b/src/main/kotlin/io/openfuture/openmessenger/service/WalletService.kt deleted file mode 100644 index ce817bc..0000000 --- a/src/main/kotlin/io/openfuture/openmessenger/service/WalletService.kt +++ /dev/null @@ -1,9 +0,0 @@ -package io.openfuture.openmessenger.service - -import io.openfuture.openmessenger.repository.entity.TaskEntity -import io.openfuture.openmessenger.service.dto.TaskRequest - -interface WalletService { - fun save(taskRequest: TaskRequest): TaskEntity - fun get(email: String): List -} \ No newline at end of file diff --git a/src/main/kotlin/io/openfuture/openmessenger/service/dto/PushNotificationRequest.kt b/src/main/kotlin/io/openfuture/openmessenger/service/dto/PushNotificationRequest.kt new file mode 100644 index 0000000..781915d --- /dev/null +++ b/src/main/kotlin/io/openfuture/openmessenger/service/dto/PushNotificationRequest.kt @@ -0,0 +1,8 @@ +package io.openfuture.openmessenger.service.dto + +class PushNotificationRequest( + var title: String? = null, + val message: String, + val topic: String, + val token: String? = null +) \ No newline at end of file diff --git a/src/main/kotlin/io/openfuture/openmessenger/service/dto/CreateWalletRequest.kt b/src/main/kotlin/io/openfuture/openmessenger/service/dto/SaveWalletRequest.kt similarity index 67% rename from src/main/kotlin/io/openfuture/openmessenger/service/dto/CreateWalletRequest.kt rename to src/main/kotlin/io/openfuture/openmessenger/service/dto/SaveWalletRequest.kt index 2a9615b..439fb7d 100644 --- a/src/main/kotlin/io/openfuture/openmessenger/service/dto/CreateWalletRequest.kt +++ b/src/main/kotlin/io/openfuture/openmessenger/service/dto/SaveWalletRequest.kt @@ -2,7 +2,8 @@ package io.openfuture.openmessenger.service.dto import io.openfuture.openmessenger.repository.entity.BlockchainType -data class CreateWalletRequest( +data class SaveWalletRequest( var blockchainType: BlockchainType, - var password: String + var address: String, + var userId: String ) diff --git a/src/main/kotlin/io/openfuture/openmessenger/service/dto/StateWalletDto.kt b/src/main/kotlin/io/openfuture/openmessenger/service/dto/StateWalletDto.kt new file mode 100644 index 0000000..8a92ef5 --- /dev/null +++ b/src/main/kotlin/io/openfuture/openmessenger/service/dto/StateWalletDto.kt @@ -0,0 +1,11 @@ +package io.openfuture.openmessenger.service.dto + +import java.time.LocalDateTime + +data class StateWalletDto( + val id: String?, + val address: String?, + val webhook: String, + val blockchain: String, + val lastUpdateDate: LocalDateTime? +) diff --git a/src/main/kotlin/io/openfuture/openmessenger/service/firebase/FCMInitializer.kt b/src/main/kotlin/io/openfuture/openmessenger/service/firebase/FCMInitializer.kt new file mode 100644 index 0000000..27604e7 --- /dev/null +++ b/src/main/kotlin/io/openfuture/openmessenger/service/firebase/FCMInitializer.kt @@ -0,0 +1,38 @@ +package io.openfuture.openmessenger.service.firebase + +import com.google.auth.oauth2.GoogleCredentials +import com.google.firebase.FirebaseApp +import com.google.firebase.FirebaseOptions +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import org.springframework.beans.factory.annotation.Value +import org.springframework.context.annotation.Configuration +import org.springframework.core.io.ClassPathResource +import org.springframework.stereotype.Service +import java.io.IOException +import javax.annotation.PostConstruct + +@Service +class FCMInitializer { + + @Value("\${app.firebase-configuration-file}") + private val firebaseConfigPath: String? = null + + var logger: Logger = LoggerFactory.getLogger(FCMInitializer::class.java) + + @PostConstruct + fun initialize() { + try { + val options = FirebaseOptions.builder() + .setCredentials(GoogleCredentials.fromStream(ClassPathResource(firebaseConfigPath!!).getInputStream())) + .build() + if (FirebaseApp.getApps().isEmpty()) { + FirebaseApp.initializeApp(options) + logger.info("Firebase application has been initialized") + } + } catch (e: IOException) { + logger.error(e.message) + } + } + +} \ No newline at end of file diff --git a/src/main/kotlin/io/openfuture/openmessenger/service/firebase/FCMService.kt b/src/main/kotlin/io/openfuture/openmessenger/service/firebase/FCMService.kt new file mode 100644 index 0000000..fad7e92 --- /dev/null +++ b/src/main/kotlin/io/openfuture/openmessenger/service/firebase/FCMService.kt @@ -0,0 +1,99 @@ +package io.openfuture.openmessenger.service.firebase + +import com.google.firebase.messaging.* +import io.openfuture.openmessenger.service.dto.PushNotificationRequest +import org.slf4j.LoggerFactory +import org.springframework.stereotype.Service +import java.time.Duration +import java.util.concurrent.ExecutionException + +@Service +class FCMService { + private val logger = LoggerFactory.getLogger(FCMService::class.java) + + @Throws(InterruptedException::class, ExecutionException::class) + fun sendMessage(data: Map, request: PushNotificationRequest) { + val message = getPreconfiguredMessageWithData(data, request) + val response = sendAndGetResponse(message) + logger.info(("Sent message with data. Topic: " + request.topic) + ", " + response) + } + + @Throws(InterruptedException::class, ExecutionException::class) + fun sendMessageWithoutData(request: PushNotificationRequest) { + val message = getPreconfiguredMessageWithoutData(request) + val response = sendAndGetResponse(message) + logger.info(("Sent message without data. Topic: " + request.topic) + ", " + response) + } + + @Throws(InterruptedException::class, ExecutionException::class) + fun sendMessageToToken(data: Map, request: PushNotificationRequest) { + val message = getPreconfiguredMessageToToken(data, request) + val response = sendAndGetResponse(message) + logger.info(("Sent message to token. Device token: " + request.token) + ", " + response) + } + + @Throws(InterruptedException::class, ExecutionException::class) + private fun sendAndGetResponse(message: Message): String { + return FirebaseMessaging.getInstance().sendAsync(message).get() + } + + @Throws(ExecutionException::class, InterruptedException::class) + fun subscribeTopic(tokens: List?, topic: String?) { + FirebaseMessaging.getInstance().subscribeToTopicAsync(tokens, topic).get() + } + + @Throws(InterruptedException::class, ExecutionException::class) + fun unsubscribeTopic(tokens: List?, topic: String?): TopicManagementResponse { + return FirebaseMessaging.getInstance().unsubscribeFromTopicAsync(tokens, topic).get() + } + + private fun getAndroidConfig(topic: String): AndroidConfig { + return AndroidConfig.builder() + .setTtl(Duration.ofMinutes(2).toMillis()).setCollapseKey(topic) + .setPriority(AndroidConfig.Priority.HIGH) + .setNotification( + AndroidNotification.builder() + .setSound(NotificationParameter.SOUND.value) + .setColor(NotificationParameter.COLOR.value) + .setTag(topic).build() + ) + .build() + } + + private fun getApnsConfig(topic: String): ApnsConfig { + return ApnsConfig.builder() + .setAps(Aps.builder().setCategory(topic).setThreadId(topic).build()) + .build() + } + + private fun getPreconfiguredMessageToToken(data: Map, request: PushNotificationRequest): Message { + return getPreconfiguredMessageBuilder(request) + .putAllData(data) + .setToken(request.token) + .build() + } + + private fun getPreconfiguredMessageWithoutData(request: PushNotificationRequest): Message { + return getPreconfiguredMessageBuilder(request) + .setTopic(request.topic) + .build() + } + + private fun getPreconfiguredMessageWithData(data: Map, request: PushNotificationRequest): Message { + return getPreconfiguredMessageBuilder(request) + .putAllData(data) + .setTopic(request.topic) + .build() + } + + private fun getPreconfiguredMessageBuilder(request: PushNotificationRequest): Message.Builder { + val androidConfig = getAndroidConfig(request.topic) + val apnsConfig = getApnsConfig(request.topic) + return Message.builder() + .setApnsConfig(apnsConfig) + .setAndroidConfig(androidConfig) + .setNotification( + Notification.builder().setTitle(request.title).setBody(request.message).build() + ) + } +} \ No newline at end of file diff --git a/src/main/kotlin/io/openfuture/openmessenger/service/firebase/NotificationParameter.kt b/src/main/kotlin/io/openfuture/openmessenger/service/firebase/NotificationParameter.kt new file mode 100644 index 0000000..257a9da --- /dev/null +++ b/src/main/kotlin/io/openfuture/openmessenger/service/firebase/NotificationParameter.kt @@ -0,0 +1,11 @@ +package io.openfuture.openmessenger.service.firebase + +enum class NotificationParameter( + val value: String +) { + SOUND("default"), + COLOR("#FFFF00"); + companion object { + fun fromInt(value: String) = entries.first { it.value == value } + } +} \ No newline at end of file diff --git a/src/main/kotlin/io/openfuture/openmessenger/service/response/WalletResponse.kt b/src/main/kotlin/io/openfuture/openmessenger/service/response/WalletResponse.kt index 0ec8659..1a43d01 100644 --- a/src/main/kotlin/io/openfuture/openmessenger/service/response/WalletResponse.kt +++ b/src/main/kotlin/io/openfuture/openmessenger/service/response/WalletResponse.kt @@ -5,7 +5,6 @@ import io.openfuture.openmessenger.repository.entity.BlockchainType data class WalletResponse( var address: String? = null, - var privateKey: String? = null, - var blockchainType: BlockchainType, - var seedPhrases: String? = null, + var balance: String? = null, + var blockchainType: BlockchainType ) \ No newline at end of file diff --git a/src/main/kotlin/io/openfuture/openmessenger/util/WalletHelper.kt b/src/main/kotlin/io/openfuture/openmessenger/util/WalletHelper.kt deleted file mode 100644 index 297c545..0000000 --- a/src/main/kotlin/io/openfuture/openmessenger/util/WalletHelper.kt +++ /dev/null @@ -1,11 +0,0 @@ -package io.openfuture.openmessenger.util - - -import org.web3j.crypto.* - -fun getDerivedKey(derivationPath: IntArray, seedCode: String): Bip32ECKeyPair { - // Generate a BIP32 master keypair from the mnemonic phrase - val masterKeypair = Bip32ECKeyPair.generateKeyPair(MnemonicUtils.generateSeed(seedCode, "")) - // Derive the keypair using the derivation path - return Bip32ECKeyPair.deriveKeyPair(masterKeypair, derivationPath) -} diff --git a/src/main/kotlin/io/openfuture/openmessenger/web/controller/UserFirebaseTokenController.kt b/src/main/kotlin/io/openfuture/openmessenger/web/controller/UserFirebaseTokenController.kt new file mode 100644 index 0000000..3099f57 --- /dev/null +++ b/src/main/kotlin/io/openfuture/openmessenger/web/controller/UserFirebaseTokenController.kt @@ -0,0 +1,32 @@ +package io.openfuture.openmessenger.web.controller + +import io.openfuture.openmessenger.repository.UserFirebaseTokenRepository +import io.openfuture.openmessenger.repository.entity.UserFireBaseToken +import io.openfuture.openmessenger.web.request.FirebaseTokenRequest +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController + +@RestController +@RequestMapping("/api/v1/token") +class UserFirebaseTokenController( + val userFirebaseTokenRepository: UserFirebaseTokenRepository +) { + companion object { + val log: Logger = LoggerFactory.getLogger(UserFirebaseTokenController::class.java) + } + + @PostMapping("/add") + fun addToken( + @RequestBody request: FirebaseTokenRequest, + ): UserFireBaseToken? { + val userFireBaseToken = UserFireBaseToken(request.userId, request.token) + if (!userFirebaseTokenRepository.existsByUserIdAndFirebaseToken(request.userId, request.token)) { + return userFirebaseTokenRepository.save(userFireBaseToken) + } + throw RuntimeException("token already exists") + } +} \ No newline at end of file diff --git a/src/main/kotlin/io/openfuture/openmessenger/web/controller/WalletController.kt b/src/main/kotlin/io/openfuture/openmessenger/web/controller/WalletController.kt index a079787..6efaef3 100644 --- a/src/main/kotlin/io/openfuture/openmessenger/web/controller/WalletController.kt +++ b/src/main/kotlin/io/openfuture/openmessenger/web/controller/WalletController.kt @@ -1,33 +1,56 @@ package io.openfuture.openmessenger.web.controller -import io.openfuture.openmessenger.repository.entity.WalletEntity +import io.openfuture.openmessenger.component.state.DefaultStateApi +import io.openfuture.openmessenger.repository.UserFirebaseTokenRepository +import io.openfuture.openmessenger.service.PushNotificationService import io.openfuture.openmessenger.service.UserAuthService import io.openfuture.openmessenger.service.WalletManagementService -import io.openfuture.openmessenger.service.dto.CreateWalletRequest +import io.openfuture.openmessenger.service.dto.SaveWalletRequest import io.openfuture.openmessenger.service.dto.DecryptWalletRequest +import io.openfuture.openmessenger.service.dto.PushNotificationRequest import io.openfuture.openmessenger.service.response.WalletResponse +import org.springframework.http.HttpStatus import org.springframework.web.bind.annotation.* @RequestMapping("/api/v1/wallets") @RestController class WalletController( val userAuthService: UserAuthService, - val walletManagementService: WalletManagementService + val stateApi: DefaultStateApi, + val walletManagementService: WalletManagementService, + val pushNotificationService: PushNotificationService, + val userFirebaseTokenRepository: UserFirebaseTokenRepository ) { - @PostMapping("/generate") - fun create( - @RequestBody request: CreateWalletRequest + @PostMapping("/save") + fun saveWallet( + @RequestBody request: SaveWalletRequest ): WalletResponse { val currentUser = userAuthService.current() - return walletManagementService.generate(request, currentUser.email!!) + val wallet = walletManagementService.saveWallet(request, currentUser.email!!) + stateApi.createWallet(wallet.address!!, "http://localhost:5001/api/v1/wallets/webhook", wallet.blockchainType, "chatx") + + return wallet } - @PostMapping("/decrypt") - fun create( - @RequestBody request: DecryptWalletRequest - ): String { - return walletManagementService.decryptWallet(request) + /*@PostMapping("/webhook") + fun processWebHook( + @RequestBody webhookResponse: WebhookResponse + ) { + val pushNotificationRequest = PushNotificationRequest("test", "message", "topic", webhookResponse.message) + println("WebHook response $webhookResponse") + pushNotificationService.sendPushNotificationToToken(pushNotificationRequest) + }*/ + + @PostMapping("/notification") + fun processWebHook( + @RequestBody notificationCreate: NotificationCreate + ) { + val tokens = userFirebaseTokenRepository.findAllByUserId(notificationCreate.userId) + for (token in tokens) { + val pushNotificationRequest = PushNotificationRequest(notificationCreate.title, notificationCreate.message!!, "topic", token.firebaseToken) + pushNotificationService.sendPushNotificationToToken(pushNotificationRequest) + } } @GetMapping @@ -35,4 +58,16 @@ class WalletController( val currentUser = userAuthService.current() return walletManagementService.getByUserId(currentUser.email!!) } + + data class WebhookResponse( + val status: HttpStatus, + val url: String, + val message: String? + ) + + data class NotificationCreate( + val userId: String, + val title: String, + val message: String? + ) } diff --git a/src/main/kotlin/io/openfuture/openmessenger/web/request/FirebaseTokenRequest.kt b/src/main/kotlin/io/openfuture/openmessenger/web/request/FirebaseTokenRequest.kt new file mode 100644 index 0000000..4ee2ca3 --- /dev/null +++ b/src/main/kotlin/io/openfuture/openmessenger/web/request/FirebaseTokenRequest.kt @@ -0,0 +1,6 @@ +package io.openfuture.openmessenger.web.request + +data class FirebaseTokenRequest( + val userId: String, + val token: String, +) \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index d95c14a..94277d7 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -10,8 +10,8 @@ spring: database-platform: org.hibernate.dialect.PostgreSQLDialect aws: - access-key: AKIAXYKJRLFYPXI732OX - secret-key: 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 + access-key: AKIAXYKJRLFYOGZSQSGZ + secret-key: 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 region: us-east-2 attachments-bucket: open-chat-attachments cognito: @@ -31,4 +31,12 @@ openai: gemini: api: key: AIzaSyDWkne0n1KzsTZWEzp3x7SaHyafjd0ueHU - url: https://generativelanguage.googleapis.com/v1beta/models \ No newline at end of file + url: https://generativelanguage.googleapis.com/v1beta/models + +# STATE +state: + base-url: ${OPEN_STATE_URL:http://localhost:8545/api} + +# FIREBASE +app: + firebase-configuration-file: open-chat-5f7ba-firebase-adminsdk-1523p-0a596434ea.json \ No newline at end of file diff --git a/src/main/resources/db/migration/V5__create-wallet-table.sql b/src/main/resources/db/migration/V5__create-wallet-table.sql index ed74088..c030981 100644 --- a/src/main/resources/db/migration/V5__create-wallet-table.sql +++ b/src/main/resources/db/migration/V5__create-wallet-table.sql @@ -6,7 +6,17 @@ create table open_wallets user_id varchar, blockchain_type varchar, address text, - private_key text, - seed_phrases text, - is_encrypted boolean + balance varchar +); + +create table wallet_contracts +( + id serial primary key, + created_at timestamp, + updated_at timestamp, + blockchain_type varchar, + address text, + contract_address text, + amount varchar, + from_address text ); \ No newline at end of file diff --git a/src/main/resources/db/migration/V6__create-firebase-token-table.sql b/src/main/resources/db/migration/V6__create-firebase-token-table.sql new file mode 100644 index 0000000..66f12dc --- /dev/null +++ b/src/main/resources/db/migration/V6__create-firebase-token-table.sql @@ -0,0 +1,8 @@ +create table user_firebase_tokens +( + id serial primary key, + created_at timestamp, + updated_at timestamp, + user_id varchar, + firebase_token text +); \ No newline at end of file diff --git a/src/main/resources/open-chat-5f7ba-firebase-adminsdk-1523p-0a596434ea.json b/src/main/resources/open-chat-5f7ba-firebase-adminsdk-1523p-0a596434ea.json new file mode 100644 index 0000000..af40347 --- /dev/null +++ b/src/main/resources/open-chat-5f7ba-firebase-adminsdk-1523p-0a596434ea.json @@ -0,0 +1,13 @@ +{ + "type": "service_account", + "project_id": "open-chat-5f7ba", + "private_key_id": "0a596434ea046a80a9d2613e04de8a7e6fd338ba", + "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCqRuJrqVLYQM/7\nW3V+oEba3w6vbu0/wKiCuTCrhnxiaZ/a9Hk3NK64xWnlX+isDxWrNgz/0RYIiAWZ\niVxy+28vTsDyqWqlsUUGuGa/mBzHJ+Xxxdk8/PAMA6Tv/wn3j+CWarTYtIZ/Nxpy\nnKKqzV8uwF/lbpOPC/pUNkWJFdwLTrQK1Yy/jEPtTFz7TZkFu8kluTL/Jo9UBZkX\nBRqZsnd6WBJMhKQe4JB9ZhvVr07V1DfrVDhZHo/ETys+J0wjLI36Vpsl6w2e3MAk\ngLdVamZtMG9oBuzgpQzPcfT0u6589zDuQ0aZHBSg/Z7X7o7dmnSH7CLBWdHK4kMa\nxAEclwFFAgMBAAECggEAMLhCbZqPhSeaOqmzBw2V6Gb9HS4IfP7DWE/jgJhku4XM\nQspDaovM2DpH7+TOvng8c3XuJz3sZ3l/3KvkQ1P0vpzycRwPUyRTAza802ITDdq5\nHMHGw//9MPrT9QVMlURZ9r/GSeDxQLIEA7oUgvlrHAXYCl3mo33CXNkAcVZLVQJx\n59SbJWoigCYBFUFqQc5ohJ2sg/RAWrVGwJ0YaFs6+jRRksfvpnOqBHL/QlubknFS\nLx3h3IGfY5pN65QvNbQqggCGhyd3LvZWJMqz6GamoCqHOr5kh5b4ZaFOQJ3jmt7c\nSzojGUs1EZuiWWvpHOixdUEklbGCHvRcF4V42Qn6GwKBgQDtJF38gr6bq2kVh6kh\n9lxj96K0LoKXlipEa+CJi9q+6x4tFa185Yvk3gDaT0+Qz1fe38WPRK3fC7BMzWMc\nfC53OSZOrWA04FMcrFYKdBCbcabY/OR6tBgsIHqPB9RX6aIWyB6zRN+4YYymjVin\nWX0a60o5++8wFDFfAShZt4Mj6wKBgQC30U5QQYlubDBwO/f4Bd5Q0UAA0tP6fHzw\njuKAzT7FM8zE6yG+QN6LeFYV4XMtXZFpWMYACFE1rWHUVSV9EaHDQVeALt/9ty3H\nKxMYXOqYVBOzr2hh+BPyQKWN9+7ITw0ck/Ekn7w+Jat/ZKal+x+2tQqRzUIrQq5r\nWPsZNqKTjwKBgCiuo30NRPvZtSZfZpGP/RudQQleLUMqHMguJZATMQytsziSzndt\nvckemNDa6FB0caOnifHhG173V2Bln8okN6h2Ym7+6VFI5pk1q3ERpkO0hKYXBG9U\ndA0l6UCeXDxUtVzpKfMhLqwn+AQenYXgIUk78jjuUoNSA4JD5ZM2m0XPAoGAYmUW\n350FPOeK0jk3ljtF8srf0NEKCXZjxr0lf77eD9+Xh/05VccRmWSz6AiDh9AjS1nq\nuw+4sNv7lxZw987dYVBzzzjIS96nEYr8MLlkFmBDH5cQcAjEXJPASwthdTXjld2X\nYnxi3n15nLq6/fQ72Kh2XO+bsN6D0RCTcL6vLf8CgYAWMaYsuzWgRlJT9EJ/89kw\nGFl30ER9dP2EZicLDSGO09yfXMB/Qxf5cWWP60zdl+HqdqRezaCeSJOzeSXHJULo\nEfe8PiubV9jXZA0uYe+aHp3aTnqgEV5JbjiQBsZd3FBTjTBZ8Hg1gMBtQj8kOabu\nqDwHLz+kL3t5cCP7FWvXMw==\n-----END PRIVATE KEY-----\n", + "client_email": "firebase-adminsdk-1523p@open-chat-5f7ba.iam.gserviceaccount.com", + "client_id": "104183128038717550675", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://oauth2.googleapis.com/token", + "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", + "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-1523p%40open-chat-5f7ba.iam.gserviceaccount.com", + "universe_domain": "googleapis.com" +} From 9ab22523a009a1337ead413b372cf57b47ca9fb5 Mon Sep 17 00:00:00 2001 From: Elaman Nazarkulov Date: Tue, 10 Sep 2024 00:40:02 +0600 Subject: [PATCH 3/6] Webhook process --- .../configuration/SecurityConfig.kt | 1 + .../repository/WalletRepository.kt | 3 +++ .../repository/entity/WalletEntity.kt | 4 ++-- .../security/AwsCognitoTokenFilter.kt | 4 +++- .../service/WalletManagementService.kt | 24 +++++++++++++++++-- .../service/dto/WebhookPayloadDto.kt | 23 ++++++++++++++++++ .../service/response/LoginResponse.kt | 1 + .../web/controller/AuthController.kt | 13 +++++----- .../controller/UserFirebaseTokenController.kt | 1 + .../web/controller/WalletController.kt | 21 +++++----------- 10 files changed, 69 insertions(+), 26 deletions(-) create mode 100644 src/main/kotlin/io/openfuture/openmessenger/service/dto/WebhookPayloadDto.kt diff --git a/src/main/kotlin/io/openfuture/openmessenger/configuration/SecurityConfig.kt b/src/main/kotlin/io/openfuture/openmessenger/configuration/SecurityConfig.kt index ed0567d..ec6cc08 100644 --- a/src/main/kotlin/io/openfuture/openmessenger/configuration/SecurityConfig.kt +++ b/src/main/kotlin/io/openfuture/openmessenger/configuration/SecurityConfig.kt @@ -43,6 +43,7 @@ class SecurityConfig( authenticationManager, "/api/v1/public/login", "/api/v1/public/signup", + "/api/v1/wallets/webhook", "/api/v1/attachments/download/**" ), UsernamePasswordAuthenticationFilter::class.java diff --git a/src/main/kotlin/io/openfuture/openmessenger/repository/WalletRepository.kt b/src/main/kotlin/io/openfuture/openmessenger/repository/WalletRepository.kt index 658712e..bc1dbce 100644 --- a/src/main/kotlin/io/openfuture/openmessenger/repository/WalletRepository.kt +++ b/src/main/kotlin/io/openfuture/openmessenger/repository/WalletRepository.kt @@ -8,4 +8,7 @@ interface WalletRepository : JpaRepository { @Query("SELECT t from WalletEntity t where t.userId =:userId ") fun findAllByUserId(userId: String) : List + @Query("SELECT t from WalletEntity t where lower(t.address) =:address ") + fun findFirstByAddress(address: String) : WalletEntity? + } \ No newline at end of file diff --git a/src/main/kotlin/io/openfuture/openmessenger/repository/entity/WalletEntity.kt b/src/main/kotlin/io/openfuture/openmessenger/repository/entity/WalletEntity.kt index db7627a..c3b92be 100644 --- a/src/main/kotlin/io/openfuture/openmessenger/repository/entity/WalletEntity.kt +++ b/src/main/kotlin/io/openfuture/openmessenger/repository/entity/WalletEntity.kt @@ -9,9 +9,9 @@ import java.time.LocalDateTime.now @Table(name = "open_wallets") class WalletEntity() { constructor( - address: String?, + address: String, blockchainType: BlockchainType, - userId: String? + userId: String ): this() { this.address = address this.userId = userId diff --git a/src/main/kotlin/io/openfuture/openmessenger/security/AwsCognitoTokenFilter.kt b/src/main/kotlin/io/openfuture/openmessenger/security/AwsCognitoTokenFilter.kt index 8492dc4..ba64520 100644 --- a/src/main/kotlin/io/openfuture/openmessenger/security/AwsCognitoTokenFilter.kt +++ b/src/main/kotlin/io/openfuture/openmessenger/security/AwsCognitoTokenFilter.kt @@ -21,6 +21,7 @@ class AwsCognitoTokenFilter( authenticationManager: AuthenticationManager?, loginUrl: String?, signupUrl: String?, + webhookUrl: String?, attachmentDownloadUrl: String? ) : AbstractAuthenticationProcessingFilter(defaultFilterProcessesUrl) { companion object{ @@ -29,6 +30,7 @@ class AwsCognitoTokenFilter( private val loginRequestMatcher: RequestMatcher = AntPathRequestMatcher(loginUrl) private val signupRequestMatcher: RequestMatcher = AntPathRequestMatcher(signupUrl) + private val webhookRequestMatcher: RequestMatcher = AntPathRequestMatcher(webhookUrl) private val attachmentDownloadRequestMatcher: RequestMatcher = AntPathRequestMatcher(attachmentDownloadUrl) init { @@ -36,7 +38,7 @@ class AwsCognitoTokenFilter( } override fun requiresAuthentication(request: HttpServletRequest, response: HttpServletResponse): Boolean { - return !loginRequestMatcher.matches(request) && !signupRequestMatcher.matches(request) && !attachmentDownloadRequestMatcher.matches(request) + return !loginRequestMatcher.matches(request) && !signupRequestMatcher.matches(request) && !webhookRequestMatcher.matches(request) && !attachmentDownloadRequestMatcher.matches(request) } @Throws(AuthenticationException::class, IOException::class, ServletException::class) diff --git a/src/main/kotlin/io/openfuture/openmessenger/service/WalletManagementService.kt b/src/main/kotlin/io/openfuture/openmessenger/service/WalletManagementService.kt index e4f3187..952dbf0 100644 --- a/src/main/kotlin/io/openfuture/openmessenger/service/WalletManagementService.kt +++ b/src/main/kotlin/io/openfuture/openmessenger/service/WalletManagementService.kt @@ -1,22 +1,27 @@ package io.openfuture.openmessenger.service +import io.openfuture.openmessenger.repository.UserFirebaseTokenRepository import io.openfuture.openmessenger.repository.WalletRepository import io.openfuture.openmessenger.repository.entity.WalletEntity +import io.openfuture.openmessenger.service.dto.PushNotificationRequest import io.openfuture.openmessenger.service.dto.SaveWalletRequest +import io.openfuture.openmessenger.service.dto.WebhookPayloadDto import io.openfuture.openmessenger.service.response.WalletResponse import org.springframework.stereotype.Service @Service class WalletManagementService( - private val walletRepository: WalletRepository + private val walletRepository: WalletRepository, + private val pushNotificationService: PushNotificationService, + private val userFirebaseTokenRepository: UserFirebaseTokenRepository ) { fun saveWallet(request: SaveWalletRequest, username: String): WalletResponse { // Save address on db val wallet = walletRepository.save(WalletEntity( - request.address, + request.address.lowercase(), request.blockchainType, username )) @@ -30,4 +35,19 @@ class WalletManagementService( .map { WalletResponse(it.address, it.balance, it.blockchainType) } } + fun processWebHook(webhookPayloadDto: WebhookPayloadDto){ + println("State webhook $webhookPayloadDto") + val walletEntity = walletRepository.findFirstByAddress(webhookPayloadDto.walletAddress) + walletEntity.let { entity -> + println("State wallet entity") + val fireBaseTokens = userFirebaseTokenRepository.findAllByUserId(entity?.userId!!) + println("WebHook response $webhookPayloadDto") + for (token in fireBaseTokens) { + val message = "New transaction from ${webhookPayloadDto.transaction.from} to ${webhookPayloadDto.walletAddress} with amount ${webhookPayloadDto.transaction.amount}" + val pushNotificationRequest = PushNotificationRequest("Transfer", message, "topic", token.firebaseToken) + pushNotificationService.sendPushNotificationToToken(pushNotificationRequest) + } + } + } + } \ No newline at end of file diff --git a/src/main/kotlin/io/openfuture/openmessenger/service/dto/WebhookPayloadDto.kt b/src/main/kotlin/io/openfuture/openmessenger/service/dto/WebhookPayloadDto.kt new file mode 100644 index 0000000..241955b --- /dev/null +++ b/src/main/kotlin/io/openfuture/openmessenger/service/dto/WebhookPayloadDto.kt @@ -0,0 +1,23 @@ +package io.openfuture.openmessenger.service.dto + +import java.math.BigDecimal +import java.time.LocalDateTime + +data class WebhookPayloadDto( + val blockchain: String, + val walletAddress: String, + val userId: String?,//omit from JSON if null + val metadata: Any?,//omit from JSON if null + val transaction: WebhookTransactionDto +) { + + data class WebhookTransactionDto( + val hash: String, + val from: Set, + val to: String, + val amount: BigDecimal, + val date: LocalDateTime, + val blockHeight: Long, + val blockHash: String + ) +} diff --git a/src/main/kotlin/io/openfuture/openmessenger/service/response/LoginResponse.kt b/src/main/kotlin/io/openfuture/openmessenger/service/response/LoginResponse.kt index 1373737..a05a97a 100644 --- a/src/main/kotlin/io/openfuture/openmessenger/service/response/LoginResponse.kt +++ b/src/main/kotlin/io/openfuture/openmessenger/service/response/LoginResponse.kt @@ -3,5 +3,6 @@ package io.openfuture.openmessenger.service.response data class LoginResponse( var token: String? = null, var message: String? = null, + val userId: String? = null, var refreshToken: String? = null ) \ No newline at end of file diff --git a/src/main/kotlin/io/openfuture/openmessenger/web/controller/AuthController.kt b/src/main/kotlin/io/openfuture/openmessenger/web/controller/AuthController.kt index 18a940a..cad5477 100644 --- a/src/main/kotlin/io/openfuture/openmessenger/web/controller/AuthController.kt +++ b/src/main/kotlin/io/openfuture/openmessenger/web/controller/AuthController.kt @@ -41,9 +41,10 @@ class AuthController(val userAuthService: UserAuthService) { fun login(@RequestBody @Validated loginRequest: LoginRequest): LoginResponse { val authenticate = userAuthService.authenticate(loginRequest) return LoginResponse( - authenticate.accessToken, - "User logged in Successfully", - authenticate.refreshToken + token = authenticate.accessToken, + message = "User logged in Successfully", + userId = loginRequest.email, + refreshToken = authenticate.refreshToken ) } @@ -51,9 +52,9 @@ class AuthController(val userAuthService: UserAuthService) { fun refreshToken(@RequestBody @Validated request: RefreshTokenRequest): LoginResponse { val authenticate = userAuthService.refreshToken(request) return LoginResponse( - authenticate.accessToken, - "Token refreshed", - authenticate.refreshToken + token = authenticate.accessToken, + message = "Token refreshed", + refreshToken = authenticate.refreshToken ) } diff --git a/src/main/kotlin/io/openfuture/openmessenger/web/controller/UserFirebaseTokenController.kt b/src/main/kotlin/io/openfuture/openmessenger/web/controller/UserFirebaseTokenController.kt index 3099f57..76d288c 100644 --- a/src/main/kotlin/io/openfuture/openmessenger/web/controller/UserFirebaseTokenController.kt +++ b/src/main/kotlin/io/openfuture/openmessenger/web/controller/UserFirebaseTokenController.kt @@ -23,6 +23,7 @@ class UserFirebaseTokenController( fun addToken( @RequestBody request: FirebaseTokenRequest, ): UserFireBaseToken? { + println("Save FCM token request $request") val userFireBaseToken = UserFireBaseToken(request.userId, request.token) if (!userFirebaseTokenRepository.existsByUserIdAndFirebaseToken(request.userId, request.token)) { return userFirebaseTokenRepository.save(userFireBaseToken) diff --git a/src/main/kotlin/io/openfuture/openmessenger/web/controller/WalletController.kt b/src/main/kotlin/io/openfuture/openmessenger/web/controller/WalletController.kt index 6efaef3..95a3daa 100644 --- a/src/main/kotlin/io/openfuture/openmessenger/web/controller/WalletController.kt +++ b/src/main/kotlin/io/openfuture/openmessenger/web/controller/WalletController.kt @@ -6,10 +6,9 @@ import io.openfuture.openmessenger.service.PushNotificationService import io.openfuture.openmessenger.service.UserAuthService import io.openfuture.openmessenger.service.WalletManagementService import io.openfuture.openmessenger.service.dto.SaveWalletRequest -import io.openfuture.openmessenger.service.dto.DecryptWalletRequest import io.openfuture.openmessenger.service.dto.PushNotificationRequest +import io.openfuture.openmessenger.service.dto.WebhookPayloadDto import io.openfuture.openmessenger.service.response.WalletResponse -import org.springframework.http.HttpStatus import org.springframework.web.bind.annotation.* @RequestMapping("/api/v1/wallets") @@ -33,17 +32,15 @@ class WalletController( return wallet } - /*@PostMapping("/webhook") + @PostMapping("/webhook") fun processWebHook( - @RequestBody webhookResponse: WebhookResponse + @RequestBody webhookPayloadDto: WebhookPayloadDto ) { - val pushNotificationRequest = PushNotificationRequest("test", "message", "topic", webhookResponse.message) - println("WebHook response $webhookResponse") - pushNotificationService.sendPushNotificationToToken(pushNotificationRequest) - }*/ + walletManagementService.processWebHook(webhookPayloadDto) + } @PostMapping("/notification") - fun processWebHook( + fun processNotification( @RequestBody notificationCreate: NotificationCreate ) { val tokens = userFirebaseTokenRepository.findAllByUserId(notificationCreate.userId) @@ -59,12 +56,6 @@ class WalletController( return walletManagementService.getByUserId(currentUser.email!!) } - data class WebhookResponse( - val status: HttpStatus, - val url: String, - val message: String? - ) - data class NotificationCreate( val userId: String, val title: String, From 32aa96fce50830d2e8be291b5afdd302ac1b8053 Mon Sep 17 00:00:00 2001 From: Elaman Nazarkulov Date: Thu, 26 Sep 2024 22:09:52 +0600 Subject: [PATCH 4/6] Added usdt contracts migration --- .../BlockchainContractRepository.kt | 15 ++++++++ .../repository/UserFirebaseTokenRepository.kt | 3 +- .../entity/BlockchainContractEntity.kt | 38 +++++++++++++++++++ .../service/WalletManagementService.kt | 33 +++++++++++++--- .../controller/UserFirebaseTokenController.kt | 13 +++++-- .../web/controller/WalletController.kt | 32 ++++++++++++++-- .../V7__create-blockchain-contracts-table.sql | 22 +++++++++++ 7 files changed, 144 insertions(+), 12 deletions(-) create mode 100644 src/main/kotlin/io/openfuture/openmessenger/repository/BlockchainContractRepository.kt create mode 100644 src/main/kotlin/io/openfuture/openmessenger/repository/entity/BlockchainContractEntity.kt create mode 100644 src/main/resources/db/migration/V7__create-blockchain-contracts-table.sql diff --git a/src/main/kotlin/io/openfuture/openmessenger/repository/BlockchainContractRepository.kt b/src/main/kotlin/io/openfuture/openmessenger/repository/BlockchainContractRepository.kt new file mode 100644 index 0000000..ff69514 --- /dev/null +++ b/src/main/kotlin/io/openfuture/openmessenger/repository/BlockchainContractRepository.kt @@ -0,0 +1,15 @@ +package io.openfuture.openmessenger.repository + +import io.openfuture.openmessenger.repository.entity.BlockchainContractEntity +import io.openfuture.openmessenger.repository.entity.BlockchainType +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.data.jpa.repository.Query + +interface BlockchainContractRepository : JpaRepository { + @Query("SELECT t from BlockchainContractEntity t where t.blockchain = :blockchain and t.isTest = :isTest") + fun findFirstByBlockchain(blockchain: BlockchainType, isTest: Boolean) : BlockchainContractEntity? + + @Query("SELECT t from BlockchainContractEntity t where t.isTest = :isTest") + fun findAllByIsTest(isTest: Boolean) : List + +} \ No newline at end of file diff --git a/src/main/kotlin/io/openfuture/openmessenger/repository/UserFirebaseTokenRepository.kt b/src/main/kotlin/io/openfuture/openmessenger/repository/UserFirebaseTokenRepository.kt index 2995615..fd86d37 100644 --- a/src/main/kotlin/io/openfuture/openmessenger/repository/UserFirebaseTokenRepository.kt +++ b/src/main/kotlin/io/openfuture/openmessenger/repository/UserFirebaseTokenRepository.kt @@ -2,8 +2,9 @@ package io.openfuture.openmessenger.repository import io.openfuture.openmessenger.repository.entity.UserFireBaseToken import org.springframework.data.jpa.repository.JpaRepository +import java.util.Optional interface UserFirebaseTokenRepository : JpaRepository { fun findAllByUserId(userId: String): List - fun existsByUserIdAndFirebaseToken(userId: String, fireBaseToken: String) : Boolean + fun findFirstByUserId(userId: String): Optional } \ No newline at end of file diff --git a/src/main/kotlin/io/openfuture/openmessenger/repository/entity/BlockchainContractEntity.kt b/src/main/kotlin/io/openfuture/openmessenger/repository/entity/BlockchainContractEntity.kt new file mode 100644 index 0000000..4eb6c20 --- /dev/null +++ b/src/main/kotlin/io/openfuture/openmessenger/repository/entity/BlockchainContractEntity.kt @@ -0,0 +1,38 @@ +package io.openfuture.openmessenger.repository.entity + +import jakarta.persistence.* +import java.time.LocalDate +import java.time.LocalDateTime +import java.time.LocalDateTime.now + +@Entity +@Table(name = "blockchain_contracts") +class BlockchainContractEntity() { + constructor( + contractAddress: String, + contractName: String, + decimal: Int, + blockchain: BlockchainType, + isTest: Boolean + ): this() { + this.contractName = contractName + this.contractAddress = contractAddress + this.blockchain = blockchain + this.isTest = isTest + this.decimal = decimal + this.createdAt = now() + this.updatedAt = now() + } + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + var id: Long? = null + var createdAt: LocalDateTime? = now() + var updatedAt: LocalDateTime? = now() + var isTest: Boolean = false + @Enumerated(EnumType.STRING) + var blockchain: BlockchainType = BlockchainType.BTC + var contractAddress: String? = null + var contractName: String? = null + var decimal: Int? = 6 +} diff --git a/src/main/kotlin/io/openfuture/openmessenger/service/WalletManagementService.kt b/src/main/kotlin/io/openfuture/openmessenger/service/WalletManagementService.kt index 952dbf0..268146f 100644 --- a/src/main/kotlin/io/openfuture/openmessenger/service/WalletManagementService.kt +++ b/src/main/kotlin/io/openfuture/openmessenger/service/WalletManagementService.kt @@ -3,12 +3,14 @@ package io.openfuture.openmessenger.service import io.openfuture.openmessenger.repository.UserFirebaseTokenRepository import io.openfuture.openmessenger.repository.WalletRepository +import io.openfuture.openmessenger.repository.entity.UserFireBaseToken import io.openfuture.openmessenger.repository.entity.WalletEntity import io.openfuture.openmessenger.service.dto.PushNotificationRequest import io.openfuture.openmessenger.service.dto.SaveWalletRequest import io.openfuture.openmessenger.service.dto.WebhookPayloadDto import io.openfuture.openmessenger.service.response.WalletResponse import org.springframework.stereotype.Service +import java.math.BigDecimal @Service @@ -39,15 +41,36 @@ class WalletManagementService( println("State webhook $webhookPayloadDto") val walletEntity = walletRepository.findFirstByAddress(webhookPayloadDto.walletAddress) walletEntity.let { entity -> - println("State wallet entity") - val fireBaseTokens = userFirebaseTokenRepository.findAllByUserId(entity?.userId!!) + + val initialBalance : BigDecimal = entity?.balance?.toBigDecimal() ?: BigDecimal.ZERO + entity?.balance = initialBalance.plus(webhookPayloadDto.transaction.amount).toString() + println("Entity $entity") + walletRepository.save(entity!!) + + val fireBaseTokens = userFirebaseTokenRepository.findAllByUserId(entity.userId!!) println("WebHook response $webhookPayloadDto") for (token in fireBaseTokens) { - val message = "New transaction from ${webhookPayloadDto.transaction.from} to ${webhookPayloadDto.walletAddress} with amount ${webhookPayloadDto.transaction.amount}" - val pushNotificationRequest = PushNotificationRequest("Transfer", message, "topic", token.firebaseToken) - pushNotificationService.sendPushNotificationToToken(pushNotificationRequest) + sendNotificationToToken(webhookPayloadDto, token) } } } + private fun sendNotificationToToken( + webhookPayloadDto: WebhookPayloadDto, + token: UserFireBaseToken + ) { + val blockchainType = when (webhookPayloadDto.blockchain) { + "EthereumBlockchain", "GoerliBlockchain" -> "ETH" + "BinanceBlockchain", "BinanceTestnetBlockchain" -> "BNB" + "TronBlockchain", "TronShastaBlockchain" -> "TRX" + "BitcoinBlockchain" -> "BTC" + else -> "ETH" + } + val message = + "New transaction ${webhookPayloadDto.walletAddress.take(4)}..${webhookPayloadDto.walletAddress.takeLast(2)} - ${webhookPayloadDto.transaction.amount} $blockchainType" + val pushNotificationRequest = + PushNotificationRequest("Transfer", message, "transaction_webhook", token.firebaseToken) + pushNotificationService.sendPushNotificationToToken(pushNotificationRequest) + } + } \ No newline at end of file diff --git a/src/main/kotlin/io/openfuture/openmessenger/web/controller/UserFirebaseTokenController.kt b/src/main/kotlin/io/openfuture/openmessenger/web/controller/UserFirebaseTokenController.kt index 76d288c..a966962 100644 --- a/src/main/kotlin/io/openfuture/openmessenger/web/controller/UserFirebaseTokenController.kt +++ b/src/main/kotlin/io/openfuture/openmessenger/web/controller/UserFirebaseTokenController.kt @@ -9,6 +9,7 @@ import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController +import java.time.LocalDateTime @RestController @RequestMapping("/api/v1/token") @@ -25,9 +26,15 @@ class UserFirebaseTokenController( ): UserFireBaseToken? { println("Save FCM token request $request") val userFireBaseToken = UserFireBaseToken(request.userId, request.token) - if (!userFirebaseTokenRepository.existsByUserIdAndFirebaseToken(request.userId, request.token)) { - return userFirebaseTokenRepository.save(userFireBaseToken) + val fireBaseToken = userFirebaseTokenRepository.findFirstByUserId(request.userId) + return if (fireBaseToken.isPresent) { + fireBaseToken.get().apply { + firebaseToken = request.token + updatedAt = LocalDateTime.now() + }.let { userFirebaseTokenRepository.save(it) } + } else { + userFirebaseTokenRepository.save(userFireBaseToken) } - throw RuntimeException("token already exists") + } } \ No newline at end of file diff --git a/src/main/kotlin/io/openfuture/openmessenger/web/controller/WalletController.kt b/src/main/kotlin/io/openfuture/openmessenger/web/controller/WalletController.kt index 95a3daa..a91d2ff 100644 --- a/src/main/kotlin/io/openfuture/openmessenger/web/controller/WalletController.kt +++ b/src/main/kotlin/io/openfuture/openmessenger/web/controller/WalletController.kt @@ -1,7 +1,9 @@ package io.openfuture.openmessenger.web.controller import io.openfuture.openmessenger.component.state.DefaultStateApi +import io.openfuture.openmessenger.repository.BlockchainContractRepository import io.openfuture.openmessenger.repository.UserFirebaseTokenRepository +import io.openfuture.openmessenger.repository.entity.BlockchainType import io.openfuture.openmessenger.service.PushNotificationService import io.openfuture.openmessenger.service.UserAuthService import io.openfuture.openmessenger.service.WalletManagementService @@ -18,7 +20,8 @@ class WalletController( val stateApi: DefaultStateApi, val walletManagementService: WalletManagementService, val pushNotificationService: PushNotificationService, - val userFirebaseTokenRepository: UserFirebaseTokenRepository + val userFirebaseTokenRepository: UserFirebaseTokenRepository, + val blockchainContractRepository: BlockchainContractRepository ) { @PostMapping("/save") @@ -27,7 +30,12 @@ class WalletController( ): WalletResponse { val currentUser = userAuthService.current() val wallet = walletManagementService.saveWallet(request, currentUser.email!!) - stateApi.createWallet(wallet.address!!, "http://localhost:5001/api/v1/wallets/webhook", wallet.blockchainType, "chatx") + stateApi.createWallet( + wallet.address!!, + "http://localhost:5001/api/v1/wallets/webhook", + wallet.blockchainType, + "chatx" + ) return wallet } @@ -45,7 +53,12 @@ class WalletController( ) { val tokens = userFirebaseTokenRepository.findAllByUserId(notificationCreate.userId) for (token in tokens) { - val pushNotificationRequest = PushNotificationRequest(notificationCreate.title, notificationCreate.message!!, "topic", token.firebaseToken) + val pushNotificationRequest = PushNotificationRequest( + notificationCreate.title, + notificationCreate.message!!, + "topic", + token.firebaseToken + ) pushNotificationService.sendPushNotificationToToken(pushNotificationRequest) } } @@ -56,9 +69,22 @@ class WalletController( return walletManagementService.getByUserId(currentUser.email!!) } + @GetMapping("/contracts") + fun getContracts(@RequestParam(defaultValue = "true") isTest: Boolean): List { + return blockchainContractRepository.findAllByIsTest(isTest).map { + ContractDto(it.contractName!!, it.contractAddress!!, it.blockchain) + } + } + data class NotificationCreate( val userId: String, val title: String, val message: String? ) + + data class ContractDto( + val name: String, + val address: String, + val blockchain: BlockchainType + ) } diff --git a/src/main/resources/db/migration/V7__create-blockchain-contracts-table.sql b/src/main/resources/db/migration/V7__create-blockchain-contracts-table.sql new file mode 100644 index 0000000..b11aadd --- /dev/null +++ b/src/main/resources/db/migration/V7__create-blockchain-contracts-table.sql @@ -0,0 +1,22 @@ +create table blockchain_contracts +( + id serial primary key, + created_at timestamp, + updated_at timestamp, + blockchain varchar, + is_test boolean default false, + contract_name text, + contract_address text, + decimal int +); + +insert into blockchain_contracts +(created_at, updated_at, blockchain, is_test, contract_name, contract_address, decimal) +values (now(), now(), 'ETH', false, 'Tether USDT', '0xdac17f958d2ee523a2206206994597c13d831ec7', 6), + (now(), now(), 'ETH', false, 'USD Coin', '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', 6), + (now(), now(), 'ETH', true, 'Tether USDT', '0xaA8E23Fb1079EA71e0a56F48a2aA51851D8433D0', 6), + (now(), now(), 'ETH', true, 'USD Coin', '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238', 6), + (now(), now(), 'TRX', false, 'Tether USDT', 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t', 6), + (now(), now(), 'TRX', true, 'Tether USDT', 'TG3XXyExBkPp9nzdajDZsozEu4BkaSJozs', 6), + (now(), now(), 'BNB', false, 'USDT Token', '0x55d398326f99059ff775485246999027b3197955', 18), + (now(), now(), 'BNB', true, 'USDT Token', '0x337610d27c682E347C9cD60BD4b3b107C9d34dDd', 18); \ No newline at end of file From ffc09f26ed55797de2054b4e7db4e13066b080d7 Mon Sep 17 00:00:00 2001 From: Elaman Nazarkulov Date: Thu, 26 Dec 2024 10:43:23 +0600 Subject: [PATCH 5/6] Refresh token api to public --- .../openfuture/openmessenger/configuration/SecurityConfig.kt | 2 ++ .../openmessenger/security/AwsCognitoTokenFilter.kt | 4 +++- .../openmessenger/service/dto/RefreshTokenRequest.kt | 2 +- .../openmessenger/service/impl/UserAuthServiceImpl.kt | 5 ++--- .../openmessenger/web/controller/WalletController.kt | 1 + 5 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/io/openfuture/openmessenger/configuration/SecurityConfig.kt b/src/main/kotlin/io/openfuture/openmessenger/configuration/SecurityConfig.kt index ec6cc08..7165209 100644 --- a/src/main/kotlin/io/openfuture/openmessenger/configuration/SecurityConfig.kt +++ b/src/main/kotlin/io/openfuture/openmessenger/configuration/SecurityConfig.kt @@ -32,6 +32,7 @@ class SecurityConfig( .authorizeHttpRequests { it.requestMatchers("/api/v1/public/login").permitAll() it.requestMatchers("/api/v1/public/signup").permitAll() + it.requestMatchers("/api/v1/refreshToken").permitAll() it.requestMatchers("/api/v1/wallets/webhook").permitAll() it.requestMatchers("/api/v1/attachments/download/**").permitAll() //.requestMatchers("/**").permitAll() @@ -43,6 +44,7 @@ class SecurityConfig( authenticationManager, "/api/v1/public/login", "/api/v1/public/signup", + "/api/v1/refreshToken", "/api/v1/wallets/webhook", "/api/v1/attachments/download/**" ), diff --git a/src/main/kotlin/io/openfuture/openmessenger/security/AwsCognitoTokenFilter.kt b/src/main/kotlin/io/openfuture/openmessenger/security/AwsCognitoTokenFilter.kt index ba64520..4d661a8 100644 --- a/src/main/kotlin/io/openfuture/openmessenger/security/AwsCognitoTokenFilter.kt +++ b/src/main/kotlin/io/openfuture/openmessenger/security/AwsCognitoTokenFilter.kt @@ -21,6 +21,7 @@ class AwsCognitoTokenFilter( authenticationManager: AuthenticationManager?, loginUrl: String?, signupUrl: String?, + refreshTokenUrl: String?, webhookUrl: String?, attachmentDownloadUrl: String? ) : AbstractAuthenticationProcessingFilter(defaultFilterProcessesUrl) { @@ -30,6 +31,7 @@ class AwsCognitoTokenFilter( private val loginRequestMatcher: RequestMatcher = AntPathRequestMatcher(loginUrl) private val signupRequestMatcher: RequestMatcher = AntPathRequestMatcher(signupUrl) + private val refreshTokenRequestMatcher: RequestMatcher = AntPathRequestMatcher(refreshTokenUrl) private val webhookRequestMatcher: RequestMatcher = AntPathRequestMatcher(webhookUrl) private val attachmentDownloadRequestMatcher: RequestMatcher = AntPathRequestMatcher(attachmentDownloadUrl) @@ -38,7 +40,7 @@ class AwsCognitoTokenFilter( } override fun requiresAuthentication(request: HttpServletRequest, response: HttpServletResponse): Boolean { - return !loginRequestMatcher.matches(request) && !signupRequestMatcher.matches(request) && !webhookRequestMatcher.matches(request) && !attachmentDownloadRequestMatcher.matches(request) + return !loginRequestMatcher.matches(request) && !signupRequestMatcher.matches(request) && !webhookRequestMatcher.matches(request) && !attachmentDownloadRequestMatcher.matches(request) && !refreshTokenRequestMatcher.matches(request) } @Throws(AuthenticationException::class, IOException::class, ServletException::class) diff --git a/src/main/kotlin/io/openfuture/openmessenger/service/dto/RefreshTokenRequest.kt b/src/main/kotlin/io/openfuture/openmessenger/service/dto/RefreshTokenRequest.kt index c99620c..e82fdd6 100644 --- a/src/main/kotlin/io/openfuture/openmessenger/service/dto/RefreshTokenRequest.kt +++ b/src/main/kotlin/io/openfuture/openmessenger/service/dto/RefreshTokenRequest.kt @@ -1,3 +1,3 @@ package io.openfuture.openmessenger.service.dto -data class RefreshTokenRequest(var refreshToken: String) \ No newline at end of file +data class RefreshTokenRequest(var refreshToken: String, var userId: String) \ No newline at end of file diff --git a/src/main/kotlin/io/openfuture/openmessenger/service/impl/UserAuthServiceImpl.kt b/src/main/kotlin/io/openfuture/openmessenger/service/impl/UserAuthServiceImpl.kt index 84c94c7..226fbe5 100644 --- a/src/main/kotlin/io/openfuture/openmessenger/service/impl/UserAuthServiceImpl.kt +++ b/src/main/kotlin/io/openfuture/openmessenger/service/impl/UserAuthServiceImpl.kt @@ -43,10 +43,9 @@ class UserAuthServiceImpl( } override fun refreshToken(request: RefreshTokenRequest): AuthenticatedResponse { - val current = current() - val result = cognitoUserService.refreshAccessToken(current.id, request.refreshToken) + val result = cognitoUserService.refreshAccessToken(request.userId, request.refreshToken) return AuthenticatedResponse( - current.email, + request.userId, result?.accessToken, result?.idToken, result?.refreshToken diff --git a/src/main/kotlin/io/openfuture/openmessenger/web/controller/WalletController.kt b/src/main/kotlin/io/openfuture/openmessenger/web/controller/WalletController.kt index a91d2ff..b5fc705 100644 --- a/src/main/kotlin/io/openfuture/openmessenger/web/controller/WalletController.kt +++ b/src/main/kotlin/io/openfuture/openmessenger/web/controller/WalletController.kt @@ -30,6 +30,7 @@ class WalletController( ): WalletResponse { val currentUser = userAuthService.current() val wallet = walletManagementService.saveWallet(request, currentUser.email!!) + println("Wallet saved: $wallet") stateApi.createWallet( wallet.address!!, "http://localhost:5001/api/v1/wallets/webhook", From 9d48b2d45771abbffbe7568533dfd462649a37a0 Mon Sep 17 00:00:00 2001 From: Beksultan Date: Mon, 30 Dec 2024 16:09:00 +0600 Subject: [PATCH 6/6] Merge develop --- .../openmessenger/configuration/SecurityConfig.kt | 9 ++------- .../openmessenger/security/AwsCognitoTokenFilter.kt | 5 +++-- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/main/kotlin/io/openfuture/openmessenger/configuration/SecurityConfig.kt b/src/main/kotlin/io/openfuture/openmessenger/configuration/SecurityConfig.kt index 7223597..f93f2d7 100644 --- a/src/main/kotlin/io/openfuture/openmessenger/configuration/SecurityConfig.kt +++ b/src/main/kotlin/io/openfuture/openmessenger/configuration/SecurityConfig.kt @@ -2,7 +2,6 @@ package io.openfuture.openmessenger.configuration import io.openfuture.openmessenger.security.AwsCognitoTokenFilter import io.openfuture.openmessenger.security.CognitoAuthenticationProvider -import jakarta.servlet.http.HttpServletRequest import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.security.config.Customizer @@ -14,9 +13,6 @@ import org.springframework.security.config.annotation.web.configurers.SessionMan import org.springframework.security.config.http.SessionCreationPolicy import org.springframework.security.web.SecurityFilterChain import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter -import org.springframework.web.cors.CorsConfiguration -import org.springframework.web.cors.CorsConfigurationSource -import org.springframework.web.cors.UrlBasedCorsConfigurationSource @Configuration @EnableWebSecurity @@ -54,10 +50,9 @@ class SecurityConfig( "/api/v1/public/login", "/api/v1/public/signup", "/api/v1/attachments/download/**", - listOf("/*", "/webjars/**", "/js/*", "/img/*", "/css/*", "/video/*") + listOf("/*", "/webjars/**", "/js/*", "/img/*", "/css/*", "/video/*"), "/api/v1/refreshToken", - "/api/v1/wallets/webhook", - "/api/v1/attachments/download/**" + "/api/v1/wallets/webhook" ), UsernamePasswordAuthenticationFilter::class.java ) diff --git a/src/main/kotlin/io/openfuture/openmessenger/security/AwsCognitoTokenFilter.kt b/src/main/kotlin/io/openfuture/openmessenger/security/AwsCognitoTokenFilter.kt index de2cbba..a695df8 100644 --- a/src/main/kotlin/io/openfuture/openmessenger/security/AwsCognitoTokenFilter.kt +++ b/src/main/kotlin/io/openfuture/openmessenger/security/AwsCognitoTokenFilter.kt @@ -25,9 +25,8 @@ class AwsCognitoTokenFilter( allowedPages: List, refreshTokenUrl: String?, webhookUrl: String?, - attachmentDownloadUrl: String? ) : AbstractAuthenticationProcessingFilter(defaultFilterProcessesUrl) { - companion object{ + companion object { private val log = LoggerFactory.getLogger(AwsCognitoTokenFilter::class.java) } @@ -47,6 +46,8 @@ class AwsCognitoTokenFilter( return !loginRequestMatcher.matches(request) && !signupRequestMatcher.matches(request) && !attachmentDownloadRequestMatcher.matches(request) && + !refreshTokenRequestMatcher.matches(request) && + !webhookRequestMatcher.matches(request) && allowedPagesRequestMatchers.all { !it.matches(request) } }