UNPKG

edge-core-js

Version:

Edge account & wallet management library

183 lines (164 loc) 4.24 kB
import { base32 } from 'rfc4648' import { asOtpResetPayload, wasChangeOtpPayload } from '../../types/server-cleaners' import { totp } from '../../util/crypto/hotp' import { applyKit, serverLogin } from '../login/login' import { loginFetch } from './login-fetch' import { getStashById, hashUsername } from './login-selectors' import { saveStash } from './login-stash' /** * Gets the current OTP for a logged-in account. */ export function getLoginOtp(login) { if (login.otpKey != null) return totp(login.otpKey) } /** * Gets the current OTP from either the disk storage or login options. */ export function getStashOtp( stash, opts ) { const { otp, otpKey } = opts if (otp != null) return otp if (otpKey != null) return totp(base32.parse(otpKey, { loose: true })) if (stash.otpKey != null) return totp(stash.otpKey) } export async function enableOtp( ai, accountId, otpTimeout ) { const { loginTree, sessionKey } = ai.props.state.accounts[accountId] const { otpKey = ai.props.io.random(10) } = loginTree const kit = { loginId: loginTree.loginId, server: wasChangeOtpPayload({ otpKey, otpTimeout }), serverPath: '/v2/login/otp', stash: { otpKey, otpResetDate: undefined, otpTimeout } } await applyKit(ai, sessionKey, kit) } /** * Enable a temporary OTP key. * This is used when we're in duress mode. * @param ai - The API input. * @param accountId - The account ID. */ export async function enableTempOtp( ai, accountId ) { const { stashTree } = ai.props.state.accounts[accountId] const otpKey = ai.props.io.random(10) await saveStash(ai, { ...stashTree, otpKey }) } export async function disableOtp( ai, accountId ) { const { loginTree, sessionKey } = ai.props.state.accounts[accountId] const kit = { loginId: loginTree.loginId, server: undefined, serverMethod: 'DELETE', serverPath: '/v2/login/otp', stash: { otpKey: undefined, otpResetDate: undefined, otpTimeout: undefined } } await applyKit(ai, sessionKey, kit) } /** * Disable a temporary OTP key. * This is used when we're in duress mode. * @param ai - The API input. * @param accountId - The account ID. */ export async function disableTempOtp( ai, accountId ) { const { stashTree } = ai.props.state.accounts[accountId] await saveStash(ai, { ...stashTree, otpKey: undefined }) } export async function cancelOtpReset( ai, accountId ) { const { loginTree, sessionKey } = ai.props.state.accounts[accountId] const { otpTimeout, otpKey } = loginTree if (otpTimeout == null || otpKey == null) { throw new Error('Cannot cancel 2FA reset: 2FA is not enabled.') } const kit = { loginId: loginTree.loginId, server: wasChangeOtpPayload({ otpTimeout, otpKey }), serverPath: '/v2/login/otp', stash: { otpResetDate: undefined } } await applyKit(ai, sessionKey, kit) } /** * Requests an OTP reset. */ export async function resetOtp( ai, username, resetToken ) { const request = { userId: await hashUsername(ai, username), otpResetAuth: resetToken } const reply = await loginFetch(ai, 'DELETE', '/v2/login/otp', request) const { otpResetDate } = asOtpResetPayload(reply) return otpResetDate } /** * If the device doesn't have the right OTP key, * this can prevent most things from working. * Let the user provide an updated key, and present that to the server. * If the key works, the server will let us in & resolve the issue. */ export async function repairOtp( ai, accountId, otpKey ) { if (ai.props.state.accounts[accountId] == null) return const { login } = ai.props.state.accounts[accountId] const { userId, passwordAuth } = login const { stashTree, stash } = getStashById(ai, login.loginId) if (passwordAuth == null || userId == null) { throw new Error('Cannot repair OTP: There is no password on this account') } const request = { userId, passwordAuth, otp: totp(otpKey) } const opts = { // Avoid updating the lastLogin date: now: stashTree.lastLogin } await serverLogin(ai, stashTree, stash, opts, request, async () => { return login.loginKey }) }