ezidam: authorize: don't prompt login if already authenticated

This commit is contained in:
Philippe Loctaux 2023-03-19 22:52:22 +01:00
parent 8db0bbb874
commit 1dec56ed14
5 changed files with 76 additions and 34 deletions

View file

@ -4,6 +4,7 @@ use jwt::JwtClaims;
use rocket::request::{FromRequest, Outcome}; use rocket::request::{FromRequest, Outcome};
use rocket::Request; use rocket::Request;
#[derive(Debug)]
pub struct JwtAdmin(pub JwtClaims); pub struct JwtAdmin(pub JwtClaims);
#[rocket::async_trait] #[rocket::async_trait]

View file

@ -1,13 +1,15 @@
use super::use_access_token_or_refresh_token; use super::use_access_token_or_refresh_token;
use super::Error;
use jwt::JwtClaims; use jwt::JwtClaims;
use rocket::request::{FromRequest, Outcome}; use rocket::request::{FromRequest, Outcome};
use rocket::Request; use rocket::Request;
#[derive(Debug)]
pub struct JwtUser(pub JwtClaims); pub struct JwtUser(pub JwtClaims);
#[rocket::async_trait] #[rocket::async_trait]
impl<'r> FromRequest<'r> for JwtUser { impl<'r> FromRequest<'r> for JwtUser {
type Error = super::Error; type Error = Error;
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> { async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
let get_admin: Option<bool> = None; let get_admin: Option<bool> = None;

View file

@ -15,6 +15,7 @@ pub fn routes() -> Vec<Route> {
} }
pub mod content { pub mod content {
use jwt::JwtClaims;
use rocket::serde::Serialize; use rocket::serde::Serialize;
#[derive(Serialize)] #[derive(Serialize)]
@ -23,6 +24,7 @@ pub mod content {
pub struct Authorize { pub struct Authorize {
pub app_name: String, pub app_name: String,
pub business_name: String, pub business_name: String,
pub user: Option<JwtClaims>,
} }
#[derive(Serialize)] #[derive(Serialize)]

View file

@ -6,11 +6,10 @@ use rocket::{get, post};
use settings::Settings; use settings::Settings;
use users::User; use users::User;
// TODO: When already signed in, pass Result<GuardType> in existing routes directly #[get("/oauth/authorize?<auth_request..>", rank = 1)]
#[get("/oauth/authorize?<auth_request..>", rank = 2)]
pub async fn authorize_page( pub async fn authorize_page(
mut db: Connection<Database>, mut db: Connection<Database>,
user: Option<JwtUser>,
flash: Option<FlashMessage<'_>>, flash: Option<FlashMessage<'_>>,
auth_request: AuthenticationRequest<'_>, auth_request: AuthenticationRequest<'_>,
) -> Result<Template> { ) -> Result<Template> {
@ -34,6 +33,7 @@ pub async fn authorize_page(
let content = super::content::Authorize { let content = super::content::Authorize {
app_name: app.label().into(), app_name: app.label().into(),
business_name: settings.business_name().into(), business_name: settings.business_name().into(),
user: user.map(|user| user.0),
}; };
Ok(flash Ok(flash
@ -41,7 +41,7 @@ pub async fn authorize_page(
.unwrap_or_else(|| Page::Authorize(content).into())) .unwrap_or_else(|| Page::Authorize(content).into()))
} }
#[get("/oauth/authorize", rank = 3)] #[get("/oauth/authorize", rank = 2)]
pub async fn authorize_ezidam(mut db: Connection<Database>) -> Result<Redirect> { pub async fn authorize_ezidam(mut db: Connection<Database>) -> Result<Redirect> {
let mut transaction = db.begin().await?; let mut transaction = db.begin().await?;
@ -92,7 +92,8 @@ fn user_archived(login: &str, request: AuthenticationRequest) -> Flash<Redirect>
#[post("/oauth/authorize?<auth_request..>", data = "<form>")] #[post("/oauth/authorize?<auth_request..>", data = "<form>")]
pub async fn authorize_form( pub async fn authorize_form(
form: Form<Authorize<'_>>, form: Option<Form<Authorize<'_>>>,
user: Option<JwtUser>,
mut db: Connection<Database>, mut db: Connection<Database>,
auth_request: AuthenticationRequest<'_>, auth_request: AuthenticationRequest<'_>,
) -> Result<Either<Redirect, Flash<Redirect>>> { ) -> Result<Either<Redirect, Flash<Redirect>>> {
@ -108,41 +109,59 @@ pub async fn authorize_form(
) )
.await?; .await?;
if form.login.is_empty() { let user_id = match user {
return Ok(Either::Right(invalid_form(auth_request))); Some(user) => {
} transaction.commit().await?;
let form = form.into_inner();
// Get user UserID(user.0.subject)
let Some(user) = User::get_by_login(&mut transaction, form.login).await? else { }
return Ok(Either::Right(invalid_credentials(form.login, auth_request))); None => {
let form = match form {
Some(form) => {
if form.login.is_empty() {
return Ok(Either::Right(invalid_form(auth_request)));
}
form.into_inner()
}
None => {
return Ok(Either::Right(invalid_form(auth_request)));
}
};
// 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)));
}
user.id().to_owned()
}
}; };
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 // Generate authorization code
let code = task::spawn_blocking(|| SecretString::new(35)).await?; let code = task::spawn_blocking(|| SecretString::new(35)).await?;
// Save authorization code // Save authorization code
let mut transaction = db.begin().await?; let mut transaction = db.begin().await?;
AuthorizationCode::insert(&mut transaction, code.as_ref(), app.id(), user.id()).await?; AuthorizationCode::insert(&mut transaction, code.as_ref(), app.id(), &user_id).await?;
transaction.commit().await?; transaction.commit().await?;
// Construct uri to redirect to // Construct uri to redirect to

View file

@ -24,6 +24,19 @@
<p class="text-muted">With your {{ business_name }} account</p> <p class="text-muted">With your {{ business_name }} account</p>
</div> </div>
<form action="" method="post" autocomplete="off" novalidate class="mt-4"> <form action="" method="post" autocomplete="off" novalidate class="mt-4">
{% if user %}
<div class="mb-4 text-center">
<span class="avatar avatar-xl mb-3"
style="background-image: url(/avatar/{{ user.sub }}?size=250)"></span>
<h3>
{% if user.name %}
{{ user.name }}
{% else %}
{{ user.username }}
{% endif %}
</h3>
</div>
{% else %}
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Login</label> <label class="form-label">Login</label>
<input name="login" type="text" class="form-control" placeholder="Email or username" <input name="login" type="text" class="form-control" placeholder="Email or username"
@ -36,15 +49,20 @@
autocomplete="off"> autocomplete="off">
</div> </div>
</div> </div>
{% endif %}
<div class="form-footer"> <div class="form-footer">
<button type="submit" class="btn btn-primary w-100">Sign in</button> <button type="submit" class="btn btn-primary w-100">Authorize</button>
</div> </div>
</form> </form>
</div> </div>
</div> </div>
{% if user %}
{% else %}
<div class="text-center text-muted mt-3"> <div class="text-center text-muted mt-3">
<a href="./sign-up.html" tabindex="-1">Reset password</a> <a href="./sign-up.html" tabindex="-1">Reset password</a>
</div> </div>
{% endif %}
</div> </div>
{% include "shell/footer" %} {% include "shell/footer" %}
</div> </div>