Skip to content

Commit

Permalink
frontend: add prompt to connect keystore
Browse files Browse the repository at this point in the history
  • Loading branch information
benma committed Oct 31, 2023
1 parent 4855dd2 commit 8e71687
Show file tree
Hide file tree
Showing 7 changed files with 118 additions and 1 deletion.
34 changes: 33 additions & 1 deletion backend/accounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -536,11 +536,43 @@ func (backend *Backend) createAndAddAccount(coin coinpkg.Coin, persistedConfig *
DBFolder: backend.arguments.CacheDirectoryPath(),
NotesFolder: backend.arguments.NotesDirectoryPath(),
ConnectKeystore: func() (keystore.Keystore, error) {
type data struct {
Type string `json:"typ"`
KeystoreName string `json:"keystoreName"`
}
accountRootFingerprint, err := persistedConfig.SigningConfigurations.RootFingerprint()
if err != nil {
return nil, err
}
return backend.connectKeystore.connect(backend.Keystore(), accountRootFingerprint, 20*time.Minute)
keystoreName := ""
persistedKeystore, err := backend.config.AccountsConfig().LookupKeystore(accountRootFingerprint)
if err == nil {
keystoreName = persistedKeystore.Name
}
backend.Notify(observable.Event{
Subject: "connect-keystore",
Action: action.Replace,
Object: data{
Type: "connect",
KeystoreName: keystoreName,
},
})
ks, err := backend.connectKeystore.connect(
backend.Keystore(),
accountRootFingerprint,
20*time.Minute,
)
// If a previous connect-keystore request is in progress, this one failed, but we don't
// dismiss the previous prompt. We dismiss it only if it is canceled, it timed out, or
// there is some other problem.
if errp.Cause(err) != errInProgress {
backend.Notify(observable.Event{
Subject: "connect-keystore",
Action: action.Replace,
Object: nil,
})
}
return ks, err
},
OnEvent: func(event accountsTypes.Event) {
backend.events <- AccountEvent{
Expand Down
5 changes: 5 additions & 0 deletions backend/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -784,3 +784,8 @@ func (backend *Backend) GetAccountFromCode(code string) (accounts.Interface, err

return acct, nil
}

// CancelConnectKeystore cancels a pending keystore connection request if one exists.
func (backend *Backend) CancelConnectKeystore() {
backend.connectKeystore.cancel()
}
7 changes: 7 additions & 0 deletions backend/handlers/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ type Backend interface {
AOPPChooseAccount(code accountsTypes.Code)
GetAccountFromCode(code string) (accounts.Interface, error)
HTTPClient() *http.Client
CancelConnectKeystore()
}

// Handlers provides a web api to the backend.
Expand Down Expand Up @@ -232,6 +233,7 @@ func NewHandlers(
getAPIRouterNoError(apiRouter)("/aopp/cancel", handlers.postAOPPCancelHandler).Methods("POST")
getAPIRouterNoError(apiRouter)("/aopp/approve", handlers.postAOPPApproveHandler).Methods("POST")
getAPIRouter(apiRouter)("/aopp/choose-account", handlers.postAOPPChooseAccountHandler).Methods("POST")
getAPIRouter(apiRouter)("/cancel-connect-keystore", handlers.postCancelConnectKeystoreHandler).Methods("POST")

devicesRouter := getAPIRouterNoError(apiRouter.PathPrefix("/devices").Subrouter())
devicesRouter("/registered", handlers.getDevicesRegisteredHandler).Methods("GET")
Expand Down Expand Up @@ -1281,3 +1283,8 @@ func (handlers *Handlers) postAOPPApproveHandler(r *http.Request) interface{} {
handlers.backend.AOPPApprove()
return nil
}

func (handlers *Handlers) postCancelConnectKeystoreHandler(r *http.Request) (interface{}, error) {
handlers.backend.CancelConnectKeystore()
return nil, nil
}
26 changes: 26 additions & 0 deletions frontends/web/src/api/backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import { AccountCode, CoinCode } from './account';
import { apiGet, apiPost } from '../utils/request';
import { FailResponse, SuccessResponse } from './response';
import { TSubscriptionCallback, subscribeEndpoint } from './subscribe';

export interface ICoin {
coinCode: CoinCode;
Expand Down Expand Up @@ -74,3 +75,28 @@ export const getDefaultConfig = (): Promise<any> => {
export const socksProxyCheck = (proxyAddress: string): Promise<ISuccess> => {
return apiPost('socksproxy/check', proxyAddress);
};

export type TSyncConnectKeystore = null | {
typ: 'connect';
keystoreName: string;
};

/**
* Returns a function that subscribes a callback on a "connect-keystore".
* Meant to be used with `useSubscribe`.
*/
export const syncConnectKeystore = () => {
return (
cb: TSubscriptionCallback<TSyncConnectKeystore>
) => {
return subscribeEndpoint('connect-keystore', (
obj: TSyncConnectKeystore,
) => {
cb(obj);
});
};
};

export const cancelConnectKeystore = (): Promise<void> => {
return apiPost('cancel-connect-keystore');
};
2 changes: 2 additions & 0 deletions frontends/web/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { Alert } from './components/alert/Alert';
import { Aopp } from './components/aopp/aopp';
import { Banner } from './components/banner/banner';
import { Confirm } from './components/confirm/Confirm';
import { KeystoreConnectPrompt } from './components/keystoreconnectprompt';
import { panelStore } from './components/sidebar/sidebar';
import { MobileDataWarning } from './components/mobiledatawarning';
import { Sidebar, toggleSidebar } from './components/sidebar/sidebar';
Expand Down Expand Up @@ -208,6 +209,7 @@ class App extends Component<Props, State> {
<Banner msgKey="bitbox02" />
<MobileDataWarning />
<Aopp />
<KeystoreConnectPrompt />
{
Object.entries(devices).map(([deviceID, productName]) => {
if (productName === 'bitbox02') {
Expand Down
41 changes: 41 additions & 0 deletions frontends/web/src/components/keystoreconnectprompt.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/**
* Copyright 2023 Shift Crypto AG
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { useTranslation } from 'react-i18next';
import { cancelConnectKeystore, syncConnectKeystore, TSyncConnectKeystore } from '../api/backend';
import { useSubscribe } from '../hooks/api';

export function KeystoreConnectPrompt() {
const { t } = useTranslation();
const data: undefined | TSyncConnectKeystore = useSubscribe(syncConnectKeystore());
if (!data) {
return null;
}
switch (data.typ) {
case 'connect':
return (
<>
{ data.keystoreName === '' ?
t('connectKeystore.promptNoName') :
t('connectKeystore.promptWithName', { name: data.keystoreName })
}
<button onClick={() => cancelConnectKeystore()}>{t('dialog.cancel')}</button>
</>
);
default:
return null;
}
}
4 changes: 4 additions & 0 deletions frontends/web/src/locales/en/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,10 @@
"infoWhenPaired": "First on the paired mobile and then your BitBox"
},
"confirmOnDevice": "Please confirm on your device.",
"connectKeystore": {
"promptNoName": "Please connect your BitBox02",
"promptWithName": "Please connect your BitBox02 named {{name}}"
},
"darkmode": {
"toggle": "Dark mode"
},
Expand Down

0 comments on commit 8e71687

Please sign in to comment.