Compare commits

...

3 Commits

Author SHA1 Message Date
SinTan1729
54a63092fe chg: More sensible names for auth functions 2025-10-27 03:07:51 -05:00
SinTan1729
0c26cdf1b9 docs: Added spdx header in quadlet 2025-10-24 18:49:48 -05:00
SinTan1729
2bd8f0faf8 new: Periodically do wal checkpoints during cleanup 2025-10-24 14:08:50 -05:00
7 changed files with 34 additions and 20 deletions

View File

@@ -11,7 +11,7 @@ use std::{rc::Rc, time::SystemTime};
use crate::config::Config;
// Validate API key
pub fn validate_key(key: &str, config: &Config) -> bool {
pub fn is_key_valid(key: &str, config: &Config) -> bool {
if let Some(api_key) = &config.api_key {
// Check if API Key is hashed using Argon2. More algorithms maybe added later.
let authorized = if config.hash_algorithm.is_some() {
@@ -54,26 +54,26 @@ pub fn gen_key() -> String {
}
// Check if the API key header exists
pub fn api_header(req: &HttpRequest) -> Option<&str> {
pub fn get_api_header(req: &HttpRequest) -> Option<&str> {
req.headers().get("X-API-Key")?.to_str().ok()
}
// Validate a session
pub fn validate(session: Session, config: &Config) -> bool {
pub fn is_session_valid(session: Session, config: &Config) -> bool {
// If there's no password provided, just return true
if config.password.is_none() {
return true;
}
if let Ok(token) = session.get::<String>("chhoto-url-auth") {
check(token.as_deref())
is_token_valid(token.as_deref())
} else {
false
}
}
// Check a token cryptographically
fn check(token: Option<&str>) -> bool {
fn is_token_valid(token: Option<&str>) -> bool {
if let Some(token_body) = token {
let token_parts: Rc<[&str]> = token_body.split(';').collect();
if token_parts.len() < 2 {

View File

@@ -175,7 +175,7 @@ pub fn read() -> Config {
if use_wal_mode {
info!("Using WAL journaling mode for database.");
} else {
warn!("Using DELETE journaling mode for database. WAL mode is recommended. (Please read the docs.)");
warn!("Using DELETE journaling mode for database. WAL mode is recommended.");
}
let ensure_acid = !var("ensure_acid").is_ok_and(|s| s.trim() == "False");
if ensure_acid {

View File

@@ -181,7 +181,7 @@ pub fn edit_link(
}
// Clean expired links
pub fn cleanup(db: &Connection) {
pub fn cleanup(db: &Connection, use_wal_mode: bool) {
let now = chrono::Utc::now().timestamp();
info!("Starting database cleanup.");
@@ -197,12 +197,22 @@ pub fn cleanup(db: &Connection) {
})
.expect("Error cleaning expired links.");
if use_wal_mode {
let mut pragma_statement = db
.prepare_cached("PRAGMA wal_checkpoint(TRUNCATE)")
.expect("Error preparing SQL statement for pragma: wal_checkpoint.");
pragma_statement
.query_one([], |row| row.get::<usize, isize>(1))
.ok()
.filter(|&v| v != -1)
.expect("Unable to create WAL checkpoint.");
}
let mut pragma_statement = db
.prepare_cached("PRAGMA optimize")
.expect("Error preparing SQL statement for pragma optimize.");
.expect("Error preparing SQL statement for pragma: optimize.");
pragma_statement
.execute([])
.expect("Unable to optimize database");
.expect("Unable to optimize database.");
info!("Optimized database.")
}

View File

@@ -97,7 +97,7 @@ async fn main() -> Result<()> {
let mut interval = time::interval(time::Duration::from_secs(3600));
loop {
interval.tick().await;
database::cleanup(&db);
database::cleanup(&db, conf.use_wal_mode);
}
});

View File

@@ -17,7 +17,7 @@ use std::env;
use crate::AppState;
use crate::{auth, database};
use crate::{auth::validate, utils};
use crate::{auth::is_session_valid, utils};
// Store the version number
const VERSION: &str = env!("CARGO_PKG_VERSION");
@@ -118,7 +118,7 @@ pub async fn add_link(
HttpResponse::Unauthorized().json(result)
// If password authentication or public mode is used - keeps backwards compatibility
} else {
let (success, reply, _) = if auth::validate(session, config) {
let (success, reply, _) = if auth::is_session_valid(session, config) {
utils::add_link(&req, &data.db, config, false)
} else if config.public_mode {
utils::add_link(&req, &data.db, config, true)
@@ -150,7 +150,7 @@ pub async fn getall(
} else if result.error {
HttpResponse::Unauthorized().json(result)
// If password authentication is used - keeps backwards compatibility
} else if auth::validate(session, config) {
} else if auth::is_session_valid(session, config) {
HttpResponse::Ok().body(utils::getall(&data.db, params.into_inner()))
} else {
HttpResponse::Unauthorized().body("Not logged in!")
@@ -196,7 +196,7 @@ pub async fn edit_link(
) -> HttpResponse {
let config = &data.config;
let result = utils::is_api_ok(http, config);
if result.success || validate(session, config) {
if result.success || is_session_valid(session, config) {
if let Some((server_error, error_msg)) = utils::edit_link(&req, &data.db, config) {
let body = Response {
success: false,
@@ -250,7 +250,7 @@ pub async fn whoami(
) -> HttpResponse {
let config = &data.config;
let result = utils::is_api_ok(http, config);
let acting_user = if result.success || validate(session, config) {
let acting_user = if result.success || is_session_valid(session, config) {
"admin"
} else if config.public_mode {
"public"
@@ -269,7 +269,7 @@ pub async fn getconfig(
) -> HttpResponse {
let config = &data.config;
let result = utils::is_api_ok(http, config);
if result.success || validate(session, config) || data.config.public_mode {
if result.success || is_session_valid(session, config) || data.config.public_mode {
let backend_config = BackendConfig {
version: VERSION.to_string(),
allow_capital_letters: config.allow_capital_letters,
@@ -424,7 +424,7 @@ pub async fn delete_link(
} else if result.error {
HttpResponse::Unauthorized().json(result)
// If "pass" is true - keeps backwards compatibility
} else if auth::validate(session, config) {
} else if auth::is_session_valid(session, config) {
if utils::delete_link(&shortlink, &data.db, data.config.allow_capital_letters) {
HttpResponse::Ok().body(format!("Deleted {shortlink}"))
} else {

View File

@@ -42,9 +42,9 @@ pub fn is_api_ok(http: HttpRequest, config: &Config) -> Response {
// If the api_key environment variable exists
if config.api_key.is_some() {
// If the header exists
if let Some(header) = auth::api_header(&http) {
if let Some(header) = auth::get_api_header(&http) {
// If the header is correct
if auth::validate_key(header, config) {
if auth::is_key_valid(header, config) {
Response {
success: true,
error: false,
@@ -72,7 +72,7 @@ pub fn is_api_ok(http: HttpRequest, config: &Config) -> Response {
}
} else {
// If the API key isn't set, but an API Key header is provided
if auth::api_header(&http).is_some() {
if auth::get_api_header(&http).is_some() {
Response {
success: false,
error: true,

View File

@@ -1,4 +1,8 @@
# SPDX-FileCopyrightText: 2025 Sayantan Santra <sayantan.santra689@gmail.com>
# SPDX-License-Identifier: MIT
#
# chhoto-url.container
#
# To be used with rootless quadlets. Put inside your $XDG_CONFIG_HOME/containers/systemd/
# Take a look at README for the explanation of the configs.
# The commented out configs are optional.