Skip to content

Commit adae7ad

Browse files
committed
chore: Added change_admin_passowrd page and logic
1 parent 7905003 commit adae7ad

File tree

16 files changed

+201
-14
lines changed

16 files changed

+201
-14
lines changed

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ actix-web-flash-messages = { version = "0.3", features = ["cookies"] }
1818
actix-session = { version = "0.7", features = ["redis-rs-tls-session"] }
1919
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
2020
serde = {version = "1.0.163", features = ["derive"]}
21-
serde-aux = "4.2.0"
21+
serde-aux = "4.2.0"
22+
serde_json = "1"
2223
config = {version = "0.13", default-features = false, features = ["yaml"] }
2324
uuid = { version = "1.3.3", features = ["v4", "serde"] }
2425
chrono = "0.4.15"
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
-- Add migration script here
2+
INSERT INTO users (user_id, username, password_hash)
3+
VALUES (
4+
'ddf8994f-d522-4659-8d02-c1d479057be6',
5+
'admin',
6+
'$argon2id$v=19$m=4096,t=3,p=1$mCtoUCDJnzgrD9XuG60AIA$inM+EWltZekuCtI2QI4o5FdJxR9wZS6Nos4xic8fY4M'
7+
);

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ pub mod configuration;
33
pub mod domain;
44
pub mod email_client;
55
pub mod routes;
6+
pub mod session_state;
67
pub mod startup;
78
pub mod telemetry;
89
pub mod utils;

src/routes/admin/dashboard.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use actix_session::Session;
1+
use crate::session_state::TypedSession;
22
use actix_web::http::header::ContentType;
33
use actix_web::{web, HttpResponse};
44
use anyhow::Context;
@@ -14,13 +14,15 @@ where
1414
}
1515

1616
pub async fn admin_dashboard(
17-
session: Session,
17+
session: TypedSession,
1818
pool: web::Data<PgPool>,
1919
) -> Result<HttpResponse, actix_web::Error> {
20-
let username = if let Some(user_id) = session.get::<Uuid>("user_id").map_err(e500)? {
20+
let username = if let Some(user_id) = session.get_user_id().map_err(e500)? {
2121
get_username(user_id, &pool).await.map_err(e500)?
2222
} else {
23-
todo!()
23+
return Ok(HttpResponse::SeeOther()
24+
.insert_header(("location", "/login"))
25+
.finish());
2426
};
2527

2628
Ok(HttpResponse::Ok()

src/routes/admin/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
mod dashboard;
2+
mod password;
23

34
pub use dashboard::admin_dashboard;
5+
pub use password::*;

src/routes/admin/password/get.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
use actix_web::http::header::ContentType;
2+
use actix_web::HttpResponse;
3+
use crate::session_state::TypedSession;
4+
use crate::utils::{e500, see_other};
5+
6+
pub async fn change_password_form() -> Result<HttpResponse, actix_web::Error> {
7+
Ok(HttpResponse::Ok().content_type(ContentType::html()).body(
8+
r#"<!DOCTYPE html>
9+
<html lang="en">
10+
<head>
11+
<meta http-equiv="content-type" content="text/html; charset=utf-8">
12+
<title>Change Password</title>
13+
</head>
14+
<body>
15+
<form action="/admin/password" method="post">
16+
<label>Current password
17+
<input
18+
type="password"
19+
placeholder="Enter current password"
20+
name="current_password"
21+
>
22+
</label>
23+
<br>
24+
<label>New password
25+
<input
26+
type="password"
27+
placeholder="Enter new password"
28+
name="new_password"
29+
>
30+
</label>
31+
<br>
32+
<label>Confirm new password
33+
<input
34+
type="password"
35+
placeholder="Type the new password again"
36+
name="new_password_check"
37+
>
38+
</label>
39+
<br>
40+
<button type="submit">Change password</button>
41+
</form>
42+
<p><a href="/admin/dashboard">&lt;- Back</a></p>
43+
</body>
44+
</html>"#,
45+
))
46+
}

src/routes/admin/password/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
mod get;
2+
mod post;
3+
4+
pub use get::change_password_form;
5+
pub use post::change_password;

src/routes/admin/password/post.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
use actix_web::{web, HttpResponse};
2+
use secrecy::Secret;
3+
#[derive(serde::Deserialize)]
4+
pub struct FormData {
5+
current_password: Secret<String>,
6+
new_password: Secret<String>,
7+
new_password_check: Secret<String>,
8+
}
9+
pub async fn change_password(form: web::Form<FormData>) -> Result<HttpResponse, actix_web::Error> {
10+
todo!()
11+
}

src/routes/login/post.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::authentication::AuthError;
22
use crate::authentication::{validate_credentials, Credentials};
33
use crate::routes::error_chain_fmt;
4-
use actix_session::Session;
4+
use crate::session_state::TypedSession;
55
use actix_web::error::InternalError;
66
use actix_web::http::header::LOCATION;
77
use actix_web::web;
@@ -24,7 +24,7 @@ pub struct FormData {
2424
pub async fn login(
2525
form: web::Form<FormData>,
2626
pool: web::Data<PgPool>,
27-
session: Session,
27+
session: TypedSession,
2828
) -> Result<HttpResponse, InternalError<LoginError>> {
2929
let credentials = Credentials {
3030
username: form.0.username,
@@ -35,8 +35,9 @@ pub async fn login(
3535
Ok(user_id) => {
3636
tracing::Span::current().record("user_id", &tracing::field::display(&user_id));
3737

38+
session.renew();
3839
session
39-
.insert("user_id", user_id)
40+
.insert_user_id(user_id)
4041
.map_err(|e| login_redirect(LoginError::UnexpectedError(e.into())))?;
4142

4243
Ok(HttpResponse::SeeOther()

src/session_state.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
use actix_session::{Session, SessionExt, SessionGetError, SessionInsertError};
2+
use actix_web::dev::Payload;
3+
use actix_web::{FromRequest, HttpRequest};
4+
use std::future::{ready, Ready};
5+
use uuid::Uuid;
6+
pub struct TypedSession(Session);
7+
8+
impl TypedSession {
9+
const USER_ID_KEY: &'static str = "user_id";
10+
11+
pub fn renew(&self) {
12+
self.0.renew();
13+
}
14+
15+
pub fn insert_user_id(&self, user_id: Uuid) -> Result<(), SessionInsertError> {
16+
self.0.insert(Self::USER_ID_KEY, user_id)
17+
}
18+
19+
pub fn get_user_id(&self) -> Result<Option<Uuid>, SessionGetError> {
20+
self.0.get(Self::USER_ID_KEY)
21+
}
22+
}
23+
24+
impl FromRequest for TypedSession {
25+
// This is a complicated way of saying
26+
// "We return the same error returned by the
27+
// implementation of `FromRequest` for `Session`".
28+
type Error = <Session as FromRequest>::Error;
29+
// Rust does not yet support the `async` syntax in traits.
30+
// From request expects a `Future` as return type to allow for extractors
31+
// that need to perform asynchronous operations (e.g. a HTTP call)
32+
// We do not have a `Future`, because we don't perform any I/O,
33+
// so we wrap `TypedSession` into `Ready` to convert it into a `Future` that
34+
// resolves to the wrapped value the first time it's polled by the executor.
35+
type Future = Ready<Result<TypedSession, Self::Error>>;
36+
37+
// The `from_request` method is called by actix-web to convert the incoming request into a `TypedSession`.
38+
fn from_request(req: &HttpRequest, _payload: &mut Payload) -> Self::Future {
39+
ready(Ok(TypedSession(req.get_session())))
40+
}
41+
}

0 commit comments

Comments
 (0)