feat(security): library visibility enforced at the source; per-library series; annotation ownership; admin user creation
The family-lens hardening: library_visibility was only enforced where file bytes flowed (download/reader/OPDS) — metadata surfaces leaked hidden libraries to any authenticated account, and registration was open by default with no admin-side user creation. - Series are grouped PER LIBRARY now (same name in two libraries = two entries: manga adaptations / LN splits no longer merge); /api/series aggregates only the requester's VISIBLE libraries and /api/series/books REQUIRES a visible library_id (breaking contract; web + app clients updated in the same change). - /api/media-items, item detail, and the reader content endpoints (chapters/panels/outline/THUMBNAILS — page images leaked unguarded next to a gated GetPage) all enforce the visible set; resolveLibrary no longer honors a hand-typed library_id outside the visible set. - Annotation/note/bookmark mutations (update/delete/tombstone, sync LWW paths incl. conflict resolution) carry ownership predicates: another account's bare row id is a 404, not a silent delete. - Admin user creation: POST /api/auth/users (AdminMiddleware) + a Create User card in the admin Users UI — accounts stay creatable with self-service registration OFF, which is now the schema default (fresh installs; the first-admin bootstrap exception is unchanged). - Tests: series contract re-pinned (per-library, visibility 403s), new ownership + admin-create integration suites, OPDS all=1 test now passes an explicit page size (shared-DB growth made the fresh books fall off page 1), device-cap helper switched to the admin endpoint.
This commit is contained in:
1 parent
97fbdf19fb
commit
431fec05ad
60 files changed
+1573
-691
No files matched your search
@@ -336,7 +336,7 @@ func createTestUserForMaxDevices(t *testing.T, ts *httptest.Server, adminToken s
|
||||
}
|
||||
|
||||
jsonData, _ := json.Marshal(createUserPayload)
|
||||
req, _ := http.NewRequest("POST", ts.URL+"/api/auth/register", bytes.NewBuffer(jsonData))
|
||||
req, _ := http.NewRequest("POST", ts.URL+"/api/auth/users", bytes.NewBuffer(jsonData))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", "Bearer "+adminToken)
|
||||
|
||||
@@ -416,14 +416,19 @@ func createTestUserForMaxDevices(t *testing.T, ts *httptest.Server, adminToken s
|
||||
err = json.NewDecoder(resp.Body).Decode(&result)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Check if user creation was successful
|
||||
if result["user"] == nil {
|
||||
// Admin create returns the user object at the top level; the legacy
|
||||
// register endpoint nested it under "user". Accept both.
|
||||
userObj, ok := result["user"].(map[string]interface{})
|
||||
if !ok {
|
||||
userObj = result
|
||||
}
|
||||
if userObj["id"] == nil {
|
||||
// User creation failed for another reason
|
||||
t.Logf("User creation failed, response: %+v", result)
|
||||
return uuid.UUID{}
|
||||
}
|
||||
|
||||
userIDStr := result["user"].(map[string]interface{})["id"].(string)
|
||||
userIDStr, _ := userObj["id"].(string)
|
||||
userID, _ := uuid.Parse(userIDStr)
|
||||
|
||||
return userID
|
||||
|
||||
Reference in new issue
Block a user