mcaptcha/api/v1/account/
password.rs1use actix_identity::Identity;
7use actix_web::{web, HttpResponse, Responder};
8use argon2_creds::Config;
9use db_core::Login;
10use serde::{Deserialize, Serialize};
11
12use crate::errors::*;
13use crate::*;
14
15#[derive(Clone, Debug, Deserialize, Serialize)]
16pub struct ChangePasswordReqest {
17 pub password: String,
18 pub new_password: String,
19 pub confirm_new_password: String,
20}
21
22pub struct UpdatePassword {
23 pub new_password: String,
24 pub confirm_new_password: String,
25}
26
27impl From<ChangePasswordReqest> for UpdatePassword {
28 fn from(s: ChangePasswordReqest) -> Self {
29 UpdatePassword {
30 new_password: s.new_password,
31 confirm_new_password: s.confirm_new_password,
32 }
33 }
34}
35
36async fn update_password_runner(
37 user: &str,
38 update: UpdatePassword,
39 data: &Data,
40) -> ServiceResult<()> {
41 if update.new_password != update.confirm_new_password {
42 return Err(ServiceError::PasswordsDontMatch);
43 }
44
45 let new_hash = data.creds.password(&update.new_password)?;
46
47 let p = db_core::NameHash {
48 username: user.to_owned(),
49 hash: new_hash,
50 };
51
52 data.db.update_password(&p).await?;
53 Ok(())
54}
55
56#[my_codegen::post(
57 path = "crate::V1_API_ROUTES.account.update_password",
58 wrap = "crate::api::v1::get_middleware()"
59)]
60async fn update_user_password(
61 id: Identity,
62 data: AppData,
63 payload: web::Json<ChangePasswordReqest>,
64) -> ServiceResult<impl Responder> {
65 if payload.new_password != payload.confirm_new_password {
66 return Err(ServiceError::PasswordsDontMatch);
67 }
68
69 let username = id.identity().unwrap();
70
71 let res = data.db.get_password(&Login::Username(&username)).await?;
73
74 if Config::verify(&res.hash, &payload.password)? {
75 let update: UpdatePassword = payload.into_inner().into();
76 update_password_runner(&username, update, &data).await?;
77 Ok(HttpResponse::Ok())
78 } else {
79 Err(ServiceError::WrongPassword)
80 }
81}
82
83pub fn services(cfg: &mut actix_web::web::ServiceConfig) {
84 cfg.service(update_user_password);
85}
86
87#[cfg(test)]
88pub mod tests {
89 use super::*;
90
91 use actix_web::http::StatusCode;
92 use actix_web::test;
93
94 use crate::api::v1::ROUTES;
95 use crate::tests::*;
96
97 #[actix_rt::test]
98 async fn update_password_works_pg() {
99 let data = crate::tests::pg::get_data().await;
100 update_password_works(data).await;
101 }
102
103 #[actix_rt::test]
104 async fn update_password_works_maria() {
105 let data = crate::tests::maria::get_data().await;
106 update_password_works(data).await;
107 }
108
109 pub async fn update_password_works(data: ArcData) {
110 const NAME: &str = "updatepassuser";
111 const PASSWORD: &str = "longpassword2";
112 const EMAIL: &str = "updatepassuser@a.com";
113
114 let data = &data;
115
116 delete_user(data, NAME).await;
117
118 let (_, signin_resp) = register_and_signin(data, NAME, EMAIL, PASSWORD).await;
119 let cookies = get_cookie!(signin_resp);
120 let app = get_app!(data).await;
121
122 let new_password = "newpassword";
123
124 let update_password = ChangePasswordReqest {
125 password: PASSWORD.into(),
126 new_password: new_password.into(),
127 confirm_new_password: PASSWORD.into(),
128 };
129
130 let res = update_password_runner(NAME, update_password.into(), data).await;
131 assert!(res.is_err());
132 assert_eq!(res, Err(ServiceError::PasswordsDontMatch));
133
134 let update_password = ChangePasswordReqest {
135 password: PASSWORD.into(),
136 new_password: new_password.into(),
137 confirm_new_password: new_password.into(),
138 };
139
140 assert!(update_password_runner(NAME, update_password.into(), data)
141 .await
142 .is_ok());
143
144 let update_password = ChangePasswordReqest {
145 password: new_password.into(),
146 new_password: new_password.into(),
147 confirm_new_password: PASSWORD.into(),
148 };
149
150 bad_post_req_test(
151 data,
152 NAME,
153 new_password,
154 ROUTES.account.update_password,
155 &update_password,
156 ServiceError::PasswordsDontMatch,
157 )
158 .await;
159
160 let update_password = ChangePasswordReqest {
161 password: PASSWORD.into(),
162 new_password: PASSWORD.into(),
163 confirm_new_password: PASSWORD.into(),
164 };
165
166 bad_post_req_test(
167 data,
168 NAME,
169 new_password,
170 ROUTES.account.update_password,
171 &update_password,
172 ServiceError::WrongPassword,
173 )
174 .await;
175
176 let update_password = ChangePasswordReqest {
177 password: new_password.into(),
178 new_password: PASSWORD.into(),
179 confirm_new_password: PASSWORD.into(),
180 };
181
182 let update_password_resp = test::call_service(
183 &app,
184 post_request!(&update_password, ROUTES.account.update_password)
185 .cookie(cookies)
186 .to_request(),
187 )
188 .await;
189 assert_eq!(update_password_resp.status(), StatusCode::OK);
190 }
191}