Updating ui
This commit is contained in:
@@ -7,8 +7,7 @@ DATABASE_HOST=localhost
|
||||
DATABASE_PORT=5432
|
||||
|
||||
KEYS_DIR_PATH=
|
||||
ACCESS_TOKEN_MAXAGE=5
|
||||
REFRESH_TOKEN_MAXAGE=30
|
||||
SESSION_TTL=1440
|
||||
|
||||
REDIS_HOST=localhost
|
||||
REDIS_PORT=6379
|
||||
|
||||
@@ -25,15 +25,12 @@ r2d2 = "0.8.10"
|
||||
lazy_static = "1.4.0"
|
||||
uuid = { version = "1.4.1", features = ["serde", "v4"] }
|
||||
argon2 = "0.5.2"
|
||||
jsonwebtoken = "9.0.0"
|
||||
redis = { version = "0.23.3", features = ["tokio-comp", "connection-manager", "r2d2"] }
|
||||
base64 = "0.21.4"
|
||||
rust-s3 = "0.33.0"
|
||||
actix-multipart = "0.6.1"
|
||||
openssl = "0.10.60" # Resolve `openssl` `X509StoreRef::objects` is unsound #10
|
||||
rand = "0.8.5"
|
||||
sha2 = "0.10.8"
|
||||
rand_chacha = "0.3.1"
|
||||
jsonwebtoken = "9.2.0"
|
||||
|
||||
[dependencies.tokio]
|
||||
version = "1.32.0"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
mod model;
|
||||
mod routes;
|
||||
mod tokens;
|
||||
mod session;
|
||||
|
||||
pub use model::*;
|
||||
pub use tokens::*;
|
||||
pub use session::*;
|
||||
pub use routes::init_routes;
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
use std::{future::{ready, Ready}, env};
|
||||
use std::future::{ready, Ready};
|
||||
use actix_web::{FromRequest, Error as ActixError, HttpRequest, dev::Payload, http};
|
||||
use diesel::prelude::*;
|
||||
use log::error;
|
||||
use redis::Commands;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use siren::ServiceError;
|
||||
|
||||
use crate::storage::{schema::users, connection};
|
||||
|
||||
use super::{hash, AccessToken};
|
||||
use super::{hash, Session, SESSION_COOKIE_NAME};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct RegisterUser {
|
||||
@@ -58,7 +56,6 @@ impl QueryUser {
|
||||
pub fn get_by_email(email: &str) -> Result<QueryUser, ServiceError> {
|
||||
let mut conn = connection()?;
|
||||
// Check if the user exists by email, case insensitive
|
||||
|
||||
let user = users::table
|
||||
.filter(users::email.eq(email.to_lowercase()))
|
||||
.first(&mut conn)?;
|
||||
@@ -121,17 +118,17 @@ impl From<QueryUser> for ResponseUser {
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct JwtAuth {
|
||||
pub struct Auth {
|
||||
pub id: String,
|
||||
pub user: ResponseUser
|
||||
}
|
||||
|
||||
impl FromRequest for JwtAuth {
|
||||
impl FromRequest for Auth {
|
||||
type Error = ActixError;
|
||||
type Future = Ready<Result<Self, Self::Error>>;
|
||||
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
||||
let access_token_string = match req
|
||||
.cookie("access_token")
|
||||
let session_id = match req
|
||||
.cookie(SESSION_COOKIE_NAME)
|
||||
.map(|c| c.value().to_string())
|
||||
.or_else(|| {
|
||||
req.headers().get(http::header::AUTHORIZATION)
|
||||
@@ -143,54 +140,17 @@ impl FromRequest for JwtAuth {
|
||||
message: "Unauthorized".to_string()
|
||||
})))
|
||||
};
|
||||
|
||||
let keys_dir = env::var("KEYS_DIR_PATH").expect("KEYS_DIR_PATH must be set");
|
||||
let public_key = std::fs::read_to_string(format!("{}/public_key.pem", keys_dir)).expect("Failed to read access public key");
|
||||
|
||||
let access_token = match AccessToken::decode(&access_token_string, &public_key) {
|
||||
Ok(token_details) => token_details,
|
||||
Err(err) => {
|
||||
error!("Failed to verify access token: {}", err);
|
||||
return ready(Err(ActixError::from(ServiceError {
|
||||
status: 401,
|
||||
message: format!("Access token is invaid: {}", err)
|
||||
})))
|
||||
}
|
||||
};
|
||||
|
||||
let mut conn = match crate::storage::redis_connection() {
|
||||
Ok(conn) => conn,
|
||||
Err(err) => {
|
||||
error!("Failed to get redis connection: {}", err);
|
||||
return ready(Err(ActixError::from(ServiceError {
|
||||
status: 500,
|
||||
message: format!("Failed to get redis connection: {}", err)
|
||||
})))
|
||||
}
|
||||
};
|
||||
let user_email = match conn.get::<_, String>(access_token.id.clone().to_string()) {
|
||||
Ok(result) => serde_json::from_str::<AccessToken>(&result).unwrap().email,
|
||||
Err(_) => {
|
||||
return ready(Err(ActixError::from(ServiceError {
|
||||
status: 401,
|
||||
message: format!("Access token is invalid")
|
||||
})))
|
||||
}
|
||||
};
|
||||
|
||||
match QueryUser::get_by_email(&user_email) {
|
||||
Ok(user) => {
|
||||
ready(Ok(JwtAuth { id: access_token.id, user: user.into() }))
|
||||
}
|
||||
Err(_) => return ready(Err(ActixError::from(ServiceError {
|
||||
status: 401,
|
||||
message: format!("User does not exist")
|
||||
})))
|
||||
let ip_address = req.peer_addr().unwrap().ip().to_string();
|
||||
|
||||
match Session::verify(&session_id, &ip_address) {
|
||||
Ok(v) => return ready(Ok(Auth { id: v.0.id, user: v.1.into() })),
|
||||
Err(err) => return ready(Err(ActixError::from(err)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn verify_role(auth: &JwtAuth, role: &str) -> Result<(), ServiceError> {
|
||||
pub fn verify_role(auth: &Auth, role: &str) -> Result<(), ServiceError> {
|
||||
if auth.user.role == role {
|
||||
Ok(())
|
||||
} else {
|
||||
|
||||
@@ -3,10 +3,9 @@ use std::env;
|
||||
use actix_web::{get, post, web, HttpResponse, ResponseError, cookie::{Cookie, time::Duration}, HttpRequest};
|
||||
use log::error;
|
||||
use redis::AsyncCommands;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use siren::ServiceError;
|
||||
|
||||
use crate::{auth::{InsertUser, JwtAuth, LoginRequest, QueryUser, RefreshToken, RegisterUser}, storage};
|
||||
use crate::{auth::{InsertUser, Auth, LoginRequest, QueryUser, RegisterUser, Session, SESSION_COOKIE_NAME}, storage::{self}};
|
||||
|
||||
use super::verify_hash;
|
||||
|
||||
@@ -47,14 +46,7 @@ async fn login(request: HttpRequest, login_request: web::Json<LoginRequest>) ->
|
||||
};
|
||||
// Verify password
|
||||
if verify_hash(&login_request.password, &query_user.hash) {
|
||||
let mut refresh_token = RefreshToken::new(&email, &ip_address);
|
||||
let access_token = match refresh_token.create_access_token() {
|
||||
Ok(token) => token,
|
||||
Err(err) => {
|
||||
error!("Failed to generate access token: {}", err);
|
||||
return ResponseError::error_response(&err)
|
||||
}
|
||||
};
|
||||
let session = Session::new(&email, &ip_address);
|
||||
|
||||
let mut conn = match storage::redis_async_connection().await {
|
||||
Ok(conn) => conn,
|
||||
@@ -64,51 +56,33 @@ async fn login(request: HttpRequest, login_request: web::Json<LoginRequest>) ->
|
||||
}
|
||||
};
|
||||
|
||||
let access_token_max_age = env::var("ACCESS_TOKEN_MAXAGE")
|
||||
.expect("ACCESS_TOKEN_MAXAGE must be set")
|
||||
let session_ttl = env::var("SESSION_TTL")
|
||||
.expect("SESSION_TTL must be set")
|
||||
.parse::<i64>()
|
||||
.expect("ACCESS_TOKEN_MAXAGE must be an integer");
|
||||
.expect("SESSION_TTL must be an integer");
|
||||
|
||||
let refresh_token_max_age = env::var("REFRESH_TOKEN_MAXAGE")
|
||||
.expect("REFRESH_TOKEN_MAXAGE must be set")
|
||||
.parse::<i64>()
|
||||
.expect("REFRESH_TOKEN_MAXAGE must be an integer");
|
||||
|
||||
let access_result: redis::RedisResult<()> = conn.set_ex(access_token.id.to_string(), &serde_json::to_string(&access_token).unwrap(), (access_token_max_age * 60) as usize).await;
|
||||
if let Err(err) = access_result {
|
||||
let session_result: redis::RedisResult<()> = conn.set_ex(session.id.to_string(), &serde_json::to_string(&session).unwrap(), (session_ttl * 60) as usize).await;
|
||||
if let Err(err) = session_result {
|
||||
error!("Failed to set access token in redis: {}", err);
|
||||
return ResponseError::error_response(&ServiceError::from(err))
|
||||
};
|
||||
|
||||
let refresh_result: redis::RedisResult<()> = conn.set_ex(refresh_token.id.to_string(), &serde_json::to_string(&refresh_token).unwrap(), (refresh_token_max_age * 60) as usize).await;
|
||||
if let Err(err) = refresh_result {
|
||||
error!("Failed to set refresh token in redis: {}", err);
|
||||
return ResponseError::error_response(&ServiceError::from(err))
|
||||
};
|
||||
|
||||
let access_cookie = Cookie::build("access_token", access_token.token.clone().unwrap())
|
||||
let session_cookie = Cookie::build(SESSION_COOKIE_NAME, session.id.clone())
|
||||
.path("/")
|
||||
.max_age(Duration::new(access_token_max_age * 60, 0))
|
||||
.max_age(Duration::new(session_ttl * 60, 0))
|
||||
.http_only(true)
|
||||
.secure(true)
|
||||
.finish();
|
||||
let refresh_cookie = Cookie::build("refresh_token", refresh_token.id.clone())
|
||||
let user_id_cookie = Cookie::build("user_id", session.user_id.clone())
|
||||
.path("/")
|
||||
.max_age(Duration::new(refresh_token_max_age * 60, 0))
|
||||
.http_only(true)
|
||||
.secure(true)
|
||||
.finish();
|
||||
let logged_in_cookie = Cookie::build("logged_in", "true")
|
||||
.path("/")
|
||||
.max_age(Duration::new(access_token_max_age * 60, 0))
|
||||
.max_age(Duration::new(session_ttl * 60, 0))
|
||||
.http_only(false)
|
||||
.finish();
|
||||
|
||||
HttpResponse::Ok()
|
||||
.cookie(access_cookie)
|
||||
.cookie(refresh_cookie)
|
||||
.cookie(logged_in_cookie)
|
||||
.json(JwtAuth { id: access_token.id, user: query_user.into() })
|
||||
.cookie(session_cookie)
|
||||
.cookie(user_id_cookie)
|
||||
.json(Auth { id: session.id, user: query_user.into() })
|
||||
} else {
|
||||
return ResponseError::error_response(&ServiceError {
|
||||
status: 401,
|
||||
@@ -117,29 +91,9 @@ async fn login(request: HttpRequest, login_request: web::Json<LoginRequest>) ->
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct RefreshParams {
|
||||
refresh_token_rotation: Option<bool>
|
||||
}
|
||||
|
||||
#[get("/refresh")]
|
||||
async fn refresh(req: HttpRequest) -> HttpResponse {
|
||||
async fn refresh(req: HttpRequest, auth: Auth) -> HttpResponse {
|
||||
let ip_address = req.peer_addr().unwrap().ip().to_string();
|
||||
let params = match web::Query::<RefreshParams>::from_query(req.query_string()) {
|
||||
Ok(params) => params,
|
||||
Err(err) => return ResponseError::error_response(&ServiceError {
|
||||
status: 422,
|
||||
message: err.to_string()
|
||||
})
|
||||
};
|
||||
|
||||
let refresh_token_string = match req.cookie("refresh_token") {
|
||||
Some(cookie) => cookie.value().to_string(),
|
||||
None => return ResponseError::error_response(&ServiceError {
|
||||
status: 401,
|
||||
message: "Refresh token not found".to_string()
|
||||
})
|
||||
};
|
||||
|
||||
let mut conn = match storage::redis_async_connection().await {
|
||||
Ok(conn) => conn,
|
||||
@@ -149,129 +103,38 @@ async fn refresh(req: HttpRequest) -> HttpResponse {
|
||||
}
|
||||
};
|
||||
|
||||
let mut refresh_token: RefreshToken = match conn.get::<_, String>(refresh_token_string.clone()).await {
|
||||
Ok(result) => match serde_json::from_str::<RefreshToken>(&result) {
|
||||
Ok(result) => {
|
||||
if verify_hash(&ip_address, &result.ip_address) {
|
||||
result
|
||||
} else {
|
||||
return ResponseError::error_response(&ServiceError {
|
||||
status: 401,
|
||||
message: "Refresh token is invalid".to_string()
|
||||
})
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
error!("Failed to deserialize refresh token: {}", err);
|
||||
return ResponseError::error_response(&ServiceError::from(err))
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
error!("Failed to get refresh token from redis: {}", err);
|
||||
return ResponseError::error_response(&ServiceError::from(err))
|
||||
}
|
||||
let session_ttl = env::var("SESSION_TTL")
|
||||
.expect("SESSION_TTL must be set")
|
||||
.parse::<i64>()
|
||||
.expect("SESSION_TTL must be an integer");
|
||||
|
||||
// Delete old session
|
||||
let _: redis::RedisResult<()> = conn.del(auth.id).await;
|
||||
|
||||
// Create new session
|
||||
let session = Session::new(&auth.user.email, &ip_address);
|
||||
let session_result: redis::RedisResult<()> = conn.set_ex(session.id.to_string(), &serde_json::to_string(&session).unwrap(), (session_ttl * 60) as usize).await;
|
||||
if let Err(err) = session_result {
|
||||
error!("Failed to set session id in redis: {}", err);
|
||||
return ResponseError::error_response(&ServiceError::from(err))
|
||||
};
|
||||
|
||||
let email = refresh_token.email.clone();
|
||||
// Create cookies
|
||||
let session_cookie = session.create_cookie();
|
||||
let user_id_cookie = Cookie::build("user_id", session.user_id.clone())
|
||||
.path("/")
|
||||
.max_age(Duration::new(session_ttl * 60, 0))
|
||||
.http_only(false)
|
||||
.finish();
|
||||
|
||||
match QueryUser::get_by_email(&email) {
|
||||
Ok(query_user) => {
|
||||
// Revoke all old access tokens
|
||||
for id in refresh_token.tokens {
|
||||
let _: redis::RedisResult<()> = conn.del(id).await;
|
||||
}
|
||||
refresh_token.tokens = vec![];
|
||||
|
||||
// Create new access token
|
||||
let access_token = match refresh_token.create_access_token() {
|
||||
Ok(token) => token,
|
||||
Err(err) => {
|
||||
error!("Failed to generate access token: {}", err);
|
||||
return ResponseError::error_response(&err)
|
||||
}
|
||||
};
|
||||
|
||||
let access_token_max_age = env::var("ACCESS_TOKEN_MAXAGE")
|
||||
.expect("ACCESS_TOKEN_MAXAGE must be set")
|
||||
.parse::<i64>()
|
||||
.expect("ACCESS_TOKEN_MAXAGE must be an integer");
|
||||
|
||||
let access_result: redis::RedisResult<()> = conn.set_ex(access_token.id.to_string(), &serde_json::to_string(&access_token).unwrap(), (access_token_max_age * 60) as usize).await;
|
||||
if let Err(err) = access_result {
|
||||
error!("Failed to set access token in redis: {}", err);
|
||||
return ResponseError::error_response(&ServiceError::from(err))
|
||||
};
|
||||
|
||||
let access_cookie = Cookie::build("access_token", access_token.id.clone())
|
||||
.path("/")
|
||||
.max_age(Duration::new(access_token_max_age * 60, 0))
|
||||
.http_only(true)
|
||||
.secure(true)
|
||||
.finish();
|
||||
let logged_in_cookie = Cookie::build("logged_in", "true")
|
||||
.path("/")
|
||||
.max_age(Duration::new(access_token_max_age * 60, 0))
|
||||
.http_only(false)
|
||||
.finish();
|
||||
|
||||
// Refresh the refresh token if requested
|
||||
let refresh_token_rotation = match params.refresh_token_rotation {
|
||||
Some(refresh_token_rotation) => refresh_token_rotation,
|
||||
None => false
|
||||
};
|
||||
if refresh_token_rotation {
|
||||
// Delete the old refresh token from redis
|
||||
let _: redis::RedisResult<()> = conn.del(refresh_token.id.to_string()).await;
|
||||
|
||||
let refresh_token = RefreshToken::new(&refresh_token.email, &ip_address);
|
||||
let refresh_token_max_age = env::var("REFRESH_TOKEN_MAXAGE")
|
||||
.expect("REFRESH_TOKEN_MAXAGE must be set")
|
||||
.parse::<i64>()
|
||||
.expect("REFRESH_TOKEN_MAXAGE must be an integer");
|
||||
|
||||
// Add the new refresh token to redis
|
||||
let refresh_result: redis::RedisResult<()> = conn.set_ex(refresh_token.id.to_string(), &serde_json::to_string(&refresh_token).unwrap(), (refresh_token_max_age * 60) as usize).await;
|
||||
if let Err(err) = refresh_result {
|
||||
error!("Failed to set refresh token in redis: {}", err);
|
||||
return ResponseError::error_response(&ServiceError {
|
||||
status: 500,
|
||||
message: format!("Failed to set refresh token in redis: {}", err)
|
||||
})
|
||||
};
|
||||
|
||||
let refresh_cookie = Cookie::build("refresh_token", refresh_token.id.clone())
|
||||
.path("/")
|
||||
.max_age(Duration::new(refresh_token_max_age * 60, 0))
|
||||
.http_only(true)
|
||||
.secure(true)
|
||||
.finish();
|
||||
|
||||
HttpResponse::Ok()
|
||||
.cookie(refresh_cookie)
|
||||
.cookie(access_cookie)
|
||||
.cookie(logged_in_cookie)
|
||||
.json(JwtAuth { id: access_token.id, user: query_user.into() })
|
||||
} else {
|
||||
HttpResponse::Ok()
|
||||
.cookie(access_cookie)
|
||||
.cookie(logged_in_cookie)
|
||||
.json(JwtAuth { id: access_token.id, user: query_user.into() })
|
||||
}
|
||||
},
|
||||
Err(err) => return ResponseError::error_response(&err)
|
||||
}
|
||||
HttpResponse::Ok()
|
||||
.cookie(session_cookie)
|
||||
.cookie(user_id_cookie)
|
||||
.json(Auth { id: session.id, user: auth.user })
|
||||
}
|
||||
|
||||
#[post("/logout")]
|
||||
async fn logout(req: HttpRequest, auth: JwtAuth) -> HttpResponse {
|
||||
let refresh_token = match req.cookie("refresh_token") {
|
||||
Some(cookie) => cookie.value().to_string(),
|
||||
None => return ResponseError::error_response(&ServiceError {
|
||||
status: 401,
|
||||
message: "Refresh token not found".to_string()
|
||||
})
|
||||
};
|
||||
|
||||
async fn logout(auth: Auth) -> HttpResponse {
|
||||
let mut conn = match storage::redis_async_connection().await {
|
||||
Ok(conn) => conn,
|
||||
Err(err) => {
|
||||
@@ -280,45 +143,32 @@ async fn logout(req: HttpRequest, auth: JwtAuth) -> HttpResponse {
|
||||
}
|
||||
};
|
||||
|
||||
let access_result: redis::RedisResult<()> = conn.del(&[
|
||||
refresh_token.to_string(),
|
||||
auth.id.to_string()
|
||||
]).await;
|
||||
if let Err(err) = access_result {
|
||||
error!("Failed to set access token in redis: {}", err);
|
||||
return ResponseError::error_response(&ServiceError {
|
||||
status: 500,
|
||||
message: format!("Failed to set access token in redis: {}", err)
|
||||
})
|
||||
let session_result: redis::RedisResult<()> = conn.del(&auth.id.to_string()).await;
|
||||
if let Err(err) = session_result {
|
||||
error!("Failed to remove session id in redis: {}", err);
|
||||
return ResponseError::error_response(&ServiceError::from(err))
|
||||
};
|
||||
|
||||
let access_cookie = Cookie::build("access_token", "")
|
||||
let session_cookie = Cookie::build(SESSION_COOKIE_NAME, "")
|
||||
.path("/")
|
||||
.max_age(Duration::new(-1, 0))
|
||||
.secure(true)
|
||||
.http_only(true)
|
||||
.finish();
|
||||
let refresh_cookie = Cookie::build("refresh_token", "")
|
||||
.path("/")
|
||||
.max_age(Duration::new(-1, 0))
|
||||
.secure(true)
|
||||
.http_only(true)
|
||||
.finish();
|
||||
let logged_in_cookie = Cookie::build("logged_in", "")
|
||||
let user_id_cookie = Cookie::build("user_id", "")
|
||||
.path("/")
|
||||
.max_age(Duration::new(-1, 0))
|
||||
.http_only(true)
|
||||
.finish();
|
||||
|
||||
HttpResponse::Ok()
|
||||
.cookie(access_cookie)
|
||||
.cookie(refresh_cookie)
|
||||
.cookie(logged_in_cookie)
|
||||
.cookie(session_cookie)
|
||||
.cookie(user_id_cookie)
|
||||
.finish()
|
||||
}
|
||||
|
||||
#[get("/me")]
|
||||
async fn me(auth: JwtAuth) -> HttpResponse {
|
||||
async fn me(auth: Auth) -> HttpResponse {
|
||||
HttpResponse::Ok().json(auth)
|
||||
}
|
||||
|
||||
|
||||
102
service/src/auth/session.rs
Normal file
102
service/src/auth/session.rs
Normal file
@@ -0,0 +1,102 @@
|
||||
use std::env;
|
||||
|
||||
use actix_web::cookie::{time::Duration, Cookie};
|
||||
use argon2::{password_hash::{rand_core::OsRng, SaltString}, Argon2, PasswordHash, PasswordHasher, PasswordVerifier};
|
||||
use rand::prelude::*;
|
||||
use rand_chacha::ChaCha20Rng;
|
||||
use redis::Commands;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use siren::ServiceError;
|
||||
|
||||
use super::QueryUser;
|
||||
|
||||
pub const SESSION_COOKIE_NAME: &str = "session";
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Session {
|
||||
pub id: String,
|
||||
pub user_id: String,
|
||||
pub ip_address: String,
|
||||
pub expiration: i64,
|
||||
}
|
||||
|
||||
impl Session {
|
||||
pub fn new(user_id: &str, ip_address: &str) -> Self {
|
||||
let ttl = env::var("SESSION_TTL")
|
||||
.expect("SESSION_TTL must be set")
|
||||
.parse::<i64>()
|
||||
.expect("SESSION_TTL must be an integer");
|
||||
let now = chrono::Utc::now();
|
||||
Self {
|
||||
id: csprng_128bit(),
|
||||
user_id: user_id.to_string(),
|
||||
ip_address: hash(&ip_address).unwrap(),
|
||||
expiration: (now + chrono::Duration::minutes(ttl)).timestamp()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn verify(session_id: &str, ip_address: &str) -> Result<(Self, QueryUser), ServiceError> {
|
||||
let mut conn = crate::storage::redis_connection()?;
|
||||
// Check if the session exists
|
||||
let session = match conn.get::<_, String>(session_id) {
|
||||
Ok(session) => session,
|
||||
Err(_) => return Err(ServiceError::new(401, "Unauthorized".to_string()))
|
||||
};
|
||||
let session: Self = serde_json::from_str(&session)?;
|
||||
// Check if the IP address matches
|
||||
let session_ip_address = session.ip_address.clone();
|
||||
let session_user_id = session.user_id.clone();
|
||||
if verify_hash(ip_address, &session_ip_address) {
|
||||
let email = session_user_id;
|
||||
// Check if the user exists
|
||||
let user = match crate::auth::model::QueryUser::get_by_email(&email) {
|
||||
Ok(user) => user,
|
||||
Err(_) => return Err(ServiceError::new(401, "Unauthorized".to_string()))
|
||||
};
|
||||
// Check if the session has expired
|
||||
let now = chrono::Utc::now().timestamp();
|
||||
if now < session.expiration {
|
||||
return Ok((session, user))
|
||||
}
|
||||
}
|
||||
Err(ServiceError::new(401, "Unauthorized".to_string()))
|
||||
}
|
||||
|
||||
pub fn create_cookie(&self) -> Cookie {
|
||||
let ttl = env::var("SESSION_TTL")
|
||||
.expect("SESSION_TTL must be set")
|
||||
.parse::<i64>()
|
||||
.expect("SESSION_TTL must be an integer");
|
||||
Cookie::build(SESSION_COOKIE_NAME, self.id.clone())
|
||||
.path("/")
|
||||
.max_age(Duration::new(ttl * 60, 0))
|
||||
.secure(true)
|
||||
.http_only(true)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
fn csprng_128bit() -> String {
|
||||
// Generate a CSPRNG 128-bit (16 byte) ID using alphanumeric characters (a-z, A-Z, 0-9)
|
||||
let rng = ChaCha20Rng::from_entropy();
|
||||
rng.sample_iter(rand::distributions::Alphanumeric).take(16).map(char::from).collect()
|
||||
}
|
||||
|
||||
pub fn hash(str: &str) -> Result<String, ServiceError> {
|
||||
let salt = SaltString::generate(&mut OsRng);
|
||||
let bytes = str.as_bytes();
|
||||
let hash = Argon2::default().hash_password(bytes, &salt)?.to_string();
|
||||
Ok(hash)
|
||||
}
|
||||
|
||||
pub fn verify_hash(str: &str, hash: &str) -> bool {
|
||||
let bytes = str.as_bytes();
|
||||
let parsed_hash = match PasswordHash::new(hash) {
|
||||
Ok(h) => h,
|
||||
Err(_) => return false
|
||||
};
|
||||
match Argon2::default().verify_password(bytes, &parsed_hash) {
|
||||
Ok(_) => true,
|
||||
Err(_) => false
|
||||
}
|
||||
}
|
||||
@@ -5,10 +5,10 @@ use serde::{Serialize, Deserialize};
|
||||
use serenity::model::prelude::{GuildChannel, ChannelType};
|
||||
use siren::{ServiceError, Response};
|
||||
|
||||
use crate::{AppState, bot::commands::audio::{play::play_track, join}, bot::guilds::QueryGuild, auth::{JwtAuth, verify_role}};
|
||||
use crate::{AppState, bot::commands::audio::{play::play_track, join}, bot::guilds::QueryGuild, auth::{Auth, verify_role}};
|
||||
|
||||
#[get("/guilds")]
|
||||
async fn get_guilds(data: web::Data<Arc<AppState>>, auth: JwtAuth) -> HttpResponse {
|
||||
async fn get_guilds(data: web::Data<Arc<AppState>>, auth: Auth) -> HttpResponse {
|
||||
if let Err(err) = verify_role(&auth, "admin") {
|
||||
return ResponseError::error_response(&err)
|
||||
};
|
||||
@@ -27,7 +27,7 @@ async fn get_guilds(data: web::Data<Arc<AppState>>, auth: JwtAuth) -> HttpRespon
|
||||
}
|
||||
|
||||
#[get("/{id}/text")]
|
||||
async fn get_text_channels(id: web::Path<String>, data: web::Data<Arc<AppState>>, auth: JwtAuth) -> HttpResponse {
|
||||
async fn get_text_channels(id: web::Path<String>, data: web::Data<Arc<AppState>>, auth: Auth) -> HttpResponse {
|
||||
if let Err(err) = verify_role(&auth, "admin") {
|
||||
return ResponseError::error_response(&err)
|
||||
};
|
||||
@@ -46,7 +46,7 @@ async fn get_text_channels(id: web::Path<String>, data: web::Data<Arc<AppState>>
|
||||
}
|
||||
|
||||
#[get("/{id}/voice")]
|
||||
async fn get_voice_channels(id: web::Path<String>, data: web::Data<Arc<AppState>>, auth: JwtAuth) -> HttpResponse {
|
||||
async fn get_voice_channels(id: web::Path<String>, data: web::Data<Arc<AppState>>, auth: Auth) -> HttpResponse {
|
||||
if let Err(err) = verify_role(&auth, "admin") {
|
||||
return ResponseError::error_response(&err)
|
||||
};
|
||||
@@ -70,7 +70,7 @@ struct ChannelMessage {
|
||||
}
|
||||
|
||||
#[post("/{guild_id}/text/{channel_id}/message")]
|
||||
async fn send_message(path: web::Path<(String, String)>, text: web::Json<ChannelMessage>, data: web::Data<Arc<AppState>>, auth: JwtAuth) -> HttpResponse {
|
||||
async fn send_message(path: web::Path<(String, String)>, text: web::Json<ChannelMessage>, data: web::Data<Arc<AppState>>, auth: Auth) -> HttpResponse {
|
||||
if let Err(err) = verify_role(&auth, "admin") {
|
||||
return ResponseError::error_response(&err)
|
||||
};
|
||||
@@ -130,7 +130,7 @@ struct PlayRequest {
|
||||
}
|
||||
|
||||
#[post("/{guild_id}/voice/{channel_id}/play")]
|
||||
async fn play(path: web::Path<(String, String)>, play_request: web::Json<PlayRequest>, data: web::Data<Arc<AppState>>, auth: JwtAuth) -> HttpResponse {
|
||||
async fn play(path: web::Path<(String, String)>, play_request: web::Json<PlayRequest>, data: web::Data<Arc<AppState>>, auth: Auth) -> HttpResponse {
|
||||
if let Err(err) = verify_role(&auth, "admin") {
|
||||
return ResponseError::error_response(&err)
|
||||
};
|
||||
@@ -179,7 +179,7 @@ async fn play(path: web::Path<(String, String)>, play_request: web::Json<PlayReq
|
||||
}
|
||||
|
||||
#[post("/{guild_id}/voice/stop")]
|
||||
async fn stop(path: web::Path<String>, data: web::Data<Arc<AppState>>, auth: JwtAuth) -> HttpResponse {
|
||||
async fn stop(path: web::Path<String>, data: web::Data<Arc<AppState>>, auth: Auth) -> HttpResponse {
|
||||
if let Err(err) = verify_role(&auth, "admin") {
|
||||
return ResponseError::error_response(&err)
|
||||
};
|
||||
@@ -203,7 +203,7 @@ async fn stop(path: web::Path<String>, data: web::Data<Arc<AppState>>, auth: Jwt
|
||||
}
|
||||
|
||||
#[post("/{guild_id}/voice/resume")]
|
||||
async fn resume(path: web::Path<String>, data: web::Data<Arc<AppState>>, auth: JwtAuth) -> HttpResponse {
|
||||
async fn resume(path: web::Path<String>, data: web::Data<Arc<AppState>>, auth: Auth) -> HttpResponse {
|
||||
if let Err(err) = verify_role(&auth, "admin") {
|
||||
return ResponseError::error_response(&err)
|
||||
};
|
||||
@@ -232,7 +232,7 @@ async fn resume(path: web::Path<String>, data: web::Data<Arc<AppState>>, auth: J
|
||||
}
|
||||
|
||||
#[post("/{guild_id}/voice/pause")]
|
||||
async fn pause(path: web::Path<String>, data: web::Data<Arc<AppState>>, auth: JwtAuth) -> HttpResponse {
|
||||
async fn pause(path: web::Path<String>, data: web::Data<Arc<AppState>>, auth: Auth) -> HttpResponse {
|
||||
if let Err(err) = verify_role(&auth, "admin") {
|
||||
return ResponseError::error_response(&err)
|
||||
};
|
||||
@@ -266,7 +266,7 @@ struct SetVolume {
|
||||
}
|
||||
|
||||
#[get("/{guild_id}/voice/volume")]
|
||||
async fn get_volume(path: web::Path<String>, auth: JwtAuth) -> HttpResponse {
|
||||
async fn get_volume(path: web::Path<String>, auth: Auth) -> HttpResponse {
|
||||
if let Err(err) = verify_role(&auth, "admin") {
|
||||
return ResponseError::error_response(&err)
|
||||
};
|
||||
@@ -295,7 +295,7 @@ async fn get_volume(path: web::Path<String>, auth: JwtAuth) -> HttpResponse {
|
||||
}
|
||||
|
||||
#[post("/{guild_id}/voice/volume")]
|
||||
async fn set_volume(path: web::Path<String>, volume: web::Json::<SetVolume>, data: web::Data<Arc<AppState>>, auth: JwtAuth) -> HttpResponse {
|
||||
async fn set_volume(path: web::Path<String>, volume: web::Json::<SetVolume>, data: web::Data<Arc<AppState>>, auth: Auth) -> HttpResponse {
|
||||
if let Err(err) = verify_role(&auth, "admin") {
|
||||
return ResponseError::error_response(&err)
|
||||
};
|
||||
@@ -325,7 +325,7 @@ async fn set_volume(path: web::Path<String>, volume: web::Json::<SetVolume>, dat
|
||||
}
|
||||
|
||||
#[post("/{guild_id}/voice/skip")]
|
||||
async fn skip(path: web::Path<String>, data: web::Data<Arc<AppState>>, auth: JwtAuth) -> HttpResponse {
|
||||
async fn skip(path: web::Path<String>, data: web::Data<Arc<AppState>>, auth: Auth) -> HttpResponse {
|
||||
if let Err(err) = verify_role(&auth, "admin") {
|
||||
return ResponseError::error_response(&err)
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@ use log::error;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use siren::{Response, Metadata, ServiceError};
|
||||
|
||||
use crate::{bot::messages::{QueryMessage, QueryFilters}, auth::{JwtAuth, verify_role}};
|
||||
use crate::{bot::messages::{QueryMessage, QueryFilters}, auth::{Auth, verify_role}};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct GetAllParams {
|
||||
@@ -21,7 +21,7 @@ struct GetAllParams {
|
||||
}
|
||||
|
||||
#[get("/messages")]
|
||||
async fn get_all(req: HttpRequest, auth: JwtAuth) -> HttpResponse {
|
||||
async fn get_all(req: HttpRequest, auth: Auth) -> HttpResponse {
|
||||
let _ = match verify_role(&auth, "admin") {
|
||||
Ok(_) => {},
|
||||
Err(err) => return ResponseError::error_response(&err)
|
||||
@@ -68,7 +68,7 @@ async fn get_all(req: HttpRequest, auth: JwtAuth) -> HttpResponse {
|
||||
}
|
||||
|
||||
#[post("/messages")]
|
||||
async fn create(message: web::Json<QueryMessage>, auth: JwtAuth) -> HttpResponse {
|
||||
async fn create(message: web::Json<QueryMessage>, auth: Auth) -> HttpResponse {
|
||||
let _ = match verify_role(&auth, "admin") {
|
||||
Ok(_) => {},
|
||||
Err(err) => return ResponseError::error_response(&err)
|
||||
|
||||
@@ -3,7 +3,7 @@ use log::error;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use siren::{Response, Metadata, ServiceError};
|
||||
|
||||
use crate::{dnd::spells::{QuerySpell, QueryFilters}, auth::{JwtAuth, verify_role}};
|
||||
use crate::{dnd::spells::{QuerySpell, QueryFilters}, auth::{Auth, verify_role}};
|
||||
|
||||
use super::{Spell, InsertSpell};
|
||||
|
||||
@@ -134,7 +134,7 @@ async fn get_by_id(id: web::Path<String>) -> HttpResponse {
|
||||
}
|
||||
|
||||
#[post("/spells")]
|
||||
async fn create(spell: web::Json<Spell>, auth: JwtAuth) -> HttpResponse {
|
||||
async fn create(spell: web::Json<Spell>, auth: Auth) -> HttpResponse {
|
||||
let _ = match verify_role(&auth, "admin") {
|
||||
Ok(_) => {},
|
||||
Err(err) => return ResponseError::error_response(&err)
|
||||
@@ -149,7 +149,7 @@ async fn create(spell: web::Json<Spell>, auth: JwtAuth) -> HttpResponse {
|
||||
}
|
||||
|
||||
#[put("/spells/{id}")]
|
||||
async fn update(id: web::Path<String>, spell: web::Json<Spell>, auth: JwtAuth) -> HttpResponse {
|
||||
async fn update(id: web::Path<String>, spell: web::Json<Spell>, auth: Auth) -> HttpResponse {
|
||||
let _ = match verify_role(&auth, "admin") {
|
||||
Ok(_) => {},
|
||||
Err(err) => return ResponseError::error_response(&err)
|
||||
@@ -171,7 +171,7 @@ async fn update(id: web::Path<String>, spell: web::Json<Spell>, auth: JwtAuth) -
|
||||
}
|
||||
|
||||
#[delete("/spells/{id}")]
|
||||
async fn delete(id: web::Path<String>, auth: JwtAuth) -> HttpResponse {
|
||||
async fn delete(id: web::Path<String>, auth: Auth) -> HttpResponse {
|
||||
let _ = match verify_role(&auth, "admin") {
|
||||
Ok(_) => {},
|
||||
Err(err) => return ResponseError::error_response(&err)
|
||||
|
||||
@@ -112,12 +112,6 @@ impl From<argon2::password_hash::Error> for ServiceError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<jsonwebtoken::errors::Error> for ServiceError {
|
||||
fn from(error: jsonwebtoken::errors::Error) -> ServiceError {
|
||||
ServiceError::new(500, format!("Unknown jsonwebtoken error: {}", error))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<redis::RedisError> for ServiceError {
|
||||
fn from(error: redis::RedisError) -> ServiceError {
|
||||
ServiceError::new(500, format!("Unknown redis error: {}", error))
|
||||
@@ -153,6 +147,12 @@ impl From<std::env::VarError> for ServiceError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<jsonwebtoken::errors::Error> for ServiceError {
|
||||
fn from(error: jsonwebtoken::errors::Error) -> ServiceError {
|
||||
ServiceError::new(500, format!("Unknown jsonwebtoken error: {}", error))
|
||||
}
|
||||
}
|
||||
|
||||
impl ResponseError for ServiceError {
|
||||
fn error_response(&self) -> HttpResponse {
|
||||
let status_code = match StatusCode::from_u16(self.status) {
|
||||
|
||||
@@ -4,10 +4,10 @@ use log::error;
|
||||
use serenity::futures::StreamExt;
|
||||
use siren::ServiceError;
|
||||
|
||||
use crate::{auth::{JwtAuth, InsertUser, QueryUser}, storage::{upload_file, get_file, delete_file}};
|
||||
use crate::{auth::{Auth, InsertUser, QueryUser}, storage::{upload_file, get_file, delete_file}};
|
||||
|
||||
#[post("/picture")]
|
||||
async fn set_picture(mut payload: Multipart, auth: JwtAuth) -> HttpResponse {
|
||||
async fn set_picture(mut payload: Multipart, auth: Auth) -> HttpResponse {
|
||||
while let Some(item) = payload.next().await {
|
||||
let mut bytes = web::BytesMut::new();
|
||||
let mut field = match item {
|
||||
@@ -73,7 +73,7 @@ async fn set_picture(mut payload: Multipart, auth: JwtAuth) -> HttpResponse {
|
||||
}
|
||||
|
||||
#[get("/picture")]
|
||||
async fn get_picture(auth: JwtAuth) -> HttpResponse {
|
||||
async fn get_picture(auth: Auth) -> HttpResponse {
|
||||
let user = match QueryUser::get_by_email(&auth.user.email) {
|
||||
Ok(user) => user,
|
||||
Err(err) => {
|
||||
@@ -95,7 +95,7 @@ async fn get_picture(auth: JwtAuth) -> HttpResponse {
|
||||
}
|
||||
|
||||
#[delete("/picture")]
|
||||
async fn delete_picture(auth: JwtAuth) -> HttpResponse {
|
||||
async fn delete_picture(auth: Auth) -> HttpResponse {
|
||||
match QueryUser::get_by_email(&auth.user.email) {
|
||||
Ok(user) => {
|
||||
match user.profile_picture {
|
||||
|
||||
Reference in New Issue
Block a user