fix(security): verify passwords, bind row-security args, fail closed on panic

Verify bcrypt passwords in Direct mode and the shipped procedures, hash on
register/reset, ignore client-supplied roles and level at registration and
drop the password from the jwt_login payload. Legacy cleartext upgrade is
opt-in. Row security templates now bind the user as a parameter, validate
identifiers, attach via common.SelectQuery and fail the request if the
filter cannot be attached. ApplyColumnSecurity and GetRowSecurityTemplate
convert panics to errors and the hooks fail closed. Update audit status.
This commit is contained in:
Hein
2026-09-30 13:44:59 +02:00
parent f9c948ca4e
commit 8a94d884e7
12 changed files with 648 additions and 158 deletions
+17 -10
View File
@@ -38,7 +38,8 @@ func (m *mockSecurityProvider) Authenticate(r *http.Request) (*UserContext, erro
return m.authUser, m.authError
}
func (m *mockSecurityProvider) SetAuthenticateCallback(_ func(r *http.Request) (*UserContext, error)) {}
func (m *mockSecurityProvider) SetAuthenticateCallback(_ func(r *http.Request) (*UserContext, error)) {
}
func (m *mockSecurityProvider) GetColumnSecurity(ctx context.Context, userID int, schema, table string) ([]ColumnSecurity, error) {
return m.columnSecurity, nil
@@ -78,13 +79,13 @@ func TestNewSecurityList(t *testing.T) {
// Test maskString function
func TestMaskString(t *testing.T) {
tests := []struct {
name string
input string
maskStart int
maskEnd int
maskChar string
invert bool
expected string
name string
input string
maskStart int
maskEnd int
maskChar string
invert bool
expected string
}{
{
name: "mask first 3 characters",
@@ -299,12 +300,18 @@ func TestRowSecurityGetTemplate(t *testing.T) {
UserID: 42,
}
result := rowSec.GetTemplate("order_id", nil)
result, args, err := rowSec.GetTemplate("order_id", nil)
if err != nil {
t.Fatalf("GetTemplate() error = %v", err)
}
expected := "order_id IN (SELECT order_id FROM public.orders_access WHERE user_id = 42)"
expected := "order_id IN (SELECT order_id FROM public.orders_access WHERE user_id = ?)"
if result != expected {
t.Errorf("GetTemplate() = %q, want %q", result, expected)
}
if len(args) != 1 || args[0] != 42 {
t.Errorf("GetTemplate() args = %v, want [42]", args)
}
}
// Test ClearSecurity