|
@@ -1,17 +1,39 @@
|
|
|
-use tide::http::mime;
|
|
|
use microrm::prelude::*;
|
|
|
+use tide::http::mime;
|
|
|
|
|
|
use crate::{schema, UIDCError};
|
|
|
|
|
|
type Request = tide::Request<super::ServerStateWrapper>;
|
|
|
|
|
|
-fn generate_template_data(qi: µrm::QueryInterface, realm: schema::RealmID, user: schema::UserID) -> Result<serde_json::Value, UIDCError> {
|
|
|
- let user = qi.get().by_id(&user).one()?.ok_or(UIDCError::Abort("couldn't find user"))?;
|
|
|
-
|
|
|
- let has_totp = qi.get().by(schema::AuthChallenge::User, &user.id()).by(schema::AuthChallenge::ChallengeType, &schema::AuthChallengeType::TOTP).one()?.is_some();
|
|
|
+fn generate_template_data(
|
|
|
+ qi: µrm::QueryInterface,
|
|
|
+ realm: schema::RealmID,
|
|
|
+ user: schema::UserID,
|
|
|
+) -> Result<serde_json::Value, UIDCError> {
|
|
|
+ let realm = qi
|
|
|
+ .get()
|
|
|
+ .by_id(&realm)
|
|
|
+ .one()?
|
|
|
+ .ok_or(UIDCError::Abort("no such realm"))?;
|
|
|
+ let user = qi
|
|
|
+ .get()
|
|
|
+ .by_id(&user)
|
|
|
+ .one()?
|
|
|
+ .ok_or(UIDCError::Abort("couldn't find user"))?;
|
|
|
+
|
|
|
+ let has_totp = qi
|
|
|
+ .get()
|
|
|
+ .by(schema::AuthChallenge::User, &user.id())
|
|
|
+ .by(
|
|
|
+ schema::AuthChallenge::ChallengeType,
|
|
|
+ &schema::AuthChallengeType::TOTP,
|
|
|
+ )
|
|
|
+ .one()?
|
|
|
+ .is_some();
|
|
|
|
|
|
let template_data = serde_json::json!({
|
|
|
"username": user.username,
|
|
|
+ "realm": realm.shortname,
|
|
|
"totp_control": if has_totp {
|
|
|
serde_json::json!([{ "value": "keep", "text": "Keep as-is"}, { "value": "remove", "text": "Remove" }, { "value": "reset", "text": "Reset" }])
|
|
|
} else {
|
|
@@ -22,14 +44,18 @@ fn generate_template_data(qi: µrm::QueryInterface, realm: schema::RealmID,
|
|
|
Ok(template_data)
|
|
|
}
|
|
|
|
|
|
-async fn um_index(mut req: Request) -> tide::Result<tide::Response> {
|
|
|
+async fn um_index(req: Request) -> tide::Result<tide::Response> {
|
|
|
let shelper = super::session::SessionHelper::new(&req);
|
|
|
|
|
|
let (realm, user) = match shelper.verify_session(&req) {
|
|
|
Some(v) => v,
|
|
|
None => {
|
|
|
- return Ok(tide::Redirect::temporary(format!("../v1/session/login?redirect={}", req.url())).into())
|
|
|
- },
|
|
|
+ return Ok(tide::Redirect::temporary(format!(
|
|
|
+ "../v1/session/login?redirect={}",
|
|
|
+ req.url()
|
|
|
+ ))
|
|
|
+ .into())
|
|
|
+ }
|
|
|
};
|
|
|
let qi = req.state().core.pool.query_interface();
|
|
|
|
|
@@ -37,38 +63,114 @@ async fn um_index(mut req: Request) -> tide::Result<tide::Response> {
|
|
|
let template_data = generate_template_data(qi, realm, user)?;
|
|
|
|
|
|
Ok(tide::Response::builder(200)
|
|
|
- .content_type(mime::HTML)
|
|
|
- .body(req.state().core.templates.render("um_index", &template_data).map_err(|_| tide::Error::from_str(500, "error rendering template"))?)
|
|
|
- .build())
|
|
|
+ .content_type(mime::HTML)
|
|
|
+ .body(
|
|
|
+ req.state()
|
|
|
+ .core
|
|
|
+ .templates
|
|
|
+ .render("um_index", &template_data)
|
|
|
+ .map_err(|_| tide::Error::from_str(500, "error rendering template"))?,
|
|
|
+ )
|
|
|
+ .build())
|
|
|
+}
|
|
|
+
|
|
|
+#[derive(serde::Deserialize)]
|
|
|
+struct UpdateForm {
|
|
|
+ current_password: String,
|
|
|
+ new_password: Option<String>,
|
|
|
+ new_password_repeated: Option<String>,
|
|
|
+ totp_control: Option<String>,
|
|
|
}
|
|
|
|
|
|
async fn um_update(mut req: Request) -> tide::Result<tide::Response> {
|
|
|
+ let update_form: UpdateForm = req.body_form().await?;
|
|
|
+
|
|
|
let shelper = super::session::SessionHelper::new(&req);
|
|
|
|
|
|
- let (realm, user) = match shelper.verify_session(&req) {
|
|
|
+ let (realm, user_id) = match shelper.verify_session(&req) {
|
|
|
Some(v) => v,
|
|
|
None => {
|
|
|
return Ok(tide::Redirect::temporary("../v1/session/login?redirect=../../um/").into())
|
|
|
- },
|
|
|
+ }
|
|
|
};
|
|
|
+
|
|
|
let qi = req.state().core.pool.query_interface();
|
|
|
|
|
|
- let progress : Result<Vec<String>, UIDCError> = {
|
|
|
+ log::info!("processing update request...");
|
|
|
+
|
|
|
+ let progress: Result<Vec<String>, UIDCError> = (|| {
|
|
|
let mut info_msgs = vec![];
|
|
|
|
|
|
- let password_challenge = qi.get().by(schema::AuthChallenge::User, &user).by(schema::AuthChallenge::ChallengeType, &schema::AuthChallengeType::Password).one()?;
|
|
|
+ let user = crate::user::User::from_id(user_id);
|
|
|
+ let challenge = user.verify_challenge(
|
|
|
+ &qi,
|
|
|
+ schema::AuthChallengeType::Password,
|
|
|
+ update_form.current_password.as_bytes(),
|
|
|
+ )?;
|
|
|
+ if !challenge {
|
|
|
+ Err(UIDCError::Abort("password verification failed"))?
|
|
|
+ }
|
|
|
+
|
|
|
+ if let Some((new_pass, new_pass_repeated)) = update_form
|
|
|
+ .new_password
|
|
|
+ .as_ref()
|
|
|
+ .zip(update_form.new_password_repeated.as_ref())
|
|
|
+ {
|
|
|
+ if new_pass != new_pass_repeated {
|
|
|
+ Err(UIDCError::Abort("entered passwords do not match"))?
|
|
|
+ }
|
|
|
+ if new_pass.len() > 0 {
|
|
|
+ user.set_new_password(qi, new_pass.as_bytes())?;
|
|
|
+ info_msgs.push("Updated password!".into());
|
|
|
+ }
|
|
|
+ } else if update_form.new_password.is_some() || update_form.new_password_repeated.is_some()
|
|
|
+ {
|
|
|
+ Err(UIDCError::Abort("must enter new password twice"))?
|
|
|
+ }
|
|
|
+
|
|
|
+ if let Some(totp) = update_form.totp_control.as_ref() {
|
|
|
+ if totp == "remove" {
|
|
|
+ user.clear_totp(qi)?;
|
|
|
+ info_msgs.push("Cleared TOTP setup".into());
|
|
|
+ } else if totp == "reset" {
|
|
|
+ let (_secret, _uri) = user.generate_totp_with_uri(qi)?;
|
|
|
+ Err(UIDCError::Abort("totp setup outside of cli not supported"))?
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
Ok(info_msgs)
|
|
|
- };
|
|
|
-
|
|
|
- let mut template_data = generate_template_data(qi, realm, user)?;
|
|
|
-
|
|
|
- template_data.as_object_mut().and_then(|o| o.insert("info_msg".into(), serde_json::json!(["Update request received"])));
|
|
|
+ })();
|
|
|
+
|
|
|
+ let mut template_data = generate_template_data(qi, realm, user_id)?;
|
|
|
+
|
|
|
+ match progress {
|
|
|
+ Ok(info_msgs) => {
|
|
|
+ template_data
|
|
|
+ .as_object_mut()
|
|
|
+ .and_then(|o| o.insert("info_msg".into(), serde_json::json!(info_msgs)));
|
|
|
+ }
|
|
|
+ Err(UIDCError::Abort(msg)) => {
|
|
|
+ template_data
|
|
|
+ .as_object_mut()
|
|
|
+ .and_then(|o| o.insert("error_msg".into(), serde_json::json!([msg])));
|
|
|
+ }
|
|
|
+ Err(e) => {
|
|
|
+ template_data
|
|
|
+ .as_object_mut()
|
|
|
+ .and_then(|o| o.insert("error_msg".into(), serde_json::json!([e.to_string()])));
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
Ok(tide::Response::builder(200)
|
|
|
- .content_type(mime::HTML)
|
|
|
- .body(req.state().core.templates.render("um_index", &template_data).map_err(|_| tide::Error::from_str(500, "error rendering template"))?)
|
|
|
- .build())
|
|
|
+ .content_type(mime::HTML)
|
|
|
+ .body(
|
|
|
+ req.state()
|
|
|
+ .core
|
|
|
+ .templates
|
|
|
+ .render("um_index", &template_data)
|
|
|
+ .map_err(|_| tide::Error::from_str(500, "error rendering template"))?,
|
|
|
+ )
|
|
|
+ .build())
|
|
|
}
|
|
|
|
|
|
pub(super) fn um_server(mut route: tide::Route<super::ServerStateWrapper>) {
|