#
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}