1
10
/*
2
 * This file is part of mailpot
3
 *
4
 * Copyright 2020 - Manos Pitsidianakis
5
 *
6
 * This program is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU Affero General Public License as
8
 * published by the Free Software Foundation, either version 3 of the
9
 * License, or (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
 * GNU Affero General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU Affero General Public License
17
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18
 */
19

            
20
#![deny(
21
    //missing_docs,
22
    rustdoc::broken_intra_doc_links,
23
    /* groups */
24
    clippy::correctness,
25
    clippy::suspicious,
26
    clippy::complexity,
27
    clippy::perf,
28
    clippy::style,
29
    clippy::cargo,
30
    clippy::nursery,
31
    /* restriction */
32
    clippy::dbg_macro,
33
    clippy::rc_buffer,
34
    clippy::as_underscore,
35
    clippy::assertions_on_result_states,
36
    /* pedantic */
37
    clippy::cast_lossless,
38
    clippy::cast_possible_wrap,
39
    clippy::ptr_as_ptr,
40
    clippy::bool_to_int_with_if,
41
    clippy::borrow_as_ptr,
42
    clippy::case_sensitive_file_extension_comparisons,
43
    clippy::cast_lossless,
44
    clippy::cast_ptr_alignment,
45
    clippy::naive_bytecount
46
)]
47
#![allow(clippy::multiple_crate_versions, clippy::missing_const_for_fn)]
48

            
49
pub use axum::{
50
    extract::{Path, Query, State},
51
    handler::Handler,
52
    response::{Html, IntoResponse, Redirect},
53
    routing::{get, post},
54
    Extension, Form, Router,
55
};
56
pub use axum_extra::routing::TypedPath;
57
pub use axum_login::{
58
    memory_store::MemoryStore as AuthMemoryStore, secrecy::SecretVec, AuthLayer, AuthUser,
59
    RequireAuthorizationLayer,
60
};
61
pub use axum_sessions::{
62
    async_session::MemoryStore,
63
    extractors::{ReadableSession, WritableSession},
64
    SessionLayer,
65
};
66

            
67
pub type AuthContext =
68
    axum_login::extractors::AuthContext<i64, auth::User, Arc<AppState>, auth::Role>;
69

            
70
pub type RequireAuth = auth::auth_request::RequireAuthorizationLayer<i64, auth::User, auth::Role>;
71

            
72
pub use std::result::Result;
73
use std::{borrow::Cow, collections::HashMap, sync::Arc};
74

            
75
use chrono::Datelike;
76
pub use http::{Request, Response, StatusCode};
77
pub use mailpot::{models::DbVal, rusqlite::OptionalExtension, *};
78
use minijinja::{
79
    value::{Object, Value},
80
    Environment, Error,
81
};
82
use tokio::sync::RwLock;
83

            
84
pub mod auth;
85
pub mod cal;
86
pub mod help;
87
pub mod lists;
88
pub mod minijinja_utils;
89
pub mod settings;
90
pub mod typed_paths;
91
pub mod utils;
92

            
93
pub use auth::*;
94
pub use cal::{calendarize, *};
95
pub use help::*;
96
pub use lists::*;
97
pub use minijinja_utils::*;
98
pub use settings::*;
99
pub use typed_paths::{tsr::RouterExt, *};
100
pub use utils::*;
101

            
102
#[derive(Debug)]
103
pub struct ResponseError {
104
    pub inner: Box<dyn std::error::Error>,
105
    pub status: StatusCode,
106
}
107

            
108
impl std::fmt::Display for ResponseError {
109
    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
110
        write!(fmt, "Inner: {}, status: {}", self.inner, self.status)
111
    }
112
}
113

            
114
impl ResponseError {
115
    pub fn new(msg: String, status: StatusCode) -> Self {
116
        Self {
117
            inner: Box::<dyn std::error::Error + Send + Sync>::from(msg),
118
            status,
119
        }
120
    }
121
}
122

            
123
impl<E: Into<Box<dyn std::error::Error>>> From<E> for ResponseError {
124
    fn from(err: E) -> Self {
125
        Self {
126
            inner: err.into(),
127
            status: StatusCode::INTERNAL_SERVER_ERROR,
128
        }
129
    }
130
}
131

            
132
pub trait IntoResponseError {
133
    fn with_status(self, status: StatusCode) -> ResponseError;
134
}
135

            
136
impl<E: Into<Box<dyn std::error::Error>>> IntoResponseError for E {
137
    fn with_status(self, status: StatusCode) -> ResponseError {
138
        ResponseError {
139
            status,
140
            ..ResponseError::from(self)
141
        }
142
    }
143
}
144

            
145
impl IntoResponse for ResponseError {
146
    fn into_response(self) -> axum::response::Response {
147
        let Self { inner, status } = self;
148
        (status, inner.to_string()).into_response()
149
    }
150
}
151

            
152
pub trait IntoResponseErrorResult<R> {
153
    fn with_status(self, status: StatusCode) -> std::result::Result<R, ResponseError>;
154
}
155

            
156
impl<R, E> IntoResponseErrorResult<R> for std::result::Result<R, E>
157
where
158
    E: IntoResponseError,
159
{
160
    fn with_status(self, status: StatusCode) -> std::result::Result<R, ResponseError> {
161
        self.map_err(|err| err.with_status(status))
162
    }
163
}
164

            
165
#[derive(Clone)]
166
pub struct AppState {
167
    pub conf: Configuration,
168
    pub root_url_prefix: Value,
169
    pub public_url: String,
170
    pub site_title: Cow<'static, str>,
171
    pub user_store: Arc<RwLock<HashMap<i64, User>>>,
172
    // ...
173
}
174

            
175
mod auth_impls {
176
    use super::*;
177
    type UserId = i64;
178
    type User = auth::User;
179
    type Role = auth::Role;
180

            
181
    impl AppState {
182
        pub async fn insert_user(&self, pk: UserId, user: User) {
183
            self.user_store.write().await.insert(pk, user);
184
        }
185
    }
186

            
187
    #[axum::async_trait]
188
    impl axum_login::UserStore<UserId, Role> for Arc<AppState>
189
    where
190
        User: axum_login::AuthUser<UserId, Role>,
191
    {
192
        type User = User;
193

            
194
        async fn load_user(
195
            &self,
196
            user_id: &UserId,
197
        ) -> std::result::Result<Option<Self::User>, eyre::Report> {
198
            Ok(self.user_store.read().await.get(user_id).cloned())
199
        }
200
    }
201
}
202

            
203
const fn _get_package_git_sha() -> Option<&'static str> {
204
    option_env!("PACKAGE_GIT_SHA")
205
}
206

            
207
const _PACKAGE_COMMIT_SHA: Option<&str> = _get_package_git_sha();
208

            
209
pub fn get_git_sha() -> std::borrow::Cow<'static, str> {
210
    if let Some(r) = _PACKAGE_COMMIT_SHA {
211
        return r.into();
212
    }
213
    build_info::build_info!(fn build_info);
214
    let info = build_info();
215
    info.version_control
216
        .as_ref()
217
        .and_then(|v| v.git())
218
        .map(|g| g.commit_short_id.clone())
219
        .map_or_else(|| "<unknown>".into(), |v| v.into())
220
}
221

            
222
pub const VERSION_INFO: &str = build_info::format!("{}", $.crate_info.version);
223
pub const BUILD_INFO: &str = build_info::format!("{}\t{}\t{}\t{}", $.crate_info.version, $.compiler, $.timestamp, $.crate_info.enabled_features);
224
pub const CLI_INFO: &str = build_info::format!("{} Version: {}\nAuthors: {}\nLicense: AGPL version 3 or later\nCompiler: {}\nBuild-Date: {}\nEnabled-features: {}", $.crate_info.name, $.crate_info.version, $.crate_info.authors, $.compiler, $.timestamp, $.crate_info.enabled_features);