Add DB constraints for binary identifier lengths

This commit is contained in:
2026-03-18 16:00:07 +01:00
parent bc66dfcbbe
commit a2bdf11139
2 changed files with 107 additions and 0 deletions

View File

@@ -0,0 +1,46 @@
defmodule Parrhesia.Repo.Migrations.AddBinaryIdentifierLengthConstraints do
use Ecto.Migration
@constraints [
{"event_ids", "event_ids_id_length_check", "octet_length(id) = 32"},
{"events", "events_id_length_check", "octet_length(id) = 32"},
{"events", "events_pubkey_length_check", "octet_length(pubkey) = 32"},
{"events", "events_sig_length_check", "octet_length(sig) = 64"},
{"event_tags", "event_tags_event_id_length_check", "octet_length(event_id) = 32"},
{"replaceable_event_state", "replaceable_event_state_pubkey_length_check",
"octet_length(pubkey) = 32"},
{"replaceable_event_state", "replaceable_event_state_event_id_length_check",
"octet_length(event_id) = 32"},
{"addressable_event_state", "addressable_event_state_pubkey_length_check",
"octet_length(pubkey) = 32"},
{"addressable_event_state", "addressable_event_state_event_id_length_check",
"octet_length(event_id) = 32"},
{"banned_pubkeys", "banned_pubkeys_pubkey_length_check", "octet_length(pubkey) = 32"},
{"allowed_pubkeys", "allowed_pubkeys_pubkey_length_check", "octet_length(pubkey) = 32"},
{"banned_events", "banned_events_event_id_length_check", "octet_length(event_id) = 32"},
{"group_memberships", "group_memberships_pubkey_length_check", "octet_length(pubkey) = 32"},
{"group_roles", "group_roles_pubkey_length_check", "octet_length(pubkey) = 32"},
{"management_audit_logs", "management_audit_logs_actor_pubkey_length_check",
"actor_pubkey IS NULL OR octet_length(actor_pubkey) = 32"},
{"acl_rules", "acl_rules_principal_length_check", "octet_length(principal) = 32"}
]
def up do
Enum.each(@constraints, fn {table_name, constraint_name, expression} ->
execute("""
ALTER TABLE #{table_name}
ADD CONSTRAINT #{constraint_name}
CHECK (#{expression})
""")
end)
end
def down do
Enum.each(@constraints, fn {table_name, constraint_name, _expression} ->
execute("""
ALTER TABLE #{table_name}
DROP CONSTRAINT #{constraint_name}
""")
end)
end
end

View File

@@ -0,0 +1,61 @@
defmodule Parrhesia.Storage.Adapters.Postgres.BinaryIdentifierConstraintsTest do
use Parrhesia.IntegrationCase, async: false, sandbox: true
alias Parrhesia.Repo
test "events rejects malformed binary identifier lengths at the database layer" do
assert_check_violation(
"""
INSERT INTO events (created_at, id, pubkey, kind, content, sig, inserted_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, NOW(), NOW())
""",
[
1_700_123_456,
:binary.copy(<<0x10>>, 31),
:binary.copy(<<0x20>>, 32),
1,
"invalid event id length",
:binary.copy(<<0x30>>, 64)
],
"events_id_length_check"
)
end
test "management audit logs allow nil actor pubkeys but reject malformed ones" do
assert {:ok, %{num_rows: 1}} =
Repo.query(
"""
INSERT INTO management_audit_logs (actor_pubkey, method, params, inserted_at)
VALUES ($1, $2, $3, NOW())
""",
[nil, "ping", %{}]
)
assert_check_violation(
"""
INSERT INTO management_audit_logs (actor_pubkey, method, params, inserted_at)
VALUES ($1, $2, $3, NOW())
""",
[:binary.copy(<<0x40>>, 31), "ping", %{}],
"management_audit_logs_actor_pubkey_length_check"
)
end
test "acl rules reject malformed principal lengths at the database layer" do
assert_check_violation(
"""
INSERT INTO acl_rules (principal_type, principal, capability, match, inserted_at)
VALUES ($1, $2, $3, $4, NOW())
""",
["pubkey", :binary.copy(<<0x50>>, 31), "sync_read", %{}],
"acl_rules_principal_length_check"
)
end
defp assert_check_violation(sql, params, constraint_name) do
assert {:error,
%Postgrex.Error{
postgres: %{code: :check_violation, constraint: ^constraint_name}
}} = Repo.query(sql, params)
end
end