edge-core-js
Version:
Edge account & wallet management library
214 lines (146 loc) • 4.08 kB
JavaScript
import {
asArray,
asCodec,
asDate,
asNumber,
asObject,
asOptional,
asString,
uncleaner
} from 'cleaners'
import { justFiles } from 'disklet'
import { base64 } from 'rfc4648'
import { fixUsername } from '../../client-side'
import {
asBase32,
asBase64,
asEdgeBox,
asEdgeKeyBox,
asEdgePendingVoucher,
asEdgeSnrp
} from '../../types/server-cleaners'
import { verifyData } from '../../util/crypto/verify'
import { base58 } from '../../util/encoding'
import { searchTree } from './login'
/**
* The login data we store on disk.
*/
/**
* Reads all login stashes from disk.
*/
export async function loadStashes(
disklet,
log
) {
const out = []
const paths = await disklet.list('logins').then(justFiles)
for (const path of paths) {
try {
out.push(asLoginStash(JSON.parse(await disklet.getText(path))))
} catch (error) {
log.error(`Could not load ${path}: ${String(error)}`)
}
}
return out
}
/**
* Removes any login stash that may be stored for the given username.
*/
export async function removeStash(
ai,
loginId
) {
const { dispatch, io } = ai.props
const paths = await io.disklet.list('logins').then(justFiles)
for (const path of paths) {
try {
const stash = asLoginStash(JSON.parse(await io.disklet.getText(path)))
if (verifyData(stash.loginId, loginId)) await io.disklet.delete(path)
} catch (error) {}
}
dispatch({
type: 'LOGIN_STASH_DELETED',
payload: loginId
})
}
/**
* Saves a login stash tree to disk.
*/
export async function saveStash(
ai,
stashTree
) {
const { dispatch, io } = ai.props
const { appId, loginId } = stashTree
if (appId !== '') {
throw new Error('Cannot save a login without an appId.')
}
if (loginId == null || loginId.length !== 32) {
throw new Error('Invalid loginId')
}
await io.disklet.setText(
`logins/${base58.stringify(loginId)}.json`,
JSON.stringify(wasLoginStash(stashTree))
)
dispatch({ type: 'LOGIN_STASH_SAVED', payload: stashTree })
}
const asUsername = raw => fixUsername(asString(raw))
export const asLoginStash = asObject({
// Identity:
appId: asString,
created: asOptional(asDate),
lastLogin: asOptional(asDate),
loginId: asBase64,
// 2-factor:
otpKey: asOptional(asBase32),
otpResetDate: asOptional(asDate),
otpTimeout: asOptional(asNumber),
pendingVouchers: asOptional(asArray(asEdgePendingVoucher), () => []),
voucherId: asOptional(asString),
voucherAuth: asOptional(asBase64),
// Return logins:
loginAuthBox: asOptional(asEdgeBox),
parentBox: asOptional(asEdgeBox),
// Password login:
passwordAuthBox: asOptional(asEdgeBox),
passwordAuthSnrp: asOptional(asEdgeSnrp),
passwordBox: asOptional(asEdgeBox),
passwordKeySnrp: asOptional(asEdgeSnrp),
// PIN v2 login:
pin2Key: asOptional(
asCodec(
// Legacy Airbitz can wrongly send this in base58 for Edge login:
raw => {
const clean = asString(raw)
return raw.slice(-1) !== '=' ? base58.parse(clean) : base64.parse(clean)
},
clean => base64.stringify(clean)
)
),
pin2TextBox: asOptional(asEdgeBox),
// Recovery v2 login:
recovery2Key: asOptional(asBase64),
// Username:
userId: asOptional(asBase64),
username: asOptional(asUsername),
// Keys and assorted goodies:
children: asOptional(asArray(raw => asLoginStash(raw))),
keyBoxes: asOptional(asArray(asEdgeKeyBox)),
mnemonicBox: asOptional(asEdgeBox),
rootKeyBox: asOptional(asEdgeBox),
syncKeyBox: asOptional(asEdgeBox)
})
export const wasLoginStash = uncleaner(asLoginStash)
/**
* Returns the duress stash nested within a given LoginStash tree.
* This will return the duress stash even if the stashTree is the duress stash
* itself.
*/
export function findDuressStash(
stashTree,
appId
) {
const duressAppId = appId.endsWith('.duress') ? appId : appId + '.duress'
if (stashTree.appId === duressAppId) return stashTree
return searchTree(stashTree, stash => stash.appId === duressAppId)
}