lgtm 2
This commit is contained in:
parent
a8de0a614d
commit
3fa95819e3
30 changed files with 907 additions and 205 deletions
|
|
@ -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"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue