Back to Feed
article 2m read

Store-move edit could demote an admin without ever mentioning admin

A user-account save treated a missing admin flag or role list as "no" and stripped access on routine store edits. The change keeps absent fields as they were and adds a token refresh after a 403, but rereading it I found the role carry-over now spreads per-store grants across every store.

Article DepthPractice

Flow

What each kind of edit does now

  1. 1Store-only edit

    Admin flag and roles are kept as they were.

  2. 2Explicit no

    Admin is removed.

  3. 3Empty role list

    Every extra role is removed.

  4. 4New account

    Starts as a non-admin unless the request asks for admin.

  5. 5Stale login token

    A 403 triggers one refresh and one retry.

Moving someone to a new store could take away their admin rights. Nobody clicked anything about admin. The request just didn't mention it.

How the save lost it

My scheduling app for the stores has one endpoint for saving user accounts, copied three times for different callers. The request can include an admin flag and a list of extra roles, but both are optional. Every copy passes the request to the same save function.

That function filled in a missing admin flag with false, wrote it, and then removed the admin role. So an edit that only changed stores demoted the person, and the save said it worked.

Roles broke the same way. The endpoint compares the roles you asked for with the roles the person has, then removes the extras. If you didn't send a role list, you asked for nothing, so every extra role got removed.

Three answers, not two

Now each copy checks whether the admin field is there at all. If it's missing on an existing account, the stored value is loaded and saved again. A new account starts at no. Only an explicit no takes admin away.

const admin = body.admin == null ? storedAdmin : body.admin;

Roles work the same way. No list means keep what they have. An empty list means remove them all. The docs say so now.

The login token goes stale

The app also copies each person's roles into a signed login token, so it doesn't have to ask the database on every request. After an edit, that token is out of date. There's now a refresh route that builds a new token from the current database, and the browser calls it once when a request gets a 403, then tries the request again once.

That only helps people who just gained access. Someone who just lost access never gets a 403, so they keep it until their token runs out. And real denials now take a bit longer.

Schedule rights leak to every store

The change came with no test of its own.

The worse one: roles are carried over as a single list of names and then given out at every store in the request. If someone edits the schedule at one store, a store-only edit gives them that right at all of their stores.

Keep reading

Pick up a connected idea or branch into a nearby one.

5 paths forward
Related idea

New delete button hands the server a path, and the server doesn't take its word for it

A Delete button was added to the backup page in the scheduling app. It sends a file path, so the server resolves it, fences it to the selected company's folder with a separator-aware check, refuses anything but a regular file, and only then deletes. What still bugs me: name-derived folders, hard deletes, no password, no tests and a vague audit entry.

via #security · #api-design · #authorization