# `MishkaCmsCore.Accounts.AuthGate`
[🔗](https://github.com/mishka-group/mishka_cms_core/blob/v0.0.1/lib/mishka_cms_core/accounts/_auth_gate.ex#L5)

The ways into an account a site's admin — or a master, for any site or every site — can close:
`:sign_in`, `:register`, `:reset` (forgot password), `:password` (signing in or registering with
a password) and `:magic_link` (sign-in links). Set on the settings page's Security tab, the last
two as switches in its "Sign-in methods" table; see `definitions/0`.

## Who still gets in

A master (`site_id` nil) always. Anyone else passes a closed scope only by its own "still
allowed" lists: an address on `auth.allowed_emails`, or a permission on `auth.allowed_permissions`
(`*` by default) that the account holds. The installation's switch closes every site, with the
installation's lists; a site's switch closes that site, with the site's lists. Both are asked, so
either can refuse.

## Where it is asked

At the account actions, so the dashboard, the sign-in pages and the API all hear the same answer,
refused with `MishkaCmsCore.Accounts.AuthGate.Closed`:

  * registering and completing a reset — policies on `MishkaCmsCore.Accounts.User` before the
    AshAuthentication bypass (`MishkaCmsCore.Accounts.Checks.AuthGateOpen`), whose
    `error_message` is `refused/3`, the way AshAuthentication's own guide asks for policies on
    its actions;
  * signing in by password, sign-in link or sign-in token — after the password or the token is
    checked (`prepare/3`), so only someone who knew it learns that sign-in is closed, and they
    read that rather than "Incorrect email or password"; the token AshAuthentication had just
    made for them is revoked;
  * asking for a reset or a sign-in link — never refused, the answer is the same for every
    address, but the email goes only to an account the gate lets through.

## Who is already signed in

Closing sign-in leaves them signed in, unless the same scope also turns on
`auth.sign_in.sign_out`: then whoever the gate would not let sign in now is signed out
everywhere (`signs_out?/2`, `sign_out/2`) at their next request — a page
(`MishkaCmsCore.Runtime.Plugs.SiteLookupPlug.confirm_tenant/2`), an API call or a GraphQL socket
(`MishkaCmsCore.Runtime.Plugs.ApiTenantLookupPlug.confirm_bearer_tenant/2`) — and a page already
open reloads on its next navigation or click, or as soon as the switch is saved
(`MishkaCmsCore.Runtime.SiteAvailability`).

## The site being offline

Another question, asked on its own (`MishkaCmsCore.Runtime.SiteAvailability`): a visitor gets in
only when neither refuses. Taking a site offline signs nobody out.

## Plugins

`"auth:gate:check"` hears every decision before it is final: `%{action:, site_id:, user:, email:,
allowed?:}`, and the `allowed?` it hands back is the answer — a captcha or an IP list can close a
way the settings left open, or open one they closed. While its plugins cannot run, its
`cannot_run:` answer closes every way in: what they would close cannot be told.
`"auth:gate:refused"` hears each refusal
(`return: false`), and `"auth:gate:signed_out"` each account `sign_out/2` signs out, with
`%{site_id:, user_id:, email:}` (`return: false`).

# `action`

```elixir
@type action() :: :sign_in | :register | :reset | :password | :magic_link
```

# `pending`

```elixir
@type pending() :: %{optional({String.t() | nil, String.t()}) =&gt; term()}
```

Values about to be saved, by scope (`nil` for Global) and key, read before the saved ones.

# `who`

```elixir
@type who() :: %{optional(:user) =&gt; map() | nil, optional(:email) =&gt; String.t() | nil}
```

# `actions`

```elixir
@spec actions() :: [action()]
```

The ways in a gate can close.

# `allowed?`

```elixir
@spec allowed?(action(), String.t() | nil, who(), pending()) :: boolean()
```

Whether `who` — `%{user: account}` once the account is known, `%{email: address}` before — may
take `action` on `site_id` (`nil` for a master's sign-in), with `pending` read over the saved
settings.

# `check`

```elixir
@spec check([action()], String.t() | nil, who()) ::
  :ok | {:error, MishkaCmsCore.Accounts.AuthGate.Closed.t()}
```

`:ok`, or the refusal for the first of `actions` the gate keeps `who` out of — `"auth:gate:refused"`
told about it.

# `closed?`

```elixir
@spec closed?(action(), String.t() | nil) :: boolean()
```

Whether `action` is closed on `site_id` at all — by the installation or the site — whoever asks.
What a page says before anyone is known.

# `definitions`

```elixir
@spec definitions() :: [MishkaCmsCore.Settings.definition()]
```

The Security tab: what can close, and who still gets in.

# `describe`

```elixir
@spec describe(action()) :: %{title: String.t(), text: String.t()}
```

What to tell whoever was refused, in their language.

# `locks_out?`

```elixir
@spec locks_out?(String.t() | nil, map(), pending()) :: boolean()
```

Whether saving `pending` would shut `user` out of `site_id` when they are not shut out now: sign
them out, or leave them unable to sign in again. The settings page refuses such a save, so an
admin cannot close the way in behind themselves; what someone else's switch already does to them
is not theirs to undo, and is not asked.

# `prepare`

```elixir
@spec prepare(Ash.Query.t(), map(), [action()]) :: Ash.Query.t()
```

The read preparation that refuses a sign-in through a closed gate, once the account is found and
its password or token checked: `prepare &AuthGate.prepare(&1, &2, [:sign_in])`.

# `refusal`

```elixir
@spec refusal(term()) :: %{action: action()} | nil
```

The refusal anywhere inside `reason`, or `nil`: an error or error class, a form's `raw_errors`, or
the JSON:API errors document written for one — the shape of
`MishkaCmsCore.Accounts.AuthRateLimit.limited/1`.

# `refused`

```elixir
@spec refused(term(), map(), action()) :: MishkaCmsCore.Accounts.AuthGate.Closed.t()
```

A gate policy's `error_message`: the refusal for `action`, told to `"auth:gate:refused"` first.

    error_message &MishkaCmsCore.Accounts.AuthGate.refused(&1, &2, :register)

# `sign_out`

```elixir
@spec sign_out(Ash.Resource.Record.t(), String.t() | nil) :: :ok
```

Signs `user` out on every device with AshAuthentication's `log_out_everywhere`, which revokes every
token it holds for them, and cuts their open pages.

# `signs_out?`

```elixir
@spec signs_out?(String.t() | nil, map() | nil, pending()) :: boolean()
```

Whether `user`, signed in on `site_id`, is to be signed out: a scope that closed sign-in there also
signs out who is in, and the gate would not let `user` sign in now. Read from the settings cache
and the permissions `user` carries, so a cached viewer
(`MishkaCmsCore.Runtime.States.ViewerState`) is asked as an account is. `pending` is read over the
saved settings.

# `who`

```elixir
@spec who(term()) :: who()
```

Who an action's changeset is for: the address being registered, or the account being reset.

---

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