Add authentication checking to api

This commit is contained in:
projectmoon 2021-06-10 15:25:44 +00:00
parent 81b4447c84
commit 00742c14a9
6 changed files with 71 additions and 2 deletions

2
Cargo.lock generated
View File

@ -3518,7 +3518,9 @@ dependencies = [
"rocket",
"rocket_cors",
"serde",
"substring",
"tenebrous-rpc",
"thiserror",
"tonic",
"tracing-subscriber",
]

View File

@ -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"] }

View File

@ -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
}

View File

@ -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]

16
api/src/errors.rs Normal file
View File

@ -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),
}

View File

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