Skip to content

CLI: add 'jss account delete' (and refactor 'passwd' to use the same wrapper) #292

Description

@melvincarvalho

Two related gaps

1. No CLI for account deletion

`src/idp/accounts.js` already exports `deleteAccount(id)` — it removes the account JSON, prunes the username/email/webId indexes, the works. But there's no CLI surface for it. Operators currently delete accounts by hand-editing index JSON files and removing the account file, which is fragile.

2. `passwd` bypasses the wrapper it should use

The existing `jss passwd ` command (`bin/jss.js:628`) reads the username index and account file with raw `fs.readJson` / `fs.writeJson` instead of going through `accounts.js`. Notably, `accounts.js` already exports `updatePassword(id, newPassword)` — `passwd` re-implements that logic inline (including the bcrypt round) rather than calling it. The other `bin/jss.js` commands (`invite`, `quota`) all consume their respective `src/` modules via imports — `passwd` is the odd one out.

Proposal

Add an `account` command group with `delete` (and room for future siblings like `list`, `info`, `disable`), and refactor `passwd` to use the wrapper too.

`jss account delete `

Sketch:

```js
import { findByUsername, deleteAccount } from '../src/idp/accounts.js';

program
.command('account')
.description('Manage user accounts');

program
.command('account delete ')
.description('Delete a user account from the IdP')
.option('-r, --root ', 'Data directory')
.option('-y, --yes', 'Skip the confirmation prompt')
.option('--purge', 'Also delete pod data at //')
.action(async (username, opts) => {
if (opts.root) process.env.DATA_ROOT = path.resolve(opts.root);
const account = await findByUsername(username);
if (!account) { console.error(`User not found: ${username}`); process.exit(1); }
if (!opts.yes) {
const ok = await confirm(`Delete account '${username}' (${account.webId})?`);
if (!ok) process.exit(1);
}
await deleteAccount(account.id);
if (opts.purge) {
await fs.remove(path.join(process.env.DATA_ROOT || './data', username));
}
console.log(`Deleted ${username}.${opts.purge ? ' Pod data removed.' : ''}`);
});
```

`--purge` is opt-in for a reason

Account record removal revokes identity. Pod data removal is destructive and irreversible. Keeping them separate behind an explicit flag means the safe default (`account delete melvin`) frees the username for re-registration without touching files at `/melvin/`. `--purge` is for the "start over completely" case.

Refactor `passwd`

Drop the inline `bcrypt.hash` and `fs.readJson`/`writeJson` in favour of:

```js
const account = await findByUsername(username);
if (!account) { /* error */ }
await updatePassword(account.id, newPassword);
```

Same external behaviour, ~30 fewer lines, and a single source of truth for hashing parameters / index updates.

Why both in one issue

They're the same shape of fix — the CLI layer should consume `accounts.js` consistently. Doing both together makes the pattern obvious and removes the temptation to copy the inline-fs pattern into the new `delete` command.

Non-goals

  • Account listing / info / disable (good follow-ups; out of scope here).
  • HTTP `DELETE /idp/account` endpoint (requires an auth story for self-service deletion vs admin deletion; deserves its own issue).
  • Federation / cleaning up sessions or refresh tokens issued to the deleted account (separate concern; tokens expire naturally, but a thorough deletion would revoke them).

Happy to PR.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions