Compare commits
3 Commits
d75242d213
...
70aaa1ff44
Author | SHA1 | Date | |
---|---|---|---|
70aaa1ff44 | |||
5bc4af399d | |||
70df96f286 |
1920
Cargo.lock
generated
1920
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -6,12 +6,14 @@ edition = "2021"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
rocket = "0.5.0-rc.1"
|
||||
rocket_dyn_templates = { version = "0.1.0-rc.1", features = ["tera"] }
|
||||
clap = { version = "3.1.6", features = ["derive", "env"] }
|
||||
actix-web = "4"
|
||||
include_dir = "0.7.2"
|
||||
log = "0.4.16"
|
||||
serde_json = "1.0.79"
|
||||
env_logger = "0.9.0"
|
||||
serde = { version = "1.0.136", features = ["derive"] }
|
||||
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"
|
57
assets/css/base_login_page.css
Normal file
57
assets/css/base_login_page.css
Normal file
@ -0,0 +1,57 @@
|
||||
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);
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
/**
|
||||
* Login page
|
||||
*
|
||||
* @author Pierre Hubert
|
||||
*/
|
5
assets/js/base_login_page.js
Normal file
5
assets/js/base_login_page.js
Normal file
@ -0,0 +1,5 @@
|
||||
// Remove un-used alerts
|
||||
document.querySelectorAll("[role=alert]").forEach(el => {
|
||||
if(el.innerHTML.trim() === "")
|
||||
el.remove();
|
||||
})
|
@ -3,4 +3,7 @@ pub const USERS_LIST_FILE: &str = "users.json";
|
||||
|
||||
/// Default built-in credentials
|
||||
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";
|
@ -1,22 +1,20 @@
|
||||
use std::path::PathBuf;
|
||||
use std::path::Path;
|
||||
|
||||
use actix_web::{HttpResponse, web};
|
||||
use include_dir::{Dir, include_dir};
|
||||
use rocket::http::{ContentType, Status};
|
||||
|
||||
/// Assets directory
|
||||
static ASSETS_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/assets");
|
||||
|
||||
#[get("/<file..>")]
|
||||
pub fn assets_route(file: PathBuf) -> (Status, (ContentType, &'static [u8])) {
|
||||
match ASSETS_DIR.get_file(file) {
|
||||
None =>
|
||||
(Status::NotFound, (ContentType::Text, "404 Not found".as_bytes())),
|
||||
pub async fn assets_route(path: web::Path<String>) -> HttpResponse {
|
||||
let path: &Path = path.as_ref().as_ref();
|
||||
match ASSETS_DIR.get_file(path) {
|
||||
None => HttpResponse::NotFound().body("404 Not found"),
|
||||
Some(file) => {
|
||||
(Status::Ok, (
|
||||
ContentType::from_extension(file.path().extension().unwrap_or_default()
|
||||
.to_string_lossy().as_ref())
|
||||
.unwrap_or(ContentType::Binary),
|
||||
file.contents()))
|
||||
let res = mime_guess::from_path(path).first_or_octet_stream();
|
||||
HttpResponse::Ok()
|
||||
.content_type(res.to_string())
|
||||
.body(file.contents())
|
||||
}
|
||||
}
|
||||
}
|
35
src/controllers/login_controller.rs
Normal file
35
src/controllers/login_controller.rs
Normal file
@ -0,0 +1,35 @@
|
||||
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())
|
||||
}
|
@ -1 +1,2 @@
|
||||
pub mod assets_controller;
|
||||
pub mod assets_controller;
|
||||
pub mod login_controller;
|
@ -1,11 +1,20 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
use rocket::serde::Deserialize;
|
||||
|
||||
use clap::Parser;
|
||||
|
||||
use crate::constants::USERS_LIST_FILE;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
/// Basic OIDC provider
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(author, version, about, long_about = None)]
|
||||
pub struct AppConfig {
|
||||
storage_path: PathBuf,
|
||||
/// Listen address
|
||||
#[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 {
|
||||
@ -14,6 +23,6 @@ impl AppConfig {
|
||||
}
|
||||
|
||||
pub fn users_file(&self) -> PathBuf {
|
||||
self.storage_path.join(USERS_LIST_FILE)
|
||||
self.storage_path().join(USERS_LIST_FILE)
|
||||
}
|
||||
}
|
@ -7,7 +7,7 @@ pub struct EntityManager<E> {
|
||||
list: Vec<E>,
|
||||
}
|
||||
|
||||
impl<E> EntityManager<E> where E: rocket::serde::Serialize + rocket::serde::DeserializeOwned + Eq + Clone {
|
||||
impl<E> EntityManager<E> where E: serde::Serialize + serde::de::DeserializeOwned + Eq + Clone {
|
||||
/// Open entity
|
||||
pub fn open_or_create<A: AsRef<Path>>(path: A) -> Res<Self> {
|
||||
if !path.as_ref().is_file() {
|
||||
|
@ -1,7 +1,3 @@
|
||||
#[macro_use]
|
||||
extern crate rocket;
|
||||
|
||||
|
||||
pub mod data;
|
||||
pub mod utils;
|
||||
pub mod constants;
|
||||
|
40
src/main.rs
40
src/main.rs
@ -1,31 +1,24 @@
|
||||
#[macro_use]
|
||||
extern crate rocket;
|
||||
|
||||
use rocket::fairing::AdHoc;
|
||||
use actix_web::{App, get, HttpServer, web};
|
||||
use actix_web::middleware::Logger;
|
||||
use clap::Parser;
|
||||
|
||||
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::entity_manager::EntityManager;
|
||||
use basic_oidc::data::user::{hash_password, User};
|
||||
use basic_oidc::controllers::assets_controller::assets_route;
|
||||
|
||||
#[get("/health")]
|
||||
fn index() -> &'static str {
|
||||
async fn health() -> &'static str {
|
||||
"Running"
|
||||
}
|
||||
|
||||
#[rocket::main]
|
||||
async fn main() -> Result<(), rocket::Error> {
|
||||
//env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
|
||||
#[actix_web::main]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
|
||||
|
||||
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");
|
||||
let config: AppConfig = AppConfig::parse();
|
||||
|
||||
if !config.storage_path().exists() {
|
||||
log::error!(
|
||||
@ -55,5 +48,16 @@ async fn main() -> Result<(), rocket::Error> {
|
||||
.expect("Failed to create initial user!");
|
||||
}
|
||||
|
||||
rocket.launch().await
|
||||
log::info!("Server will listen on {}", config.listen_address);
|
||||
|
||||
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
|
||||
}
|
||||
|
63
templates/base_login_page.html
Normal file
63
templates/base_login_page.html
Normal file
@ -0,0 +1,63 @@
|
||||
<!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">© 2022 -
|
||||
<script>document.write(new Date().getFullYear())</script>
|
||||
</p>
|
||||
|
||||
</main>
|
||||
|
||||
<script src="/assets/js/base_login_page.js"></script>
|
||||
</body>
|
||||
</html>
|
22
templates/login.html
Normal file
22
templates/login.html
Normal file
@ -0,0 +1,22 @@
|
||||
{% 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 %}
|
Loading…
x
Reference in New Issue
Block a user