Skip to content

Commit

Permalink
Merge pull request #48 from kpcyrd/rocket-4
Browse files Browse the repository at this point in the history
Port to rocket 0.4
  • Loading branch information
kpcyrd committed Dec 10, 2018
2 parents 5735af2 + 347da48 commit 699c242
Show file tree
Hide file tree
Showing 12 changed files with 558 additions and 343 deletions.
648 changes: 420 additions & 228 deletions Cargo.lock

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions sn0int-registry/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ edition = "2018"

[dependencies]
sn0int-common = { version="0.3.0", path="sn0int-common" }
rocket = "0.3.16"
rocket_codegen = "0.3.16"
rocket_contrib = { version = "0.3.16", features = ["handlebars_templates"] }
rocket = "0.4"
rocket_failure = { version = "0.1", features = ["with-rocket"] }
rocket_contrib = { version = "0.4", features = ["handlebars_templates"] }

diesel = { version = "1.3", features = ["postgres", "r2d2"] }
diesel_migrations = { version = "1.3.0", features = ["postgres"] }
Expand Down
2 changes: 2 additions & 0 deletions sn0int-registry/sn0int-common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,7 @@ edition = "2018"
[dependencies]
serde = "1.0"
serde_derive = "1.0"
#rocket_failure = { path = "../../../rocket_failure" }
rocket_failure = "0.1.1"
failure = "0.1"
nom = "4.0"
20 changes: 0 additions & 20 deletions sn0int-registry/sn0int-common/src/api.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,3 @@
use crate::errors::*;


#[derive(Debug, Serialize, Deserialize)]
pub enum ApiResponse<T> {
#[serde(rename="success")]
Success(T),
#[serde(rename="error")]
Error(String),
}

impl<T> ApiResponse<T> {
pub fn success(self) -> Result<T> {
match self {
ApiResponse::Success(x) => Ok(x),
ApiResponse::Error(err) => bail!("Api returned error: {:?}", err),
}
}
}

#[derive(Debug, Serialize, Deserialize)]
pub struct WhoamiResponse {
pub user: String,
Expand Down
2 changes: 2 additions & 0 deletions sn0int-registry/sn0int-common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ pub mod metadata;
pub mod id;
pub use crate::id::*;

pub use rocket_failure::StrictApiResponse as ApiResponse;

#[cfg(test)]
mod tests {
#[test]
Expand Down
4 changes: 2 additions & 2 deletions sn0int-registry/src/assets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ use blake2::{Blake2b, Digest};


pub static FAVICON: &[u8] = include_bytes!("../assets/favicon.ico");
pub static STYLE_SHEET: &str = include_str!("../assets/style.css");
pub static STYLE_SHEET: &[u8] = include_bytes!("../assets/style.css");

lazy_static! {
pub static ref ASSET_REV: String = {
let mut h = Blake2b::new();
h.input(STYLE_SHEET.as_bytes());
h.input(STYLE_SHEET);
hex::encode(&h.result()[0..8])
};
}
28 changes: 1 addition & 27 deletions sn0int-registry/src/errors.rs
Original file line number Diff line number Diff line change
@@ -1,30 +1,4 @@
pub use failure::{Error, ResultExt};
pub type Result<T> = ::std::result::Result<T, Error>;

use rocket::Request;
use rocket::http::Status;
use rocket::response::{self, Responder};

#[derive(Debug)]
pub struct ApiError(Error);

#[derive(Serialize)]
pub struct ErrorResponse {
pub status: &'static str,
pub message: String,
}

pub type ApiResult<T> = ::std::result::Result<T, ApiError>;

impl<'r> Responder<'r> for ApiError {
fn respond_to(self, _: &Request) -> response::Result<'static> {
error!("Error: {:?}", self.0);
Err(Status::InternalServerError)
}
}

impl From<Error> for ApiError {
fn from(error: Error) -> ApiError {
ApiError(error)
}
}
pub use rocket_failure::errors::*;
38 changes: 25 additions & 13 deletions sn0int-registry/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
#![allow(proc_macro_derive_resolution_fallback)]
#![warn(unused_extern_crates)]
#![feature(plugin)]
#![feature(custom_derive)]
#![plugin(rocket_codegen)]
#![feature(proc_macro_hygiene, decl_macro)]

#[macro_use] extern crate rocket;
#[macro_use] extern crate rocket_contrib;
#[macro_use] extern crate rocket_failure;
#[macro_use] extern crate serde_derive;
#[macro_use] extern crate log;
#[macro_use] extern crate maplit;
Expand All @@ -13,7 +13,10 @@
#[macro_use] extern crate diesel;
#[macro_use] extern crate diesel_migrations;

use rocket_contrib::{Json, Value, Template};
use rocket::fairing::AdHoc;
use rocket::http::Header;
use rocket_contrib::json::{Json, JsonValue};
use rocket_contrib::templates::Template;
use dotenv::dotenv;

use std::env;
Expand All @@ -32,21 +35,21 @@ pub mod schema;


#[catch(400)]
fn bad_request() -> Json<Value> {
fn bad_request() -> Json<JsonValue> {
Json(json!({
"error": "Bad request"
}))
}

#[catch(404)]
fn not_found() -> Json<Value> {
fn not_found() -> Json<JsonValue> {
Json(json!({
"error": "Resource was not found"
}))
}

#[catch(500)]
fn internal_error() -> Json<Value> {
fn internal_error() -> Json<JsonValue> {
Json(json!({
"error": "Internal server error"
}))
Expand All @@ -64,6 +67,15 @@ fn run() -> Result<()> {
rocket::ignite()
.manage(db::init(&database_url))
.attach(Template::fairing())
.attach(AdHoc::on_response("Security Headers", |_, resp| {
resp.set_header(Header::new("Strict-Transport-Security", "max-age=63072000; includeSubDomains; preload"));
resp.set_header(Header::new("Content-Security-Policy", "style-src 'self'"));
resp.set_header(Header::new("Feature-Policy", "geolocation 'none'; midi 'none'; notifications 'none'; push 'none'; sync-xhr 'none'; microphone 'none'; camera 'none'; magnetometer 'none'; gyroscope 'none'; speaker 'none'; vibrate 'none'; fullscreen 'none'; payment 'none'"));
resp.set_header(Header::new("X-Frame-Options", "deny"));
resp.set_header(Header::new("X-XSS-Protection", "1; mode=block"));
resp.set_header(Header::new("X-Content-Type-Options", "nosniff"));
resp.set_header(Header::new("Referrer-Policy", "same-origin"));
}))
.mount("/api/v0", routes![
routes::api::quickstart,
routes::api::search,
Expand All @@ -82,12 +94,12 @@ fn run() -> Result<()> {
routes::assets::favicon,
routes::assets::style,
])
.catch(catchers![
bad_request,
not_found,
internal_error,
])
.launch();
.register(catchers![
bad_request,
not_found,
internal_error,
])
.launch();

Ok(())
}
Expand Down
85 changes: 50 additions & 35 deletions sn0int-registry/src/routes/api.rs
Original file line number Diff line number Diff line change
@@ -1,28 +1,29 @@
use crate::errors::*;
use crate::auth2::AuthHeader;
use crate::db;
use crate::models::*;
use diesel::Connection;
use rocket::request::Form;
use rocket_contrib::json::Json;
use semver::Version;
use sn0int_common::api::*;
use sn0int_common::id;
use sn0int_common::metadata::Metadata;
use rocket_contrib::Json;
use crate::models::*;


#[get("/quickstart")]
fn quickstart(connection: db::Connection) -> ApiResult<Json<ApiResponse<Vec<Module>>>> {
pub fn quickstart(connection: db::Connection) -> ApiResult<ApiResponse<Vec<Module>>> {
let modules = Module::quickstart(&connection)?;
Ok(Json(ApiResponse::Success(modules)))
Ok(ApiResponse::Success(modules))
}

#[derive(Debug, FromForm)]
pub struct Search {
q: String,
}

#[get("/search?<q>")]
fn search(q: Search, connection: db::Connection) -> ApiResult<Json<ApiResponse<Vec<SearchResponse>>>> {
#[get("/search?<q..>")]
pub fn search(q: Form<Search>, connection: db::Connection) -> ApiResult<ApiResponse<Vec<SearchResponse>>> {
info!("Searching: {:?}", q.q);

let modules = Module::search(&q.q, &connection)?;
Expand All @@ -39,85 +40,99 @@ fn search(q: Search, connection: db::Connection) -> ApiResult<Json<ApiResponse<V
})
.collect();

Ok(Json(ApiResponse::Success(modules)))
Ok(ApiResponse::Success(modules))
}

#[get("/info/<author>/<name>", format="application/json")]
fn info(author: String, name: String, connection: db::Connection) -> ApiResult<Json<ApiResponse<ModuleInfoResponse>>> {
pub fn info(author: String, name: String, connection: db::Connection) -> ApiResult<ApiResponse<ModuleInfoResponse>> {
info!("Querying {:?}/{:?}", author, name);
let module = Module::find(&author, &name, &connection)?;
let module = Module::find(&author, &name, &connection)
.not_found()
.public_context("Module does not exist")?;

Ok(Json(ApiResponse::Success(ModuleInfoResponse {
Ok(ApiResponse::Success(ModuleInfoResponse {
author: module.author,
name: module.name,
description: module.description,
latest: module.latest,
})))
}))
}

#[get("/dl/<author>/<name>/<version>", format="application/json")]
fn download(author: String, name: String, version: String, connection: db::Connection) -> ApiResult<Json<ApiResponse<DownloadResponse>>> {
pub fn download(author: String, name: String, version: String, connection: db::Connection) -> ApiResult<ApiResponse<DownloadResponse>> {
info!("Downloading {:?}/{:?} ({:?})", author, name, version);
let module = Module::find(&author, &name, &connection)?;
let module = Module::find(&author, &name, &connection)
.not_found()
.public_context("Module does not exist")?;
debug!("Module: {:?}", module);
let release = Release::find(module.id, &version, &connection)?;
let release = Release::find(module.id, &version, &connection)
.not_found()
.public_context("Release does not exist")?;
debug!("Release: {:?}", release);

release.bump_downloads(&connection)?;

Ok(Json(ApiResponse::Success(DownloadResponse {
Ok(ApiResponse::Success(DownloadResponse {
author,
name,
version,
code: release.code,
})))
}))
}

#[post("/publish/<name>", format="application/json", data="<upload>")]
fn publish(name: String, upload: Json<PublishRequest>, session: AuthHeader, connection: db::Connection) -> ApiResult<Json<ApiResponse<PublishResponse>>> {
let user = session.verify(&connection)?;
pub fn publish(name: String, upload: Json<PublishRequest>, session: AuthHeader, connection: db::Connection) -> ApiResult<ApiResponse<PublishResponse>> {
let user = session.verify(&connection)
.bad_request()
.public_context("Invalid auth token")?;

id::valid_name(&user)
.context("Username is invalid")
.map_err(Error::from)?;
.bad_request()
.public_context("Username is invalid")?;
id::valid_name(&name)
.context("Module name is invalid")
.map_err(Error::from)?;
.bad_request()
.public_context("Module name is invalid")?;

let metadata = upload.code.parse::<Metadata>()?;
let metadata = upload.code.parse::<Metadata>()
.bad_request()
.public_context("Failed to parse module metadata")?;

let version = metadata.version.clone();
Version::parse(&version)
.context("Version is invalid")
.map_err(Error::from)?;
.bad_request()
.public_context("Version is invalid")?;

connection.transaction::<_, Error, _>(|| {
let module = Module::update_or_create(&user, &name, &metadata.description, &connection)?;
connection.transaction::<_, WebError, _>(|| {
let module = Module::update_or_create(&user, &name, &metadata.description, &connection)
.private_context("Failed to write module metadata")?;

match Release::try_find(module.id, &version, &connection)? {
Some(release) => {
// if the code is identical, pretend we published the version
if release.code != upload.code {
bail!("Version number already in use")
bad_request!("Version number already in use")
}
},
None => module.add_version(&version, &upload.code, &connection)?,
None => module.add_version(&version, &upload.code, &connection)
.private_context("Failed to add release")?,
}

Ok(())
})?;

Ok(Json(ApiResponse::Success(PublishResponse {
Ok(ApiResponse::Success(PublishResponse {
author: user,
name,
version,
})))
}))
}

#[get("/whoami")]
fn whoami(session: AuthHeader, connection: db::Connection) -> ApiResult<Json<ApiResponse<WhoamiResponse>>> {
let user = session.verify(&connection)?;
Ok(Json(ApiResponse::Success(WhoamiResponse {
pub fn whoami(session: AuthHeader, connection: db::Connection) -> ApiResult<ApiResponse<WhoamiResponse>> {
let user = session.verify(&connection)
.bad_request()
.public_context("Invalid auth token")?;
Ok(ApiResponse::Success(WhoamiResponse {
user,
})))
}))
}

0 comments on commit 699c242

Please sign in to comment.