Implement M5 media metadata policy hooks and query coverage
This commit is contained in:
+3
-3
@@ -42,9 +42,9 @@ Spec source: `~/marmot/README.md` + MIP-00..05.
|
|||||||
|
|
||||||
## M5 — optional MIP-04 (encrypted media)
|
## M5 — optional MIP-04 (encrypted media)
|
||||||
|
|
||||||
- [ ] Accept/store MIP-04 metadata-bearing events as regular Nostr events
|
- [x] Accept/store MIP-04 metadata-bearing events as regular Nostr events
|
||||||
- [ ] Add policy hooks for media metadata limits and abuse controls
|
- [x] Add policy hooks for media metadata limits and abuse controls
|
||||||
- [ ] Add tests for search/filter interactions with media metadata tags
|
- [x] Add tests for search/filter interactions with media metadata tags
|
||||||
|
|
||||||
## M6 — optional MIP-05 (push notifications)
|
## M6 — optional MIP-05 (push notifications)
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,11 @@ config :parrhesia,
|
|||||||
marmot_require_h_for_group_queries: true,
|
marmot_require_h_for_group_queries: true,
|
||||||
marmot_group_max_h_values_per_filter: 32,
|
marmot_group_max_h_values_per_filter: 32,
|
||||||
marmot_group_max_query_window_seconds: 2_592_000,
|
marmot_group_max_query_window_seconds: 2_592_000,
|
||||||
|
marmot_media_max_imeta_tags_per_event: 8,
|
||||||
|
marmot_media_max_field_value_bytes: 1024,
|
||||||
|
marmot_media_max_url_bytes: 2048,
|
||||||
|
marmot_media_allowed_mime_prefixes: [],
|
||||||
|
marmot_media_reject_mip04_v1: true,
|
||||||
management_auth_required: true
|
management_auth_required: true
|
||||||
],
|
],
|
||||||
features: [
|
features: [
|
||||||
|
|||||||
@@ -11,6 +11,14 @@ defmodule Parrhesia.Policy.EventPolicy do
|
|||||||
| :marmot_group_h_tag_required
|
| :marmot_group_h_tag_required
|
||||||
| :marmot_group_h_values_exceeded
|
| :marmot_group_h_values_exceeded
|
||||||
| :marmot_group_filter_window_too_wide
|
| :marmot_group_filter_window_too_wide
|
||||||
|
| :media_metadata_tags_exceeded
|
||||||
|
| :media_metadata_tag_value_too_large
|
||||||
|
| :media_metadata_url_too_long
|
||||||
|
| :media_metadata_invalid_url
|
||||||
|
| :media_metadata_invalid_hash
|
||||||
|
| :media_metadata_invalid_mime
|
||||||
|
| :media_metadata_mime_not_allowed
|
||||||
|
| :media_metadata_unsupported_version
|
||||||
| :protected_event_requires_auth
|
| :protected_event_requires_auth
|
||||||
| :protected_event_pubkey_mismatch
|
| :protected_event_pubkey_mismatch
|
||||||
| :pow_below_minimum
|
| :pow_below_minimum
|
||||||
@@ -42,6 +50,7 @@ defmodule Parrhesia.Policy.EventPolicy do
|
|||||||
fn -> reject_if_event_banned(event) end,
|
fn -> reject_if_event_banned(event) end,
|
||||||
fn -> enforce_pow(event) end,
|
fn -> enforce_pow(event) end,
|
||||||
fn -> enforce_protected_event(event, authenticated_pubkeys) end,
|
fn -> enforce_protected_event(event, authenticated_pubkeys) end,
|
||||||
|
fn -> enforce_media_metadata_policy(event) end,
|
||||||
fn -> enforce_mls_feature_flag(event) end
|
fn -> enforce_mls_feature_flag(event) end
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -68,6 +77,30 @@ defmodule Parrhesia.Policy.EventPolicy do
|
|||||||
def error_message(:marmot_group_filter_window_too_wide),
|
def error_message(:marmot_group_filter_window_too_wide),
|
||||||
do: "rate-limited: kind 445 query window exceeds configured maximum"
|
do: "rate-limited: kind 445 query window exceeds configured maximum"
|
||||||
|
|
||||||
|
def error_message(:media_metadata_tags_exceeded),
|
||||||
|
do: "rate-limited: too many media metadata tags in event"
|
||||||
|
|
||||||
|
def error_message(:media_metadata_tag_value_too_large),
|
||||||
|
do: "invalid: media metadata field value exceeds configured limit"
|
||||||
|
|
||||||
|
def error_message(:media_metadata_url_too_long),
|
||||||
|
do: "invalid: media metadata url exceeds configured limit"
|
||||||
|
|
||||||
|
def error_message(:media_metadata_invalid_url),
|
||||||
|
do: "invalid: media metadata url must be a valid http/https URL"
|
||||||
|
|
||||||
|
def error_message(:media_metadata_invalid_hash),
|
||||||
|
do: "invalid: media metadata x field must be 32-byte lowercase hex"
|
||||||
|
|
||||||
|
def error_message(:media_metadata_invalid_mime),
|
||||||
|
do: "invalid: media metadata mime type is invalid"
|
||||||
|
|
||||||
|
def error_message(:media_metadata_mime_not_allowed),
|
||||||
|
do: "blocked: media metadata mime type is not allowed"
|
||||||
|
|
||||||
|
def error_message(:media_metadata_unsupported_version),
|
||||||
|
do: "blocked: media metadata version is not supported"
|
||||||
|
|
||||||
def error_message(:protected_event_requires_auth),
|
def error_message(:protected_event_requires_auth),
|
||||||
do: "auth-required: protected events require authenticated pubkey"
|
do: "auth-required: protected events require authenticated pubkey"
|
||||||
|
|
||||||
@@ -207,6 +240,167 @@ defmodule Parrhesia.Policy.EventPolicy do
|
|||||||
|
|
||||||
defp lowercase_hex?(_value, _bytes), do: false
|
defp lowercase_hex?(_value, _bytes), do: false
|
||||||
|
|
||||||
|
defp enforce_media_metadata_policy(event) do
|
||||||
|
imeta_tags =
|
||||||
|
event
|
||||||
|
|> Map.get("tags", [])
|
||||||
|
|> Enum.filter(&imeta_tag?/1)
|
||||||
|
|
||||||
|
max_imeta_tags = config_int([:policies, :marmot_media_max_imeta_tags_per_event], 8)
|
||||||
|
|
||||||
|
if max_imeta_tags > 0 and length(imeta_tags) > max_imeta_tags do
|
||||||
|
{:error, :media_metadata_tags_exceeded}
|
||||||
|
else
|
||||||
|
validate_imeta_tags(imeta_tags)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp imeta_tag?(["imeta" | _rest]), do: true
|
||||||
|
defp imeta_tag?(_tag), do: false
|
||||||
|
|
||||||
|
defp validate_imeta_tags(imeta_tags) do
|
||||||
|
Enum.reduce_while(imeta_tags, :ok, fn tag, :ok ->
|
||||||
|
with {:ok, fields} <- parse_imeta_tag(tag),
|
||||||
|
:ok <- validate_imeta_fields(fields) do
|
||||||
|
{:cont, :ok}
|
||||||
|
else
|
||||||
|
{:error, _reason} = error -> {:halt, error}
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp parse_imeta_tag(["imeta" | fields]) when is_list(fields) do
|
||||||
|
if fields != [] and rem(length(fields), 2) == 0 do
|
||||||
|
parsed_fields =
|
||||||
|
fields
|
||||||
|
|> Enum.chunk_every(2)
|
||||||
|
|> Enum.reduce(%{}, fn [key, value], acc -> Map.put(acc, key, value) end)
|
||||||
|
|
||||||
|
{:ok, parsed_fields}
|
||||||
|
else
|
||||||
|
{:error, :media_metadata_tag_value_too_large}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp parse_imeta_tag(_tag), do: {:error, :media_metadata_tag_value_too_large}
|
||||||
|
|
||||||
|
defp validate_imeta_fields(fields) do
|
||||||
|
with :ok <- validate_imeta_value_sizes(fields),
|
||||||
|
:ok <- validate_imeta_url(fields),
|
||||||
|
:ok <- validate_imeta_hash(fields),
|
||||||
|
:ok <- validate_imeta_mime(fields) do
|
||||||
|
validate_imeta_version(fields)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp validate_imeta_value_sizes(fields) do
|
||||||
|
max_value_bytes = config_int([:policies, :marmot_media_max_field_value_bytes], 1024)
|
||||||
|
|
||||||
|
if max_value_bytes <= 0 or Enum.all?(Map.values(fields), &(byte_size(&1) <= max_value_bytes)) do
|
||||||
|
:ok
|
||||||
|
else
|
||||||
|
{:error, :media_metadata_tag_value_too_large}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp validate_imeta_url(fields) do
|
||||||
|
case Map.get(fields, "url") do
|
||||||
|
nil ->
|
||||||
|
:ok
|
||||||
|
|
||||||
|
url ->
|
||||||
|
max_url_bytes = config_int([:policies, :marmot_media_max_url_bytes], 2048)
|
||||||
|
|
||||||
|
cond do
|
||||||
|
max_url_bytes > 0 and byte_size(url) > max_url_bytes ->
|
||||||
|
{:error, :media_metadata_url_too_long}
|
||||||
|
|
||||||
|
valid_http_url?(url) ->
|
||||||
|
:ok
|
||||||
|
|
||||||
|
true ->
|
||||||
|
{:error, :media_metadata_invalid_url}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp validate_imeta_hash(fields) do
|
||||||
|
case Map.get(fields, "x") do
|
||||||
|
nil ->
|
||||||
|
:ok
|
||||||
|
|
||||||
|
hash ->
|
||||||
|
if lowercase_hex?(hash, 32) do
|
||||||
|
:ok
|
||||||
|
else
|
||||||
|
{:error, :media_metadata_invalid_hash}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp validate_imeta_mime(fields) do
|
||||||
|
case Map.get(fields, "m") do
|
||||||
|
nil ->
|
||||||
|
:ok
|
||||||
|
|
||||||
|
mime_type ->
|
||||||
|
allowed_prefixes = config_list([:policies, :marmot_media_allowed_mime_prefixes], [])
|
||||||
|
|
||||||
|
cond do
|
||||||
|
not valid_mime_type?(mime_type) ->
|
||||||
|
{:error, :media_metadata_invalid_mime}
|
||||||
|
|
||||||
|
allowed_prefixes == [] ->
|
||||||
|
:ok
|
||||||
|
|
||||||
|
Enum.any?(allowed_prefixes, &String.starts_with?(mime_type, &1)) ->
|
||||||
|
:ok
|
||||||
|
|
||||||
|
true ->
|
||||||
|
{:error, :media_metadata_mime_not_allowed}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp validate_imeta_version(fields) do
|
||||||
|
case Map.get(fields, "v") do
|
||||||
|
nil ->
|
||||||
|
:ok
|
||||||
|
|
||||||
|
"mip04-v2" ->
|
||||||
|
:ok
|
||||||
|
|
||||||
|
"mip04-v1" ->
|
||||||
|
if config_bool([:policies, :marmot_media_reject_mip04_v1], true) do
|
||||||
|
{:error, :media_metadata_unsupported_version}
|
||||||
|
else
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
|
_other ->
|
||||||
|
{:error, :media_metadata_unsupported_version}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp valid_http_url?(url) when is_binary(url) do
|
||||||
|
case URI.parse(url) do
|
||||||
|
%URI{scheme: scheme, host: host}
|
||||||
|
when scheme in ["http", "https"] and is_binary(host) and host != "" ->
|
||||||
|
true
|
||||||
|
|
||||||
|
_other ->
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp valid_http_url?(_url), do: false
|
||||||
|
|
||||||
|
defp valid_mime_type?(mime_type) when is_binary(mime_type) do
|
||||||
|
String.match?(mime_type, ~r/^[a-z0-9!#$&^_.+\-]+\/[a-z0-9!#$&^_.+\-]+$/)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp valid_mime_type?(_mime_type), do: false
|
||||||
|
|
||||||
defp reject_if_pubkey_banned(event) do
|
defp reject_if_pubkey_banned(event) do
|
||||||
with pubkey when is_binary(pubkey) <- Map.get(event, "pubkey"),
|
with pubkey when is_binary(pubkey) <- Map.get(event, "pubkey"),
|
||||||
{:ok, true} <- Storage.moderation().pubkey_banned?(%{}, pubkey) do
|
{:ok, true} <- Storage.moderation().pubkey_banned?(%{}, pubkey) do
|
||||||
@@ -315,4 +509,14 @@ defmodule Parrhesia.Policy.EventPolicy do
|
|||||||
_other -> default
|
_other -> default
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp config_list([scope, key], default) do
|
||||||
|
case Application.get_env(:parrhesia, scope, []) |> Keyword.get(key, default) do
|
||||||
|
value when is_list(value) ->
|
||||||
|
if Enum.all?(value, &is_binary/1), do: value, else: default
|
||||||
|
|
||||||
|
_other ->
|
||||||
|
default
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -430,6 +430,14 @@ defmodule Parrhesia.Web.Connection do
|
|||||||
:pow_below_minimum,
|
:pow_below_minimum,
|
||||||
:pubkey_banned,
|
:pubkey_banned,
|
||||||
:event_banned,
|
:event_banned,
|
||||||
|
:media_metadata_tags_exceeded,
|
||||||
|
:media_metadata_tag_value_too_large,
|
||||||
|
:media_metadata_url_too_long,
|
||||||
|
:media_metadata_invalid_url,
|
||||||
|
:media_metadata_invalid_hash,
|
||||||
|
:media_metadata_invalid_mime,
|
||||||
|
:media_metadata_mime_not_allowed,
|
||||||
|
:media_metadata_unsupported_version,
|
||||||
:mls_disabled
|
:mls_disabled
|
||||||
],
|
],
|
||||||
do: EventPolicy.error_message(reason)
|
do: EventPolicy.error_message(reason)
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ defmodule Parrhesia.ConfigTest do
|
|||||||
assert Parrhesia.Config.get([:limits, :max_outbound_queue]) == 256
|
assert Parrhesia.Config.get([:limits, :max_outbound_queue]) == 256
|
||||||
assert Parrhesia.Config.get([:limits, :max_filter_limit]) == 500
|
assert Parrhesia.Config.get([:limits, :max_filter_limit]) == 500
|
||||||
assert Parrhesia.Config.get([:policies, :auth_required_for_writes]) == false
|
assert Parrhesia.Config.get([:policies, :auth_required_for_writes]) == false
|
||||||
|
assert Parrhesia.Config.get([:policies, :marmot_media_max_imeta_tags_per_event]) == 8
|
||||||
|
assert Parrhesia.Config.get([:policies, :marmot_media_reject_mip04_v1]) == true
|
||||||
assert Parrhesia.Config.get([:features, :nip_50_search]) == true
|
assert Parrhesia.Config.get([:features, :nip_50_search]) == true
|
||||||
assert Parrhesia.Config.get([:features, :nip_ee_mls]) == false
|
assert Parrhesia.Config.get([:features, :nip_ee_mls]) == false
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -98,6 +98,131 @@ defmodule Parrhesia.Policy.EventPolicyTest do
|
|||||||
EventPolicy.authorize_read([wide_window], MapSet.new())
|
EventPolicy.authorize_read([wide_window], MapSet.new())
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "accepts MIP-04 media metadata events as regular Nostr events" do
|
||||||
|
media_event = %{
|
||||||
|
"kind" => 1,
|
||||||
|
"tags" => [
|
||||||
|
[
|
||||||
|
"imeta",
|
||||||
|
"url",
|
||||||
|
"https://media.example/blob",
|
||||||
|
"m",
|
||||||
|
"image/jpeg",
|
||||||
|
"x",
|
||||||
|
String.duplicate("a", 64),
|
||||||
|
"v",
|
||||||
|
"mip04-v2"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"pubkey" => String.duplicate("d", 64),
|
||||||
|
"id" => ""
|
||||||
|
}
|
||||||
|
|
||||||
|
assert :ok =
|
||||||
|
EventPolicy.authorize_write(media_event, MapSet.new([String.duplicate("d", 64)]))
|
||||||
|
end
|
||||||
|
|
||||||
|
test "enforces media metadata tag limits" do
|
||||||
|
Application.put_env(
|
||||||
|
:parrhesia,
|
||||||
|
:policies,
|
||||||
|
marmot_media_max_imeta_tags_per_event: 1,
|
||||||
|
marmot_media_max_field_value_bytes: 1024,
|
||||||
|
marmot_media_max_url_bytes: 2048,
|
||||||
|
marmot_media_allowed_mime_prefixes: [],
|
||||||
|
marmot_media_reject_mip04_v1: true
|
||||||
|
)
|
||||||
|
|
||||||
|
event = %{
|
||||||
|
"kind" => 1,
|
||||||
|
"tags" => [
|
||||||
|
[
|
||||||
|
"imeta",
|
||||||
|
"url",
|
||||||
|
"https://media.example/1",
|
||||||
|
"m",
|
||||||
|
"image/jpeg",
|
||||||
|
"x",
|
||||||
|
String.duplicate("a", 64)
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"imeta",
|
||||||
|
"url",
|
||||||
|
"https://media.example/2",
|
||||||
|
"m",
|
||||||
|
"image/jpeg",
|
||||||
|
"x",
|
||||||
|
String.duplicate("b", 64)
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"pubkey" => String.duplicate("d", 64),
|
||||||
|
"id" => ""
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {:error, :media_metadata_tags_exceeded} =
|
||||||
|
EventPolicy.authorize_write(event, MapSet.new([String.duplicate("d", 64)]))
|
||||||
|
end
|
||||||
|
|
||||||
|
test "rejects disallowed media mime types and unsupported versions" do
|
||||||
|
Application.put_env(
|
||||||
|
:parrhesia,
|
||||||
|
:policies,
|
||||||
|
marmot_media_max_imeta_tags_per_event: 8,
|
||||||
|
marmot_media_max_field_value_bytes: 1024,
|
||||||
|
marmot_media_max_url_bytes: 2048,
|
||||||
|
marmot_media_allowed_mime_prefixes: ["image/"],
|
||||||
|
marmot_media_reject_mip04_v1: true
|
||||||
|
)
|
||||||
|
|
||||||
|
invalid_mime_event = %{
|
||||||
|
"kind" => 1,
|
||||||
|
"tags" => [
|
||||||
|
[
|
||||||
|
"imeta",
|
||||||
|
"url",
|
||||||
|
"https://media.example/1",
|
||||||
|
"m",
|
||||||
|
"video/mp4",
|
||||||
|
"x",
|
||||||
|
String.duplicate("a", 64)
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"pubkey" => String.duplicate("d", 64),
|
||||||
|
"id" => ""
|
||||||
|
}
|
||||||
|
|
||||||
|
unsupported_version_event = %{
|
||||||
|
"kind" => 1,
|
||||||
|
"tags" => [
|
||||||
|
[
|
||||||
|
"imeta",
|
||||||
|
"url",
|
||||||
|
"https://media.example/1",
|
||||||
|
"m",
|
||||||
|
"image/jpeg",
|
||||||
|
"x",
|
||||||
|
String.duplicate("a", 64),
|
||||||
|
"v",
|
||||||
|
"mip04-v1"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"pubkey" => String.duplicate("d", 64),
|
||||||
|
"id" => ""
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {:error, :media_metadata_mime_not_allowed} =
|
||||||
|
EventPolicy.authorize_write(
|
||||||
|
invalid_mime_event,
|
||||||
|
MapSet.new([String.duplicate("d", 64)])
|
||||||
|
)
|
||||||
|
|
||||||
|
assert {:error, :media_metadata_unsupported_version} =
|
||||||
|
EventPolicy.authorize_write(
|
||||||
|
unsupported_version_event,
|
||||||
|
MapSet.new([String.duplicate("d", 64)])
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
test "rejects mls kinds when feature is disabled" do
|
test "rejects mls kinds when feature is disabled" do
|
||||||
Application.put_env(:parrhesia, :features, nip_ee_mls: false)
|
Application.put_env(:parrhesia, :features, nip_ee_mls: false)
|
||||||
|
|
||||||
|
|||||||
@@ -248,6 +248,44 @@ defmodule Parrhesia.Storage.Adapters.Postgres.EventsQueryCountTest do
|
|||||||
assert {:ok, 0} = Events.count(%{}, filters, requester_pubkeys: [])
|
assert {:ok, 0} = Events.count(%{}, filters, requester_pubkeys: [])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "query/3 combines search and media metadata tag filters" do
|
||||||
|
media_hash = String.duplicate("a", 64)
|
||||||
|
|
||||||
|
matching =
|
||||||
|
persist_event(%{
|
||||||
|
"kind" => 1,
|
||||||
|
"tags" => [
|
||||||
|
["imeta", "url", "https://media.example/blob", "m", "image/jpeg", "x", media_hash],
|
||||||
|
["m", "image/jpeg"],
|
||||||
|
["x", media_hash]
|
||||||
|
],
|
||||||
|
"content" => "photo attachment from group"
|
||||||
|
})
|
||||||
|
|
||||||
|
_wrong_mime =
|
||||||
|
persist_event(%{
|
||||||
|
"kind" => 1,
|
||||||
|
"tags" => [["m", "video/mp4"], ["x", media_hash]],
|
||||||
|
"content" => "photo attachment from group"
|
||||||
|
})
|
||||||
|
|
||||||
|
_wrong_search =
|
||||||
|
persist_event(%{
|
||||||
|
"kind" => 1,
|
||||||
|
"tags" => [["m", "image/jpeg"], ["x", media_hash]],
|
||||||
|
"content" => "document attachment"
|
||||||
|
})
|
||||||
|
|
||||||
|
filters = [
|
||||||
|
%{"kinds" => [1], "search" => "photo", "#m" => ["image/jpeg"], "#x" => [media_hash]}
|
||||||
|
]
|
||||||
|
|
||||||
|
assert {:ok, [result]} = Events.query(%{}, filters, [])
|
||||||
|
assert result["id"] == matching["id"]
|
||||||
|
|
||||||
|
assert {:ok, 1} = Events.count(%{}, filters, [])
|
||||||
|
end
|
||||||
|
|
||||||
test "query/3 supports #i keypackage reference lookups" do
|
test "query/3 supports #i keypackage reference lookups" do
|
||||||
keypackage_ref = String.duplicate("a", 64)
|
keypackage_ref = String.duplicate("a", 64)
|
||||||
|
|
||||||
|
|||||||
@@ -189,6 +189,40 @@ defmodule Parrhesia.Web.ConnectionTest do
|
|||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "unsupported media metadata version EVENT is rejected by policy" do
|
||||||
|
state = connection_state()
|
||||||
|
|
||||||
|
event =
|
||||||
|
valid_event()
|
||||||
|
|> Map.put("kind", 1)
|
||||||
|
|> Map.put("tags", [
|
||||||
|
[
|
||||||
|
"imeta",
|
||||||
|
"url",
|
||||||
|
"https://media.example/blob",
|
||||||
|
"m",
|
||||||
|
"image/jpeg",
|
||||||
|
"x",
|
||||||
|
String.duplicate("a", 64),
|
||||||
|
"v",
|
||||||
|
"mip04-v1"
|
||||||
|
]
|
||||||
|
])
|
||||||
|
|> then(&Map.put(&1, "id", EventValidator.compute_id(&1)))
|
||||||
|
|
||||||
|
payload = Jason.encode!(["EVENT", event])
|
||||||
|
|
||||||
|
assert {:push, {:text, response}, ^state} =
|
||||||
|
Connection.handle_in({payload, [opcode: :text]}, state)
|
||||||
|
|
||||||
|
assert Jason.decode!(response) == [
|
||||||
|
"OK",
|
||||||
|
event["id"],
|
||||||
|
false,
|
||||||
|
"blocked: media metadata version is not supported"
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
test "NEG sessions open and close" do
|
test "NEG sessions open and close" do
|
||||||
state = connection_state()
|
state = connection_state()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user