From 0553aaa9354fbe3f8a4d33faabec54aacc00898a Mon Sep 17 00:00:00 2001 From: Dietrich Date: Sun, 14 Feb 2021 22:28:34 +0100 Subject: [PATCH] Add permissions to the page --- .gitignore | 5 +- .../down.sql | 19 +- .../up.sql | 29 +- src/forms.rs | 2 +- src/main.rs | 52 ++- src/models.rs | 5 +- src/queries.rs | 429 ++++++++++++++++++ src/schema.rs | 1 + src/views.rs | 379 ++++++---------- static/admin.css | 3 +- templates/admin.html | 10 +- templates/index.html | 11 +- templates/view_link.html | 5 +- templates/view_profile.html | 18 +- 14 files changed, 671 insertions(+), 297 deletions(-) create mode 100644 src/queries.rs diff --git a/.gitignore b/.gitignore index 4ebbb4c..bfe228d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ /target .env -links.db \ No newline at end of file +links.db +launch.json +settings.json +links.session.sql diff --git a/migrations/2021-02-10-072528_add_user_profiles/down.sql b/migrations/2021-02-10-072528_add_user_profiles/down.sql index 7875a4a..23eb1f6 100644 --- a/migrations/2021-02-10-072528_add_user_profiles/down.sql +++ b/migrations/2021-02-10-072528_add_user_profiles/down.sql @@ -1,3 +1,20 @@ -- This file should undo anything in `up.sql` -DROP TABLE user_roles; \ No newline at end of file +CREATE TABLE usersold +( + id INTEGER PRIMARY KEY NOT NULL, + username VARCHAR NOT NULL, + email VARCHAR NOT NULL, + password VARCHAR NOT NULL, + + UNIQUE(username, email) +); + +INSERT INTO usersold +SELECT id,username,email,password +FROM users; +DROP TABLE users; + +ALTER TABLE usersold +RENAME TO users; + diff --git a/migrations/2021-02-10-072528_add_user_profiles/up.sql b/migrations/2021-02-10-072528_add_user_profiles/up.sql index c9ea528..944f56b 100644 --- a/migrations/2021-02-10-072528_add_user_profiles/up.sql +++ b/migrations/2021-02-10-072528_add_user_profiles/up.sql @@ -1,12 +1,27 @@ -- Your SQL goes here -CREATE TABLE user_roles + +ALTER TABLE users ADD COLUMN role INTEGER DEFAULT 1 NOT NULL; +CREATE TABLE usersnew ( id INTEGER PRIMARY KEY NOT NULL, - name TEXT NOT NULL, - description TEXT NOT NULL, + username VARCHAR NOT NULL, + email VARCHAR NOT NULL, + password VARCHAR NOT NULL, + role INTEGER DEFAULT 1 NOT NULL, + UNIQUE(username, email), FOREIGN KEY - (link) - REFERENCES users - (id) -); \ No newline at end of file + (role) REFERENCES user_roles + (id) +); + +INSERT INTO usersnew +SELECT * +FROM users; +DROP TABLE users; + +ALTER TABLE usersnew +RENAME TO users; + +UPDATE users SET role=2 where id is 1; + diff --git a/src/forms.rs b/src/forms.rs index 9a5a1cd..3410cde 100644 --- a/src/forms.rs +++ b/src/forms.rs @@ -1,5 +1,5 @@ use serde::Deserialize; -#[derive(Deserialize)] +#[derive(Deserialize, Debug)] pub(crate) struct LinkForm { pub title: String, pub target: String, diff --git a/src/main.rs b/src/main.rs index 58a490a..9430fca 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,7 @@ extern crate diesel; extern crate log; mod forms; pub mod models; +mod queries; pub mod schema; mod views; @@ -14,14 +15,16 @@ use diesel::prelude::*; use dotenv::dotenv; use models::NewUser; +use qrcode::types::QrError; use tera::Tera; #[derive(Debug)] pub enum ServerError { Argonautic, - Diesel, + Diesel(diesel::result::Error), Environment, Template(tera::Error), + Qr(QrError), User(String), } @@ -34,25 +37,26 @@ impl std::fmt::Display for ServerError { impl actix_web::error::ResponseError for ServerError { fn error_response(&self) -> HttpResponse { match self { - ServerError::Argonautic => { - HttpResponse::InternalServerError().json("Argonautica Error.") + Self::Argonautic => HttpResponse::InternalServerError().json("Argonautica Error."), + Self::Diesel(e) => { + HttpResponse::InternalServerError().json(format!("Diesel Error.{}", e)) } - ServerError::Diesel => HttpResponse::InternalServerError().json("Diesel Error."), - ServerError::Environment => { - HttpResponse::InternalServerError().json("Environment Error.") - } - ServerError::Template(e) => { + Self::Environment => HttpResponse::InternalServerError().json("Environment Error."), + Self::Template(e) => { HttpResponse::InternalServerError().json(format!("Template Error. {:?}", e)) } - ServerError::User(data) => HttpResponse::InternalServerError().json(data), + Self::Qr(e) => { + HttpResponse::InternalServerError().json(format!("Qr Code Error. {:?}", e)) + } + Self::User(data) => HttpResponse::InternalServerError().json(data), } } } impl From for ServerError { - fn from(e: std::env::VarError) -> ServerError { + fn from(e: std::env::VarError) -> Self { error!("Environment error {:?}", e); - ServerError::Environment + Self::Environment } } @@ -63,25 +67,28 @@ impl From for ServerError { } */ impl From for ServerError { - fn from(err: diesel::result::Error) -> ServerError { + fn from(err: diesel::result::Error) -> Self { error!("Database error {:?}", err); - match err { - diesel::result::Error::NotFound => ServerError::User("Username not found.".to_string()), - _ => ServerError::Diesel, - } + Self::Diesel(err) } } impl From for ServerError { - fn from(e: argonautica::Error) -> ServerError { + fn from(e: argonautica::Error) -> Self { error!("Authentication error {:?}", e); - ServerError::Argonautic + Self::Argonautic } } impl From for ServerError { - fn from(e: tera::Error) -> ServerError { + fn from(e: tera::Error) -> Self { error!("Template error {:?}", e); - ServerError::Template(e) + Self::Template(e) + } +} +impl From for ServerError { + fn from(e: QrError) -> Self { + error!("Template error {:?}", e); + Self::Qr(e) } } @@ -92,7 +99,7 @@ async fn main() -> std::io::Result<()> { dotenv().ok(); env_logger::init(); - let connection = views::establish_connection().expect("Failed to connect to database!"); + let connection = queries::establish_connection().expect("Failed to connect to database!"); let num_users: i64 = schema::users::dsl::users .select(diesel::dsl::count_star()) .first(&connection) @@ -191,7 +198,8 @@ async fn main() -> std::io::Result<()> { "/{user_id}", web::post().to(views::process_edit_profile), ), - ), + ) + .route("/set_admin/{user_id}", web::get().to(views::toggle_admin)), ) .service( web::scope("/delete").service( diff --git a/src/models.rs b/src/models.rs index 810a767..2291182 100644 --- a/src/models.rs +++ b/src/models.rs @@ -2,16 +2,17 @@ use crate::{forms::LinkForm, ServerError}; use super::schema::{clicks, links, users}; use argonautica::Hasher; -use diesel::{Insertable, Queryable}; +use diesel::{Identifiable, Insertable, Queryable}; use dotenv::dotenv; use serde::{Deserialize, Serialize}; -#[derive(Queryable, Serialize)] +#[derive(Identifiable, Queryable, PartialEq, Serialize, Clone, Debug)] pub struct User { pub id: i32, pub username: String, pub email: String, pub password: String, + pub role: i32, } #[derive(Debug, Deserialize, Insertable)] diff --git a/src/queries.rs b/src/queries.rs new file mode 100644 index 0000000..cc52e6a --- /dev/null +++ b/src/queries.rs @@ -0,0 +1,429 @@ +use actix_identity::Identity; +use actix_web::web; +use diesel::{prelude::*, sqlite::SqliteConnection}; +use dotenv::dotenv; +use serde::Serialize; + +use super::models::{Count, Link, NewUser, User}; +use crate::{ + forms::LinkForm, + models::{NewClick, NewLink}, + ServerError, +}; + +/// Create a connection to the database +pub(super) fn establish_connection() -> Result { + dotenv().ok(); + + let database_url = std::env::var("DATABASE_URL")?; + + match SqliteConnection::establish(&database_url) { + Ok(c) => Ok(c), + Err(e) => { + info!("Error connecting to database: {}, {}", database_url, e); + Err(ServerError::User( + "Error connecting to Database".to_string(), + )) + } + } +} + +/// The possible roles a user could have. +#[derive(Debug, Clone)] +pub enum Role { + NotAuthenticated, + Disabled, + Regular { user: User }, + Admin { user: User }, +} + +impl Role { + /// Determin if the user is admin or the given user id is his own. This is used for things where users can edit or view their own entries, whereas admins can do so for all entries. + fn admin_or_self(&self, id: i32) -> bool { + match self { + Self::Admin { .. } => true, + Self::Regular { user } => user.id == id, + Self::NotAuthenticated | Self::Disabled => false, + } + } +} + +/// queries the user matching the given [`actix_identity::Identity`] and determins its authentication and permission level. Returns a [`Role`] containing the user if it is authenticated. +pub fn authenticate(id: Identity) -> Result { + if let Some(username) = id.identity() { + use super::schema::users::dsl; + let connection = establish_connection()?; + + let user = dsl::users + .filter(dsl::username.eq(&username)) + .first::(&connection)?; + + return Ok(match user.role { + 0 => Role::Disabled, + 1 => Role::Regular { user }, + 2 => Role::Admin { user }, + _ => Role::NotAuthenticated, + }); + } + Ok(Role::NotAuthenticated) +} + +/// A generic list returntype containing the User and a Vec containing e.g. Links or Users +pub struct List { + pub user: User, + pub list: Vec, +} + +/// A link together with its author and its click-count. +#[derive(Serialize)] +pub struct FullLink { + link: Link, + user: User, + clicks: Count, +} + +/// Returns a List of `FullLink` meaning `Links` enriched by their author and statistics. This returns all links if the user is either Admin or Regular user. +pub(crate) fn list_all_allowed(id: Identity) -> Result, ServerError> { + use super::schema::clicks; + use super::schema::links; + use super::schema::users; + + // query to select all users could be const but typespecification is too complex. A filter can be added in the match below. + let query = links::dsl::links + .inner_join(users::dsl::users) + .left_join(clicks::dsl::clicks) + .group_by(links::id) + .select(( + ( + links::id, + links::title, + links::target, + links::code, + links::author, + links::created_at, + ), + ( + users::id, + users::username, + users::email, + users::password, + users::role, + ), + (diesel::dsl::sql::( + "COUNT(clicks.id)", + ),), + )); + match authenticate(id)? { + Role::Admin { user } | Role::Regular { user } => { + // show all links + let connection = establish_connection()?; + let all_links: Vec = query + .load(&connection)? + .into_iter() + .map(|l: (Link, User, Count)| FullLink { + link: l.0, + user: l.1, + clicks: l.2, + }) + .collect(); + Ok(List { + user, + list: all_links, + }) + } + Role::Disabled | Role::NotAuthenticated => Err(ServerError::User("Not allowed".to_owned())), + } +} + +/// Only admins can list all users +pub(crate) fn list_users(id: Identity) -> Result, ServerError> { + use super::schema::users::dsl::users; + match authenticate(id)? { + Role::Admin { user } => { + let connection = establish_connection()?; + let all_users: Vec = users.load(&connection)?; + Ok(List { + user, + list: all_users, + }) + } + _ => Err(ServerError::User( + "Administrator permissions required".to_owned(), + )), + } +} + +/// A generic returntype containing the User and a single item +pub struct Item { + pub user: User, + pub item: T, +} + +/// Get a user if permissions are accordingly +pub(crate) fn get_user(id: Identity, user_id: &str) -> Result, ServerError> { + use super::schema::users; + if let Ok(uid) = user_id.parse::() { + info!("Getting user {}", uid); + let auth = authenticate(id)?; + info!("{:?}", &auth); + if auth.admin_or_self(uid) { + match auth { + Role::Admin { user } | Role::Regular { user } => { + let connection = establish_connection()?; + let viewed_user = users::dsl::users + .filter(users::dsl::id.eq(&uid)) + .first::(&connection)?; + Ok(Item { + user, + item: viewed_user, + }) + } + Role::Disabled | Role::NotAuthenticated => { + unreachable!("should already be unreachable because of `admin_or_self`") + } + } + } else { + Err(ServerError::User("Permission Denied".to_owned())) + } + } else { + Err(ServerError::User("Permission Denied".to_owned())) + } +} + +/// Get a user **without permission checks** (needed for login) +pub(crate) fn get_user_by_name(username: &str) -> Result { + use super::schema::users; + + let connection = establish_connection()?; + let user = users::dsl::users + .filter(users::dsl::username.eq(username)) + .first::(&connection)?; + Ok(user) +} + +pub(crate) fn create_user( + id: Identity, + data: web::Form, +) -> Result, ServerError> { + info!("Creating a User: {:?}", &data); + let auth = authenticate(id)?; + match auth { + Role::Admin { user } => { + use super::schema::users; + + let connection = establish_connection()?; + let new_user = NewUser::new( + data.username.clone(), + data.email.clone(), + data.password.clone(), + )?; + + diesel::insert_into(users::table) + .values(&new_user) + .execute(&connection)?; + + let new_user = get_user_by_name(&data.username)?; + Ok(Item { + user, + item: new_user, + }) + } + Role::Regular { .. } | Role::Disabled | Role::NotAuthenticated => { + Err(ServerError::User("Permission denied!".to_owned())) + } + } +} +/// Take a [`actix_web::web::Form`] and update the corresponding entry in the database. +/// The password is only updated if a new password of at least 4 characters is provided. +/// The user_id is never changed. +pub(crate) fn update_user( + id: Identity, + user_id: &str, + data: web::Form, +) -> Result, ServerError> { + if let Ok(uid) = user_id.parse::() { + let auth = authenticate(id)?; + if auth.admin_or_self(uid) { + match auth { + Role::Admin { .. } | Role::Regular { .. } => { + use super::schema::users::dsl::{email, id, password, username, users}; + + info!("Updating userinfo: "); + let connection = establish_connection()?; + + // Update username and email - if they have not been changed their values will be replaced by the old ones. + diesel::update(users.filter(id.eq(&uid))) + .set(( + username.eq(data.username.clone()), + email.eq(data.email.clone()), + )) + .execute(&connection)?; + // Update the password only if the user entered something. + if data.password.len() > 3 { + let hash = NewUser::hash_password(data.password.clone())?; + diesel::update(users.filter(id.eq(&uid))) + .set((password.eq(hash),)) + .execute(&connection)?; + } + let changed_user = users.filter(id.eq(&uid)).first::(&connection)?; + Ok(Item { + user: changed_user.clone(), + item: changed_user, + }) + } + Role::NotAuthenticated | Role::Disabled => { + unreachable!("Should be unreachable because of the `admin_or_self`") + } + } + } else { + Err(ServerError::User("Not a valid UID".to_owned())) + } + } else { + Err(ServerError::User("Permission denied".to_owned())) + } +} + +pub(crate) fn toggle_admin(id: Identity, user_id: &str) -> Result, ServerError> { + if let Ok(uid) = user_id.parse::() { + let auth = authenticate(id)?; + match auth { + Role::Admin { .. } => { + use super::schema::users::dsl::{id, role, users}; + + info!("Changing administrator priviledges: "); + let connection = establish_connection()?; + + let unchanged_user = users.filter(id.eq(&uid)).first::(&connection)?; + + let new_role = 2 - (unchanged_user.role + 1) % 2; + info!( + "Assigning new role: {} - old was {}", + new_role, unchanged_user.role + ); + + // Update username and email - if they have not been changed their values will be replaced by the old ones. + diesel::update(users.filter(id.eq(&uid))) + .set((role.eq(new_role),)) + .execute(&connection)?; + + let changed_user = users.filter(id.eq(&uid)).first::(&connection)?; + Ok(Item { + user: changed_user.clone(), + item: changed_user, + }) + } + Role::Regular { .. } | Role::NotAuthenticated | Role::Disabled => { + Err(ServerError::User("Permission denied".to_owned())) + } + } + } else { + Err(ServerError::User("Permission denied".to_owned())) + } +} + +/// Get one link if permissions are accordingly. +pub(crate) fn get_link(id: Identity, link_code: &str) -> Result, ServerError> { + use super::schema::links::dsl::{code, links}; + match authenticate(id)? { + Role::Admin { user } | Role::Regular { user } => { + let connection = establish_connection()?; + let link: Link = links + .filter(code.eq(&link_code)) + .first::(&connection)?; + Ok(Item { user, item: link }) + } + Role::Disabled | Role::NotAuthenticated => Err(ServerError::User("Not Allowed".to_owned())), + } +} + +/// Get link **without authentication** +pub(crate) fn get_link_simple(link_code: &str) -> Result { + use super::schema::links::dsl::{code, links}; + info!("Getting link for {:?}", link_code); + let connection = establish_connection()?; + let link: Link = links + .filter(code.eq(&link_code)) + .first::(&connection)?; + Ok(link) +} +/// Click on a link +pub(crate) fn click_link(link_id: i32) -> Result<(), ServerError> { + use super::schema::clicks; + let new_click = NewClick::new(link_id); + let connection = establish_connection()?; + + diesel::insert_into(clicks::table) + .values(&new_click) + .execute(&connection)?; + Ok(()) +} + +/// Click on a link +pub(crate) fn delete_link(id: Identity, link_code: String) -> Result<(), ServerError> { + use super::schema::links::dsl::{code, links}; + let connection = establish_connection()?; + let auth = authenticate(id)?; + let link = get_link_simple(&link_code)?; + if auth.admin_or_self(link.author) { + diesel::delete(links.filter(code.eq(&link_code))).execute(&connection)?; + Ok(()) + } else { + Err(ServerError::User("Permission denied!".to_owned())) + } +} +/// Update a link if the user is admin or it is its own link. +pub(crate) fn update_link( + id: Identity, + link_code: &str, + data: web::Form, +) -> Result, ServerError> { + use super::schema::links::dsl::{code, links, target, title}; + info!("Changing link to: {:?} {:?}", &data, &link_code); + let auth = authenticate(id.clone())?; + match auth.clone() { + Role::Admin { .. } | Role::Regular { .. } => { + let query = get_link(id.clone(), &link_code)?; + if auth.admin_or_self(query.item.author) { + let connection = establish_connection()?; + diesel::update(links.filter(code.eq(&query.item.code))) + .set(( + code.eq(&data.code), + target.eq(&data.target), + title.eq(&data.title), + )) + .execute(&connection)?; + get_link(id, &data.code) + } else { + Err(ServerError::User("Not Allowed".to_owned())) + } + } + Role::Disabled | Role::NotAuthenticated => Err(ServerError::User("Not Allowed".to_owned())), + } +} + +pub(crate) fn create_link( + id: Identity, + data: web::Form, +) -> Result, ServerError> { + let auth = authenticate(id)?; + match auth { + Role::Admin { user } | Role::Regular { user } => { + use super::schema::links; + + let connection = establish_connection()?; + let new_link = NewLink::from_link_form(data.into_inner(), user.id); + + diesel::insert_into(links::table) + .values(&new_link) + .execute(&connection)?; + let new_link = get_link_simple(&new_link.code)?; + Ok(Item { + user, + item: new_link, + }) + } + Role::Disabled | Role::NotAuthenticated => { + Err(ServerError::User("Permission denied!".to_owned())) + } + } +} diff --git a/src/schema.rs b/src/schema.rs index 7f79273..eac00fb 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -23,6 +23,7 @@ table! { username -> Text, email -> Text, password -> Text, + role -> Integer, } } diff --git a/src/views.rs b/src/views.rs index 7b2e3a3..1fe329c 100644 --- a/src/views.rs +++ b/src/views.rs @@ -6,32 +6,16 @@ use actix_web::{ web, HttpResponse, }; use argonautica::Verifier; -use diesel::{prelude::*, result::Error::NotFound, sqlite::SqliteConnection}; use dotenv::dotenv; use image::{DynamicImage, ImageOutputFormat, Luma}; use qrcode::{render::svg, QrCode}; use tera::{Context, Tera}; use super::forms::LinkForm; -use super::models::{Count, Link, LoginUser, NewClick, NewLink, NewUser, User}; +use super::models::{LoginUser, NewUser}; +use crate::queries; use crate::ServerError; -pub(super) fn establish_connection() -> Result { - dotenv().ok(); - - let database_url = std::env::var("DATABASE_URL")?; - - match SqliteConnection::establish(&database_url) { - Ok(c) => Ok(c), - Err(e) => { - info!("Error connecting to database: {}, {}", database_url, e); - Err(ServerError::User( - "Error connecting to Database".to_string(), - )) - } - } -} - fn redirect_builder(target: &str) -> HttpResponse { HttpResponse::SeeOther() .set(CacheControl(vec![ @@ -49,36 +33,11 @@ pub(crate) async fn index( tera: web::Data, id: Identity, ) -> Result { - use super::schema::clicks; - use super::schema::links; - use super::schema::users; - if let Some(id) = id.identity() { - let connection = establish_connection()?; - let query = links::dsl::links - .inner_join(users::dsl::users) - .left_join(clicks::dsl::clicks) - .group_by(links::id) - .select(( - ( - links::id, - links::title, - links::target, - links::code, - links::author, - links::created_at, - ), - (users::id, users::username, users::email, users::password), - (diesel::dsl::sql::( - "COUNT(clicks.id)", - ),), - )); - let all_links: Vec<(Link, User, Count)> = query.load(&connection)?; - + if let Ok(links) = queries::list_all_allowed(id) { let mut data = Context::new(); - data.insert("name", &id); + data.insert("user", &links.user); data.insert("title", "Links der Freien Hochschule Stuttgart"); - data.insert("links_per_users", &all_links); - + data.insert("links_per_users", &links.list); let rendered = tera.render("index.html", &data)?; Ok(HttpResponse::Ok().body(rendered)) } else { @@ -91,20 +50,16 @@ pub(crate) async fn index_users( tera: web::Data, id: Identity, ) -> Result { - use super::schema::users::dsl::users; - if let Some(id) = id.identity() { - let connection = establish_connection()?; - let all_users: Vec = users.load(&connection)?; - + if let Ok(users) = queries::list_users(id) { let mut data = Context::new(); - data.insert("name", &id); + data.insert("user", &users.user); data.insert("title", "Benutzer der Freien Hochschule Stuttgart"); - data.insert("users", &all_users); + data.insert("users", &users.list); let rendered = tera.render("index_users.html", &data)?; Ok(HttpResponse::Ok().body(rendered)) } else { - Ok(redirect_builder("/admin/login/")) + Ok(redirect_builder("/admin/login")) } } pub(crate) async fn view_link_fhs( @@ -119,18 +74,15 @@ pub(crate) async fn view_link( id: Identity, link_id: web::Path, ) -> Result { - use super::schema::links::dsl::{code, links}; - if let Some(id) = id.identity() { - let connection = establish_connection()?; - let link: Link = links - .filter(code.eq(&link_id.0)) - .first::(&connection)?; - + if let Ok(link) = queries::get_link(id, &link_id.0) { + dotenv().ok(); + let host = std::env::var("SLINK_HOST")?; + let protocol = std::env::var("SLINK_PROTOCOL")?; let qr = QrCode::with_error_correction_level( - &format!("http://fhs.li/{}", &link_id), + &format!("http://{}/{}", &host, &link.item.id), qrcode::EcLevel::L, - ) - .unwrap(); + )?; + let svg = qr .render() .min_dimensions(200, 200) @@ -139,13 +91,15 @@ pub(crate) async fn view_link( .build(); let mut data = Context::new(); - data.insert("name", &id); + data.insert("user", &link.user); data.insert( "title", - &format!("Links {} der Freien Hochschule Stuttgart", link_id.0), + &format!("Links {} der Freien Hochschule Stuttgart", &link.item.id), ); - data.insert("link", &link); + data.insert("link", &link.item); data.insert("qr", &svg); + data.insert("host", &host); + data.insert("protocol", &protocol); let rendered = tera.render("view_link.html", &data)?; Ok(HttpResponse::Ok().body(rendered)) @@ -156,65 +110,50 @@ pub(crate) async fn view_link( pub(crate) async fn view_profile( tera: web::Data, - identity: Identity, + id: Identity, user_id: web::Path, ) -> Result { - use super::schema::users::dsl::{id, users}; info!("Viewing Profile!"); - if let Some(identity) = identity.identity() { - let connection = establish_connection()?; - if let Ok(uid) = user_id.parse::() { - let user = users.filter(id.eq(&uid)).first::(&connection)?; + if let Ok(query) = queries::get_user(id, &user_id.0) { + let mut data = Context::new(); + data.insert("user", &query.user); + data.insert( + "title", + &format!( + "Benutzer {} der Freien Hochschule Stuttgart", + &query.item.username + ), + ); + data.insert("viewed_user", &query.item); - let mut data = Context::new(); - data.insert("name", &identity); - data.insert( - "title", - &format!( - "Benutzer {} der Freien Hochschule Stuttgart", - &user.username - ), - ); - data.insert("user", &user); - - let rendered = tera.render("view_profile.html", &data)?; - Ok(HttpResponse::Ok().body(rendered)) - } else { - Ok(redirect_builder("/admin/index/")) - } + let rendered = tera.render("view_profile.html", &data)?; + Ok(HttpResponse::Ok().body(rendered)) } else { + // Parsing error -- do something else Ok(redirect_builder("/admin/login/")) } } pub(crate) async fn edit_profile( tera: web::Data, - identity: Identity, + id: Identity, user_id: web::Path, ) -> Result { - use super::schema::users::dsl::{id, users}; info!("Editing Profile!"); - if let Some(identity) = identity.identity() { - let connection = establish_connection()?; - if let Ok(uid) = user_id.parse::() { - let user = users.filter(id.eq(&uid)).first::(&connection)?; + if let Ok(query) = queries::get_user(id, &user_id.0) { + let mut data = Context::new(); + data.insert("user", &query.user); + data.insert( + "title", + &format!( + "Benutzer {} der Freien Hochschule Stuttgart", + &query.user.username + ), + ); + data.insert("user", &query.user); - let mut data = Context::new(); - data.insert("name", &identity); - data.insert( - "title", - &format!( - "Benutzer {} der Freien Hochschule Stuttgart", - &user.username - ), - ); - data.insert("user", &user); - - let rendered = tera.render("edit_profile.html", &data)?; - Ok(HttpResponse::Ok().body(rendered)) - } else { - Ok(redirect_builder("/admin/index/")) - } + let rendered = tera.render("edit_profile.html", &data)?; + Ok(HttpResponse::Ok().body(rendered)) } else { Ok(redirect_builder("/admin/login/")) } @@ -225,43 +164,29 @@ pub(crate) async fn process_edit_profile( id: Identity, user_id: web::Path, ) -> Result { - if let Some(_id) = id.identity() { - use super::schema::users::dsl::{email, id, password, username, users}; - - if let Ok(uid) = user_id.parse::() { - info!("Updating userinfo: "); - let connection = establish_connection()?; - diesel::update(users.filter(id.eq(uid))) - .set(( - username.eq(data.username.clone()), - email.eq(data.email.clone()), - )) - .execute(&connection)?; - if data.password.len() > 3 { - let hash = NewUser::hash_password(data.password.clone())?; - diesel::update(users.filter(id.eq(uid))) - .set((password.eq(hash),)) - .execute(&connection)?; - } - Ok(HttpResponse::Ok().body(format!("Successfully saved user: {}", data.username))) - } else { - Ok(redirect_builder("/admin/index/")) - } + if let Ok(query) = queries::update_user(id, &user_id.0, data) { + Ok(redirect_builder(&format!( + "admin/view/profile/{}", + query.user.username + ))) } else { - Ok(redirect_builder("/admin/login/")) + Ok(redirect_builder("/admin/index/")) } } pub(crate) async fn download_png( id: Identity, - link_id: web::Path, + link_code: web::Path, ) -> Result { - if let Some(_id) = id.identity() { - use super::schema::links::dsl::{code, links}; - let connection = establish_connection()?; - if let Ok(_link) = links.filter(code.eq(&link_id.0)).first::(&connection) { + match queries::get_link(id, &link_code.0) { + Ok(query) => { + dotenv().ok(); let qr = QrCode::with_error_correction_level( - &format!("http://fhs.li/{}", &link_id), + &format!( + "http://{}/{}", + std::env::var("SLINK_HOST")?, + &query.item.code + ), qrcode::EcLevel::L, ) .unwrap(); @@ -272,11 +197,8 @@ pub(crate) async fn download_png( .unwrap(); let image_data = temporary_data.into_inner(); Ok(HttpResponse::Ok().set(ContentType::png()).body(image_data)) - } else { - Ok(redirect_builder("/admin/index/")) } - } else { - Ok(redirect_builder("/admin/login/")) + Err(e) => Err(e), } } @@ -284,15 +206,18 @@ pub(crate) async fn signup( tera: web::Data, id: Identity, ) -> Result { - if let Some(id) = id.identity() { - let mut data = Context::new(); - data.insert("title", "Sign Up"); - data.insert("name", &id); + match queries::authenticate(id)? { + queries::Role::Admin { user } => { + let mut data = Context::new(); + data.insert("title", "Ein Benutzerkonto erstellen"); + data.insert("user", &user); - let rendered = tera.render("signup.html", &data)?; - Ok(HttpResponse::Ok().body(rendered)) - } else { - Ok(redirect_builder("/admin/login/")) + let rendered = tera.render("signup.html", &data)?; + Ok(HttpResponse::Ok().body(rendered)) + } + queries::Role::Regular { .. } + | queries::Role::NotAuthenticated + | queries::Role::Disabled => Ok(redirect_builder("/admin/login/")), } } @@ -300,26 +225,25 @@ pub(crate) async fn process_signup( data: web::Form, id: Identity, ) -> Result { - if let Some(_id) = id.identity() { - use super::schema::users; - - let connection = establish_connection()?; - let new_user = NewUser::new( - data.username.clone(), - data.email.clone(), - data.password.clone(), - )?; - - diesel::insert_into(users::table) - .values(&new_user) - .execute(&connection)?; - - Ok(HttpResponse::Ok().body(format!("Successfully saved user: {}", data.username))) + info!("Creating a User: {:?}", &data); + if let Ok(item) = queries::create_user(id, data) { + Ok(HttpResponse::Ok().body(format!("Successfully saved user: {}", item.item.username))) } else { Ok(redirect_builder("/admin/login/")) } } +pub(crate) async fn toggle_admin( + data: web::Path, + id: Identity, +) -> Result { + let update = queries::toggle_admin(id, &data.0)?; + Ok(redirect_builder(&format!( + "/admin/view/profile/{}", + update.item.id + ))) +} + pub(crate) async fn login( tera: web::Data, id: Identity, @@ -339,12 +263,7 @@ pub(crate) async fn process_login( data: web::Form, id: Identity, ) -> Result { - use super::schema::users::dsl::{username, users}; - - let connection = establish_connection()?; - let user = users - .filter(username.eq(&data.username)) - .first::(&connection); + let user = queries::get_user_by_name(&data.username); match user { Ok(u) => { @@ -381,22 +300,22 @@ pub(crate) async fn redirect( tera: web::Data, data: web::Path, ) -> Result { - use super::schema::links::dsl::{code, links}; - let connection = establish_connection()?; - - let link = links.filter(code.eq(&data.0)).first::(&connection); + info!("Redirecting to {:?}", data); + let link = queries::get_link_simple(&data.0); + info!("link: {:?}", link); match link { Ok(link) => { - use super::schema::clicks; - let new_click = NewClick::new(link.id); - let connection = establish_connection()?; - - diesel::insert_into(clicks::table) - .values(&new_click) - .execute(&connection)?; + queries::click_link(link.id)?; Ok(redirect_builder(&link.target)) } - Err(NotFound) => { + Err(ServerError::Diesel(e)) => { + dotenv().ok(); + info!( + "Link was not found: http://{}/{} \n {}", + std::env::var("SLINK_HOST")?, + &data.0, + e + ); let mut data = Context::new(); data.insert("title", "Wurde gelöscht"); let rendered = tera.render("not_found.html", &data)?; @@ -416,44 +335,30 @@ pub(crate) async fn create_link( tera: web::Data, id: Identity, ) -> Result { - if let Some(id) = id.identity() { - let mut data = Context::new(); - data.insert("title", "Submit a Post"); + match queries::authenticate(id)? { + queries::Role::Admin { user } | queries::Role::Regular { user } => { + let mut data = Context::new(); + data.insert("title", "Einen Kurzlink erstellen"); - data.insert("name", &id); - let rendered = tera.render("submission.html", &data)?; - return Ok(HttpResponse::Ok().body(rendered)); + data.insert("user", &user); + let rendered = tera.render("submission.html", &data)?; + Ok(HttpResponse::Ok().body(rendered)) + } + queries::Role::NotAuthenticated | queries::Role::Disabled => { + Ok(redirect_builder("/admin/login/")) + } } - Ok(redirect_builder("/admin/login/")) } pub(crate) async fn process_link_creation( data: web::Form, id: Identity, ) -> Result { - if let Some(id) = id.identity() { - use super::schema::users::dsl::{username, users}; - - let connection = establish_connection()?; - let user: Result = - users.filter(username.eq(id)).first(&connection); - - match user { - Ok(u) => { - use super::schema::links; - let new_post = NewLink::from_link_form(data.into_inner(), u.id); - - diesel::insert_into(links::table) - .values(&new_post) - .execute(&connection)?; - - return Ok(redirect_builder("/admin/index/")); - } - Err(_e) => Ok(redirect_builder("/admin/login/")), - } - } else { - Ok(redirect_builder("/admin/login/")) - } + let new_link = queries::create_link(id, data)?; + Ok(redirect_builder(&format!( + "/admin/view/link/{}", + new_link.item.code + ))) } pub(crate) async fn edit_link( @@ -461,17 +366,12 @@ pub(crate) async fn edit_link( id: Identity, link_id: web::Path, ) -> Result { - if let Some(id) = id.identity() { - use super::schema::links::dsl::{code, links}; - let connection = establish_connection()?; - let link: Link = links - .filter(code.eq(&link_id.0)) - .first::(&connection)?; + if let Ok(query) = queries::get_link(id, &link_id.0) { let mut data = Context::new(); data.insert("title", "Submit a Post"); - data.insert("link", &link); + data.insert("link", &query.item); - data.insert("name", &id); + data.insert("user", &query.user); let rendered = tera.render("edit_link.html", &data)?; return Ok(HttpResponse::Ok().body(rendered)); } @@ -480,38 +380,21 @@ pub(crate) async fn edit_link( pub(crate) async fn process_link_edit( data: web::Form, id: Identity, - link_id: web::Path, + link_code: web::Path, ) -> Result { - if let Some(_id) = id.identity() { - use super::schema::links::dsl::{code, links, target, title}; - - let connection = establish_connection()?; - - diesel::update(links.filter(code.eq(&link_id.0))) - .set(( - code.eq(&data.code), - target.eq(&data.target), - title.eq(&data.title), - )) - .execute(&connection)?; - - return Ok(redirect_builder(&format!( + match queries::update_link(id, &link_code.0, data) { + Ok(query) => Ok(redirect_builder(&format!( "/admin/view/link/{}", - &data.code - ))); + &query.item.code + ))), + Err(e) => Err(e), } - Ok(redirect_builder("/admin/login/")) } pub(crate) async fn process_link_delete( id: Identity, - link_id: web::Path, + link_code: web::Path, ) -> Result { - if let Some(_id) = id.identity() { - use super::schema::links::dsl::{code, links}; - let connection = establish_connection()?; - diesel::delete(links.filter(code.eq(&link_id.0))).execute(&connection)?; - return Ok(redirect_builder("/admin/index/")); - } + queries::delete_link(id, link_code.0)?; Ok(redirect_builder("/admin/login/")) } diff --git a/static/admin.css b/static/admin.css index 579d1f3..f488aa8 100644 --- a/static/admin.css +++ b/static/admin.css @@ -87,9 +87,10 @@ div.danger h3 { } a.button, div.actions input { - width: 150px; + width: 250px; display:block; padding: 15px; + margin-left: 15px; text-align: center; border-radius: 1px; border: 1px solid rgb(90, 90, 90); diff --git a/templates/admin.html b/templates/admin.html index b0b0464..649b3cd 100644 --- a/templates/admin.html +++ b/templates/admin.html @@ -11,13 +11,13 @@
diff --git a/templates/index.html b/templates/index.html index ac34a92..8350839 100644 --- a/templates/index.html +++ b/templates/index.html @@ -24,9 +24,9 @@ {% for links_user in links_per_users %} - {% set l = links_user[0] %} - {% set u = links_user[1] %} - {% set c = links_user[2] %} + {% set l = links_user.link %} + {% set u = links_user.user %} + {% set c = links_user.clicks %} {{l.code}} @@ -37,9 +37,12 @@ - + {% if user.role == 2 or user.id == u.id %} {{ u.username }} + {% else %} + {{ u.username }} + {% endif %} {{ c.count }} diff --git a/templates/view_link.html b/templates/view_link.html index 69d83a7..80fef2d 100644 --- a/templates/view_link.html +++ b/templates/view_link.html @@ -14,7 +14,8 @@ Kurzlink: - https://fhs.li/{{ link.code }} + {{ protocol }}://{{ host }}/{{ link.code }} + Ziel: @@ -30,8 +31,10 @@ + {% if user.role == 2 or user.id == link.author %} + {% endif %}
{% endblock %} \ No newline at end of file diff --git a/templates/view_profile.html b/templates/view_profile.html index 3b219db..22de951 100644 --- a/templates/view_profile.html +++ b/templates/view_profile.html @@ -2,24 +2,34 @@ {% block admin %}
-

Profil von {{user.username}}

+

Profil von {{viewed_user.username}}

- +
- +
+ {% if user.role == 2 or user.id == viewed_user.id %}
+ {% endif %}
+ {% if user.role == 2 or user.id == viewed_user.id %}
- Editieren + Editieren + {% if user.role == 2 and viewed_user.role == 1 %} + Zum Admin machen + {% endif %} + {% if user.role == 2 and viewed_user.role == 2 %} + Zum Normalo machen + {% endif %}
+ {% endif %}

 

{% endblock %} \ No newline at end of file