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
Functions
@spec admin?(map() | nil, admin_surface()) :: boolean()
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.
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.
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.
@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).
@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.
@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.
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.
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}.
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.
@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.
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.
@spec who( term(), boolean(), :guest | :system | :unknown | :deleted | String.t(), String.t() | nil ) :: String.t()
What a screen calls whoever did something.
useris 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 (seemaster_ids/1).no_accountis what to call a record with no readable account::guest,:system,:unknownor:deleted(seelabel/1), or the words themselves.master_namereplaceslabel(: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.