wip(rls): ScopedDB + rls package + schema policies + echo bridge [pin compiling state]
Phase 3 of the RLS plan, pinned mid-flight because a parallel writer
was actively editing cmd/server/tests/rls_visibility_test.go (A14):
the file flip-flopped between two implementations and gained a compile
error after the previous commit. This snapshot makes the whole tree
compile; functional verification (two-boot gate, invariant green,
full suite) lands in the next commit(s).
- internal/rls: typed ctx keys — user identity (bridged from echo by
the router) and an INTERNAL marker whose semantics grant nothing:
no GUC is declared, so policy'd tables still raise at the engine;
only RLS-ungoverned tables (system_settings, users counts) work for
user-less startup paths (settings registry Load, CountAdmins).
- internal/database/scoped.go: DBTX decorator — per-statement tx,
commit-on-Close (:many) / commit-on-Scan (:one), error paths always
terminate the tx, refusal before SQL when no identity and not
internal.
- database/schema/schema.sql: idempotent policy block (DROP IF EXISTS
+ CREATE — schema.sql re-runs every boot): ENABLE + FORCE, read
policy exactly mirroring COALESCE(is_visible,true), permissive
per-command write stand-ins (FOR ALL would OR into SELECT).
- router.go: RLS bridge at BOTH c.Set("user") sites (JWT + device
auth) — echo-local storage never reaches the stdlib ctx.
- main.go + test harness: database.New over ScopedDB at both wiring
sites; startup reads use rls.WithInternal.
This commit is contained in:
1 parent
a19d7a4cbf
commit
f4b581872f
7 files changed
+337
-9
No files matched your search
@@ -6,6 +6,7 @@ import (
|
||||
"bookhoard/internal/handlers"
|
||||
"bookhoard/internal/middleware"
|
||||
ratelimit "bookhoard/internal/middleware"
|
||||
"bookhoard/internal/rls"
|
||||
"bookhoard/internal/services"
|
||||
"bookhoard/internal/sync"
|
||||
"bookhoard/templates"
|
||||
@@ -155,6 +156,12 @@ func createOptionalAuthMiddleware(cfg *Config) echo.MiddlewareFunc {
|
||||
Timezone: liveUser.Timezone,
|
||||
Theme: liveUser.Theme, // SSR body class; see the required middleware
|
||||
})
|
||||
// RLS bridge: echo's c.Set is echo-local and never reaches
|
||||
// the stdlib context.Context the database layer receives —
|
||||
// ScopedDB refuses media queries without this (plan §5.1).
|
||||
c.SetRequest(c.Request().WithContext(context.WithValue(
|
||||
c.Request().Context(), rls.CtxKeyUserID,
|
||||
pgtype.UUID{Bytes: userUUID, Valid: true})))
|
||||
return next(c)
|
||||
}
|
||||
}
|
||||
@@ -222,6 +229,12 @@ func createJWTMiddleware(cfg *Config) echo.MiddlewareFunc {
|
||||
// admin/docs theme flash.
|
||||
Theme: liveUser.Theme,
|
||||
})
|
||||
// RLS bridge (same as the JWT path above): echo-local c.Set
|
||||
// never reaches the stdlib context.Context the database
|
||||
// layer receives — ScopedDB refuses media queries without it.
|
||||
c.SetRequest(c.Request().WithContext(context.WithValue(
|
||||
c.Request().Context(), rls.CtxKeyUserID,
|
||||
pgtype.UUID{Bytes: userUUID, Valid: true})))
|
||||
return nil
|
||||
},
|
||||
ErrorHandler: func(c *echo.Context, err error) error {
|
||||
|
||||
Reference in new issue
Block a user