Add permissions to the page

This commit is contained in:
Dietrich 2021-02-14 22:28:34 +01:00
parent 72be8e693a
commit 0553aaa935
Signed by: dietrich
GPG Key ID: 9F3C20C0F85DF67C
14 changed files with 671 additions and 297 deletions

3
.gitignore vendored
View File

@ -1,3 +1,6 @@
/target /target
.env .env
links.db links.db
launch.json
settings.json
links.session.sql

View File

@ -1,3 +1,20 @@
-- This file should undo anything in `up.sql` -- This file should undo anything in `up.sql`
DROP TABLE user_roles; 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;

View File

@ -1,12 +1,27 @@
-- Your SQL goes here -- 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, id INTEGER PRIMARY KEY NOT NULL,
name TEXT NOT NULL, username VARCHAR NOT NULL,
description TEXT NOT NULL, email VARCHAR NOT NULL,
password VARCHAR NOT NULL,
role INTEGER DEFAULT 1 NOT NULL,
UNIQUE(username, email),
FOREIGN KEY FOREIGN KEY
(link) (role) REFERENCES user_roles
REFERENCES users (id)
(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;

View File

@ -1,5 +1,5 @@
use serde::Deserialize; use serde::Deserialize;
#[derive(Deserialize)] #[derive(Deserialize, Debug)]
pub(crate) struct LinkForm { pub(crate) struct LinkForm {
pub title: String, pub title: String,
pub target: String, pub target: String,

View File

@ -4,6 +4,7 @@ extern crate diesel;
extern crate log; extern crate log;
mod forms; mod forms;
pub mod models; pub mod models;
mod queries;
pub mod schema; pub mod schema;
mod views; mod views;
@ -14,14 +15,16 @@ use diesel::prelude::*;
use dotenv::dotenv; use dotenv::dotenv;
use models::NewUser; use models::NewUser;
use qrcode::types::QrError;
use tera::Tera; use tera::Tera;
#[derive(Debug)] #[derive(Debug)]
pub enum ServerError { pub enum ServerError {
Argonautic, Argonautic,
Diesel, Diesel(diesel::result::Error),
Environment, Environment,
Template(tera::Error), Template(tera::Error),
Qr(QrError),
User(String), User(String),
} }
@ -34,25 +37,26 @@ impl std::fmt::Display for ServerError {
impl actix_web::error::ResponseError for ServerError { impl actix_web::error::ResponseError for ServerError {
fn error_response(&self) -> HttpResponse { fn error_response(&self) -> HttpResponse {
match self { match self {
ServerError::Argonautic => { Self::Argonautic => HttpResponse::InternalServerError().json("Argonautica Error."),
HttpResponse::InternalServerError().json("Argonautica Error.") Self::Diesel(e) => {
HttpResponse::InternalServerError().json(format!("Diesel Error.{}", e))
} }
ServerError::Diesel => HttpResponse::InternalServerError().json("Diesel Error."), Self::Environment => HttpResponse::InternalServerError().json("Environment Error."),
ServerError::Environment => { Self::Template(e) => {
HttpResponse::InternalServerError().json("Environment Error.")
}
ServerError::Template(e) => {
HttpResponse::InternalServerError().json(format!("Template Error. {:?}", 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<std::env::VarError> for ServerError { impl From<std::env::VarError> for ServerError {
fn from(e: std::env::VarError) -> ServerError { fn from(e: std::env::VarError) -> Self {
error!("Environment error {:?}", e); error!("Environment error {:?}", e);
ServerError::Environment Self::Environment
} }
} }
@ -63,25 +67,28 @@ impl From<std::env::VarError> for ServerError {
} */ } */
impl From<diesel::result::Error> for ServerError { impl From<diesel::result::Error> for ServerError {
fn from(err: diesel::result::Error) -> ServerError { fn from(err: diesel::result::Error) -> Self {
error!("Database error {:?}", err); error!("Database error {:?}", err);
match err { Self::Diesel(err)
diesel::result::Error::NotFound => ServerError::User("Username not found.".to_string()),
_ => ServerError::Diesel,
}
} }
} }
impl From<argonautica::Error> for ServerError { impl From<argonautica::Error> for ServerError {
fn from(e: argonautica::Error) -> ServerError { fn from(e: argonautica::Error) -> Self {
error!("Authentication error {:?}", e); error!("Authentication error {:?}", e);
ServerError::Argonautic Self::Argonautic
} }
} }
impl From<tera::Error> for ServerError { impl From<tera::Error> for ServerError {
fn from(e: tera::Error) -> ServerError { fn from(e: tera::Error) -> Self {
error!("Template error {:?}", e); error!("Template error {:?}", e);
ServerError::Template(e) Self::Template(e)
}
}
impl From<QrError> 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(); dotenv().ok();
env_logger::init(); 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 let num_users: i64 = schema::users::dsl::users
.select(diesel::dsl::count_star()) .select(diesel::dsl::count_star())
.first(&connection) .first(&connection)
@ -191,7 +198,8 @@ async fn main() -> std::io::Result<()> {
"/{user_id}", "/{user_id}",
web::post().to(views::process_edit_profile), web::post().to(views::process_edit_profile),
), ),
), )
.route("/set_admin/{user_id}", web::get().to(views::toggle_admin)),
) )
.service( .service(
web::scope("/delete").service( web::scope("/delete").service(

View File

@ -2,16 +2,17 @@ use crate::{forms::LinkForm, ServerError};
use super::schema::{clicks, links, users}; use super::schema::{clicks, links, users};
use argonautica::Hasher; use argonautica::Hasher;
use diesel::{Insertable, Queryable}; use diesel::{Identifiable, Insertable, Queryable};
use dotenv::dotenv; use dotenv::dotenv;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Queryable, Serialize)] #[derive(Identifiable, Queryable, PartialEq, Serialize, Clone, Debug)]
pub struct User { pub struct User {
pub id: i32, pub id: i32,
pub username: String, pub username: String,
pub email: String, pub email: String,
pub password: String, pub password: String,
pub role: i32,
} }
#[derive(Debug, Deserialize, Insertable)] #[derive(Debug, Deserialize, Insertable)]

429
src/queries.rs Normal file
View File

@ -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<SqliteConnection, ServerError> {
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<Role, ServerError> {
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::<User>(&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<T> {
pub user: User,
pub list: Vec<T>,
}
/// 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<List<FullLink>, 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::<diesel::sql_types::Integer>(
"COUNT(clicks.id)",
),),
));
match authenticate(id)? {
Role::Admin { user } | Role::Regular { user } => {
// show all links
let connection = establish_connection()?;
let all_links: Vec<FullLink> = 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<List<User>, ServerError> {
use super::schema::users::dsl::users;
match authenticate(id)? {
Role::Admin { user } => {
let connection = establish_connection()?;
let all_users: Vec<User> = 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<T> {
pub user: User,
pub item: T,
}
/// Get a user if permissions are accordingly
pub(crate) fn get_user(id: Identity, user_id: &str) -> Result<Item<User>, ServerError> {
use super::schema::users;
if let Ok(uid) = user_id.parse::<i32>() {
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::<User>(&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<User, ServerError> {
use super::schema::users;
let connection = establish_connection()?;
let user = users::dsl::users
.filter(users::dsl::username.eq(username))
.first::<User>(&connection)?;
Ok(user)
}
pub(crate) fn create_user(
id: Identity,
data: web::Form<NewUser>,
) -> Result<Item<User>, 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<NewUser>`] 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<NewUser>,
) -> Result<Item<User>, ServerError> {
if let Ok(uid) = user_id.parse::<i32>() {
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::<User>(&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<Item<User>, ServerError> {
if let Ok(uid) = user_id.parse::<i32>() {
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::<User>(&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::<User>(&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<Item<Link>, 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::<Link>(&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<Link, ServerError> {
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::<Link>(&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<LinkForm>,
) -> Result<Item<Link>, 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<LinkForm>,
) -> Result<Item<Link>, 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()))
}
}
}

View File

@ -23,6 +23,7 @@ table! {
username -> Text, username -> Text,
email -> Text, email -> Text,
password -> Text, password -> Text,
role -> Integer,
} }
} }

View File

@ -6,32 +6,16 @@ use actix_web::{
web, HttpResponse, web, HttpResponse,
}; };
use argonautica::Verifier; use argonautica::Verifier;
use diesel::{prelude::*, result::Error::NotFound, sqlite::SqliteConnection};
use dotenv::dotenv; use dotenv::dotenv;
use image::{DynamicImage, ImageOutputFormat, Luma}; use image::{DynamicImage, ImageOutputFormat, Luma};
use qrcode::{render::svg, QrCode}; use qrcode::{render::svg, QrCode};
use tera::{Context, Tera}; use tera::{Context, Tera};
use super::forms::LinkForm; 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; use crate::ServerError;
pub(super) fn establish_connection() -> Result<SqliteConnection, ServerError> {
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 { fn redirect_builder(target: &str) -> HttpResponse {
HttpResponse::SeeOther() HttpResponse::SeeOther()
.set(CacheControl(vec![ .set(CacheControl(vec![
@ -49,36 +33,11 @@ pub(crate) async fn index(
tera: web::Data<Tera>, tera: web::Data<Tera>,
id: Identity, id: Identity,
) -> Result<HttpResponse, ServerError> { ) -> Result<HttpResponse, ServerError> {
use super::schema::clicks; if let Ok(links) = queries::list_all_allowed(id) {
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::<diesel::sql_types::Integer>(
"COUNT(clicks.id)",
),),
));
let all_links: Vec<(Link, User, Count)> = query.load(&connection)?;
let mut data = Context::new(); let mut data = Context::new();
data.insert("name", &id); data.insert("user", &links.user);
data.insert("title", "Links der Freien Hochschule Stuttgart"); 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)?; let rendered = tera.render("index.html", &data)?;
Ok(HttpResponse::Ok().body(rendered)) Ok(HttpResponse::Ok().body(rendered))
} else { } else {
@ -91,20 +50,16 @@ pub(crate) async fn index_users(
tera: web::Data<Tera>, tera: web::Data<Tera>,
id: Identity, id: Identity,
) -> Result<HttpResponse, ServerError> { ) -> Result<HttpResponse, ServerError> {
use super::schema::users::dsl::users; if let Ok(users) = queries::list_users(id) {
if let Some(id) = id.identity() {
let connection = establish_connection()?;
let all_users: Vec<User> = users.load(&connection)?;
let mut data = Context::new(); let mut data = Context::new();
data.insert("name", &id); data.insert("user", &users.user);
data.insert("title", "Benutzer der Freien Hochschule Stuttgart"); 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)?; let rendered = tera.render("index_users.html", &data)?;
Ok(HttpResponse::Ok().body(rendered)) Ok(HttpResponse::Ok().body(rendered))
} else { } else {
Ok(redirect_builder("/admin/login/")) Ok(redirect_builder("/admin/login"))
} }
} }
pub(crate) async fn view_link_fhs( pub(crate) async fn view_link_fhs(
@ -119,18 +74,15 @@ pub(crate) async fn view_link(
id: Identity, id: Identity,
link_id: web::Path<String>, link_id: web::Path<String>,
) -> Result<HttpResponse, ServerError> { ) -> Result<HttpResponse, ServerError> {
use super::schema::links::dsl::{code, links}; if let Ok(link) = queries::get_link(id, &link_id.0) {
if let Some(id) = id.identity() { dotenv().ok();
let connection = establish_connection()?; let host = std::env::var("SLINK_HOST")?;
let link: Link = links let protocol = std::env::var("SLINK_PROTOCOL")?;
.filter(code.eq(&link_id.0))
.first::<Link>(&connection)?;
let qr = QrCode::with_error_correction_level( let qr = QrCode::with_error_correction_level(
&format!("http://fhs.li/{}", &link_id), &format!("http://{}/{}", &host, &link.item.id),
qrcode::EcLevel::L, qrcode::EcLevel::L,
) )?;
.unwrap();
let svg = qr let svg = qr
.render() .render()
.min_dimensions(200, 200) .min_dimensions(200, 200)
@ -139,13 +91,15 @@ pub(crate) async fn view_link(
.build(); .build();
let mut data = Context::new(); let mut data = Context::new();
data.insert("name", &id); data.insert("user", &link.user);
data.insert( data.insert(
"title", "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("qr", &svg);
data.insert("host", &host);
data.insert("protocol", &protocol);
let rendered = tera.render("view_link.html", &data)?; let rendered = tera.render("view_link.html", &data)?;
Ok(HttpResponse::Ok().body(rendered)) Ok(HttpResponse::Ok().body(rendered))
@ -156,65 +110,50 @@ pub(crate) async fn view_link(
pub(crate) async fn view_profile( pub(crate) async fn view_profile(
tera: web::Data<Tera>, tera: web::Data<Tera>,
identity: Identity, id: Identity,
user_id: web::Path<String>, user_id: web::Path<String>,
) -> Result<HttpResponse, ServerError> { ) -> Result<HttpResponse, ServerError> {
use super::schema::users::dsl::{id, users};
info!("Viewing Profile!"); info!("Viewing Profile!");
if let Some(identity) = identity.identity() { if let Ok(query) = queries::get_user(id, &user_id.0) {
let connection = establish_connection()?; let mut data = Context::new();
if let Ok(uid) = user_id.parse::<i32>() { data.insert("user", &query.user);
let user = users.filter(id.eq(&uid)).first::<User>(&connection)?; data.insert(
"title",
&format!(
"Benutzer {} der Freien Hochschule Stuttgart",
&query.item.username
),
);
data.insert("viewed_user", &query.item);
let mut data = Context::new(); let rendered = tera.render("view_profile.html", &data)?;
data.insert("name", &identity); Ok(HttpResponse::Ok().body(rendered))
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/"))
}
} else { } else {
// Parsing error -- do something else
Ok(redirect_builder("/admin/login/")) Ok(redirect_builder("/admin/login/"))
} }
} }
pub(crate) async fn edit_profile( pub(crate) async fn edit_profile(
tera: web::Data<Tera>, tera: web::Data<Tera>,
identity: Identity, id: Identity,
user_id: web::Path<String>, user_id: web::Path<String>,
) -> Result<HttpResponse, ServerError> { ) -> Result<HttpResponse, ServerError> {
use super::schema::users::dsl::{id, users};
info!("Editing Profile!"); info!("Editing Profile!");
if let Some(identity) = identity.identity() { if let Ok(query) = queries::get_user(id, &user_id.0) {
let connection = establish_connection()?; let mut data = Context::new();
if let Ok(uid) = user_id.parse::<i32>() { data.insert("user", &query.user);
let user = users.filter(id.eq(&uid)).first::<User>(&connection)?; data.insert(
"title",
&format!(
"Benutzer {} der Freien Hochschule Stuttgart",
&query.user.username
),
);
data.insert("user", &query.user);
let mut data = Context::new(); let rendered = tera.render("edit_profile.html", &data)?;
data.insert("name", &identity); Ok(HttpResponse::Ok().body(rendered))
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/"))
}
} else { } else {
Ok(redirect_builder("/admin/login/")) Ok(redirect_builder("/admin/login/"))
} }
@ -225,43 +164,29 @@ pub(crate) async fn process_edit_profile(
id: Identity, id: Identity,
user_id: web::Path<String>, user_id: web::Path<String>,
) -> Result<HttpResponse, ServerError> { ) -> Result<HttpResponse, ServerError> {
if let Some(_id) = id.identity() { if let Ok(query) = queries::update_user(id, &user_id.0, data) {
use super::schema::users::dsl::{email, id, password, username, users}; Ok(redirect_builder(&format!(
"admin/view/profile/{}",
if let Ok(uid) = user_id.parse::<i32>() { query.user.username
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/"))
}
} else { } else {
Ok(redirect_builder("/admin/login/")) Ok(redirect_builder("/admin/index/"))
} }
} }
pub(crate) async fn download_png( pub(crate) async fn download_png(
id: Identity, id: Identity,
link_id: web::Path<String>, link_code: web::Path<String>,
) -> Result<HttpResponse, ServerError> { ) -> Result<HttpResponse, ServerError> {
if let Some(_id) = id.identity() { match queries::get_link(id, &link_code.0) {
use super::schema::links::dsl::{code, links}; Ok(query) => {
let connection = establish_connection()?; dotenv().ok();
if let Ok(_link) = links.filter(code.eq(&link_id.0)).first::<Link>(&connection) {
let qr = QrCode::with_error_correction_level( 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, qrcode::EcLevel::L,
) )
.unwrap(); .unwrap();
@ -272,11 +197,8 @@ pub(crate) async fn download_png(
.unwrap(); .unwrap();
let image_data = temporary_data.into_inner(); let image_data = temporary_data.into_inner();
Ok(HttpResponse::Ok().set(ContentType::png()).body(image_data)) Ok(HttpResponse::Ok().set(ContentType::png()).body(image_data))
} else {
Ok(redirect_builder("/admin/index/"))
} }
} else { Err(e) => Err(e),
Ok(redirect_builder("/admin/login/"))
} }
} }
@ -284,15 +206,18 @@ pub(crate) async fn signup(
tera: web::Data<Tera>, tera: web::Data<Tera>,
id: Identity, id: Identity,
) -> Result<HttpResponse, ServerError> { ) -> Result<HttpResponse, ServerError> {
if let Some(id) = id.identity() { match queries::authenticate(id)? {
let mut data = Context::new(); queries::Role::Admin { user } => {
data.insert("title", "Sign Up"); let mut data = Context::new();
data.insert("name", &id); data.insert("title", "Ein Benutzerkonto erstellen");
data.insert("user", &user);
let rendered = tera.render("signup.html", &data)?; let rendered = tera.render("signup.html", &data)?;
Ok(HttpResponse::Ok().body(rendered)) Ok(HttpResponse::Ok().body(rendered))
} else { }
Ok(redirect_builder("/admin/login/")) 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<NewUser>, data: web::Form<NewUser>,
id: Identity, id: Identity,
) -> Result<HttpResponse, ServerError> { ) -> Result<HttpResponse, ServerError> {
if let Some(_id) = id.identity() { info!("Creating a User: {:?}", &data);
use super::schema::users; if let Ok(item) = queries::create_user(id, data) {
Ok(HttpResponse::Ok().body(format!("Successfully saved user: {}", item.item.username)))
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)))
} else { } else {
Ok(redirect_builder("/admin/login/")) Ok(redirect_builder("/admin/login/"))
} }
} }
pub(crate) async fn toggle_admin(
data: web::Path<String>,
id: Identity,
) -> Result<HttpResponse, ServerError> {
let update = queries::toggle_admin(id, &data.0)?;
Ok(redirect_builder(&format!(
"/admin/view/profile/{}",
update.item.id
)))
}
pub(crate) async fn login( pub(crate) async fn login(
tera: web::Data<Tera>, tera: web::Data<Tera>,
id: Identity, id: Identity,
@ -339,12 +263,7 @@ pub(crate) async fn process_login(
data: web::Form<LoginUser>, data: web::Form<LoginUser>,
id: Identity, id: Identity,
) -> Result<HttpResponse, ServerError> { ) -> Result<HttpResponse, ServerError> {
use super::schema::users::dsl::{username, users}; let user = queries::get_user_by_name(&data.username);
let connection = establish_connection()?;
let user = users
.filter(username.eq(&data.username))
.first::<User>(&connection);
match user { match user {
Ok(u) => { Ok(u) => {
@ -381,22 +300,22 @@ pub(crate) async fn redirect(
tera: web::Data<Tera>, tera: web::Data<Tera>,
data: web::Path<String>, data: web::Path<String>,
) -> Result<HttpResponse, ServerError> { ) -> Result<HttpResponse, ServerError> {
use super::schema::links::dsl::{code, links}; info!("Redirecting to {:?}", data);
let connection = establish_connection()?; let link = queries::get_link_simple(&data.0);
info!("link: {:?}", link);
let link = links.filter(code.eq(&data.0)).first::<Link>(&connection);
match link { match link {
Ok(link) => { Ok(link) => {
use super::schema::clicks; queries::click_link(link.id)?;
let new_click = NewClick::new(link.id);
let connection = establish_connection()?;
diesel::insert_into(clicks::table)
.values(&new_click)
.execute(&connection)?;
Ok(redirect_builder(&link.target)) 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(); let mut data = Context::new();
data.insert("title", "Wurde gelöscht"); data.insert("title", "Wurde gelöscht");
let rendered = tera.render("not_found.html", &data)?; let rendered = tera.render("not_found.html", &data)?;
@ -416,44 +335,30 @@ pub(crate) async fn create_link(
tera: web::Data<Tera>, tera: web::Data<Tera>,
id: Identity, id: Identity,
) -> Result<HttpResponse, ServerError> { ) -> Result<HttpResponse, ServerError> {
if let Some(id) = id.identity() { match queries::authenticate(id)? {
let mut data = Context::new(); queries::Role::Admin { user } | queries::Role::Regular { user } => {
data.insert("title", "Submit a Post"); let mut data = Context::new();
data.insert("title", "Einen Kurzlink erstellen");
data.insert("name", &id); data.insert("user", &user);
let rendered = tera.render("submission.html", &data)?; let rendered = tera.render("submission.html", &data)?;
return Ok(HttpResponse::Ok().body(rendered)); 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( pub(crate) async fn process_link_creation(
data: web::Form<LinkForm>, data: web::Form<LinkForm>,
id: Identity, id: Identity,
) -> Result<HttpResponse, ServerError> { ) -> Result<HttpResponse, ServerError> {
if let Some(id) = id.identity() { let new_link = queries::create_link(id, data)?;
use super::schema::users::dsl::{username, users}; Ok(redirect_builder(&format!(
"/admin/view/link/{}",
let connection = establish_connection()?; new_link.item.code
let user: Result<User, diesel::result::Error> = )))
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/"))
}
} }
pub(crate) async fn edit_link( pub(crate) async fn edit_link(
@ -461,17 +366,12 @@ pub(crate) async fn edit_link(
id: Identity, id: Identity,
link_id: web::Path<String>, link_id: web::Path<String>,
) -> Result<HttpResponse, ServerError> { ) -> Result<HttpResponse, ServerError> {
if let Some(id) = id.identity() { if let Ok(query) = queries::get_link(id, &link_id.0) {
use super::schema::links::dsl::{code, links};
let connection = establish_connection()?;
let link: Link = links
.filter(code.eq(&link_id.0))
.first::<Link>(&connection)?;
let mut data = Context::new(); let mut data = Context::new();
data.insert("title", "Submit a Post"); 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)?; let rendered = tera.render("edit_link.html", &data)?;
return Ok(HttpResponse::Ok().body(rendered)); return Ok(HttpResponse::Ok().body(rendered));
} }
@ -480,38 +380,21 @@ pub(crate) async fn edit_link(
pub(crate) async fn process_link_edit( pub(crate) async fn process_link_edit(
data: web::Form<LinkForm>, data: web::Form<LinkForm>,
id: Identity, id: Identity,
link_id: web::Path<String>, link_code: web::Path<String>,
) -> Result<HttpResponse, ServerError> { ) -> Result<HttpResponse, ServerError> {
if let Some(_id) = id.identity() { match queries::update_link(id, &link_code.0, data) {
use super::schema::links::dsl::{code, links, target, title}; Ok(query) => Ok(redirect_builder(&format!(
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!(
"/admin/view/link/{}", "/admin/view/link/{}",
&data.code &query.item.code
))); ))),
Err(e) => Err(e),
} }
Ok(redirect_builder("/admin/login/"))
} }
pub(crate) async fn process_link_delete( pub(crate) async fn process_link_delete(
id: Identity, id: Identity,
link_id: web::Path<String>, link_code: web::Path<String>,
) -> Result<HttpResponse, ServerError> { ) -> Result<HttpResponse, ServerError> {
if let Some(_id) = id.identity() { queries::delete_link(id, link_code.0)?;
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/"));
}
Ok(redirect_builder("/admin/login/")) Ok(redirect_builder("/admin/login/"))
} }

View File

@ -87,9 +87,10 @@ div.danger h3 {
} }
a.button, div.actions input { a.button, div.actions input {
width: 150px; width: 250px;
display:block; display:block;
padding: 15px; padding: 15px;
margin-left: 15px;
text-align: center; text-align: center;
border-radius: 1px; border-radius: 1px;
border: 1px solid rgb(90, 90, 90); border: 1px solid rgb(90, 90, 90);

View File

@ -11,13 +11,13 @@
<div class="admin"> <div class="admin">
<nav> <nav>
<ol> <ol>
<li><a href="/admin/index/">Liste</a></li> <li><a href="/admin/index/">Link Liste</a></li>
<li><a href="/admin/submit/">Hinzufügen</a></li> <li><a href="/admin/submit/">Link Hinzufügen</a></li>
<li><a href="/admin/signup/">Einladen</a></li> {% if user.role == 2 %}<li><a href="/admin/signup/">Einladen</a></li>
<li><a href="/admin/view/users/">Benutzer</a></li> <li><a href="/admin/view/users/">Benutzer</a></li>{% endif %}
<li style="float:right"><a href="/admin/logout/">Abmelden</a></li> <li style="float:right"><a href="/admin/logout/">Abmelden</a></li>
<li style="float:right"> <li style="float:right">
<div class="willkommen">Herzlich willkommen {{ name }}</div> <div class="willkommen">Herzlich willkommen {{ user.username }}</div>
</li> </li>
</ol> </ol>
</nav> </nav>

View File

@ -24,9 +24,9 @@
</th> </th>
</tr> </tr>
{% for links_user in links_per_users %} {% for links_user in links_per_users %}
{% set l = links_user[0] %} {% set l = links_user.link %}
{% set u = links_user[1] %} {% set u = links_user.user %}
{% set c = links_user[2] %} {% set c = links_user.clicks %}
<tr> <tr>
<td> <td>
<a href="/admin/view/link/{{l.code}}"><span>{{l.code}}</span> <a href="/admin/view/link/{{l.code}}"><span>{{l.code}}</span>
@ -37,9 +37,12 @@
</a> </a>
</td> </td>
<td> <td>
{% if user.role == 2 or user.id == u.id %}
<a href="/admin/view/profile/{{u.id}}"><small>{{ u.username }}</small> <a href="/admin/view/profile/{{u.id}}"><small>{{ u.username }}</small>
</a> </a>
{% else %}
<small>{{ u.username }}</small>
{% endif %}
</td> </td>
<td> <td>
{{ c.count }} {{ c.count }}

View File

@ -14,7 +14,8 @@
</tr> </tr>
<tr> <tr>
<td>Kurzlink:</td> <td>Kurzlink:</td>
<td><a href="https://fhs.li/{{ link.code }}">https://fhs.li/{{ link.code }}</a></td> <td><a href="{{ protocol }}://{{ host }}/{{ link.code }}">{{ protocol }}://{{ host }}/{{ link.code }}</a>
</td>
</tr> </tr>
<tr> <tr>
<td>Ziel:</td> <td>Ziel:</td>
@ -30,8 +31,10 @@
</td> </td>
</tr> </tr>
</table> </table>
{% if user.role == 2 or user.id == link.author %}
<div class="actions"> <div class="actions">
<a class="button" href="/admin/edit/link/{{ link.code }}">Editieren</a> <a class="button" href="/admin/edit/link/{{ link.code }}">Editieren</a>
</div> </div>
{% endif %}
</div> </div>
{% endblock %} {% endblock %}

View File

@ -2,24 +2,34 @@
{% block admin %} {% block admin %}
<div class="center"> <div class="center">
<h1>Profil von {{user.username}}</h1> <h1>Profil von {{viewed_user.username}}</h1>
<form action="" method="POST"> <form action="" method="POST">
<div> <div>
<label for="username">Benutzername:</label> <label for="username">Benutzername:</label>
<input type="text" name="username" value="{{ user.username }}" readonly> <input type="text" name="username" value="{{ viewed_user.username }}" readonly>
</div> </div>
<div> <div>
<label for="email">E-mail:</label> <label for="email">E-mail:</label>
<input type="email" name="email" value="{{ user.email }}" readonly> <input type="email" name="email" value="{{ viewed_user.email }}" readonly>
</div> </div>
{% if user.role == 2 or user.id == viewed_user.id %}
<div> <div>
<label for="password">Passwort:</label> <label for="password">Passwort:</label>
<input type="password" name="password" value="verschlüsselt" readonly> <input type="password" name="password" value="verschlüsselt" readonly>
</div> </div>
{% endif %}
</form> </form>
{% if user.role == 2 or user.id == viewed_user.id %}
<div class="actions"> <div class="actions">
<a class="button" href="/admin/edit/profile/{{ user.id }}">Editieren</a> <a class="button" href="/admin/edit/profile/{{ viewed_user.id }}">Editieren</a>
{% if user.role == 2 and viewed_user.role == 1 %}
<a class="button" href="/admin/edit/set_admin/{{ viewed_user.id }}">Zum Admin machen</a>
{% endif %}
{% if user.role == 2 and viewed_user.role == 2 %}
<a class="button" href="/admin/edit/set_admin/{{ viewed_user.id }}">Zum Normalo machen</a>
{% endif %}
</div> </div>
{% endif %}
<h2>&nbsp;</h2> <h2>&nbsp;</h2>
</div> </div>
{% endblock %} {% endblock %}