Update code2, and change secret protocol for code 1
This commit is contained in:
+9
-46
@@ -1,13 +1,11 @@
|
||||
use axum::{extract::ConnectInfo, http::StatusCode, Json};
|
||||
use hmac::{Hmac, Mac};
|
||||
use serde_json::Value;
|
||||
use sha2::Sha256;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
pub const ID: &str = "code1";
|
||||
const SELF_NOTIFICATION: &str = "Code 1 has been submitted while the server is live.";
|
||||
const CHALLENGE_1_FIRST: &str = "TODO: secded data";
|
||||
const CHALLENGE_1_SECOND: &str = "SECDED-13";
|
||||
const CHALLENGE_1_SECOND: &str = "SECDED-13\nMSB-LSB";
|
||||
|
||||
pub async fn buzz_event_handler(
|
||||
ConnectInfo(address): ConnectInfo<SocketAddr>,
|
||||
@@ -22,31 +20,21 @@ pub async fn buzz_event_handler(
|
||||
"delta_ms must be a signed 64-bit integer".to_string(),
|
||||
)
|
||||
})?;
|
||||
let signature = payload
|
||||
.get("signature")
|
||||
let secret = payload
|
||||
.get("secret")
|
||||
.and_then(Value::as_str)
|
||||
.ok_or_else(|| {
|
||||
(
|
||||
StatusCode::BAD_REQUEST,
|
||||
"signature must be a hexadecimal HMAC-SHA256 string".to_string(),
|
||||
"secret must be a string".to_string(),
|
||||
)
|
||||
})?;
|
||||
let signature = decode_hex_signature(signature).ok_or_else(|| {
|
||||
(
|
||||
if secret != crate::secrets::runtime().code1_secret {
|
||||
return Err((
|
||||
StatusCode::UNAUTHORIZED,
|
||||
"Invalid buzz event signature".to_string(),
|
||||
)
|
||||
})?;
|
||||
let mut mac =
|
||||
Hmac::<Sha256>::new_from_slice(crate::secrets::runtime().code1_hmac_secret.as_bytes())
|
||||
.expect("HMAC accepts secrets of any length");
|
||||
mac.update(delta_ms.to_string().as_bytes());
|
||||
mac.verify_slice(&signature).map_err(|_| {
|
||||
(
|
||||
StatusCode::UNAUTHORIZED,
|
||||
"Invalid buzz event signature".to_string(),
|
||||
)
|
||||
})?;
|
||||
"Invalid buzz event secret".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let live = crate::manage::server_live_state();
|
||||
crate::codes::history::record_successful_submission(address.ip(), live, ID)
|
||||
@@ -62,31 +50,6 @@ pub async fn buzz_event_handler(
|
||||
Ok(StatusCode::ACCEPTED)
|
||||
}
|
||||
|
||||
fn decode_hex_signature(signature: &str) -> Option<Vec<u8>> {
|
||||
if signature.len() != 64 {
|
||||
return None;
|
||||
}
|
||||
|
||||
signature
|
||||
.as_bytes()
|
||||
.chunks_exact(2)
|
||||
.map(|pair| {
|
||||
let high = hex_nibble(pair[0])?;
|
||||
let low = hex_nibble(pair[1])?;
|
||||
Some((high << 4) | low)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn hex_nibble(byte: u8) -> Option<u8> {
|
||||
match byte {
|
||||
b'0'..=b'9' => Some(byte - b'0'),
|
||||
b'a'..=b'f' => Some(byte - b'a' + 10),
|
||||
b'A'..=b'F' => Some(byte - b'A' + 10),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
async fn on_submit(live: bool, delta_ms: i64) -> Result<(), String> {
|
||||
let direction = match delta_ms.cmp(&0) {
|
||||
std::cmp::Ordering::Less => "short",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
pub const ID: &str = "code2";
|
||||
pub const CODE: &str = "Qj4eAn";
|
||||
pub const NEXT_STEP_TEXT: &str = "TODO: replace this with the next step.";
|
||||
pub const SUCCESS_MESSAGE: &str = "Congratulations on completing step 2.";
|
||||
|
||||
const SELF_NOTIFICATION: &str = "Code 2 has been submitted.";
|
||||
|
||||
|
||||
+52
-5
@@ -12,10 +12,24 @@ pub mod code2;
|
||||
|
||||
pub mod history;
|
||||
|
||||
#[derive(Clone, serde::Deserialize, serde::Serialize)]
|
||||
struct SubmissionResponse {
|
||||
message: String,
|
||||
download: Option<DownloadInfo>,
|
||||
}
|
||||
|
||||
#[derive(Clone, serde::Deserialize, serde::Serialize)]
|
||||
struct DownloadInfo {
|
||||
href: String,
|
||||
filename: String,
|
||||
title: String,
|
||||
description: String,
|
||||
}
|
||||
|
||||
#[server]
|
||||
async fn submit_code(code: String) -> Result<String, ServerFnError> {
|
||||
async fn submit_code(code: String) -> Result<Option<SubmissionResponse>, ServerFnError> {
|
||||
if code != crate::codes::code2::CODE {
|
||||
return Ok("This code is not recognized.".to_string());
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let live = crate::manage::server_live_state();
|
||||
@@ -26,7 +40,15 @@ async fn submit_code(code: String) -> Result<String, ServerFnError> {
|
||||
.await
|
||||
.map_err(ServerFnError::new)?;
|
||||
|
||||
Ok(crate::codes::code2::NEXT_STEP_TEXT.to_string())
|
||||
Ok(Some(SubmissionResponse {
|
||||
message: crate::codes::code2::SUCCESS_MESSAGE.to_string(),
|
||||
download: Some(DownloadInfo {
|
||||
href: "/challenge3.txt".to_string(),
|
||||
filename: "challenge3.txt".to_string(),
|
||||
title: "Download challenge 3".to_string(),
|
||||
description: "TXT file".to_string(),
|
||||
}),
|
||||
}))
|
||||
}
|
||||
|
||||
fn encode_path_segment(value: &str) -> String {
|
||||
@@ -78,6 +100,28 @@ pub fn HomePage() -> impl IntoView {
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn SubmissionResult(response: SubmissionResponse) -> impl IntoView {
|
||||
let download = response.download.map(|download| {
|
||||
view! {
|
||||
<a
|
||||
class="download-card"
|
||||
href=download.href
|
||||
download=download.filename
|
||||
>
|
||||
<span class="download-title">{download.title}</span>
|
||||
<span class="download-description">{download.description}</span>
|
||||
</a>
|
||||
}
|
||||
});
|
||||
|
||||
view! {
|
||||
<h1>"Submission result"</h1>
|
||||
<p class="intro">{response.message}</p>
|
||||
{download}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn SubmitPage() -> impl IntoView {
|
||||
let params = use_params_map();
|
||||
@@ -90,9 +134,12 @@ pub fn SubmitPage() -> impl IntoView {
|
||||
<Suspense fallback=|| view! { <p class="loading">"Checking code..."</p> }>
|
||||
{move || Suspend::new(async move {
|
||||
match submission.await {
|
||||
Ok(message) => view! {
|
||||
Ok(Some(response)) => {
|
||||
view! { <SubmissionResult response/> }.into_any()
|
||||
},
|
||||
Ok(None) => view! {
|
||||
<h1>"Submission result"</h1>
|
||||
<p class="intro">{message}</p>
|
||||
<p class="intro">"This code is not recognized."</p>
|
||||
}.into_any(),
|
||||
Err(error) => view! {
|
||||
<p class="error-message" role="alert">
|
||||
|
||||
+6
-8
@@ -6,7 +6,7 @@ pub(crate) const TEXTBEE_DEVICE_ID: &str = "TEXTBEE_DEVICE_ID";
|
||||
pub(crate) const RECIPIENT_SELF: &str = "RECIPIENT_SELF";
|
||||
pub(crate) const RECIPIENT_1: &str = "RECIPIENT_1";
|
||||
pub(crate) const RECIPIENT_2: &str = "RECIPIENT_2";
|
||||
pub(crate) const CODE1_HMAC_SECRET: &str = "CODE1_HMAC_SECRET";
|
||||
pub(crate) const CODE1_SECRET: &str = "CODE1_SECRET";
|
||||
|
||||
static RUNTIME_SECRETS: OnceLock<RuntimeSecrets> = OnceLock::new();
|
||||
|
||||
@@ -17,7 +17,7 @@ pub(crate) struct RuntimeSecrets {
|
||||
pub(crate) recipient_self: String,
|
||||
pub(crate) recipient_1: String,
|
||||
pub(crate) recipient_2: String,
|
||||
pub(crate) code1_hmac_secret: String,
|
||||
pub(crate) code1_secret: String,
|
||||
}
|
||||
|
||||
pub fn init() -> Result<(), String> {
|
||||
@@ -53,12 +53,10 @@ impl RuntimeSecrets {
|
||||
let recipient_self = required_recipient(RECIPIENT_SELF)?;
|
||||
let recipient_1 = required_recipient(RECIPIENT_1)?;
|
||||
let recipient_2 = required_recipient(RECIPIENT_2)?;
|
||||
let code1_hmac_secret = required(CODE1_HMAC_SECRET)?;
|
||||
let code1_secret = required(CODE1_SECRET)?;
|
||||
|
||||
if code1_hmac_secret.len() < 32 {
|
||||
return Err(format!(
|
||||
"{CODE1_HMAC_SECRET} must contain at least 32 bytes"
|
||||
));
|
||||
if code1_secret.len() < 32 {
|
||||
return Err(format!("{CODE1_SECRET} must contain at least 32 bytes"));
|
||||
}
|
||||
|
||||
let textbee_device_id = env::var(TEXTBEE_DEVICE_ID)
|
||||
@@ -72,7 +70,7 @@ impl RuntimeSecrets {
|
||||
recipient_self,
|
||||
recipient_1,
|
||||
recipient_2,
|
||||
code1_hmac_secret,
|
||||
code1_secret,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user