ezidam: renamed page auth/sign_in to oauth/authorize, started work on handling oauth requests, big WIP
This commit is contained in:
parent
d16c6760fe
commit
b5c2be6c9f
7 changed files with 153 additions and 102 deletions
|
|
@ -7,6 +7,7 @@ mod file_from_bytes;
|
|||
mod guards;
|
||||
mod id;
|
||||
mod menu;
|
||||
mod oauth;
|
||||
mod page;
|
||||
mod response_timer;
|
||||
mod routes;
|
||||
|
|
|
|||
21
crates/ezidam/src/oauth.rs
Normal file
21
crates/ezidam/src/oauth.rs
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
use rocket::{FromForm, FromFormField, UriDisplayQuery};
|
||||
|
||||
#[derive(Debug, FromFormField, UriDisplayQuery)]
|
||||
pub enum ResponseMode {
|
||||
#[field(value = "query")]
|
||||
Query,
|
||||
#[field(value = "fragment")]
|
||||
Fragment,
|
||||
#[field(value = "form_post")]
|
||||
FormPost,
|
||||
}
|
||||
|
||||
#[derive(Debug, FromForm, UriDisplayQuery)]
|
||||
pub struct AuthenticationRequest<'r> {
|
||||
pub response_type: &'r str,
|
||||
pub response_mode: ResponseMode,
|
||||
pub scope: &'r str,
|
||||
pub client_id: &'r str,
|
||||
pub redirect_uri: &'r str,
|
||||
pub state: &'r str,
|
||||
}
|
||||
|
|
@ -13,7 +13,7 @@ pub enum Page {
|
|||
Error(Error),
|
||||
Setup,
|
||||
Homepage(Homepage),
|
||||
SignIn(SignIn),
|
||||
Authorize(Authorize),
|
||||
}
|
||||
|
||||
impl Page {
|
||||
|
|
@ -23,7 +23,7 @@ impl Page {
|
|||
Page::Error(_) => "pages/error",
|
||||
Page::Setup => "pages/setup",
|
||||
Page::Homepage(_) => "pages/homepage",
|
||||
Page::SignIn(_) => "pages/auth/sign_in",
|
||||
Page::Authorize(_) => "pages/oauth/authorize",
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -33,7 +33,7 @@ impl Page {
|
|||
Page::Error(_) => "Error",
|
||||
Page::Setup => "Setup",
|
||||
Page::Homepage(_) => "Home",
|
||||
Page::SignIn(_) => "Sign in",
|
||||
Page::Authorize(_) => "Authorize app",
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -43,7 +43,7 @@ impl Page {
|
|||
Page::Error(_) => None,
|
||||
Page::Setup => None,
|
||||
Page::Homepage(_) => Some(Item::Home.into()),
|
||||
Page::SignIn(_) => None,
|
||||
Page::Authorize(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -53,7 +53,7 @@ impl Page {
|
|||
Page::Error(error) => Box::new(error),
|
||||
Page::Setup => Box::new(()),
|
||||
Page::Homepage(homepage) => Box::new(homepage),
|
||||
Page::SignIn(sig_in) => Box::new(sig_in),
|
||||
Page::Authorize(authorize) => Box::new(authorize),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
pub use crate::error::content::*;
|
||||
pub use crate::routes::auth::content::*;
|
||||
pub use crate::routes::oauth::content::*;
|
||||
pub use crate::routes::root::content::*;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use rocket::{Build, Rocket};
|
||||
|
||||
pub mod auth;
|
||||
pub mod oauth;
|
||||
pub mod root;
|
||||
pub mod setup;
|
||||
pub mod well_known;
|
||||
|
|
@ -11,6 +11,7 @@ pub(self) mod prelude {
|
|||
pub use crate::file_from_bytes::FileFromBytes;
|
||||
pub use crate::guards::*;
|
||||
pub use crate::id::*;
|
||||
pub use crate::oauth::*;
|
||||
pub use crate::page::{FlashKind, Page};
|
||||
pub use hash::Password;
|
||||
pub use id::*;
|
||||
|
|
|
|||
|
|
@ -1,95 +0,0 @@
|
|||
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!("/"))))
|
||||
}
|
||||
123
crates/ezidam/src/routes/oauth.rs
Normal file
123
crates/ezidam/src/routes/oauth.rs
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
use super::prelude::*;
|
||||
use rocket::{get, post};
|
||||
use settings::Settings;
|
||||
use users::User;
|
||||
|
||||
pub fn routes() -> Vec<Route> {
|
||||
routes![authorize_page, authorize, authorize_ezidam]
|
||||
}
|
||||
|
||||
pub mod content {
|
||||
use rocket::serde::Serialize;
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
#[derive(Clone)]
|
||||
pub struct Authorize {
|
||||
pub business_name: String,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: When already signed in, pass Result<GuardType> in existing routes directly
|
||||
|
||||
#[get("/oauth/authorize?<auth_request..>", rank = 2)]
|
||||
async fn authorize_page(
|
||||
mut db: Connection<Database>,
|
||||
flash: Option<FlashMessage<'_>>,
|
||||
auth_request: AuthenticationRequest<'_>,
|
||||
) -> Result<Template> {
|
||||
// TODO: parse "scope" and "response_type" -> from openid local crate
|
||||
// TODO: check if app is valid
|
||||
// TODO: check if redirect uri is a valid uri
|
||||
// TODO: wrap checking in function
|
||||
|
||||
// Define content
|
||||
let content = content::Authorize {
|
||||
business_name: Settings::get(&mut *db).await?.business_name().into(),
|
||||
};
|
||||
|
||||
Ok(flash
|
||||
.map(|flash| Page::with_flash(Page::Authorize(content.clone()), flash))
|
||||
.unwrap_or_else(|| Page::Authorize(content).into()))
|
||||
}
|
||||
|
||||
#[get("/oauth/authorize", rank = 3)]
|
||||
async fn authorize_ezidam(mut db: Connection<Database>) -> Redirect {
|
||||
// TODO: get ezidam app info from db
|
||||
let request = AuthenticationRequest {
|
||||
response_type: openid::CoreResponseType::Code.as_ref(),
|
||||
response_mode: ResponseMode::Query,
|
||||
scope: &openid::SupportedScopes::url_format(),
|
||||
client_id: "ezidam TODO HERE",
|
||||
redirect_uri: "put URI HERE",
|
||||
state: "TODO",
|
||||
};
|
||||
Redirect::to(uri!(authorize_page(auth_request = request)))
|
||||
}
|
||||
|
||||
#[derive(Debug, FromForm)]
|
||||
struct Authorize<'r> {
|
||||
pub login: &'r str,
|
||||
pub password: &'r str,
|
||||
}
|
||||
|
||||
fn flash(message: String, request: AuthenticationRequest) -> Flash<Redirect> {
|
||||
Flash::new(
|
||||
Redirect::to(uri!(authorize_page(auth_request = request))),
|
||||
FlashKind::Danger,
|
||||
message,
|
||||
)
|
||||
}
|
||||
|
||||
fn invalid_credentials(login: &str, request: AuthenticationRequest) -> Flash<Redirect> {
|
||||
flash(format!("Invalid credentials for {login}"), request)
|
||||
}
|
||||
|
||||
fn user_archived(login: &str, request: AuthenticationRequest) -> Flash<Redirect> {
|
||||
flash(format!("User {login} is archived"), request)
|
||||
}
|
||||
|
||||
#[post("/oauth/authorize?<auth_request..>", data = "<form>")]
|
||||
async fn authorize(
|
||||
form: Form<Authorize<'_>>,
|
||||
mut db: Connection<Database>,
|
||||
auth_request: AuthenticationRequest<'_>,
|
||||
) -> Result<Either<Redirect, Flash<Redirect>>> {
|
||||
// TODO: check app and stuff AGAIN, this is important
|
||||
|
||||
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, auth_request)));
|
||||
};
|
||||
|
||||
// Check if user is archived
|
||||
if user.is_archived() {
|
||||
return Ok(Either::Right(user_archived(form.login, auth_request)));
|
||||
}
|
||||
|
||||
// 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, auth_request))),
|
||||
};
|
||||
|
||||
// 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, auth_request)));
|
||||
}
|
||||
|
||||
// TODO: get ip
|
||||
// TODO: refresh token + jwt
|
||||
|
||||
// TODO: put more data
|
||||
Ok(Either::Left(Redirect::to(
|
||||
auth_request.redirect_uri.to_string(),
|
||||
)))
|
||||
}
|
||||
|
||||
// TODO: oauth redirect route for ezidam
|
||||
Loading…
Add table
Add a link
Reference in a new issue