mirror of
https://github.com/mCaptcha/mCaptcha.git
synced 2025-11-23 22:15:46 +00:00
upgrading to actix-v4-beta
This commit is contained in:
parent
9ed458ebfa
commit
9f940c317a
1442
Cargo.lock
generated
1442
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
27
Cargo.toml
27
Cargo.toml
@ -22,27 +22,31 @@ name = "tests-migrate"
|
|||||||
path = "./src/tests-migrate.rs"
|
path = "./src/tests-migrate.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-web = "3.3.2"
|
#actix-web = "3.3.2"
|
||||||
actix = "0.10"
|
actix-web = "4.0.0-beta.8"
|
||||||
actix-identity = "0.3"
|
actix = "0.12"
|
||||||
actix-http = "2.2"
|
#actix-identity = "0.3"
|
||||||
actix-rt = "1"
|
actix-identity = "0.4.0-beta.2"
|
||||||
actix-cors= "0.5.4"
|
actix-http = "3.0.0-beta.8"
|
||||||
actix-service = "1.0.6"
|
#actix-http = "2.2"
|
||||||
|
actix-rt = "2"
|
||||||
|
#actix-cors= "0.5.4"
|
||||||
|
actix-cors = "0.6.0-beta.2"
|
||||||
|
#actix-service = "0.0.6"
|
||||||
|
actix-service = "2.0.0"
|
||||||
my-codegen = {package = "actix-web-codegen", git ="https://github.com/realaravinth/actix-web"}
|
my-codegen = {package = "actix-web-codegen", git ="https://github.com/realaravinth/actix-web"}
|
||||||
|
|
||||||
|
|
||||||
mime_guess = "2.0.3"
|
mime_guess = "2.0.3"
|
||||||
rust-embed = "5.9.0"
|
rust-embed = "5.9.0"
|
||||||
cache-buster = { version = "0.2.0", git = "https://github.com/realaravinth/cache-buster" }
|
cache-buster = { version = "0.2.0", git = "https://github.com/realaravinth/cache-buster" }
|
||||||
|
|
||||||
futures = "0.3.14"
|
futures = "0.3.15"
|
||||||
|
|
||||||
sqlx = { version = "0.4.0", features = [ "runtime-actix-rustls", "postgres", "time", "offline" ] }
|
sqlx = { version = "0.5.5", features = [ "runtime-actix-rustls", "postgres", "time", "offline" ] }
|
||||||
argon2-creds = { branch = "master", git = "https://github.com/realaravinth/argon2-creds"}
|
argon2-creds = { branch = "master", git = "https://github.com/realaravinth/argon2-creds"}
|
||||||
#argon2-creds = { version="*", path = "../../argon2-creds/" }
|
#argon2-creds = { version="*", path = "../../argon2-creds/" }
|
||||||
config = "0.11"
|
config = "0.11"
|
||||||
validator = { version = "0.13", features = ["derive"]}
|
validator = { version = "0.14", features = ["derive"]}
|
||||||
|
|
||||||
derive_builder = "0.10"
|
derive_builder = "0.10"
|
||||||
derive_more = "0.99"
|
derive_more = "0.99"
|
||||||
@ -59,7 +63,6 @@ log = "0.4"
|
|||||||
lazy_static = "1.4"
|
lazy_static = "1.4"
|
||||||
|
|
||||||
|
|
||||||
# m_captcha = { version = "0.1.2", git = "https://github.com/mCaptcha/mCaptcha" }
|
|
||||||
libmcaptcha = { branch = "master", git = "https://github.com/mCaptcha/libmcaptcha", features = ["full"] }
|
libmcaptcha = { branch = "master", git = "https://github.com/mCaptcha/libmcaptcha", features = ["full"] }
|
||||||
#libmcaptcha = { path = "../libmcaptcha", features = ["full"]}
|
#libmcaptcha = { path = "../libmcaptcha", features = ["full"]}
|
||||||
|
|
||||||
|
|||||||
@ -49,8 +49,9 @@ pool = 4
|
|||||||
url = "redis://127.0.0.1"
|
url = "redis://127.0.0.1"
|
||||||
pool = 4
|
pool = 4
|
||||||
|
|
||||||
#[smtp]
|
[smtp]
|
||||||
#from = "admin@domain.com"
|
from = "admin@localhost"
|
||||||
#url = "smtp.domain.com"
|
reply_to = "admin@localhost"
|
||||||
#username = "admin"
|
url = "localhost:10025"
|
||||||
#password = "password"
|
username = "admin"
|
||||||
|
password = "password"
|
||||||
|
|||||||
@ -226,7 +226,6 @@ async fn signout(id: Identity) -> impl Responder {
|
|||||||
id.forget();
|
id.forget();
|
||||||
}
|
}
|
||||||
HttpResponse::Found()
|
HttpResponse::Found()
|
||||||
.header(header::LOCATION, "/login")
|
.append_header((header::LOCATION, "/login"))
|
||||||
.finish()
|
.finish()
|
||||||
.into_body()
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,11 +24,11 @@ use crate::errors::*;
|
|||||||
use crate::AppData;
|
use crate::AppData;
|
||||||
|
|
||||||
pub struct Notification {
|
pub struct Notification {
|
||||||
pub name: String,
|
pub name: Option<String>,
|
||||||
pub heading: String,
|
pub heading: Option<String>,
|
||||||
pub message: String,
|
pub message: Option<String>,
|
||||||
pub received: OffsetDateTime,
|
pub received: Option<OffsetDateTime>,
|
||||||
pub id: i32,
|
pub id: Option<i32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize)]
|
#[derive(Deserialize, Serialize)]
|
||||||
@ -43,11 +43,11 @@ pub struct NotificationResp {
|
|||||||
impl From<Notification> for NotificationResp {
|
impl From<Notification> for NotificationResp {
|
||||||
fn from(n: Notification) -> Self {
|
fn from(n: Notification) -> Self {
|
||||||
NotificationResp {
|
NotificationResp {
|
||||||
name: n.name,
|
name: n.name.unwrap(),
|
||||||
heading: n.heading,
|
heading: n.heading.unwrap(),
|
||||||
received: n.received.unix_timestamp(),
|
received: n.received.unwrap().unix_timestamp(),
|
||||||
id: n.id,
|
id: n.id.unwrap(),
|
||||||
message: n.message,
|
message: n.message.unwrap(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -161,7 +161,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn feature() {
|
fn feature() {
|
||||||
actix_rt::System::new("trest")
|
actix_rt::System::new()
|
||||||
.block_on(async move { get_pow_config_works().await });
|
.block_on(async move { get_pow_config_works().await });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -62,7 +62,7 @@ pub fn handle_embedded_file(path: &str) -> HttpResponse {
|
|||||||
Cow::Owned(bytes) => bytes.into(),
|
Cow::Owned(bytes) => bytes.into(),
|
||||||
};
|
};
|
||||||
HttpResponse::Ok()
|
HttpResponse::Ok()
|
||||||
.set(header::CacheControl(vec![header::CacheDirective::MaxAge(
|
.insert_header(header::CacheControl(vec![header::CacheDirective::MaxAge(
|
||||||
CACHE_AGE,
|
CACHE_AGE,
|
||||||
)]))
|
)]))
|
||||||
.content_type(from_path(path).first_or_octet_stream().as_ref())
|
.content_type(from_path(path).first_or_octet_stream().as_ref())
|
||||||
@ -73,7 +73,7 @@ pub fn handle_embedded_file(path: &str) -> HttpResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn dist(path: web::Path<String>) -> impl Responder {
|
async fn dist(path: web::Path<String>) -> impl Responder {
|
||||||
handle_embedded_file(&path.0)
|
handle_embedded_file(&path)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn spec() -> HttpResponse {
|
async fn spec() -> HttpResponse {
|
||||||
@ -101,7 +101,7 @@ mod tests {
|
|||||||
let mut app = test::init_service(
|
let mut app = test::init_service(
|
||||||
App::new()
|
App::new()
|
||||||
.wrap(actix_middleware::NormalizePath::new(
|
.wrap(actix_middleware::NormalizePath::new(
|
||||||
actix_middleware::normalize::TrailingSlash::Trim,
|
actix_middleware::TrailingSlash::Trim,
|
||||||
))
|
))
|
||||||
.configure(services),
|
.configure(services),
|
||||||
)
|
)
|
||||||
|
|||||||
@ -19,57 +19,92 @@ use lettre::{
|
|||||||
message::{header, MultiPart, SinglePart},
|
message::{header, MultiPart, SinglePart},
|
||||||
AsyncTransport, Message,
|
AsyncTransport, Message,
|
||||||
};
|
};
|
||||||
|
use sailfish::TemplateOnce;
|
||||||
|
|
||||||
use crate::AppData;
|
use crate::errors::*;
|
||||||
|
use crate::Data;
|
||||||
use crate::SETTINGS;
|
use crate::SETTINGS;
|
||||||
|
|
||||||
// The html we want to send.
|
const PAGE: &str = "Login";
|
||||||
const HTML: &str = r#"<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Hello from Lettre!</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div style="display: flex; flex-direction: column; align-items: center;">
|
|
||||||
<h2 style="font-family: Arial, Helvetica, sans-serif;">Hello from Lettre!</h2>
|
|
||||||
<h4 style="font-family: Arial, Helvetica, sans-serif;">A mailer library for Rust</h4>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>"#;
|
|
||||||
|
|
||||||
async fn verification(data: &AppData) {
|
#[derive(Clone, TemplateOnce)]
|
||||||
|
#[template(path = "email/verification/index.html")]
|
||||||
|
struct IndexPage<'a> {
|
||||||
|
verification_link: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> IndexPage<'a> {
|
||||||
|
fn new(verification_link: &'a str) -> Self {
|
||||||
|
Self { verification_link }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn verification(
|
||||||
|
data: &Data,
|
||||||
|
to: &str,
|
||||||
|
verification_link: &str,
|
||||||
|
) -> ServiceResult<()> {
|
||||||
if let Some(smtp) = SETTINGS.smtp.as_ref() {
|
if let Some(smtp) = SETTINGS.smtp.as_ref() {
|
||||||
let from = format!("mCaptcha Admin <{}>", smtp.from);
|
let from = format!("mCaptcha Admin <{}>", smtp.from);
|
||||||
|
let reply_to = format!("mCaptcha Admin <{}>", smtp.reply_to);
|
||||||
const SUBJECT: &str = "[mCaptcha] Please verify your email";
|
const SUBJECT: &str = "[mCaptcha] Please verify your email";
|
||||||
|
|
||||||
|
let plain_text = format!(
|
||||||
|
"
|
||||||
|
Welcome to mCaptcha!
|
||||||
|
|
||||||
|
Please verify your email address to continue.
|
||||||
|
|
||||||
|
VERIFICATION LINK: {}
|
||||||
|
|
||||||
|
Please ignore this email if you weren't expecting it.
|
||||||
|
|
||||||
|
With best regards,
|
||||||
|
Admin
|
||||||
|
instance: {}
|
||||||
|
project website: {}",
|
||||||
|
verification_link,
|
||||||
|
SETTINGS.server.domain,
|
||||||
|
crate::PKG_HOMEPAGE
|
||||||
|
);
|
||||||
|
|
||||||
|
let html = IndexPage::new(verification_link).render_once().unwrap();
|
||||||
|
|
||||||
let email = Message::builder()
|
let email = Message::builder()
|
||||||
.from(from.parse().unwrap())
|
.from(from.parse().unwrap())
|
||||||
.reply_to("Yuin <yuin@domain.tld>".parse().unwrap())
|
.reply_to(reply_to.parse().unwrap())
|
||||||
.to("Hei <hei@domain.tld>".parse().unwrap())
|
.to(to.parse().unwrap())
|
||||||
.subject(SUBJECT)
|
.subject(SUBJECT)
|
||||||
.multipart(
|
.multipart(
|
||||||
MultiPart::alternative() // This is composed of two parts.
|
MultiPart::alternative() // This is composed of two parts.
|
||||||
.singlepart(
|
.singlepart(
|
||||||
SinglePart::builder()
|
SinglePart::builder()
|
||||||
.header(header::ContentType::TEXT_PLAIN)
|
.header(header::ContentType::TEXT_PLAIN)
|
||||||
.body(String::from(
|
.body(plain_text), // Every message should have a plain text fallback.
|
||||||
"Hello from Lettre! A mailer library for Rust",
|
|
||||||
)), // Every message should have a plain text fallback.
|
|
||||||
)
|
)
|
||||||
.singlepart(
|
.singlepart(
|
||||||
SinglePart::builder()
|
SinglePart::builder()
|
||||||
.header(header::ContentType::TEXT_HTML)
|
.header(header::ContentType::TEXT_HTML)
|
||||||
.body(String::from(HTML)),
|
.body(html),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// unwrap is OK as SETTINGS.smtp is check at the start
|
// unwrap is OK as SETTINGS.smtp is check at the start
|
||||||
match data.mailer.as_ref().unwrap().send(email).await {
|
data.mailer.as_ref().unwrap().send(email).await?;
|
||||||
Ok(_) => println!("Email sent successfully!"),
|
|
||||||
Err(e) => panic!("Could not send email: {:?}", e),
|
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn email_verification_works() {
|
||||||
|
const TO_ADDR: &str = "Hello <newuser@localhost>";
|
||||||
|
const VERIFICATION_LINK: &str = "https://localhost";
|
||||||
|
let data = Data::new().await;
|
||||||
|
verification(&data, TO_ADDR, VERIFICATION_LINK).await.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,18 +18,28 @@
|
|||||||
use std::convert::From;
|
use std::convert::From;
|
||||||
|
|
||||||
use actix_web::{
|
use actix_web::{
|
||||||
dev::HttpResponseBuilder,
|
dev::BaseHttpResponseBuilder as HttpResponseBuilder,
|
||||||
error::ResponseError,
|
error::ResponseError,
|
||||||
http::{header, StatusCode},
|
http::{header, StatusCode},
|
||||||
HttpResponse,
|
HttpResponse,
|
||||||
};
|
};
|
||||||
use argon2_creds::errors::CredsError;
|
use argon2_creds::errors::CredsError;
|
||||||
use derive_more::{Display, Error};
|
use derive_more::{Display, Error};
|
||||||
|
use lettre::transport::smtp::Error as SmtpError;
|
||||||
use libmcaptcha::errors::CaptchaError;
|
use libmcaptcha::errors::CaptchaError;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use url::ParseError;
|
use url::ParseError;
|
||||||
use validator::ValidationErrors;
|
use validator::ValidationErrors;
|
||||||
|
|
||||||
|
#[derive(Debug, Display, Error)]
|
||||||
|
pub struct SmtpErrorWrapper(SmtpError);
|
||||||
|
|
||||||
|
impl std::cmp::PartialEq for SmtpErrorWrapper {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.0.status() == other.0.status()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Display, PartialEq, Error)]
|
#[derive(Debug, Display, PartialEq, Error)]
|
||||||
#[cfg(not(tarpaulin_include))]
|
#[cfg(not(tarpaulin_include))]
|
||||||
pub enum ServiceError {
|
pub enum ServiceError {
|
||||||
@ -81,6 +91,10 @@ pub enum ServiceError {
|
|||||||
#[display(fmt = "Email not available")]
|
#[display(fmt = "Email not available")]
|
||||||
EmailTaken,
|
EmailTaken,
|
||||||
|
|
||||||
|
/// Unable to send email
|
||||||
|
#[display(fmt = "Unable to send email, contact admin")]
|
||||||
|
UnableToSendEmail(SmtpErrorWrapper),
|
||||||
|
|
||||||
/// when the a token name is already taken
|
/// when the a token name is already taken
|
||||||
/// token not found
|
/// token not found
|
||||||
#[display(fmt = "Token not found. Is token registered?")]
|
#[display(fmt = "Token not found. Is token registered?")]
|
||||||
@ -101,10 +115,14 @@ impl ResponseError for ServiceError {
|
|||||||
#[cfg(not(tarpaulin_include))]
|
#[cfg(not(tarpaulin_include))]
|
||||||
fn error_response(&self) -> HttpResponse {
|
fn error_response(&self) -> HttpResponse {
|
||||||
HttpResponseBuilder::new(self.status_code())
|
HttpResponseBuilder::new(self.status_code())
|
||||||
.set_header(header::CONTENT_TYPE, "application/json; charset=UTF-8")
|
.append_header((header::CONTENT_TYPE, "application/json; charset=UTF-8"))
|
||||||
.json(ErrorToResponse {
|
.body(
|
||||||
|
serde_json::to_string(&ErrorToResponse {
|
||||||
error: self.to_string(),
|
error: self.to_string(),
|
||||||
})
|
})
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(tarpaulin_include))]
|
#[cfg(not(tarpaulin_include))]
|
||||||
@ -130,10 +148,18 @@ impl ResponseError for ServiceError {
|
|||||||
ServiceError::EmailTaken => StatusCode::BAD_REQUEST,
|
ServiceError::EmailTaken => StatusCode::BAD_REQUEST,
|
||||||
|
|
||||||
ServiceError::TokenNotFound => StatusCode::NOT_FOUND,
|
ServiceError::TokenNotFound => StatusCode::NOT_FOUND,
|
||||||
ServiceError::CaptchaError(e) => match e {
|
ServiceError::CaptchaError(e) => {
|
||||||
|
log::error!("{}", e);
|
||||||
|
match e {
|
||||||
CaptchaError::MailboxError => StatusCode::INTERNAL_SERVER_ERROR,
|
CaptchaError::MailboxError => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
_ => StatusCode::BAD_REQUEST,
|
_ => StatusCode::BAD_REQUEST,
|
||||||
},
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ServiceError::UnableToSendEmail(e) => {
|
||||||
|
log::error!("{}", e.0);
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -189,6 +215,14 @@ impl From<sqlx::Error> for ServiceError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(tarpaulin_include))]
|
||||||
|
impl From<SmtpError> for ServiceError {
|
||||||
|
#[cfg(not(tarpaulin_include))]
|
||||||
|
fn from(e: SmtpError) -> Self {
|
||||||
|
ServiceError::UnableToSendEmail(SmtpErrorWrapper(e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(not(tarpaulin_include))]
|
#[cfg(not(tarpaulin_include))]
|
||||||
pub type ServiceResult<V> = std::result::Result<V, ServiceError>;
|
pub type ServiceResult<V> = std::result::Result<V, ServiceError>;
|
||||||
|
|
||||||
@ -223,10 +257,10 @@ impl ResponseError for PageError {
|
|||||||
use crate::PAGES;
|
use crate::PAGES;
|
||||||
match self.status_code() {
|
match self.status_code() {
|
||||||
StatusCode::INTERNAL_SERVER_ERROR => HttpResponse::Found()
|
StatusCode::INTERNAL_SERVER_ERROR => HttpResponse::Found()
|
||||||
.header(header::LOCATION, PAGES.errors.internal_server_error)
|
.append_header((header::LOCATION, PAGES.errors.internal_server_error))
|
||||||
.finish(),
|
.finish(),
|
||||||
_ => HttpResponse::Found()
|
_ => HttpResponse::Found()
|
||||||
.header(header::LOCATION, PAGES.errors.unknown_error)
|
.append_header((header::LOCATION, PAGES.errors.unknown_error))
|
||||||
.finish(),
|
.finish(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -105,6 +105,7 @@ async fn main() -> std::io::Result<()> {
|
|||||||
|
|
||||||
let data = Data::new().await;
|
let data = Data::new().await;
|
||||||
sqlx::migrate!("./migrations/").run(&data.db).await.unwrap();
|
sqlx::migrate!("./migrations/").run(&data.db).await.unwrap();
|
||||||
|
let data = actix_web::web::Data::new(data);
|
||||||
|
|
||||||
println!("Starting server on: http://{}", SETTINGS.server.get_ip());
|
println!("Starting server on: http://{}", SETTINGS.server.get_ip());
|
||||||
|
|
||||||
@ -117,9 +118,9 @@ async fn main() -> std::io::Result<()> {
|
|||||||
)
|
)
|
||||||
.wrap(get_identity_service())
|
.wrap(get_identity_service())
|
||||||
.wrap(actix_middleware::Compress::default())
|
.wrap(actix_middleware::Compress::default())
|
||||||
.data(data.clone())
|
.app_data(data.clone())
|
||||||
.wrap(actix_middleware::NormalizePath::new(
|
.wrap(actix_middleware::NormalizePath::new(
|
||||||
actix_middleware::normalize::TrailingSlash::Trim,
|
actix_middleware::TrailingSlash::Trim,
|
||||||
))
|
))
|
||||||
.configure(v1::services)
|
.configure(v1::services)
|
||||||
.configure(widget::services)
|
.configure(widget::services)
|
||||||
@ -149,7 +150,7 @@ pub fn get_identity_service() -> IdentityService<CookieIdentityPolicy> {
|
|||||||
CookieIdentityPolicy::new(cookie_secret.as_bytes())
|
CookieIdentityPolicy::new(cookie_secret.as_bytes())
|
||||||
.name("Authorization")
|
.name("Authorization")
|
||||||
//TODO change cookie age
|
//TODO change cookie age
|
||||||
.max_age(216000)
|
.max_age_secs(216000)
|
||||||
.domain(&SETTINGS.server.domain)
|
.domain(&SETTINGS.server.domain)
|
||||||
.secure(false),
|
.secure(false),
|
||||||
)
|
)
|
||||||
|
|||||||
@ -16,8 +16,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#![allow(clippy::type_complexity)]
|
#![allow(clippy::type_complexity)]
|
||||||
use std::task::{Context, Poll};
|
//use std::task::{Context, Poll};
|
||||||
|
|
||||||
|
use actix_http::body::AnyBody;
|
||||||
use actix_identity::Identity;
|
use actix_identity::Identity;
|
||||||
use actix_service::{Service, Transform};
|
use actix_service::{Service, Transform};
|
||||||
use actix_web::dev::{ServiceRequest, ServiceResponse};
|
use actix_web::dev::{ServiceRequest, ServiceResponse};
|
||||||
@ -29,13 +30,12 @@ use crate::PAGES;
|
|||||||
|
|
||||||
pub struct CheckLogin;
|
pub struct CheckLogin;
|
||||||
|
|
||||||
impl<S, B> Transform<S> for CheckLogin
|
impl<S> Transform<S, ServiceRequest> for CheckLogin
|
||||||
where
|
where
|
||||||
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
|
S: Service<ServiceRequest, Response = ServiceResponse<AnyBody>, Error = Error>,
|
||||||
S::Future: 'static,
|
S::Future: 'static,
|
||||||
{
|
{
|
||||||
type Request = ServiceRequest;
|
type Response = ServiceResponse<AnyBody>;
|
||||||
type Response = ServiceResponse<B>;
|
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Transform = CheckLoginMiddleware<S>;
|
type Transform = CheckLoginMiddleware<S>;
|
||||||
type InitError = ();
|
type InitError = ();
|
||||||
@ -49,21 +49,41 @@ pub struct CheckLoginMiddleware<S> {
|
|||||||
service: S,
|
service: S,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S, B> Service for CheckLoginMiddleware<S>
|
impl<S> Service<ServiceRequest> for CheckLoginMiddleware<S>
|
||||||
where
|
where
|
||||||
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
|
S: Service<ServiceRequest, Response = ServiceResponse<AnyBody>, Error = Error>,
|
||||||
S::Future: 'static,
|
S::Future: 'static,
|
||||||
{
|
{
|
||||||
type Request = ServiceRequest;
|
type Response = ServiceResponse<AnyBody>;
|
||||||
type Response = ServiceResponse<B>;
|
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Future = Either<S::Future, Ready<Result<Self::Response, Self::Error>>>;
|
type Future = Either<S::Future, Ready<Result<Self::Response, Self::Error>>>;
|
||||||
|
|
||||||
fn poll_ready(&mut self, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
|
// fn poll_ready(&mut self, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
|
||||||
self.service.poll_ready(cx)
|
// self.service.poll_ready(cx)
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
|
actix_service::forward_ready!(service);
|
||||||
|
|
||||||
|
fn call(&self, req: ServiceRequest) -> Self::Future {
|
||||||
|
// let (r, mut pl) = req.into_parts();
|
||||||
|
|
||||||
|
// // TODO investigate when the bellow statement will
|
||||||
|
// // return error
|
||||||
|
// if let Ok(Some(_)) = Identity::from_request(&r, &mut pl)
|
||||||
|
// .into_inner()
|
||||||
|
// .map(|x| x.identity())
|
||||||
|
// {
|
||||||
|
// let req = ServiceRequest::from_parts(r, pl);
|
||||||
|
// Either::Left(self.service.call(req))
|
||||||
|
// } else {
|
||||||
|
// let resp = actix_http::ResponseBuilder::new(http::StatusCode::FOUND)
|
||||||
|
// .insert_header((http::header::LOCATION, PAGES.auth.login))
|
||||||
|
// .finish();
|
||||||
|
|
||||||
|
// let req = ServiceRequest::from_parts(r, pl);
|
||||||
|
// Either::Right(ok(req.into_response(resp)))
|
||||||
|
// }
|
||||||
|
|
||||||
fn call(&mut self, req: ServiceRequest) -> Self::Future {
|
|
||||||
let (r, mut pl) = req.into_parts();
|
let (r, mut pl) = req.into_parts();
|
||||||
|
|
||||||
// TODO investigate when the bellow statement will
|
// TODO investigate when the bellow statement will
|
||||||
@ -72,15 +92,14 @@ where
|
|||||||
.into_inner()
|
.into_inner()
|
||||||
.map(|x| x.identity())
|
.map(|x| x.identity())
|
||||||
{
|
{
|
||||||
let req = ServiceRequest::from_parts(r, pl).ok().unwrap();
|
let req = ServiceRequest::from_parts(r, pl);
|
||||||
Either::Left(self.service.call(req))
|
Either::Left(self.service.call(req))
|
||||||
} else {
|
} else {
|
||||||
let req = ServiceRequest::from_parts(r, pl).ok().unwrap();
|
let req = ServiceRequest::from_parts(r, pl); //.ok().unwrap();
|
||||||
Either::Right(ok(req.into_response(
|
Either::Right(ok(req.into_response(
|
||||||
HttpResponse::Found()
|
HttpResponse::Found()
|
||||||
.header(http::header::LOCATION, PAGES.auth.login)
|
.insert_header((http::header::LOCATION, PAGES.auth.login))
|
||||||
.finish()
|
.finish(),
|
||||||
.into_body(),
|
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -50,7 +50,7 @@ lazy_static! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn error(path: web::Path<usize>) -> impl Responder {
|
async fn error(path: web::Path<usize>) -> impl Responder {
|
||||||
let resp = match path.0 {
|
let resp = match path.into_inner() {
|
||||||
500 => HttpResponse::InternalServerError()
|
500 => HttpResponse::InternalServerError()
|
||||||
.content_type("text/html; charset=utf-8")
|
.content_type("text/html; charset=utf-8")
|
||||||
.body(&*INTERNAL_SERVER_ERROR_BODY),
|
.body(&*INTERNAL_SERVER_ERROR_BODY),
|
||||||
|
|||||||
@ -54,14 +54,8 @@ mod tests {
|
|||||||
let (data, _, signin_resp) = register_and_signin(NAME, EMAIL, PASSWORD).await;
|
let (data, _, signin_resp) = register_and_signin(NAME, EMAIL, PASSWORD).await;
|
||||||
let cookies = get_cookie!(signin_resp);
|
let cookies = get_cookie!(signin_resp);
|
||||||
|
|
||||||
let mut app = test::init_service(
|
|
||||||
App::new()
|
let mut app = get_app!(data).await;
|
||||||
.wrap(get_identity_service())
|
|
||||||
.configure(crate::api::v1::services)
|
|
||||||
.configure(services)
|
|
||||||
.data(data.clone()),
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let urls = vec![
|
let urls = vec![
|
||||||
PAGES.home,
|
PAGES.home,
|
||||||
|
|||||||
@ -66,7 +66,7 @@ pub async fn view_sitekey(
|
|||||||
id: Identity,
|
id: Identity,
|
||||||
) -> PageResult<impl Responder> {
|
) -> PageResult<impl Responder> {
|
||||||
let username = id.identity().unwrap();
|
let username = id.identity().unwrap();
|
||||||
let key = path.0;
|
let key = path.into_inner();
|
||||||
|
|
||||||
let config = sqlx::query_as!(
|
let config = sqlx::query_as!(
|
||||||
McaptchaConfig,
|
McaptchaConfig,
|
||||||
|
|||||||
@ -41,6 +41,7 @@ pub struct Captcha {
|
|||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
pub struct Smtp {
|
pub struct Smtp {
|
||||||
pub from: String,
|
pub from: String,
|
||||||
|
pub reply_to: String,
|
||||||
pub url: String,
|
pub url: String,
|
||||||
pub username: String,
|
pub username: String,
|
||||||
pub password: String,
|
pub password: String,
|
||||||
|
|||||||
@ -37,7 +37,7 @@ fn handle_assets(path: &str) -> HttpResponse {
|
|||||||
};
|
};
|
||||||
|
|
||||||
HttpResponse::Ok()
|
HttpResponse::Ok()
|
||||||
.set(header::CacheControl(vec![
|
.insert_header(header::CacheControl(vec![
|
||||||
header::CacheDirective::Public,
|
header::CacheDirective::Public,
|
||||||
header::CacheDirective::Extension("immutable".into(), None),
|
header::CacheDirective::Extension("immutable".into(), None),
|
||||||
header::CacheDirective::MaxAge(CACHE_AGE),
|
header::CacheDirective::MaxAge(CACHE_AGE),
|
||||||
@ -51,7 +51,7 @@ fn handle_assets(path: &str) -> HttpResponse {
|
|||||||
|
|
||||||
#[get("/assets/{_:.*}")]
|
#[get("/assets/{_:.*}")]
|
||||||
pub async fn static_files(path: web::Path<String>) -> impl Responder {
|
pub async fn static_files(path: web::Path<String>) -> impl Responder {
|
||||||
handle_assets(&path.0)
|
handle_assets(&path)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(RustEmbed)]
|
#[derive(RustEmbed)]
|
||||||
@ -67,7 +67,7 @@ fn handle_favicons(path: &str) -> HttpResponse {
|
|||||||
};
|
};
|
||||||
|
|
||||||
HttpResponse::Ok()
|
HttpResponse::Ok()
|
||||||
.set(header::CacheControl(vec![
|
.insert_header(header::CacheControl(vec![
|
||||||
header::CacheDirective::Public,
|
header::CacheDirective::Public,
|
||||||
header::CacheDirective::Extension("immutable".into(), None),
|
header::CacheDirective::Extension("immutable".into(), None),
|
||||||
header::CacheDirective::MaxAge(CACHE_AGE),
|
header::CacheDirective::MaxAge(CACHE_AGE),
|
||||||
@ -82,7 +82,7 @@ fn handle_favicons(path: &str) -> HttpResponse {
|
|||||||
#[get("/{file}")]
|
#[get("/{file}")]
|
||||||
pub async fn favicons(path: web::Path<String>) -> impl Responder {
|
pub async fn favicons(path: web::Path<String>) -> impl Responder {
|
||||||
debug!("searching favicons");
|
debug!("searching favicons");
|
||||||
handle_favicons(&path.0)
|
handle_favicons(&path)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@ -43,7 +43,7 @@ macro_rules! post_request {
|
|||||||
($serializable:expr, $uri:expr) => {
|
($serializable:expr, $uri:expr) => {
|
||||||
test::TestRequest::post()
|
test::TestRequest::post()
|
||||||
.uri($uri)
|
.uri($uri)
|
||||||
.header(header::CONTENT_TYPE, "application/json")
|
.insert_header((header::CONTENT_TYPE, "application/json"))
|
||||||
.set_payload(serde_json::to_string($serializable).unwrap())
|
.set_payload(serde_json::to_string($serializable).unwrap())
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -67,7 +67,7 @@ macro_rules! get_app {
|
|||||||
App::new()
|
App::new()
|
||||||
.wrap(get_identity_service())
|
.wrap(get_identity_service())
|
||||||
.wrap(actix_middleware::NormalizePath::new(
|
.wrap(actix_middleware::NormalizePath::new(
|
||||||
actix_middleware::normalize::TrailingSlash::Trim,
|
actix_middleware::TrailingSlash::Trim,
|
||||||
))
|
))
|
||||||
.configure(crate::api::v1::services)
|
.configure(crate::api::v1::services)
|
||||||
.configure(crate::widget::services)
|
.configure(crate::widget::services)
|
||||||
@ -81,7 +81,7 @@ macro_rules! get_app {
|
|||||||
App::new()
|
App::new()
|
||||||
.wrap(get_identity_service())
|
.wrap(get_identity_service())
|
||||||
.wrap(actix_middleware::NormalizePath::new(
|
.wrap(actix_middleware::NormalizePath::new(
|
||||||
actix_middleware::normalize::TrailingSlash::Trim,
|
actix_middleware::TrailingSlash::Trim,
|
||||||
))
|
))
|
||||||
.configure(crate::api::v1::services)
|
.configure(crate::api::v1::services)
|
||||||
.configure(crate::widget::services)
|
.configure(crate::widget::services)
|
||||||
@ -89,7 +89,7 @@ macro_rules! get_app {
|
|||||||
.configure(crate::pages::services)
|
.configure(crate::pages::services)
|
||||||
.configure(crate::static_assets::services)
|
.configure(crate::static_assets::services)
|
||||||
//.data(std::sync::Arc::new(crate::data::Data::new().await))
|
//.data(std::sync::Arc::new(crate::data::Data::new().await))
|
||||||
.data($data.clone()),
|
.app_data(actix_web::web::Data::new($data.clone())),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -82,7 +82,7 @@ fn handle_widget_assets(path: &str) -> HttpResponse {
|
|||||||
};
|
};
|
||||||
|
|
||||||
HttpResponse::Ok()
|
HttpResponse::Ok()
|
||||||
.set(header::CacheControl(vec![header::CacheDirective::MaxAge(
|
.insert_header(header::CacheControl(vec![header::CacheDirective::MaxAge(
|
||||||
crate::CACHE_AGE,
|
crate::CACHE_AGE,
|
||||||
)]))
|
)]))
|
||||||
.content_type(from_path(path).first_or_octet_stream().as_ref())
|
.content_type(from_path(path).first_or_octet_stream().as_ref())
|
||||||
@ -94,7 +94,7 @@ fn handle_widget_assets(path: &str) -> HttpResponse {
|
|||||||
|
|
||||||
#[get("/widget/{_:.*}")]
|
#[get("/widget/{_:.*}")]
|
||||||
pub async fn widget_assets(path: web::Path<String>) -> impl Responder {
|
pub async fn widget_assets(path: web::Path<String>) -> impl Responder {
|
||||||
handle_widget_assets(&path.0)
|
handle_widget_assets(&path)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn services(cfg: &mut web::ServiceConfig) {
|
pub fn services(cfg: &mut web::ServiceConfig) {
|
||||||
|
|||||||
59
templates/email/components/footer/index.html
Normal file
59
templates/email/components/footer/index.html
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
<div>
|
||||||
|
<hr />
|
||||||
|
</div>
|
||||||
|
<footer role="contentinfo" class="details__container">
|
||||||
|
<div class="details__row1">
|
||||||
|
<p class="details__copyright-text">© mCaptcha developers</p>
|
||||||
|
<ul class="details">
|
||||||
|
<li class="details__copyright"></li>
|
||||||
|
|
||||||
|
<li class="details__item">
|
||||||
|
<a class="details__link" href="<.= crate::PKG_HOMEPAGE .>">Homepage</a>
|
||||||
|
</li>
|
||||||
|
<li class="details__item">
|
||||||
|
<a
|
||||||
|
class="details__link"
|
||||||
|
href="<.= crate::PKG_HOMEPAGE .><.= crate::PAGES.about .>"
|
||||||
|
>About</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li class="details__item">
|
||||||
|
<a
|
||||||
|
class="details__link"
|
||||||
|
href="<.= crate::PKG_HOMEPAGE .><.= crate::PAGES.privacy .>"
|
||||||
|
>Privacy</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul class="details">
|
||||||
|
<li class="details__item">
|
||||||
|
<a
|
||||||
|
class="details__link"
|
||||||
|
href="<.= crate::PKG_HOMEPAGE .><.= crate::PAGES.security .>"
|
||||||
|
>Security</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li class="details__item">
|
||||||
|
<a
|
||||||
|
class="details__link"
|
||||||
|
href="<.= crate::PKG_HOMEPAGE .><.= crate::PAGES.donate .>"
|
||||||
|
>Donate</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="details__item">
|
||||||
|
<a
|
||||||
|
class="details__link"
|
||||||
|
href="<.= crate::PKG_HOMEPAGE .><.= crate::PAGES.thanks .>"
|
||||||
|
>Thanks</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li class="details__item">
|
||||||
|
<a class="details__link" href="<.= &*crate::SOURCE_FILES_OF_INSTANCE .>">
|
||||||
|
v<.= crate::VERSION .>-<.= crate::GIT_COMMIT_HASH[0..8] .>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</footer>
|
||||||
38
templates/email/components/footer/main.css
Normal file
38
templates/email/components/footer/main.css
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
.details__container {
|
||||||
|
display: flex;
|
||||||
|
font-size: 14px;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.details__copyright {
|
||||||
|
font-size: 14px;
|
||||||
|
flex: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.details {
|
||||||
|
list-style: none;
|
||||||
|
bottom: 0px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
font-size: 14px;
|
||||||
|
margin: auto;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.details__item {
|
||||||
|
margin: auto 10px;
|
||||||
|
list-style: none;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.details__link {
|
||||||
|
color: rgb(3, 102, 214);
|
||||||
|
}
|
||||||
|
|
||||||
|
.details__row1 {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.details__row1 > .details {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
15
templates/email/css/base.css
Normal file
15
templates/email/css/base.css
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
* {
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
max-width: 450px;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
1
templates/email/css/message-text.css
Normal file
1
templates/email/css/message-text.css
Normal file
@ -0,0 +1 @@
|
|||||||
|
font-size: 1.2rem; font-weight: 500;
|
||||||
4
templates/email/verification/css/verification__link.css
Normal file
4
templates/email/verification/css/verification__link.css
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
.verification__link {
|
||||||
|
align-self: center;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
50
templates/email/verification/index.html
Normal file
50
templates/email/verification/index.html
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title><.= PAGE .> | <.= crate::pages::NAME .></title>
|
||||||
|
<style type="text/css" media="screen">
|
||||||
|
<. include!("../components/footer/main.css"); .>
|
||||||
|
<. include!("../css/base.css"); .>
|
||||||
|
<. include!("../css/message-text.css"); .>
|
||||||
|
<. include!("./css/verification__link.css"); .>;
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1>
|
||||||
|
Welcome to mCaptcha!
|
||||||
|
</h1>
|
||||||
|
<p class="message__text">
|
||||||
|
Please verify your email address to continue.
|
||||||
|
</p>
|
||||||
|
<button class="button">
|
||||||
|
Click here to verify
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<p class="message__text">
|
||||||
|
If you were not able to see the verification button, click the following
|
||||||
|
link:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<a
|
||||||
|
class="verification__link"
|
||||||
|
href="<.= verification_link .>"
|
||||||
|
target="_blank"
|
||||||
|
><.= verification_link .></a
|
||||||
|
>
|
||||||
|
|
||||||
|
<p class="message__text">
|
||||||
|
The link expires in 15 minutes. Please ignore this email if you weren't
|
||||||
|
expecting it.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p class="message__text">
|
||||||
|
With best regards,<br />
|
||||||
|
Admin<br />
|
||||||
|
</p>
|
||||||
|
<. include!("../components/footer/index.html"); .>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Loading…
x
Reference in New Issue
Block a user