Repository navigation
identity: station-only users (amendment 1) - #175
Merged
Merged
Conversation
Admins can create users with a name, roles and PIN but no email. They can only unlock stations, can't be Admin, and become regular users once an admin adds an email. Tracked in #170.
Amendment 1: a worker with no email gets a name, roles (never Admin) and a PIN. They hold a reserved <uuid>@station.invalid placeholder, known only to isStationOnly in core; every API returns email: null + stationOnly instead. - core: createStationUser (idempotent, audited user.create without the PIN), updateUserRoles refuses Admin for them, listUsers search skips placeholders - contracts: CreateStationUserInput, users.createStationUser (POST /users), UserSummary/Me email nullable + stationOnly - web: Add station user dialog (shared PinField with Generate, then Issue badge), "Station only" chip, Edit roles without Admin; strings en + es - decisions.md: placeholder email over a nullable column, no Admin - tablet E2E: add, register station, unlock with the PIN, pick attributed Refs #171
The Add station user dialog looks the name up with users.list (debounced) and warns when an active user already has it, ignoring case and surrounding spaces. It never blocks: two people can share a name (SPEC-identity decision 13). Refs #173
sendMagicLink and mayCreateAccount refuse the station-only placeholder (even in ADMIN_EMAILS), session.create.before goes through the new core mayStartSession, and createInvitation rejects the reserved domain with errors.invitations.reservedEmail. Refs #172
…ular user
addUserEmail (users.addEmail, PUT /users/{id}/email) replaces a station-only
user's placeholder, audits user.email.add and queues the new accountEnabled
email in the same transaction. The user keeps their id, roles, PIN and badge.
Add email in the row menu asks for the address twice (en + es).
The address is stored verified (spec decision 15): Better Auth 1.7.8's
magic-link verify returns 500 for an existing unverified user, because its
lock row in verifications gets a non-uuid id with generateId: false. The spec
now records how Google and Discord link for this user.
Refs #174
Review of amendment 1 (#171-#174): - Clear PIN on a station-only user with no badge, and Revoke badge on one with no PIN, now ask first (SPEC-identity: "the PIN dialog warns about it but allows it"). One LastWayInDialog covers both; strings en + es. - publicEmail(email) in core's station-only.ts is the one rule that hides the placeholder; toUserSummary, me.get and the app layout use it. - core exports isStationOnly, publicEmail and stationOnlyPlaceholder only; stationOnlyEmailFilter stays internal. Refs #170
From the /ship review of amendment 1 (code, security and test reviewers): - addUserEmail refuses a deactivated station-only user (CONFLICT, errors.users.addEmailInactive): their sign-in email would lead nowhere, and reactivating doesn't resend it. The row menu hides Add email for them. - resolveActor also refuses a personal session for a station-only user, so one that got past the sign-in hook some other way still can't act. - displayName falls back through publicEmail, so a placeholder can never show on workflow screens. - SPEC-identity: the reserved-domain check is in core createInvitation, not the contract; addUserEmail's deactivated rule. Refs #170
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements SPEC-identity amendment 1, station-only users: everything marked (A1), success criteria 16–22, decisions 9–15.
An admin can add a worker who has no email: a name, roles (never Admin) and a PIN. That worker unlocks stations only and can never sign in personally. Later an admin can add an email, which turns them into a regular user with the same id, roles, PIN, badge and history.
Tickets
6de0a91)426bc76)0058631, merged in8ab9137)9227e51)7751eb6,8746513):publicEmailruleresolveActoranddisplayNamehardeningNotes
<uuid>@station.invalidplaceholder, known only topackages/core/src/identity/station-only.ts. APIs returnemail: nullandstationOnly: trueinstead.emailVerified: true(decision 15). Better Auth 1.7.8's magic-link verify returns 500 for an existing unverified user, because its lock row gets a non-uuid id. As a result, Google and Discord accounts that report the address as verified link right away. The spec records how they behave.SPEC.mdstatus table:identityA1 isdone.Verification
tscpasssign-in.spec.ts:23failed once under parallel load and passed on rerun.next build, which fails on a local SWC cache error. CI is its first run.Follow-ups (not in this PR)
withIdempotency: the secondcreateStationUsergets a raw P2002 (a 500), and the secondaddEmailgetsnotStationOnlyinstead of the replay. This affects other modules too, so it's a project-wide fix.Closes #170