settings/security: logout everywhere: revoke all refresh tokens, new refresh + jwt
This commit is contained in:
parent
4d9305f3d2
commit
c1daa34f2c
4 changed files with 162 additions and 5 deletions
|
|
@ -12,6 +12,7 @@ pub fn routes() -> Vec<Route> {
|
||||||
user_settings_personal,
|
user_settings_personal,
|
||||||
user_settings_personal_form,
|
user_settings_personal_form,
|
||||||
user_settings_security,
|
user_settings_security,
|
||||||
|
user_settings_security_logout_everywhere,
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -39,5 +40,6 @@ pub mod content {
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct UserSecuritySettings {
|
pub struct UserSecuritySettings {
|
||||||
pub user: JwtClaims,
|
pub user: JwtClaims,
|
||||||
|
pub logout_time_effective: i64,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,128 @@
|
||||||
use crate::routes::prelude::*;
|
use crate::routes::prelude::*;
|
||||||
use rocket::get;
|
use crate::tokens::{generate_jwt, generate_refresh_token};
|
||||||
|
use crate::tokens::{
|
||||||
|
JWT_COOKIE_NAME, JWT_DURATION_MINUTES, REFRESH_TOKEN_COOKIE_NAME, REFRESH_TOKEN_DURATION_DAYS,
|
||||||
|
};
|
||||||
|
use apps::App;
|
||||||
|
use jwt::database::Key;
|
||||||
|
use jwt::PrivateKey;
|
||||||
|
use refresh_tokens::RefreshToken;
|
||||||
|
use rocket::http::{Cookie, CookieJar, SameSite};
|
||||||
|
use rocket::time::Duration;
|
||||||
|
use rocket::{get, post};
|
||||||
|
use settings::Settings;
|
||||||
|
use std::net::IpAddr;
|
||||||
|
use users::User;
|
||||||
|
|
||||||
#[get("/settings/security")]
|
#[get("/settings/security")]
|
||||||
pub async fn user_settings_security(
|
pub async fn user_settings_security(
|
||||||
jwt_user: JwtUser,
|
jwt_user: JwtUser,
|
||||||
flash: Option<FlashMessage<'_>>,
|
flash: Option<FlashMessage<'_>>,
|
||||||
) -> Result<Template> {
|
) -> Result<Template> {
|
||||||
let page =
|
let page = Page::UserSecuritySettings(super::content::UserSecuritySettings {
|
||||||
Page::UserSecuritySettings(super::content::UserSecuritySettings { user: jwt_user.0 });
|
user: jwt_user.0,
|
||||||
|
logout_time_effective: JWT_DURATION_MINUTES,
|
||||||
|
});
|
||||||
|
|
||||||
Ok(flash
|
Ok(flash
|
||||||
.map(|flash| Page::with_flash(page.clone(), flash))
|
.map(|flash| Page::with_flash(page.clone(), flash))
|
||||||
.unwrap_or_else(|| page.into()))
|
.unwrap_or_else(|| page.into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, FromForm)]
|
||||||
|
pub struct LogoutEverywhereForm {
|
||||||
|
pub logout_everywhere: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/settings/security/logout_everywhere", data = "<form>")]
|
||||||
|
pub async fn user_settings_security_logout_everywhere(
|
||||||
|
mut db: Connection<Database>,
|
||||||
|
jwt_user: JwtUser,
|
||||||
|
form: Form<LogoutEverywhereForm>,
|
||||||
|
ip_address: IpAddr,
|
||||||
|
cookie_jar: &CookieJar<'_>,
|
||||||
|
) -> Result<Flash<Redirect>> {
|
||||||
|
let (flash_kind, flash_message) = if form.logout_everywhere {
|
||||||
|
let mut transaction = db.begin().await?;
|
||||||
|
|
||||||
|
// Get app
|
||||||
|
let app = App::get_one_by_id(&mut transaction, "ezidam")
|
||||||
|
.await?
|
||||||
|
.ok_or_else(|| Error::not_found("Could not find application"))?;
|
||||||
|
|
||||||
|
// Get user info
|
||||||
|
let user = User::get_by_login(&mut transaction, &jwt_user.0.subject)
|
||||||
|
.await?
|
||||||
|
.ok_or_else(|| Error::not_found("Could not find user"))?;
|
||||||
|
|
||||||
|
// Revoke all refresh tokens for user
|
||||||
|
RefreshToken::revoke_all_for_user(&mut transaction, user.id()).await?;
|
||||||
|
|
||||||
|
// Generate refresh token
|
||||||
|
let refresh_token =
|
||||||
|
generate_refresh_token(&mut transaction, ip_address, user.id(), app.id())
|
||||||
|
.await
|
||||||
|
.map_err(Error::internal_server_error)?;
|
||||||
|
|
||||||
|
// Add refresh token as a cookie
|
||||||
|
let mut cookie = Cookie::new(REFRESH_TOKEN_COOKIE_NAME, refresh_token);
|
||||||
|
cookie.set_secure(true);
|
||||||
|
cookie.set_http_only(true);
|
||||||
|
cookie.set_same_site(SameSite::Strict);
|
||||||
|
cookie.set_max_age(Duration::days(REFRESH_TOKEN_DURATION_DAYS));
|
||||||
|
cookie_jar.add(cookie);
|
||||||
|
|
||||||
|
// Get base url
|
||||||
|
let settings = Settings::get(&mut transaction).await?;
|
||||||
|
let home_page = settings
|
||||||
|
.url()
|
||||||
|
.map(String::from)
|
||||||
|
.ok_or_else(|| Error::bad_request("Server url is not set"))?;
|
||||||
|
|
||||||
|
// Get latest key from database
|
||||||
|
let key = Key::get_most_recent(&mut transaction)
|
||||||
|
.await?
|
||||||
|
.ok_or_else(|| Error::internal_server_error("Failed to get a signing key"))?;
|
||||||
|
|
||||||
|
// Make sure key has not been revoked
|
||||||
|
if key.is_revoked() {
|
||||||
|
return Err(Error::internal_server_error("Signing key has been revoked"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Import private key
|
||||||
|
let private_key =
|
||||||
|
task::spawn_blocking(move || PrivateKey::from_der(key.private_der(), key.key_id()))
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
// Generate jwt
|
||||||
|
let jwt = generate_jwt(
|
||||||
|
&mut transaction,
|
||||||
|
&private_key,
|
||||||
|
&home_page,
|
||||||
|
&app.id().0,
|
||||||
|
&user,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(Error::internal_server_error)?;
|
||||||
|
|
||||||
|
// Add jwt as a cookie
|
||||||
|
let mut cookie = Cookie::new(JWT_COOKIE_NAME, jwt);
|
||||||
|
cookie.set_secure(true);
|
||||||
|
cookie.set_http_only(true);
|
||||||
|
cookie.set_same_site(SameSite::Strict);
|
||||||
|
cookie.set_max_age(Duration::minutes(JWT_DURATION_MINUTES));
|
||||||
|
cookie_jar.add(cookie);
|
||||||
|
|
||||||
|
transaction.commit().await?;
|
||||||
|
|
||||||
|
(FlashKind::Success, "You have been logged out everywhere.")
|
||||||
|
} else {
|
||||||
|
(FlashKind::Warning, "Nothing to do.")
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Flash::new(
|
||||||
|
Redirect::to(uri!(user_settings_security)),
|
||||||
|
flash_kind,
|
||||||
|
flash_message,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,6 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<!-- Logout everyone modal -->
|
<!-- Logout everyone modal -->
|
||||||
<div class="modal modal-blur" tabindex="-1" id="modal-logout-confirm">
|
<div class="modal modal-blur" tabindex="-1" id="modal-logout-confirm">
|
||||||
<div class="modal-dialog modal-sm modal-dialog-centered" role="document">
|
<div class="modal-dialog modal-sm modal-dialog-centered" role="document">
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,7 @@
|
||||||
<h3 class="card-title">Logout everywhere</h3>
|
<h3 class="card-title">Logout everywhere</h3>
|
||||||
<p class="card-subtitle">Logout from every application you signed in with your account.</p>
|
<p class="card-subtitle">Logout from every application you signed in with your account.</p>
|
||||||
<div>
|
<div>
|
||||||
<a href="#" class="btn btn-danger">
|
<a class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#modal-logout-confirm">
|
||||||
Logout everywhere
|
Logout everywhere
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -78,4 +78,47 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Logout everywhere modal -->
|
||||||
|
<div class="modal modal-blur" tabindex="-1" id="modal-logout-confirm">
|
||||||
|
<div class="modal-dialog modal-sm modal-dialog-centered" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
|
||||||
|
<div class="modal-status bg-danger"></div>
|
||||||
|
|
||||||
|
<div class="modal-body text-center py-4">
|
||||||
|
|
||||||
|
<div class="text-danger mb-2">
|
||||||
|
{% include "icons/alert-triangle-large" %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>Do you want to log out everywhere?</h3>
|
||||||
|
<div class="mt-2">You will need to log in again in all applications.</div>
|
||||||
|
<div class="mt-2">This action can take up to {{ logout_time_effective }} minutes to be effective.</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-footer">
|
||||||
|
<div class="w-100">
|
||||||
|
<div class="row">
|
||||||
|
|
||||||
|
<div class="col">
|
||||||
|
<a href="#" class="btn w-100" data-bs-dismiss="modal">Cancel</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col">
|
||||||
|
<form action="./security/logout_everywhere" method="post">
|
||||||
|
<button type="submit" name="logout_everywhere" value="true" class="btn btn-danger w-100">
|
||||||
|
Logout everywhere
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue