Compare commits
4 Commits
777b8814f5
...
ad58d2de7e
Author | SHA1 | Date | |
---|---|---|---|
ad58d2de7e | |||
1070d80553 | |||
3fdb775308 | |||
ce220c52f7 |
14
Cargo.lock
generated
14
Cargo.lock
generated
@ -389,6 +389,7 @@ dependencies = [
|
|||||||
"bcrypt",
|
"bcrypt",
|
||||||
"clap",
|
"clap",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
|
"futures-util",
|
||||||
"include_dir",
|
"include_dir",
|
||||||
"log",
|
"log",
|
||||||
"mime_guess",
|
"mime_guess",
|
||||||
@ -707,6 +708,17 @@ version = "0.3.21"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3"
|
checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-macro"
|
||||||
|
version = "0.3.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-sink"
|
name = "futures-sink"
|
||||||
version = "0.3.21"
|
version = "0.3.21"
|
||||||
@ -726,9 +738,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a"
|
checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
"futures-macro",
|
||||||
"futures-task",
|
"futures-task",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"pin-utils",
|
"pin-utils",
|
||||||
|
"slab",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -19,3 +19,4 @@ bcrypt = "0.12.1"
|
|||||||
uuid = { version = "0.8.2", features = ["v4"] }
|
uuid = { version = "0.8.2", features = ["v4"] }
|
||||||
mime_guess = "2.0.4"
|
mime_guess = "2.0.4"
|
||||||
askama = "0.11.1"
|
askama = "0.11.1"
|
||||||
|
futures-util = "0.3.21"
|
@ -9,10 +9,13 @@ pub const DEFAULT_ADMIN_PASSWORD: &str = "admin";
|
|||||||
pub const APP_NAME: &str = "Basic OIDC";
|
pub const APP_NAME: &str = "Basic OIDC";
|
||||||
|
|
||||||
/// Maximum session duration after inactivity, in seconds
|
/// Maximum session duration after inactivity, in seconds
|
||||||
pub const MAX_SESSION_DURATION: u64 = 60 * 30;
|
pub const MAX_INACTIVITY_DURATION: u64 = 60 * 30;
|
||||||
|
|
||||||
/// Minimum interval between each last activity record in session
|
/// Minimum interval between each last activity record in session
|
||||||
pub const MIN_ACTIVITY_RECORD_TIME: u64 = 10;
|
pub const MIN_ACTIVITY_RECORD_TIME: u64 = 10;
|
||||||
|
|
||||||
/// Minimum password length
|
/// Minimum password length
|
||||||
pub const MIN_PASS_LEN: usize = 4;
|
pub const MIN_PASS_LEN: usize = 4;
|
||||||
|
|
||||||
|
/// Maximum session duration (6 hours)
|
||||||
|
pub const MAX_SESSION_DURATION: u64 = 3600 * 6;
|
@ -1,8 +1,8 @@
|
|||||||
use actix_identity::Identity;
|
use actix_identity::Identity;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::constants::{MAX_SESSION_DURATION, MIN_ACTIVITY_RECORD_TIME};
|
use crate::constants::{MAX_INACTIVITY_DURATION, MAX_SESSION_DURATION, MIN_ACTIVITY_RECORD_TIME};
|
||||||
use crate::data::user::User;
|
use crate::data::user::{User, UserID};
|
||||||
use crate::utils::time::time;
|
use crate::utils::time::time;
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
|
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
|
||||||
@ -22,12 +22,11 @@ impl Default for SessionStatus {
|
|||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Default)]
|
#[derive(Debug, Serialize, Deserialize, Default)]
|
||||||
struct SessionIdentityData {
|
struct SessionIdentityData {
|
||||||
pub id: String,
|
pub id: UserID,
|
||||||
pub is_admin: bool,
|
pub is_admin: bool,
|
||||||
|
login_time: u64,
|
||||||
last_access: u64,
|
last_access: u64,
|
||||||
pub status: SessionStatus,
|
pub status: SessionStatus,
|
||||||
|
|
||||||
// TODO : add session max duration (1 day)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SessionIdentity<'a>(pub &'a Identity);
|
pub struct SessionIdentity<'a>(pub &'a Identity);
|
||||||
@ -37,6 +36,7 @@ impl<'a> SessionIdentity<'a> {
|
|||||||
self.set_session_data(&SessionIdentityData {
|
self.set_session_data(&SessionIdentityData {
|
||||||
id: user.uid.clone(),
|
id: user.uid.clone(),
|
||||||
is_admin: user.admin,
|
is_admin: user.admin,
|
||||||
|
login_time: time(),
|
||||||
last_access: time(),
|
last_access: time(),
|
||||||
status: SessionStatus::SignedIn,
|
status: SessionStatus::SignedIn,
|
||||||
});
|
});
|
||||||
@ -46,7 +46,14 @@ impl<'a> SessionIdentity<'a> {
|
|||||||
let mut res: Option<SessionIdentityData> = self.0.identity()
|
let mut res: Option<SessionIdentityData> = self.0.identity()
|
||||||
.as_deref()
|
.as_deref()
|
||||||
.map(serde_json::from_str)
|
.map(serde_json::from_str)
|
||||||
.map(|f| f.expect("Failed to deserialize session data!"));
|
.map(|f| match f {
|
||||||
|
Ok(d) => Some(d),
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("Failed to deserialize session data! {:?}", e);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or(None);
|
||||||
|
|
||||||
// Check if session is valid
|
// Check if session is valid
|
||||||
if let Some(sess) = &res {
|
if let Some(sess) = &res {
|
||||||
@ -56,7 +63,13 @@ impl<'a> SessionIdentity<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(session) = res.as_mut() {
|
if let Some(session) = res.as_mut() {
|
||||||
if session.last_access + MAX_SESSION_DURATION < time() {
|
if session.login_time + MAX_SESSION_DURATION < time() {
|
||||||
|
log::info!("Session for {} reached max duration timeout", session.id);
|
||||||
|
self.0.forget();
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
if session.last_access + MAX_INACTIVITY_DURATION < time() {
|
||||||
log::info!("Session is expired for {}", session.id);
|
log::info!("Session is expired for {}", session.id);
|
||||||
self.0.forget();
|
self.0.forget();
|
||||||
return None;
|
return None;
|
||||||
@ -97,7 +110,7 @@ impl<'a> SessionIdentity<'a> {
|
|||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn user_id(&self) -> String {
|
pub fn user_id(&self) -> UserID {
|
||||||
self.get_session_data()
|
self.get_session_data()
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.id
|
.id
|
||||||
|
@ -2,4 +2,5 @@ pub mod data;
|
|||||||
pub mod utils;
|
pub mod utils;
|
||||||
pub mod constants;
|
pub mod constants;
|
||||||
pub mod controllers;
|
pub mod controllers;
|
||||||
pub mod actors;
|
pub mod actors;
|
||||||
|
pub mod middlewares;
|
@ -1,16 +1,17 @@
|
|||||||
|
use actix::Actor;
|
||||||
|
use actix_identity::{CookieIdentityPolicy, IdentityService};
|
||||||
use actix_web::{App, get, HttpServer, web};
|
use actix_web::{App, get, HttpServer, web};
|
||||||
use actix_web::middleware::Logger;
|
use actix_web::middleware::Logger;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
|
||||||
|
use basic_oidc::actors::users_actor::UsersActor;
|
||||||
use basic_oidc::constants::{DEFAULT_ADMIN_PASSWORD, DEFAULT_ADMIN_USERNAME};
|
use basic_oidc::constants::{DEFAULT_ADMIN_PASSWORD, DEFAULT_ADMIN_USERNAME};
|
||||||
use basic_oidc::controllers::assets_controller::assets_route;
|
use basic_oidc::controllers::assets_controller::assets_route;
|
||||||
use basic_oidc::controllers::login_controller::{login_route, logout_route};
|
use basic_oidc::controllers::login_controller::{login_route, logout_route};
|
||||||
use basic_oidc::data::app_config::AppConfig;
|
use basic_oidc::data::app_config::AppConfig;
|
||||||
use basic_oidc::data::entity_manager::EntityManager;
|
use basic_oidc::data::entity_manager::EntityManager;
|
||||||
use basic_oidc::data::user::{hash_password, User};
|
use basic_oidc::data::user::{hash_password, User};
|
||||||
use basic_oidc::actors::users_actor::UsersActor;
|
use basic_oidc::middlewares::auth_middleware::AuthMiddleware;
|
||||||
use actix::Actor;
|
|
||||||
use actix_identity::{IdentityService, CookieIdentityPolicy};
|
|
||||||
|
|
||||||
#[get("/health")]
|
#[get("/health")]
|
||||||
async fn health() -> &'static str {
|
async fn health() -> &'static str {
|
||||||
@ -71,6 +72,7 @@ async fn main() -> std::io::Result<()> {
|
|||||||
|
|
||||||
.wrap(Logger::default())
|
.wrap(Logger::default())
|
||||||
.wrap(IdentityService::new(policy))
|
.wrap(IdentityService::new(policy))
|
||||||
|
.wrap(AuthMiddleware {})
|
||||||
|
|
||||||
// /health route
|
// /health route
|
||||||
.service(health)
|
.service(health)
|
||||||
|
73
src/middlewares/auth_middleware.rs
Normal file
73
src/middlewares/auth_middleware.rs
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
//! # Authentication middleware
|
||||||
|
|
||||||
|
use std::future::{Future, ready, Ready};
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use actix_web::{dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform}, Error, HttpResponse};
|
||||||
|
use actix_web::body::EitherBody;
|
||||||
|
|
||||||
|
// There are two steps in middleware processing.
|
||||||
|
// 1. Middleware initialization, middleware factory gets called with
|
||||||
|
// next service in chain as parameter.
|
||||||
|
// 2. Middleware's call method gets called with normal request.
|
||||||
|
pub struct AuthMiddleware;
|
||||||
|
|
||||||
|
// Middleware factory is `Transform` trait
|
||||||
|
// `S` - type of the next service
|
||||||
|
// `B` - type of response's body
|
||||||
|
impl<S, B> Transform<S, ServiceRequest> for AuthMiddleware
|
||||||
|
where
|
||||||
|
S: Service<ServiceRequest, Response=ServiceResponse<B>, Error=Error> + 'static,
|
||||||
|
S::Future: 'static,
|
||||||
|
B: 'static,
|
||||||
|
{
|
||||||
|
type Response = ServiceResponse<EitherBody<B>>;
|
||||||
|
type Error = Error;
|
||||||
|
type Transform = AuthInnerMiddleware<S>;
|
||||||
|
type InitError = ();
|
||||||
|
type Future = Ready<Result<Self::Transform, Self::InitError>>;
|
||||||
|
|
||||||
|
fn new_transform(&self, service: S) -> Self::Future {
|
||||||
|
ready(Ok(AuthInnerMiddleware { service: Rc::new(service) }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AuthInnerMiddleware<S> {
|
||||||
|
service: Rc<S>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, B> Service<ServiceRequest> for AuthInnerMiddleware<S>
|
||||||
|
where
|
||||||
|
S: Service<ServiceRequest, Response=ServiceResponse<B>, Error=Error> + 'static,
|
||||||
|
S::Future: 'static,
|
||||||
|
B: 'static,
|
||||||
|
{
|
||||||
|
type Response = ServiceResponse<EitherBody<B>>;
|
||||||
|
type Error = Error;
|
||||||
|
type Future = Pin<Box<dyn Future<Output=Result<Self::Response, Self::Error>>>>;
|
||||||
|
|
||||||
|
forward_ready!(service);
|
||||||
|
|
||||||
|
fn call(&self, req: ServiceRequest) -> Self::Future {
|
||||||
|
println!("Hi from start. You requested: {}", req.path());
|
||||||
|
|
||||||
|
let service = Rc::clone(&self.service);
|
||||||
|
|
||||||
|
// Forward request
|
||||||
|
Box::pin(async move {
|
||||||
|
if req.path().starts_with("/.git") {
|
||||||
|
return Ok(req.into_response(
|
||||||
|
HttpResponse::Unauthorized()
|
||||||
|
.body("Hey don't touch this!")
|
||||||
|
.map_into_right_body()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
service
|
||||||
|
.call(req)
|
||||||
|
.await
|
||||||
|
.map(ServiceResponse::map_into_left_body)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
1
src/middlewares/mod.rs
Normal file
1
src/middlewares/mod.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
pub mod auth_middleware;
|
Loading…
x
Reference in New Issue
Block a user