summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorDaniel Hader <[email protected]>2026-06-05 19:29:40 -0500
committerDaniel Hader <[email protected]>2026-06-05 19:29:40 -0500
commitc071aca5c5c16d56aafe38ace2b2c158e1b875fc (patch)
treeaa947a0f6fa53be3de6fb879aa5c4d96bc087cf1 /src
parent3ac68b8b59f150e08731a62026ce3ac825655614 (diff)
server text sanitation, username/email filters, and code length measurement
Diffstat (limited to 'src')
-rw-r--r--src/database/database.rs9
-rw-r--r--src/database/sql/fetch_display_submissions.sql5
-rw-r--r--src/database/sql/initialize.sql14
-rw-r--r--src/database/sql/insert_submission.sql2
-rw-r--r--src/database/submission.rs7
-rw-r--r--src/main.rs10
-rw-r--r--src/routes/errors.rs4
-rw-r--r--src/routes/submission.rs16
-rw-r--r--src/routes/user.rs16
9 files changed, 65 insertions, 18 deletions
diff --git a/src/database/database.rs b/src/database/database.rs
index 95c8bf6..ee9b6b9 100644
--- a/src/database/database.rs
+++ b/src/database/database.rs
@@ -173,6 +173,7 @@ impl Database {
language: &str,
details: &str,
code: &str,
+ code_length: i64,
) -> Result<Submission, DatabaseError> {
static QUERY: &str = include_str!("sql/insert_submission.sql");
let conn = self.pool
@@ -182,14 +183,15 @@ impl Database {
.map_err(|e| DatabaseError::Query(e.to_string()))?;
Ok(statement
- .query_one((user_id, problem_id, language, details, code), |row| {
+ .query_one((user_id, problem_id, language, details, code, code_length), |row| {
Ok(Submission::new(
row.get("id")?,
user_id,
problem_id,
language.to_owned(),
details.to_owned(),
- code.to_owned()
+ code.to_owned(),
+ code_length,
))
})
.map_err(|e| DatabaseError::Query(e.to_string()))?
@@ -213,6 +215,7 @@ impl Database {
row.get("language")?,
row.get("details")?,
row.get("code")?,
+ row.get("code_length")?,
))
})
.map_err(|e| DatabaseError::Query(e.to_string()))?
@@ -237,6 +240,7 @@ impl Database {
language: row.get("language")?,
details: row.get("details")?,
code: row.get("code")?,
+ code_length: row.get("code_length")?,
})
})
.map_err(|e| DatabaseError::Query(e.to_string()))?
@@ -261,6 +265,7 @@ impl Database {
language: row.get("language")?,
details: row.get("details")?,
code: row.get("code")?,
+ code_length: row.get("code_length")?,
})
})
.optional()
diff --git a/src/database/sql/fetch_display_submissions.sql b/src/database/sql/fetch_display_submissions.sql
index 5e836b3..aaceaca 100644
--- a/src/database/sql/fetch_display_submissions.sql
+++ b/src/database/sql/fetch_display_submissions.sql
@@ -3,8 +3,9 @@ submission.id AS id,
user.username AS username,
submission.language AS language,
submission.details AS details,
-submission.code AS code
+submission.code AS code,
+submission.code_length as code_length
FROM submission JOIN user
ON user.id = submission.user_id
WHERE submission.problem_id = ?1
-ORDER BY length(submission.code);
+ORDER BY submission.code_length;
diff --git a/src/database/sql/initialize.sql b/src/database/sql/initialize.sql
index 8c0f2ef..3569fd5 100644
--- a/src/database/sql/initialize.sql
+++ b/src/database/sql/initialize.sql
@@ -13,13 +13,13 @@ CREATE TABLE IF NOT EXISTS user (
);
CREATE TABLE IF NOT EXISTS submission (
- id INTEGER PRIMARY KEY,
- user_id INTEGER NOT NULL,
- problem_id INTEGER NOT NULL,
- language TEXT NOT NULL,
- details TEXT NOT NULL,
- code TEXT NOT NULL
-
+ id INTEGER PRIMARY KEY,
+ user_id INTEGER NOT NULL,
+ problem_id INTEGER NOT NULL,
+ language TEXT NOT NULL,
+ details TEXT NOT NULL,
+ code TEXT NOT NULL,
+ code_length INTEGER NOT NULL
);
INSERT INTO problem (title, description) VALUES (
diff --git a/src/database/sql/insert_submission.sql b/src/database/sql/insert_submission.sql
index 96df322..3f70bca 100644
--- a/src/database/sql/insert_submission.sql
+++ b/src/database/sql/insert_submission.sql
@@ -1 +1 @@
-INSERT INTO submission (user_id, problem_id, language, details, code) VALUES (?1, ?2, ?3, ?4, ?5) RETURNING id;
+INSERT INTO submission (user_id, problem_id, language, details, code, code_length) VALUES (?1, ?2, ?3, ?4, ?5, ?6) RETURNING id;
diff --git a/src/database/submission.rs b/src/database/submission.rs
index b117e17..0d6a131 100644
--- a/src/database/submission.rs
+++ b/src/database/submission.rs
@@ -8,6 +8,7 @@ pub struct Submission {
language: String,
details: String,
code: String,
+ code_length: i64,
}
impl Submission {
@@ -18,8 +19,9 @@ impl Submission {
language: String,
details: String,
code: String,
+ code_length: i64,
) -> Self {
- Self { id, user_id, problem_id, language, details, code }
+ Self { id, user_id, problem_id, language, details, code, code_length }
}
pub fn id(&self) -> i64 { self.id }
@@ -28,6 +30,7 @@ impl Submission {
pub fn language(&self) -> &str { &self.language }
pub fn details(&self) -> &str { &self.details }
pub fn code(&self) -> &str { &self.code }
+ pub fn code_length(&self) -> i64 { self.code_length }
}
#[derive(Serialize)]
@@ -37,4 +40,6 @@ pub struct DisplaySubmission {
pub(crate) language: String,
pub(crate) details: String,
pub(crate) code: String,
+ pub(crate) code_length: i64,
}
+
diff --git a/src/main.rs b/src/main.rs
index afa9821..b446402 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -14,11 +14,12 @@ use routes::user::{create_user, me};
use routes::auth::{login, logout};
use tower_http::services::ServeDir;
-use crate::{database::Database, routes::submission::{create_submission, get_display_submission, get_display_submissions, get_submissions_by_problem_id}, utils::register_admin};
+use crate::{database::Database, routes::submission::{create_submission, get_display_submission, get_display_submissions}, utils::register_admin};
#[derive(Clone)]
struct AppState {
secret: String,
+ register_code: String,
database: Database,
}
@@ -33,6 +34,12 @@ async fn main() {
eprintln!("missing environment variable JWT_SECRET");
return;
};
+
+ let Ok(register_code) = env::var("REGISTER_CODE") else {
+ eprintln!("missing environment variable REGISTER_CODE");
+ return;
+ };
+
let Ok(admin_email) = env::var("ADMIN_EMAIL") else {
eprintln!("missing environment variable ADMIN_EMAIL");
return;
@@ -53,6 +60,7 @@ async fn main() {
let state = AppState {
secret: secret,
+ register_code: register_code,
database: database,
};
diff --git a/src/routes/errors.rs b/src/routes/errors.rs
index f6a0901..5161e9a 100644
--- a/src/routes/errors.rs
+++ b/src/routes/errors.rs
@@ -3,6 +3,7 @@ use serde_json::{json};
pub enum RouteError {
Internal(String),
+ MalformedField(String),
UserCreateEmailExists(String),
UserCreateUsernameExists(String),
UnregisteredEmail(String),
@@ -30,6 +31,9 @@ impl IntoResponse for RouteError {
},
RouteError::NotFound(resource) => {
(StatusCode::NOT_FOUND, format!("{resource} not found"))
+ },
+ RouteError::MalformedField(field) => {
+ (StatusCode::BAD_REQUEST, format!("malformed {field}"))
}
};
diff --git a/src/routes/submission.rs b/src/routes/submission.rs
index b3cf2b9..99767c2 100644
--- a/src/routes/submission.rs
+++ b/src/routes/submission.rs
@@ -1,3 +1,4 @@
+use ammonia::clean_text;
use axum::{Json, extract::{Path, State}, http::StatusCode, response::IntoResponse};
use serde::Deserialize;
@@ -18,15 +19,22 @@ pub async fn create_submission(
) -> Result<impl IntoResponse, RouteError> {
let user_id = claims.sub;
+ let code_length = request.code.len() as i64;
+
+ let language = clean_text(&request.language);
+ let details = clean_text(&request.details);
+ let code = clean_text(&request.code);
+
match state.database.insert_submission(
user_id,
request.problem_id,
- &request.language,
- &request.details,
- &request.code
+ &language,
+ &details,
+ &code,
+ code_length,
) {
Ok(submission) => Ok((StatusCode::CREATED, Json(submission))),
- Err(_) => Err(RouteError::Internal(format!("unable to insert submission")))
+ Err(e) => Err(RouteError::Internal(format!("unable to insert submission {e:?}")))
}
}
diff --git a/src/routes/user.rs b/src/routes/user.rs
index 178a272..31a5824 100644
--- a/src/routes/user.rs
+++ b/src/routes/user.rs
@@ -1,6 +1,7 @@
use axum::extract::{Json, State};
use axum::http::StatusCode;
use axum::response::IntoResponse;
+use regex::Regex;
use serde::{Deserialize, Serialize};
use crate::AppState;
@@ -12,6 +13,7 @@ pub(crate) struct CreateUserRequest {
email: String,
username: String,
password: String,
+ register_code: String,
}
pub async fn create_user(
@@ -19,6 +21,20 @@ pub async fn create_user(
Json(request): Json<CreateUserRequest>
) -> Result<impl IntoResponse, RouteError> {
+ if request.register_code != state.register_code {
+ return Err(RouteError::AuthorizationFailure());
+ }
+
+ let email_re = Regex::new(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$").unwrap();
+ if !email_re.is_match(&request.email) {
+ return Err(RouteError::MalformedField("email".into()));
+ }
+
+ let username_re = Regex::new(r"^[a-zA-Z0-9_\-]+$").unwrap();
+ if !username_re.is_match(&request.username) {
+ return Err(RouteError::MalformedField("username".into()));
+ }
+
match state.database.fetch_user_by_email(&request.email) {
Err(_) => return Err(RouteError::Internal("database action failed".into())),
Ok(Some(_)) => return Err(RouteError::UserCreateEmailExists(request.email)),