Macaw.Auth.Native.Source behaviour (macaw_auth v0.4.1)

Copy Markdown View Source

Where Macaw.Auth.Native checks a password: a behavior, and the chain that tries the configured sources in order.

A source is a struct, made from its configuration by parse/2 and dispatched on its module, so adding one is a module, not a case in the chain. Two ship: Macaw.Auth.Native.Source.Password (local users with a password hash) and Macaw.Auth.Native.Source.Ldap (a directory).

The chain (ported from exio's sign_in/3). The sources are tried in their configured order, and the first that knows the user decides: its acceptance signs them in, and its refusal is final. A wrong password at a source that knows the user never falls through to the next, or one account's password could be guessed against another's. A source that doesn't know the user (knows?/2 of its refusal is false: no such user, a directory it couldn't reach, a direct-bind directory that can't tell a wrong password from a missing user) passes the attempt on.

Who owns the row. A source that keeps the user's row returns it as :record (Password). Any other returns what it knows about the user (:username, :display_name, :roles, :extra) and the chain provisions the row just in time (Macaw.Auth.Native.Provision).

Summary

Types

What a source says about a user it accepted: their row, or what the chain makes one from.

t()

Callbacks

Checks a password: what the source knows about the user, or why not.

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

Whether a refusal with reason means the source knows the user, and so decides; false passes the attempt to the next source.

Makes the source's struct from its configuration (a map with atom or string keys): {:ok, source}, or {:error, "sources[<name>].<key>: …"} naming what is wrong. context carries :repo and :hasher, and :env (the environment, for {:env, VAR} secrets).

Functions

Runs the chain: {:ok, record, source} for the user signed in, or {:error, reason, source} with the deciding source's reason (or the last source's, when none knew the user; source is nil with no sources).

Types

attrs()

@type attrs() ::
  %{record: Macaw.Auth.Native.UserRecord.t()}
  | %{
      username: String.t(),
      display_name: String.t() | nil,
      roles: [String.t()],
      extra: map()
    }

What a source says about a user it accepted: their row, or what the chain makes one from.

t()

@type t() :: struct()

Callbacks

authenticate(t, username, password, opts)

@callback authenticate(
  t(),
  username :: String.t(),
  password :: String.t(),
  opts :: keyword()
) ::
  {:ok, attrs()} | {:error, atom()}

Checks a password: what the source knows about the user, or why not.

explain(reason)

@callback explain(reason :: atom()) :: String.t()

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

knows?(t, reason)

@callback knows?(t(), reason :: atom()) :: boolean()

Whether a refusal with reason means the source knows the user, and so decides; false passes the attempt to the next source.

parse(config, context)

@callback parse(config :: map(), context :: keyword()) ::
  {:ok, t()} | {:error, String.t()}

Makes the source's struct from its configuration (a map with atom or string keys): {:ok, source}, or {:error, "sources[<name>].<key>: …"} naming what is wrong. context carries :repo and :hasher, and :env (the environment, for {:env, VAR} secrets).

Functions

run(sources, repo, username, password, opts)

@spec run([t()], module(), String.t(), String.t(), keyword()) ::
  {:ok, Macaw.Auth.Native.UserRecord.t(), t()} | {:error, atom(), t() | nil}

Runs the chain: {:ok, record, source} for the user signed in, or {:error, reason, source} with the deciding source's reason (or the last source's, when none knew the user; source is nil with no sources).