Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1049,6 +1049,8 @@ Create a new encrypted-messages recovery key
beeper verify reset-recovery-key
```

Resetting the recovery key signs out every chat account connected through Beeper Cloud (WhatsApp, Telegram, Signal, …) on all your devices. You will need to reconnect them. Use only when you have lost your recovery key and have no other verified device.

Examples:

```sh
Expand Down
1 change: 1 addition & 0 deletions packages/cli/docs/auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ beeper verify cancel
- For agents, drive the explicit subcommands (`start` → `sas` → `sas-confirm`) and use `--json` to inspect state.
- `verify status` returns the encryption-readiness state (`ready`, `needs-verification`, `verification-in-progress`).
- `recovery-key` and `reset-recovery-key` apply to the encrypted-messages key, not to Beeper account login.
- `reset-recovery-key` signs out every chat account connected through Beeper Cloud on all your devices. Use it only when the recovery key is lost and no other device is verified; otherwise run `verify recovery-key` or approve from another device.

## Examples

Expand Down
16 changes: 10 additions & 6 deletions packages/cli/src/commands/verify/reset-recovery-key.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,28 @@
import { BeeperCommand, ensureWritable } from '../../lib/command.js'
import { createClient } from '../../lib/client.js'
import { printData } from '../../lib/output.js'
import { promptYesNoDefaultYes } from '../../lib/app-api.js'
import { promptYesNo } from '../../lib/app-api.js'

const resetWarning = 'Resetting the recovery key signs out every chat account connected through Beeper Cloud (WhatsApp, Telegram, Signal, …) on all your devices. You will need to reconnect them.'

export default class AuthVerifyResetRecoveryKey extends BeeperCommand {
static override summary = 'Create a new encrypted-messages recovery key'
static override description = `${resetWarning} Use only when you have lost your recovery key and have no other verified device.`

async run(): Promise<void> {
const { flags } = await this.parse(AuthVerifyResetRecoveryKey)
ensureWritable(flags)
const client = await createClient(flags)
const reset = await client.app.login.verification.recoveryKey.reset.create({})

if ((flags.json || !process.stdin.isTTY) && !flags.yes) {
throw new Error('Resetting the recovery key requires --yes in non-interactive mode so the new key can be confirmed.')
throw new Error(`${resetWarning} Pass --yes to confirm in non-interactive mode.`)
}

const client = await createClient(flags)
const reset = await client.app.login.verification.recoveryKey.reset.create({})

process.stderr.write(`Warning: ${resetWarning}\n`)
if (!flags.yes) {
process.stderr.write(`New recovery key:\n${reset.recoveryKey}\n`)
if (!await promptYesNoDefaultYes('I saved this recovery key. Use it for this account?')) throw new Error('Recovery key reset cancelled.')
if (!await promptYesNo('I saved this recovery key. Reset now and disconnect my chat accounts?')) throw new Error('Recovery key reset cancelled.')
}

const confirmed = await client.app.login.verification.recoveryKey.reset.confirm({ recoveryKey: reset.recoveryKey })
Expand Down
35 changes: 35 additions & 0 deletions packages/cli/test/verify-reset-recovery-key.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { fileURLToPath } from 'node:url'
import { describe, expect, it } from 'bun:test'

const cliRoot = fileURLToPath(new URL('..', import.meta.url))

describe('verify reset-recovery-key', () => {
it('refuses without --yes before touching the account, and says accounts will be disconnected', async () => {
const requests: string[] = []
const server = Bun.serve({
port: 0,
hostname: '127.0.0.1',
fetch(request) {
requests.push(`${request.method} ${new URL(request.url).pathname}`)
return Response.json({})
},
})

try {
const child = Bun.spawn([process.execPath, './bin/dev.js', 'verify', 'reset-recovery-key', '--base-url', server.url.origin, '--json'], {
cwd: cliRoot,
env: { ...process.env, BEEPER_ACCESS_TOKEN: 'test-token', BEEPER_CLI_CONFIG_DIR: '/tmp/beeper-cli-bun-test', BEEPER_NO_LOGO: '1' },
stdin: 'ignore',
stdout: 'pipe',
stderr: 'pipe',
})
const output = await new Response(child.stdout).text() + await new Response(child.stderr).text()

expect(await child.exited).not.toBe(0)
expect(output).toContain('signs out every chat account')
expect(requests).toEqual([])
} finally {
server.stop(true)
}
}, 20_000)
})
Loading