ezidam: oauth: split in multiple mods
This commit is contained in:
parent
8c8caa905d
commit
fd16e78fb1
2 changed files with 179 additions and 170 deletions
|
|
@ -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
|
|
||||||
|
|
|
||||||
167
crates/ezidam/src/routes/oauth/authorize.rs
Normal file
167
crates/ezidam/src/routes/oauth/authorize.rs
Normal 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)))
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue