Malachi. Auth. UserStore
(malachi v0.22.7)
View Source
The cluster's user store: a thin facade over the ra-replicated user cluster
(Malachi.Auth.UserServer). Replaces the old node-local Mnesia store: users now replicate across the
cluster, so a user created on one node exists on every node.
Stateless: the cluster name is fixed and the local server id is {name, node()}. Malachi.Application
starts the ra cluster and a reconciler at boot; this module just routes CRUD to it: writes go through
the log (consensus); reads come from the local replica (:ra.local_query, fast and eventually
consistent). The public API (insert_user/set_role/delete_user/update_password/get_user/
get_principal/list_users/export_users/import_users) keeps Malachi.Auth and its callers agnostic
to the backend.
The console role arrived at machine version 5 (Malachi.Auth.UserRegistry). A write that carries no role
uses the older command shape, so creating or importing a user without a role keeps working on a cluster
still mid upgrade; only a write that carries a role can meet the version refusal.
Summary
Functions
The user cluster's ra cluster name.
Deletes a user (idempotent). Returns :ok.
Exports all users (no hashes) as JSON-serializable maps. Returns {:ok, list}.
Reads a user's permissions and console role (no hash) from the local replica as
%{username, permissions, role}, or {:error, :user_not_found}.
Reads a user as {username, hash, permissions} from the local replica, or {:error, :user_not_found}.
The registry command an import of users (the maps import_users/1 takes) is sent as, and how many
entries were skipped before it for an unknown role. An entry with an unknown role is dropped here, before
the command is chosen, so it cannot push an import that is otherwise free of roles onto the version 5
command, which a cluster still below 5 refuses whole. With no role left, the command is the version 0
{:import_users, entries}.
Imports users from a list of maps (each :username, :password_hash, :permissions and an optional
:role; string keys also accepted, so an export_users/0 round trips). Existing and malformed entries,
including an unknown role, are skipped. When no entry carries a role the older command shape is used, so
an import without roles works on a cluster still below version 5. Returns
{:ok, %{imported: n, skipped: n}}, a version refusal, or {:error, reason}.
Inserts a new user, holding console role when it is not nil. Returns :ok, {:error, :user_exists},
{:error, :invalid_role}, a version refusal (see Malachi.Cluster.MachineVersion.refusal?/1) for a role
on a cluster below version 5, or {:error, reason} when the store is unreachable.
Lists all users (no hashes) as %{username, permissions, role} maps.
Sets a user's console role, or removes it with nil. Returns :ok, {:error, :user_not_found},
{:error, :invalid_role}, a version refusal, or {:error, reason} when the store is unreachable.
Updates a user's password hash. Returns :ok or {:error, :user_not_found}.
Functions
@spec cluster_name() :: atom()
The user cluster's ra cluster name.
Deletes a user (idempotent). Returns :ok.
Exports all users (no hashes) as JSON-serializable maps. Returns {:ok, list}.
Reads a user's permissions and console role (no hash) from the local replica as
%{username, permissions, role}, or {:error, :user_not_found}.
Reads a user as {username, hash, permissions} from the local replica, or {:error, :user_not_found}.
@spec import_command([map()]) :: {{:import_users, [tuple()]} | {:import_users_with_roles, [tuple()]}, non_neg_integer()}
The registry command an import of users (the maps import_users/1 takes) is sent as, and how many
entries were skipped before it for an unknown role. An entry with an unknown role is dropped here, before
the command is chosen, so it cannot push an import that is otherwise free of roles onto the version 5
command, which a cluster still below 5 refuses whole. With no role left, the command is the version 0
{:import_users, entries}.
Imports users from a list of maps (each :username, :password_hash, :permissions and an optional
:role; string keys also accepted, so an export_users/0 round trips). Existing and malformed entries,
including an unknown role, are skipped. When no entry carries a role the older command shape is used, so
an import without roles works on a cluster still below version 5. Returns
{:ok, %{imported: n, skipped: n}}, a version refusal, or {:error, reason}.
@spec insert_user( String.t(), String.t(), [atom()], Malachi.Auth.ConsoleRole.t() | nil ) :: :ok | {:error, term()}
Inserts a new user, holding console role when it is not nil. Returns :ok, {:error, :user_exists},
{:error, :invalid_role}, a version refusal (see Malachi.Cluster.MachineVersion.refusal?/1) for a role
on a cluster below version 5, or {:error, reason} when the store is unreachable.
@spec list_users() :: [map()]
Lists all users (no hashes) as %{username, permissions, role} maps.
@spec set_role(String.t(), Malachi.Auth.ConsoleRole.t() | nil) :: :ok | {:error, term()}
Sets a user's console role, or removes it with nil. Returns :ok, {:error, :user_not_found},
{:error, :invalid_role}, a version refusal, or {:error, reason} when the store is unreachable.
Updates a user's password hash. Returns :ok or {:error, :user_not_found}.