diff --git a/api/src/lib.rs b/api/src/lib.rs index 15d65ca..8a8e476 100644 --- a/api/src/lib.rs +++ b/api/src/lib.rs @@ -69,6 +69,7 @@ async fn start() -> Result<(), rocket::Error> { routes::admin::hello, routes::admin::create_candidate, routes::admin::get_candidate, + routes::admin::reset_candidate_password, ], ) .mount( diff --git a/api/src/routes/admin.rs b/api/src/routes/admin.rs index 5c90006..3d1a437 100644 --- a/api/src/routes/admin.rs +++ b/api/src/routes/admin.rs @@ -121,4 +121,19 @@ pub async fn get_candidate( .map_err(|e| Custom(Status::from_code(e.code()).unwrap(), e.to_string()))?; Ok(Json(details)) +} + +#[post("/candidate//reset_password")] +pub async fn reset_candidate_password( + conn: Connection<'_, Db>, + _session: AdminAuth, + id: i32, +) -> Result> { + let db = conn.into_inner(); + + let new_password = CandidateService::reset_password(db, id) + .await + .map_err(|e| Custom(Status::from_code(e.code()).unwrap(), e.to_string()))?; + + Ok(new_password) } \ No newline at end of file diff --git a/core/src/database/mutation/candidate.rs b/core/src/database/mutation/candidate.rs index 058a2ec..9a25e3d 100644 --- a/core/src/database/mutation/candidate.rs +++ b/core/src/database/mutation/candidate.rs @@ -22,8 +22,23 @@ impl Mutation { updated_at: Set(chrono::offset::Local::now().naive_local()), ..Default::default() } - .insert(db) - .await + .insert(db) + .await + } + + pub async fn change_candidate_password( + db: &DbConn, + candidate: candidate::Model, + new_password_hash: String, + pub_key: String, + priv_key_enc: String, + ) -> Result { + let mut candidate: candidate::ActiveModel = candidate.into(); + candidate.code = Set(new_password_hash); + candidate.public_key = Set(pub_key); + candidate.private_key = Set(priv_key_enc); + + candidate.update(db).await } pub async fn add_candidate_details( diff --git a/core/src/services/candidate_service.rs b/core/src/services/candidate_service.rs index 9deacb1..a330444 100644 --- a/core/src/services/candidate_service.rs +++ b/core/src/services/candidate_service.rs @@ -94,12 +94,32 @@ impl CandidateService { hashed_password, hashed_personal_id_number, pubkey, - encrypted_priv_key, + encrypted_priv_key, ) .await?; Ok(candidate) } + pub async fn reset_password( + db: &DbConn, + id: i32, + ) -> Result { + let candidate = Query::find_candidate_by_id(db, id).await? + .ok_or(ServiceError::CandidateNotFound)?; + + let new_password_plain = crypto::random_8_char_string(); + let new_password_hash = crypto::hash_password(new_password_plain.clone()).await?; + + let (pubkey, priv_key_plain_text) = crypto::create_identity(); + let encrypted_priv_key = crypto::encrypt_password(priv_key_plain_text, + new_password_plain.to_string() + ).await?; + + Mutation::change_candidate_password(db, candidate, new_password_hash, pubkey, encrypted_priv_key).await?; + + Ok(new_password_plain) + } + pub(in crate::services) async fn add_candidate_details( db: &DbConn, candidate: candidate::Model, @@ -423,6 +443,27 @@ mod tests { assert!(!CandidateService::is_application_id_valid(101)); } + #[tokio::test] + async fn test_password_reset() { + let db = get_memory_sqlite_connection().await; + let (candidate, _parent) = put_user_data(&db).await; + + assert!( + CandidateService::login(&db, candidate.application, "test".to_string(), "127.0.0.1".to_string()).await.is_ok() + ); + + let new_password = CandidateService::reset_password(&db, candidate.application).await.unwrap(); + + assert!( + CandidateService::login(&db, candidate.application, "test".to_string(), "127.0.0.1".to_string()).await.is_err() + ); + + assert!( + CandidateService::login(&db, candidate.application, new_password, "127.0.0.1".to_string()).await.is_ok() + ); + + } + // TODO /* #[tokio::test] async fn test_list_candidates() {