diff --git a/src/app/modules/main/communities/controller.nim b/src/app/modules/main/communities/controller.nim index cbb02ef132..056c4257f2 100644 --- a/src/app/modules/main/communities/controller.nim +++ b/src/app/modules/main/communities/controller.nim @@ -1,26 +1,22 @@ -import stint, std/strutils +import stint, std/strutils, uuids, os import ./io_interface -import ../../../core/signals/types -import ../../../core/eventemitter -import ../../../../app_service/service/chat/dto/chat -import ../../../../app_service/service/community/service as community_service -import ../../../../app_service/service/contacts/service as contacts_service -import ../../../../app_service/service/chat/service as chat_service -import ../../../../app_service/service/network/service as networks_service -import ../../../../app_service/service/community_tokens/service as community_tokens_service -import ../../../../app_service/service/token/service as token_service -import ../../../../app_service/service/wallet_account/service as wallet_account_service +import app/core/signals/types +import app/core/eventemitter +import app_service/service/chat/dto/chat +import app_service/service/community/service as community_service +import app_service/service/contacts/service as contacts_service +import app_service/service/chat/service as chat_service +import app_service/service/network/service as networks_service +import app_service/service/community_tokens/service as community_tokens_service +import app_service/service/token/service as token_service +import app_service/service/wallet_account/service as wallet_account_service +import app_service/service/keycard/service as keycard_service import backend/collectibles as backend_collectibles -import ../../shared_modules/keycard_popup/io_interface as keycard_shared_module +import app/modules/shared_modules/keycard_popup/io_interface as keycard_shared_module -const UNIQUE_COMMUNITIES_MODULE_AUTH_IDENTIFIER* = "CommunitiesModule-Action-Authentication" - -type - AuthenticationAction* {.pure.} = enum - None = 0, - CommunityJoin = 1, - CommunitySharedAddressesJoin = 2, +const UNIQUE_COMMUNITIES_MODULE_AUTH_IDENTIFIER* = "CommunitiesModule-Authentication" +const UNIQUE_COMMUNITIES_MODULE_SIGNING_IDENTIFIER* = "CommunitiesModule-Signing" type Controller* = ref object of RootObj @@ -32,13 +28,13 @@ type networksService: networks_service.Service tokenService: token_service.Service chatService: chat_service.Service - tmpCommunityId: string - tmpCommunityIdForChannelsPermisisons: string - tmpCommunityIdForRevealedAccounts: string - tmpAuthenticationAction: AuthenticationAction - tmpRequestToJoinEnsName: string - tmpAddressesToShare: seq[string] - tmpAirdropAddress: string + walletAccountService: wallet_account_service.Service + keycardService: keycard_service.Service + connectionKeycardResponse: UUID + ## the following are used for silent signing in case there are more then a single address for the same keypair + silentSigningPath: string + silentSigningKeyUid: string + silentSigningPin: string proc newController*( delegate: io_interface.AccessInterface, @@ -49,6 +45,8 @@ proc newController*( networksService: networks_service.Service, tokenService: token_service.Service, chatService: chat_service.Service, + walletAccountService: wallet_account_service.Service, + keycardService: keycard_service.Service ): Controller = result = Controller() result.delegate = delegate @@ -59,12 +57,8 @@ proc newController*( result.networksService = networksService result.tokenService = tokenService result.chatService = chatService - result.tmpCommunityId = "" - result.tmpCommunityIdForChannelsPermisisons = "" - result.tmpCommunityIdForRevealedAccounts = "" - result.tmpRequestToJoinEnsName = "" - result.tmpAirdropAddress = "" - result.tmpAddressesToShare = @[] + result.walletAccountService = walletAccountService + result.keycardService = keycardService proc delete*(self: Controller) = discard @@ -171,28 +165,22 @@ proc init*(self: Controller) = self.events.on(SIGNAL_CHECK_PERMISSIONS_TO_JOIN_RESPONSE) do(e: Args): let args = CheckPermissionsToJoinResponseArgs(e) - if (args.communityId == self.tmpCommunityId): - self.delegate.onCommunityCheckPermissionsToJoinResponse(args.communityId, args.checkPermissionsToJoinResponse) - self.tmpCommunityId = "" + self.delegate.onCommunityCheckPermissionsToJoinResponse(args.communityId, args.checkPermissionsToJoinResponse) self.events.on(SIGNAL_CHECK_ALL_CHANNELS_PERMISSIONS_RESPONSE) do(e: Args): let args = CheckAllChannelsPermissionsResponseArgs(e) - if args.communityId == self.tmpCommunityIdForChannelsPermisisons: - self.delegate.onCommunityCheckAllChannelsPermissionsResponse( - args.communityId, - args.checkAllChannelsPermissionsResponse, - ) - self.tmpCommunityIdForChannelsPermisisons = "" + self.delegate.onCommunityCheckAllChannelsPermissionsResponse( + args.communityId, + args.checkAllChannelsPermissionsResponse, + ) self.events.on(SIGNAL_COMMUNITY_MEMBER_REVEALED_ACCOUNTS_LOADED) do(e: Args): let args = CommunityMemberRevealedAccountsArgs(e) - if self.tmpCommunityIdForRevealedAccounts == args.communityId: - self.delegate.onCommunityMemberRevealedAccountsLoaded( - args.communityId, - args.memberPubkey, - args.memberRevealedAccounts, - ) - self.tmpCommunityIdForRevealedAccounts = "" + self.delegate.onCommunityMemberRevealedAccountsLoaded( + args.communityId, + args.memberPubkey, + args.memberRevealedAccounts, + ) self.events.on(SIGNAL_WALLET_ACCOUNT_TOKENS_REBUILT) do(e: Args): self.delegate.onWalletAccountTokensRebuilt() @@ -211,6 +199,12 @@ proc init*(self: Controller) = let args = CheckChannelsPermissionsErrorArgs(e) self.delegate.onCommunityCheckAllChannelPermissionsFailed(args.communityId, args.error) + self.events.on(SIGNAL_SHARED_KEYCARD_MODULE_DATA_SIGNED) do(e: Args): + let args = SharedKeycarModuleArgs(e) + if args.uniqueIdentifier != UNIQUE_COMMUNITIES_MODULE_SIGNING_IDENTIFIER: + return + self.delegate.onDataSigned(args.keyUid, args.path, args.r, args.s, args.v, args.pin) + proc getCommunityTags*(self: Controller): string = result = self.communityService.getCommunityTags() @@ -363,78 +357,92 @@ proc shareCommunityChannelUrlWithChatKey*(self: Controller, communityId: string, proc shareCommunityChannelUrlWithData*(self: Controller, communityId: string, chatId: string): string = return self.communityService.shareCommunityChannelUrlWithData(communityId, chatId) -proc userAuthenticationCanceled*(self: Controller) = - self.tmpAuthenticationAction = AuthenticationAction.None - self.tmpCommunityId = "" - self.tmpCommunityIdForChannelsPermisisons = "" - self.tmpRequestToJoinEnsName = "" - self.tmpAirdropAddress = "" - self.tmpAddressesToShare = @[] +proc asyncRequestToJoinCommunity*(self: Controller, communityId: string, ensName: string, addressesToShare: seq[string], + airdropAddress: string, signatures: seq[string]) = + self.communityService.asyncRequestToJoinCommunity(communityId, ensName, addressesToShare, airdropAddress, + signatures) -proc requestToJoinCommunityAuthenticated*(self: Controller, password: string) = - self.communityService.asyncRequestToJoinCommunity( - self.tmpCommunityId, - self.tmpRequestToJoinEnsName, - password, - self.tmpAddressesToShare, - self.tmpAirdropAddress - ) - self.tmpAuthenticationAction = AuthenticationAction.None - self.tmpCommunityId = "" - self.tmpRequestToJoinEnsName = "" - self.tmpAirdropAddress = "" - self.tmpAddressesToShare = @[] +proc asyncEditSharedAddresses*(self: Controller, communityId: string, addressesToShare: seq[string], + airdropAddress: string, signatures: seq[string]) = + self.communityService.asyncEditSharedAddresses(communityId, addressesToShare, airdropAddress, signatures) -proc editSharedAddressesAuthenticated*(self: Controller, password: string) = - self.communityService.asyncEditSharedAddresses( - self.tmpCommunityId, - password, - self.tmpAddressesToShare, - self.tmpAirdropAddress, - ) - self.tmpAuthenticationAction = AuthenticationAction.None - self.tmpCommunityId = "" - self.tmpAirdropAddress = "" - self.tmpAddressesToShare = @[] - -proc userAuthenticated*(self: Controller, password: string) = - if self.tmpAuthenticationAction == AuthenticationAction.CommunityJoin: - self.requestToJoinCommunityAuthenticated(password) - elif self.tmpAuthenticationAction == AuthenticationAction.CommunitySharedAddressesJoin: - self.editSharedAddressesAuthenticated(password) - -proc authenticate*(self: Controller, keyUid = "") = - let data = SharedKeycarModuleAuthenticationArgs(uniqueIdentifier: UNIQUE_COMMUNITIES_MODULE_AUTH_IDENTIFIER, - keyUid: keyUid) +proc authenticate*(self: Controller) = + let data = SharedKeycarModuleAuthenticationArgs(uniqueIdentifier: UNIQUE_COMMUNITIES_MODULE_AUTH_IDENTIFIER) self.events.emit(SIGNAL_SHARED_KEYCARD_MODULE_AUTHENTICATE_USER, data) -proc authenticateToRequestToJoinCommunity*(self: Controller, communityId: string, ensName: string, addressesToShare: seq[string], airdropAddress: string) = - self.tmpCommunityId = communityId - self.tmpAuthenticationAction = AuthenticationAction.CommunityJoin - self.tmpRequestToJoinEnsName = ensName - self.tmpAirdropAddress = airdropAddress - self.tmpAddressesToShare = addressesToShare - self.authenticate() - -proc authenticateToEditSharedAddresses*(self: Controller, communityId: string, addressesToShare: seq[string], airdropAddress: string) = - self.tmpCommunityId = communityId - self.tmpAuthenticationAction = AuthenticationAction.CommunitySharedAddressesJoin - self.tmpAirdropAddress = airdropAddress - self.tmpAddressesToShare = addressesToShare - self.authenticate() - - proc getCommunityPublicKeyFromPrivateKey*(self: Controller, communityPrivateKey: string): string = result = self.communityService.getCommunityPublicKeyFromPrivateKey(communityPrivateKey) proc asyncCheckPermissionsToJoin*(self: Controller, communityId: string, addressesToShare: seq[string]) = - self.tmpCommunityId = communityId self.communityService.asyncCheckPermissionsToJoin(communityId, addressesToShare) proc asyncCheckAllChannelsPermissions*(self: Controller, communityId: string, sharedAddresses: seq[string]) = - self.tmpCommunityIdForChannelsPermisisons = communityId self.chatService.asyncCheckAllChannelsPermissions(communityId, sharedAddresses) proc asyncGetRevealedAccountsForMember*(self: Controller, communityId, memberPubkey: string) = - self.tmpCommunityIdForRevealedAccounts = communityId self.communityService.asyncGetRevealedAccountsForMember(communityId, memberPubkey) + +proc generateJoiningCommunityRequestsForSigning*(self: Controller, memberPubKey: string, communityId: string, + addressesToReveal: seq[string]): seq[SignParamsDto] = + return self.communityService.generateJoiningCommunityRequestsForSigning(memberPubKey, communityId, addressesToReveal) + +proc generateEditCommunityRequestsForSigning*(self: Controller, memberPubKey: string, communityId: string, + addressesToReveal: seq[string]): seq[SignParamsDto] = + return self.communityService.generateEditCommunityRequestsForSigning(memberPubKey, communityId, addressesToReveal) + +proc signCommunityRequests*(self: Controller, communityId: string, signParams: seq[SignParamsDto]): seq[string] = + return self.communityService.signCommunityRequests(communityId, signParams) + +proc getKeypairByAccountAddress*(self: Controller, address: string): KeypairDto = + return self.walletAccountService.getKeypairByAccountAddress(address) + +proc getKeypairByKeyUid*(self: Controller, keyUid: string): KeypairDto = + return self.walletAccountService.getKeypairByKeyUid(keyUid) + +proc getKeypairs*(self: Controller): seq[KeypairDto] = + return self.walletAccountService.getKeypairs() + +proc disconnectKeycardReponseSignal(self: Controller) = + self.events.disconnect(self.connectionKeycardResponse) + +proc connectKeycardReponseSignal(self: Controller) = + self.connectionKeycardResponse = self.events.onWithUUID(SIGNAL_KEYCARD_RESPONSE) do(e: Args): + let args = KeycardLibArgs(e) + self.disconnectKeycardReponseSignal() + let currentFlow = self.keycardService.getCurrentFlow() + if currentFlow != KCSFlowType.Sign: + return + let keyUid = self.silentSigningKeyUid + let path = self.silentSigningPath + let pin = self.silentSigningPin + self.silentSigningKeyUid = "" + self.silentSigningPath = "" + self.silentSigningPin = "" + self.delegate.onDataSigned(keyUid, path, args.flowEvent.txSignature.r, args.flowEvent.txSignature.s, args.flowEvent.txSignature.v, pin) + +proc cancelCurrentFlow*(self: Controller) = + self.keycardService.cancelCurrentFlow() + # in most cases we're running another flow after canceling the current one, + # this way we're giving to the keycard some time to cancel the current flow + sleep(200) + +proc runSignFlow(self: Controller, pin, path, dataToSign: string) = + self.cancelCurrentFlow() + self.connectKeycardReponseSignal() + self.keycardService.startSignFlow(path, dataToSign, pin) + +proc runSigningOnKeycard*(self: Controller, keyUid: string, path: string, dataToSign: string, pin: string) = + var finalDataToSign = dataToSign + if finalDataToSign.startsWith("0x"): + finalDataToSign = finalDataToSign[2..^1] + if pin.len == 0: + let data = SharedKeycarModuleSigningArgs(uniqueIdentifier: UNIQUE_COMMUNITIES_MODULE_SIGNING_IDENTIFIER, + keyUid: keyUid, + path: path, + dataToSign: finalDataToSign) + self.events.emit(SIGNAL_SHARED_KEYCARD_MODULE_SIGN_DATA, data) + return + self.silentSigningKeyUid = keyUid + self.silentSigningPath = path + self.silentSigningPin = pin + self.runSignFlow(pin, path, finalDataToSign) \ No newline at end of file diff --git a/src/app/modules/main/communities/io_interface.nim b/src/app/modules/main/communities/io_interface.nim index 6d2182d5e0..68bd372cb1 100644 --- a/src/app/modules/main/communities/io_interface.nim +++ b/src/app/modules/main/communities/io_interface.nim @@ -159,10 +159,10 @@ method curatedCommunitiesLoadingFailed*(self: AccessInterface) {.base.} = method curatedCommunitiesLoaded*(self: AccessInterface, curatedCommunities: seq[CommunityDto]) {.base.} = raise newException(ValueError, "No implementation available") -method communityInfoAlreadyRequested*(self: AccessInterface) {.base.} = +method communityInfoAlreadyRequested*(self: AccessInterface) {.base.} = raise newException(ValueError, "No implementation available") -method onCommunityTokenMetadataAdded*(self: AccessInterface, communityId: string, tokenMetadata: CommunityTokensMetadataDto) {.base.} = +method onCommunityTokenMetadataAdded*(self: AccessInterface, communityId: string, tokenMetadata: CommunityTokensMetadataDto) {.base.} = raise newException(ValueError, "No implementation available") method onWalletAccountTokensRebuilt*(self: AccessInterface) {.base.} = @@ -183,12 +183,20 @@ method shareCommunityChannelUrlWithData*(self: AccessInterface, communityId: str method onUserAuthenticated*(self: AccessInterface, pin: string, password: string, keyUid: string) {.base.} = raise newException(ValueError, "No implementation available") -method requestToJoinCommunityWithAuthentication*(self: AccessInterface, communityId, ensName: string, addressesToShare: seq[string], - airdropAddress: string) {.base.} = +method onDataSigned*(self: AccessInterface, keyUid: string, path: string, r: string, s: string, v: string, pin: string) {.base.} = raise newException(ValueError, "No implementation available") -method editSharedAddressesWithAuthentication*(self: AccessInterface, communityId: string, addressesToShare: seq[string], airdropAddress: string) - {.base.} = +method prepareKeypairsForSigning*(self: AccessInterface, communityId: string, ensName: string, addresses: string, + airdropAddress: string, editMode: bool) {.base.} = + raise newException(ValueError, "No implementation available") + +method signSharedAddressesForAllNonKeycardKeypairs*(self: AccessInterface) {.base.} = + raise newException(ValueError, "No implementation available") + +method signSharedAddressesForKeypair*(self: AccessInterface, keyUid: string, pin: string) {.base.} = + raise newException(ValueError, "No implementation available") + +method joinCommunityOrEditSharedAddresses*(self: AccessInterface) {.base.} = raise newException(ValueError, "No implementation available") method prepareTokenModelForCommunity*(self: AccessInterface, communityId: string) {.base.} = diff --git a/src/app/modules/main/communities/module.nim b/src/app/modules/main/communities/module.nim index 84a7e9cd2a..ca43387a54 100644 --- a/src/app/modules/main/communities/module.nim +++ b/src/app/modules/main/communities/module.nim @@ -1,4 +1,4 @@ -import NimQml, sequtils, tables, stint, chronicles, json +import NimQml, strutils, sequtils, sugar, tables, stint, chronicles, json import ./io_interface import ../io_interface as delegate_interface @@ -12,21 +12,26 @@ import ./models/discord_channels_model import ./models/discord_file_list_model import ./models/discord_import_task_item import ./models/discord_import_tasks_model -import ../../shared_models/[member_item, section_model, section_item, token_permissions_model, token_permission_item, - token_list_item, token_list_model, token_criteria_item, token_criteria_model, token_permission_chat_list_model] -import ../../../global/global_singleton -import ../../../core/eventemitter -import ../../../../app_service/common/types -import ../../../../app_service/service/community/service as community_service -import ../../../../app_service/service/contacts/service as contacts_service -import ../../../../app_service/service/chat/service as chat_service -import ../../../../app_service/service/network/service as networks_service -import ../../../../app_service/service/transaction/service as transaction_service -import ../../../../app_service/service/community_tokens/service as community_tokens_service -import ../../../../app_service/service/token/service as token_service -import ../../../../app_service/service/chat/dto/chat +import app/modules/shared_models/[member_item, section_model, section_item, token_permissions_model, token_permission_item, + token_list_item, token_list_model, token_criteria_item, token_criteria_model, token_permission_chat_list_model, keypair_model] +import app/global/global_singleton +import app/core/eventemitter +import app_service/common/types +import app_service/common/utils as common_utils +import app_service/service/community/service as community_service +import app_service/service/contacts/service as contacts_service +import app_service/service/chat/service as chat_service +import app_service/service/network/service as networks_service +import app_service/service/transaction/service as transaction_service +import app_service/service/community_tokens/service as community_tokens_service +import app_service/service/token/service as token_service +import app_service/service/wallet_account/service as wallet_account_service +import app_service/service/chat/dto/chat +import app_service/service/keycard/service as keycard_service import ./tokens/module as community_tokens_module +import app/modules/shared/keypairs + export io_interface type @@ -35,6 +40,41 @@ type ImportingInProgress ImportingError +type + Action {.pure.} = enum + None = 0, + JoinCommunity + EditSharedAddresses + +type + AddressToShareDetails = object + keyUid: string + address: string + path: string + isAirdropAddress: bool + messageToBeSigned: string + signature: string + +type + JoiningCommunityDetails = object + communityId: string + communityIdForChannelsPermisisons: string + communityIdForRevealedAccounts: string + ensName: string + addressesToShare: OrderedTable[string, AddressToShareDetails] ## [address, AddressToShareDetails] + profilePassword: string + profilePin: string + action: Action + +proc clear(self: var JoiningCommunityDetails) = + self = JoiningCommunityDetails() + +proc allSigned(self: JoiningCommunityDetails): bool = + for _, details in self.addressesToShare.pairs: + if details.signature.len == 0: + return false + return true + type Module* = ref object of io_interface.AccessInterface delegate: delegate_interface.AccessInterface @@ -46,6 +86,7 @@ type communityTokensModule: community_tokens_module.AccessInterface checkingPermissionToJoinInProgress: bool checkingAllChannelPermissionsInProgress: bool + joiningCommunityDetails: JoiningCommunityDetails # Forward declaration method setCommunityTags*(self: Module, communityTags: string) @@ -62,6 +103,8 @@ proc newModule*( transactionService: transaction_service.Service, tokensService: token_service.Service, chatService: chat_service.Service, + walletAccountService: wallet_account_service.Service, + keycardService: keycard_service.Service, ): Module = result = Module() result.delegate = delegate @@ -76,6 +119,8 @@ proc newModule*( networksService, tokensService, chatService, + walletAccountService, + keycardService, ) result.communityTokensModule = community_tokens_module.newCommunityTokensModule(result, events, communityTokensService, transactionService, networksService, communityService) result.moduleLoaded = false @@ -89,6 +134,9 @@ method delete*(self: Module) = self.controller.delete self.communityTokensModule.delete +proc clean(self: Module) = + self.joiningCommunityDetails.clear() + method load*(self: Module) = singletonInstance.engine.setRootContextProperty("communitiesModule", self.viewVariant) self.controller.init() @@ -281,22 +329,27 @@ method createCommunity*(self: Module, name: string, pinMessageAllMembersEnabled: bool, bannerJsonStr: string) = self.controller.createCommunity(name, description, introMessage, outroMessage, access, color, tags, - imagePath, aX, aY, bX, bY, historyArchiveSupportEnabled, pinMessageAllMembersEnabled, + imagePath, aX, aY, bX, bY, historyArchiveSupportEnabled, pinMessageAllMembersEnabled, bannerJsonStr) method communityMuted*(self: Module, communityId: string, muted: bool) = self.view.model().setMuted(communityId, muted) method communityAccessRequested*(self: Module, communityId: string) = + self.clean() self.view.communityAccessRequested(communityId) -method communityAccessFailed*(self: Module, communityId, error: string) = - self.view.communityAccessFailed(communityId, error) +method communityAccessFailed*(self: Module, communityId, err: string) = + error "communities: ", err + self.clean() + self.view.communityAccessFailed(communityId, err) method communityEditSharedAddressesSucceeded*(self: Module, communityId: string) = + self.clean() self.view.communityEditSharedAddressesSucceeded(communityId) method communityEditSharedAddressesFailed*(self: Module, communityId, error: string) = + self.clean() self.view.communityEditSharedAddressesFailed(communityId, error) method communityHistoryArchivesDownloadStarted*(self: Module, communityId: string) = @@ -344,7 +397,7 @@ method communityImported*(self: Module, community: CommunityDto) = self.view.addOrUpdateItem(self.getCommunityItem(community)) self.view.emitImportingCommunityStateChangedSignal(community.id, ImportCommunityState.Imported.int, errorMsg = "") -method communityDataImported*(self: Module, community: CommunityDto) = +method communityDataImported*(self: Module, community: CommunityDto) = self.view.addItem(self.getCommunityItem(community)) self.view.emitCommunityInfoRequestCompleted(community.id, "") @@ -364,7 +417,7 @@ method requestExtractDiscordChannelsAndCategories*(self: Module, filesToImport: method requestImportDiscordCommunity*(self: Module, name: string, description, introMessage, outroMessage: string, access: int, color: string, tags: string, imagePath: string, aX: int, aY: int, bX: int, bY: int, - historyArchiveSupportEnabled: bool, pinMessageAllMembersEnabled: bool, filesToImport: seq[string], + historyArchiveSupportEnabled: bool, pinMessageAllMembersEnabled: bool, filesToImport: seq[string], fromTimestamp: int) = self.view.setDiscordImportHasCommunityImage(imagePath != "") self.controller.requestImportDiscordCommunity(name, description, introMessage, outroMessage, access, color, tags, imagePath, aX, aY, bX, bY, historyArchiveSupportEnabled, pinMessageAllMembersEnabled, filesToImport, fromTimestamp) @@ -534,32 +587,166 @@ method shareCommunityChannelUrlWithChatKey*(self: Module, communityId: string, c method shareCommunityChannelUrlWithData*(self: Module, communityId: string, chatId: string): string = return self.controller.shareCommunityChannelUrlWithData(communityId, chatId) +method signRevealedAddressesThatBelongToRegularKeypairs(self: Module): bool = + var signingParams: seq[SignParamsDto] + for address, details in self.joiningCommunityDetails.addressesToShare.pairs: + if details.signature.len > 0: + continue + let keypair = self.controller.getKeypairByAccountAddress(address) + if keypair.isNil: + self.communityAccessFailed(self.joiningCommunityDetails.communityId, "cannot resolve keypair for address" & address) + return false + if keypair.migratedToKeycard(): + continue + signingParams.add( + SignParamsDto( + address: address, + data: details.messageToBeSigned, + password: common_utils.hashPassword(self.joiningCommunityDetails.profilePassword), + ) + ) + if signingParams.len == 0: + return true + # signatures are returned in the same order as signingParams + let signatures = self.controller.signCommunityRequests(self.joiningCommunityDetails.communityId, signingParams) + for i in 0 ..< len(signingParams): + self.joiningCommunityDetails.addressesToShare[signingParams[i].address].signature = signatures[i] + return true + method onUserAuthenticated*(self: Module, pin: string, password: string, keyUid: string) = if password == "" and pin == "": - self.view.userAuthenticationCanceled() - self.controller.userAuthenticationCanceled() + info "unsuccesful authentication" + self.clean() return - self.controller.userAuthenticated(password) + self.joiningCommunityDetails.profilePassword = password + self.joiningCommunityDetails.profilePin = pin + if self.signRevealedAddressesThatBelongToRegularKeypairs(): + self.view.sendSharedAddressesForAllNonKeycardKeypairsSignedSignal() -method requestToJoinCommunityWithAuthentication*(self: Module, communityId, ensName: string, addressesToShare: seq[string], - airdropAddress: string) = - self.controller.authenticateToRequestToJoinCommunity(communityId, ensName, addressesToShare, airdropAddress) +method onDataSigned*(self: Module, keyUid: string, path: string, r: string, s: string, v: string, pin: string) = + if keyUid.len == 0 or path.len == 0 or r.len == 0 or s.len == 0 or v.len == 0 or pin.len == 0: + # being here is not an error + return -method editSharedAddressesWithAuthentication*(self: Module, communityId: string, addressesToShare: seq[string], airdropAddress: string) = - self.controller.authenticateToEditSharedAddresses(communityId, addressesToShare, airdropAddress) + for address, details in self.joiningCommunityDetails.addressesToShare.pairs: + if details.keyUid != keyUid or details.path != path: + continue + self.joiningCommunityDetails.addressesToShare[address].signature = "0x" & r & s & v + break + self.signSharedAddressesForKeypair(keyUid, pin) +method prepareKeypairsForSigning*(self: Module, communityId, ensName: string, addresses: string, + airdropAddress: string, editMode: bool) = + var addressesToShare: seq[string] + try: + addressesToShare = map(parseJson(addresses).getElems(), proc(x:JsonNode):string = x.getStr()) + except Exception as e: + self.communityAccessFailed(communityId, "error parsing addresses: " & e.msg) + return + let allKeypairs = keypairs.buildKeyPairsList(self.controller.getKeypairs(), excludeAlreadyMigratedPairs = false, + excludePrivateKeyKeypairs = false) + for it in allKeypairs: + let addressesToRemove = it.getAccountsModel().getItems() + .map(x => x.getAddress()) + .filter(x => addressesToShare.filter(y => cmpIgnoreCase(y, x) == 0).len == 0) + for address in addressesToRemove: + it.removeAccountByAddress(address) + let keypairsForSigning = allKeypairs.filter(x => x.getAccountsModel().getCount() > 0) + self.view.setKeypairsSigningModelItems(keypairsForSigning) + + self.joiningCommunityDetails.communityId = communityId + + var signingParams: seq[SignParamsDto] + if editMode: + self.joiningCommunityDetails.action = Action.EditSharedAddresses + signingParams = self.controller.generateEditCommunityRequestsForSigning( + singletonInstance.userProfile.getPubKey(), communityId, addressesToShare) + else: + self.joiningCommunityDetails.action = Action.JoinCommunity + self.joiningCommunityDetails.ensName = ensName + signingParams = self.controller.generateJoiningCommunityRequestsForSigning( + singletonInstance.userProfile.getPubKey(), communityId, addressesToShare) + + let findKeyUidAndPathForAddress = proc (items: seq[KeyPairItem], address: string): tuple[keyUid: string, path: string] = + for it in items: + for acc in it.getAccountsModel().getItems(): + if cmpIgnoreCase(acc.getAddress(), address) == 0: + return (it.getKeyUid(), acc.getPath()) + return ("", "") + + for param in signingParams: + let (keyUid, path) = findKeyUidAndPathForAddress(keypairsForSigning, param.address) + let details = AddressToShareDetails( + keyUid: keyUid, + address: param.address, + path: path, + isAirdropAddress: if cmpIgnoreCase(param.address, airdropAddress) == 0: true else: false, + messageToBeSigned: param.data + ) + self.joiningCommunityDetails.addressesToShare[param.address] = details + +method signSharedAddressesForAllNonKeycardKeypairs*(self: Module) = + self.controller.authenticate() + +# if pin is provided we're signing on a keycard silently +method signSharedAddressesForKeypair*(self: Module, keyUid: string, pin: string) = + let keypair = self.controller.getKeypairByKeyUid(keyUid) + if keypair.isNil: + self.communityAccessFailed(self.joiningCommunityDetails.communityId, "cannot resolve keypair for keyUid " & keyUid) + return + for acc in keypair.accounts: + for address, details in self.joiningCommunityDetails.addressesToShare.pairs: + if cmpIgnoreCase(address, acc.address) != 0: + continue + if details.signature.len > 0: + continue + self.controller.runSigningOnKeycard(keyUid, details.path, details.messageToBeSigned, pin) + return + self.view.keypairsSigningModel().setOwnershipVerified(keyUid, true) + +method joinCommunityOrEditSharedAddresses*(self: Module) = + if not self.joiningCommunityDetails.allSigned(): + self.communityAccessFailed(self.joiningCommunityDetails.communityId, "unexpected call to join community function before all addresses are signed") + return + var + addressesToShare: seq[string] + airdropAddress: string + signatures: seq[string] + + for _, details in self.joiningCommunityDetails.addressesToShare.pairs: + addressesToShare.add(details.address) + if details.isAirdropAddress: + airdropAddress = details.address + signatures.add(details.signature) + + if self.joiningCommunityDetails.action == Action.JoinCommunity: + self.controller.asyncRequestToJoinCommunity(self.joiningCommunityDetails.communityId, + self.joiningCommunityDetails.ensName, + addressesToShare, + airdropAddress, + signatures) + return + if self.joiningCommunityDetails.action == Action.EditSharedAddresses: + self.controller.asyncEditSharedAddresses(self.joiningCommunityDetails.communityId, + addressesToShare, + airdropAddress, + signatures) + return + self.communityAccessFailed(self.joiningCommunityDetails.communityId, "unexpected action") method getCommunityPublicKeyFromPrivateKey*(self: Module, communityPrivateKey: string): string = result = self.controller.getCommunityPublicKeyFromPrivateKey(communityPrivateKey) method checkPermissions*(self: Module, communityId: string, sharedAddresses: seq[string]) = + self.joiningCommunityDetails.communityIdForChannelsPermisisons = communityId self.controller.asyncCheckPermissionsToJoin(communityId, sharedAddresses) self.controller.asyncCheckAllChannelsPermissions(communityId, sharedAddresses) self.view.setCheckingPermissionsInProgress(inProgress = true) method prepareTokenModelForCommunity*(self: Module, communityId: string) = + self.joiningCommunityDetails.communityIdForRevealedAccounts = communityId self.controller.asyncGetRevealedAccountsForMember(communityId, singletonInstance.userProfile.getPubKey()) let community = self.controller.getCommunityById(communityId) @@ -633,12 +820,16 @@ method onCommunityCheckAllChannelPermissionsFailed*(self: Module, communityId: s method onCommunityCheckPermissionsToJoinResponse*(self: Module, communityId: string, checkPermissionsToJoinResponse: CheckPermissionsToJoinResponseDto) = + if self.joiningCommunityDetails.communityId != communityId: + return self.applyPermissionResponse(communityId, checkPermissionsToJoinResponse.permissions) self.checkingPermissionToJoinInProgress = false self.updateCheckingPermissionsInProgressIfNeeded(inProgress = false) method onCommunityCheckAllChannelsPermissionsResponse*(self: Module, communityId: string, checkChannelPermissionsResponse: CheckAllChannelsPermissionsResponseDto) = + if self.joiningCommunityDetails.communityIdForChannelsPermisisons != communityId: + return self.checkingAllChannelPermissionsInProgress = false self.updateCheckingPermissionsInProgressIfNeeded(inProgress = false) for _, channelPermissionResponse in checkChannelPermissionsResponse.channels: @@ -653,6 +844,8 @@ method onCommunityCheckAllChannelsPermissionsResponse*(self: Module, communityId method onCommunityMemberRevealedAccountsLoaded*(self: Module, communityId, memberPubkey: string, revealedAccounts: seq[RevealedAccount]) = + if self.joiningCommunityDetails.communityIdForRevealedAccounts != communityId: + return if memberPubkey == singletonInstance.userProfile.getPubKey(): var addresses: seq[string] = @[] var airdropAddress = "" diff --git a/src/app/modules/main/communities/view.nim b/src/app/modules/main/communities/view.nim index eb9562493b..5e66777d63 100644 --- a/src/app/modules/main/communities/view.nim +++ b/src/app/modules/main/communities/view.nim @@ -2,7 +2,7 @@ import NimQml, json, strutils, sequtils import ./io_interface import ../../shared_models/[section_model, section_item, token_list_model, token_list_item, - token_permissions_model] + token_permissions_model, keypair_model] import ./models/curated_community_model import ./models/discord_file_list_model import ./models/discord_file_item @@ -55,6 +55,8 @@ QtObject: checkingPermissionsInProgress: bool myRevealedAddressesStringForCurrentCommunity: string myRevealedAirdropAddressForCurrentCommunity: string + keypairsSigningModel: KeyPairModel + keypairsSigningModelVariant: QVariant proc delete*(self: View) = self.model.delete @@ -75,6 +77,10 @@ QtObject: self.tokenListModelVariant.delete self.collectiblesListModel.delete self.collectiblesListModelVariant.delete + if not self.keypairsSigningModel.isNil: + self.keypairsSigningModel.delete + if not self.keypairsSigningModelVariant.isNil: + self.keypairsSigningModelVariant.delete self.QObject.delete @@ -318,6 +324,15 @@ QtObject: proc prepareTokenModelForCommunity(self: View, communityId: string) {.slot.} = self.delegate.prepareTokenModelForCommunity(communityId) + proc signSharedAddressesForAllNonKeycardKeypairs*(self: View) {.slot.} = + self.delegate.signSharedAddressesForAllNonKeycardKeypairs() + + proc signSharedAddressesForKeypair*(self: View, keyUid: string) {.slot.} = + self.delegate.signSharedAddressesForKeypair(keyUid, pin = "") + + proc joinCommunityOrEditSharedAddresses*(self: View) {.slot.} = + self.delegate.joinCommunityOrEditSharedAddresses() + proc checkPermissions*(self: View, communityId: string, addressesToShare: string) {.slot.} = try: let sharedAddresses = map(parseJson(addressesToShare).getElems(), proc(x:JsonNode):string = x.getStr()) @@ -460,7 +475,7 @@ QtObject: historyArchiveSupportEnabled: bool, pinMessageAllMembersEnabled: bool, bannerJsonStr: string) {.slot.} = self.delegate.createCommunity(name, description, introMessage, outroMessage, access, color, tags, - imagePath, aX, aY, bX, bY, historyArchiveSupportEnabled, pinMessageAllMembersEnabled, + imagePath, aX, aY, bX, bY, historyArchiveSupportEnabled, pinMessageAllMembersEnabled, bannerJsonStr) proc clearFileList*(self: View) {.slot.} = @@ -673,25 +688,9 @@ QtObject: proc shareCommunityChannelUrlWithData*(self: View, communityId: string, chatId: string): string {.slot.} = return self.delegate.shareCommunityChannelUrlWithData(communityId, chatId) - proc userAuthenticationCanceled*(self: View) {.signal.} - - proc requestToJoinCommunityWithAuthentication*(self: View, communityId: string, ensName: string) {.slot.} = - self.delegate.requestToJoinCommunityWithAuthentication(communityId, ensName, @[], "") - - proc requestToJoinCommunityWithAuthenticationWithSharedAddresses*(self: View, communityId: string, ensName: string, - addressesToShare: string, airdropAddress: string) {.slot.} = - try: - let addressesArray = map(parseJson(addressesToShare).getElems(), proc(x:JsonNode):string = x.getStr()) - self.delegate.requestToJoinCommunityWithAuthentication(communityId, ensName, addressesArray, airdropAddress) - except Exception as e: - echo "Error requesting to join community with authentication and shared addresses: ", e.msg - - proc editSharedAddressesWithAuthentication*(self: View, communityId: string, addressesToShare: string, airdropAddress: string) {.slot.} = - try: - let addressesArray = map(parseJson(addressesToShare).getElems(), proc(x:JsonNode):string = x.getStr()) - self.delegate.editSharedAddressesWithAuthentication(communityId, addressesArray, airdropAddress) - except Exception as e: - echo "Error editing shared addresses with authentication: ", e.msg + proc prepareKeypairsForSigning*(self: View, communityId: string, ensName: string, addresses: string, + airdropAddress: string, editMode: bool) {.slot.} = + self.delegate.prepareKeypairsForSigning(communityId, ensName, addresses, airdropAddress, editMode) proc getCommunityPublicKeyFromPrivateKey*(self: View, communityPrivateKey: string): string {.slot.} = result = self.delegate.getCommunityPublicKeyFromPrivateKey(communityPrivateKey) @@ -731,3 +730,25 @@ QtObject: QtProperty[bool] requirementsCheckPending: read = getCheckingPermissionsInProgress notify = checkingPermissionsInProgressChanged + + proc keypairsSigningModel*(self: View): KeyPairModel = + return self.keypairsSigningModel + + proc keypairsSigningModelChanged*(self: View) {.signal.} + proc getKeypairsSigningModel(self: View): QVariant {.slot.} = + return newQVariant(self.keypairsSigningModel) + QtProperty[QVariant] keypairsSigningModel: + read = getKeypairsSigningModel + notify = keypairsSigningModelChanged + + proc setKeypairsSigningModelItems*(self: View, items: seq[KeyPairItem]) = + if self.keypairsSigningModel.isNil: + self.keypairsSigningModel = newKeyPairModel() + if self.keypairsSigningModelVariant.isNil: + self.keypairsSigningModelVariant = newQVariant(self.keypairsSigningModel) + self.keypairsSigningModel.setItems(items) + self.keypairsSigningModelChanged() + + proc sharedAddressesForAllNonKeycardKeypairsSigned(self: View) {.signal.} + proc sendSharedAddressesForAllNonKeycardKeypairsSignedSignal*(self: View) = + self.sharedAddressesForAllNonKeycardKeypairsSigned() \ No newline at end of file diff --git a/src/app/modules/shared_models/keypair_item.nim b/src/app/modules/shared_models/keypair_item.nim index 4f8ef8ad04..87ef9ed49e 100644 --- a/src/app/modules/shared_models/keypair_item.nim +++ b/src/app/modules/shared_models/keypair_item.nim @@ -28,6 +28,7 @@ QtObject: syncedFrom: string accounts: KeyPairAccountModel observedAccount: KeyPairAccountItem + ownershipVerified: bool proc setup*(self: KeyPairItem, keyUid: string, @@ -40,7 +41,8 @@ QtObject: derivedFrom: string, lastUsedDerivationIndex: int, migratedToKeycard: bool, - syncedFrom: string + syncedFrom: string, + ownershipVerified: bool ) = self.QObject.setup self.keyUid = keyUid @@ -54,6 +56,7 @@ QtObject: self.lastUsedDerivationIndex = lastUsedDerivationIndex self.migratedToKeycard = migratedToKeycard self.syncedFrom = syncedFrom + self.ownershipVerified = ownershipVerified self.accounts = newKeyPairAccountModel() proc delete*(self: KeyPairItem) = @@ -69,10 +72,11 @@ QtObject: derivedFrom = "", lastUsedDerivationIndex = 0, migratedToKeycard = false, - syncedFrom = ""): KeyPairItem = + syncedFrom = "", + ownershipVerified = false): KeyPairItem = new(result, delete) result.setup(keyUid, pubKey, locked, name, image, icon, pairType, derivedFrom, lastUsedDerivationIndex, - migratedToKeycard, syncedFrom) + migratedToKeycard, syncedFrom, ownershipVerified) proc `$`*(self: KeyPairItem): string = result = fmt"""KeyPairItem[ @@ -88,7 +92,8 @@ QtObject: migratedToKeycard: {self.migratedToKeycard}, operability: {self.operability}, syncedFrom: {self.syncedFrom}, - accounts: {$self.accounts} + ownershipVerified: {$self.ownershipVerified} + accounts: {$self.accounts}, ]""" proc keyUidChanged*(self: KeyPairItem) {.signal.} @@ -224,6 +229,17 @@ QtObject: write = setSyncedFrom notify = syncedFromChanged + proc ownershipVerifiedChanged*(self: KeyPairItem) {.signal.} + proc getOwnershipVerified*(self: KeyPairItem): bool {.slot.} = + return self.ownershipVerified + proc setOwnershipVerified*(self: KeyPairItem, value: bool) {.slot.} = + self.ownershipVerified = value + self.ownershipVerifiedChanged() + QtProperty[bool] ownershipVerified: + read = getOwnershipVerified + write = setOwnershipVerified + notify = ownershipVerifiedChanged + proc observedAccountChanged*(self: KeyPairItem) {.signal.} proc getObservedAccountAsVariant*(self: KeyPairItem): QVariant {.slot.} = return newQVariant(self.observedAccount) @@ -288,4 +304,5 @@ QtObject: self.setMigratedToKeycard(item.getMigratedToKeycard()) self.setLastUsedDerivationIndex(item.getLastUsedDerivationIndex()) self.setAccounts(item.getAccountsModel().getItems()) + self.setOwnershipVerified(item.getOwnershipVerified()) self.setLastAccountAsObservedAccount() diff --git a/src/app/modules/shared_models/keypair_model.nim b/src/app/modules/shared_models/keypair_model.nim index 9961fec35a..d0793d6a7d 100644 --- a/src/app/modules/shared_models/keypair_model.nim +++ b/src/app/modules/shared_models/keypair_model.nim @@ -109,6 +109,12 @@ QtObject: return item.setName(name) + proc setOwnershipVerified*(self: KeyPairModel, keyUid: string, ownershipVerified: bool) = + let item = self.findItemByKeyUid(keyUid) + if item.isNil: + return + item.setOwnershipVerified(ownershipVerified) + proc setBalanceForAddress*(self: KeyPairModel, address: string, balance: CurrencyAmount) = for item in self.items: item.setBalanceForAddress(address, balance) diff --git a/src/app/modules/shared_modules/keycard_popup/models/keycard_item.nim b/src/app/modules/shared_modules/keycard_popup/models/keycard_item.nim index c1db9e2cf8..e8ab1ad4cb 100644 --- a/src/app/modules/shared_modules/keycard_popup/models/keycard_item.nim +++ b/src/app/modules/shared_modules/keycard_popup/models/keycard_item.nim @@ -25,7 +25,7 @@ QtObject: ): KeycardItem = new(result, delete) result.KeyPairItem.setup(keyUid, pubKey, locked, name, image, icon, pairType, derivedFrom,lastUsedDerivationIndex, - migratedToKeycard, syncedFrom = "") + migratedToKeycard, syncedFrom = "", ownershipVerified = false) result.keycardUid = keycardUid proc `$`*(self: KeycardItem): string = diff --git a/src/app_service/service/community/async_tasks.nim b/src/app_service/service/community/async_tasks.nim index 1cfff58dd3..245906e268 100644 --- a/src/app_service/service/community/async_tasks.nim +++ b/src/app_service/service/community/async_tasks.nim @@ -157,15 +157,15 @@ type AsyncRequestToJoinCommunityTaskArg = ref object of QObjectTaskArg communityId: string ensName: string - password: string addressesToShare: seq[string] + signatures: seq[string] airdropAddress: string const asyncRequestToJoinCommunityTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} = let arg = decode[AsyncRequestToJoinCommunityTaskArg](argEncoded) try: - let response = status_go.requestToJoinCommunity(arg.communityId, arg.ensName, arg.password, arg.addressesToShare, - arg.airdropAddress) + let response = status_go.requestToJoinCommunity(arg.communityId, arg.ensName, arg.addressesToShare, + arg.signatures, arg.airdropAddress) arg.finish(%* { "response": response, "communityId": arg.communityId, @@ -180,14 +180,14 @@ const asyncRequestToJoinCommunityTask: Task = proc(argEncoded: string) {.gcsafe, type AsyncEditSharedAddressesTaskArg = ref object of QObjectTaskArg communityId: string - password: string addressesToShare: seq[string] + signatures: seq[string] airdropAddress: string const asyncEditSharedAddressesTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} = let arg = decode[AsyncEditSharedAddressesTaskArg](argEncoded) try: - let response = status_go.editSharedAddresses(arg.communityId, arg.password, arg.addressesToShare, + let response = status_go.editSharedAddresses(arg.communityId, arg.addressesToShare, arg.signatures, arg.airdropAddress) arg.finish(%* { "response": response, diff --git a/src/app_service/service/community/dto/sign_params.nim b/src/app_service/service/community/dto/sign_params.nim new file mode 100644 index 0000000000..77e3f310b8 --- /dev/null +++ b/src/app_service/service/community/dto/sign_params.nim @@ -0,0 +1,21 @@ +import json + +include app_service/common/json_utils + +type SignParamsDto* = object + data*: string + address*: string + password*: string + +proc toSignParamsDto*(jsonObj: JsonNode): SignParamsDto = + result = SignParamsDto() + discard jsonObj.getProp("data", result.data) + discard jsonObj.getProp("account", result.address) + discard jsonObj.getProp("password", result.password) + +proc toJson*(self: SignParamsDto): JsonNode = + return %* { + "data": $self.data, + "account": $self.address, + "password": $self.password + } \ No newline at end of file diff --git a/src/app_service/service/community/service.nim b/src/app_service/service/community/service.nim index d95a7abcaa..ae14f5dfe5 100644 --- a/src/app_service/service/community/service.nim +++ b/src/app_service/service/community/service.nim @@ -2,6 +2,7 @@ import NimQml, Tables, json, sequtils, std/sets, std/algorithm, strformat, strut import json_serialization/std/tables as ser_tables import ./dto/community as community_dto +import ./dto/sign_params as sign_params_dto import ../community_tokens/dto/community_token as community_token_dto import ../activity_center/service as activity_center_service @@ -20,7 +21,7 @@ import ../../../app_service/common/utils include ./async_tasks -export community_dto +export community_dto, sign_params_dto logScope: topics = "community-service" @@ -1503,8 +1504,52 @@ QtObject: error: errMsg, )) - proc asyncRequestToJoinCommunity*(self: Service, communityId: string, ensName: string, password: string, - addressesToShare: seq[string], airdropAddress: string) = + proc generateJoiningCommunityRequestsForSigning*(self: Service, memberPubKey: string, communityId: string, + addressesToReveal: seq[string]): seq[SignParamsDto] = + try: + let response = status_go.generateJoiningCommunityRequestsForSigning(memberPubKey, communityId, addressesToReveal) + if not response.error.isNil: + raise newException(RpcException, response.error.message) + result = map(response.result.getElems(), x => x.toSignParamsDto()) + except Exception as e: + error "Error while generating join community request", msg = e.msg + self.events.emit(SIGNAL_COMMUNITY_MY_REQUEST_FAILED, CommunityRequestFailedArgs( + communityId: communityId, + error: e.msg + )) + + proc generateEditCommunityRequestsForSigning*(self: Service, memberPubKey: string, communityId: string, + addressesToReveal: seq[string]): seq[SignParamsDto] = + try: + let response = status_go.generateEditCommunityRequestsForSigning(memberPubKey, communityId, addressesToReveal) + if not response.error.isNil: + raise newException(RpcException, response.error.message) + result = map(response.result.getElems(), x => x.toSignParamsDto()) + except Exception as e: + error "Error while generating edit community request", msg = e.msg + self.events.emit(SIGNAL_COMMUNITY_EDIT_SHARED_ADDRESSES_FAILED, CommunityRequestFailedArgs( + communityId: communityId, + error: e.msg + )) + + proc signCommunityRequests*(self: Service, communityId: string, signParams: seq[SignParamsDto]): seq[string] = + try: + var data = %* [] + for param in signParams: + data.add(param.toJson()) + let response = status_go.signData(data) + if not response.error.isNil: + raise newException(RpcException, response.error.message) + result = map(response.result.getElems(), x => x.getStr()) + except Exception as e: + error "Error while signing joining community request", msg = e.msg + self.events.emit(SIGNAL_COMMUNITY_MY_REQUEST_FAILED, CommunityRequestFailedArgs( + communityId: communityId, + error: e.msg + )) + + proc asyncRequestToJoinCommunity*(self: Service, communityId: string, ensName: string, addressesToShare: seq[string], + airdropAddress: string, signatures: seq[string]) = try: let arg = AsyncRequestToJoinCommunityTaskArg( tptr: cast[ByteAddress](asyncRequestToJoinCommunityTask), @@ -1512,8 +1557,8 @@ QtObject: slot: "onAsyncRequestToJoinCommunityDone", communityId: communityId, ensName: ensName, - password: if password != "": utils.hashPassword(password) else: "", addressesToShare: addressesToShare, + signatures: signatures, airdropAddress: airdropAddress, ) self.threadpool.start(arg) @@ -1539,15 +1584,15 @@ QtObject: error: e.msg )) - proc asyncEditSharedAddresses*(self: Service, communityId: string, password: string, addressesToShare: seq[string], - airdropAddress: string) = + proc asyncEditSharedAddresses*(self: Service, communityId: string, addressesToShare: seq[string], airdropAddress: string, + signatures: seq[string]) = let arg = AsyncEditSharedAddressesTaskArg( tptr: cast[ByteAddress](asyncEditSharedAddressesTask), vptr: cast[ByteAddress](self.vptr), slot: "onAsyncEditSharedAddressesDone", communityId: communityId, - password: if password != "": utils.hashPassword(password) else: "", addressesToShare: addressesToShare, + signatures: signatures, airdropAddress: airdropAddress, ) self.threadpool.start(arg) diff --git a/src/app_service/service/keycard/service.nim b/src/app_service/service/keycard/service.nim index e3e2098a3b..1407a8c379 100644 --- a/src/app_service/service/keycard/service.nim +++ b/src/app_service/service/keycard/service.nim @@ -346,7 +346,7 @@ QtObject: self.currentFlow = KCSFlowType.StoreMetadata self.startFlow(payload) - proc startSignFlow*(self: Service, bip44Path: string, txHash: string, pin: string) = + proc startSignFlow*(self: Service, bip44Path: string, txHash: string, pin: string = "") = var payload = %* { RequestParamTXHash: EmptyTxHash, RequestParamBIP44Path: DefaultBIP44Path diff --git a/src/backend/communities.nim b/src/backend/communities.nim index d7f182f249..039514c78b 100644 --- a/src/backend/communities.nim +++ b/src/backend/communities.nim @@ -31,31 +31,54 @@ proc getAllCommunities*(): RpcResponse[JsonNode] {.raises: [Exception].} = proc spectateCommunity*(communityId: string): RpcResponse[JsonNode] {.raises: [Exception].} = result = callPrivateRPC("spectateCommunity".prefix, %*[communityId]) +proc generateJoiningCommunityRequestsForSigning*( + memberPubKey: string, + communityId: string, + addressesToReveal: seq[string] + ): RpcResponse[JsonNode] {.raises: [Exception].} = + let payload = %*[memberPubKey, communityId, addressesToReveal] + result = callPrivateRPC("generateJoiningCommunityRequestsForSigning".prefix, payload) + +proc generateEditCommunityRequestsForSigning*( + memberPubKey: string, + communityId: string, + addressesToReveal: seq[string] + ): RpcResponse[JsonNode] {.raises: [Exception].} = + let payload = %*[memberPubKey, communityId, addressesToReveal] + result = callPrivateRPC("generateEditCommunityRequestsForSigning".prefix, payload) + +## `signParams` represents a json array of SignParamsDto. +proc signData*(signParams: JsonNode): RpcResponse[JsonNode] {.raises: [Exception].} = + if signParams.kind != JArray: + raise newException(Exception, "signParams must be an array") + let payload = %*[signParams] + result = callPrivateRPC("signData".prefix, payload) + proc requestToJoinCommunity*( communityId: string, ensName: string, - password: string, addressesToShare: seq[string], + signatures: seq[string], airdropAddress: string, ): RpcResponse[JsonNode] {.raises: [Exception].} = result = callPrivateRPC("requestToJoinCommunity".prefix, %*[{ "communityId": communityId, "ensName": ensName, - "password": password, "addressesToReveal": addressesToShare, + "signatures": signatures, "airdropAddress": airdropAddress, }]) proc editSharedAddresses*( communityId: string, - password: string, addressesToShare: seq[string], + signatures: seq[string], airdropAddress: string, ): RpcResponse[JsonNode] {.raises: [Exception].} = result = callPrivateRPC("editSharedAddressesForCommunity".prefix, %*[{ "communityId": communityId, - "password": password, "addressesToReveal": addressesToShare, + "signatures": signatures, "airdropAddress": airdropAddress, }]) diff --git a/ui/StatusQ/src/StatusQ/Popups/StatusStackModal.qml b/ui/StatusQ/src/StatusQ/Popups/StatusStackModal.qml index 8a67a8b624..ea2746ddf9 100644 --- a/ui/StatusQ/src/StatusQ/Popups/StatusStackModal.qml +++ b/ui/StatusQ/src/StatusQ/Popups/StatusStackModal.qml @@ -13,6 +13,7 @@ StatusModal { property alias stackItems: stackLayout.children property alias currentIndex: stackLayout.currentIndex + property alias replaceLoader: replaceLoader property alias replaceItem: replaceLoader.sourceComponent property alias subHeaderItem: subHeaderLoader.sourceComponent diff --git a/ui/app/AppLayouts/Chat/ChatLayout.qml b/ui/app/AppLayouts/Chat/ChatLayout.qml index a442dab37d..73244b7c84 100644 --- a/ui/app/AppLayouts/Chat/ChatLayout.qml +++ b/ui/app/AppLayouts/Chat/ChatLayout.qml @@ -229,8 +229,22 @@ StackLayout { assetsModel: root.rootStore.assetsModel collectiblesModel: root.rootStore.collectiblesModel - onJoined: { - root.rootStore.requestToJoinCommunityWithAuthentication(communityIntroDialog.communityId, root.rootStore.userProfileInst.name, sharedAddresses, airdropAddress) + onPrepareForSigning: { + root.rootStore.prepareKeypairsForSigning(sharedAddresses) + + communityIntroDialog.keypairSigningModel = root.rootStore.communitiesModuleInst.keypairsSigningModel + } + + onSignSharedAddressesForAllNonKeycardKeypairs: { + root.rootStore.signSharedAddressesForAllNonKeycardKeypairs() + } + + onSignSharedAddressesForKeypair: { + root.rootStore.signSharedAddressesForKeypair(keyUid) + } + + onJoinCommunity: { + root.rootStore.joinCommunityOrEditSharedAddresses() } onCancelMembershipRequest: { @@ -245,6 +259,16 @@ StackLayout { onClosed: { destroy() } + + Connections { + target: root.rootStore.communitiesModuleInst + + function onSharedAddressesForAllNonKeycardKeypairsSigned() { + if (!!communityIntroDialog.replaceItem) { + communityIntroDialog.replaceLoader.item.sharedAddressesForAllNonKeycardKeypairsSigned() + } + } + } } } diff --git a/ui/app/AppLayouts/Chat/stores/RootStore.qml b/ui/app/AppLayouts/Chat/stores/RootStore.qml index b60f96fb7d..840169ef8e 100644 --- a/ui/app/AppLayouts/Chat/stores/RootStore.qml +++ b/ui/app/AppLayouts/Chat/stores/RootStore.qml @@ -394,8 +394,20 @@ QtObject { return communitiesModuleInst.spectateCommunity(id, ensName) } - function requestToJoinCommunityWithAuthentication(communityId, ensName, addressesToShare = [], airdropAddress = "") { - communitiesModuleInst.requestToJoinCommunityWithAuthenticationWithSharedAddresses(communityId, ensName, JSON.stringify(addressesToShare), airdropAddress) + function prepareKeypairsForSigning(communityId, ensName, addressesToShare = [], airdropAddress = "", editMode = false) { + communitiesModuleInst.prepareKeypairsForSigning(communityId, ensName, JSON.stringify(addressesToShare), airdropAddress, editMode) + } + + function signSharedAddressesForAllNonKeycardKeypairs() { + communitiesModuleInst.signSharedAddressesForAllNonKeycardKeypairs() + } + + function signSharedAddressesForKeypair(keyUid) { + communitiesModuleInst.signSharedAddressesForKeypair(keyUid) + } + + function joinCommunityOrEditSharedAddresses() { + communitiesModuleInst.joinCommunityOrEditSharedAddresses() } function getChainIdForChat() { @@ -612,9 +624,9 @@ QtObject { readonly property bool amIMember: chatCommunitySectionModule ? chatCommunitySectionModule.amIMember : false property var oneToOneChatContact: undefined - readonly property string oneToOneChatContactName: !!_d.oneToOneChatContact ? ProfileUtils.displayName(_d.oneToOneChatContact.localNickname, - _d.oneToOneChatContact.name, - _d.oneToOneChatContact.displayName, + readonly property string oneToOneChatContactName: !!_d.oneToOneChatContact ? ProfileUtils.displayName(_d.oneToOneChatContact.localNickname, + _d.oneToOneChatContact.name, + _d.oneToOneChatContact.displayName, _d.oneToOneChatContact.alias) : "" //Update oneToOneChatContact when the contact is updated @@ -674,7 +686,7 @@ QtObject { //Update oneToOneChatContact when activeChat id changes Binding on oneToOneChatContact { - when: _d.activeChatId && _d.activeChatType === Constants.chatType.oneToOne + when: _d.activeChatId && _d.activeChatType === Constants.chatType.oneToOne value: Utils.getContactDetailsAsJson(_d.activeChatId, false) restoreMode: Binding.RestoreBindingOrValue } diff --git a/ui/app/AppLayouts/Communities/panels/SharedAddressesPanel.qml b/ui/app/AppLayouts/Communities/panels/SharedAddressesPanel.qml index 667e004729..0494b82e51 100644 --- a/ui/app/AppLayouts/Communities/panels/SharedAddressesPanel.qml +++ b/ui/app/AppLayouts/Communities/panels/SharedAddressesPanel.qml @@ -51,13 +51,14 @@ Control { enabled: d.dirty type: d.lostCommunityPermission || d.lostChannelPermissions ? StatusBaseButton.Type.Danger : StatusBaseButton.Type.Normal visible: root.isEditMode - icon.name: type === StatusBaseButton.Type.Normal && d.selectedAddressesDirty ? Constants.authenticationIconByType[root.loginType] : "" + icon.name: type === StatusBaseButton.Type.Normal && d.selectedAddressesDirty? + !root.isEditMode? Constants.authenticationIconByType[root.loginType] : "" + : "" text: d.lostCommunityPermission ? qsTr("Save changes & leave %1").arg(root.communityName) : d.lostChannelPermissions ? qsTr("Save changes & update my permissions") - : qsTr("Save changes") + : qsTr("Prove ownership") onClicked: { - root.saveSelectedAddressesClicked(root.selectedAirdropAddress, root.selectedSharedAddresses) - root.close() + root.prepareForSigning(root.selectedAirdropAddress, root.selectedSharedAddresses) } } StatusButton { @@ -78,7 +79,7 @@ Control { signal sharedAddressesChanged(string airdropAddress, var sharedAddresses) signal shareSelectedAddressesClicked(string airdropAddress, var sharedAddresses) - signal saveSelectedAddressesClicked(string airdropAddress, var sharedAddresses) + signal prepareForSigning(string airdropAddress, var sharedAddresses) signal close() @@ -118,11 +119,14 @@ Control { function setOldSharedAddresses(oldSharedAddresses) { d.initialSelectedSharedAddresses = oldSharedAddresses + accountSelector.selectedSharedAddresses = Qt.binding(() => d.initialSelectedSharedAddresses) + accountSelector.applyChange() } function setOldAirdropAddress(oldAirdropAddress) { d.initialSelectedAirdropAddress = oldAirdropAddress - accountSelector.selectedAirdropAddress = oldAirdropAddress + accountSelector.selectedAirdropAddress = Qt.binding(() => d.initialSelectedAirdropAddress) + accountSelector.applyChange() } SortFilterProxyModel { @@ -178,7 +182,8 @@ Control { model: root.walletAccountsModel selectedSharedAddresses: d.initialSelectedSharedAddresses selectedAirdropAddress: d.initialSelectedAirdropAddress - onAddressesChanged: { + onAddressesChanged: accountSelector.applyChange() + function applyChange() { root.selectedSharedAddresses = selectedSharedAddresses root.selectedAirdropAddress = selectedAirdropAddress root.sharedAddressesChanged(selectedAirdropAddress, selectedSharedAddresses) diff --git a/ui/app/AppLayouts/Communities/panels/SharedAddressesSigningPanel.qml b/ui/app/AppLayouts/Communities/panels/SharedAddressesSigningPanel.qml new file mode 100644 index 0000000000..39cb9e68bb --- /dev/null +++ b/ui/app/AppLayouts/Communities/panels/SharedAddressesSigningPanel.qml @@ -0,0 +1,181 @@ +import QtQuick 2.15 +import QtQuick.Layouts 1.15 + +import StatusQ.Core 0.1 +import StatusQ.Controls 0.1 +import StatusQ.Core.Theme 0.1 + +import utils 1.0 +import shared.popups.keycard.helpers 1.0 + +import SortFilterProxyModel 0.2 + +ColumnLayout { + id: root + + property var keypairSigningModel + + readonly property string title: qsTr("Prove ownership of keypairs") + readonly property var rightButtons: [d.rightBtn] + readonly property bool allSigned: regularKeypairs.visible == d.sharedAddressesForAllNonKeycardKeypairsSigned && + keycardKeypairs.visible == d.allKeycardKeypairsSigned + + signal joinCommunity() + signal signSharedAddressesForAllNonKeycardKeypairs() + signal signSharedAddressesForKeypair(string keyUid) + + function sharedAddressesForAllNonKeycardKeypairsSigned() { + d.sharedAddressesForAllNonKeycardKeypairsSigned = true + } + + QtObject { + id: d + + property bool sharedAddressesForAllNonKeycardKeypairsSigned: false + property bool allKeycardKeypairsSigned: false + + readonly property var rightBtn: StatusButton { + enabled: root.allSigned + text: qsTr("Share your addresses to join") + onClicked: { + root.joinCommunity() + } + } + + function reEvaluateSignedKeypairs() { + let allKeypairsSigned = true + for(var i = 0; i< keycardKeypairs.model.count; i++) { + if(!keycardKeypairs.model.get(i).keyPair.ownershipVerified) { + allKeypairsSigned = false + break + } + } + + d.allKeycardKeypairsSigned = allKeypairsSigned + } + } + + ColumnLayout { + Layout.fillWidth: true + Layout.margins: Style.current.xlPadding + + spacing: Style.current.padding + + RowLayout { + Layout.fillWidth: true + + visible: regularKeypairs.visible + + StatusBaseText { + Layout.fillWidth: true + text: qsTr("Keypairs we need an authentication for") + font.pixelSize: Constants.keycard.general.fontSize2 + color: Theme.palette.baseColor1 + wrapMode: Text.WordWrap + } + + StatusButton { + text: d.sharedAddressesForAllNonKeycardKeypairsSigned? qsTr("Authenticated") : qsTr("Authenticate") + enabled: !d.sharedAddressesForAllNonKeycardKeypairsSigned + icon.name: userProfile.usingBiometricLogin? "touch-id" : "password" + + onClicked: { + root.signSharedAddressesForAllNonKeycardKeypairs() + } + } + } + + StatusListView { + id: regularKeypairs + Layout.fillWidth: true + Layout.preferredHeight: regularKeypairs.contentHeight + visible: regularKeypairs.model.count > 0 + spacing: Style.current.padding + model: SortFilterProxyModel { + sourceModel: root.keypairSigningModel + filters: ExpressionFilter { + expression: !model.keyPair.migratedToKeycard + } + } + delegate: KeyPairItem { + width: ListView.view.width + sensor.hoverEnabled: false + additionalInfoForProfileKeypair: "" + + keyPairType: model.keyPair.pairType + keyPairKeyUid: model.keyPair.keyUid + keyPairName: model.keyPair.name + keyPairIcon: model.keyPair.icon + keyPairImage: model.keyPair.image + keyPairDerivedFrom: model.keyPair.derivedFrom + keyPairAccounts: model.keyPair.accounts + } + } + + Item { + visible: regularKeypairs.visible && keycardKeypairs.visible + Layout.fillWidth: true + Layout.preferredHeight: Style.current.xlPadding + } + + StatusBaseText { + Layout.fillWidth: true + visible: keycardKeypairs.visible + text: qsTr("Keypairs that need to be singed using appropriate Keycard") + font.pixelSize: Constants.keycard.general.fontSize2 + color: Theme.palette.baseColor1 + wrapMode: Text.WordWrap + } + + StatusListView { + id: keycardKeypairs + Layout.fillWidth: true + Layout.preferredHeight: keycardKeypairs.contentHeight + visible: keycardKeypairs.model.count > 0 + spacing: Style.current.padding + model: SortFilterProxyModel { + sourceModel: root.keypairSigningModel + filters: ExpressionFilter { + expression: model.keyPair.migratedToKeycard + } + } + delegate: KeyPairItem { + width: ListView.view.width + sensor.hoverEnabled: !model.keyPair.ownershipVerified + additionalInfoForProfileKeypair: "" + + keyPairType: model.keyPair.pairType + keyPairKeyUid: model.keyPair.keyUid + keyPairName: model.keyPair.name + keyPairIcon: model.keyPair.icon + keyPairImage: model.keyPair.image + keyPairDerivedFrom: model.keyPair.derivedFrom + keyPairAccounts: model.keyPair.accounts + + components: [ + StatusBaseText { + font.weight: Font.Medium + font.underline: mouseArea.containsMouse + font.pixelSize: Theme.primaryTextFontSize + color: model.keyPair.ownershipVerified? Theme.palette.baseColor1 : Theme.palette.primaryColor1 + text: model.keyPair.ownershipVerified? qsTr("Signed") : qsTr("Sign") + MouseArea { + id: mouseArea + anchors.fill: parent + cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor + acceptedButtons: Qt.LeftButton | Qt.RightButton + hoverEnabled: !model.keyPair.ownershipVerified + enabled: !model.keyPair.ownershipVerified + onEnabledChanged: { + d.reEvaluateSignedKeypairs() + } + onClicked: { + root.signSharedAddressesForKeypair(model.keyPair.keyUid) + } + } + } + ] + } + } + } +} diff --git a/ui/app/AppLayouts/Communities/panels/qmldir b/ui/app/AppLayouts/Communities/panels/qmldir index 93569fda18..d3ef3f4fda 100644 --- a/ui/app/AppLayouts/Communities/panels/qmldir +++ b/ui/app/AppLayouts/Communities/panels/qmldir @@ -30,6 +30,7 @@ ProfilePopupInviteMessagePanel 1.0 ProfilePopupInviteMessagePanel.qml ProfilePopupOverviewPanel 1.0 ProfilePopupOverviewPanel.qml RequirementsCheckPendingLoader 1.0 RequirementsCheckPendingLoader.qml SharedAddressesPanel 1.0 SharedAddressesPanel.qml +SharedAddressesSigningPanel 1.0 SharedAddressesSigningPanel.qml SortableTokenHoldersList 1.0 SortableTokenHoldersList.qml SortableTokenHoldersPanel 1.0 SortableTokenHoldersPanel.qml TagsPanel 1.0 TagsPanel.qml diff --git a/ui/app/AppLayouts/Communities/popups/SharedAddressesPopup.qml b/ui/app/AppLayouts/Communities/popups/SharedAddressesPopup.qml index 1cb36f786f..ed46d86ed5 100644 --- a/ui/app/AppLayouts/Communities/popups/SharedAddressesPopup.qml +++ b/ui/app/AppLayouts/Communities/popups/SharedAddressesPopup.qml @@ -1,6 +1,10 @@ import QtQuick 2.15 +import QtQml.Models 2.15 +import QtQuick.Layouts 1.15 +import StatusQ.Controls 0.1 import StatusQ.Popups.Dialog 0.1 +import StatusQ.Core.Theme 0.1 import AppLayouts.Communities.panels 1.0 @@ -12,6 +16,7 @@ StatusDialog { property bool isEditMode property bool requirementsCheckPending + property var keypairSigningModel required property string communityName required property string communityIcon @@ -22,46 +27,138 @@ StatusDialog { required property var assetsModel required property var collectiblesModel - property alias selectedSharedAddresses: panel.selectedSharedAddresses - property alias selectedAirdropAddress: panel.selectedAirdropAddress - signal shareSelectedAddressesClicked(string airdropAddress, var sharedAddresses) - signal saveSelectedAddressesClicked(string airdropAddress, var sharedAddresses) signal sharedAddressesChanged(string airdropAddress, var sharedAddresses) + signal prepareForSigning(string airdropAddress, var sharedAddresses) + signal editRevealedAddresses() + signal signSharedAddressesForAllNonKeycardKeypairs() + signal signSharedAddressesForKeypair(string keyUid) + function setOldSharedAddresses(oldSharedAddresses) { - panel.setOldSharedAddresses(oldSharedAddresses) + if (!d.displaySigningPanel && !!loader.item) { + d.oldSharedAddresses = oldSharedAddresses + loader.item.setOldSharedAddresses(oldSharedAddresses) + } } function setOldAirdropAddress(oldAirdropAddress) { - panel.setOldAirdropAddress(oldAirdropAddress) + if (!d.displaySigningPanel && !!loader.item) { + d.oldAirdropAddress = oldAirdropAddress + loader.item.setOldAirdropAddress(oldAirdropAddress) + } } - title: panel.title + function sharedAddressesForAllNonKeycardKeypairsSigned() { + if (d.displaySigningPanel && !!loader.item) { + loader.item.sharedAddressesForAllNonKeycardKeypairsSigned() + } + } + + title: !!loader.item? loader.item.title : "" implicitWidth: 640 // by design padding: 0 - contentItem: SharedAddressesPanel { - id: panel - isEditMode: root.isEditMode - requirementsCheckPending: root.requirementsCheckPending - communityName: root.communityName - communityIcon: root.communityIcon - loginType: root.loginType - walletAccountsModel: root.walletAccountsModel - permissionsModel: root.permissionsModel - assetsModel: root.assetsModel - collectiblesModel: root.collectiblesModel - onShareSelectedAddressesClicked: root.shareSelectedAddressesClicked(airdropAddress, sharedAddresses) - onSaveSelectedAddressesClicked: root.saveSelectedAddressesClicked(airdropAddress, sharedAddresses) - onSharedAddressesChanged: { - root.sharedAddressesChanged(airdropAddress, sharedAddresses) + QtObject { + id: d + + property bool displaySigningPanel: false + property bool allSigned: false + + property var oldSharedAddresses + property string oldAirdropAddress + + property var selectAddressesPanelButtons: ObjectModel {} + readonly property var signingPanelButtons: ObjectModel { + StatusFlatButton { + visible: root.isEditMode + borderColor: Theme.palette.baseColor2 + text: qsTr("Cancel") + onClicked: root.close() + } + StatusButton { + text: qsTr("Save changes") + enabled: d.allSigned + onClicked: { + root.editRevealedAddresses() + root.close() + } + } + } + + readonly property var signingPanelBackButtons: ObjectModel { + StatusBackButton { + onClicked: { + d.displaySigningPanel = false + } + } + } + } + + contentItem: Loader { + id: loader + sourceComponent: d.displaySigningPanel? sharedAddressesSigningPanelComponent : selectSharedAddressesPanelComponent + + onLoaded: { + if (!d.displaySigningPanel) { + if (!!d.oldSharedAddresses) { + root.setOldSharedAddresses(d.oldSharedAddresses) + } + if (!!d.oldAirdropAddress) { + root.setOldAirdropAddress(d.oldAirdropAddress) + } + d.selectAddressesPanelButtons = loader.item.buttons + } + } + } + + Component { + id: selectSharedAddressesPanelComponent + SharedAddressesPanel { + isEditMode: root.isEditMode + requirementsCheckPending: root.requirementsCheckPending + communityName: root.communityName + communityIcon: root.communityIcon + loginType: root.loginType + walletAccountsModel: root.walletAccountsModel + permissionsModel: root.permissionsModel + assetsModel: root.assetsModel + collectiblesModel: root.collectiblesModel + onShareSelectedAddressesClicked: root.shareSelectedAddressesClicked(airdropAddress, sharedAddresses) + onPrepareForSigning: { + root.prepareForSigning(airdropAddress, sharedAddresses) + d.displaySigningPanel = true + } + onSharedAddressesChanged: { + root.sharedAddressesChanged(airdropAddress, sharedAddresses) + } + onClose: root.close() + } + } + + Component { + id: sharedAddressesSigningPanelComponent + SharedAddressesSigningPanel { + + keypairSigningModel: root.keypairSigningModel + + onSignSharedAddressesForAllNonKeycardKeypairs: { + root.signSharedAddressesForAllNonKeycardKeypairs() + } + + onSignSharedAddressesForKeypair: { + root.signSharedAddressesForKeypair(keyUid) + } + + onAllSignedChanged: { + d.allSigned = allSigned + } } - onClose: root.close() } footer: StatusDialogFooter { spacing: Style.current.padding - rightButtons: panel.buttons + rightButtons: d.displaySigningPanel? d.signingPanelButtons : d.selectAddressesPanelButtons + leftButtons: d.displaySigningPanel? d.signingPanelBackButtons : null } } diff --git a/ui/app/AppLayouts/Communities/views/CommunityColumnView.qml b/ui/app/AppLayouts/Communities/views/CommunityColumnView.qml index 7c5da6ed20..00e5377a78 100644 --- a/ui/app/AppLayouts/Communities/views/CommunityColumnView.qml +++ b/ui/app/AppLayouts/Communities/views/CommunityColumnView.qml @@ -61,7 +61,8 @@ Item { || root.communitiesStore.discordImportInProgress property bool invitationPending: root.store.isCommunityRequestPending(communityData.id) - property bool isJoinBtnLoading: false + + property bool joiningCommunityInProgress: false } ColumnHeaderPanel { @@ -468,7 +469,7 @@ Item { anchors.bottomMargin: Style.current.halfPadding anchors.horizontalCenter: parent.horizontalCenter enabled: !root.communityData.amIBanned - loading: d.isJoinBtnLoading + loading: d.joiningCommunityInProgress text: { if (root.communityData.amIBanned) return qsTr("You were banned from community") @@ -479,22 +480,23 @@ Item { } onClicked: { - Global.openPopup(communityIntroDialog); + Global.openPopup(communityIntroDialogComponent); } Connections { - enabled: d.isJoinBtnLoading + enabled: d.joiningCommunityInProgress target: root.store.communitiesModuleInst function onCommunityAccessRequested(communityId: string) { if (communityId === communityData.id) { d.invitationPending = root.store.isCommunityRequestPending(communityData.id) - d.isJoinBtnLoading = false + d.joiningCommunityInProgress = false } } - function onCommunityAccessFailed(communityId: string) { + + function onCommunityAccessFailed(communityId: string, error: string) { if (communityId === communityData.id) { d.invitationPending = false - d.isJoinBtnLoading = false + d.joiningCommunityInProgress = false Global.displayToastMessage(qsTr("Request to join failed"), qsTr("Please try again later"), "", @@ -503,15 +505,12 @@ Item { "") } } - function onUserAuthenticationCanceled() { - d.invitationPending = false - d.isJoinBtnLoading = false - } } Component { - id: communityIntroDialog + id: communityIntroDialogComponent CommunityIntroDialog { + id: communityIntroDialog isInvitationPending: d.invitationPending requirementsCheckPending: root.store.requirementsCheckPending @@ -528,19 +527,47 @@ Item { assetsModel: root.store.assetsModel collectiblesModel: root.store.collectiblesModel - onJoined: { - d.isJoinBtnLoading = true - root.store.requestToJoinCommunityWithAuthentication(communityData.id, root.store.userProfileInst.name, sharedAddresses, airdropAddress) + onPrepareForSigning: { + root.store.prepareKeypairsForSigning(communityData.id, root.store.userProfileInst.name, sharedAddresses, airdropAddress, false) + + communityIntroDialog.keypairSigningModel = root.store.communitiesModuleInst.keypairsSigningModel } + + onSignSharedAddressesForAllNonKeycardKeypairs: { + root.store.signSharedAddressesForAllNonKeycardKeypairs() + } + + onSignSharedAddressesForKeypair: { + root.store.signSharedAddressesForKeypair(keyUid) + } + + onJoinCommunity: { + d.joiningCommunityInProgress = true + root.store.joinCommunityOrEditSharedAddresses() + } + onCancelMembershipRequest: { root.store.cancelPendingRequest(communityData.id) d.invitationPending = root.store.isCommunityRequestPending(communityData.id) } + onSharedAddressesUpdated: { root.store.updatePermissionsModel(communityData.id, sharedAddresses) } - onClosed: destroy() + onClosed: { + destroy() + } + + Connections { + target: root.store.communitiesModuleInst + + function onSharedAddressesForAllNonKeycardKeypairsSigned() { + if (!!communityIntroDialog.replaceItem) { + communityIntroDialog.replaceLoader.item.sharedAddressesForAllNonKeycardKeypairsSigned() + } + } + } } } } diff --git a/ui/app/AppLayouts/Profile/views/CommunitiesView.qml b/ui/app/AppLayouts/Profile/views/CommunitiesView.qml index c1337ade76..41bd2d7ae0 100644 --- a/ui/app/AppLayouts/Profile/views/CommunitiesView.qml +++ b/ui/app/AppLayouts/Profile/views/CommunitiesView.qml @@ -242,7 +242,24 @@ SettingsContentBase { assetsModel: chatStore.assetsModel collectiblesModel: chatStore.collectiblesModel - onJoined: chatStore.requestToJoinCommunityWithAuthentication(communityIntroDialog.communityId, root.rootStore.userProfileInst.name, sharedAddresses, airdropAddress) + onPrepareForSigning: { + chatStore.prepareKeypairsForSigning(communityIntroDialog.communityId, root.rootStore.userProfileInst.name, sharedAddresses, airdropAddress, false) + + communityIntroDialog.keypairSigningModel = chatStore.communitiesModuleInst.keypairsSigningModel + } + + onSignSharedAddressesForAllNonKeycardKeypairs: { + chatStore.signSharedAddressesForAllNonKeycardKeypairs() + } + + onSignSharedAddressesForKeypair: { + chatStore.signSharedAddressesForKeypair(keyUid) + } + + onJoinCommunity: { + chatStore.joinCommunityOrEditSharedAddresses() + } + onCancelMembershipRequest: root.rootStore.cancelPendingRequest(communityIntroDialog.communityId) onSharedAddressesUpdated: { @@ -250,6 +267,16 @@ SettingsContentBase { } onClosed: destroy() + + Connections { + target: chatStore.communitiesModuleInst + + function onSharedAddressesForAllNonKeycardKeypairsSigned() { + if (!!communityIntroDialog.replaceItem) { + communityIntroDialog.replaceLoader.item.sharedAddressesForAllNonKeycardKeypairsSigned() + } + } + } } } } diff --git a/ui/app/AppLayouts/stores/RootStore.qml b/ui/app/AppLayouts/stores/RootStore.qml index 05697fc023..6d39236d83 100644 --- a/ui/app/AppLayouts/stores/RootStore.qml +++ b/ui/app/AppLayouts/stores/RootStore.qml @@ -241,14 +241,20 @@ QtObject { mainModuleInst.windowDeactivated() } - function requestToJoinCommunityWithAuthentication(communityId, ensName, addressesToShare = [], airdropAddress = "") { - communitiesModuleInst.requestToJoinCommunityWithAuthenticationWithSharedAddresses( - communityId, ensName, JSON.stringify(addressesToShare), airdropAddress) + function prepareKeypairsForSigning(communityId, ensName, addressesToShare = [], airdropAddress = "", editMode = false) { + communitiesModuleInst.prepareKeypairsForSigning(communityId, ensName, JSON.stringify(addressesToShare), airdropAddress, editMode) } - function editSharedAddressesWithAuthentication(communityId, addressesToShare = [], airdropAddress = "") { - communitiesModuleInst.editSharedAddressesWithAuthentication( - communityId, JSON.stringify(addressesToShare), airdropAddress) + function signSharedAddressesForAllNonKeycardKeypairs() { + communitiesModuleInst.signSharedAddressesForAllNonKeycardKeypairs() + } + + function signSharedAddressesForKeypair(keyUid) { + communitiesModuleInst.signSharedAddressesForKeypair(keyUid) + } + + function joinCommunityOrEditSharedAddresses() { + communitiesModuleInst.joinCommunityOrEditSharedAddresses() } function updatePermissionsModel(communityId, sharedAddresses) { diff --git a/ui/app/mainui/Popups.qml b/ui/app/mainui/Popups.qml index 791d40e865..69acc77eee 100644 --- a/ui/app/mainui/Popups.qml +++ b/ui/app/mainui/Popups.qml @@ -531,7 +531,24 @@ QtObject { } assetsModel: root.rootStore.assetsModel collectiblesModel: root.rootStore.collectiblesModel - onJoined: root.rootStore.requestToJoinCommunityWithAuthentication(communityIntroDialog.communityId, communityIntroDialog.name, sharedAddresses, airdropAddress) + onPrepareForSigning: { + root.rootStore.prepareKeypairsForSigning(communityIntroDialog.communityId, communityIntroDialog.name, sharedAddresses, airdropAddress, false) + + communityIntroDialog.keypairSigningModel = root.rootStore.communitiesModuleInst.keypairsSigningModel + } + + onSignSharedAddressesForAllNonKeycardKeypairs: { + root.rootStore.signSharedAddressesForAllNonKeycardKeypairs() + } + + onSignSharedAddressesForKeypair: { + root.rootStore.signSharedAddressesForKeypair(keyUid) + } + + onJoinCommunity: { + root.rootStore.joinCommunityOrEditSharedAddresses() + } + onCancelMembershipRequest: root.rootStore.cancelPendingRequest(communityIntroDialog.communityId) Connections { target: root.communitiesStore.communitiesModuleInst @@ -541,20 +558,27 @@ QtObject { root.communitiesStore.spectateCommunity(communityId); communityIntroDialog.close(); } - function onCommunityAccessFailed(communityId: string) { + function onCommunityAccessFailed(communityId: string, error: string) { if (communityId !== communityIntroDialog.communityId) return communityIntroDialog.close(); } - function onUserAuthenticationCanceled() { - communityIntroDialog.close(); - } } onSharedAddressesUpdated: { root.rootStore.updatePermissionsModel(communityIntroDialog.communityId, sharedAddresses) } onAboutToShow: { root.rootStore.communityKeyToImport = communityIntroDialog.communityId; } onClosed: { root.rootStore.communityKeyToImport = ""; destroy(); } + + Connections { + target: root.rootStore.communitiesModuleInst + + function onSharedAddressesForAllNonKeycardKeypairsSigned() { + if (!!communityIntroDialog.replaceItem) { + communityIntroDialog.replaceLoader.item.sharedAddressesForAllNonKeycardKeypairsSigned() + } + } + } } }, @@ -738,9 +762,33 @@ QtObject { onSharedAddressesChanged: root.rootStore.updatePermissionsModel( editSharedAddressesPopup.communityId, sharedAddresses) - onSaveSelectedAddressesClicked: root.rootStore.editSharedAddressesWithAuthentication( - editSharedAddressesPopup.communityId, sharedAddresses, airdropAddress) + onPrepareForSigning: { + root.rootStore.prepareKeypairsForSigning(editSharedAddressesPopup.communityId, "", sharedAddresses, airdropAddress, true) + + editSharedAddressesPopup.keypairSigningModel = root.rootStore.communitiesModuleInst.keypairsSigningModel + } + + onSignSharedAddressesForAllNonKeycardKeypairs: { + root.rootStore.signSharedAddressesForAllNonKeycardKeypairs() + } + + onSignSharedAddressesForKeypair: { + root.rootStore.signSharedAddressesForKeypair(keyUid) + } + + onEditRevealedAddresses: { + root.rootStore.joinCommunityOrEditSharedAddresses() + } + onClosed: destroy() + + Connections { + target: root.rootStore.communitiesModuleInst + + function onSharedAddressesForAllNonKeycardKeypairsSigned() { + editSharedAddressesPopup.sharedAddressesForAllNonKeycardKeypairsSigned() + } + } } }, diff --git a/vendor/status-go b/vendor/status-go index 9f69c32593..11a3612290 160000 --- a/vendor/status-go +++ b/vendor/status-go @@ -1 +1 @@ -Subproject commit 9f69c3259397821483bad722ab92eb52470185de +Subproject commit 11a36122901967bc3fac2daf8d2f8bce9b5ed427