From 592c925bc15138d408ffb68a69f15adb43a57d1c Mon Sep 17 00:00:00 2001 From: jeff Date: Sun, 3 Jan 2021 22:05:28 +0000 Subject: [PATCH] WIP - at a crossroads where we need some kind of auth mechanism --- src/errors.rs | 23 ++++++++++++ src/grpc.rs | 89 ++++++++++++++++++++++++++++++++++++++++++++--- src/routes/api.rs | 5 +-- 3 files changed, 109 insertions(+), 8 deletions(-) diff --git a/src/errors.rs b/src/errors.rs index f631094..e424eef 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -4,6 +4,7 @@ use rocket::response::status; use rocket::response::{self, Responder}; use rocket_contrib::templates::Template; use std::collections::HashMap; +use std::convert::Into; use thiserror::Error; #[derive(Error, Debug)] @@ -48,6 +49,28 @@ impl Error { _ => false, } } + + fn message(&self) -> String { + if self.is_sensitive() { + "internal error".to_string() + } else { + self.to_string() + } + } +} + +impl From for tonic::Status { + fn from(err: Error) -> tonic::Status { + use tonic::{Code, Status}; + use Error::*; + match err { + NotFound => Status::new(Code::NotFound, err.message()), + NotLoggedIn => Status::new(Code::Unauthenticated, err.message()), + NoPermission => Status::new(Code::PermissionDenied, err.message()), + InvalidInput => Status::new(Code::InvalidArgument, err.message()), + _ => Status::new(Code::Internal, err.message()), + } + } } #[rocket::async_trait] diff --git a/src/grpc.rs b/src/grpc.rs index e92c58a..b57bea7 100644 --- a/src/grpc.rs +++ b/src/grpc.rs @@ -1,7 +1,66 @@ -use crate::models::proto::cofd::api::cofd_api_server::{CofdApi, CofdApiServer}; +use crate::db::Dao; +use crate::errors::Error; +use crate::models::characters::Character; +use crate::models::proto::cofd::api::cofd_api_server::CofdApi; use crate::models::proto::cofd::api::UpdateSkillValueRequest; use crate::models::proto::cofd::cofd_sheet::Skill; -use tonic::{transport::Server, Request, Response, Status}; +use crate::models::proto::cofd::*; +use crate::models::users::User; +use std::collections::btree_map::{Entry, OccupiedEntry}; +use tonic::{Request, Response, Status}; + +/// Load the character belonging to the given user, as long as they're +/// the owner of that character. Returns an error if user is not +/// logged in, the owner of the character is not found, or the logged +/// in user does not have the permission to access this character. +async fn load_character( + conn: &sqlx::SqlitePool, + logged_in_user: Option<&User>, + owner: &str, + character_id: i32, +) -> Result { + let logged_in_user = logged_in_user.ok_or(Error::NotLoggedIn)?; + + let character: Character = conn + .load_character(character_id) + .await? + .ok_or(Error::NotFound)?; + + if &logged_in_user.username != owner { + return Err(Error::NoPermission); + } + + Ok(character) +} + +fn find_skill_entry<'a>( + sheet: &'a mut CofdSheet, + skill_name: &'a str, +) -> Option> { + let all_skills = vec![ + &mut sheet.mental_skills, + &mut sheet.physical_skills, + &mut sheet.social_skills, + ]; + + // Search all skill lists for this value using "workaround" to + // break value from for loops. + let skill: Option> = 'l: loop { + for skill_map in all_skills { + if let Entry::Occupied(entry) = skill_map.entry(skill_name.to_owned()) { + break 'l Some(entry); + } + } + + break None; + }; + + skill +} + +fn find_skill<'a>(sheet: &'a mut CofdSheet, skill_name: &'a str) -> Option<&'a mut Skill> { + find_skill_entry(sheet, skill_name).map(|entry| entry.into_mut()) +} #[derive(Debug)] pub struct CofdApiService { @@ -14,8 +73,30 @@ impl CofdApi for CofdApiService { &self, request: Request, // Accept request of type HelloRequest ) -> Result, Status> { - // Return an instance of type HelloReply - println!("Got a request: {:?}", request); + //Can use metadata map to add user id inside interceptor for auth. + let request = request.into_inner(); + let mut character = load_character( + &self.db, + logged_in_user, + &request.character_username, + request.character_id, + ) + .await?; + + let mut sheet: CofdSheet = character.try_deserialize()?; + let skill: Option<&mut Skill> = find_skill(&mut sheet, &request.skill_name); + + skill + .map(|s| s.dots = request.skill_value) + .ok_or(Error::InvalidInput)?; + + println!("updated skill value",); + + character.update_data(sheet)?; + self.db + .update_character_sheet(&character) + .await + .map_err(|e| e.into())?; //TODO maybe use crate Error for db let reply = Skill::default(); diff --git a/src/routes/api.rs b/src/routes/api.rs index 8c632c8..17ac5b8 100644 --- a/src/routes/api.rs +++ b/src/routes/api.rs @@ -1,11 +1,8 @@ use crate::db::{Dao, TenebrousDbConn}; use crate::errors::Error; -use crate::models::characters::{Character, CharacterDataType, DynCharacterData, Visibility}; +use crate::models::characters::Character; use crate::models::proto::cofd::*; use crate::models::users::User; -use rocket_contrib::templates::Template; -use serde::Serialize; -use std::borrow::Cow; use std::collections::btree_map::{Entry, OccupiedEntry}; pub(crate) fn routes() -> Vec {