diff --git a/src/core/account/account-api.ts b/src/core/account/account-api.ts index d4718cc1..f1045b65 100644 --- a/src/core/account/account-api.ts +++ b/src/core/account/account-api.ts @@ -330,15 +330,18 @@ export function makeAccountApi(ai: ApiInput, accountId: string): EdgeAccount { return await checkPassword(ai, stashTree, password, loginKey) }, - async checkPin(pin: string): Promise { + async checkPin( + pin: string, + opts: { forDuressAccount?: boolean } = {} + ): Promise { lockdown() const { login, loginTree } = accountState() // Try to check the PIN locally, then fall back on the server: - if (login.pin != null) { + if (login.pin != null && opts.forDuressAccount == null) { return pin === login.pin } else { - return await checkPin2(ai, loginTree, pin) + return await checkPin2(ai, loginTree, pin, opts.forDuressAccount) } }, diff --git a/src/core/login/pin2.ts b/src/core/login/pin2.ts index 9ddf71fe..a6527e6d 100644 --- a/src/core/login/pin2.ts +++ b/src/core/login/pin2.ts @@ -161,18 +161,37 @@ export async function changePin( /** * Returns true if the given pin is correct. + * + * @param loginTree - The root login tree to check the pin for. + * @param pin - The pin to check. + * @param forDuressAccount - If true, check the pin for the duress account. + * @return A `Promise` for the result of the pin check. */ export async function checkPin2( ai: ApiInput, - login: LoginTree, - pin: string + loginTree: LoginTree, + pin: string, + forDuressAccount?: boolean ): Promise { - const { appId, loginId, username } = login + const { loginId, username } = loginTree if (username == null) return false + // This will never be `.duress.duress` because `loginTree` is the root. + const appId = + forDuressAccount === true ? loginTree.appId + '.duress' : loginTree.appId + + // Special case: checking pin against theoretical, non-existent + // '.duress.duress' account should always be faked as `false`. + if (ai.props.state.clientInfo.duressEnabled) { + return false + } + // Find the stash to use: const { stashTree } = getStashById(ai, loginId) - const stash = findPin2Stash(stashTree, appId) + const stash = + forDuressAccount === true + ? findPin2StashDuress(stashTree, appId) + : findPin2Stash(stashTree, appId) if (stash == null || stash.pin2Key == null) { throw new PinDisabledError('No PIN set locally for this account') } @@ -182,7 +201,7 @@ export async function checkPin2( const request: LoginRequestBody = { pin2Id: makePin2Id(pin2Key, username), pin2Auth: makePin2Auth(pin2Key, pin), - otp: getLoginOtp(login) + otp: getLoginOtp(loginTree) } return await loginFetch(ai, 'POST', '/v2/login', request).then( good => true, diff --git a/src/types/types.ts b/src/types/types.ts index f1cf4717..c9366050 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -1729,7 +1729,10 @@ export interface EdgeAccount { // Verify existing credentials: readonly checkPassword: (password: string) => Promise - readonly checkPin: (pin: string) => Promise + readonly checkPin: ( + pin: string, + opts?: { forDuressAccount?: boolean } + ) => Promise readonly getPin: () => Promise // Remove credentials: diff --git a/test/core/login/login.test.ts b/test/core/login/login.test.ts index f7957131..2d1da66a 100644 --- a/test/core/login/login.test.ts +++ b/test/core/login/login.test.ts @@ -522,6 +522,39 @@ describe('pin', function () { expect(await account.checkPin(fakeUser.pin + '!')).equals(false) }) + it('check for duress', async function () { + const world = await makeFakeEdgeWorld([fakeUser], quiet) + const context = await world.makeEdgeContext(contextOptions) + const account = await context.loginWithPIN(fakeUser.username, fakeUser.pin) + await account.changePin({ pin: '0000', forDuressAccount: true }) + + expect(await account.checkPin(fakeUser.pin)).equals(true) + expect(await account.checkPin('0000', { forDuressAccount: true })).equals( + true + ) + expect(await account.checkPin('1111', { forDuressAccount: true })).equals( + false + ) + }) + + it('check for duress while in duress', async function () { + const world = await makeFakeEdgeWorld([fakeUser], quiet) + const context = await world.makeEdgeContext(contextOptions) + const account = await context.loginWithPIN(fakeUser.username, fakeUser.pin) + await account.changePin({ pin: '0000', forDuressAccount: true }) + await account.logout() + const duressAccount = await context.loginWithPIN(fakeUser.username, '0000') + + expect(await duressAccount.checkPin('0000')).equals(true) + expect(await duressAccount.checkPin(fakeUser.pin)).equals(false) + expect( + await duressAccount.checkPin('0000', { forDuressAccount: true }) + ).equals(false) + expect( + await duressAccount.checkPin('1111', { forDuressAccount: true }) + ).equals(false) + }) + it('check duress', async function () { const world = await makeFakeEdgeWorld([fakeUser], quiet) const context = await world.makeEdgeContext(contextOptions)