Pour tout problème contactez-nous par mail : support@froggit.fr | La FAQ :grey_question: | Rejoignez-nous sur le Chat :speech_balloon:

Skip to content
Snippets Groups Projects
Commit 6c5b376a authored by Pierre Jarriges's avatar Pierre Jarriges
Browse files

admin auth basically working

parent 3ce7eec8
No related branches found
No related tags found
No related merge requests found
......@@ -997,6 +997,7 @@ dependencies = [
"env_logger",
"fs_extra",
"futures",
"rand",
"regex",
"rustls",
"rustls-pemfile",
......
......@@ -22,3 +22,4 @@ actix-web-lab = "0.17.0"
actix-files = "0.6.2"
actix-multipart = "0.4"
futures = "0.3.24"
rand = "0.8"
console.log("Hello from default js")
\ No newline at end of file
document.getElementById('admin-login-form').onsubmit = function (e) {
e.preventDefault();
fetch('/admin/login', { method: 'POST', body: new URLSearchParams(new FormData(e.target)) }).then(res => {
if (res.status >= 200 && res.status < 400) {
console.log(res)
window.location = '/admin/auth/workspace';
}
}).catch(err => console.log(err))
}
\ No newline at end of file
......@@ -25,8 +25,11 @@ pub struct AppArgs {
pub ssl_certs_dir: PathBuf,
#[structopt(long = "adm", default_value = "admin")]
pub admin_id: String,
pub admin_username: String,
#[structopt(long = "pwd", default_value = "password")]
pub admin_pwd: String,
#[structopt(long = "admin_cookie_name", default_value = "krustacea_admin_auth")]
pub admin_cookie_name: String,
}
......@@ -18,8 +18,9 @@ pub struct AppConfig {
pub port_tls: u16,
pub load: Option<PathBuf>,
pub ssl_certs_dir: PathBuf,
pub admin_id: String,
pub admin_username: String,
pub admin_pwd: String,
pub admin_cookie_name: String,
}
impl AppConfig {
......@@ -59,8 +60,9 @@ impl AppConfig {
port_tls: app_args.port_tls,
load: app_args.load,
ssl_certs_dir,
admin_id: app_args.admin_id,
admin_username: app_args.admin_username,
admin_pwd: app_args.admin_pwd,
admin_cookie_name: app_args.admin_cookie_name,
}
}
pub fn get_log_level(&self) -> String {
......
use crate::app::{AppArgs, AppConfig, AppPreferences};
use rand::{distributions::Alphanumeric, Rng};
use structopt::StructOpt;
#[derive(Clone)]
pub struct AppState {
pub config: AppConfig,
pub preferences: AppPreferences,
// authentication
// ...
pub admin_auth_token: AdminAuthToken,
}
impl AppState {
pub fn new() -> Self {
let config = AppConfig::new(AppArgs::from_args());
let admin_cookie_name = config.admin_cookie_name.to_owned();
AppState {
config: AppConfig::new(AppArgs::from_args()),
config,
preferences: AppPreferences {},
admin_auth_token: AdminAuthToken::new(admin_cookie_name),
}
}
}
#[derive(Clone)]
pub struct AdminAuthToken {
pub value: Option<String>,
pub cookie_name: String,
}
impl AdminAuthToken {
pub fn new(cookie_name: String) -> Self {
AdminAuthToken {
value: None,
cookie_name,
}
}
pub fn match_value(&self, value: String) -> bool {
match &self.value {
Some(token) => token.eq(&value),
None => false,
}
}
pub fn generate(&mut self) {
// Thanks to https://stackoverflow.com/questions/54275459/how-do-i-create-a-random-string-by-sampling-from-alphanumeric-characters#54277357
self.value = Some(
rand::thread_rng()
.sample_iter(&Alphanumeric)
.take(36)
.map(char::from)
.collect::<String>()
.to_ascii_lowercase(),
)
}
}
......@@ -14,11 +14,6 @@ use static_files::StaticFilesManager;
use tls_config::tls_config;
use website::WebSiteBuilder;
#[actix_web::get("/admin")]
async fn test_unauthorized() -> impl actix_web::Responder {
actix_web::HttpResponse::Ok().finish()
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let app_state = AppState::new();
......@@ -38,9 +33,8 @@ async fn main() -> std::io::Result<()> {
let srv_conf = tls_config(&app_state.config);
let static_dir = website.static_files_manager.dir.clone();
let app_state = web::Data::new(std::sync::Mutex::new(app_state));
let mut_app_state = std::sync::Mutex::new(app_state);
let app_state = web::Data::new(mut_app_state);
let mut_website = web::Data::new(std::sync::Mutex::new(website));
HttpServer::new(move || {
......@@ -53,10 +47,13 @@ async fn main() -> std::io::Result<()> {
.service(Files::new("/static/", &static_dir))
.service(
web::scope("/admin")
.wrap(AuthService {
token: String::from("abc"),
})
.service(test_unauthorized),
.service(service::admin_login)
.service(service::admin_authenticate)
.service(
web::scope("/auth")
.wrap(AuthService {})
.service(service::admin_workspace),
),
)
.service(service::files::favicon)
.service(service::page)
......
use crate::{app::AdminAuthToken, AppState};
use actix_web::{
body::{EitherBody, MessageBody},
dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform},
......@@ -7,9 +8,7 @@ use futures::prelude::future::LocalBoxFuture;
use std::future::{ready, Ready};
#[derive(Clone)]
pub struct AuthService {
pub token: String,
}
pub struct AuthService;
impl<S, B> Transform<S, ServiceRequest> for AuthService
where
......@@ -25,20 +24,17 @@ where
fn new_transform(&self, service: S) -> Self::Future {
ready(Ok(AuthenticatedMiddleware {
service: std::rc::Rc::new(service),
auth: self.clone(),
}))
}
}
pub struct AuthenticatedMiddleware<S> {
service: std::rc::Rc<S>,
auth: AuthService,
}
async fn authenticate(req: &mut ServiceRequest, token: String) -> bool {
let cookie = req.cookie("auth");
match cookie {
Some(cookie) => return cookie.value().to_string().eq(&token),
async fn authenticate(req: &mut ServiceRequest, token: &AdminAuthToken) -> bool {
match req.cookie(&token.cookie_name) {
Some(cookie) => token.match_value(cookie.value().to_string()),
None => false,
}
}
......@@ -55,15 +51,23 @@ where
forward_ready!(service);
fn call(&self, req: ServiceRequest) -> Self::Future {
let token = {
let app_state = req
.app_data::<actix_web::web::Data<std::sync::Mutex<AppState>>>()
.expect("Failed to extract AppState from ServiceRequest")
.lock()
.expect("Failed to lock AppState Mutex");
app_state.admin_auth_token.clone()
};
let service = self.service.clone();
let token = self.auth.token.to_owned();
Box::pin(async move {
let mut req = req;
if let false = authenticate(&mut req, token).await {
if let false = authenticate(&mut req, &token).await {
return Ok(req.into_response(
actix_web::HttpResponse::Unauthorized()
.finish()
.body("Error 401 - Unauthorized") // TODO a proper 401 view ?
.map_into_right_body(),
));
}
......
use crate::AppState;
use actix_web::{
cookie::{time::Duration, Cookie, SameSite},
get, post,
web::{Data, Form},
HttpRequest, HttpResponse, Responder,
};
#[get("/workspace")]
async fn admin_workspace() -> impl Responder {
actix_web::HttpResponse::Ok().body("Welcome Admin")
}
#[derive(serde::Deserialize)]
struct Credentials {
username: String,
password: String,
}
#[post("/login")]
async fn admin_authenticate(
credentials: Form<Credentials>,
app_state: Data<std::sync::Mutex<AppState>>,
req: HttpRequest,
) -> impl Responder {
let (admin_username, admin_pwd, cookie_name) = {
let app_state = app_state.lock().unwrap();
(
app_state.config.admin_username.to_owned(),
app_state.config.admin_pwd.to_owned(),
app_state.config.admin_cookie_name.to_owned(),
)
};
if admin_username.eq(&credentials.username) && admin_pwd.eq(&credentials.password) {
let cookie_value = {
let mut app_state = app_state.lock().unwrap();
app_state.admin_auth_token.generate();
app_state
.admin_auth_token
.value
.as_ref()
.unwrap()
.to_owned()
};
let cookie = Cookie::build(cookie_name, cookie_value)
.path("/")
.http_only(true)
.max_age(Duration::days(7))
.same_site(SameSite::Strict)
.secure(true)
.finish();
return HttpResponse::Accepted().cookie(cookie).finish();
} else {
let mut res = HttpResponse::Unauthorized().finish();
match req.cookie(&cookie_name) {
Some(_) => {
res.del_cookie(&cookie_name);
return res;
}
None => return res,
}
}
}
#[get("/login")]
pub async fn admin_login() -> impl Responder {
// TODO real HTML doc - create a module with admin views and a js file to load.
HttpResponse::Ok().body(
"
<form id='admin-login-form'>
<input type='text' name='username'/>
<input type='password' name='password' />
<input type='submit' />
</form>
<script>
document.getElementById('admin-login-form').onsubmit = function (e) {
e.preventDefault();
fetch('/admin/login', { method: 'POST', body: new URLSearchParams(new FormData(e.target)) }).then(res => {
if (res.status >= 200 && res.status < 400) {
console.log(res)
window.location = '/admin/auth/workspace';
}
}).catch(err => console.log(err))
}
</script>
",
)
}
mod admin;
pub mod files;
mod page;
pub use admin::*;
pub use page::*;
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment