MishkaCmsCore.Accounts.Helper (Mishka CMS Core v0.0.1)

Copy Markdown View Source

Helper functions for account management including user role checks and email domain configuration for multi-tenant environments.

Summary

Types

A surface, or :any for either one.

Functions

An admin of surface — :dashboard, :api, or :any (the default) for either one.

An API admin: holds anything under admin:api, whatever their site.

The link a sign-in email carries: <site>/auth/<path>/<token>/, marked for the sign-in pool the token belongs to — a master's when there is no tenant.

Builds a complete URL structure with schema, domain, and port for email link generation, handling localhost development environments.

A dashboard admin: holds anything under admin:dashboard, whatever their site.

A full admin of surface: holds admin:<surface>:* or a wildcard covering it — never a set of narrower names.

Determines the appropriate email sender domain based on user/email and tenant context for multi-site email delivery. Uses frontend_domain if available, otherwise falls back to host.

The word a screen shows for an account it does not name, in the mishka_gervaz Gettext domain: :master "Master", :guest "Guest" (written signed out), :system "System" (no user did it), :unknown "Unknown" (an account the reader's read did not return) and :deleted "Deleted user" (a person's work whose account has been deleted, so its author was released).

A master admin: a user whose site_id is nil and who is an admin of surface (admin?/2), either surface by default. A permission never makes a master.

The ids among user_ids that belong to a master, as a MapSet, in one read.

Whether user is a master: a user whose site_id is nil.

Whether a sign-in email — a magic link, a password reset, a confirmation — may go to user_or_email for the tenant in context.

The names of the catalogue rows whose ids are permission_ids, read with no authorization. An id that names no row names nothing.

Who may see every site's jobs — Oban Web and the Sites screen's job panel: a master (master_user?/1) who is a full dashboard admin (full_admin?/2). A site's full admin never is.

The ids of the permissions the account user_id holds now, as a MapSet of strings, read with no authorization: what an account holds is not a question of who asks. Empty for no account.

The names of the permissions the account user_id holds now, read with no authorization, as permission_ids/1 reads their ids. Empty for no account.

Sends one of the sign-in emails through MishkaCmsCore.Mail, from the scope the tenant names — the installation's for a master — with that scope's provider and sender. email holds :purpose, :token, :subject, :html and :text. The token names the delivery, hashed, so a retry is never sent twice and the token itself goes nowhere but the body. Always :ok: AshAuthentication ignores a sender's answer, and an email is never worth failing the sign-in.

An admin of site_id: a user of that site who is an admin of surface (admin?/2), for that site only. surface defaults to :dashboard; pass :api for a request to the admin API, or :any for either.

user when reader may see its email, else nil.

What a screen calls whoever did something.

Types

admin_surface()

@type admin_surface() :: :dashboard | :api | :any

A surface, or :any for either one.

Functions

admin?(user, surface \\ :any)

@spec admin?(map() | nil, admin_surface()) :: boolean()

An admin of surface — :dashboard, :api, or :any (the default) for either one.

api_admin?(user)

@spec api_admin?(map() | nil) :: boolean()

An API admin: holds anything under admin:api, whatever their site.

auth_link(domain, path, token, tenant)

@spec auth_link(String.t(), String.t(), String.t(), term()) :: String.t()

The link a sign-in email carries: <site>/auth/<path>/<token>/, marked for the sign-in pool the token belongs to — a master's when there is no tenant.

dashboard_admin?(user)

@spec dashboard_admin?(map() | nil) :: boolean()

A dashboard admin: holds anything under admin:dashboard, whatever their site.

Every predicate below reads the permissions the user was loaded with, so a signed-in user costs no query, and also takes the viewer MishkaCmsCore.Runtime.States.ViewerState caches for a user.

full_admin?(user, atom)

@spec full_admin?(map() | nil, :dashboard | :api) :: boolean()

A full admin of surface: holds admin:<surface>:* or a wildcard covering it — never a set of narrower names.

get_email_sender_domain(user_or_email, context)

Determines the appropriate email sender domain based on user/email and tenant context for multi-site email delivery. Uses frontend_domain if available, otherwise falls back to host.

label(atom)

@spec label(:master | :guest | :system | :unknown | :deleted) :: String.t()

The word a screen shows for an account it does not name, in the mishka_gervaz Gettext domain: :master "Master", :guest "Guest" (written signed out), :system "System" (no user did it), :unknown "Unknown" (an account the reader's read did not return) and :deleted "Deleted user" (a person's work whose account has been deleted, so its author was released).

master_admin?(user, surface \\ :any)

@spec master_admin?(map() | nil, admin_surface()) :: boolean()

A master admin: a user whose site_id is nil and who is an admin of surface (admin?/2), either surface by default. A permission never makes a master.

master_ids(user_ids)

@spec master_ids([Ash.UUID.t() | nil]) :: {:ok, MapSet.t()} | {:error, term()}

The ids among user_ids that belong to a master, as a MapSet, in one read.

Reads with no tenant and selects only :id, so it answers for a site user's screen too, where a master's account does not load. nil ids are ignored, and a list with nothing left in it reads nothing.

master_user?(arg1)

@spec master_user?(map() | nil) :: boolean()

Whether user is a master: a user whose site_id is nil.

False for nil and for anything that does not carry a site_id. Permissions play no part: a site user holding * is not a master.

may_email?(user_or_email, context)

@spec may_email?(map() | String.t(), map() | keyword()) :: boolean()

Whether a sign-in email — a magic link, a password reset, a confirmation — may go to user_or_email for the tenant in context.

A site that is up sends to anyone; an offline site to its own admins and the masters only, of either surface, since a link can be followed on the screens or its token used over the API; an inactive site to nobody. Without a tenant, as when a master signs in, it may. An email held back is announced on auth:email:held with %{to: user_or_email, tenant: tenant}.

names_of(permission_ids)

@spec names_of([String.t()]) :: [String.t()]

The names of the catalogue rows whose ids are permission_ids, read with no authorization. An id that names no row names nothing.

oban_admin?(user)

@spec oban_admin?(map() | nil) :: boolean()

Who may see every site's jobs — Oban Web and the Sites screen's job panel: a master (master_user?/1) who is a full dashboard admin (full_admin?/2). A site's full admin never is.

permission_ids(user_id)

@spec permission_ids(String.t() | nil) :: MapSet.t(String.t())

The ids of the permissions the account user_id holds now, as a MapSet of strings, read with no authorization: what an account holds is not a question of who asks. Empty for no account.

permission_names(user_id)

@spec permission_names(String.t() | nil) :: [String.t()]

The names of the permissions the account user_id holds now, read with no authorization, as permission_ids/1 reads their ids. Empty for no account.

send_auth_email(user_or_email, context, email)

@spec send_auth_email(map() | String.t(), keyword() | map(), keyword()) :: :ok

Sends one of the sign-in emails through MishkaCmsCore.Mail, from the scope the tenant names — the installation's for a master — with that scope's provider and sender. email holds :purpose, :token, :subject, :html and :text. The token names the delivery, hashed, so a retry is never sent twice and the token itself goes nowhere but the body. Always :ok: AshAuthentication ignores a sender's answer, and an email is never worth failing the sign-in.

site_admin?(user, site_id, surface \\ :dashboard)

@spec site_admin?(map() | nil, String.t() | nil, admin_surface()) :: boolean()

An admin of site_id: a user of that site who is an admin of surface (admin?/2), for that site only. surface defaults to :dashboard; pass :api for a request to the admin API, or :any for either.

visible_to(user, arg2)

@spec visible_to(term(), map() | nil) :: map() | nil

user when reader may see its email, else nil.

A master (site_id nil) reads everyone's; a site user reads its own site's. Pass the result to who/4 on a screen that draws an account it did not read as the reader.

who(user, master?, no_account, master_name \\ nil)

@spec who(
  term(),
  boolean(),
  :guest | :system | :unknown | :deleted | String.t(),
  String.t() | nil
) ::
  String.t()

What a screen calls whoever did something.

  • user is the account as the reader's own read returned it: a user, nil, %Ash.NotLoaded{} or %Ash.ForbiddenField{}.
  • master? says whether the account's id is a master's (see master_ids/1).
  • no_account is what to call a record with no readable account: :guest, :system, :unknown or :deleted (see label/1), or the words themselves.
  • master_name replaces label(:master), such as a master's blogger name on a comment.

Returns the email when user carries a readable one, else master_name or label(:master) for a master, else no_account. Never returns any part of an unreadable email.