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
.env
links.db
launch.json
settings.json
links.session.sql

View File

@ -1,3 +1,20 @@
-- 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
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)
(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;

View File

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

View File

@ -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<std::env::VarError> 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<std::env::VarError> 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);
match err {
diesel::result::Error::NotFound => ServerError::User("Username not found.".to_string()),
_ => ServerError::Diesel,
}
Self::Diesel(err)
}
}
impl From<argonautica::Error> for ServerError {
fn from(e: argonautica::Error) -> ServerError {
fn from(e: argonautica::Error) -> Self {
error!("Authentication error {:?}", e);
ServerError::Argonautic
Self::Argonautic
}
}
impl From<tera::Error> 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<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();
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(

View File

@ -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)]

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,
email -> Text,
password -> Text,
role -> Integer,
}
}

View File

@ -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<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 {
HttpResponse::SeeOther()
.set(CacheControl(vec![
@ -49,36 +33,11 @@ pub(crate) async fn index(
tera: web::Data<Tera>,
id: Identity,
) -> Result<HttpResponse, ServerError> {
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::<diesel::sql_types::Integer>(
"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<Tera>,
id: Identity,
) -> Result<HttpResponse, ServerError> {
use super::schema::users::dsl::users;
if let Some(id) = id.identity() {
let connection = establish_connection()?;
let all_users: Vec<User> = 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<String>,
) -> Result<HttpResponse, ServerError> {
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::<Link>(&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<Tera>,
identity: Identity,
id: Identity,
user_id: web::Path<String>,
) -> Result<HttpResponse, ServerError> {
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::<i32>() {
let user = users.filter(id.eq(&uid)).first::<User>(&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<Tera>,
identity: Identity,
id: Identity,
user_id: web::Path<String>,
) -> Result<HttpResponse, ServerError> {
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::<i32>() {
let user = users.filter(id.eq(&uid)).first::<User>(&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<String>,
) -> Result<HttpResponse, ServerError> {
if let Some(_id) = id.identity() {
use super::schema::users::dsl::{email, id, password, username, users};
if let Ok(uid) = user_id.parse::<i32>() {
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<String>,
link_code: web::Path<String>,
) -> Result<HttpResponse, ServerError> {
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::<Link>(&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<Tera>,
id: Identity,
) -> Result<HttpResponse, ServerError> {
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<NewUser>,
id: Identity,
) -> Result<HttpResponse, ServerError> {
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<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(
tera: web::Data<Tera>,
id: Identity,
@ -339,12 +263,7 @@ pub(crate) async fn process_login(
data: web::Form<LoginUser>,
id: Identity,
) -> Result<HttpResponse, ServerError> {
use super::schema::users::dsl::{username, users};
let connection = establish_connection()?;
let user = users
.filter(username.eq(&data.username))
.first::<User>(&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<Tera>,
data: web::Path<String>,
) -> Result<HttpResponse, ServerError> {
use super::schema::links::dsl::{code, links};
let connection = establish_connection()?;
let link = links.filter(code.eq(&data.0)).first::<Link>(&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<Tera>,
id: Identity,
) -> Result<HttpResponse, ServerError> {
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<LinkForm>,
id: Identity,
) -> Result<HttpResponse, ServerError> {
if let Some(id) = id.identity() {
use super::schema::users::dsl::{username, users};
let connection = establish_connection()?;
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/"))
}
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<String>,
) -> Result<HttpResponse, ServerError> {
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::<Link>(&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<LinkForm>,
id: Identity,
link_id: web::Path<String>,
link_code: web::Path<String>,
) -> Result<HttpResponse, ServerError> {
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<String>,
link_code: web::Path<String>,
) -> Result<HttpResponse, ServerError> {
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/"))
}

View File

@ -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);

View File

@ -11,13 +11,13 @@
<div class="admin">
<nav>
<ol>
<li><a href="/admin/index/">Liste</a></li>
<li><a href="/admin/submit/">Hinzufügen</a></li>
<li><a href="/admin/signup/">Einladen</a></li>
<li><a href="/admin/view/users/">Benutzer</a></li>
<li><a href="/admin/index/">Link Liste</a></li>
<li><a href="/admin/submit/">Link Hinzufügen</a></li>
{% if user.role == 2 %}<li><a href="/admin/signup/">Einladen</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">
<div class="willkommen">Herzlich willkommen {{ name }}</div>
<div class="willkommen">Herzlich willkommen {{ user.username }}</div>
</li>
</ol>
</nav>

View File

@ -24,9 +24,9 @@
</th>
</tr>
{% 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 %}
<tr>
<td>
<a href="/admin/view/link/{{l.code}}"><span>{{l.code}}</span>
@ -37,9 +37,12 @@
</a>
</td>
<td>
{% if user.role == 2 or user.id == u.id %}
<a href="/admin/view/profile/{{u.id}}"><small>{{ u.username }}</small>
</a>
{% else %}
<small>{{ u.username }}</small>
{% endif %}
</td>
<td>
{{ c.count }}

View File

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

View File

@ -2,24 +2,34 @@
{% block admin %}
<div class="center">
<h1>Profil von {{user.username}}</h1>
<h1>Profil von {{viewed_user.username}}</h1>
<form action="" method="POST">
<div>
<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>
<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>
{% if user.role == 2 or user.id == viewed_user.id %}
<div>
<label for="password">Passwort:</label>
<input type="password" name="password" value="verschlüsselt" readonly>
</div>
{% endif %}
</form>
{% if user.role == 2 or user.id == viewed_user.id %}
<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>
{% endif %}
<h2>&nbsp;</h2>
</div>
{% endblock %}