owner can reset a member's platform password
The gap at the other end of create-user.ts: the owner could set a password once, at creation, and never again. Losing it meant a hand-written UPDATE with an argon2 hash — the same "edit Postgres by hand" hole that creating accounts used to have. POST /api/users/:id/password, owner-gated, with a button on the row. GENERATED, not typed. The failure this exists for is "I created the account and forgot to copy the password down", and an owner typing a replacement can lose it the same way on the second go. Shown once in a dialog built to be copied — a dialog and not a toast, because a toast that times out while somebody finds a pen loses the one thing they came for. The generator satisfies validatePassword BY CONSTRUCTION rather than by luck: one character drawn from each of the four required classes, the rest from the union, then Fisher-Yates shuffled so the first four positions are not always lower/upper/digit/special. Rejection sampling throughout — `% n` on a byte biases the early characters. Then it runs validatePassword on its own output, so if the rules ever gain a requirement the alphabets do not cover it throws at the one call site instead of minting passwords the login form rejects. Measured: 20,000 generations, all four classes present every time. l, I, 1, O and 0 are absent from the alphabets. This gets read off a screen and typed somewhere else. Signs them out everywhere, as asked: passwordChangedAt = now, and userMiddleware already refuses any token whose iat predates it. That overwrites the null create-user leaves to mean "the owner chose this, not them" — checked, nothing reads that column except the token check. The Linux account is deliberately untouched, and the dialog says so. Members have no Linux password and never had one: ensureOsUser runs useradd with no -p, so it is created locked. Their terminal goes through setpriv, which does not authenticate; their SSH is the key the owner pasted; `su - <member>` as root does not ask. And machine-setup sets PasswordAuthentication no — verified on this host — so one could not be used to log in even if it existed. Setting one would be a new way in, not a repair. The owner is excluded: they have change-password, which asks for the current one, and resetting themselves here would end the session doing it. Verified: transpiles, all lucide icons exist, 20k generator runs. tsgo next. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,7 @@ import { updateUserHandler } from './update-user';
|
||||
import { listUsersHandler, updateUserRoleHandler, deleteUserHandler } from './manage-users';
|
||||
import { createUserHandler } from './create-user';
|
||||
import { provisionLinuxHandler } from './provision-linux-route';
|
||||
import { resetUserPasswordHandler } from './reset-user-password';
|
||||
import { capabilityAdminRouter } from './capabilities-routes';
|
||||
|
||||
export const usersRouter = createRouter();
|
||||
@@ -31,6 +32,9 @@ usersRouter.get('/', ownerGate, listUsersHandler);
|
||||
usersRouter.post('/', ownerGate, createUserHandler);
|
||||
usersRouter.patch('/:id/role', ownerGate, updateUserRoleHandler);
|
||||
usersRouter.post('/:id/provision-linux', ownerGate, provisionLinuxHandler);
|
||||
// Generates a new platform password, returns it once, and signs the account out everywhere. Not the same
|
||||
// thing as auth/reset-password.ts, which is the token flow the person runs on themselves.
|
||||
usersRouter.post('/:id/password', ownerGate, resetUserPasswordHandler);
|
||||
usersRouter.delete('/:id', ownerGate, deleteUserHandler);
|
||||
|
||||
// Which capabilities each role holds. Owner-gated inside its own router.
|
||||
|
||||
Reference in New Issue
Block a user