Web API, Web UI #86
|
@ -3518,7 +3518,9 @@ dependencies = [
|
|||
"rocket",
|
||||
"rocket_cors",
|
||||
"serde",
|
||||
"substring",
|
||||
"tenebrous-rpc",
|
||||
"thiserror",
|
||||
"tonic",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
|
|
@ -9,6 +9,8 @@ log = "0.4"
|
|||
tracing-subscriber = "0.2"
|
||||
tonic = { version = "0.4" }
|
||||
prost = "0.7"
|
||||
thiserror = "1.0"
|
||||
substring = "1.4"
|
||||
jsonwebtoken = "7.2"
|
||||
chrono = "0.4"
|
||||
serde = {version = "1.0", features = ["derive"] }
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use crate::auth::User;
|
||||
use crate::config::create_config;
|
||||
use crate::schema::{self, Context, Schema};
|
||||
use log::info;
|
||||
|
@ -26,7 +27,9 @@ async fn post_graphql_handler(
|
|||
context: &State<Context>,
|
||||
request: juniper_rocket_async::GraphQLRequest,
|
||||
schema: &State<Schema>,
|
||||
user: User,
|
||||
) -> juniper_rocket_async::GraphQLResponse {
|
||||
println!("User is {:?}", user);
|
||||
request.execute(&*schema, &*context).await
|
||||
}
|
||||
|
||||
|
|
|
@ -1,14 +1,59 @@
|
|||
use crate::config::Config;
|
||||
use crate::errors::ApiError;
|
||||
use chrono::{Duration, Utc};
|
||||
use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation};
|
||||
use rocket::http::{Cookie, CookieJar};
|
||||
use rocket::request::{self, FromRequest, Request};
|
||||
use rocket::response::status::Custom;
|
||||
use rocket::{
|
||||
http::Status,
|
||||
serde::{json::Json, Deserialize, Serialize},
|
||||
};
|
||||
use rocket::{
|
||||
http::{Cookie, CookieJar},
|
||||
outcome::Outcome,
|
||||
};
|
||||
use rocket::{routes, Route, State};
|
||||
use std::error::Error;
|
||||
use substring::Substring;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct User {
|
||||
username: String, //TODO more state and such here.
|
||||
}
|
||||
|
||||
fn decode_token(token: &str, config: &Config) -> Result<Claims, ApiError> {
|
||||
let token_data = decode::<Claims>(
|
||||
token,
|
||||
&DecodingKey::from_secret(config.jwt_secret.as_bytes()),
|
||||
&Validation::default(),
|
||||
)?;
|
||||
|
||||
Ok(token_data.claims)
|
||||
}
|
||||
|
||||
#[rocket::async_trait]
|
||||
impl<'r> FromRequest<'r> for User {
|
||||
type Error = ApiError;
|
||||
|
||||
async fn from_request(req: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
|
||||
let config: Option<&Config> = req.rocket().state();
|
||||
let auth_header = req
|
||||
.headers()
|
||||
.get_one("Authorization")
|
||||
.map(|auth| auth.substring("Bearer ".len(), auth.len()));
|
||||
|
||||
let token = auth_header
|
||||
.zip(config)
|
||||
.map(|(encoded_token, app_cfg)| decode_token(encoded_token, app_cfg))
|
||||
.unwrap_or(Err(ApiError::AuthenticationDenied("username".to_string())));
|
||||
|
||||
match token {
|
||||
Err(e) => Outcome::Failure((Status::Forbidden, e)),
|
||||
Ok(token) => Outcome::Success(User {
|
||||
username: token.sub,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn routes() -> Vec<Route> {
|
||||
routes![login]
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ApiError {
|
||||
#[error("user account does not exist: {0}")]
|
||||
UserDoesNotExist(String),
|
||||
|
||||
#[error("invalid password for user account: {0}")]
|
||||
AuthenticationDenied(String),
|
||||
|
||||
#[error("authentication token missing from request")]
|
||||
AuthenticationRequired,
|
||||
|
||||
#[error("error decoding token: {0}")]
|
||||
TokenDecodingError(#[from] jsonwebtoken::errors::Error),
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
pub mod api;
|
||||
pub mod auth;
|
||||
pub mod config;
|
||||
pub mod errors;
|
||||
pub mod schema;
|
||||
|
|
Loading…
Reference in New Issue