otp_std/auth/
label.rs

1//! Authentication labels.
2
3use std::{fmt, str::FromStr};
4
5use bon::Builder;
6use const_macros::const_early;
7
8use miette::Diagnostic;
9
10#[cfg(feature = "serde")]
11use serde::{Deserialize, Serialize};
12
13use thiserror::Error;
14
15use crate::{
16    auth::{
17        part::{self, Part, SEPARATOR},
18        query::Query,
19        url::{self, Url},
20        utf8,
21    },
22    macros::errors,
23};
24
25/// Represents errors that occur when the label is empty.
26#[derive(Debug, Error, Diagnostic)]
27#[error("empty label encountered")]
28#[diagnostic(
29    code(otp_std::auth::label::empty),
30    help("make sure the label is non-empty")
31)]
32pub struct EmptyError;
33
34/// Represents sources of errors that can occur when parsing labels.
35#[derive(Debug, Error, Diagnostic)]
36#[error(transparent)]
37#[diagnostic(transparent)]
38pub enum ParseErrorSource {
39    /// The label is empty.
40    Empty(#[from] EmptyError),
41    /// The label part is invalid.
42    Part(#[from] part::Error),
43}
44
45/// Represents errors that occur when parsing labels.
46#[derive(Debug, Error, Diagnostic)]
47#[error("failed to parse label")]
48#[diagnostic(
49    code(otp_std::auth::label),
50    help("make sure the label is formatted correctly")
51)]
52pub struct ParseError {
53    /// The source of this error.
54    #[source]
55    #[diagnostic_source]
56    pub source: ParseErrorSource,
57}
58
59impl ParseError {
60    /// Constructs [`Self`].
61    pub const fn new(source: ParseErrorSource) -> Self {
62        Self { source }
63    }
64
65    /// Constructs [`Self`] from [`EmptyError`].
66    pub fn empty(error: EmptyError) -> Self {
67        Self::new(error.into())
68    }
69
70    /// Constructs [`Self`] from [`part::Error`].
71    pub fn part(error: part::Error) -> Self {
72        Self::new(error.into())
73    }
74
75    /// Constructs [`EmptyError`] and constructs [`Self`] from it.
76    pub fn new_empty() -> Self {
77        Self::empty(EmptyError)
78    }
79}
80
81/// Represents authentication labels.
82#[derive(Debug, Clone, PartialEq, Eq, Hash, Builder)]
83#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
84pub struct Label<'l> {
85    /// The authentication issuer.
86    pub issuer: Option<Part<'l>>,
87    /// The authentication user.
88    pub user: Part<'l>,
89}
90
91/// Represents `(issuer, user)` parts of the label.
92pub type Parts<'p> = (Option<Part<'p>>, Part<'p>);
93
94/// Represents owned [`Parts`].
95pub type OwnedParts = Parts<'static>;
96
97impl<'l> Label<'l> {
98    /// Constructs [`Self`] from parts.
99    pub fn from_parts(parts: Parts<'l>) -> Self {
100        let (issuer, user) = parts;
101
102        Self::builder().maybe_issuer(issuer).user(user).build()
103    }
104
105    /// Consumes [`Self`], returning the contained parts.
106    pub fn into_parts(self) -> Parts<'l> {
107        (self.issuer, self.user)
108    }
109}
110
111impl<'p> From<Parts<'p>> for Label<'p> {
112    fn from(parts: Parts<'p>) -> Self {
113        Self::from_parts(parts)
114    }
115}
116
117impl<'l> From<Label<'l>> for Parts<'l> {
118    fn from(label: Label<'l>) -> Self {
119        label.into_parts()
120    }
121}
122
123impl fmt::Display for Label<'_> {
124    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
125        if let Some(issuer) = self.issuer.as_ref() {
126            issuer.fmt(formatter)?;
127
128            formatter.write_str(SEPARATOR)?;
129        };
130
131        self.user.fmt(formatter)
132    }
133}
134
135errors! {
136    Type = ParseError,
137    Hack = $,
138    empty_error => new_empty(),
139}
140
141impl FromStr for Label<'_> {
142    type Err = ParseError;
143
144    fn from_str(string: &str) -> Result<Self, Self::Err> {
145        const_early!(string.is_empty() => empty_error!());
146
147        if let Some((issuer_string, user_string)) = string.split_once(SEPARATOR) {
148            let issuer = issuer_string.parse().map_err(Self::Err::part)?;
149            let user = user_string.parse().map_err(Self::Err::part)?;
150
151            Ok(Self::builder().issuer(issuer).user(user).build())
152        } else {
153            let user = string.parse().map_err(Self::Err::part)?;
154
155            Ok(Self::builder().user(user).build())
156        }
157    }
158}
159
160/// Represents sources of errors that can occur when decoding labels.
161#[derive(Debug, Error, Diagnostic)]
162#[error(transparent)]
163#[diagnostic(transparent)]
164pub enum DecodeErrorSource {
165    /// The label is not valid UTF-8.
166    Utf8(#[from] utf8::Error),
167    /// The label is otherwise not valid.
168    Parse(#[from] ParseError),
169}
170
171/// Represents errors that occur when decoding labels.
172#[derive(Debug, Error, Diagnostic)]
173#[error("failed to decode label")]
174#[diagnostic(
175    code(otp_std::auth::label::decode),
176    help("make sure the label is correctly formatted")
177)]
178pub struct DecodeError {
179    /// The source of this error.
180    #[source]
181    #[diagnostic_source]
182    pub source: DecodeErrorSource,
183}
184
185impl DecodeError {
186    /// Constructs [`Self`].
187    pub const fn new(source: DecodeErrorSource) -> Self {
188        Self { source }
189    }
190
191    /// Constructs [`Self`] from [`utf8::Error`].
192    pub fn utf8(error: utf8::Error) -> Self {
193        Self::new(error.into())
194    }
195
196    /// Constructs [`Self`] from [`ParseError`].
197    pub fn label(error: ParseError) -> Self {
198        Self::new(error.into())
199    }
200}
201
202impl Label<'_> {
203    /// Decodes the label from the given string.
204    ///
205    /// # Errors
206    ///
207    /// Returns [`DecodeError`] if the label could not be decoded.
208    pub fn decode<S: AsRef<str>>(string: S) -> Result<Self, DecodeError> {
209        let string = string.as_ref();
210
211        let decoded = url::decode(string)
212            .map_err(utf8::wrap)
213            .map_err(DecodeError::utf8)?;
214
215        decoded.parse().map_err(DecodeError::label)
216    }
217}
218
219impl Label<'_> {
220    /// Encodes the label.
221    pub fn encode(&self) -> String {
222        self.to_string()
223    }
224}
225
226/// Represnets errors that can occur on issuer mismatch.
227#[derive(Debug, Error, Diagnostic)]
228#[error("issuer mismatch: `{label}` in label, `{query}` in query")]
229#[diagnostic(
230    code(otp_std::auth::label::mismatch),
231    help("if the issuer is present both in the label and the query, they must match")
232)]
233pub struct MismatchError {
234    /// The label issuer.
235    pub label: String,
236    /// The query issuer.
237    pub query: String,
238}
239
240impl MismatchError {
241    /// Constructs [`Self`].
242    pub const fn new(label: String, query: String) -> Self {
243        Self { label, query }
244    }
245}
246
247errors! {
248    Type = MismatchError,
249    Hack = $,
250    mismatch_error => new(label => into_owned, query => into_owned),
251}
252
253/// Checks whether the label issuer and the query issuer match, provided both are present.
254///
255/// This function returns either the label issuer or the query issuer.
256///
257/// # Errors
258///
259/// Returns [`MismatchError`] if the both issuers are present and do not match.
260pub fn try_match<'p>(
261    label_issuer: Option<Part<'p>>,
262    query_issuer: Option<Part<'p>>,
263) -> Result<Option<Part<'p>>, MismatchError> {
264    match (label_issuer, query_issuer) {
265        (Some(label), Some(query)) if label != query => {
266            Err(mismatch_error!(label.get(), query.get()))
267        }
268        (label_option, query_option) => Ok(label_option.or(query_option)),
269    }
270}
271
272/// Represents sources of errors that can occur when extracting labels.
273#[derive(Debug, Error, Diagnostic)]
274#[error(transparent)]
275#[diagnostic(transparent)]
276pub enum ErrorSource {
277    /// The label could not be decoded.
278    Decode(#[from] DecodeError),
279    /// The issuer could not be decoded.
280    Issuer(#[from] part::DecodeError),
281    /// The label and query issuers do not match.
282    Mismatch(#[from] MismatchError),
283}
284
285/// Represents errors that can occur when extracting labels.
286#[derive(Debug, Error, Diagnostic)]
287#[error("failed to extract label from OTP URL")]
288#[diagnostic(
289    code(otp_std::auth::label),
290    help("see the report for more information")
291)]
292pub struct Error {
293    /// The source of this error.
294    #[source]
295    #[diagnostic_source]
296    pub source: ErrorSource,
297}
298
299impl Error {
300    /// Constructs [`Self`].
301    pub const fn new(source: ErrorSource) -> Self {
302        Self { source }
303    }
304
305    /// Constructs [`Self`] from [`DecodeError`].
306    pub fn decode(error: DecodeError) -> Self {
307        Self::new(error.into())
308    }
309
310    /// Constructs [`Self`] from [`MismatchError`].
311    pub fn mismatch(error: MismatchError) -> Self {
312        Self::new(error.into())
313    }
314
315    /// Constructs [`Self`] from [`part::DecodeError`].
316    pub fn issuer(error: part::DecodeError) -> Self {
317        Self::new(error.into())
318    }
319}
320
321/// The `issuer` literal.
322pub const ISSUER: &str = "issuer";
323
324/// The `/` literal.
325pub const SLASH: &str = "/";
326
327impl Label<'_> {
328    /// Applies the label to the given URL.
329    pub fn query_for(&self, url: &mut Url) {
330        if let Some(issuer) = self.issuer.as_ref() {
331            url.query_pairs_mut()
332                .append_pair(ISSUER, issuer.encode().as_ref());
333        };
334    }
335
336    /// Extracts [`Self`] from the given query and URL.
337    ///
338    /// # Errors
339    ///
340    /// Returns [`struct@Error`] if the label can not be extracted.
341    pub fn extract_from(query: &mut Query<'_>, url: &Url) -> Result<Self, Error> {
342        let path = url.path().trim_start_matches(SLASH);
343
344        let label = Self::decode(path).map_err(Error::decode)?;
345
346        // we will need to reconstruct the label
347        let (label_issuer, user) = label.into_parts();
348
349        let query_issuer = query
350            .remove(ISSUER)
351            .map(Part::decode)
352            .transpose()
353            .map_err(Error::issuer)?;
354
355        let issuer = try_match(label_issuer, query_issuer).map_err(Error::mismatch)?;
356
357        Ok(Self::from_parts((issuer, user)))
358    }
359}
360
361/// Represents owned [`Label`].
362pub type Owned = Label<'static>;
363
364impl Label<'_> {
365    /// Converts [`Self`] into [`Owned`].
366    pub fn into_owned(self) -> Owned {
367        Owned::builder()
368            .maybe_issuer(self.issuer.map(Part::into_owned))
369            .user(self.user.into_owned())
370            .build()
371    }
372}