Skip to content

Commit

Permalink
Merge pull request #3 from dhruvdabhi101/develop
Browse files Browse the repository at this point in the history
fix: authentication and added a new pages route
  • Loading branch information
dhruvdabhi101 authored Jan 24, 2024
2 parents 8d57136 + b07725d commit 68364b0
Show file tree
Hide file tree
Showing 13 changed files with 176 additions and 78 deletions.
12 changes: 12 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ dotenv = "0.15.0"
jsonwebtoken = "9.2.0"
pwhash = "1.0.0"
rocket = {version = "0.5.0", features = ["json"]}
rocket-authorization = "1.0.0"
serde = "1.0.195"
shuttle-rocket = "0.36.0"
shuttle-runtime = "0.36.0"
Expand Down
61 changes: 17 additions & 44 deletions src/api/page_api.rs
Original file line number Diff line number Diff line change
@@ -1,56 +1,29 @@
use mongodb::bson::extjson::de::Error;
use mongodb::results::InsertOneResult;
use rocket::{get, http::Status, post, serde::json::Json, State};
use rocket_authorization::oauth::OAuth;
use rocket_authorization::{AuthError, Credential};

use crate::config::jwt::decode_jwt;
use crate::{
config::jwt::create_jwt,
models::user_model::{LoginResponse, LoginUser, User, UserFromMongo},
models::{user_model::{LoginResponse, LoginUser, User, UserFromMongo}, page_model::{PageCreateRequest, PageCreateResponse, Page}},
repository::mongodb_repo::MongoRepo,
};

#[post("/user", data = "<new_user>")]
pub fn create_user(
db: &State<MongoRepo>,
new_user: Json<User>,
) -> Result<Json<InsertOneResult>, Status> {
let data = User {
username: new_user.username.clone(),
password: new_user.password.clone(),
email: new_user.email.clone(),
name: new_user.name.clone(),
#[post("/create-page", data="<new_page>")]
pub fn create_page(auth: Credential<OAuth>, db: &State<MongoRepo>, new_page: Json<PageCreateRequest>) -> Result<Json<PageCreateResponse>, Status> {
// get user from jwt token
let token = &auth.token;
println!("{:?}", token);

let new_page = PageCreateRequest {
title: new_page.title.clone(),
content: new_page.content.clone(),
slug: new_page.slug.clone(),
published: new_page.published.clone(),
};
let user_details = db.create_user(data);
match user_details {
Ok(user) => Ok(Json(user)),
Err(_) => Err(Status::InternalServerError),
}
}

#[get("/user/<path>")]
pub fn get_user(db: &State<MongoRepo>, path: &str) -> Result<Json<UserFromMongo>, Status> {
let id = path;
if id.is_empty() {
return Err(Status::BadRequest);
}
let user_details = db.get_user(&id);
match user_details {
Ok(user) => Ok(Json(user)),
Err(_) => Err(Status::InternalServerError),
}
let page: PageCreateResponse = db.create_page(new_page, token).unwrap();
Ok(Json(page))
}
#[post("/login", data = "<user>")]
pub fn login(db: &State<MongoRepo>, user: Json<LoginUser>) -> Result<Json<LoginResponse>, Status> {
// get user details from db
let user_details: Result<UserFromMongo, Error> = db.login(&user.username, &user.password);

// if user not found return 404
if user_details.is_err() {
return Err(Status::InternalServerError);
} else {
// if user found return jwt token
let user: UserFromMongo = user_details.unwrap();
Ok(Json(LoginResponse {
token: create_jwt(user._id.to_hex()).unwrap(),
}))
}
}
23 changes: 11 additions & 12 deletions src/api/user_api.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
use mongodb::bson::extjson::de::Error;
use mongodb::results::InsertOneResult;
use rocket::{get, http::Status, post, serde::json::Json, State};

use crate::{
config::jwt::create_jwt,
models::user_model::{LoginResponse, LoginUser, User, UserFromMongo},
repository::mongodb_repo::MongoRepo,
repository::{mongodb_repo::MongoRepo, error::UserError},
};

#[post("/user", data = "<new_user>")]
Expand Down Expand Up @@ -41,16 +40,16 @@ pub fn get_user(db: &State<MongoRepo>, path: &str) -> Result<Json<UserFromMongo>
#[post("/login", data = "<user>")]
pub fn login(db: &State<MongoRepo>, user: Json<LoginUser>) -> Result<Json<LoginResponse>, Status> {
// get user details from db
let user_details: Result<UserFromMongo, Error> = db.login(&user.username, &user.password);
let user_details: Result<UserFromMongo, UserError> = db.login(&user.username, &user.password);
println!("{:?}", user_details);

// if user not found return 404
if user_details.is_err() {
return Err(Status::InternalServerError);
} else {
// if user found return jwt token
let user: UserFromMongo = user_details.unwrap();
Ok(Json(LoginResponse {
token: create_jwt(user._id.to_hex()).unwrap(),
}))
match user_details {
Ok(user) => {
// create jwt token
let token = create_jwt(user._id.to_hex()).unwrap();
// return token
Ok(Json(LoginResponse { token }))
}
Err(_) => Err(Status::NotFound),
}
}
32 changes: 32 additions & 0 deletions src/config/auth.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
use rocket::{
outcome::Outcome,
http::Status,
request::{self, FromRequest, Request},
};

use super::jwt::decode_jwt;

pub const SECRET: &str = "secrets";

#[derive(Debug, serde::Serialize, serde::Deserialize)]
Expand All @@ -13,3 +21,27 @@ pub fn hash_password(password: String) -> String {
pub fn verify_password(password: &str, hash: &str) -> bool {
bcrypt::verify(password, hash).unwrap()
}

// pub struct Token(pub String);
//
// #[derive(Debug)]
// enum ApiTokenError {
// Missing,
// Invalid,
// }
//
// #[shuttle_runtime::async_trait]
// impl<'a, 'r> FromRequest<'a> for Token {
// type Error = ApiTokenError;
//
// fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, Self::Error> {
// let token = request.headers().get_one("Authorization");
// match token {
// Some(token) => {
// let token_str = decode_jwt(token).unwrap();
// Outcome::Success(Token(token_str.sub))
// }
// None => Outcome::Error((Status::Unauthorized, ApiTokenError::Missing)),
// }
// }
// }
12 changes: 11 additions & 1 deletion src/config/jwt.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use chrono::{Duration, Utc};
use jsonwebtoken::{encode, errors::Error, Algorithm, EncodingKey, Header};
use jsonwebtoken::{encode, decode,errors::Error, Algorithm, EncodingKey, Header, DecodingKey};

use super::auth::{Claims, SECRET};

Expand All @@ -11,3 +11,13 @@ pub fn create_jwt(uid: String) -> Result<String, Error> {
};
encode(&header, &claims, &EncodingKey::from_secret(SECRET.as_ref())).map_err(|e| e.into())
}

pub fn decode_jwt(token: &str) -> Result<Claims, Error> {
decode::<Claims>(
token,
&DecodingKey::from_secret(SECRET.as_ref()),
&jsonwebtoken::Validation::new(Algorithm::HS512),
)
.map(|data| data.claims)
.map_err(|e| e.into())
}
5 changes: 3 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ pub mod api;
pub mod config;
pub mod models;
pub mod repository;
use api::user_api::{create_user, get_user, login};
use api::{user_api::{create_user, get_user, login}, page_api::create_page};
use repository::mongodb_repo::MongoRepo;
use rocket::{get, http::Status, routes, serde::json::Json, Rocket};

Expand All @@ -16,6 +16,7 @@ async fn main() -> shuttle_rocket::ShuttleRocket {
let db: MongoRepo = MongoRepo::init();
let rocket: Rocket<rocket::Build> = rocket::build()
.manage(db)
.mount("/", routes![index, create_user, get_user, login]);
.mount("/", routes![index, create_user, get_user, login])
.mount("/pages", routes![create_page]);
Ok(rocket.into())
}
1 change: 1 addition & 0 deletions src/models/mod.rs
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
pub mod user_model;
pub mod page_model;
23 changes: 19 additions & 4 deletions src/models/page_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,27 @@ use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Debug)]
pub struct Page {
#[serde(rename = "_id")]
pub id: ObjectId,
pub _id: ObjectId,
pub title: String,
pub content: String,
pub slug: String,
pub published: bool,
pub created_at: i64,
pub updated_at: i64,
pub user_id: ObjectId
}

#[derive(Serialize, Deserialize, Debug)]
pub struct PageCreateRequest {
pub title: String,
pub content: String,
pub slug: String,
pub published: bool,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct PageCreateResponse {
pub title: String,
pub content: String,
pub slug: String,
pub published: bool,
pub user_id: ObjectId
}
3 changes: 1 addition & 2 deletions src/models/user_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,8 @@ pub struct User {
pub password: String,
}

#[derive(Debug, Serialize, Deserialize)]
#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct UserFromMongo {
// #[serde(serialize_with = "bson::serde_helpers::serialize_object_id_as_hex_string")]
pub _id: ObjectId,
pub username: String,
pub name: String,
Expand Down
15 changes: 15 additions & 0 deletions src/repository/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#[derive(Debug)]
pub enum UserError {
NotFound,
AlreadyExists,
InvalidPassword,
InvalidCredentials,
InternalError,
}

#[derive(Debug)]
pub enum PageError {
NotFound,
AlreadyExists,
InternalError
}
1 change: 1 addition & 0 deletions src/repository/mod.rs
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
pub mod mongodb_repo;
pub mod error;
65 changes: 52 additions & 13 deletions src/repository/mongodb_repo.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use crate::config::auth::{hash_password, verify_password};
use crate::config::jwt::decode_jwt;
use crate::models::page_model::{Page, PageCreateRequest, PageCreateResponse};
use crate::models::user_model::{User, UserFromMongo};
use dotenv::dotenv;
use mongodb::bson::extjson::de::Error;
Expand All @@ -8,9 +10,13 @@ use mongodb::{
sync::{Client, Collection},
};
use std::env;
use std::str::FromStr;

use super::error::UserError;

pub struct MongoRepo {
col: Collection<UserFromMongo>,
page: Collection<Page>,
}

impl MongoRepo {
Expand All @@ -23,12 +29,11 @@ impl MongoRepo {
let client = Client::with_uri_str(uri).unwrap();
let db = client.database("rusty-leaf");
let col: Collection<UserFromMongo> = db.collection("user");
MongoRepo { col }
let page: Collection<Page> = db.collection("page");
MongoRepo { col, page }
}

pub fn create_user(&self, new_user: User) -> Result<InsertOneResult, Error> {
//TODO: hash password

let hashed_password: String = hash_password(new_user.password);

let new_doc = UserFromMongo {
Expand All @@ -54,26 +59,60 @@ impl MongoRepo {
.expect("Error Finding User");
Ok(user_detail.expect("User not found"))
}
pub fn login(&self, username: &str, password: &str) -> Result<UserFromMongo, Error> {
let filter = doc! {"username": username, "password": password};

pub fn login(&self, username: &str, password: &str) -> Result<UserFromMongo, UserError> {
let filter = doc! {"username": username};

let user_detail = self
.col
.find_one(filter, None)
.ok()
.expect("Error Finding User");

// check password
if user_detail.is_none() {
//TODO: return error
} else {
let hashed_password = user_detail.as_ref().unwrap().password.clone();
let is_valid = verify_password(password, hashed_password.as_str());

if user_detail.is_some() {
let user: UserFromMongo = user_detail.unwrap();
let is_valid = verify_password(password, user.password.as_str());
if !is_valid {
//TODO: return error
return Err(UserError::InvalidPassword);
}
return Ok(user);
} else {
return Err(UserError::NotFound);
}


Ok(user_detail.expect("User not found"))
}

// Page CRUD
pub fn create_page(&self, new_page: PageCreateRequest, token: &String) -> Result<PageCreateResponse, Error> {
// get user id from jwt token
let user_id = decode_jwt(token.as_str()).unwrap().sub;
// let user_obj: ObjectId = ;

let user_id = ObjectId::from_str(user_id.as_str()).unwrap();
// create page
let page = Page {
_id: ObjectId::new(),
title: new_page.title.clone(),
content: new_page.content.clone(),
published: new_page.published.clone(),
slug: new_page.slug.clone(),
user_id: user_id.clone()
};
self
.page
.insert_one(page, None)
.ok()
.expect("Error Creating Page");

let page_response = PageCreateResponse {
title: new_page.title,
content: new_page.content,
published: new_page.published,
slug: new_page.slug,
user_id,
};
Ok(page_response)
}
}

0 comments on commit 68364b0

Please sign in to comment.