docs(users): live-smoke correction — GET /api/auth/users emits sqlc ListUsersRow, no password_hash

Curated smoke run of the generated collection against the live dev
server (37/37 after fixes): registration matrix (409 duplicate email,
403 admin-creation-with-admins-present, 400 weak password), non-admin
403s, device-token/opds 401s, collection CRUD incl. the documented 201
update quirk and 204 delete, stats routes, scanner, jobs, malformed-uuid
400s. Testuser untouched (password test uses a guaranteed-wrong current
password -> 401); the smoke user was admin-deleted in cleanup and its
token verified dead.

Correction the smoke run forced: the batch-8 'password_hash leak' was a
false alarm — my registry pin ([]database.Users) never matched the
handler, which returns []database.ListUsersRow (sqlc join with
device_count). Live response confirmed: id/email/username/theme/names/
role/max_devices/created_at/updated_at/device_count — NO hash. Doc
example, field table, and registry pin all corrected; leak note
replaced with the live-verified statement.

Also verified via the real Bruno CLI: 9 safe generated requests
(4 shown passing incl. stats/collections/devices; password-rules
public) execute against the server with collection-level auth.
Full-collection bru run deliberately not attempted (mutations in
static bodies would touch live data).
This commit is contained in:
John O'Keefe committed 2026-10-24 18:58:39 -04:00
1 parent cfcd95b25e
commit 0c45137f5b
2 files changed
+9 -15

No files matched your search

+6 -14
View File
@@ -42,27 +42,22 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
"id": "550e8400-e29b-41d4-a716-446655440000", "id": "550e8400-e29b-41d4-a716-446655440000",
"email": "user@example.com", "email": "user@example.com",
"username": "johndoe", "username": "johndoe",
"password_hash": "$2a$04$REDACTED", "theme": "tokyo-night",
"first_name": "John", "first_name": "John",
"last_name": "Doe", "last_name": "Doe",
"role": "user", "role": "user",
"theme": "tokyo-night",
"max_devices": 10, "max_devices": 10,
"created_at": "2026-02-08T10:00:00Z", "created_at": "2026-02-08T10:00:00Z",
"timezone": "America/New_York",
"updated_at": "2026-02-08T10:00:00Z", "updated_at": "2026-02-08T10:00:00Z",
"default_audio_speed": 1 "device_count": 3
} }
] ]
``` ```
Response is a bare JSON array of full user rows — no pagination wrapper Response is a bare JSON array of sqlc `ListUsersRow`s (user fields plus a
and no `device_count` (those were invented in an earlier draft). `device_count` join) — no pagination wrapper. Live-smoke-verified
2026-10-24: **`password_hash` is NOT in the response** (an earlier draft
**Leak note:** the user model json-marshals `password_hash` verbatim — of this doc wrongly claimed a hash leak).
the list-users response exposes password hashes to any admin caller.
Known defect, tracked separately (doc mirrors the real field set; do not
implement against a redacted shape).
### Response Fields ### Response Fields
@@ -79,9 +74,6 @@ implement against a redacted shape).
| `device_count` | integer | Current number of registered devices | | `device_count` | integer | Current number of registered devices |
| `created_at` | string | Account creation timestamp (ISO 8601) | | `created_at` | string | Account creation timestamp (ISO 8601) |
| `updated_at` | string | Last update timestamp (ISO 8601) | | `updated_at` | string | Last update timestamp (ISO 8601) |
| `total` | integer | Total number of users matching query |
| `limit` | integer | Limit applied to this request |
| `offset` | integer | Offset applied to this request |
## Error Responses ## Error Responses
+3 -1
View File
@@ -687,7 +687,9 @@ var Schemas = map[string]Schema{
}`), }`),
}, },
"GET /api/auth/users": { "GET /api/auth/users": {
Response: []database.Users{}, // sqlc join row (device_count), NOT database.Users — no password_hash
// on the wire (live-smoke-verified 2026-10-24).
Response: []database.ListUsersRow{},
}, },
"PUT /api/auth/users/:id/max-devices": { "PUT /api/auth/users/:id/max-devices": {
Request: handlers.UpdateUserMaxDevicesRequest{}, Request: handlers.UpdateUserMaxDevicesRequest{},