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
endsource 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 takingusernameandpassword, returning the user. Through AshAuthentication (its plug, orAshAuthentication.Strategy.action/4) it signs a user token as the password strategy does;Macaw.Auth.Ashcallssign_in/4itself 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.
Summary
Functions
A sentence explaining a refusal's reason, for logs and diagnostics.
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.
The directory's configuration, parsed: raises ArgumentError naming a fault.
Types
@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() }
Functions
A sentence explaining a refusal's reason, for logs and diagnostics.
@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.
@spec source!(t()) :: Macaw.Auth.Native.Source.Ldap.t()
The directory's configuration, parsed: raises ArgumentError naming a fault.