lgtm 2
Some checks failed
Build and publish Docker image / build-and-push (push) Failing after 2m43s
CI / Check (push) Failing after 3m7s

This commit is contained in:
Andras Schmelczer 2026-05-14 22:39:41 +01:00
parent a8de0a614d
commit 3fa95819e3
30 changed files with 907 additions and 205 deletions

View file

@ -14,6 +14,7 @@ use crate::state::SharedState;
#[derive(Deserialize)]
pub struct CheckoutRequest {
referral_code: Option<String>,
return_path: Option<String>,
}
#[derive(Serialize)]
@ -21,6 +22,27 @@ struct CheckoutResponse {
url: String,
}
fn sanitize_return_path(path: Option<&str>) -> &str {
let Some(path) = path else {
return "/pricing";
};
let path = path.split('#').next().unwrap_or(path);
if path.is_empty()
|| path.len() > 2048
|| !path.starts_with('/')
|| path.starts_with("//")
|| path.chars().any(char::is_control)
{
return "/pricing";
}
path
}
fn append_query_param(path: &str, key: &str, value: &str) -> String {
let separator = if path.contains('?') { '&' } else { '?' };
format!("{path}{separator}{key}={value}")
}
/// Create a reserved Stripe Checkout session for the lifetime license.
/// Requires authentication. Referral discounts are issued via invite redemption.
pub async fn post_checkout(
@ -34,9 +56,13 @@ pub async fn post_checkout(
None => return StatusCode::UNAUTHORIZED.into_response(),
};
let public_url = &state.public_url;
let success_url = format!("{public_url}/pricing?license_success=1");
let cancel_url = format!("{public_url}/pricing");
let public_url = state.public_url.trim_end_matches('/');
let return_path = sanitize_return_path(req.return_path.as_deref());
let success_url = format!(
"{public_url}{}",
append_query_param(return_path, "license_success", "1")
);
let cancel_url = format!("{public_url}{return_path}");
if req.referral_code.is_some() {
return (
@ -46,6 +72,10 @@ pub async fn post_checkout(
.into_response();
}
if user.is_admin || user.subscription == "licensed" {
return (StatusCode::CONFLICT, "This account already has full access").into_response();
}
match start_license_checkout(&state, &user, &success_url, &cancel_url, None, None).await {
Ok(CheckoutStart::Free) => {
info!(user_id = %user.id, "Granted free early-bird license");
@ -58,3 +88,38 @@ pub async fn post_checkout(
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sanitize_return_path_accepts_local_paths_and_strips_fragments() {
assert_eq!(
sanitize_return_path(Some("/map?postcode=SW1A#details")),
"/map?postcode=SW1A"
);
}
#[test]
fn sanitize_return_path_rejects_external_or_control_paths() {
assert_eq!(sanitize_return_path(Some("//evil.test/path")), "/pricing");
assert_eq!(
sanitize_return_path(Some("https://evil.test/path")),
"/pricing"
);
assert_eq!(sanitize_return_path(Some("/map\nbad")), "/pricing");
}
#[test]
fn append_query_param_preserves_existing_query_separator() {
assert_eq!(
append_query_param("/map?postcode=SW1A", "license_success", "1"),
"/map?postcode=SW1A&license_success=1"
);
assert_eq!(
append_query_param("/pricing", "license_success", "1"),
"/pricing?license_success=1"
);
}
}