Web API, Web UI #86

Merged
projectmoon merged 37 commits from web-api into master 2021-07-15 15:04:54 +00:00
7 changed files with 250 additions and 71 deletions
Showing only changes of commit 9b8d5dd2ea - Show all commits

102
Cargo.lock generated
View File

@ -260,6 +260,12 @@ version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b" checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b"
[[package]]
name = "base64"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff"
[[package]] [[package]]
name = "base64" name = "base64"
version = "0.13.0" version = "0.13.0"
@ -331,7 +337,7 @@ version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38b6553abdb9d2d8f262f0b5bccf807321d5b7d1a12796bcede8e1f150e85f2e" checksum = "38b6553abdb9d2d8f262f0b5bccf807321d5b7d1a12796bcede8e1f150e85f2e"
dependencies = [ dependencies = [
"base64", "base64 0.13.0",
"chrono", "chrono",
"hex", "hex",
"lazy_static", "lazy_static",
@ -994,9 +1000,9 @@ dependencies = [
[[package]] [[package]]
name = "generator" name = "generator"
version = "0.7.0" version = "0.6.25"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1d9279ca822891c1a4dae06d185612cf8fc6acfe5dff37781b41297811b12ee" checksum = "061d3be1afec479d56fa3bd182bf966c7999ec175fcfdb87ac14d417241366c6"
dependencies = [ dependencies = [
"cc", "cc",
"libc", "libc",
@ -1463,10 +1469,24 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "jsonwebtoken"
version = "7.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "afabcc15e437a6484fc4f12d0fd63068fe457bf93f1c148d3d9649c60b103f32"
dependencies = [
"base64 0.12.3",
"pem",
"ring",
"serde",
"serde_json",
"simple_asn1",
]
[[package]] [[package]]
name = "juniper" name = "juniper"
version = "0.15.6" version = "0.15.5"
source = "git+https://github.com/graphql-rust/juniper?branch=master#ae199387fcf3a46888ef8464acb6011a149268c1" source = "git+https://github.com/graphql-rust/juniper?branch=master#84a07c4a93f96d4352a9a6a23732c46eae486be6"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"bson", "bson",
@ -1486,8 +1506,8 @@ dependencies = [
[[package]] [[package]]
name = "juniper_codegen" name = "juniper_codegen"
version = "0.15.6" version = "0.15.5"
source = "git+https://github.com/graphql-rust/juniper?branch=master#ae199387fcf3a46888ef8464acb6011a149268c1" source = "git+https://github.com/graphql-rust/juniper?branch=master#84a07c4a93f96d4352a9a6a23732c46eae486be6"
dependencies = [ dependencies = [
"proc-macro-error", "proc-macro-error",
"proc-macro2", "proc-macro2",
@ -1498,7 +1518,7 @@ dependencies = [
[[package]] [[package]]
name = "juniper_rocket_async" name = "juniper_rocket_async"
version = "0.5.1" version = "0.5.1"
source = "git+https://github.com/graphql-rust/juniper?branch=master#ae199387fcf3a46888ef8464acb6011a149268c1" source = "git+https://github.com/graphql-rust/juniper?branch=master#84a07c4a93f96d4352a9a6a23732c46eae486be6"
dependencies = [ dependencies = [
"futures", "futures",
"juniper", "juniper",
@ -1568,11 +1588,11 @@ dependencies = [
[[package]] [[package]]
name = "loom" name = "loom"
version = "0.5.0" version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7aa5348dc45fa5f2419b6dd4ea20345e6b01b1fcc9d176a322eada1ac3f382ba" checksum = "a0e8460f2f2121162705187214720353c517b97bdfb3494c0b1e33d83ebe4bed"
dependencies = [ dependencies = [
"cfg-if 1.0.0", "cfg-if 0.1.10",
"generator", "generator",
"scoped-tls", "scoped-tls",
"serde", "serde",
@ -1713,7 +1733,7 @@ dependencies = [
"aes-ctr", "aes-ctr",
"aes-gcm", "aes-gcm",
"atomic", "atomic",
"base64", "base64 0.13.0",
"byteorder", "byteorder",
"dashmap", "dashmap",
"futures", "futures",
@ -1868,6 +1888,17 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "num-bigint"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]] [[package]]
name = "num-integer" name = "num-integer"
version = "0.1.44" version = "0.1.44"
@ -2036,6 +2067,17 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "pem"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd56cbd21fea48d0c440b41cd69c589faacade08c992d9a54e471b79d0fd13eb"
dependencies = [
"base64 0.13.0",
"once_cell",
"regex",
]
[[package]] [[package]]
name = "percent-encoding" name = "percent-encoding"
version = "2.1.0" version = "2.1.0"
@ -2526,7 +2568,7 @@ version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2296f2fac53979e8ccbc4a1136b25dcefd37be9ed7e4a1f6b05a6029c84ff124" checksum = "2296f2fac53979e8ccbc4a1136b25dcefd37be9ed7e4a1f6b05a6029c84ff124"
dependencies = [ dependencies = [
"base64", "base64 0.13.0",
"bytes", "bytes",
"encoding_rs", "encoding_rs",
"futures-core", "futures-core",
@ -2572,7 +2614,7 @@ dependencies = [
[[package]] [[package]]
name = "rocket" name = "rocket"
version = "0.5.0-dev" version = "0.5.0-dev"
source = "git+https://github.com/SergioBenitez/Rocket?branch=master#7595450adc1aa3892004f02b606706597eb924e9" source = "git+https://github.com/SergioBenitez/Rocket?branch=master#0d53e23bf6cb86f9136fa8b37a92ba8076aacf67"
dependencies = [ dependencies = [
"async-stream", "async-stream",
"async-trait", "async-trait",
@ -2595,6 +2637,7 @@ dependencies = [
"rocket_codegen", "rocket_codegen",
"rocket_http", "rocket_http",
"serde", "serde",
"serde_json",
"state", "state",
"tempfile", "tempfile",
"time 0.2.26", "time 0.2.26",
@ -2609,7 +2652,7 @@ dependencies = [
[[package]] [[package]]
name = "rocket_codegen" name = "rocket_codegen"
version = "0.5.0-dev" version = "0.5.0-dev"
source = "git+https://github.com/SergioBenitez/Rocket?branch=master#7595450adc1aa3892004f02b606706597eb924e9" source = "git+https://github.com/SergioBenitez/Rocket?branch=master#0d53e23bf6cb86f9136fa8b37a92ba8076aacf67"
dependencies = [ dependencies = [
"devise", "devise",
"glob", "glob",
@ -2624,7 +2667,7 @@ dependencies = [
[[package]] [[package]]
name = "rocket_cors" name = "rocket_cors"
version = "0.5.2" version = "0.5.2"
source = "git+https://git.agnos.is/projectmoon/rocket_cors?branch=sync-rocket-version#a25ba220140030e4553936a8ae130af0d89318dd" source = "git+https://git.agnos.is/projectmoon/rocket_cors?branch=sync-rocket-version#acd524db2594b6117160d78983941bc52db69a28"
dependencies = [ dependencies = [
"log", "log",
"regex", "regex",
@ -2639,7 +2682,7 @@ dependencies = [
[[package]] [[package]]
name = "rocket_http" name = "rocket_http"
version = "0.5.0-dev" version = "0.5.0-dev"
source = "git+https://github.com/SergioBenitez/Rocket?branch=master#7595450adc1aa3892004f02b606706597eb924e9" source = "git+https://github.com/SergioBenitez/Rocket?branch=master#0d53e23bf6cb86f9136fa8b37a92ba8076aacf67"
dependencies = [ dependencies = [
"cookie", "cookie",
"either", "either",
@ -2861,7 +2904,7 @@ version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7007ae39c0ae535438e5b8047e89d50d5dc1f0d6ed0f8c19c54c8ad1d814817" checksum = "e7007ae39c0ae535438e5b8047e89d50d5dc1f0d6ed0f8c19c54c8ad1d814817"
dependencies = [ dependencies = [
"base64", "base64 0.13.0",
"ring", "ring",
"ruma-identifiers", "ruma-identifiers",
"ruma-serde", "ruma-serde",
@ -2891,7 +2934,7 @@ version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b18820d944b33caa75a71378964ac46f58517c92b6ae5f762636247c09e78fb" checksum = "4b18820d944b33caa75a71378964ac46f58517c92b6ae5f762636247c09e78fb"
dependencies = [ dependencies = [
"base64", "base64 0.13.0",
"blake2b_simd", "blake2b_simd",
"constant_time_eq", "constant_time_eq",
"crossbeam-utils", "crossbeam-utils",
@ -3086,6 +3129,17 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "simple_asn1"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "692ca13de57ce0613a363c8c2f1de925adebc81b04c923ac60c5488bb44abe4b"
dependencies = [
"chrono",
"num-bigint",
"num-traits",
]
[[package]] [[package]]
name = "siphasher" name = "siphasher"
version = "0.3.5" version = "0.3.5"
@ -3270,9 +3324,8 @@ dependencies = [
[[package]] [[package]]
name = "state" name = "state"
version = "0.5.1" version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "git+https://github.com/SergioBenitez/state.git?rev=8f94dc#8f94dce673b7d4b0e7b96c808a84f5e2a4be4a60"
checksum = "0b54c22963194db84a59ee48e1fa9ed6c1fa9909ad5db92a700aa6fe956d632b"
dependencies = [ dependencies = [
"loom", "loom",
] ]
@ -3440,12 +3493,15 @@ dependencies = [
name = "tenebrous-api" name = "tenebrous-api"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"chrono",
"jsonwebtoken",
"juniper", "juniper",
"juniper_rocket_async", "juniper_rocket_async",
"log", "log",
"prost", "prost",
"rocket", "rocket",
"rocket_cors", "rocket_cors",
"serde",
"tenebrous-rpc", "tenebrous-rpc",
"tonic", "tonic",
"tracing-subscriber", "tracing-subscriber",
@ -3691,7 +3747,7 @@ checksum = "2ac42cd97ac6bd2339af5bcabf105540e21e45636ec6fa6aae5e85d44db31be0"
dependencies = [ dependencies = [
"async-stream", "async-stream",
"async-trait", "async-trait",
"base64", "base64 0.13.0",
"bytes", "bytes",
"futures-core", "futures-core",
"futures-util", "futures-util",

View File

@ -9,8 +9,11 @@ log = "0.4"
tracing-subscriber = "0.2" tracing-subscriber = "0.2"
tonic = { version = "0.4" } tonic = { version = "0.4" }
prost = "0.7" prost = "0.7"
jsonwebtoken = "7.2"
chrono = "0.4"
serde = {version = "1.0", features = ["derive"] }
tenebrous-rpc = { path = "../rpc" } tenebrous-rpc = { path = "../rpc" }
juniper = { git = "https://github.com/graphql-rust/juniper", branch = "master" } juniper = { git = "https://github.com/graphql-rust/juniper", branch = "master" }
juniper_rocket_async = { git = "https://github.com/graphql-rust/juniper", branch = "master" } juniper_rocket_async = { git = "https://github.com/graphql-rust/juniper", branch = "master" }
rocket = { git = "https://github.com/SergioBenitez/Rocket", branch = "master" } rocket = { git = "https://github.com/SergioBenitez/Rocket", branch = "master", features = ["json"] }
rocket_cors = { git = "https://github.com/lawliet89/rocket_cors", branch = "master" } rocket_cors = { git = "https://github.com/lawliet89/rocket_cors", branch = "master" }

88
api/src/api.rs Normal file
View File

@ -0,0 +1,88 @@
use crate::config::{create_config, Config};
use crate::schema::{self, Context, Schema};
use log::info;
use rocket::http::Method;
use rocket::serde::{json::Json, Deserialize};
use rocket::{response::content, Rocket, State};
use rocket_cors::AllowedOrigins;
use std::env;
use tracing_subscriber::filter::EnvFilter;
#[rocket::get("/")]
fn graphiql() -> content::Html<String> {
juniper_rocket_async::graphiql_source("/graphql", None)
}
#[rocket::get("/graphql?<request>")]
async fn get_graphql_handler(
context: &State<Context>,
request: juniper_rocket_async::GraphQLRequest,
schema: &State<Schema>,
) -> juniper_rocket_async::GraphQLResponse {
request.execute(&*schema, &*context).await
}
#[rocket::post("/graphql", data = "<request>")]
async fn post_graphql_handler(
context: &State<Context>,
request: juniper_rocket_async::GraphQLRequest,
schema: &State<Schema>,
) -> juniper_rocket_async::GraphQLResponse {
request.execute(&*schema, &*context).await
}
pub async fn run() -> Result<(), Box<dyn std::error::Error>> {
let filter = if env::var("RUST_LOG").is_ok() {
EnvFilter::from_default_env()
} else {
EnvFilter::new("tenebrous_api=info,tonic=info,rocket=info,rocket_cors=info")
};
tracing_subscriber::fmt().with_env_filter(filter).init();
log::info!("Setting up gRPC connection");
let rocket = Rocket::build();
let config = create_config(&rocket);
info!("Allowed CORS origins: {}", config.allowed_origins.join(","));
//TODO move to config
let client = tenebrous_rpc::create_client("http://localhost:9090", "abc123").await?;
let context = Context {
dicebot_client: client,
};
let schema = schema::schema();
let allowed_origins = AllowedOrigins::some_exact(&config.allowed_origins);
let cors = rocket_cors::CorsOptions {
allowed_origins,
allowed_methods: vec![Method::Get, Method::Post]
.into_iter()
.map(From::from)
.collect(),
allow_credentials: true,
..Default::default()
}
.to_cors()?;
let routes: Vec<rocket::Route> = {
rocket::routes![graphiql, get_graphql_handler, post_graphql_handler]
.into_iter()
.chain(crate::auth::routes().into_iter())
.collect()
};
rocket
.mount("/", routes)
.attach(cors)
.manage(context)
.manage(schema)
.manage(config)
.launch()
.await
.expect("server to launch");
Ok(())
}

51
api/src/auth.rs Normal file
View File

@ -0,0 +1,51 @@
use crate::config::Config;
use chrono::{Duration, Utc};
use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation};
use rocket::response::status::Custom;
use rocket::{
http::Status,
serde::{json::Json, Deserialize, Serialize},
};
use rocket::{routes, Route, State};
use std::error::Error;
pub(crate) fn routes() -> Vec<Route> {
routes![login]
}
#[derive(Debug, Serialize, Deserialize)]
struct Claims {
exp: usize,
sub: String,
}
#[derive(Deserialize)]
struct LoginRequest<'a> {
username: &'a str,
password: &'a str,
}
#[rocket::post("/login", data = "<request>")]
async fn login<'a>(
request: Json<LoginRequest<'a>>,
config: &State<Config>,
) -> Result<String, Custom<String>> {
let expiration = Utc::now()
.checked_add_signed(Duration::seconds(60))
.expect("clock went awry")
.timestamp();
let claims = Claims {
exp: expiration as usize,
sub: request.username.to_owned(),
};
let token = encode(
&Header::default(),
&claims,
&EncodingKey::from_secret(config.jwt_secret.as_ref()),
)
.map_err(|e| Custom(Status::InternalServerError, e.to_string()))?;
Ok(token)
}

22
api/src/config.rs Normal file
View File

@ -0,0 +1,22 @@
use rocket::{Phase, Rocket};
/// Config values for the API service. Available as a rocket request
/// guard.
pub struct Config {
/// The list of origins allowed to access the service.
pub allowed_origins: Vec<String>,
/// The secret key for signing JWTs.
pub jwt_secret: String,
}
pub fn create_config<T: Phase>(rocket: &Rocket<T>) -> Config {
let figment = rocket.figment();
let allowed_origins: Vec<String> = figment.extract_inner("origins").expect("origins");
let jwt_secret: String = figment.extract_inner("jwt_secret").expect("jwt_secret");
Config {
allowed_origins,
jwt_secret,
}
}

View File

@ -1 +1,4 @@
pub mod api;
pub mod auth;
pub mod config;
pub mod schema; pub mod schema;

View File

@ -1,8 +1,10 @@
use log::info; use log::info;
use rocket::http::Method; use rocket::http::Method;
use rocket::serde::{json::Json, Deserialize};
use rocket::{response::content, Rocket, State}; use rocket::{response::content, Rocket, State};
use rocket_cors::AllowedOrigins; use rocket_cors::AllowedOrigins;
use std::env; use std::env;
use tenebrous_api::config::{create_config, Config};
use tenebrous_api::schema::{self, Context, Schema}; use tenebrous_api::schema::{self, Context, Schema};
use tracing_subscriber::filter::EnvFilter; use tracing_subscriber::filter::EnvFilter;
@ -31,52 +33,6 @@ async fn post_graphql_handler(
#[rocket::main] #[rocket::main]
pub async fn main() -> Result<(), Box<dyn std::error::Error>> { pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
let filter = if env::var("RUST_LOG").is_ok() { tenebrous_api::api::run().await?;
EnvFilter::from_default_env()
} else {
EnvFilter::new("tenebrous_api=info,tonic=info,rocket=info,rocket_cors=info")
};
tracing_subscriber::fmt().with_env_filter(filter).init();
log::info!("Setting up gRPC connection");
let client = tenebrous_rpc::create_client("http://localhost:9090", "abc123").await?;
let context = Context {
dicebot_client: client,
};
let schema = schema::schema();
let rocket = Rocket::build();
let figment = rocket.figment();
let allowed_origins: Vec<String> = figment.extract_inner("origins").expect("origins");
info!("Allowed CORS origins: {}", allowed_origins.join(","));
let allowed_origins = AllowedOrigins::some_exact(&allowed_origins);
let cors = rocket_cors::CorsOptions {
allowed_origins,
allowed_methods: vec![Method::Get, Method::Post]
.into_iter()
.map(From::from)
.collect(),
allow_credentials: true,
..Default::default()
}
.to_cors()?;
rocket
.mount(
"/",
rocket::routes![graphiql, get_graphql_handler, post_graphql_handler],
)
.attach(cors)
.manage(context)
.manage(schema)
.launch()
.await
.expect("server to launch");
Ok(()) Ok(())
} }