Make secret more logsafe, implement add and edit for links
This commit is contained in:
@@ -3,7 +3,7 @@ use clap::{
|
||||
ArgMatches, SubCommand,
|
||||
};
|
||||
use dotenv::dotenv;
|
||||
use shared::datatypes::User;
|
||||
use shared::datatypes::{Secret, User};
|
||||
use sqlx::{migrate::Migrator, Pool, Sqlite};
|
||||
use std::{
|
||||
fs::File,
|
||||
@@ -151,7 +151,7 @@ async fn parse_args_to_config(config: ArgMatches<'_>) -> ServerConfig {
|
||||
} else {
|
||||
secret
|
||||
};
|
||||
let secret = pslink::Secret::new(secret);
|
||||
let secret = Secret::new(secret);
|
||||
let db = config
|
||||
.value_of("database")
|
||||
.expect(concat!(
|
||||
|
||||
+24
-25
@@ -11,6 +11,7 @@ use actix_web::HttpResponse;
|
||||
use actix_web::{web, App, HttpServer};
|
||||
use fluent_templates::{static_loader, FluentLoader};
|
||||
use qrcode::types::QrError;
|
||||
use shared::datatypes::Secret;
|
||||
use sqlx::{Pool, Sqlite};
|
||||
use std::{fmt::Display, path::PathBuf, str::FromStr};
|
||||
use tera::Tera;
|
||||
@@ -156,30 +157,6 @@ impl FromStr for Protocol {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Secret {
|
||||
secret: String,
|
||||
}
|
||||
|
||||
impl Secret {
|
||||
#[must_use]
|
||||
pub const fn new(secret: String) -> Self {
|
||||
Self { secret }
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Secret {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str("*****SECRET*****")
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Secret {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str("*****SECRET*****")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ServerConfig {
|
||||
pub secret: Secret,
|
||||
@@ -209,7 +186,13 @@ impl ServerConfig {
|
||||
"# If it is changed all existing passwords are invalid.\n"
|
||||
)
|
||||
.to_owned(),
|
||||
format!("PSLINK_SECRET=\"{}\"\n", self.secret.secret),
|
||||
format!(
|
||||
"PSLINK_SECRET=\"{}\"\n",
|
||||
self.secret
|
||||
.secret
|
||||
.as_ref()
|
||||
.expect("A Secret was not specified!")
|
||||
),
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -370,6 +353,18 @@ pub async fn webservice(
|
||||
.service(
|
||||
web::scope("/json")
|
||||
.route("/list_links/", web::post().to(views::index_json))
|
||||
.route(
|
||||
"/create_link/",
|
||||
web::post().to(views::process_create_link_json),
|
||||
)
|
||||
.route(
|
||||
"/edit_link/",
|
||||
web::post().to(views::process_update_link_json),
|
||||
)
|
||||
.route(
|
||||
"/delete_link/",
|
||||
web::post().to(views::process_delete_link_json),
|
||||
)
|
||||
.route("/list_users/", web::post().to(views::index_users_json))
|
||||
.route(
|
||||
"/create_user/",
|
||||
@@ -378,6 +373,10 @@ pub async fn webservice(
|
||||
.route(
|
||||
"/update_user/",
|
||||
web::post().to(views::process_update_user_json),
|
||||
)
|
||||
.route(
|
||||
"/get_logged_user/",
|
||||
web::post().to(views::get_logged_user_json),
|
||||
),
|
||||
)
|
||||
// login to the admin area
|
||||
|
||||
+86
-14
@@ -5,7 +5,12 @@ use async_trait::async_trait;
|
||||
use dotenv::dotenv;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use shared::datatypes::{Count, Link, User};
|
||||
use shared::{
|
||||
apirequests::links::LinkDelta,
|
||||
datatypes::{Count, Link, User},
|
||||
};
|
||||
use sqlx::Row;
|
||||
use tracing::{error, info, instrument};
|
||||
|
||||
#[async_trait]
|
||||
pub trait UserDbOperations<T> {
|
||||
@@ -24,10 +29,19 @@ pub trait UserDbOperations<T> {
|
||||
|
||||
#[async_trait]
|
||||
impl UserDbOperations<Self> for User {
|
||||
#[instrument()]
|
||||
async fn get_user(id: i64, server_config: &ServerConfig) -> Result<Self, ServerError> {
|
||||
let user = sqlx::query_as!(Self, "Select * from users where id = ? ", id)
|
||||
let user = sqlx::query!("Select * from users where id = ? ", id)
|
||||
.fetch_one(&server_config.db_pool)
|
||||
.await;
|
||||
.await
|
||||
.map(|row| Self {
|
||||
id: row.id,
|
||||
username: row.username,
|
||||
email: row.email,
|
||||
password: Secret::new(row.password),
|
||||
role: row.role,
|
||||
language: row.language,
|
||||
});
|
||||
user.map_err(ServerError::Database)
|
||||
}
|
||||
|
||||
@@ -35,23 +49,46 @@ impl UserDbOperations<Self> for User {
|
||||
///
|
||||
/// # Errors
|
||||
/// fails with [`ServerError`] if the user does not exist or the database cannot be acessed.
|
||||
#[instrument()]
|
||||
async fn get_user_by_name(
|
||||
name: &str,
|
||||
server_config: &ServerConfig,
|
||||
) -> Result<Self, ServerError> {
|
||||
let user = sqlx::query_as!(Self, "Select * from users where username = ? ", name)
|
||||
let user = sqlx::query!("Select * from users where username = ? ", name)
|
||||
.fetch_one(&server_config.db_pool)
|
||||
.await;
|
||||
.await
|
||||
.map(|row| Self {
|
||||
id: row.id,
|
||||
username: row.username,
|
||||
email: row.email,
|
||||
password: Secret::new(row.password),
|
||||
role: row.role,
|
||||
language: row.language,
|
||||
});
|
||||
user.map_err(ServerError::Database)
|
||||
}
|
||||
|
||||
#[instrument()]
|
||||
async fn get_all_users(server_config: &ServerConfig) -> Result<Vec<Self>, ServerError> {
|
||||
let user = sqlx::query_as!(Self, "Select * from users")
|
||||
let user = sqlx::query("Select * from users")
|
||||
.fetch_all(&server_config.db_pool)
|
||||
.await;
|
||||
.await
|
||||
.map(|row| {
|
||||
row.into_iter()
|
||||
.map(|r| Self {
|
||||
id: r.get("id"),
|
||||
username: r.get("username"),
|
||||
email: r.get("email"),
|
||||
password: Secret::new(r.get("password")),
|
||||
role: r.get("role"),
|
||||
language: r.get("language"),
|
||||
})
|
||||
.collect()
|
||||
});
|
||||
user.map_err(ServerError::Database)
|
||||
}
|
||||
|
||||
#[instrument()]
|
||||
async fn update_user(&self, server_config: &ServerConfig) -> Result<(), ServerError> {
|
||||
sqlx::query!(
|
||||
"UPDATE users SET
|
||||
@@ -61,7 +98,7 @@ impl UserDbOperations<Self> for User {
|
||||
role = ? where id = ?",
|
||||
self.username,
|
||||
self.email,
|
||||
self.password,
|
||||
self.password.secret,
|
||||
self.role,
|
||||
self.id
|
||||
)
|
||||
@@ -69,10 +106,12 @@ impl UserDbOperations<Self> for User {
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Change an admin user to normal user and a normal user to admin
|
||||
///
|
||||
/// # Errors
|
||||
/// fails with [`ServerError`] if the database cannot be acessed. (the user should exist)
|
||||
#[instrument()]
|
||||
async fn toggle_admin(self, server_config: &ServerConfig) -> Result<(), ServerError> {
|
||||
let new_role = 2 - (self.role + 1) % 2;
|
||||
sqlx::query!("UPDATE users SET role = ? where id = ?", new_role, self.id)
|
||||
@@ -81,6 +120,7 @@ impl UserDbOperations<Self> for User {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument()]
|
||||
async fn set_language(
|
||||
self,
|
||||
server_config: &ServerConfig,
|
||||
@@ -102,6 +142,7 @@ impl UserDbOperations<Self> for User {
|
||||
///
|
||||
/// # Errors
|
||||
/// fails with [`ServerError`] if the database cannot be acessed.
|
||||
#[instrument()]
|
||||
async fn count_admins(server_config: &ServerConfig) -> Result<Count, ServerError> {
|
||||
let num = sqlx::query_as!(Count, "select count(*) as number from users where role = 2")
|
||||
.fetch_one(&server_config.db_pool)
|
||||
@@ -122,6 +163,7 @@ impl NewUser {
|
||||
///
|
||||
/// # Errors
|
||||
/// fails with [`ServerError`] if the password could not be encrypted.
|
||||
#[instrument()]
|
||||
pub fn new(
|
||||
username: String,
|
||||
email: String,
|
||||
@@ -137,12 +179,13 @@ impl NewUser {
|
||||
})
|
||||
}
|
||||
|
||||
#[instrument()]
|
||||
pub(crate) fn hash_password(password: &str, secret: &Secret) -> Result<String, ServerError> {
|
||||
dotenv().ok();
|
||||
|
||||
let hash = Hasher::default()
|
||||
.with_password(password)
|
||||
.with_secret_key(&secret.secret)
|
||||
.with_secret_key(secret.secret.as_ref().expect("A secret key was not given"))
|
||||
.hash()?;
|
||||
|
||||
Ok(hash)
|
||||
@@ -152,6 +195,7 @@ impl NewUser {
|
||||
///
|
||||
/// # Errors
|
||||
/// fails with [`ServerError`] if the database cannot be acessed.
|
||||
#[instrument()]
|
||||
pub async fn insert_user(&self, server_config: &ServerConfig) -> Result<(), ServerError> {
|
||||
sqlx::query!(
|
||||
"Insert into users (
|
||||
@@ -178,6 +222,7 @@ pub struct LoginUser {
|
||||
#[async_trait]
|
||||
pub trait LinkDbOperations<T> {
|
||||
async fn get_link_by_code(code: &str, server_config: &ServerConfig) -> Result<T, ServerError>;
|
||||
async fn get_link_by_id(id: i64, server_config: &ServerConfig) -> Result<T, ServerError>;
|
||||
async fn delete_link_by_code(
|
||||
code: &str,
|
||||
server_config: &ServerConfig,
|
||||
@@ -187,6 +232,7 @@ pub trait LinkDbOperations<T> {
|
||||
|
||||
#[async_trait]
|
||||
impl LinkDbOperations<Self> for Link {
|
||||
#[instrument()]
|
||||
async fn get_link_by_code(
|
||||
code: &str,
|
||||
server_config: &ServerConfig,
|
||||
@@ -197,7 +243,16 @@ impl LinkDbOperations<Self> for Link {
|
||||
tracing::info!("Found link: {:?}", &link);
|
||||
link.map_err(ServerError::Database)
|
||||
}
|
||||
#[instrument()]
|
||||
async fn get_link_by_id(id: i64, server_config: &ServerConfig) -> Result<Self, ServerError> {
|
||||
let link = sqlx::query_as!(Self, "Select * from links where id = ? ", id)
|
||||
.fetch_one(&server_config.db_pool)
|
||||
.await;
|
||||
tracing::info!("Found link: {:?}", &link);
|
||||
link.map_err(ServerError::Database)
|
||||
}
|
||||
|
||||
#[instrument()]
|
||||
async fn delete_link_by_code(
|
||||
code: &str,
|
||||
server_config: &ServerConfig,
|
||||
@@ -207,8 +262,11 @@ impl LinkDbOperations<Self> for Link {
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument()]
|
||||
async fn update_link(&self, server_config: &ServerConfig) -> Result<(), ServerError> {
|
||||
sqlx::query!(
|
||||
info!("{:?}", self);
|
||||
let qry = sqlx::query!(
|
||||
"UPDATE links SET
|
||||
title = ?,
|
||||
target = ?,
|
||||
@@ -221,10 +279,15 @@ impl LinkDbOperations<Self> for Link {
|
||||
self.author,
|
||||
self.created_at,
|
||||
self.id
|
||||
)
|
||||
.execute(&server_config.db_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
);
|
||||
match qry.execute(&server_config.db_pool).await {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => {
|
||||
//error!("{}", qry);
|
||||
error!("{}", e);
|
||||
Err(e.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -247,6 +310,15 @@ impl NewLink {
|
||||
created_at: chrono::Local::now().naive_utc(),
|
||||
}
|
||||
}
|
||||
pub(crate) fn from_link_delta(link: LinkDelta, uid: i64) -> Self {
|
||||
Self {
|
||||
title: link.title,
|
||||
target: link.target,
|
||||
code: link.code,
|
||||
author: uid,
|
||||
created_at: chrono::Local::now().naive_utc(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn insert(self, server_config: &ServerConfig) -> Result<(), ServerError> {
|
||||
sqlx::query!(
|
||||
|
||||
+103
-6
@@ -5,10 +5,10 @@ use serde::Serialize;
|
||||
use shared::{
|
||||
apirequests::{
|
||||
general::{EditMode, Filter, Operation, Ordering},
|
||||
links::{LinkOverviewColumns, LinkRequestForm},
|
||||
links::{LinkDelta, LinkOverviewColumns, LinkRequestForm},
|
||||
users::{UserDelta, UserOverviewColumns, UserRequestForm},
|
||||
},
|
||||
datatypes::{Count, FullLink, Link, User},
|
||||
datatypes::{Count, FullLink, Link, Secret, User},
|
||||
};
|
||||
use sqlx::Row;
|
||||
use tracing::{info, instrument, warn};
|
||||
@@ -127,7 +127,7 @@ pub async fn list_all_allowed(
|
||||
id: v.get("usid"),
|
||||
username: v.get("usern"),
|
||||
email: v.get("uemail"),
|
||||
password: "invalid".to_owned(),
|
||||
password: Secret::new("invalid".to_string()),
|
||||
role: v.get("urole"),
|
||||
language: v.get("ulang"),
|
||||
},
|
||||
@@ -240,7 +240,7 @@ pub async fn list_users(
|
||||
id: v.get("id"),
|
||||
username: v.get("username"),
|
||||
email: v.get("email"),
|
||||
password: "invalid".to_owned(),
|
||||
password: Secret::new("".to_string()),
|
||||
role: v.get("role"),
|
||||
language: v.get("language"),
|
||||
})
|
||||
@@ -458,7 +458,9 @@ pub async fn update_user_json(
|
||||
Role::Admin { .. } | Role::Regular { .. } => {
|
||||
info!("Updating userinfo: ");
|
||||
let password = match &data.password {
|
||||
Some(password) => NewUser::hash_password(password, &server_config.secret)?,
|
||||
Some(password) => {
|
||||
Secret::new(NewUser::hash_password(password, &server_config.secret)?)
|
||||
}
|
||||
None => unmodified_user.password,
|
||||
};
|
||||
let new_user = User {
|
||||
@@ -510,7 +512,10 @@ pub async fn update_user(
|
||||
Role::Admin { .. } | Role::Regular { .. } => {
|
||||
info!("Updating userinfo: ");
|
||||
let password = if data.password.len() > 3 {
|
||||
NewUser::hash_password(&data.password, &server_config.secret)?
|
||||
Secret::new(NewUser::hash_password(
|
||||
&data.password,
|
||||
&server_config.secret,
|
||||
)?)
|
||||
} else {
|
||||
unmodified_user.password
|
||||
};
|
||||
@@ -629,6 +634,28 @@ pub async fn get_link(
|
||||
}
|
||||
}
|
||||
|
||||
/// Get one link if permissions are accordingly.
|
||||
///
|
||||
/// # Errors
|
||||
/// Fails with [`ServerError`] if access to the database fails or this user does not have permissions.
|
||||
#[instrument(skip(id))]
|
||||
pub async fn get_link_by_id(
|
||||
id: &Identity,
|
||||
lid: i64,
|
||||
server_config: &ServerConfig,
|
||||
) -> Result<Item<Link>, ServerError> {
|
||||
match authenticate(id, server_config).await? {
|
||||
Role::Admin { user } | Role::Regular { user } => {
|
||||
let link = Link::get_link_by_id(lid, server_config).await?;
|
||||
Ok(Item { user, item: link })
|
||||
}
|
||||
Role::Disabled | Role::NotAuthenticated => {
|
||||
warn!("User could not be authenticated!");
|
||||
Err(ServerError::User("Not Allowed".to_owned()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get link **without authentication**
|
||||
///
|
||||
/// # Errors
|
||||
@@ -743,3 +770,73 @@ pub async fn create_link(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new link
|
||||
///
|
||||
/// # Errors
|
||||
/// Fails with [`ServerError`] if access to the database fails or this user does not have permissions.
|
||||
#[instrument(skip(id))]
|
||||
pub async fn create_link_json(
|
||||
id: &Identity,
|
||||
data: web::Json<LinkDelta>,
|
||||
server_config: &ServerConfig,
|
||||
) -> Result<Item<Link>, ServerError> {
|
||||
let auth = authenticate(id, server_config).await?;
|
||||
match auth {
|
||||
Role::Admin { user } | Role::Regular { user } => {
|
||||
let code = data.code.clone();
|
||||
info!("Creating link for: {}", &code);
|
||||
let new_link = NewLink::from_link_delta(data.into_inner(), user.id);
|
||||
info!("Creating link for: {:?}", &new_link);
|
||||
|
||||
new_link.insert(server_config).await?;
|
||||
let new_link: Link = get_link_simple(&code, server_config).await?;
|
||||
Ok(Item {
|
||||
user,
|
||||
item: new_link,
|
||||
})
|
||||
}
|
||||
Role::Disabled | Role::NotAuthenticated => {
|
||||
Err(ServerError::User("Permission denied!".to_owned()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Update a link if the user is admin or it is its own link.
|
||||
///
|
||||
/// # Errors
|
||||
/// Fails with [`ServerError`] if access to the database fails or this user does not have permissions.
|
||||
#[instrument(skip(ident))]
|
||||
pub async fn update_link_json(
|
||||
ident: &Identity,
|
||||
data: web::Json<LinkDelta>,
|
||||
server_config: &ServerConfig,
|
||||
) -> Result<Item<Link>, ServerError> {
|
||||
let auth = authenticate(ident, server_config).await?;
|
||||
match auth {
|
||||
Role::Admin { .. } | Role::Regular { .. } => {
|
||||
if let Some(id) = data.id {
|
||||
let query: Item<Link> = get_link_by_id(ident, id, server_config).await?;
|
||||
if auth.admin_or_self(query.item.author) {
|
||||
let mut link = query.item;
|
||||
let LinkDelta {
|
||||
title,
|
||||
target,
|
||||
code,
|
||||
..
|
||||
} = data.into_inner();
|
||||
link.code = code.clone();
|
||||
link.target = target;
|
||||
link.title = title;
|
||||
link.update_link(server_config).await?;
|
||||
get_link(ident, &code, server_config).await
|
||||
} else {
|
||||
Err(ServerError::User("Invalid Request".to_owned()))
|
||||
}
|
||||
} else {
|
||||
Err(ServerError::User("Not Allowed".to_owned()))
|
||||
}
|
||||
}
|
||||
Role::Disabled | Role::NotAuthenticated => Err(ServerError::User("Not Allowed".to_owned())),
|
||||
}
|
||||
}
|
||||
|
||||
+78
-18
@@ -15,8 +15,8 @@ use image::{DynamicImage, ImageOutputFormat, Luma};
|
||||
use qrcode::{render::svg, QrCode};
|
||||
use queries::{authenticate, Role};
|
||||
use shared::apirequests::{
|
||||
general::SuccessMessage,
|
||||
links::LinkRequestForm,
|
||||
general::{Message, Status},
|
||||
links::{LinkDelta, LinkRequestForm},
|
||||
users::{UserDelta, UserRequestForm},
|
||||
};
|
||||
use tera::{Context, Tera};
|
||||
@@ -171,6 +171,19 @@ pub async fn index_users_json(
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_logged_user_json(
|
||||
config: web::Data<crate::ServerConfig>,
|
||||
id: Identity,
|
||||
) -> Result<HttpResponse, ServerError> {
|
||||
let user = authenticate(&id, &config).await?;
|
||||
match user {
|
||||
Role::NotAuthenticated | Role::Disabled => {
|
||||
Err(ServerError::User("User not logged in!".to_string()))
|
||||
}
|
||||
Role::Regular { user } | Role::Admin { user } => Ok(HttpResponse::Ok().json2(&user)),
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(skip(id, tera))]
|
||||
pub async fn view_link_empty(
|
||||
tera: web::Data<Tera>,
|
||||
@@ -358,9 +371,9 @@ pub async fn process_create_user_json(
|
||||
) -> Result<HttpResponse, ServerError> {
|
||||
info!("Listing Users to Json api");
|
||||
match queries::create_user_json(&id, &form, &config).await {
|
||||
Ok(item) => Ok(HttpResponse::Ok().json2(&SuccessMessage {
|
||||
Ok(item) => Ok(HttpResponse::Ok().json2(&Status::Success(Message {
|
||||
message: format!("Successfully saved user: {}", item.item.username),
|
||||
})),
|
||||
}))),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
@@ -373,9 +386,9 @@ pub async fn process_update_user_json(
|
||||
) -> Result<HttpResponse, ServerError> {
|
||||
info!("Listing Users to Json api");
|
||||
match queries::update_user_json(&id, &form, &config).await {
|
||||
Ok(item) => Ok(HttpResponse::Ok().json2(&SuccessMessage {
|
||||
Ok(item) => Ok(HttpResponse::Ok().json2(&Status::Success(Message {
|
||||
message: format!("Successfully saved user: {}", item.item.username),
|
||||
})),
|
||||
}))),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
@@ -447,20 +460,26 @@ pub async fn process_login(
|
||||
|
||||
match user {
|
||||
Ok(u) => {
|
||||
let secret = &config.secret;
|
||||
let valid = Verifier::default()
|
||||
.with_hash(&u.password)
|
||||
.with_password(&data.password)
|
||||
.with_secret_key(&secret.secret)
|
||||
.verify()?;
|
||||
if let Some(hash) = u.password.secret {
|
||||
let secret = &config.secret;
|
||||
let valid = Verifier::default()
|
||||
.with_hash(hash)
|
||||
.with_password(&data.password)
|
||||
.with_secret_key(secret.secret.as_ref().expect("No secret available"))
|
||||
.verify()?;
|
||||
|
||||
if valid {
|
||||
info!("Log-in of user: {}", &u.username);
|
||||
let session_token = u.username;
|
||||
id.remember(session_token);
|
||||
Ok(redirect_builder("/admin/index/"))
|
||||
if valid {
|
||||
info!("Log-in of user: {}", &u.username);
|
||||
let session_token = u.username;
|
||||
id.remember(session_token);
|
||||
Ok(redirect_builder("/admin/index/"))
|
||||
} else {
|
||||
Ok(redirect_builder("/admin/login/"))
|
||||
}
|
||||
} else {
|
||||
Ok(redirect_builder("/admin/login/"))
|
||||
Ok(HttpResponse::Unauthorized().json2(&Status::Error(Message {
|
||||
message: "Failed to Login".to_string(),
|
||||
})))
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
@@ -515,6 +534,36 @@ pub async fn redirect_empty(
|
||||
Ok(redirect_builder(&config.empty_forward_url))
|
||||
}
|
||||
|
||||
#[instrument(skip(id))]
|
||||
pub async fn process_create_link_json(
|
||||
config: web::Data<crate::ServerConfig>,
|
||||
data: web::Json<LinkDelta>,
|
||||
id: Identity,
|
||||
) -> Result<HttpResponse, ServerError> {
|
||||
let new_link = queries::create_link_json(&id, data, &config).await;
|
||||
match new_link {
|
||||
Ok(item) => Ok(HttpResponse::Ok().json2(&Status::Success(Message {
|
||||
message: format!("Successfully saved link: {}", item.item.code),
|
||||
}))),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(skip(id))]
|
||||
pub async fn process_update_link_json(
|
||||
config: web::Data<crate::ServerConfig>,
|
||||
data: web::Json<LinkDelta>,
|
||||
id: Identity,
|
||||
) -> Result<HttpResponse, ServerError> {
|
||||
let new_link = queries::update_link_json(&id, data, &config).await;
|
||||
match new_link {
|
||||
Ok(item) => Ok(HttpResponse::Ok().json2(&Status::Success(Message {
|
||||
message: format!("Successfully updated link: {}", item.item.code),
|
||||
}))),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(skip(id))]
|
||||
pub async fn create_link(
|
||||
tera: web::Data<Tera>,
|
||||
@@ -591,3 +640,14 @@ pub async fn process_link_delete(
|
||||
queries::delete_link(&id, &link_code.0, &config).await?;
|
||||
Ok(redirect_builder("/admin/login/"))
|
||||
}
|
||||
#[instrument(skip(id))]
|
||||
pub async fn process_delete_link_json(
|
||||
id: Identity,
|
||||
config: web::Data<crate::ServerConfig>,
|
||||
data: web::Json<LinkDelta>,
|
||||
) -> Result<HttpResponse, ServerError> {
|
||||
queries::delete_link(&id, &data.code, &config).await?;
|
||||
Ok(HttpResponse::Ok().json2(&Status::Success(Message {
|
||||
message: format!("Successfully deleted link: {}", &data.code),
|
||||
})))
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ form {
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
th,
|
||||
@@ -184,4 +185,12 @@ div.message {
|
||||
background-color: aliceblue;
|
||||
border: 5px solid rgb(90, 90, 90);
|
||||
height: auto;
|
||||
}
|
||||
|
||||
a {
|
||||
cursor: pointer
|
||||
}
|
||||
|
||||
img.trashicon {
|
||||
width: 0.5cm;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<svg width="99.857mm" height="134.86mm" version="1.1" viewBox="0 0 99.857 134.86" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<metadata>
|
||||
<rdf:RDF>
|
||||
<cc:Work rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
|
||||
<dc:title/>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g transform="translate(-58.021 -74.624)">
|
||||
<path d="m65.101 94.716h84.426l-10.99 112.77h-62.431z" style="fill:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:4;stroke:#000"/>
|
||||
<path d="m107.57 113.88v74.637" style="fill:none;stroke-linecap:round;stroke-width:3;stroke:#000"/>
|
||||
<path d="m86.154 113.62 5.4485 74.438" style="fill:none;stroke-linecap:round;stroke-width:3;stroke:#000"/>
|
||||
<path d="m129.73 114.36-5.5378 74.432" style="fill:none;stroke-linecap:round;stroke-width:3;stroke:#000"/>
|
||||
<path d="m59.882 87.653h96.136" style="fill:none;stroke-linecap:round;stroke-width:3.721;stroke:#000"/>
|
||||
<path d="m93.588 87.521c13.139-19.351 20.218-10.383 27.029-0.1697" style="fill:none;stroke-linecap:round;stroke-width:3;stroke:#000"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -1,3 +1,5 @@
|
||||
use shared::datatypes::Secret;
|
||||
|
||||
#[test]
|
||||
fn test_help_of_command_for_breaking_changes() {
|
||||
let output = test_bin::get_test_bin("pslink")
|
||||
@@ -208,7 +210,7 @@ async fn run_server() {
|
||||
);
|
||||
|
||||
let server_config = pslink::ServerConfig {
|
||||
secret: pslink::Secret::new("abcdefghijklmnopqrstuvw".to_string()),
|
||||
secret: Secret::new("abcdefghijklmnopqrstuvw".to_string()),
|
||||
db: std::path::PathBuf::from("links.db"),
|
||||
db_pool,
|
||||
public_url: "localhost:8080".to_string(),
|
||||
|
||||
Reference in New Issue
Block a user