mcaptcha/
errors.rs

1// Copyright (C) 2022  Aravinth Manivannan <realaravinth@batsense.net>
2// SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
3//
4// SPDX-License-Identifier: AGPL-3.0-or-later
5
6use std::convert::From;
7
8use actix::MailboxError;
9use actix_web::{
10    error::ResponseError,
11    http::{header, StatusCode},
12    HttpResponse, HttpResponseBuilder,
13};
14use argon2_creds::errors::CredsError;
15use db_core::errors::DBError;
16use derive_more::{Display, Error};
17use lettre::transport::smtp::Error as SmtpError;
18use libmcaptcha::errors::CaptchaError;
19use serde::{Deserialize, Serialize};
20use tokio::sync::oneshot::error::RecvError;
21use url::ParseError;
22use validator::ValidationErrors;
23
24#[derive(Debug, Display, Error)]
25pub struct SmtpErrorWrapper(SmtpError);
26
27#[derive(Debug, Display, Error)]
28pub struct DBErrorWrapper(DBError);
29
30impl std::cmp::PartialEq for DBErrorWrapper {
31    fn eq(&self, other: &Self) -> bool {
32        format!("{}", self.0) == format!("{}", other.0)
33    }
34}
35
36impl std::cmp::PartialEq for SmtpErrorWrapper {
37    fn eq(&self, other: &Self) -> bool {
38        self.0.status() == other.0.status()
39    }
40}
41
42#[derive(Debug, Display, PartialEq, Error)]
43pub enum ServiceError {
44    #[display(fmt = "internal server error")]
45    InternalServerError,
46
47    #[display(
48        fmt = "This server is is closed for registration. Contact admin if this is unexpecter"
49    )]
50    ClosedForRegistration,
51
52    #[display(fmt = "The value you entered for email is not an email")] //405j
53    NotAnEmail,
54    #[display(fmt = "The value you entered for URL is not a URL")] //405j
55    NotAUrl,
56
57    #[display(fmt = "Wrong password")]
58    WrongPassword,
59    #[display(fmt = "Username not found")]
60    UsernameNotFound,
61    #[display(fmt = "Account not found")]
62    AccountNotFound,
63
64    /// when the value passed contains profainity
65    #[display(fmt = "Can't allow profanity in usernames")]
66    ProfainityError,
67    /// when the value passed contains blacklisted words
68    /// see [blacklist](https://github.com/shuttlecraft/The-Big-Username-Blacklist)
69    #[display(fmt = "Username contains blacklisted words")]
70    BlacklistError,
71    /// when the value passed contains characters not present
72    /// in [UsernameCaseMapped](https://tools.ietf.org/html/rfc8265#page-7)
73    /// profile
74    #[display(fmt = "username_case_mapped violation")]
75    UsernameCaseMappedError,
76
77    #[display(fmt = "Passsword too short")]
78    PasswordTooShort,
79    #[display(fmt = "Username too long")]
80    PasswordTooLong,
81    #[display(fmt = "Passwords don't match")]
82    PasswordsDontMatch,
83
84    /// when the a username is already taken
85    #[display(fmt = "Username not available")]
86    UsernameTaken,
87
88    /// email is already taken
89    #[display(fmt = "Email not available")]
90    EmailTaken,
91
92    /// Unable to send email
93    #[display(fmt = "Unable to send email, contact admin")]
94    UnableToSendEmail(SmtpErrorWrapper),
95
96    /// token not found
97    #[display(fmt = "Token not found. Is token registered?")]
98    TokenNotFound,
99
100    #[display(fmt = "{}", _0)]
101    CaptchaError(CaptchaError),
102
103    #[display(fmt = "{}", _0)]
104    DBError(DBErrorWrapper),
105
106    /// captcha not found
107    #[display(fmt = "Captcha not found.")]
108    CaptchaNotFound,
109
110    /// Traffic pattern not found
111    #[display(fmt = "Traffic pattern not found")]
112    TrafficPatternNotFound,
113}
114
115#[derive(Serialize, Deserialize)]
116pub struct ErrorToResponse {
117    pub error: String,
118}
119
120impl ResponseError for ServiceError {
121    fn error_response(&self) -> HttpResponse {
122        HttpResponseBuilder::new(self.status_code())
123            .append_header((header::CONTENT_TYPE, "application/json; charset=UTF-8"))
124            .body(
125                serde_json::to_string(&ErrorToResponse {
126                    error: self.to_string(),
127                })
128                .unwrap(),
129            )
130    }
131
132    fn status_code(&self) -> StatusCode {
133        match self {
134            ServiceError::ClosedForRegistration => StatusCode::FORBIDDEN,
135            ServiceError::InternalServerError => StatusCode::INTERNAL_SERVER_ERROR,
136            ServiceError::NotAnEmail => StatusCode::BAD_REQUEST,
137            ServiceError::NotAUrl => StatusCode::BAD_REQUEST,
138            ServiceError::WrongPassword => StatusCode::UNAUTHORIZED,
139            ServiceError::UsernameNotFound => StatusCode::NOT_FOUND,
140            ServiceError::AccountNotFound => StatusCode::NOT_FOUND,
141
142            ServiceError::ProfainityError => StatusCode::BAD_REQUEST,
143            ServiceError::BlacklistError => StatusCode::BAD_REQUEST,
144            ServiceError::UsernameCaseMappedError => StatusCode::BAD_REQUEST,
145
146            ServiceError::PasswordTooShort => StatusCode::BAD_REQUEST,
147            ServiceError::PasswordTooLong => StatusCode::BAD_REQUEST,
148            ServiceError::PasswordsDontMatch => StatusCode::BAD_REQUEST,
149
150            ServiceError::UsernameTaken => StatusCode::BAD_REQUEST,
151            ServiceError::EmailTaken => StatusCode::BAD_REQUEST,
152
153            ServiceError::TokenNotFound => StatusCode::NOT_FOUND,
154            ServiceError::CaptchaError(e) => {
155                log::error!("{}", e);
156                match e {
157                    CaptchaError::MailboxError => StatusCode::INTERNAL_SERVER_ERROR,
158                    _ => StatusCode::BAD_REQUEST,
159                }
160            }
161
162            ServiceError::UnableToSendEmail(e) => {
163                log::error!("{}", e.0);
164                StatusCode::INTERNAL_SERVER_ERROR
165            }
166
167            ServiceError::DBError(_) => StatusCode::INTERNAL_SERVER_ERROR,
168            ServiceError::CaptchaNotFound => StatusCode::NOT_FOUND,
169            ServiceError::TrafficPatternNotFound => StatusCode::NOT_FOUND,
170        }
171    }
172}
173
174impl From<CredsError> for ServiceError {
175    fn from(e: CredsError) -> ServiceError {
176        match e {
177            CredsError::UsernameCaseMappedError => ServiceError::UsernameCaseMappedError,
178            CredsError::ProfainityError => ServiceError::ProfainityError,
179            CredsError::BlacklistError => ServiceError::BlacklistError,
180            CredsError::NotAnEmail => ServiceError::NotAnEmail,
181            CredsError::Argon2Error(_) => ServiceError::InternalServerError,
182            CredsError::PasswordTooLong => ServiceError::PasswordTooLong,
183            CredsError::PasswordTooShort => ServiceError::PasswordTooShort,
184        }
185    }
186}
187
188impl From<DBError> for ServiceError {
189    fn from(e: DBError) -> ServiceError {
190        println!("from conversin: {}", e);
191        match e {
192            DBError::UsernameTaken => ServiceError::UsernameTaken,
193            DBError::SecretTaken => ServiceError::InternalServerError,
194            DBError::EmailTaken => ServiceError::EmailTaken,
195            DBError::AccountNotFound => ServiceError::AccountNotFound,
196            DBError::CaptchaNotFound => ServiceError::CaptchaNotFound,
197            DBError::TrafficPatternNotFound => ServiceError::TrafficPatternNotFound,
198            _ => ServiceError::DBError(DBErrorWrapper(e)),
199        }
200    }
201}
202
203impl From<ValidationErrors> for ServiceError {
204    fn from(_: ValidationErrors) -> ServiceError {
205        ServiceError::NotAnEmail
206    }
207}
208
209impl From<ParseError> for ServiceError {
210    fn from(_: ParseError) -> ServiceError {
211        ServiceError::NotAUrl
212    }
213}
214
215impl From<CaptchaError> for ServiceError {
216    fn from(e: CaptchaError) -> ServiceError {
217        ServiceError::CaptchaError(e)
218    }
219}
220
221impl From<SmtpError> for ServiceError {
222    fn from(e: SmtpError) -> Self {
223        ServiceError::UnableToSendEmail(SmtpErrorWrapper(e))
224    }
225}
226
227impl From<RecvError> for ServiceError {
228    fn from(e: RecvError) -> Self {
229        log::error!("{:?}", e);
230        ServiceError::InternalServerError
231    }
232}
233
234impl From<MailboxError> for ServiceError {
235    fn from(e: MailboxError) -> Self {
236        log::error!("{:?}", e);
237        ServiceError::InternalServerError
238    }
239}
240
241pub type ServiceResult<V> = std::result::Result<V, ServiceError>;
242
243#[derive(Debug, Display, PartialEq, Error)]
244pub enum PageError {
245    #[display(fmt = "Something weng wrong: Internal server error")]
246    InternalServerError,
247
248    #[display(fmt = "{}", _0)]
249    ServiceError(ServiceError),
250}
251
252impl From<ServiceError> for PageError {
253    fn from(e: ServiceError) -> Self {
254        PageError::ServiceError(e)
255    }
256}
257
258impl From<DBError> for PageError {
259    fn from(e: DBError) -> Self {
260        let se: ServiceError = e.into();
261        se.into()
262    }
263}
264
265impl ResponseError for PageError {
266    fn error_response(&self) -> HttpResponse {
267        use crate::PAGES;
268        match self.status_code() {
269            StatusCode::INTERNAL_SERVER_ERROR => HttpResponse::Found()
270                .append_header((header::LOCATION, PAGES.errors.internal_server_error))
271                .finish(),
272            _ => HttpResponse::Found()
273                .append_header((header::LOCATION, PAGES.errors.unknown_error))
274                .finish(),
275        }
276    }
277
278    fn status_code(&self) -> StatusCode {
279        match self {
280            PageError::InternalServerError => StatusCode::INTERNAL_SERVER_ERROR,
281            PageError::ServiceError(e) => e.status_code(),
282        }
283    }
284}
285
286pub type PageResult<V> = std::result::Result<V, PageError>;
287
288#[cfg(test)]
289mod tests {
290    use super::*;
291    use crate::PAGES;
292
293    #[test]
294    fn error_works() {
295        let resp: HttpResponse = PageError::InternalServerError.error_response();
296        assert_eq!(resp.status(), StatusCode::FOUND);
297        let headers = resp.headers();
298        assert_eq!(
299            headers.get(header::LOCATION).unwrap(),
300            PAGES.errors.internal_server_error
301        );
302    }
303}