Skip to content

Commit

Permalink
Merge pull request #843 from airgap-it/fix/user_deactivated
Browse files Browse the repository at this point in the history
fix: account deactivated
  • Loading branch information
jsamol authored Jan 23, 2025
2 parents c736001 + 1b97b9b commit 3f9a08b
Show file tree
Hide file tree
Showing 6 changed files with 116 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export abstract class BeaconClient {
/**
* This method initializes the SDK by setting some values in the storage and generating a keypair.
*/
private async initSDK(): Promise<void> {
protected async initSDK(): Promise<void> {
this.storage.set(StorageKey.BEACON_SDK_VERSION, SDK_VERSION).catch(console.error)

this.loadOrCreateBeaconSecret().catch(console.error)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { ClientEvents } from './ClientEvents'
*
*/
export abstract class CommunicationClient {
constructor(protected readonly keyPair?: KeyPair) {}
constructor(protected keyPair?: KeyPair) {}

public eventHandlers: Map<ClientEvents, Function> = new Map()

Expand Down
73 changes: 49 additions & 24 deletions packages/beacon-dapp/src/dapp-client/DAppClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,8 @@ import {
signMessage,
CONTRACT_PREFIX,
prefixPublicKey,
isValidAddress
isValidAddress,
getKeypairFromSeed
} from '@airgap/beacon-utils'
import { messageEvents } from '../beacon-message-events'
import { BlockExplorer } from '../utils/block-explorer'
Expand Down Expand Up @@ -264,7 +265,9 @@ export class DAppClient extends Client {
this.storage.subscribeToStorageChanged(async (event) => {
if (event.eventType === 'storageCleared') {
this.setActiveAccount(undefined)
} else if (event.eventType === 'entryModified') {
return
}
if (event.eventType === 'entryModified') {
if (event.key === this.storage.getPrefixedKey(StorageKey.ACTIVE_ACCOUNT)) {
const accountIdentifier = event.newValue
if (!accountIdentifier || accountIdentifier === 'undefined') {
Expand All @@ -273,8 +276,17 @@ export class DAppClient extends Client {
const account = await this.getAccount(accountIdentifier)
this.setActiveAccount(account)
}
} else if (event.key === this.storage.getPrefixedKey(StorageKey.ENABLE_METRICS)) {
return
}
if (event.key === this.storage.getPrefixedKey(StorageKey.ENABLE_METRICS)) {
this.enableMetrics = !!(await this.storage.get(StorageKey.ENABLE_METRICS))
return
}
if (event.key === this.storage.getPrefixedKey(StorageKey.BEACON_SDK_SECRET_SEED)) {
this._keyPair = new ExposedPromise()
this._beaconId = new ExposedPromise()
await this.initSDK()
return
}
}
})
Expand All @@ -292,8 +304,9 @@ export class DAppClient extends Client {
}
})
.catch(async (storageError) => {
await this.setActiveAccount(undefined)
logger.error(storageError)
await this.resetInvalidState(false)
this.events.emit(BeaconEvent.INVALID_ACCOUNT_DEACTIVATED)
return undefined
})

Expand Down Expand Up @@ -555,7 +568,11 @@ export class DAppClient extends Client {
}

public async initInternalTransports(): Promise<void> {
const keyPair = await this.keyPair
const seed = await this.storage.get(StorageKey.BEACON_SDK_SECRET_SEED)
if (!seed) {
throw new Error('Secret seed not found')
}
const keyPair = await getKeypairFromSeed(seed)

if (this.postMessageTransport || this.p2pTransport || this.walletConnectTransport) {
return
Expand Down Expand Up @@ -785,33 +802,41 @@ export class DAppClient extends Client {
console.error(error)
})

const abortHandler = async () => {
logger.log('init', 'ABORTED')
this.sendMetrics(
'performance-metrics/save',
await this.buildPayload('connect', 'abort')
)
await Promise.all([
postMessageTransport.disconnect(),
// p2pTransport.disconnect(), do not abort connection manually
walletConnectTransport.disconnect()
])
this.postMessageTransport = this.walletConnectTransport = this.p2pTransport = undefined
this._activeAccount.isResolved() && this.clearActiveAccount()
this._initPromise = undefined
}

this.events
.emit(BeaconEvent.PAIR_INIT, {
p2pPeerInfo: () => {
p2pTransport.connect().then().catch(console.error)
p2pTransport
.connect()
.then()
.catch((err) => {
logger.error(err)
if (err.message === 'The account is deactivated.') {
this.hideUI(['alert'])
abortHandler()
}
})
return p2pTransport.getPairingRequestInfo()
},
postmessagePeerInfo: () => postMessageTransport.getPairingRequestInfo(),
walletConnectPeerInfo: () => walletConnectTransport.getPairingRequestInfo(),
networkType: this.network.type,
abortedHandler: async () => {
logger.log('init', 'ABORTED')
this.sendMetrics(
'performance-metrics/save',
await this.buildPayload('connect', 'abort')
)
await Promise.all([
postMessageTransport.disconnect(),
// p2pTransport.disconnect(), do not abort connection manually
walletConnectTransport.disconnect()
])
this.postMessageTransport =
this.walletConnectTransport =
this.p2pTransport =
undefined
this._activeAccount.isResolved() && this.clearActiveAccount()
this._initPromise = undefined
},
abortedHandler: abortHandler.bind(this),
disclaimerText: this.disclaimerText,
analytics: this.analytics,
featuredWallets: this.featuredWallets
Expand Down
20 changes: 18 additions & 2 deletions packages/beacon-dapp/src/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ export enum BeaconEvent {
SHOW_PREPARE = 'SHOW_PREPARE',
HIDE_UI = 'HIDE_UI',
INVALID_ACTIVE_ACCOUNT_STATE = 'INVALID_ACTIVE_ACCOUNT_STATE',
INVALID_ACCOUNT_DEACTIVATED = 'INVALID_ACCOUNT_DEACTIVATED',
PAIR_INIT = 'PAIR_INIT',
PAIR_SUCCESS = 'PAIR_SUCCESS',
CHANNEL_CLOSED = 'CHANNEL_CLOSED',
Expand Down Expand Up @@ -195,7 +196,8 @@ export interface BeaconEventType {
[BeaconEvent.NO_PERMISSIONS]: undefined
[BeaconEvent.ACTIVE_ACCOUNT_SET]: AccountInfo
[BeaconEvent.ACTIVE_TRANSPORT_SET]: Transport
[BeaconEvent.INVALID_ACTIVE_ACCOUNT_STATE]: undefined
[BeaconEvent.INVALID_ACTIVE_ACCOUNT_STATE]: undefined
[BeaconEvent.INVALID_ACCOUNT_DEACTIVATED]: undefined
[BeaconEvent.SHOW_PREPARE]: { walletInfo?: WalletInfo }
[BeaconEvent.HIDE_UI]: ('alert' | 'toast')[] | undefined
[BeaconEvent.PAIR_INIT]: {
Expand Down Expand Up @@ -314,7 +316,7 @@ const showNoPermissionAlert = async (): Promise<void> => {
}

/**
* Show a
* Show an "Invalid state" alert
*/
const showInvalidActiveAccountState = async (): Promise<void> => {
await openAlert({
Expand All @@ -324,6 +326,16 @@ const showInvalidActiveAccountState = async (): Promise<void> => {
})
}

/**
* Show an "account deactivated" error alert
*/
const showInvalidAccountDeactivated = async (): Promise<void> => {
await openAlert({
title: 'Error',
body: `Your session has expired. Please pair with your wallet again.`
})
}

/**
* Show an error toast
*
Expand Down Expand Up @@ -756,6 +768,7 @@ export const defaultEventCallbacks: {
[BeaconEvent.ACTIVE_ACCOUNT_SET]: emptyHandler(),
[BeaconEvent.ACTIVE_TRANSPORT_SET]: emptyHandler(),
[BeaconEvent.INVALID_ACTIVE_ACCOUNT_STATE]: showInvalidActiveAccountState,
[BeaconEvent.INVALID_ACCOUNT_DEACTIVATED]: showInvalidAccountDeactivated,
[BeaconEvent.SHOW_PREPARE]: showPrepare,
[BeaconEvent.HIDE_UI]: hideUI,
[BeaconEvent.PAIR_INIT]: showPairAlert,
Expand Down Expand Up @@ -816,6 +829,9 @@ export class BeaconEventHandler {
[BeaconEvent.INVALID_ACTIVE_ACCOUNT_STATE]: [
defaultEventCallbacks.INVALID_ACTIVE_ACCOUNT_STATE
],
[BeaconEvent.INVALID_ACCOUNT_DEACTIVATED]: [
defaultEventCallbacks.INVALID_ACCOUNT_DEACTIVATED
],
[BeaconEvent.SHOW_PREPARE]: [defaultEventCallbacks.SHOW_PREPARE],
[BeaconEvent.HIDE_UI]: [defaultEventCallbacks.HIDE_UI],
[BeaconEvent.PAIR_INIT]: [defaultEventCallbacks.PAIR_INIT],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import {
encryptCryptoboxPayload,
decryptCryptoboxPayload,
secretbox_NONCEBYTES,
secretbox_MACBYTES
secretbox_MACBYTES,
getKeypairFromSeed
} from '@airgap/beacon-utils'
import { MatrixClient } from '../matrix-client/MatrixClient'
import {
Expand Down Expand Up @@ -387,8 +388,15 @@ export class P2PCommunicationClient extends CommunicationClient {
password: `ed:${toHex(rawSignature)}:${await this.getPublicKey()}`,
deviceId: toHex(this.keyPair!.publicKey)
})
} catch (error) {
logger.error('start', 'Could not log in, retrying')
} catch (error: any) {
logger.error('start', 'Could not log in, retrying', error)

if (error.errcode === 'M_USER_DEACTIVATED') {
await this.generateNewKeyPair()
await this.reset()
throw new Error('The account is deactivated.')
}

await this.reset() // If we can't log in, let's reset
if (!this.selectedRegion) {
throw new Error('No region selected.')
Expand Down Expand Up @@ -426,6 +434,7 @@ export class P2PCommunicationClient extends CommunicationClient {
await this.storage.delete(StorageKey.MATRIX_PEER_ROOM_IDS).catch((error) => logger.log(error))
await this.storage.delete(StorageKey.MATRIX_PRESERVED_STATE).catch((error) => logger.log(error))
await this.storage.delete(StorageKey.MATRIX_SELECTED_NODE).catch((error) => logger.log(error))

// Instead of resetting everything, maybe we should make sure a new instance is created?
this.relayServer = undefined
this.client = new ExposedPromise()
Expand Down Expand Up @@ -779,6 +788,16 @@ export class P2PCommunicationClient extends CommunicationClient {
)
}

private async generateNewKeyPair() {
const newSeed = await generateGUID()

console.warn(`The current user ID has been deactivated. Generating new ID: ${newSeed}`)

this.storage.set(StorageKey.BEACON_SDK_SECRET_SEED, newSeed)

this.keyPair = await getKeypairFromSeed(newSeed)
}

private async getRelevantRoom(recipient: string): Promise<string> {
const roomIds = await this.storage.get(StorageKey.MATRIX_PEER_ROOM_IDS)
let roomId = roomIds[recipient]
Expand Down
40 changes: 25 additions & 15 deletions packages/beacon-wallet/src/client/WalletClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,24 +235,34 @@ export class WalletClient extends Client {
/**
* The method will attempt to initiate a connection using the active transport.
*/
public async _connect(): Promise<void> {
public async _connect(attempts: number = 3): Promise<void> {
const transport: WalletP2PTransport = (await this.transport) as WalletP2PTransport
if (transport.connectionStatus === TransportStatus.NOT_CONNECTED) {
if (attempts == 0 || transport.connectionStatus !== TransportStatus.NOT_CONNECTED) {
return
}

try {
await transport.connect()
transport
.addListener(async (message: unknown, connectionInfo: ConnectionContext) => {
if (typeof message === 'string') {
const deserializedMessage = (await new Serializer().deserialize(
message
)) as BeaconRequestMessage
this.handleResponse(deserializedMessage, connectionInfo)
}
})
.catch((error) => logger.log('_connect', error))
this._isConnected.resolve(true)
} else {
// NO-OP
} catch (err: any) {
logger.warn('_connect', err.message)
if (err.message === 'The account is deactivated.') {
await transport.disconnect()
await this._connect(--attempts)
return
}
}

transport
.addListener(async (message: unknown, connectionInfo: ConnectionContext) => {
if (typeof message === 'string') {
const deserializedMessage = (await new Serializer().deserialize(
message
)) as BeaconRequestMessage
this.handleResponse(deserializedMessage, connectionInfo)
}
})
.catch((error) => logger.log('_connect', error))
this._isConnected.resolve(true)
}

/**
Expand Down

0 comments on commit 3f9a08b

Please sign in to comment.