Lines
56.25 %
Functions
10.61 %
Branches
100 %
mod auth;
mod config;
pub mod data;
mod data_format;
pub mod error;
pub mod handlers;
mod mail_config;
mod mailer;
mod util;
use crate::auth::Claims;
use actix_http::HttpMessage;
use actix_web::{
dev::ServiceRequest,
error::ErrorBadRequest,
web::{self, Json},
Responder, Result,
};
use actix_web_httpauth::extractors::bearer::BearerAuth;
use auth::validate_token;
use chrono::Utc;
use data::Data;
use data_format::ReviewRequest;
use jsonwebtoken::{encode, EncodingKey, Header};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::{collections::HashMap, path::PathBuf};
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
#[serde(deny_unknown_fields)]
pub struct ReviewRequestForm {
pub projectname: String,
pub name: String,
pub phone: Option<String>,
pub email: String,
pub programme: String,
pub summary: String,
pub www: String,
pub i18n: Option<String>,
pub licensing: Option<String>,
pub testing: Option<String>,
pub community: Option<String>,
pub a11y: Option<String>,
pub packaging: Option<String>,
pub diversity: Option<String>,
pub standards: Option<String>,
pub security: Option<String>,
pub governance: Option<String>,
pub uxtesting: Option<String>,
pub technicalwriting: Option<String>,
pub comments: String,
}
fn form_to_request(form: ReviewRequestForm) -> Result<ReviewRequest> {
use actix_web::error;
if form.projectname.is_empty() {
return Err(error::ErrorBadRequest("projectname is empty"));
if form.name.is_empty() {
return Err(error::ErrorBadRequest("name is empty"));
if form.email.is_empty() {
return Err(error::ErrorBadRequest("email is empty"));
if form.programme.is_empty() {
return Err(error::ErrorBadRequest("programme is empty"));
if form.www.is_empty() {
return Err(error::ErrorBadRequest("www is empty"));
Ok(ReviewRequest {
date: Utc::now(),
projectname: form.projectname,
name: form.name,
phone: form.phone,
email: form.email,
programme: form.programme,
www: form.www,
summary: form.summary,
i18n: form.i18n.is_some(),
licensing: form.licensing.is_some(),
testing: form.testing.is_some(),
community: form.community.is_some(),
a11y: form.a11y.is_some(),
packaging: form.packaging.is_some(),
diversity: form.diversity.is_some(),
standards: form.standards.is_some(),
security: form.security.is_some(),
governance: form.governance.is_some(),
uxtesting: form.uxtesting.is_some(),
technicalwriting: form.technicalwriting.is_some(),
comments: form.comments,
})
// Handler for the web form
pub async fn form_cgi(
form: web::Form<ReviewRequestForm>,
data: web::Data<Data>,
) -> Result<impl Responder> {
let request = form_to_request(form.into_inner())?;
if !util::check_websites(&request.www) {
return Err(ErrorBadRequest(
"One or more of the websites are not valid.",
));
let json = serde_json::to_string_pretty(&request)?;
let _request = data.add_request(json.as_bytes()).await?;
Ok("your form was submitted!")
// Function for test_mode to get a jwt token.
pub async fn get_jwt(claims: Json<Value>, data: web::Data<Data>) -> Result<impl Responder> {
let header = encode(
&Header::default(),
&claims,
&EncodingKey::from_secret(data.jwks().secret()),
)
.unwrap();
let mut token = HashMap::new();
token.insert("token".to_string(), header);
Ok(Json(token))
/*
* ```
* curl -v --header "Authorization: Bearer testing" http://0.0.0.0:8085/api/requests
*/
pub async fn validator(
req: ServiceRequest,
credentials: BearerAuth,
) -> Result<ServiceRequest, (actix_web::Error, ServiceRequest)> {
match validate_token(credentials.token(), data.jwks().secret()) {
Ok(claims) => {
req.extensions_mut().insert::<Claims>(claims);
Ok(req)
Err(e) => Err((error::ErrorBadRequest(e), req)),
// return 'static' from the working directory if that is a dir and otherwise
// use the installed version
pub fn get_static_dir() -> PathBuf {
let dir = PathBuf::from("static");
if dir.is_dir() && dir.join("app.js").exists() {
return dir;
let dir = std::env::current_exe()
.unwrap()
.parent()
.join("share/static");
if !dir.is_dir() {
panic!("There is no 'static' directory.");
dir
#[macro_export]
macro_rules! app (
($data: expr, $cors: expr) => ({
use ngi0review::{validator, handlers::*};
use actix_files::Files;
use actix_web::middleware::Logger;
let data_clone = $data.clone();
let auth = actix_web_httpauth::middleware::HttpAuthentication::bearer(
move |req, credentials|
validator(req, credentials, data_clone.clone()));
let cors = actix_cors::Cors::default()
.allow_any_origin()
.allowed_methods(["GET"]);
let test_mode = $data.config().test_mode;
let app = App::new()
.wrap(Logger::default())
.app_data($data.clone())
.service(web::scope("/static").wrap(cors).service(Files::new("", $crate::get_static_dir())))
.service(Files::new("/files", $data.data_path().join("files")).disable_content_disposition())
// receive the posted forms
.service(web::scope("/NGI0/review").route("/form.cgi", web::post().to(ngi0review::form_cgi)))
.service(web::scope("/api")
.wrap(auth)
.wrap($cors)
.route("/selectors", web::get().to(get_selectors))
.route("/review-types", web::get().to(get_review_types))
.route("/rejections", web::get().to(get_rejections))
.route("/rejections", web::post().to(add_rejection))
.route("/requests", web::get().to(get_requests))
.route("/requests", web::post().to(add_request))
.route("/tasks", web::get().to(get_tasks))
.route("/tasks", web::post().to(add_task))
.route("/reports", web::get().to(get_reports))
.route("/reports", web::post().to(add_report))
.route("/files", web::post().to(add_file)));
if test_mode {
// add route to hand out jwt tokens
app.route("/jwt", web::post().to(ngi0review::get_jwt))
} else {
app
});
);