mirror of
https://github.com/mCaptcha/mCaptcha.git
synced 2024-11-26 11:25:58 +03:00
upgrading to actix-v4-beta
This commit is contained in:
parent
9ed458ebfa
commit
9f940c317a
24 changed files with 728 additions and 1192 deletions
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"
|
||||
|
||||
[dependencies]
|
||||
actix-web = "3.3.2"
|
||||
actix = "0.10"
|
||||
actix-identity = "0.3"
|
||||
actix-http = "2.2"
|
||||
actix-rt = "1"
|
||||
actix-cors= "0.5.4"
|
||||
actix-service = "1.0.6"
|
||||
#actix-web = "3.3.2"
|
||||
actix-web = "4.0.0-beta.8"
|
||||
actix = "0.12"
|
||||
#actix-identity = "0.3"
|
||||
actix-identity = "0.4.0-beta.2"
|
||||
actix-http = "3.0.0-beta.8"
|
||||
#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"}
|
||||
|
||||
|
||||
mime_guess = "2.0.3"
|
||||
rust-embed = "5.9.0"
|
||||
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 = { version="*", path = "../../argon2-creds/" }
|
||||
config = "0.11"
|
||||
validator = { version = "0.13", features = ["derive"]}
|
||||
validator = { version = "0.14", features = ["derive"]}
|
||||
|
||||
derive_builder = "0.10"
|
||||
derive_more = "0.99"
|
||||
|
@ -59,7 +63,6 @@ log = "0.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 = { path = "../libmcaptcha", features = ["full"]}
|
||||
|
||||
|
|
|
@ -49,8 +49,9 @@ pool = 4
|
|||
url = "redis://127.0.0.1"
|
||||
pool = 4
|
||||
|
||||
#[smtp]
|
||||
#from = "admin@domain.com"
|
||||
#url = "smtp.domain.com"
|
||||
#username = "admin"
|
||||
#password = "password"
|
||||
[smtp]
|
||||
from = "admin@localhost"
|
||||
reply_to = "admin@localhost"
|
||||
url = "localhost:10025"
|
||||
username = "admin"
|
||||
password = "password"
|
||||
|
|
|
@ -226,7 +226,6 @@ async fn signout(id: Identity) -> impl Responder {
|
|||
id.forget();
|
||||
}
|
||||
HttpResponse::Found()
|
||||
.header(header::LOCATION, "/login")
|
||||
.append_header((header::LOCATION, "/login"))
|
||||
.finish()
|
||||
.into_body()
|
||||
}
|
||||
|
|
|
@ -24,11 +24,11 @@ use crate::errors::*;
|
|||
use crate::AppData;
|
||||
|
||||
pub struct Notification {
|
||||
pub name: String,
|
||||
pub heading: String,
|
||||
pub message: String,
|
||||
pub received: OffsetDateTime,
|
||||
pub id: i32,
|
||||
pub name: Option<String>,
|
||||
pub heading: Option<String>,
|
||||
pub message: Option<String>,
|
||||
pub received: Option<OffsetDateTime>,
|
||||
pub id: Option<i32>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
|
@ -43,11 +43,11 @@ pub struct NotificationResp {
|
|||
impl From<Notification> for NotificationResp {
|
||||
fn from(n: Notification) -> Self {
|
||||
NotificationResp {
|
||||
name: n.name,
|
||||
heading: n.heading,
|
||||
received: n.received.unix_timestamp(),
|
||||
id: n.id,
|
||||
message: n.message,
|
||||
name: n.name.unwrap(),
|
||||
heading: n.heading.unwrap(),
|
||||
received: n.received.unwrap().unix_timestamp(),
|
||||
id: n.id.unwrap(),
|
||||
message: n.message.unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -161,7 +161,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn feature() {
|
||||
actix_rt::System::new("trest")
|
||||
actix_rt::System::new()
|
||||
.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(),
|
||||
};
|
||||
HttpResponse::Ok()
|
||||
.set(header::CacheControl(vec![header::CacheDirective::MaxAge(
|
||||
.insert_header(header::CacheControl(vec![header::CacheDirective::MaxAge(
|
||||
CACHE_AGE,
|
||||
)]))
|
||||
.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 {
|
||||
handle_embedded_file(&path.0)
|
||||
handle_embedded_file(&path)
|
||||
}
|
||||
|
||||
async fn spec() -> HttpResponse {
|
||||
|
@ -101,7 +101,7 @@ mod tests {
|
|||
let mut app = test::init_service(
|
||||
App::new()
|
||||
.wrap(actix_middleware::NormalizePath::new(
|
||||
actix_middleware::normalize::TrailingSlash::Trim,
|
||||
actix_middleware::TrailingSlash::Trim,
|
||||
))
|
||||
.configure(services),
|
||||
)
|
||||
|
|
|
@ -19,57 +19,92 @@ use lettre::{
|
|||
message::{header, MultiPart, SinglePart},
|
||||
AsyncTransport, Message,
|
||||
};
|
||||
use sailfish::TemplateOnce;
|
||||
|
||||
use crate::AppData;
|
||||
use crate::errors::*;
|
||||
use crate::Data;
|
||||
use crate::SETTINGS;
|
||||
|
||||
// The html we want to send.
|
||||
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>"#;
|
||||
const PAGE: &str = "Login";
|
||||
|
||||
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() {
|
||||
let from = format!("mCaptcha Admin <{}>", smtp.from);
|
||||
let reply_to = format!("mCaptcha Admin <{}>", smtp.reply_to);
|
||||
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()
|
||||
.from(from.parse().unwrap())
|
||||
.reply_to("Yuin <yuin@domain.tld>".parse().unwrap())
|
||||
.to("Hei <hei@domain.tld>".parse().unwrap())
|
||||
.reply_to(reply_to.parse().unwrap())
|
||||
.to(to.parse().unwrap())
|
||||
.subject(SUBJECT)
|
||||
.multipart(
|
||||
MultiPart::alternative() // This is composed of two parts.
|
||||
.singlepart(
|
||||
SinglePart::builder()
|
||||
.header(header::ContentType::TEXT_PLAIN)
|
||||
.body(String::from(
|
||||
"Hello from Lettre! A mailer library for Rust",
|
||||
)), // Every message should have a plain text fallback.
|
||||
.body(plain_text), // Every message should have a plain text fallback.
|
||||
)
|
||||
.singlepart(
|
||||
SinglePart::builder()
|
||||
.header(header::ContentType::TEXT_HTML)
|
||||
.body(String::from(HTML)),
|
||||
.body(html),
|
||||
),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// unwrap is OK as SETTINGS.smtp is check at the start
|
||||
match data.mailer.as_ref().unwrap().send(email).await {
|
||||
Ok(_) => println!("Email sent successfully!"),
|
||||
Err(e) => panic!("Could not send email: {:?}", e),
|
||||
}
|
||||
data.mailer.as_ref().unwrap().send(email).await?;
|
||||
}
|
||||
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 actix_web::{
|
||||
dev::HttpResponseBuilder,
|
||||
dev::BaseHttpResponseBuilder as HttpResponseBuilder,
|
||||
error::ResponseError,
|
||||
http::{header, StatusCode},
|
||||
HttpResponse,
|
||||
};
|
||||
use argon2_creds::errors::CredsError;
|
||||
use derive_more::{Display, Error};
|
||||
use lettre::transport::smtp::Error as SmtpError;
|
||||
use libmcaptcha::errors::CaptchaError;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::ParseError;
|
||||
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)]
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
pub enum ServiceError {
|
||||
|
@ -81,6 +91,10 @@ pub enum ServiceError {
|
|||
#[display(fmt = "Email not available")]
|
||||
EmailTaken,
|
||||
|
||||
/// Unable to send email
|
||||
#[display(fmt = "Unable to send email, contact admin")]
|
||||
UnableToSendEmail(SmtpErrorWrapper),
|
||||
|
||||
/// when the a token name is already taken
|
||||
/// token not found
|
||||
#[display(fmt = "Token not found. Is token registered?")]
|
||||
|
@ -101,10 +115,14 @@ impl ResponseError for ServiceError {
|
|||
#[cfg(not(tarpaulin_include))]
|
||||
fn error_response(&self) -> HttpResponse {
|
||||
HttpResponseBuilder::new(self.status_code())
|
||||
.set_header(header::CONTENT_TYPE, "application/json; charset=UTF-8")
|
||||
.json(ErrorToResponse {
|
||||
error: self.to_string(),
|
||||
})
|
||||
.append_header((header::CONTENT_TYPE, "application/json; charset=UTF-8"))
|
||||
.body(
|
||||
serde_json::to_string(&ErrorToResponse {
|
||||
error: self.to_string(),
|
||||
})
|
||||
.unwrap(),
|
||||
)
|
||||
.into()
|
||||
}
|
||||
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
|
@ -130,10 +148,18 @@ impl ResponseError for ServiceError {
|
|||
ServiceError::EmailTaken => StatusCode::BAD_REQUEST,
|
||||
|
||||
ServiceError::TokenNotFound => StatusCode::NOT_FOUND,
|
||||
ServiceError::CaptchaError(e) => match e {
|
||||
CaptchaError::MailboxError => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
_ => StatusCode::BAD_REQUEST,
|
||||
},
|
||||
ServiceError::CaptchaError(e) => {
|
||||
log::error!("{}", e);
|
||||
match e {
|
||||
CaptchaError::MailboxError => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
_ => 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))]
|
||||
pub type ServiceResult<V> = std::result::Result<V, ServiceError>;
|
||||
|
||||
|
@ -223,10 +257,10 @@ impl ResponseError for PageError {
|
|||
use crate::PAGES;
|
||||
match self.status_code() {
|
||||
StatusCode::INTERNAL_SERVER_ERROR => HttpResponse::Found()
|
||||
.header(header::LOCATION, PAGES.errors.internal_server_error)
|
||||
.append_header((header::LOCATION, PAGES.errors.internal_server_error))
|
||||
.finish(),
|
||||
_ => HttpResponse::Found()
|
||||
.header(header::LOCATION, PAGES.errors.unknown_error)
|
||||
.append_header((header::LOCATION, PAGES.errors.unknown_error))
|
||||
.finish(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -105,6 +105,7 @@ async fn main() -> std::io::Result<()> {
|
|||
|
||||
let data = Data::new().await;
|
||||
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());
|
||||
|
||||
|
@ -117,9 +118,9 @@ async fn main() -> std::io::Result<()> {
|
|||
)
|
||||
.wrap(get_identity_service())
|
||||
.wrap(actix_middleware::Compress::default())
|
||||
.data(data.clone())
|
||||
.app_data(data.clone())
|
||||
.wrap(actix_middleware::NormalizePath::new(
|
||||
actix_middleware::normalize::TrailingSlash::Trim,
|
||||
actix_middleware::TrailingSlash::Trim,
|
||||
))
|
||||
.configure(v1::services)
|
||||
.configure(widget::services)
|
||||
|
@ -149,7 +150,7 @@ pub fn get_identity_service() -> IdentityService<CookieIdentityPolicy> {
|
|||
CookieIdentityPolicy::new(cookie_secret.as_bytes())
|
||||
.name("Authorization")
|
||||
//TODO change cookie age
|
||||
.max_age(216000)
|
||||
.max_age_secs(216000)
|
||||
.domain(&SETTINGS.server.domain)
|
||||
.secure(false),
|
||||
)
|
||||
|
|
|
@ -16,8 +16,9 @@
|
|||
*/
|
||||
|
||||
#![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_service::{Service, Transform};
|
||||
use actix_web::dev::{ServiceRequest, ServiceResponse};
|
||||
|
@ -29,13 +30,12 @@ use crate::PAGES;
|
|||
|
||||
pub struct CheckLogin;
|
||||
|
||||
impl<S, B> Transform<S> for CheckLogin
|
||||
impl<S> Transform<S, ServiceRequest> for CheckLogin
|
||||
where
|
||||
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
|
||||
S: Service<ServiceRequest, Response = ServiceResponse<AnyBody>, Error = Error>,
|
||||
S::Future: 'static,
|
||||
{
|
||||
type Request = ServiceRequest;
|
||||
type Response = ServiceResponse<B>;
|
||||
type Response = ServiceResponse<AnyBody>;
|
||||
type Error = Error;
|
||||
type Transform = CheckLoginMiddleware<S>;
|
||||
type InitError = ();
|
||||
|
@ -49,21 +49,41 @@ pub struct CheckLoginMiddleware<S> {
|
|||
service: S,
|
||||
}
|
||||
|
||||
impl<S, B> Service for CheckLoginMiddleware<S>
|
||||
impl<S> Service<ServiceRequest> for CheckLoginMiddleware<S>
|
||||
where
|
||||
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
|
||||
S: Service<ServiceRequest, Response = ServiceResponse<AnyBody>, Error = Error>,
|
||||
S::Future: 'static,
|
||||
{
|
||||
type Request = ServiceRequest;
|
||||
type Response = ServiceResponse<B>;
|
||||
type Response = ServiceResponse<AnyBody>;
|
||||
type Error = Error;
|
||||
type Future = Either<S::Future, Ready<Result<Self::Response, Self::Error>>>;
|
||||
|
||||
fn poll_ready(&mut self, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
|
||||
self.service.poll_ready(cx)
|
||||
}
|
||||
// fn poll_ready(&mut self, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
|
||||
// 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();
|
||||
|
||||
// TODO investigate when the bellow statement will
|
||||
|
@ -72,15 +92,14 @@ where
|
|||
.into_inner()
|
||||
.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))
|
||||
} 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(
|
||||
HttpResponse::Found()
|
||||
.header(http::header::LOCATION, PAGES.auth.login)
|
||||
.finish()
|
||||
.into_body(),
|
||||
.insert_header((http::header::LOCATION, PAGES.auth.login))
|
||||
.finish(),
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@ lazy_static! {
|
|||
}
|
||||
|
||||
async fn error(path: web::Path<usize>) -> impl Responder {
|
||||
let resp = match path.0 {
|
||||
let resp = match path.into_inner() {
|
||||
500 => HttpResponse::InternalServerError()
|
||||
.content_type("text/html; charset=utf-8")
|
||||
.body(&*INTERNAL_SERVER_ERROR_BODY),
|
||||
|
|
|
@ -54,14 +54,8 @@ mod tests {
|
|||
let (data, _, signin_resp) = register_and_signin(NAME, EMAIL, PASSWORD).await;
|
||||
let cookies = get_cookie!(signin_resp);
|
||||
|
||||
let mut app = test::init_service(
|
||||
App::new()
|
||||
.wrap(get_identity_service())
|
||||
.configure(crate::api::v1::services)
|
||||
.configure(services)
|
||||
.data(data.clone()),
|
||||
)
|
||||
.await;
|
||||
|
||||
let mut app = get_app!(data).await;
|
||||
|
||||
let urls = vec![
|
||||
PAGES.home,
|
||||
|
|
|
@ -66,7 +66,7 @@ pub async fn view_sitekey(
|
|||
id: Identity,
|
||||
) -> PageResult<impl Responder> {
|
||||
let username = id.identity().unwrap();
|
||||
let key = path.0;
|
||||
let key = path.into_inner();
|
||||
|
||||
let config = sqlx::query_as!(
|
||||
McaptchaConfig,
|
||||
|
|
|
@ -41,6 +41,7 @@ pub struct Captcha {
|
|||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct Smtp {
|
||||
pub from: String,
|
||||
pub reply_to: String,
|
||||
pub url: String,
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
|
|
|
@ -37,7 +37,7 @@ fn handle_assets(path: &str) -> HttpResponse {
|
|||
};
|
||||
|
||||
HttpResponse::Ok()
|
||||
.set(header::CacheControl(vec![
|
||||
.insert_header(header::CacheControl(vec![
|
||||
header::CacheDirective::Public,
|
||||
header::CacheDirective::Extension("immutable".into(), None),
|
||||
header::CacheDirective::MaxAge(CACHE_AGE),
|
||||
|
@ -51,7 +51,7 @@ fn handle_assets(path: &str) -> HttpResponse {
|
|||
|
||||
#[get("/assets/{_:.*}")]
|
||||
pub async fn static_files(path: web::Path<String>) -> impl Responder {
|
||||
handle_assets(&path.0)
|
||||
handle_assets(&path)
|
||||
}
|
||||
|
||||
#[derive(RustEmbed)]
|
||||
|
@ -67,7 +67,7 @@ fn handle_favicons(path: &str) -> HttpResponse {
|
|||
};
|
||||
|
||||
HttpResponse::Ok()
|
||||
.set(header::CacheControl(vec![
|
||||
.insert_header(header::CacheControl(vec![
|
||||
header::CacheDirective::Public,
|
||||
header::CacheDirective::Extension("immutable".into(), None),
|
||||
header::CacheDirective::MaxAge(CACHE_AGE),
|
||||
|
@ -82,7 +82,7 @@ fn handle_favicons(path: &str) -> HttpResponse {
|
|||
#[get("/{file}")]
|
||||
pub async fn favicons(path: web::Path<String>) -> impl Responder {
|
||||
debug!("searching favicons");
|
||||
handle_favicons(&path.0)
|
||||
handle_favicons(&path)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -43,7 +43,7 @@ macro_rules! post_request {
|
|||
($serializable:expr, $uri:expr) => {
|
||||
test::TestRequest::post()
|
||||
.uri($uri)
|
||||
.header(header::CONTENT_TYPE, "application/json")
|
||||
.insert_header((header::CONTENT_TYPE, "application/json"))
|
||||
.set_payload(serde_json::to_string($serializable).unwrap())
|
||||
};
|
||||
}
|
||||
|
@ -67,7 +67,7 @@ macro_rules! get_app {
|
|||
App::new()
|
||||
.wrap(get_identity_service())
|
||||
.wrap(actix_middleware::NormalizePath::new(
|
||||
actix_middleware::normalize::TrailingSlash::Trim,
|
||||
actix_middleware::TrailingSlash::Trim,
|
||||
))
|
||||
.configure(crate::api::v1::services)
|
||||
.configure(crate::widget::services)
|
||||
|
@ -81,7 +81,7 @@ macro_rules! get_app {
|
|||
App::new()
|
||||
.wrap(get_identity_service())
|
||||
.wrap(actix_middleware::NormalizePath::new(
|
||||
actix_middleware::normalize::TrailingSlash::Trim,
|
||||
actix_middleware::TrailingSlash::Trim,
|
||||
))
|
||||
.configure(crate::api::v1::services)
|
||||
.configure(crate::widget::services)
|
||||
|
@ -89,7 +89,7 @@ macro_rules! get_app {
|
|||
.configure(crate::pages::services)
|
||||
.configure(crate::static_assets::services)
|
||||
//.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()
|
||||
.set(header::CacheControl(vec![header::CacheDirective::MaxAge(
|
||||
.insert_header(header::CacheControl(vec![header::CacheDirective::MaxAge(
|
||||
crate::CACHE_AGE,
|
||||
)]))
|
||||
.content_type(from_path(path).first_or_octet_stream().as_ref())
|
||||
|
@ -94,7 +94,7 @@ fn handle_widget_assets(path: &str) -> HttpResponse {
|
|||
|
||||
#[get("/widget/{_:.*}")]
|
||||
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) {
|
||||
|
|
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…
Reference in a new issue