1#![allow(warnings)]
2use 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();
55pub 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 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 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 .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}