new laptop setup

This commit is contained in:
2022-12-24 16:34:17 +01:00
parent 5b95621d04
commit 31b47e892d
55 changed files with 789 additions and 5 deletions
+104
View File
@@ -0,0 +1,104 @@
extern crate hmac;
extern crate jwt;
extern crate sha2;
use std::collections::BTreeMap;
use actix_web::HttpRequest;
use hmac::{Hmac, Mac};
use jwt::{Header, SignWithKey, Token, VerifyWithKey};
use sha2::Sha256;
pub struct JwtToken {
pub user_id: i32,
pub body: String,
}
type HmacSha256 = Hmac<Sha256>;
impl JwtToken {
pub fn encode(user_id: i32) -> String {
let key: HmacSha256 = HmacSha256::new_from_slice(b"secret").unwrap();
let mut claims = BTreeMap::new();
claims.insert("user_id", user_id);
claims.sign_with_key(&key).unwrap()
}
pub fn decode(encoded_token: String) -> Result<JwtToken, &'static str> {
let key: HmacSha256 = HmacSha256::new_from_slice(b"secret").unwrap();
let token_str: &str = encoded_token.as_str();
let token: Result<Token<Header, BTreeMap<String, i32>, jwt::Verified>, jwt::Error> =
VerifyWithKey::verify_with_key(token_str, &key);
match token {
Ok(token) => {
let _header = token.header();
let claims = token.claims();
Ok(JwtToken {
user_id: claims["user_id"],
body: encoded_token,
})
}
Err(_err) => Err("could not decode token"),
}
}
pub fn decode_from_request(request: HttpRequest) -> Result<JwtToken, &'static str> {
match request.headers().get("user-token") {
Some(token) => JwtToken::decode(String::from(token.to_str().unwrap())),
None => Err("There is no token"),
}
}
}
#[cfg(test)]
mod jwt_test {
use actix_web::{http::header, test};
use super::JwtToken;
#[test]
async fn encode_decode() {
let encoded_token: String = JwtToken::encode(32);
let decoded_token: JwtToken = JwtToken::decode(encoded_token).unwrap();
assert_eq!(32, decoded_token.user_id);
}
#[test]
async fn decode_incorrect_token() {
let encoded_token: String = String::from("test");
match JwtToken::decode(encoded_token) {
Err(message) => assert_eq!(message, "could not decode token"),
_ => panic!("Incorrect token should not be able to decode."),
}
}
#[actix_web::test]
async fn decode_from_request_with_correct_token() {
let encoded_token: String = JwtToken::encode(32);
let request = test::TestRequest::default()
.insert_header(header::ContentType::json())
.insert_header(("user-token", encoded_token))
.to_http_request();
let out_come = JwtToken::decode_from_request(request);
match out_come {
Ok(token) => assert_eq!(32, token.user_id),
_ => panic!("Token is not returned with it should be."),
}
}
#[actix_web::test]
async fn decode_from_request_with_no_token() {
let request = test::TestRequest::default()
.insert_header(("test", "test"))
.to_http_request();
let out_come = JwtToken::decode_from_request(request);
match out_come {
Err(message) => assert_eq!("There is no token", message),
_ => panic!("Token should not be returned when it is not present in the header."),
}
}
}
+55
View File
@@ -0,0 +1,55 @@
use actix_web::dev::ServiceRequest;
pub mod jwt;
pub mod processes;
use crate::auth::processes::check_password;
use crate::auth::processes::extract_header_token;
pub fn process_token(request: &ServiceRequest) -> Result<String, &'static str> {
match extract_header_token(request) {
Ok(token) => check_password(token),
Err(message) => Err(message),
}
}
#[cfg(test)]
mod mod_test {
use actix_web::test::TestRequest;
use super::{jwt::JwtToken, process_token};
#[test]
fn process_token_test() {
let token = JwtToken::encode(32);
let request = TestRequest::delete()
.insert_header(("user-token", token))
.to_srv_request();
match process_token(&request) {
Ok(message) => assert_eq!("passed", message),
Err(_) => panic!("process token failed"),
}
}
#[actix_web::test]
async fn process_token_not_existing() {
let request = TestRequest::default().to_srv_request();
match process_token(&request) {
Err(error) => assert_eq!("there is no token", error),
_ => panic!("Not existing token should not be processes"),
}
}
#[actix_web::test]
async fn process_wrong_token() {
let request = TestRequest::default()
.insert_header(("user-token", "bla"))
.to_srv_request();
match process_token(&request) {
Err(error) => assert_eq!("could not decode token", error),
_ => panic!("Not existing token should not be processes"),
}
}
}
+75
View File
@@ -0,0 +1,75 @@
use super::jwt;
use actix_web::dev::ServiceRequest;
pub fn check_password(password: String) -> Result<String, &'static str> {
match jwt::JwtToken::decode(password) {
Ok(_token) => Ok(String::from("passed")),
Err(message) => Err(message),
}
}
pub fn extract_header_token(request: &ServiceRequest) -> Result<String, &'static str> {
match request.headers().get("user-token") {
Some(token) => match token.to_str() {
Ok(processed_password) => Ok(String::from(processed_password)),
Err(_processed_password) => Err("there was an error processing token"),
},
None => Err("there is no token"),
}
}
#[cfg(test)]
mod processes_test {
use actix_web::test::TestRequest;
use crate::auth::jwt::JwtToken;
use super::check_password;
#[test]
fn check_correct_password() {
let password_string: String = JwtToken::encode(32);
let result = check_password(password_string);
match result {
Ok(check) => assert_eq!("passed", check),
_ => panic!("Check correct password failed."),
}
}
#[test]
fn incorrect_check_password() {
let password: String = String::from("test");
match check_password(password) {
Err(message) => assert_eq!("could not decode token", message),
_ => panic!("check password should not be able to be decoded"),
}
}
#[test]
fn successful_extract_header_token() {
let request = TestRequest::default()
.insert_header(("user-token", "token"))
.to_srv_request();
match super::extract_header_token(&request) {
Ok(processed_password) => assert_eq!("token", processed_password),
_ => panic!("failed extract_header_token"),
}
}
#[test]
fn failed_extract_header_token() {
let request = TestRequest::default()
.insert_header(("wrong", "bla"))
.to_srv_request();
match super::extract_header_token(&request) {
Err(processed_password) => assert_eq!("there is no token", processed_password),
_ => panic!("Extract header token should fail when not provided."),
}
}
}
+12
View File
@@ -0,0 +1,12 @@
use diesel::pg::PgConnection;
use diesel::prelude::*;
use dotenv::dotenv;
use std::env;
pub fn establish_connection() -> PgConnection {
dotenv().ok();
let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
PgConnection::establish(&database_url)
.unwrap_or_else(|_| panic!("Error connecting to database {}", database_url))
}
View File
+7
View File
@@ -0,0 +1,7 @@
use serde::Deserialize;
#[derive(Deserialize, Debug)]
pub struct Login {
pub username: String,
pub password: String,
}
+2
View File
@@ -0,0 +1,2 @@
pub mod login;
pub mod new_user;
+8
View File
@@ -0,0 +1,8 @@
use serde::Deserialize;
#[derive(Deserialize)]
pub struct NewUserSchema {
pub name: String,
pub email: String,
pub password: String,
}
Regular → Executable
+1 -2
View File
@@ -3,9 +3,7 @@ extern crate dotenv;
use actix_service::Service;
use actix_web::{App, HttpResponse, HttpServer};
use env_logger;
use futures::future::{ok, Either};
use log;
mod auth;
mod database;
mod json_serialization;
@@ -31,6 +29,7 @@ async fn main() -> std::io::Result<()> {
Err(_message) => passed = false,
}
} else {
log::warn!("No auth check done.");
passed = true;
}
+13
View File
@@ -0,0 +1,13 @@
use super::super::feed;
use super::super::user::user::User;
use diesel::{Associations, Identifiable, Queryable};
#[derive(Queryable, Identifiable, Associations)]
#[diesel(belongs_to(User))]
#[diesel(table_name=feed)]
pub struct Feed {
pub id: i32,
pub user_id: i32,
pub title: String,
pub url: String,
}
+1
View File
@@ -0,0 +1 @@
pub mod feed;
+2
View File
@@ -0,0 +1,2 @@
+1
View File
@@ -0,0 +1 @@
mod feed_item;
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
+9
View File
@@ -0,0 +1,9 @@
use actix_web::{HttpRequest, HttpResponse};
use crate::auth::jwt::JwtToken;
pub async fn get(req: HttpRequest) -> HttpResponse {
let token: JwtToken = JwtToken::decode_from_request(req).unwrap();
todo!();
}
Regular → Executable
+15
View File
@@ -1 +1,16 @@
use actix_web::web;
use crate::views::path::Path;
pub mod feeds;
mod get;
pub fn feed_factory(app: &mut web::ServiceConfig) {
let base_path: Path = Path {
prefix: String::from("/article"),
backend: true,
};
app.route(
&base_path.define(String::from("/get")),
web::get().to(get::get),
);
}
Regular → Executable
View File
+23
View File
@@ -0,0 +1,23 @@
use std::fs;
pub fn read_file(file_path: &str) -> String {
let data: String = fs::read_to_string(file_path)
.expect(format!("Unable to read file {}", file_path).as_str());
return data;
}
pub fn add_component(component_tag: String, html_data: String) -> String {
let css_tag: String = component_tag.to_uppercase() + &String::from("_CSS");
let html_tag: String = component_tag.to_uppercase() + &String::from("_HTML");
let css_path = String::from("./templates/components/")
+ &component_tag.to_lowercase()
+ &String::from(".css");
let css_loaded = read_file(&css_path);
let html_path = String::from("./templates/components/")
+ &component_tag.to_lowercase()
+ &String::from(".html");
let html_loaded = read_file(&html_path);
let html_data = html_data.replace(html_tag.as_str(), &html_loaded);
let html_data = html_data.replace(css_tag.as_str(),&css_loaded);
return html_data
}
+17
View File
@@ -0,0 +1,17 @@
use super::content_loader::read_file;
use actix_web::HttpResponse;
pub async fn login() -> HttpResponse {
let mut html_data = read_file(&String::from("./templates/login.html"));
let javascript_data = read_file(&String::from("./javascript/login.js"));
let css_data = read_file(&String::from("./css/main.css"));
let base_css_data = read_file(&String::from("./css/base.css"));
html_data = html_data.replace("{{JAVASCRIPT}}", &javascript_data);
html_data = html_data.replace("{{CSS}}", &css_data);
html_data = html_data.replace("{{BASE_CSS}}", &base_css_data);
HttpResponse::Ok()
.content_type("text/html; charset=utf-8")
.body(html_data)
}
+15
View File
@@ -0,0 +1,15 @@
use actix_web::HttpResponse;
pub async fn logout() -> HttpResponse {
HttpResponse::Ok()
.content_type("text/html; charset=utf-8")
.body(
"<html>\
<script>\
localStorage.removeItem('user-token'); \
window.location.replace(document.location.origin);\
</script>\
</html>
",
)
}
+35
View File
@@ -0,0 +1,35 @@
use actix_web::web;
mod content_loader;
mod login;
mod logout;
mod reader;
use super::path::Path;
/// This function adds the app views to the web server serving HTML.
///
/// # Arguments
/// * (&mut web::ServiceConfig): reference to the app for configuration
///
/// # Returns
/// None
pub fn app_factory(app: &mut web::ServiceConfig) {
// define the path struct
let base_path: Path = Path {
prefix: String::from("/"),
backend: false,
};
// define the routes for the app
app.route(
&base_path.define(String::from("")),
web::get().to(reader::reader),
);
app.route(
&base_path.define(String::from("login")),
web::get().to(login::login),
);
app.route(
&base_path.define(String::from("logout")),
web::get().to(logout::logout),
);
}
+18
View File
@@ -0,0 +1,18 @@
use super::content_loader::{add_component, read_file};
use actix_web::HttpResponse;
pub async fn reader() -> HttpResponse {
let mut html_data = read_file("./templates/reader.html");
let javascript = read_file("./javascript/main.js");
let css = read_file("./css/main.css");
let base_css = read_file("./css/base.css");
html_data = html_data.replace("JAVASCRIPT", &javascript);
html_data = html_data.replace("CSS", &css);
html_data = html_data.replace("BASE_CSS", &base_css);
// html_data = add_component(String::from("header"), html_data);
HttpResponse::Ok()
.content_type("text/html; charset=utf-8")
.body(html_data)
}
+44
View File
@@ -0,0 +1,44 @@
use crate::database::establish_connection;
use crate::diesel;
use crate::json_serialization::login::Login;
use crate::models::user::user::User;
use crate::schema::users;
use crate::{auth::jwt::JwtToken, schema::users::username};
use actix_web::{web, HttpResponse};
use diesel::prelude::*;
pub async fn login(credentials: web::Json<Login>) -> HttpResponse {
let username_cred: String = credentials.username.clone();
let password: String = credentials.password.clone();
let mut connection = establish_connection();
let users = users::table
.filter(username.eq(username_cred.as_str()))
.load::<User>(&mut connection)
.unwrap();
if users.is_empty() {
return HttpResponse::NotFound().await.unwrap();
} else if users.len() > 1 {
log::error!(
"multiple user have the usernam: {}",
credentials.username.clone()
);
return HttpResponse::Conflict().await.unwrap();
}
let user: &User = &users[0];
match user.clone().verify(password) {
true => {
log::info!("verified password successfully");
let token: String = JwtToken::encode(user.clone().id);
HttpResponse::Ok()
.insert_header(("token", token))
.await
.unwrap()
}
false => HttpResponse::Unauthorized().await.unwrap(),
}
}
+3
View File
@@ -0,0 +1,3 @@
pub async fn logout() -> String {
format!("logout view")
}
+23
View File
@@ -0,0 +1,23 @@
use actix_web::web;
use actix_web::web::ServiceConfig;
use super::path::Path;
mod login;
mod logout;
pub fn auth_factory(app: &mut ServiceConfig) {
let base_path: Path = Path {
prefix: String::from("/auth"),
backend: true,
};
app.route(
&base_path.define(String::from("/login")),
web::post().to(login::login),
);
app.route(
&base_path.define(String::from("/logout")),
web::post().to(logout::logout),
);
}
Regular → Executable
+7 -3
View File
@@ -1,10 +1,14 @@
use actix_web::web;
use crate::reader;
mod app;
mod auth;
pub(crate) mod path;
mod users;
pub fn views_factory(app: &mut web::ServiceConfig) {
// auth::auth_factory(app);
// to_do::item_factory(app);
// app::app_factory(app);
auth::auth_factory(app);
app::app_factory(app);
users::user_factory(app);
reader::feed_factory(app);
}
Regular → Executable
+1
View File
@@ -14,3 +14,4 @@ impl Path {
}
}
}
+25
View File
@@ -0,0 +1,25 @@
use crate::database::establish_connection;
use crate::diesel;
use crate::json_serialization::new_user::NewUserSchema;
use crate::models::user::new_user::NewUser;
use crate::schema::users;
use actix_web::{web, HttpResponse};
use diesel::prelude::*;
pub async fn create(new_user: web::Json<NewUserSchema>) -> HttpResponse {
let mut connection = establish_connection();
let name: String = new_user.name.clone();
let email: String = new_user.email.clone();
let new_password: String = new_user.password.clone();
let new_user = NewUser::new(name, email, new_password);
let insert_result = diesel::insert_into(users::table)
.values(&new_user)
.execute(&mut connection);
match insert_result {
Ok(_) => HttpResponse::Created().await.unwrap(),
Err(_) => HttpResponse::Conflict().await.unwrap(),
}
}
+15
View File
@@ -0,0 +1,15 @@
use super::path::Path;
use actix_web::web;
mod create;
pub fn user_factory(app: &mut web::ServiceConfig) {
let base_path: Path = Path {
prefix: String::from("/user"),
backend: true,
};
app.route(
&base_path.define(String::from("/create")),
web::post().to(create::create),
);
}