auth: wip: sign in page and form, need to finish
This commit is contained in:
parent
d790d2ff29
commit
1695eca466
9 changed files with 179 additions and 6 deletions
|
|
@ -13,6 +13,7 @@ pub enum Page {
|
||||||
Error(Error),
|
Error(Error),
|
||||||
Setup,
|
Setup,
|
||||||
Homepage(Homepage),
|
Homepage(Homepage),
|
||||||
|
SignIn(SignIn),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Page {
|
impl Page {
|
||||||
|
|
@ -22,6 +23,7 @@ impl Page {
|
||||||
Page::Error(_) => "pages/error",
|
Page::Error(_) => "pages/error",
|
||||||
Page::Setup => "pages/setup",
|
Page::Setup => "pages/setup",
|
||||||
Page::Homepage(_) => "pages/homepage",
|
Page::Homepage(_) => "pages/homepage",
|
||||||
|
Page::SignIn(_) => "pages/auth/sign_in",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -31,6 +33,7 @@ impl Page {
|
||||||
Page::Error(_) => "Error",
|
Page::Error(_) => "Error",
|
||||||
Page::Setup => "Setup",
|
Page::Setup => "Setup",
|
||||||
Page::Homepage(_) => "Home",
|
Page::Homepage(_) => "Home",
|
||||||
|
Page::SignIn(_) => "Sign in",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -40,6 +43,7 @@ impl Page {
|
||||||
Page::Error(_) => None,
|
Page::Error(_) => None,
|
||||||
Page::Setup => None,
|
Page::Setup => None,
|
||||||
Page::Homepage(_) => Some(Item::Home.into()),
|
Page::Homepage(_) => Some(Item::Home.into()),
|
||||||
|
Page::SignIn(_) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -49,6 +53,7 @@ impl Page {
|
||||||
Page::Error(error) => Box::new(error),
|
Page::Error(error) => Box::new(error),
|
||||||
Page::Setup => Box::new(()),
|
Page::Setup => Box::new(()),
|
||||||
Page::Homepage(homepage) => Box::new(homepage),
|
Page::Homepage(homepage) => Box::new(homepage),
|
||||||
|
Page::SignIn(sig_in) => Box::new(sig_in),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,3 @@
|
||||||
pub use crate::error::content::*;
|
pub use crate::error::content::*;
|
||||||
|
pub use crate::routes::auth::content::*;
|
||||||
pub use crate::routes::root::content::*;
|
pub use crate::routes::root::content::*;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use rocket::{Build, Rocket};
|
use rocket::{Build, Rocket};
|
||||||
|
|
||||||
|
pub mod auth;
|
||||||
pub mod root;
|
pub mod root;
|
||||||
pub mod setup;
|
pub mod setup;
|
||||||
|
|
||||||
|
|
@ -32,4 +33,5 @@ pub fn routes(rocket_builder: Rocket<Build>) -> Rocket<Build> {
|
||||||
.mount("/", root::routes())
|
.mount("/", root::routes())
|
||||||
// Setup
|
// Setup
|
||||||
.mount("/setup", setup::routes())
|
.mount("/setup", setup::routes())
|
||||||
|
.mount("/auth", auth::routes())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
95
crates/ezidam/src/routes/auth.rs
Normal file
95
crates/ezidam/src/routes/auth.rs
Normal file
|
|
@ -0,0 +1,95 @@
|
||||||
|
use super::prelude::*;
|
||||||
|
use rocket::{get, post};
|
||||||
|
use settings::Settings;
|
||||||
|
use users::User;
|
||||||
|
|
||||||
|
pub fn routes() -> Vec<Route> {
|
||||||
|
routes![sign_in_page, sign_in]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod content {
|
||||||
|
use rocket::serde::Serialize;
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
#[serde(crate = "rocket::serde")]
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SignIn {
|
||||||
|
pub business_name: String,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: default page when signed in -> redirect to home
|
||||||
|
|
||||||
|
#[get("/sign_in", rank = 2)]
|
||||||
|
async fn sign_in_page(
|
||||||
|
mut db: Connection<Database>,
|
||||||
|
flash: Option<FlashMessage<'_>>,
|
||||||
|
) -> Result<Template> {
|
||||||
|
// Define content
|
||||||
|
let content = content::SignIn {
|
||||||
|
business_name: Settings::get(&mut *db).await?.business_name().into(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(flash
|
||||||
|
.map(|flash| Page::with_flash(Page::SignIn(content.clone()), flash))
|
||||||
|
.unwrap_or_else(|| Page::SignIn(content).into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, FromForm)]
|
||||||
|
struct SignIn<'r> {
|
||||||
|
pub login: &'r str,
|
||||||
|
pub password: &'r str,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flash(message: String) -> Flash<Redirect> {
|
||||||
|
Flash::new(
|
||||||
|
Redirect::to(uri!("/auth/sign_in")),
|
||||||
|
FlashKind::Danger,
|
||||||
|
message,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn invalid_credentials(login: &str) -> Flash<Redirect> {
|
||||||
|
flash(format!("Invalid credentials for {login}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn user_archived(login: &str) -> Flash<Redirect> {
|
||||||
|
flash(format!("User {login} is archived"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/sign_in", data = "<form>")]
|
||||||
|
async fn sign_in(
|
||||||
|
form: Form<SignIn<'_>>,
|
||||||
|
mut db: Connection<Database>,
|
||||||
|
) -> Result<Either<Redirect, Flash<Redirect>>> {
|
||||||
|
let form = form.into_inner();
|
||||||
|
|
||||||
|
let mut transaction = db.begin().await?;
|
||||||
|
|
||||||
|
// Get user
|
||||||
|
let Some(user) = User::get_by_login(&mut transaction, form.login).await? else {
|
||||||
|
return Ok(Either::Right(invalid_credentials(form.login)));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check if user is archived
|
||||||
|
if user.is_archived() {
|
||||||
|
return Ok(Either::Right(user_archived(form.login)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get password (can't use Password struct directly because of non-async `compare`)
|
||||||
|
let password = match user.password_hashed() {
|
||||||
|
Some(password_hashed) => Password::from_hash(password_hashed),
|
||||||
|
None => return Ok(Either::Right(invalid_credentials(form.login))),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Verify password
|
||||||
|
let password_input = form.password.to_string();
|
||||||
|
if !task::spawn_blocking(move || password.compare(&password_input)).await?? {
|
||||||
|
return Ok(Either::Right(invalid_credentials(form.login)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: get ip
|
||||||
|
// TODO: refresh token + jwt
|
||||||
|
|
||||||
|
Ok(Either::Left(Redirect::to(uri!("/"))))
|
||||||
|
}
|
||||||
|
|
@ -21,7 +21,7 @@ async fn setup(flash: Option<FlashMessage<'_>>) -> Template {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, FromForm)]
|
#[derive(Debug, FromForm)]
|
||||||
pub struct CreateFirstAccount<'r> {
|
struct CreateFirstAccount<'r> {
|
||||||
pub username: &'r str,
|
pub username: &'r str,
|
||||||
pub password: &'r str,
|
pub password: &'r str,
|
||||||
pub url: &'r str,
|
pub url: &'r str,
|
||||||
|
|
@ -40,7 +40,7 @@ async fn create_first_account(
|
||||||
Ok(url) => url,
|
Ok(url) => url,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
return Ok(Either::Right(Flash::new(
|
return Ok(Either::Right(Flash::new(
|
||||||
Redirect::to(uri!(self::setup)),
|
Redirect::to(uri!("/setup")),
|
||||||
FlashKind::Danger,
|
FlashKind::Danger,
|
||||||
e.to_string(),
|
e.to_string(),
|
||||||
)));
|
)));
|
||||||
|
|
|
||||||
55
crates/ezidam/templates/pages/auth/sign_in.html.tera
Normal file
55
crates/ezidam/templates/pages/auth/sign_in.html.tera
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
{% extends "base" %}
|
||||||
|
|
||||||
|
{% block page %}
|
||||||
|
<body class=" d-flex flex-column">
|
||||||
|
<script src="/js/demo-theme.min.js"></script>
|
||||||
|
<div>
|
||||||
|
<div class="container container-tight py-4">
|
||||||
|
<div class="text-center mb-4">
|
||||||
|
{% include "utils/logo" %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if flash %}
|
||||||
|
<div class="alert alert-{{flash.0}}" role="alert">
|
||||||
|
<h4 class="alert-title">Failed to sign in</h4>
|
||||||
|
<div class="text-muted">{{ flash.1 }}</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="card card-md">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="text-center mb-2">
|
||||||
|
<h2 class="h2">Sign in</h2>
|
||||||
|
<p class="text-muted">With your {{ business_name }} account</p>
|
||||||
|
</div>
|
||||||
|
<form action="/auth/sign_in" method="post" autocomplete="off" novalidate>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Login</label>
|
||||||
|
<input name="login" type="text" class="form-control" placeholder="Email or username"
|
||||||
|
autocomplete="off">
|
||||||
|
</div>
|
||||||
|
<div class="mb-2">
|
||||||
|
<label class="form-label">Password</label>
|
||||||
|
<div class="input-group input-group-flat">
|
||||||
|
<input name="password" type="password" class="form-control" placeholder="Your password"
|
||||||
|
autocomplete="off">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-footer">
|
||||||
|
<button type="submit" class="btn btn-primary w-100">Sign in</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-center text-muted mt-3">
|
||||||
|
<a href="./sign-up.html" tabindex="-1">Reset password</a>
|
||||||
|
</div>
|
||||||
|
{% include "shell/footer" %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Libs JS -->
|
||||||
|
<!-- Tabler Core -->
|
||||||
|
<script src="/js/tabler.min.js" defer></script>
|
||||||
|
<script src="/js/demo.min.js" defer></script>
|
||||||
|
</body>
|
||||||
|
{% endblock page %}
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
{% block page %}
|
{% block page %}
|
||||||
<body class=" d-flex flex-column">
|
<body class=" d-flex flex-column">
|
||||||
<script src="/js/demo-theme.min.js"></script>
|
<script src="/js/demo-theme.min.js"></script>
|
||||||
<div class="">
|
<div>
|
||||||
<div class="container container-tight py-4">
|
<div class="container container-tight py-4">
|
||||||
<div class="text-center mb-4">
|
<div class="text-center mb-4">
|
||||||
{% include "utils/logo" %}
|
{% include "utils/logo" %}
|
||||||
|
|
@ -28,12 +28,14 @@
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label required" for="username">Username</label>
|
<label class="form-label required" for="username">Username</label>
|
||||||
<input name="username" id="username" type="text" placeholder="Enter a username" class="form-control" required>
|
<input name="username" id="username" type="text" placeholder="Enter a username"
|
||||||
|
class="form-control" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label required" for="password">Password</label>
|
<label class="form-label required" for="password">Password</label>
|
||||||
<div class="input-group input-group-flat">
|
<div class="input-group input-group-flat">
|
||||||
<input name="password" id="password" type="password" placeholder="Enter password" class="form-control" autocomplete="off" required>
|
<input name="password" id="password" type="password" placeholder="Enter password"
|
||||||
|
class="form-control" autocomplete="off" required>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -43,7 +45,8 @@
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label required" for="url">Base URL</label>
|
<label class="form-label required" for="url">Base URL</label>
|
||||||
<input name="url" id="url" type="url" placeholder="https://example.com" class="form-control" required>
|
<input name="url" id="url" type="url" placeholder="https://example.com" class="form-control"
|
||||||
|
required>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -20,4 +20,7 @@ impl Settings {
|
||||||
pub fn business_logo(&self) -> &[u8] {
|
pub fn business_logo(&self) -> &[u8] {
|
||||||
self.business_logo.as_slice()
|
self.business_logo.as_slice()
|
||||||
}
|
}
|
||||||
|
pub fn business_name(&self) -> &str {
|
||||||
|
&self.business_name
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,3 +20,12 @@ pub struct User {
|
||||||
paper_key: Option<String>,
|
paper_key: Option<String>,
|
||||||
is_archived: bool,
|
is_archived: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl User {
|
||||||
|
pub fn is_archived(&self) -> bool {
|
||||||
|
self.is_archived
|
||||||
|
}
|
||||||
|
pub fn password_hashed(&self) -> Option<&str> {
|
||||||
|
self.password.as_deref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue