Compare commits

...

5 Commits

Author SHA1 Message Date
b759b8e00b Update gitignore
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2023-04-25 17:16:02 +00:00
803ffae22c Tidy up for production 2023-04-25 17:15:07 +00:00
49a56f13ba Update API paths to DB and capsules 2023-04-25 17:10:44 +00:00
07f7917152 Add function to return view logs from API 2023-04-25 17:06:38 +00:00
35ca28316d Upgraded rocket 2023-04-25 16:35:46 +00:00
4 changed files with 1050 additions and 707 deletions

1
.gitignore vendored
View File

@ -5,3 +5,4 @@ db.db3
cert.pem cert.pem
key.rsa key.rsa
content/ content/
.DS_Store

1681
api/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -7,12 +7,12 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
rocket = "0.4.8" rocket = { version = "=0.5.0-rc.3", features = ["json"] }
rocket_contrib = "0.4.6" jsonwebtoken = "8.3.0"
jsonwebtoken = "7.2"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
flate2 = "1.0.19" flate2 = "1.0.19"
tar = "0.4.30" tar = "0.4.30"
base64 = "0.13.0" base64 = "0.13.0"
rusqlite = "0.24.2" rusqlite = "0.29.0"
uuid = { version = "0.8", features = ["v4"] } uuid = { version = "0.8", features = ["v4"] }
ubyte = "0.10.3"

View File

@ -1,13 +1,9 @@
// Copyright (c) 2021, Will Webberley. See LICENSE. // Copyright (c) 2021, Will Webberley. See LICENSE.
#![feature(proc_macro_hygiene, decl_macro)] use std::{env, str, fs::{self, File}, time::{SystemTime, UNIX_EPOCH}, io::Write, collections::HashSet};
#[macro_use] extern crate rocket_contrib; use rocket::{self, routes, post, put, get, launch, config::Config, response::Responder, request::{Request, FromRequest, Outcome}, data::{Limits, ToByteUnit}, http::Status, serde::json::{json, Json, Value}};
use std::{env, str, fs::{self, File}, time::{SystemTime, UNIX_EPOCH}, io::Write};
use rocket::{self, routes, get, post, put, config::{Config, Limits}, response::Responder, request::{self, Request, FromRequest}, Outcome, http::Status};
use rocket_contrib::json::{Json, JsonValue};
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use jsonwebtoken::{self, encode, decode, Header, Validation, EncodingKey, DecodingKey}; use jsonwebtoken::{self, encode, decode, Header, Validation, EncodingKey, DecodingKey, Algorithm};
use rusqlite::{params, Connection}; use rusqlite::{params, Connection};
use flate2::read::GzDecoder; use flate2::read::GzDecoder;
use tar::Archive; use tar::Archive;
@ -73,10 +69,9 @@ fn generate_access_token(capsule_id: &str) -> Result<String> {
// Verify the access JWT token, and checks still valid in DB. Returns the authenticated capsule. // Verify the access JWT token, and checks still valid in DB. Returns the authenticated capsule.
fn validate_access_token(token: &str) -> Result<AuthenticatedCapsule> { fn validate_access_token(token: &str) -> Result<AuthenticatedCapsule> {
// Validate the token integrity itself // Validate the token integrity itself
let my_validation = Validation { let mut my_validation = Validation::new(Algorithm::HS256);
validate_exp: false, my_validation.validate_exp = false;
..Default::default() my_validation.required_spec_claims = HashSet::new();
};
let claims = decode::<Claims>(&token, &DecodingKey::from_secret(get_jwt_secret().as_ref()), &my_validation)?.claims; let claims = decode::<Claims>(&token, &DecodingKey::from_secret(get_jwt_secret().as_ref()), &my_validation)?.claims;
// Verify the token in the DB and retrieve the capsule info // Verify the token in the DB and retrieve the capsule info
@ -96,13 +91,13 @@ fn validate_access_token(token: &str) -> Result<AuthenticatedCapsule> {
#[derive(Responder)] #[derive(Responder)]
enum Resp { enum Resp {
#[response(status = 200)] #[response(status = 200)]
Ok(JsonValue), Ok(Value),
#[response(status = 500)] #[response(status = 500)]
InternalServerError(JsonValue), InternalServerError(Value),
#[response(status = 400)] #[response(status = 400)]
BadRequest(JsonValue), BadRequest(Value),
#[response(status = 404)] #[response(status = 404)]
NotFound(JsonValue), NotFound(Value),
} }
#[get("/")] #[get("/")]
@ -204,10 +199,11 @@ enum ApiTokenError {
Missing, Missing,
Invalid, Invalid,
} }
impl<'a, 'r> FromRequest<'a, 'r> for AuthenticatedCapsule { #[rocket::async_trait]
impl<'r> FromRequest<'r> for AuthenticatedCapsule {
type Error = ApiTokenError; type Error = ApiTokenError;
fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, Self::Error> { async fn from_request(req: &'r Request<'_>) -> Outcome<Self, Self::Error> {
let keys: Vec<_> = request.headers().get("api-key").collect(); let keys: Vec<_> = req.headers().get("api-key").collect();
match keys.len() { match keys.len() {
0 => Outcome::Failure((Status::BadRequest, ApiTokenError::Missing)), 0 => Outcome::Failure((Status::BadRequest, ApiTokenError::Missing)),
1 => { 1 => {
@ -267,14 +263,39 @@ fn deploy_capsule(capsule: Json<CapsuleDeployRequest>, authenticated_capsule: Au
return Resp::Ok(json!({"message": "Your capsule was deployed", "url": url})); return Resp::Ok(json!({"message": "Your capsule was deployed", "url": url}));
} }
fn main() { #[derive(Serialize)]
struct CapsuleViewLog {
path: String,
timestamp: i32,
}
#[get("/logs")]
fn get_logs(authenticated_capsule: AuthenticatedCapsule) -> Resp {
let conn = match get_db() {
Ok(c) => c,
Err(_) => return Resp::InternalServerError(json!({"message": "Database problem"})),
};
let mut stmt = conn
.prepare("SELECT path, timestamp FROM view WHERE capsule_id = ?1 ORDER BY timestamp DESC LIMIT 100")
.unwrap();
let view_results = stmt.query_map(params![authenticated_capsule.id], |row| {
Ok(CapsuleViewLog {
path: row.get(0)?,
timestamp: row.get(1)?,
})
}).unwrap();
let views: Vec<CapsuleViewLog> = view_results.map(|view| view.unwrap()).collect();
return Resp::Ok(json!({"logs": &views}));
}
#[launch]
fn rocket() -> _ {
// Create needed directories // Create needed directories
fs::create_dir_all(CAPSULE_DIR).unwrap_or_default(); fs::create_dir_all(CAPSULE_DIR).unwrap_or_default();
// Initialise and launch Rocket // Initialise and launch Rocket
let mut my_config = Config::active().unwrap(); let mut my_config = Config::default();
my_config.set_port(9000); my_config.port = 9000;
my_config.set_limits(Limits::new().limit("json", 50 * 1024 * 1024)); // 50MB my_config.limits = Limits::new().limit("json", 50.megabytes()); // 50MB
let app = rocket::custom(my_config); let app = rocket::custom(my_config);
app.mount("/", routes![index, create_capsule, deploy_capsule]).launch(); app.mount("/", routes![index, create_capsule, deploy_capsule, get_logs])
} }