ezidam: oauth: split in multiple mods

This commit is contained in:
Philippe Loctaux 2023-03-17 23:30:12 +01:00
parent 8c8caa905d
commit fd16e78fb1
2 changed files with 179 additions and 170 deletions

View file

@ -1,13 +1,17 @@
use super::prelude::*; use authorize::*;
use apps::App; use redirect::*;
use authorization_codes::AuthorizationCodes; use rocket::{routes, Route};
use hash::SecretString;
use rocket::{get, post}; mod authorize;
use settings::Settings; mod redirect;
use users::User;
pub fn routes() -> Vec<Route> { pub fn routes() -> Vec<Route> {
routes![authorize_page, authorize, authorize_ezidam] routes![
authorize_page,
authorize_form,
authorize_ezidam,
redirect_page
]
} }
pub mod content { pub mod content {
@ -21,165 +25,3 @@ pub mod content {
pub business_name: String, 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> {
let mut transaction = db.begin().await?;
// Get app info
let app = App::get_valid_app(
&mut transaction,
auth_request.response_type,
auth_request.scope,
auth_request.client_id,
auth_request.redirect_uri,
)
.await?;
let settings = Settings::get(&mut transaction).await?;
transaction.commit().await?;
// Define content
let content = content::Authorize {
app_name: app.label().into(),
business_name: settings.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>) -> Result<Redirect> {
let mut transaction = db.begin().await?;
// Get ezidam app info
let app_id = "ezidam";
let app = App::get_one_by_id(&mut transaction, app_id)
.await?
.ok_or_else(|| Error::not_found(app_id))?;
transaction.commit().await?;
let request = AuthenticationRequest {
response_type: openid::CoreResponseType::Code.as_ref(),
response_mode: ResponseMode::Query,
scope: &openid::SupportedScopes::url_format(),
client_id: app.id().as_ref(),
redirect_uri: app.redirect_uri(),
state: "TODO",
};
Ok(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_form(request: AuthenticationRequest) -> Flash<Redirect> {
flash("Please fill out the form".to_string(), request)
}
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>>> {
let mut transaction = db.begin().await?;
// Get app info
let app = App::get_valid_app(
&mut transaction,
auth_request.response_type,
auth_request.scope,
auth_request.client_id,
auth_request.redirect_uri,
)
.await?;
if form.login.is_empty() {
return Ok(Either::Right(invalid_form(auth_request)));
}
let form = form.into_inner();
// 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)));
};
transaction.commit().await?;
// 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)));
}
// Generate authorization code
let code = task::spawn_blocking(|| SecretString::new(35)).await?;
// Save authorization code
let mut transaction = db.begin().await?;
AuthorizationCodes::insert(&mut transaction, code.as_ref(), app.id(), user.id()).await?;
transaction.commit().await?;
// Construct uri to redirect to
let uri = {
let uri_mode = match auth_request.response_mode {
ResponseMode::Query => "?",
ResponseMode::Fragment => "#",
};
// Redirect + authorization code
let uri = format!("{}{}code={}", app.redirect_uri(), uri_mode, code.as_ref());
// Add state if present
if auth_request.state.is_empty() {
uri
} else {
format!("{}&state={}", uri, auth_request.state)
}
};
Ok(Either::Left(Redirect::found(uri)))
}
// TODO: oauth redirect route for ezidam

View file

@ -0,0 +1,167 @@
use crate::routes::prelude::*;
use apps::App;
use authorization_codes::AuthorizationCodes;
use hash::SecretString;
use rocket::{get, post};
use settings::Settings;
use users::User;
// TODO: When already signed in, pass Result<GuardType> in existing routes directly
#[get("/oauth/authorize?<auth_request..>", rank = 2)]
pub async fn authorize_page(
mut db: Connection<Database>,
flash: Option<FlashMessage<'_>>,
auth_request: AuthenticationRequest<'_>,
) -> Result<Template> {
let mut transaction = db.begin().await?;
// Get app info
let app = App::get_valid_app(
&mut transaction,
auth_request.response_type,
auth_request.scope,
auth_request.client_id,
auth_request.redirect_uri,
)
.await?;
let settings = Settings::get(&mut transaction).await?;
transaction.commit().await?;
// Define content
let content = super::content::Authorize {
app_name: app.label().into(),
business_name: settings.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)]
pub async fn authorize_ezidam(mut db: Connection<Database>) -> Result<Redirect> {
let mut transaction = db.begin().await?;
// Get ezidam app info
let app_id = "ezidam";
let app = App::get_one_by_id(&mut transaction, app_id)
.await?
.ok_or_else(|| Error::not_found(app_id))?;
transaction.commit().await?;
let request = AuthenticationRequest {
response_type: openid::CoreResponseType::Code.as_ref(),
response_mode: ResponseMode::Query,
scope: &openid::SupportedScopes::url_format(),
client_id: app.id().as_ref(),
redirect_uri: app.redirect_uri(),
state: "TODO",
};
Ok(Redirect::to(uri!(authorize_page(auth_request = request))))
}
#[derive(Debug, FromForm)]
pub 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_form(request: AuthenticationRequest) -> Flash<Redirect> {
flash("Please fill out the form".to_string(), request)
}
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>")]
pub async fn authorize_form(
form: Form<Authorize<'_>>,
mut db: Connection<Database>,
auth_request: AuthenticationRequest<'_>,
) -> Result<Either<Redirect, Flash<Redirect>>> {
let mut transaction = db.begin().await?;
// Get app info
let app = App::get_valid_app(
&mut transaction,
auth_request.response_type,
auth_request.scope,
auth_request.client_id,
auth_request.redirect_uri,
)
.await?;
if form.login.is_empty() {
return Ok(Either::Right(invalid_form(auth_request)));
}
let form = form.into_inner();
// 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)));
};
transaction.commit().await?;
// 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)));
}
// Generate authorization code
let code = task::spawn_blocking(|| SecretString::new(35)).await?;
// Save authorization code
let mut transaction = db.begin().await?;
AuthorizationCodes::insert(&mut transaction, code.as_ref(), app.id(), user.id()).await?;
transaction.commit().await?;
// Construct uri to redirect to
let uri = {
let uri_mode = match auth_request.response_mode {
ResponseMode::Query => "?",
ResponseMode::Fragment => "#",
};
// Redirect + authorization code
let uri = format!("{}{}code={}", app.redirect_uri(), uri_mode, code.as_ref());
// Add state if present
if auth_request.state.is_empty() {
uri
} else {
format!("{}&state={}", uri, auth_request.state)
}
};
Ok(Either::Left(Redirect::found(uri)))
}