ops design and app

add more comments feature, more tags feature, and other frontend updates
This commit is contained in:
2026-06-09 18:27:59 -04:00
parent 1e939f307d
commit 04a1f2bb6d
33 changed files with 3276 additions and 113 deletions

View File

@@ -409,6 +409,96 @@ func (s *Service) UpdateUserAccess(ctx context.Context, actor Principal, updates
return users, nil
}
func (s *Service) ListResourcePermissions(ctx context.Context, actor Principal, resourceType ResourceType, resourceID string) ([]ResourcePermission, error) {
if actor.UserID == "" {
return nil, fmt.Errorf("authentication required")
}
if actor.Role != RoleAdmin {
permission, err := s.repo.EffectiveResourcePermission(ctx, actor, resourceType, resourceID, nil)
if err != nil {
return nil, err
}
if !PermissionAllows(permission, PermissionAdmin) {
return nil, fmt.Errorf("admin permission required")
}
}
return s.repo.ListResourcePermissions(ctx, resourceType, resourceID)
}
func (s *Service) UpdateResourcePermissions(ctx context.Context, actor Principal, resourceType ResourceType, resourceID string, updates []ResourcePermissionUpdate) ([]ResourcePermission, error) {
if actor.UserID == "" {
return nil, fmt.Errorf("authentication required")
}
if actor.Role != RoleAdmin {
permission, err := s.repo.EffectiveResourcePermission(ctx, actor, resourceType, resourceID, nil)
if err != nil {
return nil, err
}
if !PermissionAllows(permission, PermissionAdmin) {
return nil, fmt.Errorf("admin permission required")
}
}
normalized := make([]ResourcePermissionUpdate, 0, len(updates))
seen := make(map[string]struct{}, len(updates))
for _, update := range updates {
update.UserID = strings.TrimSpace(update.UserID)
if update.UserID == "" {
continue
}
if _, ok := seen[update.UserID]; ok {
return nil, fmt.Errorf("duplicate user permission")
}
seen[update.UserID] = struct{}{}
update.Permission = normalizePermission(update.Permission)
if !validPermission(update.Permission) {
return nil, fmt.Errorf("invalid permission")
}
normalized = append(normalized, update)
}
if err := s.repo.ReplaceResourcePermissions(ctx, resourceType, resourceID, actor.UserID, normalized); err != nil {
return nil, err
}
s.repo.Audit(ctx, actor.UserID, "auth.resource.permissions.update", string(resourceType), resourceID, "", "", map[string]any{
"updates": len(normalized),
})
return s.repo.ListResourcePermissions(ctx, resourceType, resourceID)
}
func (s *Service) EnsureResourcePermission(ctx context.Context, actor Principal, resourceType ResourceType, resourceID string, update ResourcePermissionUpdate) error {
if actor.UserID == "" {
return fmt.Errorf("authentication required")
}
update.UserID = strings.TrimSpace(update.UserID)
if update.UserID == "" {
return fmt.Errorf("user id is required")
}
update.Permission = normalizePermission(update.Permission)
if !validPermission(update.Permission) || update.Permission == PermissionNone {
return fmt.Errorf("invalid permission")
}
if update.UserID != actor.UserID && actor.Role != RoleAdmin {
permission, err := s.repo.EffectiveResourcePermission(ctx, actor, resourceType, resourceID, nil)
if err != nil {
return err
}
if !PermissionAllows(permission, PermissionAdmin) {
return fmt.Errorf("admin permission required")
}
}
if err := s.repo.EnsureResourcePermission(ctx, resourceType, resourceID, actor.UserID, update); err != nil {
return err
}
s.repo.Audit(ctx, actor.UserID, "auth.resource.permissions.ensure", string(resourceType), resourceID, "", "", map[string]any{
"userID": update.UserID,
"permission": update.Permission,
})
return nil
}
func (s *Service) EffectiveResourcePermission(ctx context.Context, principal Principal, resourceType ResourceType, resourceID string, collectionIDs []string) (Permission, error) {
return s.repo.EffectiveResourcePermission(ctx, principal, resourceType, resourceID, collectionIDs)
}
func (s *Service) SendPasswordReset(ctx context.Context, actor Principal, userID string) error {
if !Allows(actor, ScopeAdmin) {
return fmt.Errorf("admin role required")
@@ -715,6 +805,23 @@ func RoleAllows(role Role, required Scope) bool {
}
}
func PermissionAllows(actual Permission, required Permission) bool {
return permissionRank(actual) >= permissionRank(required)
}
func normalizePermission(permission Permission) Permission {
switch Permission(strings.ToLower(strings.TrimSpace(string(permission)))) {
case "view":
return PermissionRead
case "edit":
return PermissionWrite
case PermissionRead, PermissionWrite, PermissionAdmin, PermissionNone:
return Permission(strings.ToLower(strings.TrimSpace(string(permission))))
default:
return Permission(strings.ToLower(strings.TrimSpace(string(permission))))
}
}
func Allows(principal Principal, required Scope) bool {
if principal.UserID == "" {
return false