Switch from lazy_static to once_cell

This commit is contained in:
Evie Viau-Chow-Stuart 2022-10-12 00:49:17 -04:00
parent 11e71bcbd1
commit 9c4efe027a
Signed by: evie
GPG key ID: 928652CDFCEC8099
5 changed files with 248 additions and 178 deletions

6
Cargo.lock generated
View file

@ -1050,9 +1050,9 @@ dependencies = [
"dotenv", "dotenv",
"futures", "futures",
"lapin", "lapin",
"lazy_static",
"log", "log",
"migration", "migration",
"once_cell",
"picky", "picky",
"pretty_env_logger", "pretty_env_logger",
"rand", "rand",
@ -1859,9 +1859,9 @@ dependencies = [
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.14.0" version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f7254b99e31cad77da24b08ebf628882739a608578bb1bcdfc1f9c21260d7c0" checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1"
[[package]] [[package]]
name = "onig" name = "onig"

View file

@ -38,7 +38,7 @@ chrono = "0.4.22"
zxcvbn = "2.2.1" zxcvbn = "2.2.1"
lazy_static = "1.4.0" once_cell = "1.15.0"
regex = "1.6.0" regex = "1.6.0"

View file

@ -1,14 +1,14 @@
use std::net::SocketAddr;
use argon2::{Argon2, PasswordHash, PasswordVerifier}; use argon2::{Argon2, PasswordHash, PasswordVerifier};
use axum::{Extension, Form, Json};
use axum::extract::ConnectInfo; use axum::extract::ConnectInfo;
use axum::http::{HeaderMap, StatusCode}; use axum::http::{HeaderMap, StatusCode};
use axum::response::IntoResponse; use axum::response::IntoResponse;
use axum::{Extension, Form, Json};
use chrono::{Duration, NaiveDateTime}; use chrono::{Duration, NaiveDateTime};
use lazy_static::lazy_static; use once_cell::sync::Lazy;
use regex::Regex; use regex::Regex;
use sea_orm::*; use sea_orm::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::net::SocketAddr;
use ulid::Ulid; use ulid::Ulid;
use crate::entities::session; use crate::entities::session;
@ -21,51 +21,63 @@ use crate::util::generate_session_token;
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct AuthUserForm { pub struct AuthUserForm {
email: String, email: String,
password: String password: String,
} }
#[derive(Serialize)] #[derive(Serialize)]
pub struct AuthUserFormIssues { pub struct AuthUserFormIssues {
email: Vec<String>, email: Vec<String>,
password: Vec<String> password: Vec<String>,
} }
#[derive(Serialize)] #[derive(Serialize)]
pub struct AuthUserFormResponse { pub struct AuthUserFormResponse {
session_token: Option<String>, session_token: Option<String>,
issues: Option<AuthUserFormIssues> issues: Option<AuthUserFormIssues>,
} }
pub async fn login( pub async fn login(
Extension(ref connection): Extension<DatabaseConnection>, Extension(ref connection): Extension<DatabaseConnection>,
ConnectInfo(addr): ConnectInfo<SocketAddr>, ConnectInfo(addr): ConnectInfo<SocketAddr>,
headers: HeaderMap, headers: HeaderMap,
Form(input): Form<AuthUserForm> Form(input): Form<AuthUserForm>,
) -> impl IntoResponse { ) -> impl IntoResponse {
let mut validation_issues = AuthUserFormIssues { let mut validation_issues = AuthUserFormIssues {
email: vec![], email: vec![],
password: vec![] password: vec![],
}; };
lazy_static! { static EMAIL_RE: Lazy<Regex> = Lazy::new(|| {
static ref EMAIL_RE: Regex = Regex::new(r#"(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])"#) Regex::new(r#"(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])"#)
.expect("Failed to compile email regex"); .expect("Failed to compile email regex")
} });
// Ensure form contents are safe // Ensure form contents are safe
if input.email.is_empty() { if input.email.is_empty() {
validation_issues.email.push("Email cannot be empty.".to_string()); validation_issues
.email
.push("Email cannot be empty.".to_string());
} else if !EMAIL_RE.is_match(&input.email) { } else if !EMAIL_RE.is_match(&input.email) {
validation_issues.email.push("Email is invalid.".to_string()); validation_issues
.email
.push("Email is invalid.".to_string());
} }
if input.password.is_empty(){ if input.password.is_empty() {
validation_issues.password.push("Password cannot be empty.".to_string()); validation_issues
.password
.push("Password cannot be empty.".to_string());
} }
// Return early if we have issues with form content so far // Return early if we have issues with form content so far
if !validation_issues.email.is_empty() || !validation_issues.password.is_empty() { if !validation_issues.email.is_empty() || !validation_issues.password.is_empty() {
return (StatusCode::BAD_REQUEST, Json(AuthUserFormResponse { session_token: None, issues: Some(validation_issues) })); return (
StatusCode::BAD_REQUEST,
Json(AuthUserFormResponse {
session_token: None,
issues: Some(validation_issues),
}),
);
} }
// Check to see if a user with the email exists // Check to see if a user with the email exists
@ -76,9 +88,17 @@ pub async fn login(
.expect("Failed to check database."); .expect("Failed to check database.");
if !existing_user.is_some() { if !existing_user.is_some() {
validation_issues.email.push("An account with this email doesn't exist.".to_string()); validation_issues
.email
.push("An account with this email doesn't exist.".to_string());
return (StatusCode::BAD_REQUEST, Json(AuthUserFormResponse { session_token: None, issues: Some(validation_issues) })); return (
StatusCode::BAD_REQUEST,
Json(AuthUserFormResponse {
session_token: None,
issues: Some(validation_issues),
}),
);
} }
let existing_user = existing_user.unwrap(); let existing_user = existing_user.unwrap();
@ -86,9 +106,20 @@ pub async fn login(
// Check password // Check password
let existing_password_hash = PasswordHash::new(&existing_user.password) let existing_password_hash = PasswordHash::new(&existing_user.password)
.expect("Failed to generate password hash from database."); .expect("Failed to generate password hash from database.");
if !Argon2::default().verify_password(input.password.as_bytes(), &existing_password_hash).is_ok() { if !Argon2::default()
validation_issues.password.push("Incorrect password.".to_string()); .verify_password(input.password.as_bytes(), &existing_password_hash)
return (StatusCode::BAD_REQUEST, Json(AuthUserFormResponse { session_token: None, issues: Some(validation_issues) })); .is_ok()
{
validation_issues
.password
.push("Incorrect password.".to_string());
return (
StatusCode::BAD_REQUEST,
Json(AuthUserFormResponse {
session_token: None,
issues: Some(validation_issues),
}),
);
} }
let session_name; let session_name;
@ -96,16 +127,14 @@ pub async fn login(
None => { None => {
session_name = String::from("Unknown"); session_name = String::from("Unknown");
} }
Some(header) => { Some(header) => match header.to_str() {
match header.to_str() { Ok(header) => {
Ok(header) => { session_name = assemble_session_name(header).await;
session_name = assemble_session_name(header).await;
}
Err(_) => {
session_name = String::from("Unknown");
}
} }
} Err(_) => {
session_name = String::from("Unknown");
}
},
} }
let ip; let ip;
@ -113,16 +142,14 @@ pub async fn login(
None => { None => {
ip = addr.ip().to_string(); ip = addr.ip().to_string();
} }
Some(header) => { Some(header) => match header.to_str() {
match header.to_str() { Ok(header) => {
Ok(header) => { ip = String::from(header);
ip = String::from(header);
}
Err(_) => {
ip = addr.ip().to_string();
}
} }
} Err(_) => {
ip = addr.ip().to_string();
}
},
} }
let session_token = generate_session_token(); let session_token = generate_session_token();
@ -135,19 +162,25 @@ pub async fn login(
ip: ActiveValue::set(ip.clone()), ip: ActiveValue::set(ip.clone()),
token: ActiveValue::Set(session_token.clone()), token: ActiveValue::Set(session_token.clone()),
context: ActiveValue::Set(existing_user.id.clone()), context: ActiveValue::Set(existing_user.id.clone()),
expiry: ActiveValue::Set(expiry) expiry: ActiveValue::Set(expiry),
}; };
let session_res = Session::insert(new_session) let session_res = Session::insert(new_session).exec(connection).await;
.exec(connection)
.await;
match session_res { match session_res {
Ok(_) => { Ok(_) => (
(StatusCode::OK, Json(AuthUserFormResponse { session_token: Some(session_token), issues: None })) StatusCode::OK,
} Json(AuthUserFormResponse {
Err(_) => { session_token: Some(session_token),
(StatusCode::INTERNAL_SERVER_ERROR, Json(AuthUserFormResponse { session_token: None, issues: None })) issues: None,
} }),
),
Err(_) => (
StatusCode::INTERNAL_SERVER_ERROR,
Json(AuthUserFormResponse {
session_token: None,
issues: None,
}),
),
} }
} }

View file

@ -1,81 +1,89 @@
use std::net::SocketAddr;
use axum::{Extension, Form, Json};
use axum::extract::ConnectInfo; use axum::extract::ConnectInfo;
use axum::http::{HeaderMap, StatusCode}; use axum::http::{HeaderMap, StatusCode};
use axum::response::IntoResponse; use axum::response::IntoResponse;
use axum::{Extension, Form, Json};
use chrono::{Duration, NaiveDateTime}; use chrono::{Duration, NaiveDateTime};
use once_cell::sync::Lazy;
use regex::Regex;
use sea_orm::*; use sea_orm::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::net::SocketAddr;
use ulid::Ulid; use ulid::Ulid;
use lazy_static::lazy_static;
use regex::Regex;
use crate::entities::{team, team_member, user};
use crate::entities::prelude::{Team, TeamMember}; use crate::entities::prelude::{Team, TeamMember};
use crate::entities::user::Entity as User; use crate::entities::user::Entity as User;
use crate::entities::{team, team_member, user};
use crate::entities::session; use crate::entities::session;
use crate::entities::session::Entity as Session; use crate::entities::session::Entity as Session;
use crate::util::{generate_session_token, hash_password};
use crate::util::auth::{assemble_session_name, TeamPermissions}; use crate::util::auth::{assemble_session_name, TeamPermissions};
use crate::util::{generate_session_token, hash_password};
#[derive(Deserialize, Clone)] #[derive(Deserialize, Clone)]
pub struct NewUserForm { pub struct NewUserForm {
name: String, name: String,
email: String, email: String,
password: String password: String,
} }
#[derive(Serialize)] #[derive(Serialize)]
pub struct NewUserFormIssues { pub struct NewUserFormIssues {
name: Vec<String>, name: Vec<String>,
email: Vec<String>, email: Vec<String>,
password: Vec<String> password: Vec<String>,
} }
#[derive(Serialize)] #[derive(Serialize)]
pub struct NewUserFormResponse { pub struct NewUserFormResponse {
session_token: Option<String>, session_token: Option<String>,
issues: Option<NewUserFormIssues> issues: Option<NewUserFormIssues>,
} }
pub async fn register( pub async fn register(
Extension(ref connection): Extension<DatabaseConnection>, Extension(ref connection): Extension<DatabaseConnection>,
ConnectInfo(addr): ConnectInfo<SocketAddr>, ConnectInfo(addr): ConnectInfo<SocketAddr>,
headers: HeaderMap, headers: HeaderMap,
Form(input): Form<NewUserForm> Form(input): Form<NewUserForm>,
) -> impl IntoResponse { ) -> impl IntoResponse {
let mut validation_issues = NewUserFormIssues { let mut validation_issues = NewUserFormIssues {
name: vec![], name: vec![],
email: vec![], email: vec![],
password: vec![] password: vec![],
}; };
lazy_static! { static EMAIL_RE: Lazy<Regex> = Lazy::new(|| {
static ref EMAIL_RE: Regex = Regex::new(r#"(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])"#) Regex::new(r#"(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])"#)
.expect("Failed to compile email regex"); .expect("Failed to compile email regex")
} });
// Ensure form contents are safe // Ensure form contents are safe
if input.name.is_empty() { if input.name.is_empty() {
validation_issues.name.push("Name cannot be empty.".to_string()); validation_issues
.name
.push("Name cannot be empty.".to_string());
} else if input.name.len() > 128 { } else if input.name.len() > 128 {
validation_issues.name.push("Name is too long.".to_string()); validation_issues.name.push("Name is too long.".to_string());
} }
if input.email.is_empty() { if input.email.is_empty() {
validation_issues.email.push("Email cannot be empty.".to_string()); validation_issues
.email
.push("Email cannot be empty.".to_string());
} else if !EMAIL_RE.is_match(&input.email) { } else if !EMAIL_RE.is_match(&input.email) {
validation_issues.email.push("Email is invalid.".to_string()); validation_issues
.email
.push("Email is invalid.".to_string());
} }
if input.password.is_empty(){ if input.password.is_empty() {
validation_issues.password.push("Password cannot be empty.".to_string()); validation_issues
.password
.push("Password cannot be empty.".to_string());
} else { } else {
// Check password security // Check password security
let pass_estimate = zxcvbn::zxcvbn(&input.password, &[]) let pass_estimate =
.expect("Failed to check password security."); zxcvbn::zxcvbn(&input.password, &[]).expect("Failed to check password security.");
if pass_estimate.score() <= 2 { if pass_estimate.score() <= 2 {
for i in pass_estimate.feedback().clone().unwrap().suggestions() { for i in pass_estimate.feedback().clone().unwrap().suggestions() {
@ -85,8 +93,17 @@ pub async fn register(
} }
// Return early if we have issues with form content so far // Return early if we have issues with form content so far
if !validation_issues.name.is_empty() || !validation_issues.email.is_empty() || !validation_issues.password.is_empty() { if !validation_issues.name.is_empty()
return (StatusCode::BAD_REQUEST, Json(NewUserFormResponse { session_token: None, issues: Some(validation_issues) })); || !validation_issues.email.is_empty()
|| !validation_issues.password.is_empty()
{
return (
StatusCode::BAD_REQUEST,
Json(NewUserFormResponse {
session_token: None,
issues: Some(validation_issues),
}),
);
} }
// Check to see if a user with the email already exists // Check to see if a user with the email already exists
@ -97,9 +114,17 @@ pub async fn register(
.expect("Failed to check database."); .expect("Failed to check database.");
if existing_user.is_some() { if existing_user.is_some() {
validation_issues.email.push("An account with this email already exists.".to_string()); validation_issues
.email
.push("An account with this email already exists.".to_string());
return (StatusCode::BAD_REQUEST, Json(NewUserFormResponse { session_token: None, issues: Some(validation_issues) })); return (
StatusCode::BAD_REQUEST,
Json(NewUserFormResponse {
session_token: None,
issues: Some(validation_issues),
}),
);
} }
let user_id = Ulid::new().to_string(); let user_id = Ulid::new().to_string();
@ -112,9 +137,7 @@ pub async fn register(
..Default::default() ..Default::default()
}; };
let res = User::insert(new_user.clone()) let res = User::insert(new_user.clone()).exec(connection).await;
.exec(connection)
.await;
return match res { return match res {
Ok(_) => { Ok(_) => {
@ -123,35 +146,36 @@ pub async fn register(
let new_team = team::ActiveModel { let new_team = team::ActiveModel {
id: ActiveValue::set(team_id.clone()), id: ActiveValue::set(team_id.clone()),
name: ActiveValue::Set( name: ActiveValue::Set(format!("{}'s Personal Team", &input.name)),
format!("{}'s Personal Team", &input.name)
),
active: Default::default(), active: Default::default(),
personal: ActiveValue::Set(true) personal: ActiveValue::Set(true),
}; };
let team_creation = Team::insert(new_team.clone()) let team_creation = Team::insert(new_team.clone()).exec(connection).await;
.exec(connection)
.await;
if team_creation.is_err() { if team_creation.is_err() {
// Delete user on team creation error // Delete user on team creation error
new_user.delete(connection).await new_user.delete(connection).await
.expect("Failed to delete user from database after failing to create personal team for said user!"); .expect("Failed to delete user from database after failing to create personal team for said user!");
return (StatusCode::INTERNAL_SERVER_ERROR, Json(NewUserFormResponse { session_token: None, issues: None })); return (
StatusCode::INTERNAL_SERVER_ERROR,
Json(NewUserFormResponse {
session_token: None,
issues: None,
}),
);
} }
// Add user to personal team // Add user to personal team
let team_addition = TeamMember::insert( let team_addition = TeamMember::insert(team_member::ActiveModel {
team_member::ActiveModel { id: ActiveValue::Set(String::from(Ulid::new())),
id: ActiveValue::Set(String::from(Ulid::new())), team_id: ActiveValue::Set(team_id.clone()),
team_id: ActiveValue::Set(team_id.clone()), user_id: ActiveValue::Set(user_id.clone()),
user_id: ActiveValue::Set(user_id.clone()), permission: ActiveValue::Set(TeamPermissions::OWNER.to_string()),
permission: ActiveValue::Set(TeamPermissions::OWNER.to_string()) })
} .exec(connection)
).exec(connection) .await;
.await;
if team_addition.is_err() { if team_addition.is_err() {
// Delete team and user on permission addition error // Delete team and user on permission addition error
@ -160,7 +184,13 @@ pub async fn register(
new_user.delete(connection).await new_user.delete(connection).await
.expect("Failed to delete user from database after failing to add permissions for personal team for said user!"); .expect("Failed to delete user from database after failing to add permissions for personal team for said user!");
return (StatusCode::INTERNAL_SERVER_ERROR, Json(NewUserFormResponse { session_token: None, issues: None })); return (
StatusCode::INTERNAL_SERVER_ERROR,
Json(NewUserFormResponse {
session_token: None,
issues: None,
}),
);
} }
let session_name; let session_name;
@ -168,16 +198,14 @@ pub async fn register(
None => { None => {
session_name = String::from("Unknown"); session_name = String::from("Unknown");
} }
Some(header) => { Some(header) => match header.to_str() {
match header.to_str() { Ok(header) => {
Ok(header) => { session_name = assemble_session_name(header).await;
session_name = assemble_session_name(header).await;
}
Err(_) => {
session_name = String::from("Unknown");
}
} }
} Err(_) => {
session_name = String::from("Unknown");
}
},
} }
let ip; let ip;
@ -185,20 +213,19 @@ pub async fn register(
None => { None => {
ip = addr.ip().to_string(); ip = addr.ip().to_string();
} }
Some(header) => { Some(header) => match header.to_str() {
match header.to_str() { Ok(header) => {
Ok(header) => { ip = String::from(header);
ip = String::from(header);
}
Err(_) => {
ip = addr.ip().to_string();
}
} }
} Err(_) => {
ip = addr.ip().to_string();
}
},
} }
let session_token = generate_session_token(); let session_token = generate_session_token();
let expiry: NaiveDateTime = chrono::offset::Utc::now().naive_local() + Duration::days(20); let expiry: NaiveDateTime =
chrono::offset::Utc::now().naive_local() + Duration::days(20);
// Generate session for newly created user // Generate session for newly created user
let new_session = session::ActiveModel { let new_session = session::ActiveModel {
@ -207,24 +234,34 @@ pub async fn register(
ip: ActiveValue::set(ip.clone()), ip: ActiveValue::set(ip.clone()),
token: ActiveValue::Set(session_token.clone()), token: ActiveValue::Set(session_token.clone()),
context: ActiveValue::Set(user_id.clone()), context: ActiveValue::Set(user_id.clone()),
expiry: ActiveValue::Set(expiry) expiry: ActiveValue::Set(expiry),
}; };
let session_res = Session::insert(new_session) let session_res = Session::insert(new_session).exec(connection).await;
.exec(connection)
.await;
match session_res { match session_res {
Ok(_) => { Ok(_) => (
(StatusCode::CREATED, Json(NewUserFormResponse { session_token: Some(session_token), issues: None })) StatusCode::CREATED,
} Json(NewUserFormResponse {
Err(_) => { session_token: Some(session_token),
(StatusCode::INTERNAL_SERVER_ERROR, Json(NewUserFormResponse { session_token: None, issues: None })) issues: None,
} }),
),
Err(_) => (
StatusCode::INTERNAL_SERVER_ERROR,
Json(NewUserFormResponse {
session_token: None,
issues: None,
}),
),
} }
} }
Err(_) => { Err(_) => (
(StatusCode::INTERNAL_SERVER_ERROR, Json(NewUserFormResponse { session_token: None, issues: None })) StatusCode::INTERNAL_SERVER_ERROR,
} Json(NewUserFormResponse {
} session_token: None,
} issues: None,
}),
),
};
}

View file

@ -5,21 +5,21 @@ use std::fmt::Formatter;
use async_trait::async_trait; use async_trait::async_trait;
use axum::extract::FromRequestParts; use axum::extract::FromRequestParts;
use axum::http::{header::AUTHORIZATION, StatusCode};
use axum::http::request::Parts; use axum::http::request::Parts;
use lazy_static::lazy_static; use axum::http::{header::AUTHORIZATION, StatusCode};
use once_cell::sync::Lazy;
use sea_orm::{ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter}; use sea_orm::{ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter};
use user_agent_parser::{OS, Product};
use user_agent_parser::UserAgentParser; use user_agent_parser::UserAgentParser;
use user_agent_parser::{Product, OS};
use crate::entities::{session, user};
use crate::entities::prelude::{Session, User}; use crate::entities::prelude::{Session, User};
use crate::entities::{session, user};
pub enum TeamPermissions { pub enum TeamPermissions {
OWNER, OWNER,
ADMIN, ADMIN,
EDITOR, EDITOR,
VIEWER VIEWER,
} }
impl fmt::Display for TeamPermissions { impl fmt::Display for TeamPermissions {
@ -28,13 +28,16 @@ impl fmt::Display for TeamPermissions {
TeamPermissions::OWNER => write!(f, "OWNER"), TeamPermissions::OWNER => write!(f, "OWNER"),
TeamPermissions::ADMIN => write!(f, "ADMIN"), TeamPermissions::ADMIN => write!(f, "ADMIN"),
TeamPermissions::EDITOR => write!(f, "EDITOR"), TeamPermissions::EDITOR => write!(f, "EDITOR"),
TeamPermissions::VIEWER => write!(f, "VIEWER") TeamPermissions::VIEWER => write!(f, "VIEWER"),
} }
} }
} }
/// Gets a user model and session id from a supplied session token /// Gets a user model and session id from a supplied session token
pub async fn get_user_from_token(token: String, connection: &DatabaseConnection) -> Option<(user::Model, String)> { pub async fn get_user_from_token(
token: String,
connection: &DatabaseConnection,
) -> Option<(user::Model, String)> {
let requested_session: Option<session::Model> = Session::find() let requested_session: Option<session::Model> = Session::find()
.filter(session::Column::Token.eq(token)) .filter(session::Column::Token.eq(token))
.one(connection) .one(connection)
@ -42,9 +45,7 @@ pub async fn get_user_from_token(token: String, connection: &DatabaseConnection)
.expect("Failed to retrieve session from the database."); .expect("Failed to retrieve session from the database.");
return match requested_session { return match requested_session {
None => { None => None,
None
}
Some(_) => { Some(_) => {
let requested_session = requested_session.unwrap(); let requested_session = requested_session.unwrap();
@ -56,15 +57,16 @@ pub async fn get_user_from_token(token: String, connection: &DatabaseConnection)
match contexted_user { match contexted_user {
None => { None => {
error!("Session {} still exists for user {} of which doesn't exist!", requested_session.id, requested_session.context); error!(
"Session {} still exists for user {} of which doesn't exist!",
requested_session.id, requested_session.context
);
None None
} }
Some(_) => { Some(_) => Some((contexted_user.unwrap(), requested_session.id)),
Some((contexted_user.unwrap(), requested_session.id))
}
} }
} }
} };
} }
#[derive(Clone)] #[derive(Clone)]
@ -72,8 +74,8 @@ pub struct UserFromBearer(pub (user::Model, String));
#[async_trait] #[async_trait]
impl<S> FromRequestParts<S> for UserFromBearer impl<S> FromRequestParts<S> for UserFromBearer
where where
S: Send + Sync, S: Send + Sync,
{ {
type Rejection = (StatusCode, &'static str); type Rejection = (StatusCode, &'static str);
@ -82,7 +84,10 @@ impl<S> FromRequestParts<S> for UserFromBearer
let authorisation = parts let authorisation = parts
.headers .headers
.get(AUTHORIZATION) .get(AUTHORIZATION)
.ok_or((StatusCode::UNAUTHORIZED, "`Authorization` header is missing"))? .ok_or((
StatusCode::UNAUTHORIZED,
"`Authorization` header is missing",
))?
.to_str() .to_str()
.map_err(|_| { .map_err(|_| {
( (
@ -96,16 +101,16 @@ impl<S> FromRequestParts<S> for UserFromBearer
match split { match split {
Some((name, contents)) if name == "Bearer" => { Some((name, contents)) if name == "Bearer" => {
// Get database connection from header // Get database connection from header
let connection: &DatabaseConnection = parts.extensions.get::<DatabaseConnection>() let connection: &DatabaseConnection = parts
.extensions
.get::<DatabaseConnection>()
.expect("Failed to get database connection from users extractor"); .expect("Failed to get database connection from users extractor");
match get_user_from_token(contents.to_string(), connection).await { match get_user_from_token(contents.to_string(), connection).await {
None => { None => Err((StatusCode::UNAUTHORIZED, "Provided token is invalid")),
Err((StatusCode::UNAUTHORIZED, "Provided token is invalid")) Some(user) => Ok(Self(user)),
}
Some(user) => Ok(Self(user))
} }
}, }
_ => Err(( _ => Err((
StatusCode::BAD_REQUEST, StatusCode::BAD_REQUEST,
"`Authorization` header must be a bearer token", "`Authorization` header must be a bearer token",
@ -117,40 +122,35 @@ impl<S> FromRequestParts<S> for UserFromBearer
pub async fn assemble_session_name(header: &str) -> String { pub async fn assemble_session_name(header: &str) -> String {
// TODO: Maybe reconsider having a static session name? // TODO: Maybe reconsider having a static session name?
lazy_static! { static UAP: Lazy<UserAgentParser> = Lazy::new(|| {
static ref UAP: UserAgentParser = UserAgentParser::from_path(&env::var("UAP_REGEXES").unwrap_or(String::from("./regexes.yaml"))).expect("Failed to load regexes.yaml"); UserAgentParser::from_path(
} &env::var("UAP_REGEXES").unwrap_or(String::from("./regexes.yaml")),
)
.expect("Failed to load regexes.yaml")
});
let product: Product = UAP.parse_product(&header); let product: Product = UAP.parse_product(&header);
let os: OS = UAP.parse_os(&header); let os: OS = UAP.parse_os(&header);
let mut session_name_builder: String = String::new(); let mut session_name_builder: String = String::new();
session_name_builder.push_str( session_name_builder.push_str(&product.name.unwrap_or(Cow::from("Unknown")));
&product.name.unwrap_or(Cow::from("Unknown"))
);
if product.major.is_some() { if product.major.is_some() {
session_name_builder.push_str(" "); session_name_builder.push_str(" ");
session_name_builder.push_str( session_name_builder.push_str(&product.major.unwrap())
&product.major.unwrap()
)
} }
session_name_builder.push_str(" / "); session_name_builder.push_str(" / ");
session_name_builder.push_str( session_name_builder.push_str(&os.name.unwrap_or(Cow::from("Unknown")));
&os.name.unwrap_or(Cow::from("Unknown"))
);
if os.major.is_some() { if os.major.is_some() {
session_name_builder.push_str(" "); session_name_builder.push_str(" ");
session_name_builder.push_str( session_name_builder.push_str(&os.major.unwrap())
&os.major.unwrap()
)
}; };
return session_name_builder return session_name_builder;
} }