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

Compare commits

..

3 commits

Author SHA1 Message Date
SolninjaA
1093e9ccad
Merge 5183279cab into 756d675f06 2025-01-05 04:55:47 -06:00
5183279cab
docs: Small changes to the README 2025-01-05 16:25:08 +05:30
f1c1642976
chg: Small semantic changes 2025-01-05 16:20:38 +05:30
7 changed files with 99 additions and 66 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/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

View file

@ -128,7 +128,7 @@ docker run -p 4567:4567 \
-e site_url="https://www.example.com" \
-d chhoto-url:latest
```
1.c Optionally, set an API key to activate JSON result mode (optional)
1.c Further, set an API key to activate JSON result mode (optional)
```
docker run -p 4567:4567 \
@ -159,9 +159,11 @@ served through a proxy.
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.
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
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
curl -X POST -d "<your-password>" -c cookie.txt http://localhost:4567/api/login
```
@ -188,8 +190,8 @@ The server will send a confirmation.
### API key validation
**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 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.
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). 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:
``` bash
@ -205,16 +207,11 @@ To delete a link:
``` bash
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 `http://localhost:4567/example`, `<shortlink>` would be `example`.
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`.
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
If you do not define a password environment variable when starting the docker image, authentication
will be disabled.

View file

@ -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 {

View file

@ -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 || {

View file

@ -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
@ -37,7 +43,12 @@ struct CreatedURL {
// Add new links
#[post("/api/new")]
pub async fn add_link(req: String, data: web::Data<AppState>, session: Session, http: HttpRequest) -> HttpResponse {
pub async fn add_link(
req: String,
data: web::Data<AppState>,
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,26 +59,29 @@ pub async fn add_link(req: String, data: web::Data<AppState>, session: Session,
.unwrap_or(String::from("4567"))
.parse::<u16>()
.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) {
// 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)
@ -77,12 +91,15 @@ pub async fn add_link(req: String, data: web::Data<AppState>, session: Session,
} else {
HttpResponse::Unauthorized().body("Not logged in!")
}
}
}
// Return all active links
#[get("/api/all")]
pub async fn getall(data: web::Data<AppState>, session: Session, http: HttpRequest) -> HttpResponse {
pub async fn getall(
data: web::Data<AppState>,
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,9 +107,8 @@ pub async fn getall(data: web::Data<AppState>, 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
} else {
if auth::validate(session){
// If password authentication is used - keeps backwards compatibility
} else if auth::validate(session) {
HttpResponse::Ok().body(utils::getall(&data.db))
} else {
let body = if env::var("public_mode") == Ok(String::from("Enable")) {
@ -102,7 +118,6 @@ pub async fn getall(data: web::Data<AppState>, session: Session, http: HttpReque
};
HttpResponse::Unauthorized().body(body)
}
}
}
// Get the site URL
@ -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,22 +240,21 @@ 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) {
} else if auth::validate(session) {
if utils::delete_link(shortlink.to_string(), &data.db) {
HttpResponse::Ok().body(format!("Deleted {shortlink}"))
} else {
@ -249,5 +263,4 @@ pub async fn delete_link(
} else {
HttpResponse::Unauthorized().body("Not logged in!")
}
}
}

View file

@ -1,14 +1,14 @@
// SPDX-FileCopyrightText: 2023 Sayantan Santra <sayantan.santra689@gmail.com>
// 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<String> {
if validate_link(&shortlink) {

View file

@ -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