tablo_core/auth/config.rs
1//! [`Auth`]: the panel's authentication configuration, from the authenticator to the pages and
2//! mail it adds.
3
4use std::{marker::PhantomData, sync::Arc};
5
6use super::{
7 AdminUser, AuthMail, Authenticator, DynAuthenticator, EmailVerification, LoginThrottle,
8 PanelUser, PasswordAuth, PasswordReset, ProfileEditor, Registrar, TenantEditor,
9 TenantRegistration, TwoFactor,
10 external::{DynExternal, ExternalAuth, ExternalUsers},
11 profile::DynProfileEditor,
12 registration::DynRegistrar,
13 tenant_profile::DynTenantEditor,
14};
15
16/// The panel's authentication configuration (ADR-0013).
17///
18/// The default is [`Auth::password`]; [`Auth::custom`] installs an app-owned
19/// [`Authenticator`]; [`Auth::external`] hands sign-in to the host app; [`Auth::disabled`] is the
20/// explicit fail-open opt-out.
21/// Sign-in attempts are throttled per login and address by default; see [`Auth::throttle`].
22/// [`Auth::account_throttle`] adds an opt-in count per login across addresses.
23/// [`Auth::registration`] adds a sign-up page, [`Auth::profile`] a profile page, and
24/// [`Auth::password_reset`] a forgot-password page that mails reset links through
25/// [`Auth::mail`], [`Auth::tenant_registration`] a page creating a tenant, and
26/// [`Auth::tenant_profile`] a page editing one.
27///
28/// `U` is the user type the authenticator loads. Every seam that reads or writes a user, from the
29/// registrar to the tenant editor, names the same type, so a seam over another user type does not
30/// compile.
31pub struct Auth<U = AdminUser> {
32 pub(super) config: AuthConfig,
33 user: PhantomData<fn() -> U>,
34}
35
36impl Auth {
37 /// The shipped Argon2id + [`AdminUser`] authenticator.
38 #[must_use]
39 pub fn password() -> Self {
40 Self::custom(PasswordAuth)
41 }
42
43 /// Explicit fail-open opt-out for public demos (ADR-0013): no gate, no
44 /// login routes, sessions unused.
45 ///
46 /// It is no way to put the panel behind an app's own sign-in: a layer over the prefix leaves
47 /// Topcoat's runtime path open, where the panel's search and other endpoints answer anyone.
48 /// [`Auth::external`] gates both.
49 #[must_use]
50 pub fn disabled() -> Self {
51 Self::new(AuthConfig {
52 authenticator: None,
53 external: None,
54 throttle: LoginThrottle::off(),
55 account_throttle: LoginThrottle::off(),
56 registrar: None,
57 sign_up_throttle: LoginThrottle::off(),
58 profile_editor: None,
59 profile_throttle: LoginThrottle::off(),
60 reset: None,
61 mail: None,
62 verification: None,
63 tenant_registration: None,
64 tenant_editor: None,
65 two_factor: None,
66 impersonation: None,
67 })
68 }
69}
70
71impl<U: PanelUser> Auth<U> {
72 /// An app-owned authenticator over its own user table.
73 #[must_use]
74 pub fn custom<A: Authenticator<User = U>>(authenticator: A) -> Self {
75 Self::new(AuthConfig {
76 authenticator: Some(Box::new(authenticator)),
77 external: None,
78 throttle: LoginThrottle::default(),
79 account_throttle: LoginThrottle::off(),
80 registrar: None,
81 sign_up_throttle: LoginThrottle::default(),
82 profile_editor: None,
83 profile_throttle: LoginThrottle::default(),
84 reset: None,
85 mail: None,
86 verification: None,
87 tenant_registration: None,
88 tenant_editor: None,
89 two_factor: None,
90 impersonation: None,
91 })
92 }
93
94 /// A panel whose users sign in through the host app: no password form, and no credential of
95 /// Tablo's. See [`ExternalAuth`].
96 ///
97 /// Mounting refuses what needs Tablo to own the credentials:
98 /// [`registration`](Self::registration), [`password_reset`](Self::password_reset),
99 /// [`email_verification`](Self::email_verification), [`two_factor`](Self::two_factor) and
100 /// [`impersonation`](Self::impersonation).
101 #[must_use]
102 pub fn external<E: ExternalAuth<User = U>>(external: E) -> Self {
103 let external = Arc::new(external);
104 let mut auth = Self::custom(ExternalUsers(Arc::clone(&external)));
105 auth.config.external = Some(external);
106 auth
107 }
108
109 fn new(config: AuthConfig) -> Self {
110 Self {
111 config,
112 user: PhantomData,
113 }
114 }
115
116 /// How many sign-in attempts one login may make from one client address
117 /// in a window; defaults to five a minute. [`LoginThrottle::off`] counts nothing, for a proxy
118 /// that limits sign-ins itself.
119 #[must_use]
120 pub fn throttle(mut self, throttle: LoginThrottle) -> Self {
121 self.config.throttle = throttle;
122 self
123 }
124
125 /// How many sign-in attempts one login may make from all addresses together in a window;
126 /// off by default. It stops a guesser who rotates addresses, at the cost of letting anyone
127 /// who knows a login refuse its owner until the window ends, again each window; a successful
128 /// sign-in clears it. Turn it on where a guessed password costs more than a locked-out
129 /// owner, with a limit high enough that only a distributed guesser reaches it, such as
130 /// `LoginThrottle::new(50, Duration::from_hours(1))`.
131 #[must_use]
132 pub fn account_throttle(mut self, throttle: LoginThrottle) -> Self {
133 self.config.account_throttle = throttle;
134 self
135 }
136
137 /// Lets visitors create their own account at `{prefix}/register`, through `registrar`; the
138 /// login page links to it.
139 ///
140 /// ```rust
141 /// use tablo_core::{Auth, PasswordSignUp};
142 ///
143 /// let auth = Auth::password().registration(PasswordSignUp::pending());
144 /// # let _ = auth;
145 /// ```
146 ///
147 /// [`PasswordSignUp`](crate::PasswordSignUp) registers an [`AdminUser`](super::AdminUser) from
148 /// a [`SignUp`](super::SignUp), inactive until an administrator approves it or, when built
149 /// with `open`, active and signed in at once. A custom [`Registrar`] decides what a new
150 /// account may do; one whose user may not access the panel lands on the login page.
151 ///
152 /// - The form is any `FormInput`, or the shipped `SignUp`. `#[form(password)]` renders a masked
153 /// control, with a button that shows what was typed, that a refused form renders empty, and
154 /// reads the password as typed, outer spaces included, as the login compares it.
155 /// `#[form(email)]` refuses a malformed address before `validate` runs. A missing field and a
156 /// malformed value answer 422 with the error under the field, as `validate`'s refusals do;
157 /// nothing is written.
158 /// - `new_password_errors` refuses a password under eight characters and a confirmation that
159 /// differs, under the `password` and `password_confirmation` keys.
160 /// - After the commit, an account that may open the panel is signed in, its session rotated as
161 /// a login rotates it, and lands on `next` or the panel's home page. One that may not, such
162 /// as an account awaiting approval, lands on the login page with a notification instead. A
163 /// signed-in user who opens the sign-up page lands on the panel's home page.
164 /// - The registrar's `User` is `U`. Mounting refuses a registration on `Auth::disabled()`, a
165 /// form field named `csrf_token` or `next`, a file field and a relationship choice, whose
166 /// options would list records to a visitor nobody signed in.
167 /// - Throttling. Each client address may submit five sign-ups a minute, counted whether or not
168 /// they create an account; `Auth::sign_up_throttle` changes the limit. Past it the page
169 /// answers 429.
170 ///
171 /// The sign-up page tells a visitor whether an email is taken, which the login page never does:
172 /// an app that must keep its accounts private verifies the address by email before saying so,
173 /// which Tablo does not ship.
174 #[must_use]
175 pub fn registration(mut self, registrar: impl Registrar<User = U>) -> Self {
176 self.config.registrar = Some(Box::new(registrar));
177 self
178 }
179
180 /// How many sign-ups one client address may submit in a window; defaults to five a minute.
181 /// Every sign-up POST that passes the CSRF check and parses counts, whether or not it creates
182 /// an account.
183 #[must_use]
184 pub fn sign_up_throttle(mut self, throttle: LoginThrottle) -> Self {
185 self.config.sign_up_throttle = throttle;
186 self
187 }
188
189 /// Lets the signed-in user edit their own account at `{prefix}/profile`, through `editor`;
190 /// the user menu links to it.
191 ///
192 /// ```rust
193 /// use tablo_core::{Auth, PasswordAuth};
194 ///
195 /// let auth = Auth::password().profile(PasswordAuth);
196 /// # let _ = auth;
197 /// ```
198 ///
199 /// [`PasswordAuth`] edits an [`AdminUser`](super::AdminUser)'s name, email and password through
200 /// a [`Profile`](super::Profile), asking for the current password before the email or the
201 /// password changes.
202 ///
203 /// - The form is any `FormInput`, rendered in the panel's shell. The page shows what the
204 /// input's `write` writes, so `Profile` never shows a password. A disabled control keeps what
205 /// `fill` wrote, whatever the POST carries. A refused save renders the form again with each
206 /// error under its field; nothing is written.
207 /// - `save` answers `ProfileUpdate::Details`, which keeps every session, or
208 /// `ProfileUpdate::Credentials` for a change to what the user signs in with. The user's
209 /// sessions then end on every panel whose authenticator loads the same user type, in the
210 /// transaction that saves, and the browser that saved gets a new session under the tenant it
211 /// acted for. `PasswordAuth` answers `Credentials` for a new email or password.
212 /// - An email another account holds is reported only once the current password verifies, so a
213 /// stolen session cannot probe which addresses have accounts.
214 /// - After the commit the page reloads with a notification.
215 /// - The editor's `User` is `U`. Mounting refuses a profile on `Auth::disabled()`, a form field
216 /// named `csrf_token`, a file field and a relationship choice, whose options no route serves
217 /// the page.
218 /// - Throttling. Each user may submit five saves a minute, from any address, counted whether or
219 /// not they save; `Auth::profile_throttle` changes the limit. Past it the page answers 429,
220 /// which keeps a stolen session from guessing the current password. A user past the limit can
221 /// still sign in.
222 #[must_use]
223 pub fn profile(mut self, editor: impl ProfileEditor<User = U>) -> Self {
224 self.config.profile_editor = Some(Box::new(editor));
225 self
226 }
227
228 /// How many profile saves one user may submit in a window, from any address; defaults to five
229 /// a minute. Every profile POST that passes the CSRF check and parses counts, whether or not
230 /// it saves.
231 #[must_use]
232 pub fn profile_throttle(mut self, throttle: LoginThrottle) -> Self {
233 self.config.profile_throttle = throttle;
234 self
235 }
236
237 /// Lets a visitor who forgot their password ask for a reset link at
238 /// `{prefix}/forgot-password`, mailed through [`Auth::mail`], and choose a new password at
239 /// `{prefix}/reset-password`; the login page links to it.
240 ///
241 /// - The request page answers the same whether an account uses the address or not: it redirects
242 /// to the login page with a notification, and writes and mails the link after answering, so
243 /// the database and the mail server add no time when an account exists. An account that may
244 /// not enter the panel gets no link, and a failed send is logged, not shown.
245 /// - The link goes to the stored address, never the typed one. A lookup that matches loosely,
246 /// such as a collation that folds accents, would otherwise mail one account's link to another
247 /// address.
248 /// - A link works once, for one hour, on the panel that mailed it, and only while the account
249 /// may still enter the panel. A new request replaces the account's earlier link, and a
250 /// password change on the profile page ends it, as it ends every link the account holds. The
251 /// token is stored only as a hash, and the reset page sends no referrer and may not be
252 /// cached.
253 /// - A reset checks the new password with `new_password_errors`, then sets it, ends the
254 /// account's sessions and reset links on every panel whose authenticator loads the same user
255 /// type, and redirects to the login page. All of this commits in one transaction.
256 /// - Throttling. Each address may request three links in fifteen minutes, from any client,
257 /// counted whether or not an account uses it; `PasswordReset::throttle` changes the limit.
258 /// Past it the page answers 429. A limit per client belongs at the proxy.
259 /// - The resetter's `User` is `U`. Mounting refuses a reset on `Auth::disabled()`, a reset
260 /// without `Auth::mail`, a base URL that is not an absolute `http` or `https` URL or that
261 /// carries a query or fragment, and a router without a `Mailer`.
262 #[must_use]
263 pub fn password_reset(mut self, reset: PasswordReset<U>) -> Self {
264 self.config.reset = Some(reset.erase());
265 self
266 }
267
268 /// Makes each signed-in user prove they read the address [`PanelUser::email`] answers before
269 /// they reach anything but `{prefix}/verify-email`, which mails them a link through
270 /// [`Auth::mail`]. A sign-up mails the link at once, after the response.
271 ///
272 /// - Until the user verifies, a page redirects to `{prefix}/verify-email` and any other request
273 /// answers 403. The page offers to mail a link, and to sign out. A user whose
274 /// `PanelUser::email` is `None` has no address to prove and is not held.
275 /// - Accounts that predate verification are unverified: turning it on sends each of them,
276 /// seeded admins included, to `{prefix}/verify-email` at their next request. Backfill
277 /// `email_verified_at` (from `created_at`, say) for the addresses you already trust.
278 /// - A link works once, for one day, on the panel that mailed it, and only while the account
279 /// may enter the panel and still stores the address it was mailed to. It verifies on `GET` in
280 /// any browser, signed in or not, then lands on the panel or on the login page. A mail
281 /// scanner that opens the link verifies the address too: it fetched the link from the mailbox
282 /// the link proves.
283 /// - A new email waits for its own link. A profile editor calls `auth::request_email_change`
284 /// from `save` in place of writing the address: on a panel with email verification it records
285 /// the change and answers `EmailChange::Pending`, and once the save commits the panel mails
286 /// the new address a link that stores it and the old address a link that keeps it. Elsewhere
287 /// it answers `EmailChange::Immediate` and the editor stores the address itself.
288 /// `PasswordAuth` does this. The link proves only that whoever asked reads the new mailbox,
289 /// so ask for the current password before calling it, as `PasswordAuth` does: a stolen
290 /// session could name its thief's mailbox.
291 /// - Either link opens a page whose button does the work, so a mail scanner that fetches it
292 /// changes nothing.
293 /// - The new address's link stores it, verified, once, for one day. It is a credential change:
294 /// the account's sessions and sign-in links end on every panel signing in its user type, and
295 /// the browser that confirmed keeps a new session when it was signed in as the account.
296 /// - The old address's link works for a week, before or after the change goes through: it keeps
297 /// the old address, restoring it if the change went through, and ends every session, sign-in
298 /// link and pending change the account holds, the cancelling browser's included: whoever
299 /// asked for the change may hold one.
300 /// - Throttling. Each account may ask for three links in fifteen minutes;
301 /// `EmailVerification::throttle` changes the limit. Past it the page answers 429.
302 /// - The verifier's `User` is `U`. Mounting refuses a verification on `Auth::disabled()`, and
303 /// one without `Auth::mail`.
304 ///
305 /// [`PanelUser::email`]: super::PanelUser::email
306 #[must_use]
307 pub fn email_verification(mut self, verification: EmailVerification<U>) -> Self {
308 self.config.verification = Some(verification.erase());
309 self
310 }
311
312 /// Lets the signed-in user create a tenant to act for at `{prefix}/register-tenant`, through
313 /// `registration`'s [`TenantRegistrar`](super::TenantRegistrar); the tenant menu links to
314 /// it from one membership on. [`TenantRegistration::required`] sends a user who acts for no
315 /// tenant there first.
316 ///
317 /// The registrar's `Input` is the page's form; `validate` and `register` run in one
318 /// transaction, and `register` writes the tenant and the user's membership of it, answering
319 /// that [`Membership`](crate::Membership). The session then acts for the new tenant, and the
320 /// next request's [`find_by_id`](crate::Authenticator::find_by_id) must list it.
321 ///
322 /// The registrar's [`User`](super::TenantRegistrar::User) is `U`. Mounting refuses a
323 /// registration on [`Auth::disabled`], a form field named `csrf_token`, a file field and a
324 /// relationship choice.
325 #[must_use]
326 pub fn tenant_registration(mut self, registration: TenantRegistration<U>) -> Self {
327 self.config.tenant_registration = Some(registration.erase());
328 self
329 }
330
331 /// Lets the signed-in user edit the tenant they act for at `{prefix}/tenant/profile`, through
332 /// `editor`; the tenant menu links to it for a user [`TenantEditor::may_edit`] admits, and the
333 /// page answers 403 to anyone else. `fill` loads the form from the tenant, and `validate` and
334 /// `save` run in one transaction. The form names the tenant it was filled from, so one posted
335 /// after another tab switched tenants saves nothing.
336 ///
337 /// The editor's [`User`](TenantEditor::User) is `U`. Mounting refuses a settings page on
338 /// [`Auth::disabled`], a form field named `csrf_token` or `editing_tenant`, a file field and a
339 /// relationship choice.
340 #[must_use]
341 pub fn tenant_profile(mut self, editor: impl TenantEditor<User = U>) -> Self {
342 self.config.tenant_editor = Some(Box::new(editor));
343 self
344 }
345
346 /// Lets each signed-in user add an authenticator app at `{prefix}/two-factor`; the user menu
347 /// links to it. An account with one signs in with a code from it, or a recovery code, at
348 /// `{prefix}/two-factor-challenge` after its password.
349 ///
350 /// - Setting up happens at `{prefix}/two-factor`, which the user menu links to. It asks for the
351 /// current password first, through `Authenticator::confirm_password`, so a stolen session
352 /// cannot add a factor its thief holds. That method verifies with the user's
353 /// `PanelUser::email` as the login; override it when users sign in with something else. The
354 /// page then shows a QR code and the key, and turns two-factor sign-in on once a code from
355 /// the app matches. It then shows eight recovery codes, once: the panel stores only their
356 /// hashes, keyed like the secrets.
357 /// - Each panel enrols apart. A factor signs in to the panel that set it up only, even when
358 /// other panels load the same user type: enrol on each.
359 /// - Signing in, a correct password leads to `{prefix}/two-factor-challenge` instead of a
360 /// session. The challenge lasts five minutes in its own cookie and takes a code from the app
361 /// or a recovery code. Each code works once: an app code does not sign in twice, and a
362 /// recovery code is used up.
363 /// - Wrong codes. Every fifth wrong code in a row ends the challenge, so the next try takes the
364 /// password again. Past the fifth, each code of the app or the mail waits for the one before
365 /// it: 30 seconds, doubling up to an hour, across challenges, until a right code; a recovery
366 /// code never waits. Each fifth mails the account, through `Auth::mail`, that its password
367 /// signed in and codes failed. Someone holding the password can so keep the app's codes
368 /// waiting and make the owner spend recovery codes; the mail tells the owner, and a new
369 /// password ends it.
370 /// - Turning it on or off is a credential change: the account's other sessions and links end.
371 /// Turning it off, and showing new recovery codes, take a code.
372 /// - Codes by email. `TwoFactor::new().email_codes()` lets a user with an address choose codes
373 /// mailed to it in place of an app, through `Auth::mail`. Each sign-in mails a six-digit code
374 /// that works once, for ten minutes; the challenge page mails a new one on request, in place
375 /// of the last. Each account may have three mailed in fifteen minutes;
376 /// `TwoFactor::mail_throttle` changes the limit. An account holds one factor at a time: it
377 /// turns one off before setting up the other. A panel that drops `email_codes()` mails no
378 /// more codes: an account set up for them signs in with a recovery code. A mail server that
379 /// fails leaves the challenge up, with its button to send another code.
380 /// - Throttling. Each account may try five codes or passwords a minute, at sign-in and on the
381 /// settings page together; `TwoFactor::throttle` changes the limit. Past it the page answers
382 /// 429. The count lives in each process: behind several, the limit holds per process. Someone
383 /// who knows the password can use up the owner's attempts, and each new sign-in replaces the
384 /// last challenge.
385 /// - The cookie `Key` seals the secrets. Rotating it makes every app code fail until its owner
386 /// signs in with a recovery code and sets the app up again; a leaked key opens every secret.
387 /// - Requiring it. `TwoFactor::new().required()` sends a signed-in user without a second factor
388 /// to `{prefix}/two-factor` before anything else, as an unverified address does: a page
389 /// redirects there and any other request answers 403.
390 /// - Mounting refuses two-factor sign-in on `Auth::disabled()`, a router without a cookie
391 /// `Key`, and codes by email without `Auth::mail`.
392 #[must_use]
393 pub fn two_factor(mut self, two_factor: TwoFactor) -> Self {
394 self.config.two_factor = Some(two_factor);
395 self
396 }
397
398 /// Sends the panel's auth mail, such as reset links, from `mail`'s sender, with links that
399 /// start with its base URL. Mounting refuses a base URL that is not absolute, and a router
400 /// without a [`Mailer`](topcoat::mail::Mailer).
401 #[must_use]
402 pub fn mail(mut self, mail: AuthMail) -> Self {
403 self.config.mail = Some(mail);
404 self
405 }
406
407 /// Whether the explicit opt-out is set.
408 #[must_use]
409 pub fn is_disabled(&self) -> bool {
410 self.config.is_disabled()
411 }
412
413 /// The declaration, its user type checked.
414 pub(crate) fn into_config(self) -> AuthConfig {
415 self.config
416 }
417}
418
419impl Default for Auth {
420 fn default() -> Self {
421 Self::password()
422 }
423}
424
425impl<U> std::fmt::Debug for Auth<U> {
426 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
427 self.config.fmt(f)
428 }
429}
430
431/// What an [`Auth`] declares, with the user type erased once the builder has checked it.
432pub(crate) struct AuthConfig {
433 authenticator: Option<Box<dyn DynAuthenticator>>,
434 external: Option<Arc<dyn DynExternal>>,
435 throttle: LoginThrottle,
436 account_throttle: LoginThrottle,
437 pub(super) registrar: Option<Box<dyn DynRegistrar>>,
438 sign_up_throttle: LoginThrottle,
439 pub(super) profile_editor: Option<Box<dyn DynProfileEditor>>,
440 profile_throttle: LoginThrottle,
441 pub(super) reset: Option<PasswordReset<()>>,
442 pub(super) mail: Option<AuthMail>,
443 pub(super) verification: Option<EmailVerification<()>>,
444 pub(super) tenant_registration: Option<TenantRegistration<()>>,
445 pub(super) tenant_editor: Option<Box<dyn DynTenantEditor>>,
446 pub(super) two_factor: Option<TwoFactor>,
447 pub(super) impersonation: Option<super::impersonation::ImpersonationConfig>,
448}
449
450impl AuthConfig {
451 /// Whether the explicit opt-out is set.
452 pub(crate) fn is_disabled(&self) -> bool {
453 self.authenticator.is_none()
454 }
455
456 /// The authenticator, or `None` when auth is disabled.
457 pub(crate) fn authenticator(&self) -> Option<&dyn DynAuthenticator> {
458 self.authenticator.as_deref()
459 }
460
461 /// The host app's sign-in, when [`Auth::external`] installs it.
462 pub(crate) fn external(&self) -> Option<&dyn DynExternal> {
463 self.external.as_deref()
464 }
465
466 /// The sign-in throttle.
467 pub(crate) fn login_throttle(&self) -> &LoginThrottle {
468 &self.throttle
469 }
470
471 /// The sign-in throttle counted per login across addresses.
472 pub(crate) fn account_throttle_of(&self) -> &LoginThrottle {
473 &self.account_throttle
474 }
475
476 /// The registrar, or `None` without a sign-up page.
477 pub(crate) fn registrar(&self) -> Option<&dyn DynRegistrar> {
478 self.authenticator.as_ref().and(self.registrar.as_deref())
479 }
480
481 /// The sign-up throttle, counted per client address.
482 pub(crate) fn throttle_sign_ups(&self) -> &LoginThrottle {
483 &self.sign_up_throttle
484 }
485
486 /// The profile editor, or `None` without a profile page.
487 pub(crate) fn profile_editor(&self) -> Option<&dyn DynProfileEditor> {
488 self.authenticator
489 .as_ref()
490 .and(self.profile_editor.as_deref())
491 }
492
493 /// The profile throttle, counted per user.
494 pub(crate) fn throttle_profile(&self) -> &LoginThrottle {
495 &self.profile_throttle
496 }
497
498 /// The password reset, or `None` without a forgot-password page.
499 pub(crate) fn reset(&self) -> Option<&PasswordReset<()>> {
500 self.authenticator.as_ref().and(self.reset.as_ref())
501 }
502
503 /// The email verification, or `None` when the panel asks for none.
504 pub(crate) fn verification(&self) -> Option<&EmailVerification<()>> {
505 self.authenticator.as_ref().and(self.verification.as_ref())
506 }
507
508 /// The tenant registration, or `None` without a tenant registration page.
509 pub(crate) fn tenant_registration_page(&self) -> Option<&TenantRegistration<()>> {
510 self.authenticator
511 .as_ref()
512 .and(self.tenant_registration.as_ref())
513 }
514
515 /// The tenant editor, or `None` without a tenant settings page.
516 pub(crate) fn tenant_editor(&self) -> Option<&dyn DynTenantEditor> {
517 self.authenticator
518 .as_ref()
519 .and(self.tenant_editor.as_deref())
520 }
521
522 /// The two-factor sign-in, or `None` when the panel asks for one factor.
523 pub(crate) fn two_factor_config(&self) -> Option<&TwoFactor> {
524 self.authenticator.as_ref().and(self.two_factor.as_ref())
525 }
526
527 /// The auth mail, or `None` when the panel mails nothing.
528 pub(crate) fn auth_mail(&self) -> Option<&AuthMail> {
529 self.authenticator.as_ref().and(self.mail.as_ref())
530 }
531
532 /// The features that send mail, by name, for the mount's checks.
533 pub(super) fn mailing(&self) -> Vec<&'static str> {
534 [
535 self.reset.is_some().then_some("password_reset"),
536 self.verification.is_some().then_some("email_verification"),
537 self.two_factor
538 .as_ref()
539 .is_some_and(|two_factor| two_factor.mails())
540 .then_some("two_factor"),
541 ]
542 .into_iter()
543 .flatten()
544 .collect()
545 }
546
547 /// Whether the panel issues [`AuthToken`](super::AuthToken)s, so the model must be registered:
548 /// it mails links, or challenges a second factor.
549 pub(crate) fn issues_tokens(&self) -> bool {
550 self.authenticator.is_some() && (!self.mailing().is_empty() || self.two_factor.is_some())
551 }
552}
553
554impl std::fmt::Debug for AuthConfig {
555 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
556 if self.is_disabled() {
557 f.write_str("Auth::disabled")
558 } else {
559 f.debug_struct("Auth")
560 .field("throttle", &self.throttle)
561 .field("account_throttle", &self.account_throttle)
562 .field("registration", &self.registrar.is_some())
563 .field("profile", &self.profile_editor.is_some())
564 .field("password_reset", &self.reset)
565 .field("email_verification", &self.verification)
566 .field("tenant_registration", &self.tenant_registration)
567 .field("tenant_profile", &self.tenant_editor.is_some())
568 .field("two_factor", &self.two_factor)
569 .field("impersonation", &self.impersonation)
570 .field("mail", &self.mail)
571 .finish_non_exhaustive()
572 }
573 }
574}