diff --git a/Dockerfile b/Dockerfile index 16bc272..e8ac570 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ RUN cargo chef cook --release --target=$target --recipe-path recipe.json COPY ./actix/Cargo.toml ./actix/Cargo.lock ./ COPY ./actix/src ./src # Build application -RUN cargo build --release --target=$target --offline --bin chhoto-url +RUN cargo build --release --target=$target --locked --bin chhoto-url RUN cp /chhoto-url/target/$target/release/chhoto-url /chhoto-url/release FROM scratch diff --git a/actix/src/auth.rs b/actix/src/auth.rs index 0e91da6..611d93d 100644 --- a/actix/src/auth.rs +++ b/actix/src/auth.rs @@ -2,11 +2,11 @@ // SPDX-License-Identifier: MIT use actix_session::Session; -use std::{env, time::SystemTime}; use actix_web::HttpRequest; +use std::{env, time::SystemTime}; // API key generation and scoring -use passwords::{PasswordGenerator, scorer, analyzer}; +use passwords::{analyzer, scorer, PasswordGenerator}; // Validate API key pub fn validate_key(key: String) -> bool { diff --git a/actix/src/main.rs b/actix/src/main.rs index 79dcfa7..da27c68 100644 --- a/actix/src/main.rs +++ b/actix/src/main.rs @@ -47,7 +47,11 @@ async fn main() -> Result<()> { } // Tell the user that the server has started, and where it is listening to, rather than simply outputting nothing - eprintln!("Server has started at 0.0.0.0 on port {}. Configured Site URL is: {}", port, env::var("site_url").unwrap_or(String::from("http://localhost"))); + eprintln!( + "Server has started at 0.0.0.0 on port {}. Configured Site URL is: {}", + port, + env::var("site_url").unwrap_or(String::from("http://localhost")) + ); // Actually start the server HttpServer::new(move || { diff --git a/actix/src/services.rs b/actix/src/services.rs index 4b4a573..4a10d4d 100644 --- a/actix/src/services.rs +++ b/actix/src/services.rs @@ -3,7 +3,13 @@ use actix_files::NamedFile; use actix_session::Session; -use actix_web::{delete, get, http::StatusCode, post, web::{self, Redirect}, Either, HttpRequest, HttpResponse, Responder}; +use actix_web::{ + delete, get, + http::StatusCode, + post, + web::{self, Redirect}, + Either, HttpRequest, HttpResponse, Responder, +}; use std::env; // Serialize JSON data @@ -20,9 +26,9 @@ const VERSION: &str = env!("CARGO_PKG_VERSION"); // Define JSON struct for returning JSON data #[derive(Serialize)] struct Response { - success: bool, - error: bool, - reason: String, + success: bool, + error: bool, + reason: String, } // Needs to return the short URL to make it easier for programs leveraging the API @@ -37,7 +43,12 @@ struct CreatedURL { // Add new links #[post("/api/new")] -pub async fn add_link(req: String, data: web::Data, session: Session, http: HttpRequest) -> HttpResponse { +pub async fn add_link( + req: String, + data: web::Data, + session: Session, + http: HttpRequest, +) -> HttpResponse { // Call is_api_ok() function, pass HttpRequest let result = utils::is_api_ok(http); // If success, add new link @@ -48,41 +59,47 @@ pub async fn add_link(req: String, data: web::Data, session: Session, .unwrap_or(String::from("4567")) .parse::() .expect("Supplied port is not an integer"); - let url = format!("{}:{}", env::var("site_url").unwrap_or(String::from("http://localhost")), port); + let url = format!( + "{}:{}", + env::var("site_url").unwrap_or(String::from("http://localhost")), + port + ); let response = CreatedURL { success: true, error: false, - shorturl: format!("{}/{}", url, out.1) + shorturl: format!("{}/{}", url, out.1), }; HttpResponse::Created().json(response) } else { let response = Response { success: false, error: true, - reason: out.1 + reason: out.1, }; HttpResponse::Conflict().json(response) } } else if result.error { HttpResponse::Unauthorized().json(result) - // If "pass" is true - keeps backwards compatibility - } else { - if env::var("public_mode") == Ok(String::from("Enable")) || auth::validate(session) { - let out = utils::add_link(req, &data.db); - if out.0 { - HttpResponse::Created().body(out.1) - } else { - HttpResponse::Conflict().body(out.1) - } + // If password authentication or public mode is used - keeps backwards compatibility + } else if env::var("public_mode") == Ok(String::from("Enable")) || auth::validate(session) { + let out = utils::add_link(req, &data.db); + if out.0 { + HttpResponse::Created().body(out.1) } else { - HttpResponse::Unauthorized().body("Not logged in!") + HttpResponse::Conflict().body(out.1) } + } else { + HttpResponse::Unauthorized().body("Not logged in!") } } // Return all active links #[get("/api/all")] -pub async fn getall(data: web::Data, session: Session, http: HttpRequest) -> HttpResponse { +pub async fn getall( + data: web::Data, + session: Session, + http: HttpRequest, +) -> HttpResponse { // Call is_api_ok() function, pass HttpRequest let result = utils::is_api_ok(http); // If success, return all links @@ -90,18 +107,16 @@ pub async fn getall(data: web::Data, session: Session, http: HttpReque HttpResponse::Ok().body(utils::getall(&data.db)) } else if result.error { HttpResponse::Unauthorized().json(result) - // If "pass" is true - keeps backwards compatibility + // If password authentication is used - keeps backwards compatibility + } else if auth::validate(session) { + HttpResponse::Ok().body(utils::getall(&data.db)) } else { - if auth::validate(session){ - HttpResponse::Ok().body(utils::getall(&data.db)) + let body = if env::var("public_mode") == Ok(String::from("Enable")) { + "Using public mode." } else { - let body = if env::var("public_mode") == Ok(String::from("Enable")) { - "Using public mode." - } else { - "Not logged in!" - }; - HttpResponse::Unauthorized().body(body) - } + "Not logged in!" + }; + HttpResponse::Unauthorized().body(body) } } @@ -166,7 +181,7 @@ pub async fn login(req: String, session: Session) -> HttpResponse { let response = Response { success: false, error: true, - reason: "Wrong password!".to_string() + reason: "Wrong password!".to_string(), }; return HttpResponse::Unauthorized().json(response); } @@ -179,7 +194,7 @@ pub async fn login(req: String, session: Session) -> HttpResponse { let response = Response { success: true, error: false, - reason: "Correct password!".to_string() + reason: "Correct password!".to_string(), }; HttpResponse::Ok().json(response) } else { @@ -225,29 +240,27 @@ pub async fn delete_link( let response = Response { success: true, error: false, - reason: format!("Deleted {}", shortlink) + reason: format!("Deleted {}", shortlink), }; HttpResponse::Ok().json(response) } else { let response = Response { success: false, error: true, - reason: "The short link was not found, and could not be deleted.".to_string() + reason: "The short link was not found, and could not be deleted.".to_string(), }; HttpResponse::NotFound().json(response) } } else if result.error { HttpResponse::Unauthorized().json(result) // If "pass" is true - keeps backwards compatibility - } else { - if auth::validate(session) { - if utils::delete_link(shortlink.to_string(), &data.db) { - HttpResponse::Ok().body(format!("Deleted {shortlink}")) - } else { - HttpResponse::NotFound().body("Not found!") - } + } else if auth::validate(session) { + if utils::delete_link(shortlink.to_string(), &data.db) { + HttpResponse::Ok().body(format!("Deleted {shortlink}")) } else { - HttpResponse::Unauthorized().body("Not logged in!") + HttpResponse::NotFound().body("Not found!") } + } else { + HttpResponse::Unauthorized().body("Not logged in!") } } diff --git a/actix/src/utils.rs b/actix/src/utils.rs index 9641577..ab5ef9f 100644 --- a/actix/src/utils.rs +++ b/actix/src/utils.rs @@ -1,14 +1,14 @@ // SPDX-FileCopyrightText: 2023 Sayantan Santra // SPDX-License-Identifier: MIT +use crate::{auth, database}; +use actix_web::HttpRequest; use nanoid::nanoid; use rand::seq::SliceRandom; use regex::Regex; use rusqlite::Connection; use serde::{Deserialize, Serialize}; use std::env; -use actix_web::HttpRequest; -use crate::{auth, database}; // Struct for reading link pairs sent during API call #[derive(Deserialize)] @@ -26,7 +26,7 @@ pub struct Response { pass: bool, } -// If the api_key environment variable eists +// If the api_key environment variable exists pub fn is_api_ok(http: HttpRequest) -> Response { // If the api_key environment variable exists if env::var("api_key").is_ok() { @@ -34,27 +34,46 @@ pub fn is_api_ok(http: HttpRequest) -> Response { if let Some(header) = auth::api_header(&http) { // If the header is correct if auth::validate_key(header.to_string()) { - Response { success: true, error: false, reason: "Correct API key".to_string(), pass: false } + Response { + success: true, + error: false, + reason: "Correct API key".to_string(), + pass: false, + } } else { - Response { success: false, error: true, reason: "Incorrect API key".to_string(), pass: false } + Response { + success: false, + error: true, + reason: "Incorrect API key".to_string(), + pass: false, + } } // The header may not exist when the user logs in through the web interface, so allow a request with no header. // Further authentication checks will be conducted in services.rs } else { // Due to the implementation of this result in services.rs, this JSON object will not be outputted. - Response { success: false, error: false, reason: "X-API-Key header was not found".to_string(), pass: true } + Response { + success: false, + error: false, + reason: "X-API-Key header was not found".to_string(), + pass: true, + } } } else { // If the API key isn't set, but an API Key header is provided if auth::api_header(&http).is_some() { Response {success: false, error: true, reason: "An API key was provided, but the 'api_key' environment variable is not configured in the Chhoto URL instance".to_string(), pass: false} } else { - Response {success: false, error: false, reason: "".to_string(), pass: true} + Response { + success: false, + error: false, + reason: "".to_string(), + pass: true, + } } } } - // Request the DB for searching an URL pub fn get_longurl(shortlink: String, db: &Connection) -> Option { if validate_link(&shortlink) { diff --git a/compose.yaml b/compose.yaml index 78a51d6..7236653 100644 --- a/compose.yaml +++ b/compose.yaml @@ -26,7 +26,7 @@ services: - password=TopSecretPass # This needs to be set in order to use programs that use the JSON interface of Chhoto URL. - # You will get a warning if this is insecure, and a generated value will be outputted + # You will get a warning if this is insecure, and a generated value will be output # You may use that value if you can't think of a secure key # - api_key=SECURE_API_KEY