Switch from lazy_static to once_cell
This commit is contained in:
parent
11e71bcbd1
commit
9c4efe027a
5 changed files with 248 additions and 178 deletions
6
Cargo.lock
generated
6
Cargo.lock
generated
|
@ -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"
|
||||||
|
|
|
@ -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"
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
}),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue