security: remove is_system_role from public API

Remove is_system_role from accept lists in create_role and update_role
actions. This field should only be set via seeds or internal actions to
prevent users from creating unkillable roles through the public API.
This commit is contained in:
Moritz 2026-01-06 19:04:03 +01:00
parent 73763b1f58
commit 5f13901ca5
4 changed files with 11 additions and 122 deletions

View file

@ -17,6 +17,10 @@ defmodule Mv.Accounts.User do
# When a member is deleted, set the user's member_id to NULL
# This allows users to continue existing even if their linked member is removed
reference :member, on_delete: :nilify
# When a role is deleted, prevent deletion if users are assigned to it
# This protects critical roles from accidental deletion
reference :role, on_delete: :restrict
end
end

View file

@ -61,14 +61,16 @@ defmodule Mv.Authorization.Role do
create :create_role do
primary? true
accept [:name, :description, :permission_set_name, :is_system_role]
# is_system_role is intentionally excluded - should only be set via seeds/internal actions
accept [:name, :description, :permission_set_name]
# Note: In Ash 3.0, require_atomic? is not available for create actions
# Custom validations will still work
end
update :update_role do
primary? true
accept [:name, :description, :permission_set_name, :is_system_role]
# is_system_role is intentionally excluded - should only be set via seeds/internal actions
accept [:name, :description, :permission_set_name]
# Required because custom validation functions cannot be executed atomically
require_atomic? false
end
@ -85,7 +87,8 @@ defmodule Mv.Authorization.Role do
Mv.Authorization.PermissionSets.all_permission_sets()
|> Enum.map(&Atom.to_string/1)
),
message: "must be one of: own_data, read_only, normal_user, admin"
message:
"must be one of: #{Mv.Authorization.PermissionSets.all_permission_sets() |> Enum.map_join(", ", &Atom.to_string/1)}"
validate fn changeset, _context ->
if changeset.data.is_system_role do

View file

@ -1,118 +0,0 @@
{
"attributes": [
{
"allow_nil?": false,
"default": "fragment(\"gen_random_uuid()\")",
"generated?": false,
"precision": null,
"primary_key?": true,
"references": null,
"scale": null,
"size": null,
"source": "id",
"type": "uuid"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "name",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "description",
"type": "text"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "permission_set_name",
"type": "text"
},
{
"allow_nil?": false,
"default": "false",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "is_system_role",
"type": "boolean"
},
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "inserted_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "updated_at",
"type": "utc_datetime_usec"
}
],
"base_filter": null,
"check_constraints": [],
"custom_indexes": [],
"custom_statements": [],
"has_create_action": true,
"hash": "FFDA74F44B5F11381D4C1F4DACA54901A1E02C3D181A88484AEED4E1ADA21B87",
"identities": [
{
"all_tenants?": false,
"base_filter": null,
"index_name": "roles_unique_name_index",
"keys": [
{
"type": "atom",
"value": "name"
}
],
"name": "unique_name",
"nils_distinct?": true,
"where": null
}
],
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"repo": "Elixir.Mv.Repo",
"schema": null,
"table": "roles"
}

View file

@ -99,7 +99,7 @@
"strategy": null
},
"name": "users_role_id_fkey",
"on_delete": null,
"on_delete": "restrict",
"on_update": null,
"primary_key?": true,
"schema": "public",