# `LightningWeb.API.Target`
[🔗](https://github.com/OpenFn/lightning/blob/main/lib/lightning_web/api/target.ex#L1)

Resolves the project an API action works on, asks the policy whether the
caller may act there, and only then looks up the record inside it. It takes
the `Lightning.Scope` that `LightningWeb.Plugs.AuthorizeAction` assigned, and
every refusal it returns is one `LightningWeb.API.FallbackController`
answers.

A caller the policy refuses gets the same 403 whether the record exists, is
missing or has an id that isn't a UUID, and only a caller it permits learns
which.

# `get`

```elixir
@type get(record) :: (Ecto.UUID.t(), Ecto.UUID.t() -&gt;
                  {:ok, record} | {:error, :not_found})
```

Looks a record up by id inside a project, given the project's id and the
record's.

# `permission`

```elixir
@type permission() :: {policy :: atom(), action :: atom()}
```

# `subject`

```elixir
@type subject() ::
  LightningWeb.API.Request.param()
  | Lightning.Projects.Project.t()
  | %{:project_id =&gt; Ecto.UUID.t(), optional(any()) =&gt; any()}
```

What identifies a project: an id the request sent, as a
`t:LightningWeb.API.Request.param/0`, or a record loaded from the database,
a `%Project{}` or anything carrying its `project_id`. A bare id is not
accepted, so an id that isn't a UUID is always refused naming where the
request sent it.

# `in_project`

```elixir
@spec in_project(
  Lightning.Scope.t() | Lightning.Scope.actor(),
  subject(),
  permission(),
  LightningWeb.API.Request.param(),
  get(record)
) :: {:ok, record} | {:error, term()}
when record: term()
```

The record `id_param` names in the project `subject` identifies, once the
caller may apply the permission to that project. The id is checked as a UUID
only then, and `get` is given the project's id and the record's.

    Target.in_project(scope, Request.param(conn, "project_id"),
      {:project_users, :edit_workflow},
      Request.param(conn, "id"),
      &Workflows.get_workflow_for_project/2
    )

# `in_project_of`

```elixir
@spec in_project_of(
  Lightning.Scope.t(),
  LightningWeb.API.Request.param(),
  permission(),
  (Ecto.UUID.t() -&gt; {:ok, Ecto.UUID.t()} | {:error, :not_found}),
  get(record)
) :: {:ok, record} | {:error, term()}
when record: term()
```

The record `id_param` names on a route whose parent is the instance, such as
`/api/jobs/:id`, once the caller may apply the permission to the project the
record sits in. `locate` answers the id of that project, and the record is
then looked up in it with `get`, as `in_project/5` does on the nested route.

Only an operator learns that an id names no record (`{:error, :not_found}`)
or isn't a UUID (`{:error, {:invalid_id, location}}`). Anyone else gets
`{:error, :no_standing}` for both. A record in a project the caller may not
apply the permission to is `{:error, :no_standing}` or
`{:error, :forbidden}`. All three answer 403 `forbidden`.

A project deleted between locating it and reading it is
`{:error, :no_such_project}` to a caller who may learn which projects exist,
as `Lightning.Scope.fetch/2` answers it.

    Target.in_project_of(scope, Request.param(conn, "id"),
      {:project_users, :access_project},
      &Jobs.get_project_id/1,
      &Jobs.get_job_for_project/2
    )

# `project`

```elixir
@spec project(Lightning.Scope.t() | Lightning.Scope.actor(), subject(), permission()) ::
  {:ok, Lightning.Projects.Project.t()} | {:error, term()}
```

The project `subject` identifies, once the caller may apply the permission to
it.

A project `subject` doesn't identify is refused as `Lightning.Scope.fetch/2`
refuses it.

    Target.project(conn.assigns.scope, Request.param(conn, "project_id"),
      {:project_users, :access_project}
    )

# `project_or_new`

```elixir
@spec project_or_new(
  Lightning.Scope.t() | Lightning.Scope.actor(),
  LightningWeb.API.Request.param(),
  permission(),
  permission()
) :: {:ok, Lightning.Projects.Project.t()} | {:error, term()}
```

The project `subject` names, as `project/3` answers it, or a new, unsaved
`%Project{}` when no project has that id, or `subject` carries no id, and the
caller holds `create_permission`.

An id that isn't a UUID is `{:error, {:invalid_id, location}}` to a caller
who holds `create_permission`. An id held by a project the caller may not
apply `permission` to is that refusal, never a new project.

# `record`

```elixir
@spec record(
  Lightning.Scope.t(),
  LightningWeb.API.Request.param(),
  permission(),
  (Ecto.UUID.t() -&gt; {:ok, record} | {:error, :not_found})
) :: {:ok, record} | {:error, term()}
when record: term()
```

The record `param` names on a route whose parent is the instance and whose
record sits in no project, such as `/api/credentials/:id`, once the caller
may apply the permission to it. `get` is given the id once it parses as a
UUID and answers `{:ok, record}` or `{:error, :not_found}`.

Only a caller who may apply the permission to the instance, asked of the
policy with a nil subject, learns that an id names nothing
(`{:error, :not_found}`) or isn't a UUID
(`{:error, {:invalid_id, location}}`). Anyone else gets
`{:error, :no_standing}`, as for a record they can't see.

    Target.record(scope, Request.param(conn, "id"),
      {:credentials, :see_credential},
      &fetch_credential/1
    )

---

*Consult [api-reference.md](api-reference.md) for complete listing*
