Skip to content

Commit

Permalink
Simplify makeAccount arguments
Browse files Browse the repository at this point in the history
We just require the decryption keys now.
  • Loading branch information
swansontec committed Sep 6, 2024
1 parent 9a716da commit c488643
Show file tree
Hide file tree
Showing 20 changed files with 339 additions and 326 deletions.
18 changes: 9 additions & 9 deletions src/core/account/account-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -383,13 +383,13 @@ export function makeAccountApi(ai: ApiInput, accountId: string): EdgeAccount {
},

async createWallet(walletType: string, keys?: object): Promise<string> {
const { sessionKey } = accountState()

// For crash errors:
ai.props.log.breadcrumb('EdgeAccount.createWallet', {})

const { login, loginTree } = accountState()

const walletInfo = await makeCurrencyWalletKeys(ai, walletType, { keys })
await applyKit(ai, loginTree, makeKeysKit(ai, login, [walletInfo]))
await applyKit(ai, sessionKey, makeKeysKit(ai, sessionKey, [walletInfo]))
return walletInfo.id
},

Expand Down Expand Up @@ -499,13 +499,13 @@ export function makeAccountApi(ai: ApiInput, accountId: string): EdgeAccount {
walletType: string,
opts: EdgeCreateCurrencyWalletOptions = {}
): Promise<EdgeCurrencyWallet> {
const { sessionKey } = accountState()

// For crash errors:
ai.props.log.breadcrumb('EdgeAccount.createCurrencyWallet', {})

const { login, loginTree } = accountState()

const walletInfo = await makeCurrencyWalletKeys(ai, walletType, opts)
await applyKit(ai, loginTree, makeKeysKit(ai, login, [walletInfo]))
await applyKit(ai, sessionKey, makeKeysKit(ai, sessionKey, [walletInfo]))
return await finishWalletCreation(ai, accountId, walletInfo.id, opts)
},

Expand All @@ -524,11 +524,11 @@ export function makeAccountApi(ai: ApiInput, accountId: string): EdgeAccount {
async createCurrencyWallets(
createWallets: EdgeCreateCurrencyWallet[]
): Promise<Array<EdgeResult<EdgeCurrencyWallet>>> {
const { sessionKey } = accountState()

// For crash errors:
ai.props.log.breadcrumb('EdgeAccount.makeMemoryWallet', {})

const { login, loginTree } = accountState()

// Create the keys:
const walletInfos = await Promise.all(
createWallets.map(
Expand All @@ -537,7 +537,7 @@ export function makeAccountApi(ai: ApiInput, accountId: string): EdgeAccount {
)

// Store the keys on the server:
await applyKit(ai, loginTree, makeKeysKit(ai, login, walletInfos))
await applyKit(ai, sessionKey, makeKeysKit(ai, sessionKey, walletInfos))

// Set up options:
return await Promise.all(
Expand Down
129 changes: 61 additions & 68 deletions src/core/account/account-init.ts
Original file line number Diff line number Diff line change
@@ -1,61 +1,55 @@
import { base64 } from 'rfc4648'

import { EdgeAccount, EdgeAccountOptions } from '../../types/types'
import { LoginCreateOpts, makeCreateKit } from '../login/create'
import { decryptText } from '../../util/crypto/crypto'
import { makeCreateKit } from '../login/create'
import {
findFirstKey,
makeAccountType,
makeKeyInfo,
makeKeysKit
} from '../login/keys'
import { applyKit, searchTree } from '../login/login'
import {
applyKit,
decryptChildKey,
decryptKeyInfos,
searchTree
} from '../login/login'
import { getStashById } from '../login/login-selectors'
import { LoginStash } from '../login/login-stash'
import { LoginKit, LoginTree, LoginType } from '../login/login-types'
import { LoginKit, LoginType, SessionKey } from '../login/login-types'
import { createStorageKeys, wasEdgeStorageKeys } from '../login/storage-keys'
import { ApiInput, RootProps } from '../root-pixie'

function checkLogin(login: LoginTree): void {
if (login == null || login.loginKey == null) {
throw new Error('Incomplete login')
}
}

export function findAppLogin(loginTree: LoginTree, appId: string): LoginTree {
const out = searchTree(loginTree, login => login.appId === appId)
if (out == null) {
throw new Error(`Internal error: cannot find login for ${appId}`)
}
return out
}

/**
* Creates a child login under the provided login, with the given appId.
*/
async function createChildLogin(
ai: ApiInput,
loginTree: LoginTree,
login: LoginTree,
stashTree: LoginStash,
sessionKey: SessionKey,
appId: string
): Promise<LoginTree> {
checkLogin(login)

const opts: LoginCreateOpts = {
pin: loginTree.pin,
username: loginTree.username
): Promise<LoginStash> {
let pin: string | undefined
if (stashTree.pin2TextBox != null) {
pin = decryptText(stashTree.pin2TextBox, sessionKey.loginKey)
}
opts.keyInfo = makeKeyInfo(
makeAccountType(appId),
wasEdgeStorageKeys(createStorageKeys(ai))
)
const kit = await makeCreateKit(ai, login, appId, opts)

const { kit } = await makeCreateKit(ai, sessionKey, appId, {
keyInfo: makeKeyInfo(
makeAccountType(appId),
wasEdgeStorageKeys(createStorageKeys(ai))
),
pin,
username: stashTree.username
})
const parentKit: LoginKit = {
login: { children: [kit.login as LoginTree] },
loginId: login.loginId,
loginId: sessionKey.loginId,
server: kit.server,
serverPath: kit.serverPath,
stash: { children: [kit.stash as LoginStash] }
}
return await applyKit(ai, loginTree, parentKit)
return await applyKit(ai, sessionKey, parentKit)
}

/**
Expand All @@ -65,74 +59,76 @@ async function createChildLogin(
*/
export async function ensureAccountExists(
ai: ApiInput,
loginTree: LoginTree,
stashTree: LoginStash,
sessionKey: SessionKey,
appId: string
): Promise<LoginTree> {
// For crash errors:
ai.props.log.breadcrumb('ensureAccountExists', {})

const accountType = makeAccountType(appId)
): Promise<LoginStash> {
const { log } = ai.props

// If there is no app login, make that:
const login = searchTree(loginTree, login => login.appId === appId)
if (login == null) {
const appStash = searchTree(stashTree, stash => stash.appId === appId)
if (appStash == null) {
// For crash errors:
ai.props.log.breadcrumb('createChildLogin', {})
return await createChildLogin(ai, loginTree, loginTree, appId)

return await createChildLogin(ai, stashTree, sessionKey, appId)
}

// Otherwise, make the repo:
if (findFirstKey(login.keyInfos, accountType) == null) {
// Decrypt the wallet keys:
// TODO: Once we cache public keys, use those instead:
const appKey = decryptChildKey(stashTree, sessionKey, appStash.loginId)
const keyInfos = decryptKeyInfos(appStash, appKey.loginKey)
log.warn(
`Login: decrypted keys for user ${base64.stringify(stashTree.loginId)}`
)

// If the account has no repo, make one:
const accountType = makeAccountType(appId)
if (findFirstKey(keyInfos, accountType) == null) {
// For crash errors:
ai.props.log.breadcrumb('createAccountRepo', {})
checkLogin(login)

const keyInfo = makeKeyInfo(
accountType,
wasEdgeStorageKeys(createStorageKeys(ai))
)
const keysKit = makeKeysKit(ai, login, [keyInfo])
return await applyKit(ai, loginTree, keysKit)
const keysKit = makeKeysKit(ai, appKey, [keyInfo])
return await applyKit(ai, sessionKey, keysKit)
}

// Everything is fine, so do nothing:
return loginTree
return stashTree
}

/**
* Creates an `EdgeAccount` API object.
*/
export async function makeAccount(
ai: ApiInput,
appId: string,
loginTree: LoginTree,
sessionKey: SessionKey,
loginType: LoginType,
opts: EdgeAccountOptions
): Promise<EdgeAccount> {
// For crash errors:
ai.props.log.breadcrumb('makeAccount', {})

const { pauseWallets = false } = opts
const { appId } = ai.props.state.login
const { log } = ai.props
log.warn(
`Login: decrypted keys for user ${base64.stringify(loginTree.loginId)}`
)

loginTree = await ensureAccountExists(ai, loginTree, appId)
// For crash errors:
ai.props.log.breadcrumb('makeAccount', {})

// Create the loginTree:
const { stashTree } = getStashById(ai, sessionKey.loginId)
await ensureAccountExists(ai, stashTree, sessionKey, appId)
log.warn('Login: account exists for appId')

// Add the login to redux:
const hasRootKey = loginTree.loginKey != null
ai.props.dispatch({
type: 'LOGIN',
payload: {
appId,
hasRootKey,
loginKey: hasRootKey
? loginTree.loginKey
: findAppLogin(loginTree, appId).loginKey,
loginType,
pauseWallets,
rootLoginId: loginTree.loginId
rootLoginId: stashTree.loginId,
sessionKey
}
})

Expand All @@ -142,10 +138,7 @@ export async function makeAccount(
/**
* Waits for the account API to appear and returns it.
*/
export function waitForAccount(
ai: ApiInput,
accountId: string
): Promise<EdgeAccount> {
function waitForAccount(ai: ApiInput, accountId: string): Promise<EdgeAccount> {
const out: Promise<EdgeAccount> = ai.waitFor(
(props: RootProps): EdgeAccount | undefined => {
const accountState = props.state.accounts[accountId]
Expand Down
4 changes: 2 additions & 2 deletions src/core/account/account-pixie.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,8 @@ const accountPixie: TamePixie<AccountProps> = combinePixies({
const ai = toApiInput(input)
const { accountId } = input.props
if (input.props.state.accounts[accountId] == null) return
const { login, loginTree } = input.props.state.accounts[accountId]
await syncLogin(ai, loginTree, login)
const { sessionKey } = input.props.state.accounts[accountId]
await syncLogin(ai, sessionKey)
}

// We don't report sync failures, since that could be annoying:
Expand Down
55 changes: 28 additions & 27 deletions src/core/account/account-reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,16 @@ import { compare } from '../../util/compare'
import { verifyData } from '../../util/crypto/verify'
import { RootAction } from '../actions'
import { findFirstKey, getAllWalletInfos, makeAccountType } from '../login/keys'
import { makeLoginTree } from '../login/login'
import { makeLoginTree, searchTree } from '../login/login'
import { LoginStash } from '../login/login-stash'
import { LoginTree, LoginType, WalletInfoFullMap } from '../login/login-types'
import {
LoginTree,
LoginType,
SessionKey,
WalletInfoFullMap
} from '../login/login-types'
import { maybeFindCurrencyPluginId } from '../plugins/plugins-selectors'
import { RootState } from '../root-reducer'
import { findAppLogin } from './account-init'
import { SwapSettings } from './account-types'

export interface AccountState {
Expand All @@ -37,14 +41,12 @@ export interface AccountState {
readonly pauseWallets: boolean

// Login stuff:
readonly appId: string // Copy of the context appId
readonly hasRootKey: boolean // True if the loginKey is for the root
readonly loadFailure: Error | null // Failed to create API object.
readonly login: LoginTree
readonly loginKey: Uint8Array
readonly loginTree: LoginTree
readonly loginType: LoginType
readonly rootLoginId: Uint8Array
readonly sessionKey: SessionKey
readonly stashTree: LoginStash

// Plugin stuff:
Expand All @@ -63,10 +65,14 @@ export interface AccountNext {
}

export const initialCustomTokens: EdgePluginMap<EdgeTokenMap> = {}
const blankSessionKey = {
loginId: new Uint8Array(),
loginKey: new Uint8Array()
}

const accountInner = buildReducer<AccountState, RootAction, AccountNext>({
accountWalletInfo: memoizeReducer(
(next: AccountNext) => next.self.appId,
(next: AccountNext) => next.root.login.appId,
(next: AccountNext) => next.self.login,
(appId: string, login: LoginTree): EdgeWalletInfo => {
const type = makeAccountType(appId)
Expand All @@ -79,7 +85,7 @@ const accountInner = buildReducer<AccountState, RootAction, AccountNext>({
),

accountWalletInfos: memoizeReducer(
(next: AccountNext) => next.self.appId,
(next: AccountNext) => next.root.login.appId,
(next: AccountNext) => next.self.login,
(appId: string, login: LoginTree): EdgeWalletInfo[] => {
// Wallets created in Edge that then log into Airbitz or BitcoinPay
Expand Down Expand Up @@ -209,14 +215,6 @@ const accountInner = buildReducer<AccountState, RootAction, AccountNext>({
return action.type === 'LOGIN' ? action.payload.pauseWallets : state
},

appId(state = '', action): string {
return action.type === 'LOGIN' ? action.payload.appId : state
},

hasRootKey(state = true, action): boolean {
return action.type === 'LOGIN' ? action.payload.hasRootKey : state
},

loadFailure(state = null, action): Error | null {
if (action.type === 'ACCOUNT_LOAD_FAILED') {
const { error } = action.payload
Expand All @@ -227,22 +225,21 @@ const accountInner = buildReducer<AccountState, RootAction, AccountNext>({
},

login: memoizeReducer(
(next: AccountNext) => next.self.appId,
(next: AccountNext) => next.root.login.appId,
(next: AccountNext) => next.self.loginTree,
(appId, loginTree): LoginTree => findAppLogin(loginTree, appId)
(appId, loginTree): LoginTree => {
const out = searchTree(loginTree, login => login.appId === appId)
if (out == null) {
throw new Error(`Internal error: cannot find login for ${appId}`)
}
return out
}
),

loginKey(state = new Uint8Array(0), action): Uint8Array {
return action.type === 'LOGIN' ? action.payload.loginKey : state
},

loginTree: memoizeReducer(
(next: AccountNext) => next.self.appId,
(next: AccountNext) => next.self.loginKey,
(next: AccountNext) => next.self.hasRootKey,
(next: AccountNext) => next.self.stashTree,
(appId, loginKey, hasRootKey, stashTree): LoginTree =>
makeLoginTree(stashTree, loginKey, hasRootKey ? '' : appId)
(next: AccountNext) => next.self.sessionKey,
(stashTree, sessionKey): LoginTree => makeLoginTree(stashTree, sessionKey)
),

loginType(state = 'newAccount', action): LoginType {
Expand All @@ -253,6 +250,10 @@ const accountInner = buildReducer<AccountState, RootAction, AccountNext>({
return action.type === 'LOGIN' ? action.payload.rootLoginId : state
},

sessionKey(state = blankSessionKey, action): SessionKey {
return action.type === 'LOGIN' ? action.payload.sessionKey : state
},

stashTree: memoizeReducer(
(next: AccountNext) => next.self.rootLoginId,
(next: AccountNext) => next.root.login.stashes,
Expand Down
Loading

0 comments on commit c488643

Please sign in to comment.