mcaptcha/
main.rs

1#![allow(warnings)]
2// Copyright (C) 2022  Aravinth Manivannan <realaravinth@batsense.net>
3// SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
4//
5// SPDX-License-Identifier: AGPL-3.0-or-later
6
7use std::env;
8use std::sync::Arc;
9
10use actix_identity::{CookieIdentityPolicy, IdentityService};
11use actix_web::{
12    error::InternalError, http::StatusCode, middleware as actix_middleware,
13    web::JsonConfig, App, HttpServer,
14};
15use lazy_static::lazy_static;
16use log::info;
17use tokio::task::JoinHandle;
18
19mod api;
20mod data;
21mod date;
22mod db;
23mod demo;
24mod docs;
25mod easy;
26mod email;
27mod errors;
28#[macro_use]
29mod pages;
30#[macro_use]
31mod routes;
32mod settings;
33mod static_assets;
34mod stats;
35mod survey;
36#[cfg(test)]
37#[macro_use]
38mod tests;
39mod widget;
40
41pub use crate::data::Data;
42pub use crate::static_assets::static_files::assets::*;
43pub use api::v1::ROUTES as V1_API_ROUTES;
44pub use docs::DOCS;
45pub use pages::routes::ROUTES as PAGES;
46pub use settings::Settings;
47use static_assets::FileMap;
48pub use widget::WIDGET_ROUTES;
49
50use crate::demo::DemoUser;
51use survey::SurveyClientTrait;
52
53lazy_static! {
54    pub static ref SETTINGS: Settings = Settings::new().unwrap();
55//    pub static ref S: String = env::var("S").unwrap();
56    pub static ref FILES: FileMap = FileMap::new();
57    pub static ref JS: &'static str =
58        FILES.get("./static/cache/bundle/bundle.js").unwrap();
59    pub static ref CSS: &'static str =
60        FILES.get("./static/cache/bundle/css/main.css").unwrap();
61    pub static ref MOBILE_CSS: &'static str =
62        FILES.get("./static/cache/bundle/css/mobile.css").unwrap();
63
64    pub static ref VERIFICATIN_WIDGET_JS: &'static str =
65        FILES.get("./static/cache/bundle/verificationWidget.js").unwrap();
66    pub static ref VERIFICATIN_WIDGET_CSS: &'static str =
67        FILES.get("./static/cache/bundle/css/widget.css").unwrap();
68
69    /// points to source files matching build commit
70    pub static ref SOURCE_FILES_OF_INSTANCE: String = {
71        let mut url = SETTINGS.source_code.clone();
72        if !url.ends_with('/') {
73            url.push('/');
74        }
75        let mut  base = url::Url::parse(&url).unwrap();
76        base =  base.join("tree/").unwrap();
77        base =  base.join(GIT_COMMIT_HASH).unwrap();
78        base.into()
79    };
80
81}
82
83pub const COMPILED_DATE: &str = env!("COMPILED_DATE");
84pub const GIT_COMMIT_HASH: &str = env!("GIT_HASH");
85pub const VERSION: &str = env!("CARGO_PKG_VERSION");
86pub const PKG_NAME: &str = env!("CARGO_PKG_NAME");
87pub const PKG_DESCRIPTION: &str = env!("CARGO_PKG_DESCRIPTION");
88pub const PKG_HOMEPAGE: &str = env!("CARGO_PKG_HOMEPAGE");
89
90pub const CACHE_AGE: u32 = 604800;
91
92pub type ArcData = Arc<crate::data::Data>;
93pub type AppData = actix_web::web::Data<ArcData>;
94
95#[actix_web::main]
96async fn main() -> std::io::Result<()> {
97    use std::time::Duration;
98
99    if env::var("RUST_LOG").is_err() {
100        env::set_var("RUST_LOG", "info");
101    }
102
103    pretty_env_logger::init();
104    info!(
105        "{}: {}.\nFor more information, see: {}\nBuild info:\nVersion: {} commit: {}",
106        PKG_NAME, PKG_DESCRIPTION, PKG_HOMEPAGE, VERSION, GIT_COMMIT_HASH
107    );
108
109    let settings = Settings::new().unwrap();
110    let secrets = survey::SecretsStore::default();
111    let data = Data::new(&settings, secrets.clone()).await;
112    let data = actix_web::web::Data::new(data);
113
114    let mut demo_user: Option<(DemoUser, JoinHandle<()>)> = None;
115
116    if settings.allow_demo && settings.allow_registration {
117        demo_user = Some(DemoUser::spawn(data.clone(), 60 * 30).await.unwrap());
118    }
119
120    let mut update_easy_captcha: Option<(easy::UpdateEasyCaptcha, JoinHandle<()>)> =
121        None;
122    if settings
123        .captcha
124        .default_difficulty_strategy
125        .avg_traffic_time
126        .is_some()
127    {
128        update_easy_captcha = Some(
129            easy::UpdateEasyCaptcha::spawn(data.clone(), 60 * 30)
130                .await
131                .unwrap(),
132        );
133    }
134
135    let (mut survey_upload_tx, mut survey_upload_handle) = (None, None);
136    if settings.survey.is_some() {
137        let survey_runner_ctx = survey::Survey::new(data.clone());
138        let (x, y) = survey_runner_ctx.start_job().await.unwrap();
139        (survey_upload_tx, survey_upload_handle) = (Some(x), Some(y));
140    }
141
142    let ip = settings.server.get_ip();
143    println!("Starting server on: http://{ip}");
144
145    HttpServer::new(move || {
146        App::new()
147            .wrap(actix_middleware::Logger::default())
148            .wrap(
149                actix_middleware::DefaultHeaders::new()
150                    .add(("Permissions-Policy", "interest-cohort=()")),
151            )
152            .wrap(get_identity_service(&settings))
153            .wrap(actix_middleware::Compress::default())
154            .app_data(data.clone())
155            .wrap(actix_middleware::NormalizePath::new(
156                actix_middleware::TrailingSlash::Trim,
157            ))
158            .configure(routes::services)
159            .app_data(get_json_err())
160    })
161    .bind(&ip)
162    .unwrap()
163    .run()
164    .await?;
165
166    if let Some(survey_upload_tx) = survey_upload_tx {
167        survey_upload_tx.send(()).unwrap();
168    }
169
170    if let Some(demo_user) = demo_user {
171        demo_user.0.abort();
172        demo_user.1.await.unwrap();
173    }
174
175    if let Some(update_easy_captcha) = update_easy_captcha {
176        update_easy_captcha.0.abort();
177        update_easy_captcha.1.await.unwrap();
178    }
179
180    if let Some(survey_upload_handle) = survey_upload_handle {
181        survey_upload_handle.await.unwrap();
182    }
183
184    Ok(())
185}
186
187pub fn get_json_err() -> JsonConfig {
188    JsonConfig::default().error_handler(|err, _| {
189        //debug!("JSON deserialization error: {:?}", &err);
190        InternalError::new(err, StatusCode::BAD_REQUEST).into()
191    })
192}
193
194pub fn get_identity_service(
195    settings: &Settings,
196) -> IdentityService<CookieIdentityPolicy> {
197    let cookie_secret = &settings.server.cookie_secret;
198    IdentityService::new(
199        CookieIdentityPolicy::new(cookie_secret.as_bytes())
200            .name("Authorization")
201            //TODO change cookie age
202            .max_age_secs(216000)
203            .domain(&settings.server.domain)
204            .secure(false),
205    )
206}
207
208#[cfg(test)]
209mod test {
210    #[test]
211    fn version_source_code_url_works() {
212        assert_eq!(
213            &*crate::SOURCE_FILES_OF_INSTANCE,
214            &format!(
215                "https://github.com/mCaptcha/mCaptcha/tree/{}",
216                crate::GIT_COMMIT_HASH
217            )
218        );
219    }
220}