10 Commits
Author SHA1 Message Date
Guarzo b0593de99a refactor(api): normalize policy style and split out duplication fixes
Review cleanups, no behavior change to the authorization model.

* Normalize the trusted-actor bypass to `MapScoped.trusted()` in all 18
  resources. Half previously used the bare `MapScoped.Trusted` module and half
  the helper. The blocks are meant to be scanned as one repeated template, so
  an inconsistent first line defeats the point.

* Drop the `WriteDirect` check. After update/destroy moved to filter checks it
  had a single caller, `MapDefaultSettings.create`, where it is equivalent to
  `create_map_matches_token/0`: map_id is `allow_nil? false` with no
  InjectMapFromActor, so the "absent map_id" branch is unreachable and an
  omitted map_id fails validation before persisting. Removes ~35 lines and one
  concept from the policy module. Comments in map.ex / map_system.ex /
  map_connection.ex that referenced it by name were updated.

* Document why CreateMapMatchesToken reads params before the attribute and
  checks both key types. This is the check enforcing the foreign-map_id
  breaking change, and the ordering is load-bearing: InjectMapFromActor
  overwrites the attribute with the token's map before policies run, so
  reading only the attribute would compare the token map against itself and
  authorize every foreign map_id.

The map-duplication fixes that were previously bundled here have moved to
their own branch (fix/map-duplication-attrs) -- they are unrelated to /api/v1
scoping and fix a bug that reproduces on main.
2026-08-01 20:09:48 +00:00
GuarzoandClaude Opus 5 ddc03ffbe0 style: mix format map_scoped_test
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 18:32:09 +00:00
Guarzo 0c0c90dce4 test(api): internal non-breakage + non-vacuous includes coverage
Two guards closing out the authz work.

internal_authz_regression_test.exs exercises the real internal call sites
that pass an `actor:`, so tightening a policy later cannot silently break the
app's own paths. Each test names the production site it mirrors.

Two corrections to the planned tests, both from reading the callers:

  * Map.duplicate is invoked with conn.assigns[:current_character], a
    Character (map_api_controller.ex:1276), and the :duplicate action sets
    owner_id from context.actor.id. Map.owner is a belongs_to Character, so a
    User actor would write a User id into a Character FK. The test uses a
    Character and asserts owner_id comes back correct.
  * :available is never called with a Character. Its only callers
    (Maps.get_available_maps/1, Acls.get_available_acls/1) pass a User, and
    FilterMapsByRoles reads actor.characters, which a Character struct lacks.
    The suite reloads the User with `load: :characters` the way
    CheckJsonApiAuth does, and covers the Character bypass via the write and
    duplicate tests instead.

authz_includes_leak_test.exs covers `include=`, the one path that reaches a
resource without going through its own route. Every refutation is paired
with a positive control so it cannot pass vacuously.

Note Character is NOT an AshJsonApi resource (extensions: [AshCloak] only),
so ?include=owner emits a resource identifier with "type": null and an empty
attributes object -- no Character field is serialized at all. The negative
assertion is therefore about sensitive attributes being absent rather than
about the type name, which the planned test would have asserted and which
would never have matched.
2026-08-01 17:15:33 +00:00
Guarzo 4383437b90 test(api): assert on raw InTokenMap filter value, not just query round-trip
MapSystem's primary :read action has its own always-on
FilterSystemsByActorMap preparation, which independently scopes every
read to the actor's map. The prior InTokenMap filter/3 test round-tripped
through that action, so it measured the preparation rather than the
check under test — stubbing InTokenMap.build_filter/2 to expr(true)
left the suite green. Assert directly on the raw filter/3 return value
instead. Verified by actually stubbing build_filter/2 to always-match,
confirming the new assertion fails, then reverting.
2026-08-01 17:15:06 +00:00
Guarzo b8ae9cb6ee fix(api): replace broken Ash.Expr.ref/2 filters with nested keyword filters
InTokenMap/ParentInTokenMap called Ash.Expr.ref/2 with reversed args
and, even fixed, dynamic relationship-path refs don't resolve through
filter hydration. Build filters as nested keyword lists instead (a
form FilterCheck.filter/3 accepts directly and Ash resolves natively
through relationships). Also fixes CreateParentInTokenMap's internal
parent lookup, which silently returned no rows without an actor due
to MapSystem's actor-scoped read preparation. Adds direct filter/3
tests against real queries/DB rows for both FilterCheck modules plus
positive/negative CreateParentInTokenMap coverage.
2026-08-01 17:15:05 +00:00
Guarzo 771fa408db feat(api): add MapScoped bypass/read/write policy checks 2026-08-01 17:15:04 +00:00
Dmitry Popov a872561b18 chore: fix tests issues 2025-11-24 11:33:08 +01:00
Dmitry Popov 857608f8ef chore: fix tests issues 2025-11-23 22:43:59 +01:00
Guarzo 09444596ff fix: apiv1 token auth and structure fixes 2025-11-18 20:10:06 +00:00
Guarzo d732d15ef6 feature: disable sse by default 2025-11-15 12:46:03 +00:00