feat: /v1/role list, get, create, update and delete. Reassign members on delete. Write-time validation (ValidateGrants, duplicates, typed keys against real content types). Name uniqueness (role_name_taken), role_locked, role_has_members, role_conflict on version mismatch. No-escalation: nobody grants a permission they don't hold, and roles.edit_owner_only guards the Owner role. Activity events for role changes. Typed-key exposure in /auth/me. Cleanup of typed rows when a content type is deleted. | Backend only. Will carry the deferred ListGrants decision.

This commit is contained in:
Florian Sylvain committed 2026-10-27 00:45:24 +01:00
1 parent a043f7f41a
commit 094df63a7f
51 files changed
+4822 -115

No files matched your search

+44
View File
@@ -0,0 +1,44 @@
package role
import (
"slices"
"strings"
)
func NormalizeName(name string) (string, error) {
trimmed := strings.TrimSpace(name)
if trimmed == "" {
return "", ErrInvalidRoleName
}
return trimmed, nil
}
func SameName(left string, right string) bool {
return strings.EqualFold(strings.TrimSpace(left), strings.TrimSpace(right))
}
func ValidateGrantTypes(grants []Grant, allowedTypeKeys []string) error {
for _, grant := range grants {
if !isTypedKey(grant.Key) {
continue
}
_, typeKey, err := ParseTypedKey(grant.Key)
if err != nil {
return err
}
if !slices.Contains(allowedTypeKeys, typeKey) {
return ErrUnknownContentType
}
}
return nil
}
func TypedKeys(typeKey string) []string {
keys := []string{}
for _, permission := range catalogue {
if permission.PerType() {
keys = append(keys, permission.Key+typeSeparator+typeKey)
}
}
return keys
}