started integrating the flow
This commit is contained in:
parent
e01df6266c
commit
77a681c02a
10 changed files with 220 additions and 157 deletions
|
@ -1,6 +1,7 @@
|
|||
pub mod admin;
|
||||
pub mod doll;
|
||||
pub mod migrate;
|
||||
pub mod otp;
|
||||
pub mod reseed;
|
||||
pub mod schema;
|
||||
pub mod user;
|
||||
|
|
11
src/db/otp.rs
Normal file
11
src/db/otp.rs
Normal file
|
@ -0,0 +1,11 @@
|
|||
use uuid::Uuid;
|
||||
|
||||
use super::schema::DbHook;
|
||||
|
||||
/// Checks that the provided user has at least one OTP method enabled
|
||||
pub async fn has_otp(db: &mut DbHook, id: &Uuid) -> sqlx::Result<bool> {
|
||||
sqlx::query_scalar!("select count(otp_method) from otp where user_id = $1", id)
|
||||
.fetch_one(&mut **db)
|
||||
.await
|
||||
.map(|count| count.unwrap_or(0) > 0)
|
||||
}
|
21
src/main.rs
21
src/main.rs
|
@ -68,20 +68,21 @@ fn rocket() -> _ {
|
|||
.mount(
|
||||
"/account",
|
||||
routes![
|
||||
account::index,
|
||||
account::qr_profile,
|
||||
account::show_settings,
|
||||
account::change_settings,
|
||||
account::change_password,
|
||||
account::common::index,
|
||||
account::common::qr_profile,
|
||||
account::settings::show_settings,
|
||||
account::settings::change_settings,
|
||||
account::settings::change_password,
|
||||
form::register_tag::show_register,
|
||||
form::register_tag::handle_register,
|
||||
form::register_tag::show_edit_tag,
|
||||
form::register_tag::handle_edit_tag,
|
||||
account::ask_delete,
|
||||
account::confirm_delete,
|
||||
account::ask_terminate_account,
|
||||
account::confirm_terminate_account,
|
||||
account::export_data,
|
||||
account::common::ask_delete,
|
||||
account::common::confirm_delete,
|
||||
account::common::ask_terminate_account,
|
||||
account::common::confirm_terminate_account,
|
||||
account::common::export_data,
|
||||
account::otp::show_otp_enable_start,
|
||||
],
|
||||
)
|
||||
.mount(
|
||||
|
|
154
src/routes/account/common.rs
Normal file
154
src/routes/account/common.rs
Normal file
|
@ -0,0 +1,154 @@
|
|||
use std::net::IpAddr;
|
||||
|
||||
use qrcode_generator::QrCodeEcc;
|
||||
use rocket::{
|
||||
http::{uri::Absolute, CookieJar},
|
||||
response::Redirect,
|
||||
serde::{json::Json, Serialize},
|
||||
State,
|
||||
};
|
||||
use rocket_dyn_templates::{context, Template};
|
||||
use sqlx::Acquire;
|
||||
|
||||
use crate::{
|
||||
auth::session,
|
||||
config::Config,
|
||||
db::{
|
||||
doll,
|
||||
schema::{DollProfile, DollTagsDb, User},
|
||||
user,
|
||||
},
|
||||
pages::CommonTemplateState,
|
||||
routes::error_handlers::{PageResult, RawResult},
|
||||
};
|
||||
|
||||
#[get("/")]
|
||||
pub async fn index(mut db: DollTagsDb, user: User, meta: CommonTemplateState) -> PageResult {
|
||||
let tags = doll::list(&mut *db, &user.id).await?;
|
||||
let archived_tags = doll::list_archived(&mut *db, &user.id).await?;
|
||||
|
||||
Ok(Template::render(
|
||||
"account/index",
|
||||
context! {
|
||||
meta,
|
||||
user,
|
||||
tags,
|
||||
archived_tags,
|
||||
},
|
||||
)
|
||||
.into())
|
||||
}
|
||||
|
||||
#[get("/qr-png/<id>")]
|
||||
pub fn qr_profile(id: &str, _user: User, config: &State<Config>) -> PageResult {
|
||||
let public_uri = Absolute::parse(&config.public_url)?;
|
||||
let built_uri = uri!(public_uri, crate::routes::public::short_url(id));
|
||||
let image = qrcode_generator::to_png_to_vec(built_uri.to_string(), QrCodeEcc::Low, 400)?;
|
||||
|
||||
Ok(image.into())
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
pub struct DataDump {
|
||||
account: User,
|
||||
tags: Vec<DollProfile>,
|
||||
reserved_tags: Vec<i32>,
|
||||
}
|
||||
|
||||
#[get("/data_dump")]
|
||||
pub async fn export_data(
|
||||
mut db: DollTagsDb,
|
||||
user: User,
|
||||
client_ip: IpAddr,
|
||||
) -> RawResult<Json<DataDump>> {
|
||||
let tags = doll::list_all(&mut *db, &user.id).await?;
|
||||
let reserved_tags = doll::list_archived(&mut *db, &user.id).await?;
|
||||
|
||||
warn!(
|
||||
"[audit|{}] [{}] exported data",
|
||||
client_ip,
|
||||
user.id.to_string()
|
||||
);
|
||||
|
||||
Ok(Json(DataDump {
|
||||
account: user,
|
||||
tags,
|
||||
reserved_tags,
|
||||
}))
|
||||
}
|
||||
|
||||
#[get("/delete/<id>")]
|
||||
pub async fn ask_delete(
|
||||
mut db: DollTagsDb,
|
||||
id: i32,
|
||||
user: User,
|
||||
meta: CommonTemplateState,
|
||||
) -> PageResult {
|
||||
let db_tag = doll::get(&mut *db, id, "", false).await?;
|
||||
|
||||
if let Some(tag) = db_tag {
|
||||
if tag.bound_to_id != user.id {
|
||||
Ok(Redirect::to(uri!("/account", index)).into())
|
||||
} else {
|
||||
Ok(Template::render(
|
||||
"tag/delete",
|
||||
context! {
|
||||
meta,
|
||||
tag,
|
||||
},
|
||||
)
|
||||
.into())
|
||||
}
|
||||
} else {
|
||||
Ok(Redirect::to(uri!("/account", index)).into())
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/yes_delete_this/<id>")]
|
||||
pub async fn confirm_delete(
|
||||
mut db: DollTagsDb,
|
||||
id: i32,
|
||||
user: User,
|
||||
client_ip: IpAddr,
|
||||
) -> PageResult {
|
||||
let mut trx = db.begin().await?;
|
||||
doll::delete(&mut trx, id, &user.id).await?;
|
||||
trx.commit().await?;
|
||||
|
||||
warn!(
|
||||
"[audit|{}] [{}] deleted tag {:0>6}",
|
||||
client_ip,
|
||||
user.id.to_string(),
|
||||
id
|
||||
);
|
||||
|
||||
Ok(Redirect::to(uri!("/account", index)).into())
|
||||
}
|
||||
|
||||
#[get("/terminate")]
|
||||
pub fn ask_terminate_account(_user: User, meta: CommonTemplateState) -> Template {
|
||||
Template::render("account/terminate", context! {meta})
|
||||
}
|
||||
|
||||
#[get("/termin@or")]
|
||||
pub async fn confirm_terminate_account(
|
||||
mut db: DollTagsDb,
|
||||
user: User,
|
||||
cookies: &CookieJar<'_>,
|
||||
client_ip: IpAddr,
|
||||
) -> PageResult {
|
||||
let mut trx = db.begin().await?;
|
||||
doll::delete_all_from_account(&mut trx, &user.id).await?;
|
||||
user::delete(&mut trx, &user.id).await?;
|
||||
session::logout(cookies);
|
||||
trx.commit().await?;
|
||||
|
||||
warn!(
|
||||
"[audit|{}] [{}] deleted account",
|
||||
client_ip,
|
||||
user.id.to_string()
|
||||
);
|
||||
|
||||
Ok(Redirect::to("/").into())
|
||||
}
|
3
src/routes/account/mod.rs
Normal file
3
src/routes/account/mod.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
pub mod common;
|
||||
pub mod otp;
|
||||
pub mod settings;
|
6
src/routes/account/otp.rs
Normal file
6
src/routes/account/otp.rs
Normal file
|
@ -0,0 +1,6 @@
|
|||
use rocket_dyn_templates::{context, Template};
|
||||
|
||||
#[get("/settings/otp")]
|
||||
pub fn show_otp_enable_start() -> Template {
|
||||
Template::render("account/otp/start", context! {})
|
||||
}
|
|
@ -1,67 +1,42 @@
|
|||
use std::net::IpAddr;
|
||||
|
||||
use qrcode_generator::QrCodeEcc;
|
||||
use rocket::{
|
||||
form::{self, Contextual, Error, Form},
|
||||
http::{uri::Absolute, CookieJar},
|
||||
response::Redirect,
|
||||
serde::{json::Json, Serialize},
|
||||
tokio::task,
|
||||
State,
|
||||
};
|
||||
use rocket_dyn_templates::{context, Template};
|
||||
use sqlx::Acquire;
|
||||
|
||||
use crate::{
|
||||
auth::{pw, session},
|
||||
config::Config,
|
||||
auth::pw,
|
||||
db::{
|
||||
doll,
|
||||
schema::{DollProfile, DollTagsDb, User},
|
||||
otp,
|
||||
schema::{DollTagsDb, User},
|
||||
user,
|
||||
},
|
||||
pages::CommonTemplateState,
|
||||
routes::error_handlers::PageResult,
|
||||
};
|
||||
|
||||
use super::error_handlers::{PageResult, RawResult};
|
||||
|
||||
#[get("/")]
|
||||
pub async fn index(mut db: DollTagsDb, user: User, meta: CommonTemplateState) -> PageResult {
|
||||
let tags = doll::list(&mut *db, &user.id).await?;
|
||||
let archived_tags = doll::list_archived(&mut *db, &user.id).await?;
|
||||
#[get("/settings")]
|
||||
pub async fn show_settings(
|
||||
mut db: DollTagsDb,
|
||||
user: User,
|
||||
meta: CommonTemplateState,
|
||||
) -> PageResult {
|
||||
let has_otp = otp::has_otp(&mut *db, &user.id).await?;
|
||||
|
||||
Ok(Template::render(
|
||||
"account/index",
|
||||
context! {
|
||||
meta,
|
||||
user,
|
||||
tags,
|
||||
archived_tags,
|
||||
},
|
||||
)
|
||||
.into())
|
||||
}
|
||||
|
||||
#[get("/qr-png/<id>")]
|
||||
pub fn qr_profile(id: &str, _user: User, config: &State<Config>) -> PageResult {
|
||||
let public_uri = Absolute::parse(&config.public_url)?;
|
||||
let built_uri = uri!(public_uri, crate::routes::public::short_url(id));
|
||||
let image = qrcode_generator::to_png_to_vec(built_uri.to_string(), QrCodeEcc::Low, 400)?;
|
||||
|
||||
Ok(image.into())
|
||||
}
|
||||
|
||||
#[get("/settings")]
|
||||
pub fn show_settings(user: User, meta: CommonTemplateState) -> Template {
|
||||
Template::render(
|
||||
"account/settings",
|
||||
context! {
|
||||
user,
|
||||
meta,
|
||||
otp: has_otp,
|
||||
prev_common: form::Context::default(),
|
||||
prev_password: form::Context::default(),
|
||||
},
|
||||
)
|
||||
.into())
|
||||
}
|
||||
|
||||
#[derive(FromForm)]
|
||||
|
@ -209,108 +184,3 @@ pub async fn change_password(
|
|||
|
||||
Ok(Redirect::to(uri!("/account", show_settings)).into())
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
pub struct DataDump {
|
||||
account: User,
|
||||
tags: Vec<DollProfile>,
|
||||
reserved_tags: Vec<i32>,
|
||||
}
|
||||
|
||||
#[get("/data_dump")]
|
||||
pub async fn export_data(
|
||||
mut db: DollTagsDb,
|
||||
user: User,
|
||||
client_ip: IpAddr,
|
||||
) -> RawResult<Json<DataDump>> {
|
||||
let tags = doll::list_all(&mut *db, &user.id).await?;
|
||||
let reserved_tags = doll::list_archived(&mut *db, &user.id).await?;
|
||||
|
||||
warn!(
|
||||
"[audit|{}] [{}] exported data",
|
||||
client_ip,
|
||||
user.id.to_string()
|
||||
);
|
||||
|
||||
Ok(Json(DataDump {
|
||||
account: user,
|
||||
tags,
|
||||
reserved_tags,
|
||||
}))
|
||||
}
|
||||
|
||||
#[get("/delete/<id>")]
|
||||
pub async fn ask_delete(
|
||||
mut db: DollTagsDb,
|
||||
id: i32,
|
||||
user: User,
|
||||
meta: CommonTemplateState,
|
||||
) -> PageResult {
|
||||
let db_tag = doll::get(&mut *db, id, "", false).await?;
|
||||
|
||||
if let Some(tag) = db_tag {
|
||||
if tag.bound_to_id != user.id {
|
||||
Ok(Redirect::to(uri!("/account", index)).into())
|
||||
} else {
|
||||
Ok(Template::render(
|
||||
"tag/delete",
|
||||
context! {
|
||||
meta,
|
||||
tag,
|
||||
},
|
||||
)
|
||||
.into())
|
||||
}
|
||||
} else {
|
||||
Ok(Redirect::to(uri!("/account", index)).into())
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/yes_delete_this/<id>")]
|
||||
pub async fn confirm_delete(
|
||||
mut db: DollTagsDb,
|
||||
id: i32,
|
||||
user: User,
|
||||
client_ip: IpAddr,
|
||||
) -> PageResult {
|
||||
let mut trx = db.begin().await?;
|
||||
doll::delete(&mut trx, id, &user.id).await?;
|
||||
trx.commit().await?;
|
||||
|
||||
warn!(
|
||||
"[audit|{}] [{}] deleted tag {:0>6}",
|
||||
client_ip,
|
||||
user.id.to_string(),
|
||||
id
|
||||
);
|
||||
|
||||
Ok(Redirect::to(uri!("/account", index)).into())
|
||||
}
|
||||
|
||||
#[get("/terminate")]
|
||||
pub fn ask_terminate_account(_user: User, meta: CommonTemplateState) -> Template {
|
||||
Template::render("account/terminate", context! {meta})
|
||||
}
|
||||
|
||||
#[get("/termin@or")]
|
||||
pub async fn confirm_terminate_account(
|
||||
mut db: DollTagsDb,
|
||||
user: User,
|
||||
cookies: &CookieJar<'_>,
|
||||
client_ip: IpAddr,
|
||||
) -> PageResult {
|
||||
let mut trx = db.begin().await?;
|
||||
doll::delete_all_from_account(&mut trx, &user.id).await?;
|
||||
user::delete(&mut trx, &user.id).await?;
|
||||
session::logout(cookies);
|
||||
trx.commit().await?;
|
||||
|
||||
warn!(
|
||||
"[audit|{}] [{}] deleted account",
|
||||
client_ip,
|
||||
user.id.to_string()
|
||||
);
|
||||
|
||||
Ok(Redirect::to("/").into())
|
||||
}
|
|
@ -85,17 +85,17 @@ pub async fn show_edit_tag(
|
|||
) -> PageResult {
|
||||
let normalized_id = match id_public_to_db(id) {
|
||||
Some(v) => v,
|
||||
None => return Ok(Redirect::to(uri!("/account", account::index)).into()),
|
||||
None => return Ok(Redirect::to(uri!("/account", account::common::index)).into()),
|
||||
};
|
||||
let tag = match doll::get(&mut *db, normalized_id, "", true).await? {
|
||||
Some(v) => {
|
||||
if v.bound_to_id != user.id {
|
||||
return Ok(Redirect::to(uri!("/account", account::index)).into());
|
||||
return Ok(Redirect::to(uri!("/account", account::common::index)).into());
|
||||
}
|
||||
|
||||
v
|
||||
}
|
||||
None => return Ok(Redirect::to(uri!("/account", account::index)).into()),
|
||||
None => return Ok(Redirect::to(uri!("/account", account::common::index)).into()),
|
||||
};
|
||||
|
||||
Ok(Template::render(
|
||||
|
@ -262,7 +262,9 @@ pub async fn handle_edit_tag(
|
|||
meta: CommonTemplateState,
|
||||
) -> PageResult {
|
||||
let id = match id_public_to_db(id) {
|
||||
None => return Ok(Redirect::to(uri!("/account", crate::routes::account::index)).into()),
|
||||
None => {
|
||||
return Ok(Redirect::to(uri!("/account", crate::routes::account::common::index)).into())
|
||||
}
|
||||
Some(v) => v,
|
||||
};
|
||||
let tag = match tag.value {
|
||||
|
|
0
templates/account/otp/start.html.tera
Normal file
0
templates/account/otp/start.html.tera
Normal file
|
@ -66,6 +66,21 @@
|
|||
</form>
|
||||
</div>
|
||||
|
||||
<section id="otp">
|
||||
{% if otp %}
|
||||
<p>Wow, you already have OTP enabled even though it's not yet implemented.</p>
|
||||
{% else %}
|
||||
<h3>Two-factor authentication</h3>
|
||||
|
||||
<p>
|
||||
You don't have two-factor auth enabled.<br />
|
||||
You can add one using your authenticator app of choice by clicking below.
|
||||
</p>
|
||||
|
||||
<a href="/account/settings/otp" class="btn">Enable 2FA with an authenticator</a>
|
||||
{% endif %}
|
||||
</section>
|
||||
|
||||
<section id="data-export">
|
||||
<h3>Exporting your data</h3>
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue