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

cluster_name()

@type cluster_name() :: atom()

server_id()

@type server_id() :: {cluster_name(), node()}

user_entry()

Functions

delete(cluster_name)

@spec delete(cluster_name()) :: :ok

Stops and deletes the user store's Raft cluster (removing its on-disk state).

delete_user(server_id, username)

@spec delete_user(server_id(), Malachi.Auth.UserRegistry.username()) ::
  {:ok, Malachi.Auth.UserRegistry.reply()} | {:error, term()}

Deletes a user (idempotent). Machine reply is :ok.

export_users(server_id)

@spec export_users(server_id()) :: {:ok, [map()]} | {:error, term()}

Exports all users (no hashes) from the local replica.

get_principal(server_id, username)

@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.

get_user(server_id, username)

Reads a user as {username, hash, permissions} from the local replica, or {:error, :user_not_found}.

import_users(server_id, users)

@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}}.

import_users_with_roles(server_id, users)

@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.

list_users(server_id)

@spec list_users(server_id()) :: {:ok, [map()]} | {:error, term()}

Lists all users (no hashes) from the local replica.

put_user(server_id, username, hash, permissions)

Inserts a user. Machine reply is :ok or {:error, :user_exists}.

put_user(server_id, username, hash, permissions, role)

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.

reconcile(cluster_name, nodes)

@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.

set_role(server_id, username, role)

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.

start(cluster_name, nodes \\ [node()])

@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).

update_password(server_id, username, new_hash)

Updates a user's password hash. Machine reply is :ok or {:error, :user_not_found}.