Add authentication #1
					 3 changed files with 90 additions and 12 deletions
				
			
		basic authentification check
				commit
				
					
					
						5e08f2c2c8
					
				
			
		| 
						 | 
				
			
			@ -20,6 +20,7 @@ mod auth;
 | 
			
		|||
mod schema;
 | 
			
		||||
 | 
			
		||||
use models::errors::ErrorResponse;
 | 
			
		||||
use models::users::UserInfo;
 | 
			
		||||
use auth::routes::*;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -27,7 +28,12 @@ use auth::routes::*;
 | 
			
		|||
pub struct DbConn(diesel::SqliteConnection);
 | 
			
		||||
 | 
			
		||||
#[get("/zones/<zone>/records")]
 | 
			
		||||
fn get_zone_records(client: State<SyncClient<TcpClientConnection>>, zone: String) -> Result<Json<Vec<models::dns::Record>>, ErrorResponse<()>> {
 | 
			
		||||
fn get_zone_records(
 | 
			
		||||
    client: State<SyncClient<TcpClientConnection>>,
 | 
			
		||||
    _user_info: UserInfo,
 | 
			
		||||
    zone: String
 | 
			
		||||
) -> Result<Json<Vec<models::dns::Record>>, ErrorResponse<()>> {
 | 
			
		||||
 | 
			
		||||
    // TODO: Implement FromParam for Name
 | 
			
		||||
    let name = Name::from_utf8(&zone).unwrap();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -54,8 +54,12 @@ impl<'r, T: Serialize> Responder<'r> for ErrorResponse<T> {
 | 
			
		|||
impl From<UserError> for ErrorResponse<()> {
 | 
			
		||||
    fn from(e: UserError) -> Self {
 | 
			
		||||
        match e {
 | 
			
		||||
            UserError::NotFound => ErrorResponse::new(Status::Unauthorized, "Incorrect password or username.".into()),
 | 
			
		||||
            UserError::UserExists => ErrorResponse::new(Status::Conflict, "User already exists.".into()),
 | 
			
		||||
            UserError::NotFound => ErrorResponse::new(Status::Unauthorized, "Provided credentials or token do not match any existing user".into()),
 | 
			
		||||
            UserError::UserExists => ErrorResponse::new(Status::Conflict, "User already exists".into()),
 | 
			
		||||
            UserError::BadToken => ErrorResponse::new(Status::BadRequest, "Malformed token".into()),
 | 
			
		||||
            UserError::ExpiredToken => ErrorResponse::new(Status::Unauthorized, "The provided token has expired".into()),
 | 
			
		||||
            UserError::MalformedHeader => ErrorResponse::new(Status::BadRequest, "Malformed authorization header".into()),
 | 
			
		||||
            UserError::PermissionDenied => ErrorResponse::new(Status::Forbidden, "Bearer is not authorized to access the resource".into()),
 | 
			
		||||
            UserError::DbError(e) => make_500(e),
 | 
			
		||||
            UserError::PasswordError(e) => make_500(e)
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,17 +2,29 @@ use uuid::Uuid;
 | 
			
		|||
use diesel::prelude::*;
 | 
			
		||||
use diesel::result::Error as DieselError;
 | 
			
		||||
use diesel_derive_enum::DbEnum;
 | 
			
		||||
use rocket::request::{FromRequest, Request, Outcome};
 | 
			
		||||
use rocket::request::{FromRequest, Request, Outcome, State};
 | 
			
		||||
use serde::{Serialize, Deserialize};
 | 
			
		||||
use rocket::http::Status;
 | 
			
		||||
use chrono::serde::ts_seconds;
 | 
			
		||||
use chrono::prelude::{DateTime, Utc};
 | 
			
		||||
use chrono::Duration;
 | 
			
		||||
// TODO: Maybe just use argon2 crate directly
 | 
			
		||||
use djangohashers::{make_password_with_algorithm, check_password, HasherError, Algorithm};
 | 
			
		||||
use jsonwebtoken::{encode, Header, EncodingKey, errors::Result as JwtResult};
 | 
			
		||||
use jsonwebtoken::{
 | 
			
		||||
    encode, decode,
 | 
			
		||||
    Header, Validation,
 | 
			
		||||
    Algorithm as JwtAlgorithm, EncodingKey, DecodingKey,
 | 
			
		||||
    errors::Result as JwtResult,
 | 
			
		||||
    errors::ErrorKind as JwtErrorKind
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use crate::schema::*;
 | 
			
		||||
use crate::DbConn;
 | 
			
		||||
use crate::config::Config;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
const BEARER: &'static str = "Bearer ";
 | 
			
		||||
const AUTH_HEADER: &'static str = "Authentication";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, DbEnum, Deserialize)]
 | 
			
		||||
| 
						 | 
				
			
			@ -82,11 +94,44 @@ pub struct UserInfo {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
impl<'a, 'r> FromRequest<'a, 'r> for UserInfo {
 | 
			
		||||
    type Error = ();
 | 
			
		||||
    type Error = UserError;
 | 
			
		||||
 | 
			
		||||
    fn from_request(request: &'a Request<'r>) -> Outcome<UserInfo, ()> {
 | 
			
		||||
        // LocalUser::get_user_by_uuid()
 | 
			
		||||
        Outcome::Forward(())
 | 
			
		||||
    fn from_request(request: &'a Request<'r>) -> Outcome<UserInfo, UserError> {
 | 
			
		||||
        let auth_header = match request.headers().get_one(AUTH_HEADER) {
 | 
			
		||||
            None => return Outcome::Forward(()),
 | 
			
		||||
            Some(auth_header) => auth_header,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let token = if auth_header.starts_with(BEARER) {
 | 
			
		||||
            auth_header.trim_start_matches(BEARER)
 | 
			
		||||
        } else {
 | 
			
		||||
            return Outcome::Failure((Status::BadRequest, UserError::MalformedHeader))
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // TODO: Better error handling
 | 
			
		||||
        let config = request.guard::<State<Config>>().unwrap();
 | 
			
		||||
        let conn = request.guard::<DbConn>().unwrap();
 | 
			
		||||
 | 
			
		||||
        let token_data = AuthClaims::decode(
 | 
			
		||||
            token, &config.web_app.secret
 | 
			
		||||
        ).map_err(|e| match e.into_kind() {
 | 
			
		||||
            JwtErrorKind::ExpiredSignature => (Status::Unauthorized, UserError::ExpiredToken),
 | 
			
		||||
            _ => (Status::BadRequest, UserError::BadToken),
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        let token_data = match token_data {
 | 
			
		||||
            Err(e) => return Outcome::Failure(e),
 | 
			
		||||
            Ok(data) => data
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let user_id = token_data.sub;
 | 
			
		||||
        let user_info = match LocalUser::get_user_by_uuid(&conn, user_id) {
 | 
			
		||||
            Err(UserError::NotFound) => return Outcome::Failure((Status::NotFound, UserError::NotFound)),
 | 
			
		||||
            Err(e) => return Outcome::Failure((Status::InternalServerError, e)),
 | 
			
		||||
            Ok(d) => d,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return Outcome::Success(user_info)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -94,6 +139,10 @@ impl<'a, 'r> FromRequest<'a, 'r> for UserInfo {
 | 
			
		|||
pub enum UserError {
 | 
			
		||||
    NotFound,
 | 
			
		||||
    UserExists,
 | 
			
		||||
    BadToken,
 | 
			
		||||
    ExpiredToken,
 | 
			
		||||
    MalformedHeader,
 | 
			
		||||
    PermissionDenied,
 | 
			
		||||
    DbError(DieselError),
 | 
			
		||||
    PasswordError(HasherError),
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -180,8 +229,19 @@ impl LocalUser {
 | 
			
		|||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn get_user_by_uuid(user_id: Uuid) -> Result<UserInfo, ()> {
 | 
			
		||||
        unimplemented!()
 | 
			
		||||
    pub fn get_user_by_uuid(conn: &DbConn, request_user_id: String) -> Result<UserInfo, UserError> {
 | 
			
		||||
        use crate::schema::localuser::dsl::*;
 | 
			
		||||
        use crate::schema::user::dsl::*;
 | 
			
		||||
 | 
			
		||||
        let (client_user, client_localuser): (User, LocalUser) = user.inner_join(localuser)
 | 
			
		||||
            .filter(id.eq(request_user_id))
 | 
			
		||||
            .get_result(&**conn)?;
 | 
			
		||||
 | 
			
		||||
        Ok(UserInfo {
 | 
			
		||||
            id: client_user.id,
 | 
			
		||||
            role: client_user.role,
 | 
			
		||||
            username: client_localuser.username,
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -200,7 +260,15 @@ impl AuthClaims {
 | 
			
		|||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn decode(token: &str, secret: &str) -> JwtResult<AuthClaims> {
 | 
			
		||||
        decode::<AuthClaims>(
 | 
			
		||||
            token,
 | 
			
		||||
            &DecodingKey::from_secret(secret.as_ref()),
 | 
			
		||||
            &Validation::new(JwtAlgorithm::HS256)
 | 
			
		||||
        ).and_then(|data| Ok(data.claims))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn encode(self, secret: &str) -> JwtResult<String> {
 | 
			
		||||
        encode(&Header::default(), &self, &EncodingKey::from_secret(secret.as_bytes()))
 | 
			
		||||
        encode(&Header::default(), &self, &EncodingKey::from_secret(secret.as_ref()))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue