|
@@ -362,7 +362,37 @@ The `op` field in `where` filters accepts either a string name (`EQ`, `NE`, `GT`
|
|
|
|
|
|
|
|
#### Why AND-merge filters?
|
|
#### Why AND-merge filters?
|
|
|
|
|
|
|
|
-The view's filters act as a **security enforcement boundary**. A consumer querying `active_admins` literally cannot see inactive users — the filter is applied server-side, after authentication, before projection. This is how SQL views with `WHERE` clauses work, and how row-level security works in most databases. Any consumer filter is ANDed on top, narrowing further.
|
|
|
|
|
|
|
+A view's filters are applied **server-side**, before projection, and a consumer
|
|
|
|
|
+filter is ANDed on top rather than replacing them — so a caller querying
|
|
|
|
|
+`active_admins` cannot widen the result set beyond what the view defines. That
|
|
|
|
|
+makes a view a reliable **schema and shape contract**: what comes back through
|
|
|
|
|
+this name is always a subset of what the view allows.
|
|
|
|
|
+
|
|
|
|
|
+> **A view is NOT a security boundary.** Earlier revisions of this guide
|
|
|
|
|
+> described view filters as "a security enforcement boundary" and compared them
|
|
|
|
|
+> to row-level security. That was wrong, and should not be used as the basis of
|
|
|
|
|
+> a threat model. Three reasons:
|
|
|
|
|
+>
|
|
|
|
|
+> 1. **There is no caller identity.** Authentication (2.4+) compares a bearer
|
|
|
|
|
+> token against a flat list of shared keys per listener. It attaches no
|
|
|
|
|
+> principal to the call, so the server cannot know *who* is asking — and
|
|
|
|
|
+> every key holder has identical, complete access.
|
|
|
|
|
+> 2. **The underlying collection stays readable.** Views only reject *writes* on
|
|
|
|
|
+> a view name. `find("active_admins")` narrows; `find("users")` does not.
|
|
|
|
|
+> Nothing stops a client bypassing the view entirely.
|
|
|
|
|
+> 3. **Projects are not a boundary either.** `Config::project` is chosen by the
|
|
|
|
|
+> client, and a fully-qualified `other_project:collection` name is accepted
|
|
|
|
|
+> from any client, so namespaces isolate naming, not access.
|
|
|
|
|
+>
|
|
|
|
|
+> Views constrain what a **cooperating** client sees. Treat them as ergonomics
|
|
|
|
|
+> and API hygiene. Real row- and column-level security requires a per-principal
|
|
|
|
|
+> identity and a policy evaluated at every read handler; that is planned but not
|
|
|
|
|
+> present today.
|
|
|
|
|
+>
|
|
|
|
|
+> Related: field-level encryption (`sensitiveFields`) is encryption **at rest**,
|
|
|
|
|
+> not access control. Sensitive fields are decrypted for every authenticated
|
|
|
|
|
+> reader; the protection is against someone reading data files, snapshots, or
|
|
|
|
|
+> backups, not against a client.
|
|
|
|
|
|
|
|
### Collection Configuration
|
|
### Collection Configuration
|
|
|
|
|
|