Update code2, and change secret protocol for code 1

This commit is contained in:
2026-10-12 23:20:30 -05:00
parent 8f0b2931e9
commit 1ff23a3004
9 changed files with 114 additions and 106 deletions
+9 -46
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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,
})
}
}