diff --git a/build.gradle.kts b/build.gradle.kts index 9891cc1..116e9dc 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -63,7 +63,7 @@ dependencies { testImplementation("io.kotest:kotest-assertions-json:5.5.5") // NftKit - implementation("id.walt:waltid-nftkit:1.2305230002.0") + implementation("id.walt:waltid-nftkit:1.2308101426.0") // HTTP / Client: ktor implementation("io.ktor:ktor-client-core:2.2.4") diff --git a/config/idp-config.json b/config/idp-config.json index 801714e..6a9299d 100644 --- a/config/idp-config.json +++ b/config/idp-config.json @@ -105,6 +105,12 @@ "factorySmartContractAddress": "", "smartContractAddress": "0xa9ccb9756a0ee7eb", "collectionPath": "/public/exampleNFTCollection" + }, + "ALGORAND": { + "chain": "TESTNET", + "factorySmartContractAddress": "", + "smartContractAddress": "266553681", + "collectionPath": "" } } }, diff --git a/src/main/kotlin/id/walt/idp/config/ClaimConfig.kt b/src/main/kotlin/id/walt/idp/config/ClaimConfig.kt index 87ba8db..77e1781 100644 --- a/src/main/kotlin/id/walt/idp/config/ClaimConfig.kt +++ b/src/main/kotlin/id/walt/idp/config/ClaimConfig.kt @@ -61,7 +61,7 @@ class NFTClaimMapping( } ChainEcosystem.POLKADOT -> verificationResult.nftresponseVerificationResult.metadata?.uniqueNftMetadata?.attributes?.let { a -> a.firstOrNull { a -> a.name == mappingDefinition.trait }?.value} ChainEcosystem.FLOW -> verificationResult.nftresponseVerificationResult.metadata?.flowNftMetadata?.traits?.traits?.firstOrNull { a -> a.name == mappingDefinition.trait }?.value - + ChainEcosystem.ALGORAND -> verificationResult.nftresponseVerificationResult.metadata?.algorandNftMetadata }?: throw BadRequestResponse("Requested nft metadata trait not found in verification response") diff --git a/src/main/kotlin/id/walt/idp/nfts/NFTClaims.kt b/src/main/kotlin/id/walt/idp/nfts/NFTClaims.kt index fd4452b..f7c26e9 100644 --- a/src/main/kotlin/id/walt/idp/nfts/NFTClaims.kt +++ b/src/main/kotlin/id/walt/idp/nfts/NFTClaims.kt @@ -19,7 +19,7 @@ data class NftTokenConstraint( ) enum class ChainEcosystem { - EVM, TEZOS, NEAR , POLKADOT , FLOW + EVM, TEZOS, NEAR , POLKADOT , FLOW , ALGORAND } data class NftTokenClaim( diff --git a/src/main/kotlin/id/walt/idp/nfts/NFTController.kt b/src/main/kotlin/id/walt/idp/nfts/NFTController.kt index 8d9762f..25aba1b 100644 --- a/src/main/kotlin/id/walt/idp/nfts/NFTController.kt +++ b/src/main/kotlin/id/walt/idp/nfts/NFTController.kt @@ -91,8 +91,12 @@ object NFTController { address = siwfManager.getAddress(message) siwfManager.verifySignature(session!!, message, signature) + } - + ChainEcosystem.ALGORAND -> { + address = SiwaManager.getAddress(message) + val publicKey = SiwaManager.getPublicKey(message) + SiwaManager.verifySignature(session!!, message, publicKey,signature) } } diff --git a/src/main/kotlin/id/walt/idp/nfts/NFTManager.kt b/src/main/kotlin/id/walt/idp/nfts/NFTManager.kt index 69c1bf0..5823e65 100644 --- a/src/main/kotlin/id/walt/idp/nfts/NFTManager.kt +++ b/src/main/kotlin/id/walt/idp/nfts/NFTManager.kt @@ -86,6 +86,9 @@ object NFTManager { ChainEcosystem.FLOW -> VerificationService.verifyNftOwnershipInCollectionFlow(tokenConstraint.chain!!, tokenConstraint.smartContractAddress!!,account ,tokenConstraint.collectionPath!!) + ChainEcosystem.ALGORAND -> VerificationService.NFTsAlgorandOwnershipVerification(AlgorandChain.valueOf( + tokenConstraint.chain!!.toString() + ),account,tokenConstraint.smartContractAddress!!) } } else { println("data nft verification") @@ -125,7 +128,12 @@ object NFTManager { flowNftMetadata = if(ecosystem == ChainEcosystem.FLOW) FlowNftService.getAllNFTs(account , FlowChain.valueOf(tokenConstraint.chain!!.toString()) ).get(0) - else null + else null, + + algorandNftMetadata = if (ecosystem == ChainEcosystem.ALGORAND) + AlgorandNftService.getAccountAssets(account,AlgorandChain.valueOf(tokenConstraint.chain!!.toString())).get(0).Metadata + else null + ) } diff --git a/src/main/kotlin/id/walt/idp/siwe/siwaManager.kt b/src/main/kotlin/id/walt/idp/siwe/siwaManager.kt new file mode 100644 index 0000000..ad68a18 --- /dev/null +++ b/src/main/kotlin/id/walt/idp/siwe/siwaManager.kt @@ -0,0 +1,70 @@ +package id.walt.idp.siwe + +import id.walt.idp.config.IDPConfig +import id.walt.idp.oidc.OIDCSession +import io.ktor.client.* +import io.ktor.client.call.* +import io.ktor.client.engine.cio.* +import io.ktor.client.plugins.contentnegotiation.* +import io.ktor.client.plugins.logging.* +import io.ktor.client.request.* +import io.ktor.serialization.kotlinx.json.* +import kotlinx.coroutines.runBlocking +import kotlinx.serialization.json.Json +import java.net.URLEncoder +import java.nio.charset.StandardCharsets + +object SiwaManager { + val client = HttpClient(CIO.create{requestTimeout = 0}) { + install(ContentNegotiation) { + json(Json { + ignoreUnknownKeys = true + }) + } + install(Logging) { + logger = Logger.SIMPLE + level = LogLevel.ALL + } + expectSuccess = false + } + + + fun verifySignature(session: OIDCSession, message: String, publicKey: String, signature: String): Boolean{ + + val nonce= getNonce(message) + if (session.siweSession?.nonce != nonce) { + return false; + } + if (SiweManager.nonceBlacklists.contains(nonce)) { + return false + } + SiweManager.nonceBlacklists.add(nonce) + + + return runBlocking { + val result = client.get("${IDPConfig.config.jsProjectExternalUrl}/algorand/signature/verification?publicKey=${publicKey}&signature=${URLEncoder.encode(signature, StandardCharsets.UTF_8)}&message=${URLEncoder.encode(message, StandardCharsets.UTF_8)}") { + }.body() + return@runBlocking result + } + } + + + + fun getAddress(message:String): String{ + val regex = Regex("Public Key: ([A-Z0-9]+)\\s*\\.\\s*Date:") + val matchResult = regex.find(message) + val publicKey = matchResult?.groupValues?.get(1) + return publicKey!! + } + + fun getNonce(message: String): String{ + val nonce= message.split(".").last().split(":").last().trim() + return nonce + } + fun getPublicKey(message: String): String { + val regex = Regex("Public Key: ([A-Z0-9]+)\\s*\\.\\s*Date:") + val matchResult = regex.find(message) + val publicKey = matchResult?.groupValues?.get(1) + return publicKey!! + } +} diff --git a/web/waltid-idpkit-ui/package.json b/web/waltid-idpkit-ui/package.json index 69dbfbc..e598048 100644 --- a/web/waltid-idpkit-ui/package.json +++ b/web/waltid-idpkit-ui/package.json @@ -38,6 +38,7 @@ "@polkadot/api": "^10.8.1", "@polkadot/extension-dapp": "^0.46.4", "@polkadot/util": "^12.2.2", + "@randlabs/myalgo-connect": "^1.4.2", "@taquito/beacon-wallet": "^15.0.0", "@taquito/taquito": "^15.0.0", "@vue/cli-plugin-babel": "^5.0.8", @@ -56,6 +57,6 @@ "webpack": "^4.46.0" }, "devDependencies": { - "@babel/plugin-proposal-private-property-in-object" : "^7.14.5" + "@babel/plugin-proposal-private-property-in-object": "^7.14.5" } } diff --git a/web/waltid-idpkit-ui/pages/connect-wallet.vue b/web/waltid-idpkit-ui/pages/connect-wallet.vue index 8f48dc3..188c901 100644 --- a/web/waltid-idpkit-ui/pages/connect-wallet.vue +++ b/web/waltid-idpkit-ui/pages/connect-wallet.vue @@ -31,6 +31,12 @@ Connect wallet(Flow) +
+
+ +
@@ -47,6 +53,7 @@ import { setupWalletSelector } from "@near-wallet-selector/core"; import { setupModal } from "@near-wallet-selector/modal-ui"; import { setupWelldoneWallet } from "@near-wallet-selector/welldone-wallet"; +import MyAlgoConnect from "@randlabs/myalgo-connect"; import { setupDefaultWallets } from "@near-wallet-selector/default-wallets"; import { @@ -348,6 +355,46 @@ Nonce: ${nonce}`; }, + async AlgorandWallet(){ + + + const redirect_uri = this.$route.query["redirect_uri"]; + const session_id = this.$route.query["session"]; + const nonce = this.$route.query["nonce"]; + const origin = window.location.origin; + const domain = window.location.host; + const ISO8601formatedTimestamp = new Date().toISOString(); + const description = "Sign in with Flow to the app."; + + + const settings = { + shouldSelectOneAccount: true, + }; + + + const myAlgoWallet = new MyAlgoConnect(); + console.log("logging in") +// try { + const accounts = await myAlgoWallet.connect(settings); + const account = accounts[0] + const message = `${domain} wants you to sign in with your Algorand account:${account.name} . Public Key: ${account.address} .Date: ${ISO8601formatedTimestamp}. ${description} URI: ${origin}. Version: 1. Nonce: ${nonce}`; + + + const textEncoder = new TextEncoder(); + const encodedMessage = textEncoder.encode(message); + + const data = new Uint8Array([...encodedMessage]); + + const signature = await myAlgoWallet.signBytes(data, account.address); + + const urlSignature = encodeURIComponent(JSON.stringify(signature)) + + const urlMessage = encodeURIComponent(message); + let url = `${redirect_uri}?session=${session_id}&ecosystem=Algorand&message=${urlMessage}&signature=${signature}`; + + window.location = url; + + } }, };