Compare commits

..

No commits in common. "70aaa1ff44a66501c27adbe73da7321050515d3e" and "d75242d21394cb764a83ea7a56c29fbbd57bf747" have entirely different histories.

15 changed files with 1360 additions and 856 deletions

1926
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -6,14 +6,12 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
clap = { version = "3.1.6", features = ["derive", "env"] } rocket = "0.5.0-rc.1"
actix-web = "4" rocket_dyn_templates = { version = "0.1.0-rc.1", features = ["tera"] }
include_dir = "0.7.2" include_dir = "0.7.2"
log = "0.4.16" log = "0.4.16"
serde_json = "1.0.79" serde_json = "1.0.79"
env_logger = "0.9.0" env_logger = "0.9.0"
serde = { version = "1.0.136", features = ["derive"] } serde = { version = "1.0.136", features = ["derive"] }
bcrypt = "0.12.1" bcrypt = "0.12.1"
uuid = { version = "0.8.2", features = ["v4"] } uuid = { version = "0.8.2", features = ["v4"] }
mime_guess = "2.0.4"
askama = "0.11.1"

View File

@ -1,57 +0,0 @@
html,
body {
height: 100%;
}
body {
display: flex;
align-items: center;
padding-top: 40px;
padding-bottom: 40px;
/* background-color: #f5f5f5; */
}
.form-signin {
width: 100%;
max-width: 330px;
padding: 15px;
margin: auto;
}
.form-signin .checkbox {
font-weight: 400;
}
.form-signin .form-floating:focus-within {
z-index: 2;
}
.form-floating:first-child input {
margin-bottom: -1px;
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
}
.form-floating:not(:first-child):not(:last-child) input {
margin-bottom: -1px;
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
border-top-left-radius: 0;
border-top-right-radius: 0;
}
.form-floating:last-child input {
margin-bottom: 10px;
border-top-left-radius: 0;
border-top-right-radius: 0;
}
.form-control {
background-color: var(--bs-gray-700);
color: var(--bs-gray-100);
}
.form-control:focus {
background-color: var(--bs-gray-600);
color: var(--bs-gray-100);
}

5
assets/css/login.css Normal file
View File

@ -0,0 +1,5 @@
/**
* Login page
*
* @author Pierre Hubert
*/

View File

@ -1,5 +0,0 @@
// Remove un-used alerts
document.querySelectorAll("[role=alert]").forEach(el => {
if(el.innerHTML.trim() === "")
el.remove();
})

View File

@ -3,7 +3,4 @@ pub const USERS_LIST_FILE: &str = "users.json";
/// Default built-in credentials /// Default built-in credentials
pub const DEFAULT_ADMIN_USERNAME: &str = "admin"; pub const DEFAULT_ADMIN_USERNAME: &str = "admin";
pub const DEFAULT_ADMIN_PASSWORD: &str = "admin"; pub const DEFAULT_ADMIN_PASSWORD: &str = "admin";
/// App name
pub const APP_NAME: &str = "Basic OIDC";

View File

@ -1,20 +1,22 @@
use std::path::Path; use std::path::PathBuf;
use actix_web::{HttpResponse, web};
use include_dir::{Dir, include_dir}; use include_dir::{Dir, include_dir};
use rocket::http::{ContentType, Status};
/// Assets directory /// Assets directory
static ASSETS_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/assets"); static ASSETS_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/assets");
pub async fn assets_route(path: web::Path<String>) -> HttpResponse { #[get("/<file..>")]
let path: &Path = path.as_ref().as_ref(); pub fn assets_route(file: PathBuf) -> (Status, (ContentType, &'static [u8])) {
match ASSETS_DIR.get_file(path) { match ASSETS_DIR.get_file(file) {
None => HttpResponse::NotFound().body("404 Not found"), None =>
(Status::NotFound, (ContentType::Text, "404 Not found".as_bytes())),
Some(file) => { Some(file) => {
let res = mime_guess::from_path(path).first_or_octet_stream(); (Status::Ok, (
HttpResponse::Ok() ContentType::from_extension(file.path().extension().unwrap_or_default()
.content_type(res.to_string()) .to_string_lossy().as_ref())
.body(file.contents()) .unwrap_or(ContentType::Binary),
file.contents()))
} }
} }
} }

View File

@ -1,35 +0,0 @@
use actix_web::{HttpResponse, Responder};
use askama::Template;
use crate::constants::APP_NAME;
#[derive(Template)]
#[template(path = "base_login_page.html")]
struct BaseLoginPage {
danger: String,
success: String,
page_title: &'static str,
app_name: &'static str,
}
#[derive(Template)]
#[template(path = "login.html")]
struct LoginTemplate {
_parent: BaseLoginPage,
mail: String,
}
pub async fn login_route() -> impl Responder {
HttpResponse::Ok()
.content_type("text/html")
.body(LoginTemplate {
_parent: BaseLoginPage {
page_title: "Login",
danger: "".to_string(),
success: "".to_string(),
app_name: APP_NAME,
},
mail: "".to_string()
}.render().unwrap())
}

View File

@ -1,2 +1 @@
pub mod assets_controller; pub mod assets_controller;
pub mod login_controller;

View File

@ -1,20 +1,11 @@
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use rocket::serde::Deserialize;
use clap::Parser;
use crate::constants::USERS_LIST_FILE; use crate::constants::USERS_LIST_FILE;
/// Basic OIDC provider #[derive(Debug, Deserialize)]
#[derive(Parser, Debug)] #[serde(crate = "rocket::serde")]
#[clap(author, version, about, long_about = None)]
pub struct AppConfig { pub struct AppConfig {
/// Listen address storage_path: PathBuf,
#[clap(short, long, env, default_value = "0.0.0.0:8000")]
pub listen_address: String,
/// Storage path
#[clap(short, long, env)]
pub storage_path: String,
} }
impl AppConfig { impl AppConfig {
@ -23,6 +14,6 @@ impl AppConfig {
} }
pub fn users_file(&self) -> PathBuf { pub fn users_file(&self) -> PathBuf {
self.storage_path().join(USERS_LIST_FILE) self.storage_path.join(USERS_LIST_FILE)
} }
} }

View File

@ -7,7 +7,7 @@ pub struct EntityManager<E> {
list: Vec<E>, list: Vec<E>,
} }
impl<E> EntityManager<E> where E: serde::Serialize + serde::de::DeserializeOwned + Eq + Clone { impl<E> EntityManager<E> where E: rocket::serde::Serialize + rocket::serde::DeserializeOwned + Eq + Clone {
/// Open entity /// Open entity
pub fn open_or_create<A: AsRef<Path>>(path: A) -> Res<Self> { pub fn open_or_create<A: AsRef<Path>>(path: A) -> Res<Self> {
if !path.as_ref().is_file() { if !path.as_ref().is_file() {

View File

@ -1,3 +1,7 @@
#[macro_use]
extern crate rocket;
pub mod data; pub mod data;
pub mod utils; pub mod utils;
pub mod constants; pub mod constants;

View File

@ -1,24 +1,31 @@
use actix_web::{App, get, HttpServer, web}; #[macro_use]
use actix_web::middleware::Logger; extern crate rocket;
use clap::Parser;
use rocket::fairing::AdHoc;
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::login_controller::login_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::controllers::assets_controller::assets_route;
#[get("/health")] #[get("/health")]
async fn health() -> &'static str { fn index() -> &'static str {
"Running" "Running"
} }
#[actix_web::main] #[rocket::main]
async fn main() -> std::io::Result<()> { async fn main() -> Result<(), rocket::Error> {
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); //env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
let config: AppConfig = AppConfig::parse(); let rocket = rocket::build()
.mount("/", routes![index])
.mount("/assets", routes![assets_route])
.attach(AdHoc::config::<AppConfig>());
let figment = rocket.figment();
// Initialize application
let config: AppConfig = figment.extract().expect("config");
if !config.storage_path().exists() { if !config.storage_path().exists() {
log::error!( log::error!(
@ -48,16 +55,5 @@ async fn main() -> std::io::Result<()> {
.expect("Failed to create initial user!"); .expect("Failed to create initial user!");
} }
log::info!("Server will listen on {}", config.listen_address); rocket.launch().await
HttpServer::new(|| {
App::new()
.wrap(Logger::default())
.service(health)
.route("/assets/{path:.*}", web::get().to(assets_route))
.route("/login", web::get().to(login_route))
})
.bind(config.listen_address)?
.run()
.await
} }

View File

@ -1,63 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="Auth service">
<title>{{ app_name }} - {{ page_title }}</title>
<!-- Bootstrap core CSS -->
<link href="/assets/css/bootstrap.css" rel="stylesheet" crossorigin="anonymous"/>
<!-- Favicons -->
<meta name="theme-color" content="#7952b3">
<style>
.bd-placeholder-img {
font-size: 1.125rem;
text-anchor: middle;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
}
@media (min-width: 768px) {
.bd-placeholder-img-lg {
font-size: 3.5rem;
}
}
</style>
<!-- Custom styles for this template -->
<link href="/assets/css/base_login_page.css" rel="stylesheet">
</head>
<body class="text-center">
<main class="form-signin">
<h1 class="h3 mb-3 fw-normal">{{ page_title }}</h1>
<div class="alert alert-danger" role="alert">
{{ danger }}
</div>
<div class="alert alert-success" role="alert">
{{ success }}
</div>
{% block content %}
TO_REPLACE
{% endblock content %}
<p class="mt-5 mb-3 text-muted">&copy; 2022 -
<script>document.write(new Date().getFullYear())</script>
</p>
</main>
<script src="/assets/js/base_login_page.js"></script>
</body>
</html>

View File

@ -1,22 +0,0 @@
{% extends "base_login_page.html" %}
{% block content %}
<form action="/login" method="post">
<div>
<div class="form-floating">
<input name="mail" type="text" required class="form-control" id="floatingName" placeholder="unsername"
value="{{ mail }}">
<label for="floatingName">Email address or username</label>
</div>
<div class="form-floating">
<input name="password" type="password" required class="form-control" id="floatingPassword"
placeholder="Password">
<label for="floatingPassword">Password</label>
</div>
</div>
<button class="w-100 btn btn-lg btn-primary" type="submit">Sign in</button>
</form>
{% endblock content %}