Skip to content

Feat: block users - #53

Draft
khanzadimahdi wants to merge 1 commit into
mainfrom
feat/block-users
Draft

Feat: block users#53
khanzadimahdi wants to merge 1 commit into
mainfrom
feat/block-users

Conversation

@khanzadimahdi

Copy link
Copy Markdown
Member

The admin control for banning an account, and the plumbing that makes the whole app react when the API refuses a banned session.

Pairs with backend PR Tarhche/backend#74 — this branch reads the banned / banned_at fields and the 403 {"code": "user_banned"} responses that PR introduces. Merge the backend first; until then the switch has nothing to write to.

The admin side

A banned switch on the user edit form. It appears only for an existing user — banning is an action on an account, not part of creating one. It sends the intent only; the API owns the timestamp. When the user is already banned, the switch's description says since when instead of the generic hint.

Reacting to a ban

The API answers 403 with {"code": "user_banned", "message": "…"}. The code is what matters: an ordinary permission denial is also a 403, and the dashboard already depends on that behaving as it does today (a warning, not a sign-out). So a ban has to be distinguished by code, not status.

Both DAL drivers now recognise it and force a sign-out:

  • Server driver — a ServerBannedInterceptor that redirects.
  • Client driver — a redirect to a new /api/auth/banned route that clears the session and lands on the login page with the explanation carried in query params.

The login form itself also handles the refusal, so a banned account gets "your account has been suspended" rather than something that reads like a wrong password. The message the API sends is already translated, so it's shown as-is; errors.banned is only the fallback for when the API sends none.

The unstable_rethrow sweep

This is the part most worth a look. Next's redirect() travels as a throw. A number of catch { return false } blocks around DAL calls would therefore catch the forced sign-out and discard it — the ban would silently do nothing. Those blocks now call unstable_rethrow(error) first, which re-throws Next's control-flow errors and leaves ordinary failures to be handled as before.

The central one is in extractValidationErrors, which nearly every catch around an API call funnels through. The rest are individual delete actions and the settings page.

Notes for review

  • isBannedError accepts several spellings of the code (user_banned, user_is_banned, account_banned, banned) so the frontend isn't pinned to one exact string. It reads both error shapes the app produces — DALDriverError from the server drivers and the raw AxiosError from the client one.
  • The react-query error handler returns early for a ban, because the client DAL is already navigating away — a "you don't have permission" toast on the way out would only mislead.
  • dal/private/users.ts widens its payload type from Record<string, string> to Record<string, string | boolean>, since banned is a real JSON boolean.
  • Verified with tsc --noEmit and next build on this branch alone; /api/auth/banned appears in the route manifest. The repo has jest configured but no test files.

Relationship to the other PRs

Split out of one working branch alongside contact-us and notes. All three are cut from main and mergeable in any order; the overlap is the two i18n dictionaries. One thing to know: the notes PR also touches remove-bookmark.ts and delete-comment.ts, and carries their unstable_rethrow line along with its own changes, so that sweep is complete either way.

🤖 Generated with Claude Code

Adds the admin control for banning an account and makes the whole app
react when the API refuses a banned session.

- A "banned" switch on the user edit form. It only appears for an existing
  user, since banning is an action on an account rather than part of
  creating one, and it sends the intent only — the API keeps the date. When
  the user is already banned the switch says since when
- The API answers 403 with {"code": "user_banned", "message": "..."} for a
  banned session. Both DAL drivers recognise it and force a sign-out
  instead of showing the usual permission warning: the server driver via a
  ServerBannedInterceptor, the client driver via a redirect to a new
  /api/auth/banned route that clears the session and lands on the login
  page with the explanation
- The login form says the account is suspended rather than letting the
  refusal read as a wrong password
- catch blocks that swallowed failures now call unstable_rethrow first.
  Next's redirect() travels as a throw, so without this the forced
  sign-out would be caught and discarded by the very code it passes through

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant