Skip to main content

tablo_core/declaration/
auth.rs

1//! The declaration mistakes of a panel's auth: its sign-up, profile, password-reset,
2//! email-verification, tenant registration and tenant settings pages, and the mail they send.
3
4use std::fmt;
5
6/// Why a panel's sign-up page cannot be served:
7/// [`DeclarationErrorKind::SignUp`](super::DeclarationErrorKind::SignUp).
8#[derive(Debug, Clone, PartialEq, Eq)]
9#[non_exhaustive]
10pub enum SignUpFault {
11    /// `Auth::registration` on `Auth::disabled`, which serves no sign-in to land on.
12    ///
13    /// Turn authentication on.
14    AuthDisabled,
15    /// A field named like a key the sign-up POST carries itself: `csrf_token` or `next`.
16    ///
17    /// Rename the field.
18    ReservedField(String),
19    /// A file field, whose upload the sign-up POST does not read.
20    ///
21    /// Ask for the file once the account signs in.
22    FileField(String),
23    /// A relationship choice, whose options would list records to a visitor nobody signed in.
24    ///
25    /// Offer static options.
26    RelationshipField(String),
27}
28
29/// Why a panel's profile page cannot be served:
30/// [`DeclarationErrorKind::Profile`](super::DeclarationErrorKind::Profile).
31#[derive(Debug, Clone, PartialEq, Eq)]
32#[non_exhaustive]
33pub enum ProfileFault {
34    /// `Auth::profile` on `Auth::disabled`, which signs nobody in to edit.
35    ///
36    /// Turn authentication on.
37    AuthDisabled,
38    /// A field named like a key the profile POST carries itself: `csrf_token`.
39    ///
40    /// Rename the field.
41    ReservedField(String),
42    /// A file field, whose upload the profile POST does not read.
43    ///
44    /// Edit the file on a resource's form.
45    FileField(String),
46    /// A relationship choice, whose options no route serves the profile page.
47    ///
48    /// Offer static options.
49    RelationshipField(String),
50}
51
52/// Why a panel's password reset cannot be served:
53/// [`DeclarationErrorKind::PasswordReset`](super::DeclarationErrorKind::PasswordReset).
54#[derive(Debug, Clone, PartialEq, Eq)]
55#[non_exhaustive]
56pub enum ResetFault {
57    /// `Auth::password_reset` on `Auth::disabled`, which signs nobody in after a reset.
58    ///
59    /// Turn authentication on.
60    AuthDisabled,
61}
62
63impl fmt::Display for ResetFault {
64    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65        match self {
66            Self::AuthDisabled => f.write_str(
67                "a password reset needs auth: `Auth::disabled()` serves no sign-in to reset",
68            ),
69        }
70    }
71}
72
73impl fmt::Display for ProfileFault {
74    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75        match self {
76            Self::AuthDisabled => {
77                f.write_str("a profile page needs auth: `Auth::disabled()` signs nobody in to edit")
78            }
79            Self::ReservedField(field) => write!(
80                f,
81                "the profile form declares a field named '{field}', which its POST carries \
82             itself: rename the field"
83            ),
84            Self::FileField(field) => write!(
85                f,
86                "the profile form declares a file field '{field}', which its POST does not \
87             upload: edit the file on a resource's form"
88            ),
89            Self::RelationshipField(field) => write!(
90                f,
91                "the profile form declares a relationship choice '{field}', whose options no \
92             route serves the profile page: offer static options"
93            ),
94        }
95    }
96}
97
98impl fmt::Display for SignUpFault {
99    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
100        match self {
101            Self::AuthDisabled => f.write_str(
102                "registration needs auth: `Auth::disabled()` serves no sign-in for a new \
103             account to land on",
104            ),
105            Self::ReservedField(field) => write!(
106                f,
107                "the sign-up form declares a field named '{field}', which its POST carries \
108             itself: rename the field"
109            ),
110            Self::FileField(field) => write!(
111                f,
112                "the sign-up form declares a file field '{field}', which its POST does not \
113             upload: ask for the file once the account signs in"
114            ),
115            Self::RelationshipField(field) => write!(
116                f,
117                "the sign-up form declares a relationship choice '{field}', which would list \
118             records to a visitor nobody signed in: offer static options"
119            ),
120        }
121    }
122}
123
124/// Why a panel's auth mail cannot be sent:
125/// [`DeclarationErrorKind::AuthMail`](super::DeclarationErrorKind::AuthMail).
126#[derive(Debug, Clone, PartialEq, Eq)]
127#[non_exhaustive]
128pub enum AuthMailFault {
129    /// A feature that mails links or codes, named as its `Auth` method, on an `Auth` without
130    /// `Auth::mail`.
131    ///
132    /// Install a sender with `Auth::mail(AuthMail::new(..))`.
133    Unset {
134        /// The `Auth` method that installs the feature, such as `password_reset`.
135        feature: &'static str,
136    },
137    /// The base URL links start with is not an absolute `http` or `https` URL.
138    ///
139    /// Pass the scheme and host, such as `https://admin.example.com`.
140    BaseUrl(String),
141    /// The router registers no `Mailer` to send the mail through.
142    ///
143    /// Add one with `.with(Mailer::new(..))` before `.panel(..)`.
144    MissingMail,
145}
146
147impl fmt::Display for AuthMailFault {
148    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
149        match self {
150            Self::Unset { feature } => write!(
151                f,
152                "`Auth::{feature}` sends mail, but the auth sets no sender: install one with \
153             `Auth::mail(AuthMail::new(..))`"
154            ),
155            Self::BaseUrl(url) => write!(
156                f,
157                "the auth mail's base URL '{url}' is not an absolute http or https URL: pass the \
158             scheme and host the app answers on, such as `https://admin.example.com`"
159            ),
160            Self::MissingMail => f.write_str(
161                "the panel's auth mails links, but the router registers no `Mailer`: add \
162             one with `.with(Mailer::new(..))` before mounting the panel",
163            ),
164        }
165    }
166}
167
168/// Why a panel's email verification cannot be served:
169/// [`DeclarationErrorKind::EmailVerification`](super::DeclarationErrorKind::EmailVerification).
170#[derive(Debug, Clone, PartialEq, Eq)]
171#[non_exhaustive]
172pub enum VerificationFault {
173    /// `Auth::email_verification` on `Auth::disabled`, which signs nobody in to verify.
174    ///
175    /// Turn authentication on.
176    AuthDisabled,
177}
178
179impl fmt::Display for VerificationFault {
180    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
181        match self {
182            Self::AuthDisabled => f.write_str(
183                "email verification needs auth: `Auth::disabled()` signs nobody in to verify",
184            ),
185        }
186    }
187}
188
189/// Why a panel's tenant registration page cannot be served:
190/// [`DeclarationErrorKind::TenantRegistration`](super::DeclarationErrorKind::TenantRegistration).
191#[derive(Debug, Clone, PartialEq, Eq)]
192#[non_exhaustive]
193pub enum TenantRegistrationFault {
194    /// `Auth::tenant_registration` on `Auth::disabled`, which signs nobody in to register one.
195    ///
196    /// Turn authentication on.
197    AuthDisabled,
198    /// A field named like a key the registration POST carries itself: `csrf_token`.
199    ///
200    /// Rename the field.
201    ReservedField(String),
202    /// A file field, whose upload the registration POST does not read.
203    ///
204    /// Ask for the file on a resource's form.
205    FileField(String),
206    /// A relationship choice, whose options no route serves the registration page.
207    ///
208    /// Offer static options.
209    RelationshipField(String),
210}
211
212impl fmt::Display for TenantRegistrationFault {
213    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
214        match self {
215            Self::AuthDisabled => f.write_str(
216                "tenant registration needs auth: `Auth::disabled()` signs nobody in to register \
217             a tenant",
218            ),
219            Self::ReservedField(field) => write!(
220                f,
221                "the tenant registration form declares a field named '{field}', which its POST \
222             carries itself: rename the field"
223            ),
224            Self::FileField(field) => write!(
225                f,
226                "the tenant registration form declares a file field '{field}', which its POST \
227             does not upload: ask for the file on a resource's form"
228            ),
229            Self::RelationshipField(field) => write!(
230                f,
231                "the tenant registration form declares a relationship choice '{field}', whose \
232             options no route serves the registration page: offer static options"
233            ),
234        }
235    }
236}
237
238/// Why a panel's tenant settings page cannot be served:
239/// [`DeclarationErrorKind::TenantProfile`](super::DeclarationErrorKind::TenantProfile).
240#[derive(Debug, Clone, PartialEq, Eq)]
241#[non_exhaustive]
242pub enum TenantProfileFault {
243    /// `Auth::tenant_profile` on `Auth::disabled`, which signs nobody in to edit a tenant.
244    ///
245    /// Turn authentication on.
246    AuthDisabled,
247    /// A field named like a key the settings POST carries itself: `csrf_token`.
248    ///
249    /// Rename the field.
250    ReservedField(String),
251    /// A file field, whose upload the settings POST does not read.
252    ///
253    /// Edit the file on a resource's form.
254    FileField(String),
255    /// A relationship choice, whose options no route serves the settings page.
256    ///
257    /// Offer static options.
258    RelationshipField(String),
259}
260
261impl fmt::Display for TenantProfileFault {
262    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
263        match self {
264            Self::AuthDisabled => f.write_str(
265                "a tenant settings page needs auth: `Auth::disabled()` signs nobody in to edit a \
266             tenant",
267            ),
268            Self::ReservedField(field) => write!(
269                f,
270                "the tenant settings form declares a field named '{field}', which its POST \
271             carries itself: rename the field"
272            ),
273            Self::FileField(field) => write!(
274                f,
275                "the tenant settings form declares a file field '{field}', which its POST does \
276             not upload: edit the file on a resource's form"
277            ),
278            Self::RelationshipField(field) => write!(
279                f,
280                "the tenant settings form declares a relationship choice '{field}', whose \
281             options no route serves the settings page: offer static options"
282            ),
283        }
284    }
285}
286
287/// Why the panel's two-factor sign-in cannot be served:
288/// [`DeclarationErrorKind::TwoFactor`](super::DeclarationErrorKind::TwoFactor).
289#[derive(Debug, Clone, PartialEq, Eq)]
290#[non_exhaustive]
291pub enum TwoFactorFault {
292    /// `Auth::two_factor` on `Auth::disabled`, which signs nobody in.
293    ///
294    /// Turn authentication on.
295    AuthDisabled,
296    /// The router registers no cookie `Key`, which seals the authenticator
297    /// secrets.
298    ///
299    /// Call `.with(Key::from(..))` before `.panel(..)`.
300    MissingKey,
301}
302
303impl fmt::Display for TwoFactorFault {
304    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
305        match self {
306            Self::AuthDisabled => f.write_str(
307                "two-factor sign-in needs auth: `Auth::disabled()` signs nobody in to ask a \
308             second factor of",
309            ),
310            Self::MissingKey => f.write_str(
311                "two-factor sign-in seals each authenticator secret under the router's cookie \
312             key, and the router has none: register a `topcoat::cookie::Key` with `.with(..)`",
313            ),
314        }
315    }
316}