use uuid::Uuid; use diesel::prelude::*; use diesel::result::Error as DieselError; use diesel_derive_enum::DbEnum; use rocket::request::{FromRequest, Request, Outcome}; use serde::{Serialize, Deserialize}; 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 crate::schema::*; use crate::DbConn; #[derive(Debug, DbEnum, Deserialize)] #[serde(rename_all = "snake_case")] pub enum Role { Admin, ZoneAdmin, } // TODO: Store Uuid instead of string?? // TODO: Store role as Role and not String. #[derive(Debug, Queryable, Identifiable, Insertable)] #[table_name = "user"] pub struct User { pub id: String, pub role: String, } #[derive(Debug, Queryable, Identifiable, Insertable)] #[table_name = "localuser"] #[primary_key(user_id)] pub struct LocalUser { pub user_id: String, pub username: String, pub password: String, } #[derive(Debug, Deserialize)] pub struct CreateUserRequest { pub username: String, pub password: String, pub email: String, pub role: Option } // pub struct LdapUserAssociation { // user_id: Uuid, // ldap_id: String // } #[derive(Debug, Serialize, Deserialize)] pub struct AuthClaims { pub jti: String, pub sub: String, #[serde(with = "ts_seconds")] pub exp: DateTime, #[serde(with = "ts_seconds")] pub iat: DateTime, } #[derive(Debug, Serialize)] pub struct AuthTokenResponse { pub token: String } #[derive(Debug, Deserialize)] pub struct AuthTokenRequest { pub username: String, pub password: String, } #[derive(Debug)] pub struct UserInfo { pub id: String, pub role: String, pub username: String, } impl<'a, 'r> FromRequest<'a, 'r> for UserInfo { type Error = (); fn from_request(request: &'a Request<'r>) -> Outcome { // LocalUser::get_user_by_uuid() Outcome::Forward(()) } } #[derive(Debug)] pub enum UserError { NotFound, UserExists, DbError(DieselError), PasswordError(HasherError), } impl From for UserError { fn from(e: DieselError) -> Self { match e { DieselError::NotFound => UserError::NotFound, DieselError::DatabaseError(diesel::result::DatabaseErrorKind::UniqueViolation, _) => UserError::UserExists, other => UserError::DbError(other) } } } impl From for UserError { fn from(e: HasherError) -> Self { match e { other => UserError::PasswordError(other) } } } impl LocalUser { pub fn create_user(conn: &DbConn, user_request: CreateUserRequest) -> Result { use crate::schema::localuser::dsl::*; use crate::schema::user::dsl::*; let new_user_id = Uuid::new_v4().to_simple().to_string(); let new_user = User { id: new_user_id.clone(), // TODO: Use role from request role: "zoneadmin".into(), }; let new_localuser = LocalUser { user_id: new_user_id.clone(), username: user_request.username.clone(), password: make_password_with_algorithm(&user_request.password, Algorithm::Argon2), }; let res = UserInfo { id: new_user.id.clone(), role: new_user.role.clone(), username: new_localuser.username.clone(), }; conn.immediate_transaction(|| -> diesel::QueryResult<()> { diesel::insert_into(user) .values(new_user) .execute(&**conn)?; diesel::insert_into(localuser) .values(new_localuser) .execute(&**conn)?; Ok(()) })?; Ok(res) } pub fn get_user_by_creds( conn: &DbConn, request_username: &str, request_password: &str ) -> Result { use crate::schema::localuser::dsl::*; use crate::schema::user::dsl::*; let (client_user, client_localuser): (User, LocalUser) = user.inner_join(localuser) .filter(username.eq(request_username)) .get_result(&**conn)?; if !check_password(&request_password, &client_localuser.password)? { return Err(UserError::NotFound); } Ok(UserInfo { id: client_user.id, role: client_user.role, username: client_localuser.username, }) } pub fn get_user_by_uuid(user_id: Uuid) -> Result { unimplemented!() } } impl AuthClaims { pub fn new(user_info: &UserInfo, token_duration: Duration) -> AuthClaims { let jti = Uuid::new_v4().to_simple().to_string(); let iat = Utc::now(); let exp = iat + token_duration; AuthClaims { jti: jti, sub: user_info.id.clone(), exp: exp, iat: iat, } } pub fn encode(self, secret: &str) -> JwtResult { encode(&Header::default(), &self, &EncodingKey::from_secret(secret.as_bytes())) } }