code.vegaprotocol.io/vega@v0.79.0/wallet/api/client_connect_wallet.go (about) 1 // Copyright (C) 2023 Gobalsky Labs Limited 2 // 3 // This program is free software: you can redistribute it and/or modify 4 // it under the terms of the GNU Affero General Public License as 5 // published by the Free Software Foundation, either version 3 of the 6 // License, or (at your option) any later version. 7 // 8 // This program is distributed in the hope that it will be useful, 9 // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 // GNU Affero General Public License for more details. 12 // 13 // You should have received a copy of the GNU Affero General Public License 14 // along with this program. If not, see <http://www.gnu.org/licenses/>. 15 16 package api 17 18 import ( 19 "context" 20 "errors" 21 "fmt" 22 23 "code.vegaprotocol.io/vega/libs/jsonrpc" 24 "code.vegaprotocol.io/vega/wallet/preferences" 25 "code.vegaprotocol.io/vega/wallet/wallet" 26 ) 27 28 const ( 29 WalletConnectionSuccessfullyEstablished = "The connection to the wallet has been successfully established." 30 PassphraseRequestReasonUnlockWallet = "The application wants to unlock the wallet." 31 ) 32 33 type ClientConnectWallet struct { 34 walletStore WalletStore 35 interactor Interactor 36 } 37 38 // Handle initiates the wallet connection between the API and a third-party 39 // application. 40 // 41 // It triggers a selection of the wallet the client wants to use for this 42 // connection. The wallet is then loaded in memory. All changes done to that wallet 43 // will start in-memory, and then, be saved in the wallet file. Any changes done 44 // to the wallet outside the JSON-RPC session (via the command-line for example) 45 // will be overridden. For the effects to be taken into account, the wallet has 46 // to be disconnected first, and then re-connected. 47 // 48 // All sessions have to be initialized by using this handler. Otherwise, a call 49 // to any other handlers will be rejected. 50 func (h *ClientConnectWallet) Handle(ctx context.Context, hostname string) (wallet.Wallet, *jsonrpc.ErrorDetails) { 51 traceID := jsonrpc.TraceIDFromContext(ctx) 52 53 if err := h.interactor.NotifyInteractionSessionBegan(ctx, traceID, WalletConnectionWorkflow, 4); err != nil { 54 return nil, RequestNotPermittedError(err) 55 } 56 defer h.interactor.NotifyInteractionSessionEnded(ctx, traceID) 57 58 availableWallets, err := h.walletStore.ListWallets(ctx) 59 if err != nil { 60 h.interactor.NotifyError(ctx, traceID, InternalErrorType, fmt.Errorf("could not list the available wallets: %w", err)) 61 return nil, InternalError(ErrCouldNotConnectToWallet) 62 } 63 if len(availableWallets) == 0 { 64 h.interactor.NotifyError(ctx, traceID, ApplicationErrorType, ErrNoWalletToConnectTo) 65 return nil, ApplicationCancellationError(ErrApplicationCancelledTheRequest) 66 } 67 68 var approval preferences.ConnectionApproval 69 for { 70 rawApproval, err := h.interactor.RequestWalletConnectionReview(ctx, traceID, 1, hostname) 71 if err != nil { 72 if errDetails := HandleRequestFlowError(ctx, traceID, h.interactor, err); errDetails != nil { 73 return nil, errDetails 74 } 75 h.interactor.NotifyError(ctx, traceID, InternalErrorType, fmt.Errorf("reviewing the wallet connection failed: %w", err)) 76 return nil, InternalError(ErrCouldNotConnectToWallet) 77 } 78 79 a, err := preferences.ParseConnectionApproval(rawApproval) 80 if err != nil { 81 h.interactor.NotifyError(ctx, traceID, UserErrorType, err) 82 continue 83 } 84 approval = a 85 break 86 } 87 88 if isConnectionRejected(approval) { 89 return nil, UserRejectionError(ErrUserRejectedWalletConnection) 90 } 91 92 var walletName string 93 if len(availableWallets) > 1 { 94 for { 95 if ctx.Err() != nil { 96 h.interactor.NotifyError(ctx, traceID, ApplicationErrorType, ErrRequestInterrupted) 97 return nil, RequestInterruptedError(ErrRequestInterrupted) 98 } 99 100 selectedWallet, err := h.interactor.RequestWalletSelection(ctx, traceID, 2, hostname, availableWallets) 101 if err != nil { 102 if errDetails := HandleRequestFlowError(ctx, traceID, h.interactor, err); errDetails != nil { 103 return nil, errDetails 104 } 105 h.interactor.NotifyError(ctx, traceID, InternalErrorType, fmt.Errorf("requesting the wallet selection failed: %w", err)) 106 return nil, InternalError(ErrCouldNotConnectToWallet) 107 } 108 109 if exist, err := h.walletStore.WalletExists(ctx, selectedWallet); err != nil { 110 h.interactor.NotifyError(ctx, traceID, InternalErrorType, fmt.Errorf("could not verify the wallet existence: %w", err)) 111 return nil, InternalError(ErrCouldNotConnectToWallet) 112 } else if !exist { 113 h.interactor.NotifyError(ctx, traceID, UserErrorType, ErrWalletDoesNotExist) 114 continue 115 } 116 117 walletName = selectedWallet 118 break 119 } 120 } else { 121 // There is single wallet available, it doesn't make sense to ask which 122 // wallet to use. 123 walletName = availableWallets[0] 124 } 125 126 alreadyUnlocked, err := h.walletStore.IsWalletAlreadyUnlocked(ctx, walletName) 127 if err != nil { 128 h.interactor.NotifyError(ctx, traceID, InternalErrorType, fmt.Errorf("could not verify whether the wallet is already unlock or not: %w", err)) 129 return nil, InternalError(ErrCouldNotConnectToWallet) 130 } 131 132 if !alreadyUnlocked { 133 for { 134 if ctx.Err() != nil { 135 h.interactor.NotifyError(ctx, traceID, ApplicationErrorType, ErrRequestInterrupted) 136 return nil, RequestInterruptedError(ErrRequestInterrupted) 137 } 138 139 walletPassphrase, err := h.interactor.RequestPassphrase(ctx, traceID, 3, walletName, PassphraseRequestReasonUnlockWallet) 140 if err != nil { 141 if errDetails := HandleRequestFlowError(ctx, traceID, h.interactor, err); errDetails != nil { 142 return nil, errDetails 143 } 144 h.interactor.NotifyError(ctx, traceID, InternalErrorType, fmt.Errorf("requesting the wallet passphrase failed: %w", err)) 145 return nil, InternalError(ErrCouldNotConnectToWallet) 146 } 147 148 if err := h.walletStore.UnlockWallet(ctx, walletName, walletPassphrase); err != nil { 149 if errors.Is(err, wallet.ErrWrongPassphrase) { 150 h.interactor.NotifyError(ctx, traceID, UserErrorType, wallet.ErrWrongPassphrase) 151 continue 152 } 153 if errDetails := HandleRequestFlowError(ctx, traceID, h.interactor, err); errDetails != nil { 154 return nil, errDetails 155 } 156 h.interactor.NotifyError(ctx, traceID, InternalErrorType, fmt.Errorf("could not unlock the wallet: %w", err)) 157 return nil, InternalError(ErrCouldNotConnectToWallet) 158 } 159 break 160 } 161 } 162 163 w, err := h.walletStore.GetWallet(ctx, walletName) 164 if err != nil { 165 h.interactor.NotifyError(ctx, traceID, InternalErrorType, fmt.Errorf("could not retrieve the wallet: %w", err)) 166 return nil, InternalError(ErrCouldNotConnectToWallet) 167 } 168 169 h.interactor.NotifySuccessfulRequest(ctx, traceID, 4, WalletConnectionSuccessfullyEstablished) 170 171 return w, nil 172 } 173 174 func isConnectionRejected(approval preferences.ConnectionApproval) bool { 175 return approval != preferences.ApprovedOnlyThisTime 176 } 177 178 func NewConnectWallet(walletStore WalletStore, interactor Interactor) *ClientConnectWallet { 179 return &ClientConnectWallet{ 180 walletStore: walletStore, 181 interactor: interactor, 182 } 183 }