1
0
Fork 0
mirror of https://github.com/SinTan1729/chhoto-url synced 2025-04-19 19:30:01 -05:00

Compare commits

..

No commits in common. "5183279cab799f410f5e4f10c6bfad0cf3c4b417" and "eed3c2292a1b5c9666bfacec6a158d0f78c070a6" have entirely different histories.

7 changed files with 68 additions and 101 deletions

View file

@ -21,7 +21,7 @@ RUN cargo chef cook --release --target=$target --recipe-path recipe.json
COPY ./actix/Cargo.toml ./actix/Cargo.lock ./ COPY ./actix/Cargo.toml ./actix/Cargo.lock ./
COPY ./actix/src ./src COPY ./actix/src ./src
# Build application # Build application
RUN cargo build --release --target=$target --locked --bin chhoto-url RUN cargo build --release --target=$target --offline --bin chhoto-url
RUN cp /chhoto-url/target/$target/release/chhoto-url /chhoto-url/release RUN cp /chhoto-url/target/$target/release/chhoto-url /chhoto-url/release
FROM scratch FROM scratch

View file

@ -128,7 +128,7 @@ docker run -p 4567:4567 \
-e site_url="https://www.example.com" \ -e site_url="https://www.example.com" \
-d chhoto-url:latest -d chhoto-url:latest
``` ```
1.c Further, set an API key to activate JSON result mode (optional) 1.c Optionally, set an API key to activate JSON result mode (optional)
``` ```
docker run -p 4567:4567 \ docker run -p 4567:4567 \
@ -159,11 +159,9 @@ served through a proxy.
The application can be used from the terminal using something like `curl`. In all the examples The application can be used from the terminal using something like `curl`. In all the examples
below, replace `http://localhost:4567` with where your instance of `chhoto-url` is accessible. below, replace `http://localhost:4567` with where your instance of `chhoto-url` is accessible.
You can get the version of `chhoto-url` the server is running using `curl http://localhost:4567/api/version` and
get the siteurl using `curl http://localhost:4567/api/siteurl`. These routes are accessible without any authentication.
### Cookie validation ### Cookie validation
If you have set up a password, first do the following to get an authentication cookie and store it in a file. If you have set up
a password, first do the following to get an authentication cookie and store it in a file.
```bash ```bash
curl -X POST -d "<your-password>" -c cookie.txt http://localhost:4567/api/login curl -X POST -d "<your-password>" -c cookie.txt http://localhost:4567/api/login
``` ```
@ -190,8 +188,8 @@ The server will send a confirmation.
### API key validation ### API key validation
**This is required for programs that rely on a JSON response from Chhoto URL** **This is required for programs that rely on a JSON response from Chhoto URL**
In order to use API key validation, set the `api_key` environment variable. If this is not set, the API will default to cookie In order to use API key validation, set the `api_key` environment variable. If this is not set, the API will default to cookie validation (see section above).
validation (see section above). If the API key is insecure, a warning will be outputted along with a generated API key which may be used. If the API key is insecure, a warning will be outputted along with a generated API key which may be used.
To add a link: To add a link:
``` bash ``` bash
@ -207,11 +205,16 @@ To delete a link:
``` bash ``` bash
curl -X DELETE -H "X-API-Key: <YOUR_API_KEY>" http://localhost:4567/api/del/<shortlink> curl -X DELETE -H "X-API-Key: <YOUR_API_KEY>" http://localhost:4567/api/del/<shortlink>
``` ```
Where `<shortlink>` is name of the shortened link you would like to delete. For example, if the shortened link is Where `<shortlink>` is name of the shortened link you would like to delete. For example, if the shortened link is `http://localhost:4567/example`, `<shortlink>` would be `example`.
`http://localhost:4567/example`, `<shortlink>` would be `example`.
The server will output when the instance is accessed over API, when an incorrect API key is received, etc. The server will output when the instance is accessed over API, when an incorrect API key is received, etc.
In both modes, these routes are accessible:
You can get the version of `chhoto-url` the server is running using `curl http://localhost:4567/api/version` and
get the siteurl using `curl http://localhost:4567/api/siteurl`.
## Disable authentication ## Disable authentication
If you do not define a password environment variable when starting the docker image, authentication If you do not define a password environment variable when starting the docker image, authentication
will be disabled. will be disabled.

View file

@ -2,11 +2,11 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
use actix_session::Session; use actix_session::Session;
use actix_web::HttpRequest;
use std::{env, time::SystemTime}; use std::{env, time::SystemTime};
use actix_web::HttpRequest;
// API key generation and scoring // API key generation and scoring
use passwords::{analyzer, scorer, PasswordGenerator}; use passwords::{PasswordGenerator, scorer, analyzer};
// Validate API key // Validate API key
pub fn validate_key(key: String) -> bool { pub fn validate_key(key: String) -> bool {

View file

@ -47,11 +47,7 @@ async fn main() -> Result<()> {
} }
// Tell the user that the server has started, and where it is listening to, rather than simply outputting nothing // Tell the user that the server has started, and where it is listening to, rather than simply outputting nothing
eprintln!( 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")));
"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 // Actually start the server
HttpServer::new(move || { HttpServer::new(move || {

View file

@ -3,13 +3,7 @@
use actix_files::NamedFile; use actix_files::NamedFile;
use actix_session::Session; use actix_session::Session;
use actix_web::{ use actix_web::{delete, get, http::StatusCode, post, web::{self, Redirect}, Either, HttpRequest, HttpResponse, Responder};
delete, get,
http::StatusCode,
post,
web::{self, Redirect},
Either, HttpRequest, HttpResponse, Responder,
};
use std::env; use std::env;
// Serialize JSON data // Serialize JSON data
@ -43,12 +37,7 @@ struct CreatedURL {
// Add new links // Add new links
#[post("/api/new")] #[post("/api/new")]
pub async fn add_link( pub async fn add_link(req: String, data: web::Data<AppState>, session: Session, http: HttpRequest) -> HttpResponse {
req: String,
data: web::Data<AppState>,
session: Session,
http: HttpRequest,
) -> HttpResponse {
// Call is_api_ok() function, pass HttpRequest // Call is_api_ok() function, pass HttpRequest
let result = utils::is_api_ok(http); let result = utils::is_api_ok(http);
// If success, add new link // If success, add new link
@ -59,29 +48,26 @@ pub async fn add_link(
.unwrap_or(String::from("4567")) .unwrap_or(String::from("4567"))
.parse::<u16>() .parse::<u16>()
.expect("Supplied port is not an integer"); .expect("Supplied port is not an integer");
let url = format!( let url = format!("{}:{}", env::var("site_url").unwrap_or(String::from("http://localhost")), port);
"{}:{}",
env::var("site_url").unwrap_or(String::from("http://localhost")),
port
);
let response = CreatedURL { let response = CreatedURL {
success: true, success: true,
error: false, error: false,
shorturl: format!("{}/{}", url, out.1), shorturl: format!("{}/{}", url, out.1)
}; };
HttpResponse::Created().json(response) HttpResponse::Created().json(response)
} else { } else {
let response = Response { let response = Response {
success: false, success: false,
error: true, error: true,
reason: out.1, reason: out.1
}; };
HttpResponse::Conflict().json(response) HttpResponse::Conflict().json(response)
} }
} else if result.error { } else if result.error {
HttpResponse::Unauthorized().json(result) HttpResponse::Unauthorized().json(result)
// If password authentication or public mode is used - keeps backwards compatibility // If "pass" is true - keeps backwards compatibility
} else if env::var("public_mode") == Ok(String::from("Enable")) || auth::validate(session) { } else {
if env::var("public_mode") == Ok(String::from("Enable")) || auth::validate(session) {
let out = utils::add_link(req, &data.db); let out = utils::add_link(req, &data.db);
if out.0 { if out.0 {
HttpResponse::Created().body(out.1) HttpResponse::Created().body(out.1)
@ -92,14 +78,11 @@ pub async fn add_link(
HttpResponse::Unauthorized().body("Not logged in!") HttpResponse::Unauthorized().body("Not logged in!")
} }
} }
}
// Return all active links // Return all active links
#[get("/api/all")] #[get("/api/all")]
pub async fn getall( pub async fn getall(data: web::Data<AppState>, session: Session, http: HttpRequest) -> HttpResponse {
data: web::Data<AppState>,
session: Session,
http: HttpRequest,
) -> HttpResponse {
// Call is_api_ok() function, pass HttpRequest // Call is_api_ok() function, pass HttpRequest
let result = utils::is_api_ok(http); let result = utils::is_api_ok(http);
// If success, return all links // If success, return all links
@ -107,8 +90,9 @@ pub async fn getall(
HttpResponse::Ok().body(utils::getall(&data.db)) HttpResponse::Ok().body(utils::getall(&data.db))
} else if result.error { } else if result.error {
HttpResponse::Unauthorized().json(result) HttpResponse::Unauthorized().json(result)
// If password authentication is used - keeps backwards compatibility // If "pass" is true - keeps backwards compatibility
} else if auth::validate(session) { } else {
if auth::validate(session){
HttpResponse::Ok().body(utils::getall(&data.db)) HttpResponse::Ok().body(utils::getall(&data.db))
} else { } else {
let body = if env::var("public_mode") == Ok(String::from("Enable")) { let body = if env::var("public_mode") == Ok(String::from("Enable")) {
@ -119,6 +103,7 @@ pub async fn getall(
HttpResponse::Unauthorized().body(body) HttpResponse::Unauthorized().body(body)
} }
} }
}
// Get the site URL // Get the site URL
#[get("/api/siteurl")] #[get("/api/siteurl")]
@ -181,7 +166,7 @@ pub async fn login(req: String, session: Session) -> HttpResponse {
let response = Response { let response = Response {
success: false, success: false,
error: true, error: true,
reason: "Wrong password!".to_string(), reason: "Wrong password!".to_string()
}; };
return HttpResponse::Unauthorized().json(response); return HttpResponse::Unauthorized().json(response);
} }
@ -194,7 +179,7 @@ pub async fn login(req: String, session: Session) -> HttpResponse {
let response = Response { let response = Response {
success: true, success: true,
error: false, error: false,
reason: "Correct password!".to_string(), reason: "Correct password!".to_string()
}; };
HttpResponse::Ok().json(response) HttpResponse::Ok().json(response)
} else { } else {
@ -240,21 +225,22 @@ pub async fn delete_link(
let response = Response { let response = Response {
success: true, success: true,
error: false, error: false,
reason: format!("Deleted {}", shortlink), reason: format!("Deleted {}", shortlink)
}; };
HttpResponse::Ok().json(response) HttpResponse::Ok().json(response)
} else { } else {
let response = Response { let response = Response {
success: false, success: false,
error: true, 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) HttpResponse::NotFound().json(response)
} }
} else if result.error { } else if result.error {
HttpResponse::Unauthorized().json(result) HttpResponse::Unauthorized().json(result)
// If "pass" is true - keeps backwards compatibility // If "pass" is true - keeps backwards compatibility
} else if auth::validate(session) { } else {
if auth::validate(session) {
if utils::delete_link(shortlink.to_string(), &data.db) { if utils::delete_link(shortlink.to_string(), &data.db) {
HttpResponse::Ok().body(format!("Deleted {shortlink}")) HttpResponse::Ok().body(format!("Deleted {shortlink}"))
} else { } else {
@ -264,3 +250,4 @@ pub async fn delete_link(
HttpResponse::Unauthorized().body("Not logged in!") HttpResponse::Unauthorized().body("Not logged in!")
} }
} }
}

View file

@ -1,14 +1,14 @@
// SPDX-FileCopyrightText: 2023 Sayantan Santra <sayantan.santra689@gmail.com> // SPDX-FileCopyrightText: 2023 Sayantan Santra <sayantan.santra689@gmail.com>
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
use crate::{auth, database};
use actix_web::HttpRequest;
use nanoid::nanoid; use nanoid::nanoid;
use rand::seq::SliceRandom; use rand::seq::SliceRandom;
use regex::Regex; use regex::Regex;
use rusqlite::Connection; use rusqlite::Connection;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::env; use std::env;
use actix_web::HttpRequest;
use crate::{auth, database};
// Struct for reading link pairs sent during API call // Struct for reading link pairs sent during API call
#[derive(Deserialize)] #[derive(Deserialize)]
@ -26,7 +26,7 @@ pub struct Response {
pass: bool, pass: bool,
} }
// If the api_key environment variable exists // If the api_key environment variable eists
pub fn is_api_ok(http: HttpRequest) -> Response { pub fn is_api_ok(http: HttpRequest) -> Response {
// If the api_key environment variable exists // If the api_key environment variable exists
if env::var("api_key").is_ok() { if env::var("api_key").is_ok() {
@ -34,46 +34,27 @@ pub fn is_api_ok(http: HttpRequest) -> Response {
if let Some(header) = auth::api_header(&http) { if let Some(header) = auth::api_header(&http) {
// If the header is correct // If the header is correct
if auth::validate_key(header.to_string()) { if auth::validate_key(header.to_string()) {
Response { Response { success: true, error: false, reason: "Correct API key".to_string(), pass: false }
success: true,
error: false,
reason: "Correct API key".to_string(),
pass: false,
}
} else { } else {
Response { Response { success: false, error: true, reason: "Incorrect API key".to_string(), pass: false }
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. // 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 // Further authentication checks will be conducted in services.rs
} else { } else {
// Due to the implementation of this result in services.rs, this JSON object will not be outputted. // Due to the implementation of this result in services.rs, this JSON object will not be outputted.
Response { Response { success: false, error: false, reason: "X-API-Key header was not found".to_string(), pass: true }
success: false,
error: false,
reason: "X-API-Key header was not found".to_string(),
pass: true,
}
} }
} else { } else {
// If the API key isn't set, but an API Key header is provided // If the API key isn't set, but an API Key header is provided
if auth::api_header(&http).is_some() { 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} 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 { } else {
Response { Response {success: false, error: false, reason: "".to_string(), pass: true}
success: false,
error: false,
reason: "".to_string(),
pass: true,
}
} }
} }
} }
// Request the DB for searching an URL // Request the DB for searching an URL
pub fn get_longurl(shortlink: String, db: &Connection) -> Option<String> { pub fn get_longurl(shortlink: String, db: &Connection) -> Option<String> {
if validate_link(&shortlink) { if validate_link(&shortlink) {

View file

@ -26,7 +26,7 @@ services:
- password=TopSecretPass - password=TopSecretPass
# This needs to be set in order to use programs that use the JSON interface of Chhoto URL. # 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 output # You will get a warning if this is insecure, and a generated value will be outputted
# You may use that value if you can't think of a secure key # You may use that value if you can't think of a secure key
# - api_key=SECURE_API_KEY # - api_key=SECURE_API_KEY