Malachi. Auth. UserServer
(malachi v0.22.7)
View Source
A thin named facade over Malachi.Cluster.RaCluster for the cluster's user store (Malachi.Auth.UserMachine): start the dedicated
Raft cluster, submit user commands through the log, and read the replicated user set. Mirrors
Malachi.Cluster.LeaseServer; ra must already be running (:ra.start_in/1). This module owns only the
user cluster, not ra's lifecycle.
Writes (put_user/set_role/delete_user/update_password/import_users) go through the log, replicated by
consensus, so every node converges on the same users (unlike the old node-local Mnesia store). They return
{:ok, machine_reply} (e.g. {:ok, :ok} or {:ok, {:error, :user_exists}}) or {:error, reason} when
the cluster is unreachable, {:error, :timeout} after two seconds without a commit.
Reads (get_user/get_principal/list_users/export_users) use :ra.local_query against the local replica:
fast (no consensus round-trip) and adequate for the auth hot paths: credentials once per connection, and
get_principal on every dashboard and console request (Malachi.Console.Access). They are eventually
consistent: a just-written user propagates within replication lag, which is acceptable for auth; a
caller needing linearizable reads can query the leader instead.
Summary
Functions
Stops and deletes the user store's Raft cluster (removing its on-disk state).
Deletes a user (idempotent). Machine reply is :ok.
Exports all users (no hashes) from the local replica.
Reads a user's permissions and console role (no hash) from the local replica.
Reads a user as {username, hash, permissions} from the local replica, or {:error, :user_not_found}.
Bulk-imports users (skips existing). Machine reply is {:ok, %{imported: n, skipped: n}}.
Bulk-imports users with their console roles (machine version 5); otherwise as import_users/2.
Lists all users (no hashes) from the local replica.
Inserts a user. Machine reply is :ok or {:error, :user_exists}.
Inserts a user holding a console role (machine version 5). Machine reply is :ok,
{:error, :user_exists}, {:error, :invalid_role}, or a version refusal on a cluster still below 5.
Ensures this node participates in the user cluster (self-join), so a staggered boot converges to a
fully-replicated user store. Idempotent and best-effort: forms the cluster if not yet formed (start/2,
auto-fenced) and starts the local server if it is not running (:ra.start_server). Meant to be called
periodically until the node has joined. Mirrors LeaseServer.reconcile/2.
Sets (or with nil removes) a user's console role (machine version 5). Machine reply is :ok,
{:error, :user_not_found}, {:error, :invalid_role}, or a version refusal.
Starts the user store's Raft cluster named cluster_name across nodes (default the local node) and
returns a server_id addressing a real member (the local node when it is one, else the first).
Updates a user's password hash. Machine reply is :ok or {:error, :user_not_found}.
Types
@type cluster_name() :: atom()
@type server_id() :: {cluster_name(), node()}
@type user_entry() :: {Malachi.Auth.UserRegistry.username(), Malachi.Auth.UserRegistry.password_hash(), Malachi.Auth.UserRegistry.permissions()}
Functions
@spec delete(cluster_name()) :: :ok
Stops and deletes the user store's Raft cluster (removing its on-disk state).
@spec delete_user(server_id(), Malachi.Auth.UserRegistry.username()) :: {:ok, Malachi.Auth.UserRegistry.reply()} | {:error, term()}
Deletes a user (idempotent). Machine reply is :ok.
Exports all users (no hashes) from the local replica.
@spec get_principal(server_id(), Malachi.Auth.UserRegistry.username()) :: {:ok, Malachi.Auth.UserRegistry.principal()} | {:error, term()}
Reads a user's permissions and console role (no hash) from the local replica.
@spec get_user(server_id(), Malachi.Auth.UserRegistry.username()) :: {:ok, {Malachi.Auth.UserRegistry.username(), Malachi.Auth.UserRegistry.password_hash(), Malachi.Auth.UserRegistry.permissions()}} | {:error, term()}
Reads a user as {username, hash, permissions} from the local replica, or {:error, :user_not_found}.
@spec import_users(server_id(), [user_entry()]) :: {:ok, Malachi.Auth.UserRegistry.reply()} | {:error, term()}
Bulk-imports users (skips existing). Machine reply is {:ok, %{imported: n, skipped: n}}.
@spec import_users_with_roles(server_id(), [Malachi.Auth.UserRegistry.role_entry()]) :: {:ok, Malachi.Auth.UserRegistry.reply()} | {:error, term()}
Bulk-imports users with their console roles (machine version 5); otherwise as import_users/2.
Lists all users (no hashes) from the local replica.
@spec put_user( server_id(), Malachi.Auth.UserRegistry.username(), Malachi.Auth.UserRegistry.password_hash(), Malachi.Auth.UserRegistry.permissions() ) :: {:ok, Malachi.Auth.UserRegistry.reply()} | {:error, term()}
Inserts a user. Machine reply is :ok or {:error, :user_exists}.
@spec put_user( server_id(), Malachi.Auth.UserRegistry.username(), Malachi.Auth.UserRegistry.password_hash(), Malachi.Auth.UserRegistry.permissions(), Malachi.Auth.UserRegistry.role() | nil ) :: {:ok, Malachi.Auth.UserRegistry.reply()} | {:error, term()}
Inserts a user holding a console role (machine version 5). Machine reply is :ok,
{:error, :user_exists}, {:error, :invalid_role}, or a version refusal on a cluster still below 5.
@spec reconcile(cluster_name(), [node()]) :: :ok
Ensures this node participates in the user cluster (self-join), so a staggered boot converges to a
fully-replicated user store. Idempotent and best-effort: forms the cluster if not yet formed (start/2,
auto-fenced) and starts the local server if it is not running (:ra.start_server). Meant to be called
periodically until the node has joined. Mirrors LeaseServer.reconcile/2.
@spec set_role( server_id(), Malachi.Auth.UserRegistry.username(), Malachi.Auth.UserRegistry.role() | nil ) :: {:ok, Malachi.Auth.UserRegistry.reply()} | {:error, term()}
Sets (or with nil removes) a user's console role (machine version 5). Machine reply is :ok,
{:error, :user_not_found}, {:error, :invalid_role}, or a version refusal.
@spec start(cluster_name(), [node()]) :: {:ok, server_id()} | {:error, term()}
Starts the user store's Raft cluster named cluster_name across nodes (default the local node) and
returns a server_id addressing a real member (the local node when it is one, else the first).
@spec update_password( server_id(), Malachi.Auth.UserRegistry.username(), Malachi.Auth.UserRegistry.password_hash() ) :: {:ok, Malachi.Auth.UserRegistry.reply()} | {:error, term()}
Updates a user's password hash. Machine reply is :ok or {:error, :user_not_found}.