Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit d1330ea

Browse files
Feat: introduced hma secret feature
1 parent c387e50 commit d1330ea

File tree

17 files changed

+333
-9
lines changed

17 files changed

+333
-9
lines changed

‎Cargo.lock

Lines changed: 17 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ thiserror = "1"
3535
anyhow = "1"
3636
base64 = "0.13.0"
3737
argon2 = { version = "0.3", features = ["std"] }
38+
urlencoding = "2"
39+
htmlescape = "0.3"
40+
hmac = { version = "0.12", features = ["std"] }
41+
sha2 = "0.10"
42+
hex = "0.4"
3843

3944
[dev-dependencies]
4045
once_cell = "1"

‎configuration/base.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
application:
22
port: 8000
33
host: 0.0.0.0
4+
hmac_secret: "super-long-and-secret-random-key-needed-to-verify-message-integrity"
45
database:
56
host: "127.0.0.1"
67
port: 5432

‎src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
pub mod authentication;
12
pub mod configuration;
23
pub mod domain;
34
pub mod email_client;
45
pub mod routes;
6+
pub mod session_state;
57
pub mod startup;
68
pub mod telemetry;
9+
pub mod utils;

‎src/routes/home/home.html

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
5+
<title>Home</title>
6+
</head>
7+
<body>
8+
<p>Welcome to our newsletter!</p>
9+
</body>
10+
</html>

‎src/routes/home/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
use actix_web::{http::header::ContentType, HttpResponse};
2+
3+
pub async fn home() -> HttpResponse {
4+
HttpResponse::Ok()
5+
.content_type(ContentType::html())
6+
.body(include_str!("home.html"))
7+
}

‎src/routes/login/get.rs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
use crate::startup::HmacSecret;
2+
use actix_web::{http::header::ContentType, web, HttpResponse};
3+
use hmac::{Hmac, Mac};
4+
use secrecy::ExposeSecret;
5+
6+
#[derive(serde::Deserialize)]
7+
pub struct QueryParams {
8+
error: String,
9+
tag: String,
10+
}
11+
12+
impl QueryParams {
13+
fn verify(self, secret: &HmacSecret) -> Result<String, anyhow::Error> {
14+
let tag = hex::decode(self.tag)?;
15+
let query_string = format!("error={}", urlencoding::Encoded::new(&self.error));
16+
17+
let mut mac =
18+
Hmac::<sha2::Sha256>::new_from_slice(secret.0.expose_secret().as_bytes()).unwrap();
19+
mac.update(query_string.as_bytes());
20+
mac.verify_slice(&tag)?;
21+
22+
Ok(self.error)
23+
}
24+
}
25+
26+
pub async fn login_form(
27+
query: Option<web::Query<QueryParams>>,
28+
secret: web::Data<HmacSecret>,
29+
) -> HttpResponse {
30+
let error_html = match query {
31+
None => "".into(),
32+
Some(query) => match query.0.verify(&secret) {
33+
Ok(error) => {
34+
format!("<p><i>{}</i></p>", htmlescape::encode_minimal(&error))
35+
}
36+
Err(e) => {
37+
tracing::warn!(
38+
error.message = %e,
39+
error.cause_chain = ?e,
40+
"Failed to verify query parameters using the HMAC tag"
41+
);
42+
"".into()
43+
}
44+
},
45+
};
46+
HttpResponse::Ok()
47+
.content_type(ContentType::html())
48+
.body(format!(
49+
r#"<!DOCTYPE html>
50+
<html lang="en">
51+
<head>
52+
<meta http-equiv="content-type" content="text/html; charset=utf-8">
53+
<title>Login</title>
54+
</head>
55+
<body>
56+
{error_html}
57+
<form action="/login" method="post">
58+
<label>Username
59+
<input
60+
type="text"
61+
placeholder="Enter Username"
62+
name="username"
63+
>
64+
</label>
65+
<label>Password
66+
<input
67+
type="password"
68+
placeholder="Enter Password"
69+
name="password"
70+
>
71+
</label>
72+
<button type="submit">Login</button>
73+
</form>
74+
</body>
75+
</html>"#,
76+
))
77+
}

‎src/routes/login/login.html

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
5+
<title>Login</title>
6+
</head>
7+
<body>
8+
<form action="/login" method="post">
9+
<label
10+
>Username
11+
<input type="text" placeholder="Enter Username" name="username" />
12+
</label>
13+
<label
14+
>Password
15+
<input type="password" placeholder="Enter Password" name="password" />
16+
</label>
17+
<button type="submit">Login</button>
18+
</form>
19+
</body>
20+
</html>

‎src/routes/login/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::login_form;
5+
pub use post::login;

‎src/routes/login/post.rs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
use crate::authentication::AuthError;
2+
use crate::authentication::{validate_credentials, Credentials};
3+
use crate::routes::error_chain_fmt;
4+
use crate::startup::HmacSecret;
5+
use actix_web::error::InternalError;
6+
use actix_web::http::header::LOCATION;
7+
use actix_web::web;
8+
use actix_web::HttpResponse;
9+
use hmac::{Hmac, Mac};
10+
use secrecy::{ExposeSecret, Secret};
11+
use sqlx::PgPool;
12+
13+
#[derive(serde::Deserialize)]
14+
pub struct FormData {
15+
username: String,
16+
password: Secret<String>,
17+
}
18+
19+
#[tracing::instrument(
20+
skip(form, pool, secret),
21+
fields(username=tracing::field::Empty, user_id=tracing::field::Empty)
22+
)]
23+
// We are now injecting `PgPool` to retrieve stored credentials from the database
24+
pub async fn login(
25+
form: web::Form<FormData>,
26+
pool: web::Data<PgPool>,
27+
secret: web::Data<HmacSecret>,
28+
) -> Result<HttpResponse, InternalError<LoginError>> {
29+
let credentials = Credentials {
30+
username: form.0.username,
31+
password: form.0.password,
32+
};
33+
tracing::Span::current().record("username", &tracing::field::display(&credentials.username));
34+
match validate_credentials(credentials, &pool).await {
35+
Ok(user_id) => {
36+
tracing::Span::current().record("user_id", &tracing::field::display(&user_id));
37+
Ok(HttpResponse::SeeOther()
38+
.insert_header((LOCATION, "/"))
39+
.finish())
40+
}
41+
Err(e) => {
42+
let e = match e {
43+
AuthError::InvalidCredentials(_) => LoginError::AuthError(e.into()),
44+
AuthError::UnexpectedError(_) => LoginError::UnexpectedError(e.into()),
45+
};
46+
let query_string = format!("error={}", urlencoding::Encoded::new(e.to_string()));
47+
let hmac_tag = {
48+
let mut mac =
49+
Hmac::<sha2::Sha256>::new_from_slice(secret.0.expose_secret().as_bytes())
50+
.unwrap();
51+
mac.update(query_string.as_bytes());
52+
mac.finalize().into_bytes()
53+
};
54+
let response = HttpResponse::SeeOther()
55+
.insert_header((LOCATION, format!("/login?{query_string}&tag={hmac_tag:x}")))
56+
.finish();
57+
Err(InternalError::from_response(e, response))
58+
}
59+
}
60+
}
61+
62+
#[derive(thiserror::Error)]
63+
pub enum LoginError {
64+
#[error("Authentication failed")]
65+
AuthError(#[source] anyhow::Error),
66+
#[error("Something went wrong")]
67+
UnexpectedError(#[from] anyhow::Error),
68+
}
69+
70+
impl std::fmt::Debug for LoginError {
71+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
72+
error_chain_fmt(self, f)
73+
}
74+
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /