Malachi.Auth (malachi v0.8.13)

View Source

Simple authentication system for Malachi. Manages users with username/password credentials.

Summary

Functions

Adds a new user. Permissions: :admin, :produce, :consume

Authenticates a user with username and password (legacy compatibility). Uses a dummy IP address. For production use, prefer authenticate/3 with actual client IP. Returns {:ok, session_token} or {:error, reason}

Authenticates a user with username and password. Returns {:ok, session_token} or {:error, reason}

Returns a specification to start this module under a supervisor.

Generates a random password for username (an admin) and seeds it, but only when generation is enabled (:generate_admin config, set when no admin password is configured) and no such user exists yet. The password is logged once; the replicated store dedups, so on a multi-node boot exactly one node's seed succeeds and announces its password (the others get :user_exists and discard theirs). A no-op when generation is disabled or the admin already exists. Called at boot after seed_default_users/0.

Grants username a per-topic ACL: operation (:produce/:consume) on pattern (an exact topic, or a *-suffixed prefix like "orders.*"). Returns :ok, or {:error, :invalid_acl} for a bad operation/pattern.

Whether the subject has permission (or is :admin). Accepts either a username, looked up in the user table, where an unknown user has no permissions, or a permission list directly.

Lists username's ACL grants as %{operation, resource} maps (resource rendered as its pattern string).

Lists all users (without passwords).

Invalidates a session token (logout).

Parses an ACL operation string into :produce/:consume, or :error. Mapping explicitly (rather than String.to_atom/1) keeps an untrusted client from exhausting the atom table. Shared by the ACL management surfaces (wire ops, dashboard).

Parses a list of permission strings into the allowed permission atoms, or :error if any is unknown (or the input is not a list). The allowed permissions are :admin, :produce, :consume. Mapping explicitly (rather than String.to_atom/1) keeps an untrusted client from exhausting the atom table.

Removes a user.

Revokes a per-topic ACL grant (idempotent). Returns :ok or {:error, reason}.

Starts the auth server, which owns the in-memory user and session ETS tables.

Validates a session token without IP binding (legacy compatibility).

Validates a session token with IP binding. Returns {:ok, %{username: username, permissions: permissions}} or {:error, reason}

Verifies a username/password against the user store without creating a session: the session-less core of authentication, used by Malachi.Auth.PasswordProvider. Returns {:ok, permissions} or {:error, :invalid_password | :user_not_found}.

Functions

add_user(username, password, permissions \\ [:produce, :consume])

Adds a new user. Permissions: :admin, :produce, :consume

authenticate(username, password)

Authenticates a user with username and password (legacy compatibility). Uses a dummy IP address. For production use, prefer authenticate/3 with actual client IP. Returns {:ok, session_token} or {:error, reason}

authenticate(username, password, client_ip, user_agent \\ "")

Authenticates a user with username and password. Returns {:ok, session_token} or {:error, reason}

Parameters

  • username - Username to authenticate
  • password - Password to verify
  • client_ip - Client IP address (tuple) for session binding and audit logging

change_password(username, new_password)

Changes user password.

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

generate_admin_if_absent(username \\ "admin")

@spec generate_admin_if_absent(String.t()) :: :ok

Generates a random password for username (an admin) and seeds it, but only when generation is enabled (:generate_admin config, set when no admin password is configured) and no such user exists yet. The password is logged once; the replicated store dedups, so on a multi-node boot exactly one node's seed succeeds and announces its password (the others get :user_exists and discard theirs). A no-op when generation is disabled or the admin already exists. Called at boot after seed_default_users/0.

grant_acl(username, operation, pattern)

@spec grant_acl(String.t(), atom(), String.t()) :: :ok | {:error, term()}

Grants username a per-topic ACL: operation (:produce/:consume) on pattern (an exact topic, or a *-suffixed prefix like "orders.*"). Returns :ok, or {:error, :invalid_acl} for a bad operation/pattern.

has_permission?(username, permission)

Whether the subject has permission (or is :admin). Accepts either a username, looked up in the user table, where an unknown user has no permissions, or a permission list directly.

list_acls(username)

@spec list_acls(String.t()) :: [%{operation: atom(), resource: String.t()}]

Lists username's ACL grants as %{operation, resource} maps (resource rendered as its pattern string).

list_users()

Lists all users (without passwords).

logout(token)

Invalidates a session token (logout).

parse_acl_operation(arg1)

@spec parse_acl_operation(String.t()) :: {:ok, :produce | :consume} | :error

Parses an ACL operation string into :produce/:consume, or :error. Mapping explicitly (rather than String.to_atom/1) keeps an untrusted client from exhausting the atom table. Shared by the ACL management surfaces (wire ops, dashboard).

parse_permissions(strings)

@spec parse_permissions([String.t()]) :: {:ok, [atom()]} | :error

Parses a list of permission strings into the allowed permission atoms, or :error if any is unknown (or the input is not a list). The allowed permissions are :admin, :produce, :consume. Mapping explicitly (rather than String.to_atom/1) keeps an untrusted client from exhausting the atom table.

remove_user(username)

Removes a user.

revoke_acl(username, operation, pattern)

@spec revoke_acl(String.t(), atom(), String.t()) :: :ok | {:error, term()}

Revokes a per-topic ACL grant (idempotent). Returns :ok or {:error, reason}.

start_link(_)

Starts the auth server, which owns the in-memory user and session ETS tables.

validate_token(token)

Validates a session token without IP binding (legacy compatibility).

validate_token(token, client_ip, user_agent \\ "")

Validates a session token with IP binding. Returns {:ok, %{username: username, permissions: permissions}} or {:error, reason}

Parameters

  • token - Session token to validate
  • client_ip - Current client IP address for binding verification

verify_credentials(username, password)

@spec verify_credentials(String.t(), String.t()) ::
  {:ok, [atom()]} | {:error, :invalid_password | :user_not_found}

Verifies a username/password against the user store without creating a session: the session-less core of authentication, used by Malachi.Auth.PasswordProvider. Returns {:ok, permissions} or {:error, :invalid_password | :user_not_found}.

Runs a dummy hash (Argon2.no_user_verify/0) for an unknown user so the response time does not reveal whether the username exists (timing-attack mitigation). The caller mints the session and maps both error reasons to a single client-facing :invalid_credentials.