# `Macaw.Auth.Ash.Strategy.Ldap`
[🔗](https://github.com/dcoai/macaw_auth_ash/blob/v0.3.2/lib/macaw/auth/ash/strategy/ldap.ex#L1)

A directory sign-in as an AshAuthentication strategy: macaw_auth's
`Macaw.Auth.Native.Source.Ldap` (its RFC 4514 and filter escaping, its
TLS rules with `allow_plaintext: false`, its group checks and role
mapping) on an Ash user resource, which it provisions at a user's first
sign-in.

    defmodule MyApp.Accounts.User do
      use Ash.Resource,
        extensions: [AshAuthentication, Macaw.Auth.Ash.Strategy.Ldap], ...

      authentication do
        strategies do
          ldap :nas do
            source {MyApp.Directory, :nas, []}
            identity_field :username
            roles_field :roles
            source_field :source
            display_name_field :display_name
            disabled_field :disabled_at
          end
        end
      end
    end

`source` is the directory's configuration, exactly as a native LDAP
source's (`Macaw.Auth.Native.Source.Ldap.Config`, its `name` defaulting
to the strategy's): a map, or `{module, function, args}` returning one,
called at each sign-in. Prefer the function: a map in the resource is
fixed when it compiles, and a host's directory and its service password
belong to the runtime (`bind_password: {:env, "VAR"}` is resolved at
sign-in either way). A configuration fault raises at sign-in, naming the
key: it is a deployment bug, not a refusal.

**Each field option must name an attribute of the resource.** One that
doesn't is warned of at compile time (the verifier's error, which Spark
reports as a compile warning), refused by `Macaw.Auth.Ash.Config` when
it loads, and never read as unset at sign-in: a source or disabled field
the record lacks raises `KeyError`.

**The DSL entity builds** (unless the resource defines them already):

- `sign_in_with_<name>`: a generic action taking `username` and
  `password`, returning the user. Through AshAuthentication (its plug, or
  `AshAuthentication.Strategy.action/4`) it signs a user token as the
  password strategy does; `Macaw.Auth.Ash` calls `sign_in/4` itself and
  makes its own session.
- `provision_with_<name>`: a create accepting the identity, roles,
  source and display name fields.
- `refresh_with_<name>`: an update accepting the roles and display name.

**Provisioning** (as `Macaw.Auth.Native.Provision`): a directory user
signing in for the first time gets a row, with `source_field` the
directory's name and no password; at every later sign-in the
directory's roles and display name replace the row's, so a user removed
from an admin group loses the role at their next sign-in. A row with the
username that another source owns (a local user, or another directory's)
is never taken over (`:username_taken`), and a disabled row stays
refused (`:disabled`), left as it is. The user resource needs a unique
identity on `identity_field`: two first sign-ins at once then make one
row.

What the directory says beyond these (its DN, entry id, email) is not
stored: a user resource has no standard place for it.

# `t`

```elixir
@type t() :: %Macaw.Auth.Ash.Strategy.Ldap{
  __spark_metadata__: term(),
  disabled_field: term(),
  display_name_field: term(),
  identity_field: term(),
  name: term(),
  provision_action_name: term(),
  refresh_action_name: term(),
  resource: term(),
  roles_field: term(),
  sign_in_action_name: term(),
  source: term(),
  source_field: term(),
  strategy_module: term()
}
```

# `explain_refusal`

```elixir
@spec explain_refusal(atom()) :: String.t()
```

A sentence explaining a refusal's reason, for logs and diagnostics.

# `sign_in`

```elixir
@spec sign_in(t(), String.t(), String.t(), keyword()) ::
  {:ok, Ash.Resource.record()} | {:error, atom(), boolean()}
```

Checks a password against the directory and provisions the user:
`{:ok, record}`, or `{:error, reason, knows?}`, where `knows?` says
whether the refusal is final (`Macaw.Auth.Native.Source.Ldap.knows?/2`;
a provisioning refusal always is). `reason` is the source's
(`explain_refusal/1`), or `:username_taken` or
`:disabled`: for logs, never for the visitor.

# `source!`

```elixir
@spec source!(t()) :: Macaw.Auth.Native.Source.Ldap.t()
```

The directory's configuration, parsed: raises `ArgumentError` naming a fault.

---

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