MishkaCmsCore.Accounts.SignInProvider behaviour (Mishka CMS Core v0.0.1)

Copy Markdown View Source

Signing in with another service's account as mishka_installer plugins on "auth:providers", the way MishkaCmsCore.Captcha.Provider makes captchas. The core names no provider: each ships as its own package (mishka_cms_auth_google, mishka_cms_auth_github, …) whose application starts its plugin, so a host that adds the dependency has the provider on every site's Security tab.

AshAuthentication does the sign-in itself, through one strategy for every provider (MishkaCmsCore.Accounts.ProviderStrategy: /api/auth/user/provider/<id>/request, and one callback for all, /api/auth/user/provider/callback). A plugin says which Assent strategy speaks to its provider (oauth/0) — Assent knows each provider's addresses and how its answer reads — and adds what differs per site: its keys, whether the site offers it, and its button. Every account a provider signs in keeps one MishkaCmsCore.Accounts.UserIdentity for it, named "provider/<id>".

A site that has not switched the provider on, has no keys for it, or whose provider plugin is stopped refuses it before the visitor is sent anywhere.

Settings

The Security tab's "Sign-in methods" table: each provider switched on per site (auth.providers.<id>.enabled) with its keys in its row, and auth.social.register, whether a first sign-in makes an account. A master signs in on the master site's address with the master site's providers. The address each provider sends the visitor back to is callback_url/1, which its console has to list.

use MishkaCmsCore.Accounts.SignInProvider,
  id: "github", label: gettext_noop("GitHub"), priority: 20

@impl true
def oauth, do: %{assent: Assent.Strategy.Github}

Summary

Callbacks

The button's mark: an SVG path in a 24×24 box, and its Tailwind text colour.

The provider's id, in its route and its setting keys: "github".

The name on the button and the settings page, as a gettext_noop/1 msgid.

How it signs in: :assent, the Assent strategy that speaks to the provider (Assent.Strategy.Github, or Assent.Strategy.OAuth2 / Assent.Strategy.OIDC with its addresses for one Assent does not name), and any MishkaCmsCore.Accounts.ProviderStrategy field set over Assent's own defaults for it — base_url, authorize_url, token_url, user_url, authorization_params, and code_verifier: true for PKCE (S256) where the provider takes it.

Whether the provider is offered on this installation. Every provider is, unless it says not.

Whether site_id holds every key the provider needs.

One of its keys for site_id: :client_id or :client_secret.

The provider's own key fields (MishkaCmsCore.Settings.define/3).

Functions

Every registered provider module, the first of each id.

The providers site_id offers, in the installation's order: the buttons its sign-in pages draw.

The address a provider sends a visitor back to on site, which its console has to list: the callback every provider shares, on the site's own address, read from the site record.

The setting that switches provider on for a site.

"auth:providers": a provider plugin adds itself to the providers list it carries.

The offered provider registered under id.

A provider's key field: a :string (or type) setting on the Security tab that each site sets for itself, drawn in the provider's own row of the "Sign-in methods" table while it is on. opts are MishkaCmsCore.Settings.define/3's.

The Security tab's group every way of signing in is switched in.

The providers this installation offers.

Whether site_id offers provider: switched on, with every key it needs.

A site's own value of a provider setting, trimmed; nil when it set none.

Whether a first sign-in with a provider on site_id makes an account there.

The rows of the Security tab's "Sign-in methods" table that are about providers — one switch per provider and whether a first sign-in makes an account — then each provider's keys, drawn while its switch is on. Part of MishkaCmsCore.Settings' core definitions.

Types

site_id()

@type site_id() :: String.t() | nil

Callbacks

icon()

@callback icon() :: %{path: String.t(), color: String.t()}

The button's mark: an SVG path in a 24×24 box, and its Tailwind text colour.

id()

@callback id() :: String.t()

The provider's id, in its route and its setting keys: "github".

label()

@callback label() :: String.t()

The name on the button and the settings page, as a gettext_noop/1 msgid.

oauth()

@callback oauth() :: %{:assent => module(), optional(atom()) => term()}

How it signs in: :assent, the Assent strategy that speaks to the provider (Assent.Strategy.Github, or Assent.Strategy.OAuth2 / Assent.Strategy.OIDC with its addresses for one Assent does not name), and any MishkaCmsCore.Accounts.ProviderStrategy field set over Assent's own defaults for it — base_url, authorize_url, token_url, user_url, authorization_params, and code_verifier: true for PKCE (S256) where the provider takes it.

offered?()

(optional)
@callback offered?() :: boolean()

Whether the provider is offered on this installation. Every provider is, unless it says not.

ready?(site_id)

@callback ready?(site_id()) :: boolean()

Whether site_id holds every key the provider needs.

secret(name, site_id)

@callback secret(name :: atom(), site_id()) :: {:ok, String.t()} | :error

One of its keys for site_id: :client_id or :client_secret.

settings()

@callback settings() :: [MishkaCmsCore.Settings.definition()]

The provider's own key fields (MishkaCmsCore.Settings.define/3).

Functions

all()

@spec all() :: [module()]

Every registered provider module, the first of each id.

available(site_id)

@spec available(site_id()) :: [module()]

The providers site_id offers, in the installation's order: the buttons its sign-in pages draw.

callback_url(map)

@spec callback_url(%{host: String.t()}) :: String.t()

The address a provider sends a visitor back to on site, which its console has to list: the callback every provider shares, on the site's own address, read from the site record.

SignInProvider.callback_url(site) #=> "https://example.com/api/auth/user/provider/callback"

enabled_key(provider)

@spec enabled_key(module() | String.t()) :: String.t()

The setting that switches provider on for a site.

event()

@spec event() :: String.t()

"auth:providers": a provider plugin adds itself to the providers list it carries.

fetch(id)

@spec fetch(String.t() | nil) ::
  {:ok, module()} | {:error, :unknown_provider | :cannot_run}

The offered provider registered under id.

key(key, type \\ :string, label, opts)

A provider's key field: a :string (or type) setting on the Security tab that each site sets for itself, drawn in the provider's own row of the "Sign-in methods" table while it is on. opts are MishkaCmsCore.Settings.define/3's.

methods_group()

@spec methods_group() :: String.t()

The Security tab's group every way of signing in is switched in.

offered()

@spec offered() :: [module()]

The providers this installation offers.

on?(provider, site_id)

@spec on?(module(), site_id()) :: boolean()

Whether site_id offers provider: switched on, with every key it needs.

own(key, site_id)

@spec own(String.t(), site_id()) :: String.t() | nil

A site's own value of a provider setting, trimmed; nil when it set none.

register?(site_id)

@spec register?(site_id()) :: boolean()

Whether a first sign-in with a provider on site_id makes an account there.

settings()

@spec settings() :: [MishkaCmsCore.Settings.definition()]

The rows of the Security tab's "Sign-in methods" table that are about providers — one switch per provider and whether a first sign-in makes an account — then each provider's keys, drawn while its switch is on. Part of MishkaCmsCore.Settings' core definitions.