diff --git a/docs/developer/api/admin/list_users.md b/docs/developer/api/admin/list_users.md index 8c733292..c15d4976 100644 --- a/docs/developer/api/admin/list_users.md +++ b/docs/developer/api/admin/list_users.md @@ -1,3 +1,11 @@ +--- +method: GET +path: /api/auth/users +auth: jwt+admin +area: authentication +--- + + # List Users List all users in the system (admin only). @@ -28,29 +36,34 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ## Response (200 OK) -```json -{ - "users": [ - { - "id": "uuid", - "email": "user@example.com", - "username": "johndoe", - "first_name": "John", - "last_name": "Doe", - "role": "user", - "theme": "tokyo-night", - "max_devices": 10, - "device_count": 3, - "created_at": "2026-02-08T10:00:00Z", - "updated_at": "2026-02-08T10:00:00Z" - } - ], - "total": 25, - "limit": 10, - "offset": 0 -} +```json example-response +[ + { + "id": "550e8400-e29b-41d4-a716-446655440000", + "email": "user@example.com", + "username": "johndoe", + "password_hash": "$2a$04$REDACTED", + "first_name": "John", + "last_name": "Doe", + "role": "user", + "theme": "tokyo-night", + "max_devices": 10, + "created_at": "2026-02-08T10:00:00Z", + "timezone": "America/New_York", + "updated_at": "2026-02-08T10:00:00Z", + "default_audio_speed": 1 + } +] ``` +Response is a bare JSON array of full user rows — no pagination wrapper +and no `device_count` (those were invented in an earlier draft). + +**Leak note:** the user model json-marshals `password_hash` verbatim — +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 | Field | Type | Description | diff --git a/docs/developer/api/admin/update_user_max_devices.md b/docs/developer/api/admin/update_user_max_devices.md index 3178a383..7c3000aa 100644 --- a/docs/developer/api/admin/update_user_max_devices.md +++ b/docs/developer/api/admin/update_user_max_devices.md @@ -1,3 +1,11 @@ +--- +method: PUT +path: /api/auth/users/:id/max-devices +auth: jwt+admin +area: authentication +--- + + # Update User Max Devices Update the maximum number of devices a user can register (admin only). @@ -20,7 +28,7 @@ Update the maximum number of devices a user can register (admin only). ### Example Request -```json +```json example-request { "max_devices": 15 } @@ -28,14 +36,9 @@ Update the maximum number of devices a user can register (admin only). ## Response (200 OK) -```json +```json example-response { - "id": "uuid", - "email": "user@example.com", - "username": "johndoe", - "max_devices": 15, - "active_devices": 3, - "updated_at": "2026-02-08T11:00:00Z" + "message": null } ``` diff --git a/docs/developer/api/authentication/password_rules.md b/docs/developer/api/authentication/password_rules.md new file mode 100644 index 00000000..299a26a4 --- /dev/null +++ b/docs/developer/api/authentication/password_rules.md @@ -0,0 +1,31 @@ +--- +method: GET +path: /api/auth/password-rules +auth: none +area: authentication +--- + + +# Password Rules + +Returns the active password complexity configuration, so UI requirement +checkers mirror the real rules. The server remains the enforcer. This +route is rate-limited. + +## Example response + +```json example-response +{ + "min_length": 8, + "require_upper": true, + "require_lower": true, + "require_number": true, + "require_special": true +} +``` + +## Errors + +| Status | Body | When | +| ------ | ---- | ---- | +| 429 | `{"error": "..."}` | rate limited | \ No newline at end of file diff --git a/docs/developer/api/book-matching/get_book_matches.md b/docs/developer/api/book-matching/get_book_matches.md index b86d0477..376350e2 100644 --- a/docs/developer/api/book-matching/get_book_matches.md +++ b/docs/developer/api/book-matching/get_book_matches.md @@ -1,3 +1,11 @@ +--- +method: GET +path: /api/books/match +auth: jwt +area: book-matching +--- + + # Get Book Matches Get potential book matches for a given query. @@ -27,20 +35,22 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ## Response (200 OK) -```json +```json example-response { "matches": [ { "id": "uuid", "title": "Foundation", - "authors": ["Isaac Asimov"], + "authors": [ + "Isaac Asimov" + ], "isbn": "978-0553293357", "published_year": 1951, "publisher": "Bantam Books", "cover_url": "https://example.com/cover.jpg" } ], - "total": 5 + "action": null } ``` diff --git a/docs/developer/api/devices/approve_device_registration.md b/docs/developer/api/devices/approve_device_registration.md index 77fb6591..0ffbca00 100644 --- a/docs/developer/api/devices/approve_device_registration.md +++ b/docs/developer/api/devices/approve_device_registration.md @@ -1,3 +1,11 @@ +--- +method: GET +path: /api/devices/approve/:registration_id +auth: jwt +area: devices +--- + + # Approve Device Registration Approve a pending device registration request. @@ -26,7 +34,7 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ## Response (200 OK) -```json +```json example-response { "message": "device approved successfully", "device_name": "string", diff --git a/docs/developer/api/devices/list_pending_registrations.md b/docs/developer/api/devices/list_pending_registrations.md index 99e6f293..e4b9e4fb 100644 --- a/docs/developer/api/devices/list_pending_registrations.md +++ b/docs/developer/api/devices/list_pending_registrations.md @@ -1,4 +1,13 @@ -# List Pending Device Registrations +--- +method: GET +path: /api/devices/pending +auth: jwt +area: devices +--- + + +# List Pending Device Registrations:<|end|><|start|><|start|>assistant to=edit<|channel|>analysis <|constrain|>xml<|message|> +/home/nymusicman/Code/bookhoard/docs/developer/api/devices/reject_device_registration.md List all pending device registration requests. @@ -20,7 +29,7 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ## Response (200 OK) -```json +```json example-response { "registrations": [ { @@ -31,7 +40,8 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... "created_at": "2026-02-08T10:00:00Z" } ], - "total": 3 + "total": 3, + "pending_registrations": null } ``` diff --git a/docs/developer/api/devices/reject_device_registration.md b/docs/developer/api/devices/reject_device_registration.md index bac1f40a..d1392783 100644 --- a/docs/developer/api/devices/reject_device_registration.md +++ b/docs/developer/api/devices/reject_device_registration.md @@ -1,3 +1,11 @@ +--- +method: POST +path: /api/devices/reject/:registration_id +auth: jwt +area: devices +--- + + # Reject Device Registration Reject a pending device registration request. @@ -26,7 +34,7 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ## Response (200 OK) -```json +```json example-response { "message": "device registration rejected" } diff --git a/docs/developer/api/progress/get_progress_history.md b/docs/developer/api/progress/get_progress_history.md index f143b4fb..9a02b876 100644 --- a/docs/developer/api/progress/get_progress_history.md +++ b/docs/developer/api/progress/get_progress_history.md @@ -1,3 +1,11 @@ +--- +method: GET +path: /api/progress/:id/history +auth: jwt +area: progress +--- + + # Get Progress History Get historical reading progress data for a media item. @@ -33,31 +41,11 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ## Response (200 OK) -```json +```json example-response { - "history": [ - { - "media_item_id": "uuid", - "percentage": 75.5, - "position": 1234, - "page": 150, - "device_id": "device-uuid", - "device_name": "My Kobo", - "created_at": "2026-02-08T10:00:00Z" - }, - { - "media_item_id": "uuid", - "percentage": 50.0, - "position": 800, - "page": 100, - "device_id": "device-uuid", - "device_name": "My Kobo", - "created_at": "2026-02-07T15:00:00Z" - } - ], - "total": 45, - "limit": 20, - "offset": 0 + "count": null, + "media_item_id": null, + "sessions": null } ``` diff --git a/docs/developer/api/users/change_password.md b/docs/developer/api/users/change_password.md index cada988d..52445574 100644 --- a/docs/developer/api/users/change_password.md +++ b/docs/developer/api/users/change_password.md @@ -1,3 +1,11 @@ +--- +method: PUT +path: /api/auth/password +auth: jwt +area: authentication +--- + + # Change Password Change user password. Supports both self-service and admin modes. @@ -24,7 +32,7 @@ Users can change their own password by providing current password verification. ### Example Request -```json +```json example-request { "current_password": "OldPassword123!", "new_password": "NewSecureP@ss123!", @@ -56,7 +64,7 @@ Admins can reset any user's password without knowing the current password. ## Response (200 OK) -```json +```json example-response { "message": "password updated" } diff --git a/docs/developer/api/users/delete_user.md b/docs/developer/api/users/delete_user.md index 640f9d1e..bd69b51d 100644 --- a/docs/developer/api/users/delete_user.md +++ b/docs/developer/api/users/delete_user.md @@ -1,3 +1,11 @@ +--- +method: DELETE +path: /api/auth/profile +auth: jwt +area: authentication +--- + + # Delete User Delete a user account. Supports both self-deletion and admin deletion. @@ -25,7 +33,7 @@ Admins can delete any user account by providing the user ID in the URL. ## Response (200 OK) -```json +```json example-response { "message": "account deleted" } diff --git a/docs/developer/api/users/update_profile.md b/docs/developer/api/users/update_profile.md index cef488e2..543471cc 100644 --- a/docs/developer/api/users/update_profile.md +++ b/docs/developer/api/users/update_profile.md @@ -1,3 +1,11 @@ +--- +method: PUT +path: /api/auth/profile +auth: jwt +area: authentication +--- + + # Update Profile Update user profile information. Supports both self-edit and admin modes. @@ -26,7 +34,7 @@ Users can update their own profile. All fields are optional. ### Example Request -```json +```json example-request { "username": "newusername", "email": "newemail@example.com", @@ -60,17 +68,9 @@ Admins can update any user by providing the user ID in the URL. Additionally sup ## Response (200 OK) -```json +```json example-response { - "id": "uuid", - "email": "user@example.com", - "username": "john", - "first_name": "John", - "last_name": "Smith", - "theme": "dracula", - "role": "user", - "max_devices": 10, - "created_at": "2026-01-31T10:00:00Z" + "message": null } ``` diff --git a/internal/docs/drift_test.go b/internal/docs/drift_test.go index 93a3b789..705d0a51 100644 --- a/internal/docs/drift_test.go +++ b/internal/docs/drift_test.go @@ -140,6 +140,10 @@ func authLabel(chain []string) string { return "device-token" case has("createOptionalAuthMiddleware"): return "optional-jwt" + case has("JWTMiddleware"): + // Wrapper-inlined forms like createJWTMiddleware(cfg) carry uppercase + // JWT; bare jwtMiddleware carries lowercase. Match both. + fallthrough case has("jwt"): return "jwt" default: diff --git a/internal/docs/schema.go b/internal/docs/schema.go index 8081367a..bef9a7fd 100644 --- a/internal/docs/schema.go +++ b/internal/docs/schema.go @@ -659,4 +659,71 @@ var Schemas = map[string]Schema{ "watching_libraries": [""], "total_watching": 0 }`), }, + + // --- Beam batch 8: devices remaining + auth remainder --- + "GET /api/devices/pending": { + // Device registrations live in an in-memory map; both key spellings + // are emitted for compatibility. + Response: json.RawMessage(`{ + "registrations": [], "pending_registrations": [], "total": 0 + }`), + }, + "GET /api/devices/approve/:registration_id": { + // Already-approved variant omits registration_id. Credentials ride + // the registration map polled elsewhere — NOT this JSON body. + Response: json.RawMessage(`{ + "message": "", "device_name": "", "device_type": "", + "registration_id": "", "approved": false + }`), + }, + "POST /api/devices/reject/:registration_id": { + Response: json.RawMessage(`{"message": ""}`), + }, + "POST /api/auth/users": { + Request: handlers.RegisterRequest{}, + Response: json.RawMessage(`{ + "id": "", "email": "", "username": "", "role": "", + "timezone": "", "created_at": null + }`), + }, + "GET /api/auth/users": { + Response: []database.Users{}, + }, + "PUT /api/auth/users/:id/max-devices": { + Request: handlers.UpdateUserMaxDevicesRequest{}, + Response: json.RawMessage(`{"message": ""}`), + }, + "PUT /api/auth/password": { + // Self-change (jwt). Admin variant PUT /auth/password/:id documented + // in prose; handler is shared (admin mode skips current_password). + Request: json.RawMessage(`{ + "current_password": "", "new_password": "", "confirm_password": "" + }`), + Response: json.RawMessage(`{"message": ""}`), + }, + "PUT /api/auth/profile": { + // Shared with admin variant (PUT /auth/profile/:id). + Request: handlers.AdminUpdateUserRequest{}, + Response: json.RawMessage(`{"message": ""}`), + }, + "DELETE /api/auth/profile": { + // Self-delete; admin variant DELETE /auth/profile/:id (reassignment + // of admin-owned assets to another admin before cascade). + Response: json.RawMessage(`{"message": ""}`), + }, + "GET /api/progress/:id/history": { + // id is MEDIA ITEM id; sessions are ReadingHistory rows. + Response: json.RawMessage(`{ + "media_item_id": "", "sessions": [], "count": 0 + }`), + }, + "GET /api/books/match": { + Response: services.BookQueryResponse{}, + }, + "GET /api/auth/password-rules": { + Response: json.RawMessage(`{ + "min_length": 8, "require_upper": true, "require_lower": true, + "require_number": true, "require_special": true + }`), + }, }