From 822f3ed07306669d7d3d3801235730dae5f00c0d Mon Sep 17 00:00:00 2001 From: Antoine Gersant Date: Wed, 9 Nov 2022 00:39:48 -0800 Subject: [PATCH] Merged trivial modules --- src/app/user.rs | 263 ++++++++++++++++++++++++++++++++++-- src/app/user/error.rs | 23 ---- src/app/user/preferences.rs | 40 ------ src/app/user/test.rs | 189 -------------------------- 4 files changed, 255 insertions(+), 260 deletions(-) delete mode 100644 src/app/user/error.rs delete mode 100644 src/app/user/preferences.rs delete mode 100644 src/app/user/test.rs diff --git a/src/app/user.rs b/src/app/user.rs index 9e095c2..063b795 100644 --- a/src/app/user.rs +++ b/src/app/user.rs @@ -9,13 +9,29 @@ use std::time::{SystemTime, UNIX_EPOCH}; use crate::app::settings::AuthSecret; use crate::db::{users, DB}; -mod error; -mod preferences; -#[cfg(test)] -mod test; +#[derive(thiserror::Error, Debug, PartialEq, Eq)] +pub enum Error { + #[error("Cannot use empty username")] + EmptyUsername, + #[error("Cannot use empty password")] + EmptyPassword, + #[error("Username does not exist")] + IncorrectUsername, + #[error("Password does not match username")] + IncorrectPassword, + #[error("Invalid auth token")] + InvalidAuthToken, + #[error("Incorrect authorization scope")] + IncorrectAuthorizationScope, + #[error("Unspecified")] + Unspecified, +} -pub use error::*; -pub use preferences::*; +impl From for Error { + fn from(_: anyhow::Error) -> Self { + Error::Unspecified + } +} #[derive(Debug, Insertable, Queryable)] #[diesel(table_name = users)] @@ -53,10 +69,16 @@ pub struct Authorization { pub scope: AuthorizationScope, } +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct Preferences { + pub lastfm_username: Option, + pub web_theme_base: Option, + pub web_theme_accent: Option, +} + #[derive(Clone)] pub struct Manager { - // TODO make this private and move preferences methods in this file - pub db: DB, + db: DB, auth_secret: AuthSecret, } @@ -226,6 +248,38 @@ impl Manager { Ok(is_admin != 0) } + pub fn read_preferences(&self, username: &str) -> Result { + use crate::db::users::dsl::*; + let mut connection = self.db.connect()?; + let (theme_base, theme_accent, read_lastfm_username) = users + .select((web_theme_base, web_theme_accent, lastfm_username)) + .filter(name.eq(username)) + .get_result(&mut connection) + .map_err(|_| Error::Unspecified)?; + Ok(Preferences { + web_theme_base: theme_base, + web_theme_accent: theme_accent, + lastfm_username: read_lastfm_username, + }) + } + + pub fn write_preferences( + &self, + username: &str, + preferences: &Preferences, + ) -> Result<(), Error> { + use crate::db::users::dsl::*; + let mut connection = self.db.connect()?; + diesel::update(users.filter(name.eq(username))) + .set(( + web_theme_base.eq(&preferences.web_theme_base), + web_theme_accent.eq(&preferences.web_theme_accent), + )) + .execute(&mut connection) + .map_err(|_| Error::Unspecified)?; + Ok(()) + } + pub fn lastfm_link( &self, username: &str, @@ -298,3 +352,196 @@ fn verify_password(password_hash: &str, attempted_password: &str) -> bool { Err(_) => false, } } + +#[cfg(test)] +mod test { + use super::*; + use crate::app::test; + use crate::test_name; + + const TEST_USERNAME: &str = "Walter"; + const TEST_PASSWORD: &str = "super_secret!"; + + #[test] + fn create_delete_user_golden_path() { + let ctx = test::ContextBuilder::new(test_name!()).build(); + + let new_user = NewUser { + name: TEST_USERNAME.to_owned(), + password: TEST_PASSWORD.to_owned(), + admin: false, + }; + + ctx.user_manager.create(&new_user).unwrap(); + assert_eq!(ctx.user_manager.list().unwrap().len(), 1); + + ctx.user_manager.delete(&new_user.name).unwrap(); + assert_eq!(ctx.user_manager.list().unwrap().len(), 0); + } + + #[test] + fn cannot_create_user_with_blank_username() { + let ctx = test::ContextBuilder::new(test_name!()).build(); + let new_user = NewUser { + name: "".to_owned(), + password: TEST_PASSWORD.to_owned(), + admin: false, + }; + assert_eq!( + ctx.user_manager.create(&new_user).unwrap_err(), + Error::EmptyUsername + ); + } + + #[test] + fn cannot_create_user_with_blank_password() { + let ctx = test::ContextBuilder::new(test_name!()).build(); + let new_user = NewUser { + name: TEST_USERNAME.to_owned(), + password: "".to_owned(), + admin: false, + }; + assert_eq!( + ctx.user_manager.create(&new_user).unwrap_err(), + Error::EmptyPassword + ); + } + + #[test] + fn cannot_create_duplicate_user() { + let ctx = test::ContextBuilder::new(test_name!()).build(); + let new_user = NewUser { + name: TEST_USERNAME.to_owned(), + password: TEST_PASSWORD.to_owned(), + admin: false, + }; + ctx.user_manager.create(&new_user).unwrap(); + ctx.user_manager.create(&new_user).unwrap_err(); + } + + #[test] + fn can_read_write_preferences() { + let ctx = test::ContextBuilder::new(test_name!()).build(); + + let new_preferences = Preferences { + web_theme_base: Some("very-dark-theme".to_owned()), + web_theme_accent: Some("#FF0000".to_owned()), + lastfm_username: None, + }; + + let new_user = NewUser { + name: TEST_USERNAME.to_owned(), + password: TEST_PASSWORD.to_owned(), + admin: false, + }; + ctx.user_manager.create(&new_user).unwrap(); + + ctx.user_manager + .write_preferences(TEST_USERNAME, &new_preferences) + .unwrap(); + + let read_preferences = ctx.user_manager.read_preferences("Walter").unwrap(); + assert_eq!(new_preferences, read_preferences); + } + + #[test] + fn login_rejects_bad_password() { + let ctx = test::ContextBuilder::new(test_name!()).build(); + + let new_user = NewUser { + name: TEST_USERNAME.to_owned(), + password: TEST_PASSWORD.to_owned(), + admin: false, + }; + + ctx.user_manager.create(&new_user).unwrap(); + assert_eq!( + ctx.user_manager + .login(TEST_USERNAME, "not the password") + .unwrap_err(), + Error::IncorrectPassword + ) + } + + #[test] + fn login_golden_path() { + let ctx = test::ContextBuilder::new(test_name!()).build(); + let new_user = NewUser { + name: TEST_USERNAME.to_owned(), + password: TEST_PASSWORD.to_owned(), + admin: false, + }; + ctx.user_manager.create(&new_user).unwrap(); + assert!(ctx.user_manager.login(TEST_USERNAME, TEST_PASSWORD).is_ok()) + } + + #[test] + fn authenticate_rejects_bad_token() { + let ctx = test::ContextBuilder::new(test_name!()).build(); + + let new_user = NewUser { + name: TEST_USERNAME.to_owned(), + password: TEST_PASSWORD.to_owned(), + admin: false, + }; + + ctx.user_manager.create(&new_user).unwrap(); + let fake_token = AuthToken("fake token".to_owned()); + assert!(ctx + .user_manager + .authenticate(&fake_token, AuthorizationScope::PolarisAuth) + .is_err()) + } + + #[test] + fn authenticate_golden_path() { + let ctx = test::ContextBuilder::new(test_name!()).build(); + + let new_user = NewUser { + name: TEST_USERNAME.to_owned(), + password: TEST_PASSWORD.to_owned(), + admin: false, + }; + + ctx.user_manager.create(&new_user).unwrap(); + let token = ctx + .user_manager + .login(TEST_USERNAME, TEST_PASSWORD) + .unwrap(); + let authorization = ctx + .user_manager + .authenticate(&token, AuthorizationScope::PolarisAuth) + .unwrap(); + assert_eq!( + authorization, + Authorization { + username: TEST_USERNAME.to_owned(), + scope: AuthorizationScope::PolarisAuth, + } + ) + } + + #[test] + fn authenticate_validates_scope() { + let ctx = test::ContextBuilder::new(test_name!()).build(); + + let new_user = NewUser { + name: TEST_USERNAME.to_owned(), + password: TEST_PASSWORD.to_owned(), + admin: false, + }; + + ctx.user_manager.create(&new_user).unwrap(); + let token = ctx + .user_manager + .generate_lastfm_link_token(TEST_USERNAME) + .unwrap(); + let authorization = ctx + .user_manager + .authenticate(&token, AuthorizationScope::PolarisAuth); + assert_eq!( + authorization.unwrap_err(), + Error::IncorrectAuthorizationScope + ) + } +} diff --git a/src/app/user/error.rs b/src/app/user/error.rs deleted file mode 100644 index bb8c082..0000000 --- a/src/app/user/error.rs +++ /dev/null @@ -1,23 +0,0 @@ -#[derive(thiserror::Error, Debug, PartialEq, Eq)] -pub enum Error { - #[error("Cannot use empty username")] - EmptyUsername, - #[error("Cannot use empty password")] - EmptyPassword, - #[error("Username does not exist")] - IncorrectUsername, - #[error("Password does not match username")] - IncorrectPassword, - #[error("Invalid auth token")] - InvalidAuthToken, - #[error("Incorrect authorization scope")] - IncorrectAuthorizationScope, - #[error("Unspecified")] - Unspecified, -} - -impl From for Error { - fn from(_: anyhow::Error) -> Self { - Error::Unspecified - } -} diff --git a/src/app/user/preferences.rs b/src/app/user/preferences.rs deleted file mode 100644 index a0bb558..0000000 --- a/src/app/user/preferences.rs +++ /dev/null @@ -1,40 +0,0 @@ -use anyhow::Result; -use diesel::prelude::*; -use serde::{Deserialize, Serialize}; - -use super::*; - -#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] -pub struct Preferences { - pub lastfm_username: Option, - pub web_theme_base: Option, - pub web_theme_accent: Option, -} - -impl Manager { - pub fn read_preferences(&self, username: &str) -> Result { - use self::users::dsl::*; - let mut connection = self.db.connect()?; - let (theme_base, theme_accent, read_lastfm_username) = users - .select((web_theme_base, web_theme_accent, lastfm_username)) - .filter(name.eq(username)) - .get_result(&mut connection)?; - Ok(Preferences { - web_theme_base: theme_base, - web_theme_accent: theme_accent, - lastfm_username: read_lastfm_username, - }) - } - - pub fn write_preferences(&self, username: &str, preferences: &Preferences) -> Result<()> { - use crate::db::users::dsl::*; - let mut connection = self.db.connect()?; - diesel::update(users.filter(name.eq(username))) - .set(( - web_theme_base.eq(&preferences.web_theme_base), - web_theme_accent.eq(&preferences.web_theme_accent), - )) - .execute(&mut connection)?; - Ok(()) - } -} diff --git a/src/app/user/test.rs b/src/app/user/test.rs deleted file mode 100644 index f9cd721..0000000 --- a/src/app/user/test.rs +++ /dev/null @@ -1,189 +0,0 @@ -use super::*; -use crate::app::test; -use crate::test_name; - -const TEST_USERNAME: &str = "Walter"; -const TEST_PASSWORD: &str = "super_secret!"; - -#[test] -fn create_delete_user_golden_path() { - let ctx = test::ContextBuilder::new(test_name!()).build(); - - let new_user = NewUser { - name: TEST_USERNAME.to_owned(), - password: TEST_PASSWORD.to_owned(), - admin: false, - }; - - ctx.user_manager.create(&new_user).unwrap(); - assert_eq!(ctx.user_manager.list().unwrap().len(), 1); - - ctx.user_manager.delete(&new_user.name).unwrap(); - assert_eq!(ctx.user_manager.list().unwrap().len(), 0); -} - -#[test] -fn cannot_create_user_with_blank_username() { - let ctx = test::ContextBuilder::new(test_name!()).build(); - let new_user = NewUser { - name: "".to_owned(), - password: TEST_PASSWORD.to_owned(), - admin: false, - }; - assert_eq!( - ctx.user_manager.create(&new_user).unwrap_err(), - Error::EmptyUsername - ); -} - -#[test] -fn cannot_create_user_with_blank_password() { - let ctx = test::ContextBuilder::new(test_name!()).build(); - let new_user = NewUser { - name: TEST_USERNAME.to_owned(), - password: "".to_owned(), - admin: false, - }; - assert_eq!( - ctx.user_manager.create(&new_user).unwrap_err(), - Error::EmptyPassword - ); -} - -#[test] -fn cannot_create_duplicate_user() { - let ctx = test::ContextBuilder::new(test_name!()).build(); - let new_user = NewUser { - name: TEST_USERNAME.to_owned(), - password: TEST_PASSWORD.to_owned(), - admin: false, - }; - ctx.user_manager.create(&new_user).unwrap(); - ctx.user_manager.create(&new_user).unwrap_err(); -} - -#[test] -fn can_read_write_preferences() { - let ctx = test::ContextBuilder::new(test_name!()).build(); - - let new_preferences = Preferences { - web_theme_base: Some("very-dark-theme".to_owned()), - web_theme_accent: Some("#FF0000".to_owned()), - lastfm_username: None, - }; - - let new_user = NewUser { - name: TEST_USERNAME.to_owned(), - password: TEST_PASSWORD.to_owned(), - admin: false, - }; - ctx.user_manager.create(&new_user).unwrap(); - - ctx.user_manager - .write_preferences(TEST_USERNAME, &new_preferences) - .unwrap(); - - let read_preferences = ctx.user_manager.read_preferences("Walter").unwrap(); - assert_eq!(new_preferences, read_preferences); -} - -#[test] -fn login_rejects_bad_password() { - let ctx = test::ContextBuilder::new(test_name!()).build(); - - let new_user = NewUser { - name: TEST_USERNAME.to_owned(), - password: TEST_PASSWORD.to_owned(), - admin: false, - }; - - ctx.user_manager.create(&new_user).unwrap(); - assert_eq!( - ctx.user_manager - .login(TEST_USERNAME, "not the password") - .unwrap_err(), - Error::IncorrectPassword - ) -} - -#[test] -fn login_golden_path() { - let ctx = test::ContextBuilder::new(test_name!()).build(); - let new_user = NewUser { - name: TEST_USERNAME.to_owned(), - password: TEST_PASSWORD.to_owned(), - admin: false, - }; - ctx.user_manager.create(&new_user).unwrap(); - assert!(ctx.user_manager.login(TEST_USERNAME, TEST_PASSWORD).is_ok()) -} - -#[test] -fn authenticate_rejects_bad_token() { - let ctx = test::ContextBuilder::new(test_name!()).build(); - - let new_user = NewUser { - name: TEST_USERNAME.to_owned(), - password: TEST_PASSWORD.to_owned(), - admin: false, - }; - - ctx.user_manager.create(&new_user).unwrap(); - let fake_token = AuthToken("fake token".to_owned()); - assert!(ctx - .user_manager - .authenticate(&fake_token, AuthorizationScope::PolarisAuth) - .is_err()) -} - -#[test] -fn authenticate_golden_path() { - let ctx = test::ContextBuilder::new(test_name!()).build(); - - let new_user = NewUser { - name: TEST_USERNAME.to_owned(), - password: TEST_PASSWORD.to_owned(), - admin: false, - }; - - ctx.user_manager.create(&new_user).unwrap(); - let token = ctx - .user_manager - .login(TEST_USERNAME, TEST_PASSWORD) - .unwrap(); - let authorization = ctx - .user_manager - .authenticate(&token, AuthorizationScope::PolarisAuth) - .unwrap(); - assert_eq!( - authorization, - Authorization { - username: TEST_USERNAME.to_owned(), - scope: AuthorizationScope::PolarisAuth, - } - ) -} - -#[test] -fn authenticate_validates_scope() { - let ctx = test::ContextBuilder::new(test_name!()).build(); - - let new_user = NewUser { - name: TEST_USERNAME.to_owned(), - password: TEST_PASSWORD.to_owned(), - admin: false, - }; - - ctx.user_manager.create(&new_user).unwrap(); - let token = ctx - .user_manager - .generate_lastfm_link_token(TEST_USERNAME) - .unwrap(); - let authorization = ctx - .user_manager - .authenticate(&token, AuthorizationScope::PolarisAuth); - assert_eq!( - authorization.unwrap_err(), - Error::IncorrectAuthorizationScope - ) -}