Skip to main content

max / makenotwork

Cap platform credit as spend-once balance; authenticate inbound sender Two /audit Run 13 launch-gating fixes: - Cart platform-credit multiplication (SERIOUS): a platform-wide Fan+ credit was applied per eligible cart line, so one single-use $5 credit reimbursed the seller (and discounted the buyer) $5*N. Model it as a spend-once balance: ValidatedPromo::platform_credit_budget_cents() is the face value (fixed platform-wide only), and cap_line_to_credit_budget caps each line's credit to the remaining budget, reverting the uncovered discount to the buyer. Total credit <= face value, once, across the cart. - Postmark inbound From-header spoofing (HIGH): inbound issue/patch/reply identity was taken from the attacker-controlled From address. Add routes/postmark/auth_check.rs: classify_sender parses Authentication-Results/Received-SPF and requires SPF or aligned DKIM to pass for the From domain before attributing the message. Gated by POSTMARK_ENFORCE_SENDER_AUTH (default true / fail-closed; false = observe-only). Wired into all three identity sites. 13 new unit tests. cargo check --tests and clippy --lib clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-07-01 20:53 UTC
Commit: ce0a459717b33e7ee47430068a292df4348e7027
Parent: e0b8582
10 files changed, +438 insertions, -3 deletions
@@ -883,6 +883,7 @@ mod tests {
883 883 cdn_base_url: None,
884 884 user_pages_host: std::sync::Arc::from("u.localhost"),
885 885 postmark_inbound_webhook_token: None,
886 + postmark_enforce_sender_auth: true,
886 887 internal_shared_secret: None,
887 888 cli_service_token: None,
888 889 wam_url: None,
@@ -953,6 +954,7 @@ mod tests {
953 954 cdn_base_url: None,
954 955 user_pages_host: std::sync::Arc::from("u.localhost"),
955 956 postmark_inbound_webhook_token: None,
957 + postmark_enforce_sender_auth: true,
956 958 internal_shared_secret: None,
957 959 cli_service_token: None,
958 960 wam_url: None,
@@ -84,6 +84,13 @@ pub struct Config {
84 84 pub user_pages_host: Arc<str>,
85 85 /// Bearer token for authenticating Postmark inbound email webhook (optional).
86 86 pub postmark_inbound_webhook_token: Option<String>,
87 + /// Enforce SPF/DKIM alignment on inbound email before trusting the `From`
88 + /// address as an MNW user's identity (issues, patches, replies). Defaults to
89 + /// `true` (fail closed): a message whose `From` domain isn't SPF/DKIM-aligned
90 + /// is not attributed to the account that owns that address, closing the
91 + /// inbound sender-spoofing hole. Set `POSTMARK_ENFORCE_SENDER_AUTH=false`
92 + /// only to observe verdicts during rollout (logs but does not reject).
93 + pub postmark_enforce_sender_auth: bool,
87 94 /// Shared secret for HMAC-signed internal API requests to MT.
88 95 /// Must match `INTERNAL_SHARED_SECRET` on the MT instance.
89 96 pub internal_shared_secret: Option<String>,
@@ -356,6 +363,14 @@ impl Config {
356 363 // Postmark inbound email webhook token - optional, inbound endpoint returns 401 if unset
357 364 let postmark_inbound_webhook_token = std::env::var("POSTMARK_INBOUND_WEBHOOK_TOKEN").ok();
358 365
366 + // Enforce inbound SPF/DKIM sender-auth by default; only an explicit
367 + // `false` disables it (observe-only during rollout). Fail closed so a
368 + // missing/typo'd value can't silently reopen the spoofing hole.
369 + let postmark_enforce_sender_auth = std::env::var("POSTMARK_ENFORCE_SENDER_AUTH")
370 + .ok()
371 + .map(|v| !(v == "false" || v == "0"))
372 + .unwrap_or(true);
373 +
359 374 // Internal shared secret for MT communication. Bearer-token-equivalent, so
360 375 // enforce the same >=32-char floor as the signing secrets — a short value
361 376 // is offline-brute-forceable. Fail closed rather than boot on a weak secret.
@@ -421,6 +436,7 @@ impl Config {
421 436 cdn_base_url,
422 437 user_pages_host: Arc::from(user_pages_host),
423 438 postmark_inbound_webhook_token,
439 + postmark_enforce_sender_auth,
424 440 internal_shared_secret,
425 441 cli_service_token,
426 442 wam_url,
@@ -798,6 +814,7 @@ mod tests {
798 814 cdn_base_url: None,
799 815 user_pages_host: Arc::from("u.localhost"),
800 816 postmark_inbound_webhook_token: None,
817 + postmark_enforce_sender_auth: true,
801 818 internal_shared_secret: None,
802 819 cli_service_token: None,
803 820 wam_url: None,
@@ -707,6 +707,53 @@ impl ValidatedPromo {
707 707 pub fn id(&self) -> PromoCodeId {
708 708 self.code.id
709 709 }
710 +
711 + /// The maximum total platform credit MNW will transfer for a single
712 + /// redemption of this code — the credit's face value — or `None` when the
713 + /// code carries no spend-once balance.
714 + ///
715 + /// A platform-wide *fixed* credit (the $5 Fan+ renewal credit) is a monetary
716 + /// BALANCE, spent at most once across a multi-item cart, not a per-line
717 + /// coupon. `Some(budget)` lets the cart cap the cumulative discount+credit at
718 + /// that value so one single-use credit can't discount the buyer and reimburse
719 + /// the seller N times over a cart (ultra-fuzz Run 13 Payments SERIOUS).
720 + /// `None` means there is no balance to over-spend: a seller-funded code (its
721 + /// credit is always `0`) or a platform-wide *percentage* code (an intentional
722 + /// platform-funded sale that legitimately applies to every line).
723 + pub fn platform_credit_budget_cents(&self) -> Option<i64> {
724 + if !self.is_platform_wide {
725 + return None;
726 + }
727 + match (self.code.code_purpose, self.code.discount_type, self.code.discount_value) {
728 + (CodePurpose::Discount, Some(DiscountType::Fixed), Some(value)) => {
729 + Some(i64::from(value.max(0)))
730 + }
731 + _ => None,
732 + }
733 + }
734 + }
735 +
736 + /// Spend a platform credit BALANCE across one cart line, capping it to the
737 + /// remaining budget. Returns the line's `(final_price_cents, platform_credit_cents)`
738 + /// and decrements `*budget` by the credit actually granted; the uncovered part of
739 + /// the discount reverts to the buyer's bill so a single-use credit is spent at
740 + /// most once across the whole cart (ultra-fuzz Run 13 Payments SERIOUS).
741 + /// `*budget == None` disables the cap (per-line: seller-funded or percentage).
742 + pub fn cap_line_to_credit_budget(
743 + applied: AppliedDiscount,
744 + budget: &mut Option<i64>,
745 + ) -> (i32, i64) {
746 + let mut final_price = applied.price_cents;
747 + let mut credit = i64::from(applied.funding.platform_credit_cents());
748 + if let Some(remaining) = budget.as_mut() {
749 + let granted = credit.min(*remaining);
750 + // The buyer pays the discount the balance can no longer cover; price
751 + // rises back toward full, keeping `final_price == base - granted`.
752 + final_price += (credit - granted) as i32;
753 + credit = granted;
754 + *remaining -= granted;
755 + }
756 + (final_price, credit)
710 757 }
711 758
712 759 /// Look up and code-level-validate a checkout promo. Tries the seller's code
@@ -1167,4 +1214,96 @@ mod tests {
1167 1214 // handler's once-per-checkout concern.
1168 1215 assert_eq!(promo.code.use_count, 0);
1169 1216 }
1217 +
1218 + // -- Platform credit is a spend-once balance (ultra-fuzz Run 13 Payments) --
1219 +
1220 + /// Build a platform-wide fixed credit (the $5 Fan+ renewal credit shape).
1221 + fn platform_fixed_credit(cents: i32) -> ValidatedPromo {
1222 + ValidatedPromo {
1223 + code: DbPromoCode {
1224 + id: PromoCodeId::new(),
1225 + creator_id: UserId::new(),
1226 + code: "FANPLUS".to_string(),
1227 + code_purpose: CodePurpose::Discount,
1228 + discount_type: Some(DiscountType::Fixed),
1229 + discount_value: Some(cents),
1230 + min_price_cents: 0,
1231 + trial_days: None,
1232 + item_id: None,
1233 + project_id: None,
1234 + tier_id: None,
1235 + max_uses: None,
1236 + use_count: 0,
1237 + expires_at: None,
1238 + starts_at: None,
1239 + created_at: chrono::Utc::now(),
1240 + is_platform_wide: true,
1241 + },
1242 + is_platform_wide: true,
1243 + }
1244 + }
1245 +
1246 + #[test]
1247 + fn platform_fixed_credit_budget_is_face_value() {
1248 + assert_eq!(platform_fixed_credit(500).platform_credit_budget_cents(), Some(500));
1249 + }
1250 +
1251 + #[test]
1252 + fn seller_and_percentage_codes_have_no_credit_budget() {
1253 + // Seller-funded code: credit is always 0, no balance to cap.
1254 + assert_eq!(unscoped_discount_promo(None).platform_credit_budget_cents(), None);
1255 + // Platform-wide *percentage*: an intentional platform-funded sale that
1256 + // legitimately applies to every line — not a spend-once balance.
1257 + let mut pct = platform_fixed_credit(500);
1258 + pct.code.discount_type = Some(DiscountType::Percentage);
1259 + pct.code.discount_value = Some(20);
1260 + assert_eq!(pct.platform_credit_budget_cents(), None);
1261 + }
1262 +
1263 + #[test]
1264 + fn platform_fixed_credit_spent_once_across_cart() {
1265 + // The $5 (500¢) Fan+ credit across three $10 (1000¢) lines must discount
1266 + // the buyer and reimburse the seller a total of exactly 500¢ — once — not
1267 + // 500¢ per line (Run 13 SERIOUS: cart platform-credit multiplication).
1268 + let promo = platform_fixed_credit(500);
1269 + let mut budget = promo.platform_credit_budget_cents();
1270 + assert_eq!(budget, Some(500));
1271 +
1272 + let mut total_credit = 0i64;
1273 + let mut total_buyer_paid = 0i64;
1274 + for _ in 0..3 {
1275 + let PromoApplication::Apply(applied) =
1276 + apply_promo_to_item(&promo, ItemId::new(), ProjectId::new(), 1000).unwrap()
1277 + else {
1278 + panic!("expected Apply for an eligible platform-credit line");
1279 + };
1280 + let (final_price, credit) = cap_line_to_credit_budget(applied, &mut budget);
1281 + total_credit += credit;
1282 + total_buyer_paid += i64::from(final_price);
1283 + }
1284 + assert_eq!(total_credit, 500, "MNW reimburses the seller exactly the face value, once");
1285 + assert_eq!(total_buyer_paid, 3000 - 500, "buyer gets the $5 credit exactly once");
1286 + assert_eq!(budget, Some(0), "balance fully spent");
1287 + }
1288 +
1289 + #[test]
1290 + fn platform_fixed_credit_carries_balance_across_cheap_lines() {
1291 + // A $5 credit on two $1 (100¢) items spends 100 then 100 (the balance
1292 + // carries instead of burning the whole $5 on the first line); 300¢ remain.
1293 + let promo = platform_fixed_credit(500);
1294 + let mut budget = promo.platform_credit_budget_cents();
1295 + let mut total_credit = 0i64;
1296 + for _ in 0..2 {
1297 + let PromoApplication::Apply(applied) =
1298 + apply_promo_to_item(&promo, ItemId::new(), ProjectId::new(), 100).unwrap()
1299 + else {
1300 + panic!("expected Apply");
1301 + };
1302 + let (final_price, credit) = cap_line_to_credit_budget(applied, &mut budget);
1303 + assert_eq!(final_price, 0, "a $1 item is fully covered by the credit");
1304 + total_credit += credit;
1305 + }
1306 + assert_eq!(total_credit, 200);
1307 + assert_eq!(budget, Some(300), "unspent balance carries to the rest of the cart");
1308 + }
1170 1309 }
@@ -0,0 +1,232 @@
1 + //! Inbound sender authentication.
2 + //!
3 + //! Postmark's inbound webhook proves only that *Postmark relayed the message* —
4 + //! the bearer token says nothing about who actually wrote it. The `From` address
5 + //! is attacker-controlled: anyone can `git send-email` (or reply) with a forged
6 + //! `From:` matching a verified MNW user and, unguarded, act as that user (file
7 + //! issues, submit patches, post replies). See ultra-fuzz Run 13 Payments/Security
8 + //! HIGH — inbound sender spoofing.
9 + //!
10 + //! Postmark forwards the original message headers, into which the receiving MX
11 + //! stamps SPF/DKIM verdicts (`Authentication-Results`, `Received-SPF`). Before we
12 + //! attribute an inbound message to the account owning its `From` address, we
13 + //! require that SPF *or* DKIM **passed and is aligned** with the `From` domain.
14 + //! Everything here is a pure function of `(from, headers)` so it is unit-tested
15 + //! without a live mail path.
16 +
17 + use super::PostmarkHeader;
18 +
19 + /// Whether an inbound message's `From` address is authenticated.
20 + #[derive(Debug, Clone, Copy, PartialEq, Eq)]
21 + pub(super) enum SenderAuth {
22 + /// SPF or aligned DKIM passed for the `From` domain — trust the identity.
23 + Aligned,
24 + /// A verdict was present but neither SPF nor DKIM aligned/passed.
25 + Unaligned,
26 + /// No SPF/DKIM verdict was present in the forwarded headers.
27 + Missing,
28 + }
29 +
30 + /// Decide whether to trust the inbound `From` as sender identity.
31 + ///
32 + /// Returns `true` to proceed. When `enforce` is false (observe-only rollout) an
33 + /// unaligned/missing verdict is logged but allowed; when `enforce` is true it is
34 + /// logged and rejected.
35 + pub(super) fn inbound_sender_trusted(
36 + enforce: bool,
37 + from_email: &str,
38 + headers: &[PostmarkHeader],
39 + ) -> bool {
40 + let verdict = classify_sender(from_email, headers);
41 + match verdict {
42 + SenderAuth::Aligned => true,
43 + _ if !enforce => {
44 + tracing::warn!(
45 + from = %from_email, ?verdict,
46 + "inbound: sender not SPF/DKIM-aligned; allowing (POSTMARK_ENFORCE_SENDER_AUTH=false, observe-only)"
47 + );
48 + true
49 + }
50 + _ => {
51 + tracing::warn!(
52 + from = %from_email, ?verdict,
53 + "inbound: rejecting unauthenticated sender — SPF/DKIM not aligned with From domain (possible spoof)"
54 + );
55 + false
56 + }
57 + }
58 + }
59 +
60 + /// Classify the `From` address against the SPF/DKIM verdicts in `headers`.
61 + pub(super) fn classify_sender(from_email: &str, headers: &[PostmarkHeader]) -> SenderAuth {
62 + let Some(from_domain) = domain_of(from_email) else {
63 + return SenderAuth::Unaligned;
64 + };
65 +
66 + let mut saw_verdict = false;
67 +
68 + for h in headers {
69 + if h.name.eq_ignore_ascii_case("Authentication-Results") {
70 + let lower = h.value.to_ascii_lowercase();
71 + for chunk in lower.split(';') {
72 + let chunk = chunk.trim();
73 + if let Some(rest) = chunk.strip_prefix("spf=") {
74 + saw_verdict = true;
75 + if rest.starts_with("pass")
76 + && let Some(mf) = tag_value(chunk, "smtp.mailfrom=")
77 + && let Some(d) = domain_of(mf)
78 + && domains_aligned(from_domain, d)
79 + {
80 + return SenderAuth::Aligned;
81 + }
82 + } else if let Some(rest) = chunk.strip_prefix("dkim=") {
83 + saw_verdict = true;
84 + if rest.starts_with("pass")
85 + && let Some(d) = tag_value(chunk, "header.d=")
86 + && domains_aligned(from_domain, d)
87 + {
88 + return SenderAuth::Aligned;
89 + }
90 + }
91 + }
92 + } else if h.name.eq_ignore_ascii_case("Received-SPF") {
93 + saw_verdict = true;
94 + let lower = h.value.to_ascii_lowercase();
95 + let result = lower.split_whitespace().next().unwrap_or("");
96 + if result == "pass"
97 + && let Some(ef) = tag_value(&lower, "envelope-from=")
98 + && let Some(d) = domain_of(ef)
99 + && domains_aligned(from_domain, d)
100 + {
101 + return SenderAuth::Aligned;
102 + }
103 + }
104 + }
105 +
106 + if saw_verdict { SenderAuth::Unaligned } else { SenderAuth::Missing }
107 + }
108 +
109 + /// The lowercased domain of an email address (or bare domain), stripped of the
110 + /// angle brackets / quotes SPF/DKIM tags often wrap values in.
111 + fn domain_of(email: &str) -> Option<&str> {
112 + let d = match email.rsplit_once('@') {
113 + Some((_, d)) => d,
114 + None => email,
115 + };
116 + let d = d.trim().trim_matches(|c| matches!(c, '>' | '<' | '"' | ';' | ',' | '(' | ')'));
117 + if d.is_empty() || !d.contains('.') {
118 + None
119 + } else {
120 + Some(d)
121 + }
122 + }
123 +
124 + /// Relaxed domain alignment: `auth_domain` equals `from_domain` or is its
125 + /// organizational parent (so DKIM `d=example.com` covers `mail.example.com`).
126 + fn domains_aligned(from_domain: &str, auth_domain: &str) -> bool {
127 + let from = from_domain.trim_end_matches('.').to_ascii_lowercase();
128 + let auth = auth_domain.trim_end_matches('.').to_ascii_lowercase();
129 + !auth.is_empty() && (from == auth || from.ends_with(&format!(".{auth}")))
130 + }
131 +
132 + /// Pull the value of `key` (a `key=` tag) from `s`, up to the next whitespace or
133 + /// `;`. `s` is expected already-lowercased by the caller.
134 + fn tag_value<'a>(s: &'a str, key: &str) -> Option<&'a str> {
135 + let start = s.find(key)? + key.len();
136 + let rest = &s[start..];
137 + let end = rest.find(|c: char| c.is_whitespace() || c == ';').unwrap_or(rest.len());
138 + let val = rest[..end].trim();
139 + (!val.is_empty()).then_some(val)
140 + }
141 +
142 + #[cfg(test)]
143 + mod tests {
144 + use super::*;
145 +
146 + fn hdr(name: &str, value: &str) -> PostmarkHeader {
147 + PostmarkHeader { name: name.to_string(), value: value.to_string() }
148 + }
149 +
150 + #[test]
151 + fn spf_pass_aligned_is_authenticated() {
152 + let headers = vec![hdr(
153 + "Authentication-Results",
154 + "mx.postmark.com; spf=pass smtp.mailfrom=alice@example.com; dkim=none; dmarc=pass",
155 + )];
156 + assert_eq!(classify_sender("alice@example.com", &headers), SenderAuth::Aligned);
157 + }
158 +
159 + #[test]
160 + fn dkim_pass_aligned_is_authenticated() {
161 + let headers = vec![hdr(
162 + "Authentication-Results",
163 + "mx.postmark.com; spf=softfail; dkim=pass header.d=example.com header.i=@example.com",
164 + )];
165 + assert_eq!(classify_sender("alice@example.com", &headers), SenderAuth::Aligned);
166 + }
167 +
168 + #[test]
169 + fn dkim_on_org_domain_covers_subdomain_from() {
170 + let headers = vec![hdr(
171 + "Authentication-Results",
172 + "mx; dkim=pass header.d=example.com",
173 + )];
174 + assert_eq!(classify_sender("bob@mail.example.com", &headers), SenderAuth::Aligned);
175 + }
176 +
177 + #[test]
178 + fn received_spf_pass_is_authenticated() {
179 + let headers = vec![hdr(
180 + "Received-SPF",
181 + "Pass (postmark: domain of alice@example.com designates 1.2.3.4) envelope-from=alice@example.com; client-ip=1.2.3.4",
182 + )];
183 + assert_eq!(classify_sender("alice@example.com", &headers), SenderAuth::Aligned);
184 + }
185 +
186 + #[test]
187 + fn spoofed_from_with_attacker_spf_domain_is_unaligned() {
188 + // The attacker's own domain passes SPF, but `From` claims the victim's —
189 + // the exact spoof this guard closes.
190 + let headers = vec![hdr(
191 + "Authentication-Results",
192 + "mx.postmark.com; spf=pass smtp.mailfrom=mallory@evil.test; dkim=pass header.d=evil.test",
193 + )];
194 + assert_eq!(
195 + classify_sender("victim@example.com", &headers),
196 + SenderAuth::Unaligned
197 + );
198 + }
199 +
200 + #[test]
201 + fn spf_and_dkim_fail_is_unaligned() {
202 + let headers = vec![hdr(
203 + "Authentication-Results",
204 + "mx; spf=fail smtp.mailfrom=alice@example.com; dkim=fail header.d=example.com",
205 + )];
206 + assert_eq!(classify_sender("alice@example.com", &headers), SenderAuth::Unaligned);
207 + }
208 +
209 + #[test]
210 + fn no_auth_headers_is_missing() {
211 + let headers = vec![hdr("Subject", "hi"), hdr("In-Reply-To", "<x@y>")];
212 + assert_eq!(classify_sender("alice@example.com", &headers), SenderAuth::Missing);
213 + }
214 +
215 + #[test]
216 + fn enforce_rejects_unaligned_and_missing() {
217 + let unaligned = vec![hdr("Authentication-Results", "mx; spf=fail smtp.mailfrom=x@evil.test")];
218 + let missing: Vec<PostmarkHeader> = vec![];
219 + assert!(!inbound_sender_trusted(true, "alice@example.com", &unaligned));
220 + assert!(!inbound_sender_trusted(true, "alice@example.com", &missing));
221 + }
222 +
223 + #[test]
224 + fn observe_only_allows_but_still_trusts_aligned() {
225 + let aligned = vec![hdr("Authentication-Results", "mx; spf=pass smtp.mailfrom=alice@example.com")];
226 + let missing: Vec<PostmarkHeader> = vec![];
227 + // observe-only: everything allowed
228 + assert!(inbound_sender_trusted(false, "alice@example.com", &missing));
229 + // enforce: aligned still allowed
230 + assert!(inbound_sender_trusted(true, "alice@example.com", &aligned));
231 + }
232 + }
@@ -61,6 +61,15 @@ async fn handle_new_issue(
61 61 return HandlerOutcome::Terminal(StatusCode::OK);
62 62 }
63 63 };
64 + // Reject a spoofed `From` before attributing the issue to that account
65 + // (Run 13 sender-spoofing): require SPF/DKIM alignment with the From domain.
66 + if !super::inbound_sender_trusted(
67 + state.config.postmark_enforce_sender_auth,
68 + &payload.from_full.email,
69 + &payload.headers,
70 + ) {
71 + return HandlerOutcome::Terminal(StatusCode::OK);
72 + }
64 73 let sender = match db::users::get_user_by_email(&state.db, &sender_email).await {
65 74 Ok(Some(u)) if u.email_verified && !u.is_suspended() => u,
66 75 Ok(Some(u)) if !u.email_verified => {
@@ -246,6 +255,16 @@ async fn handle_issue_reply(
246 255 return HandlerOutcome::Terminal(StatusCode::OK);
247 256 }
248 257 };
258 + // The signed reply token names the expected user, but the comment is authored
259 + // as `sender` — so the `From` must itself be SPF/DKIM-authenticated, not just
260 + // match the token, before we trust it (Run 13 sender-spoofing).
261 + if !super::inbound_sender_trusted(
262 + state.config.postmark_enforce_sender_auth,
263 + &payload.from_full.email,
264 + &payload.headers,
265 + ) {
266 + return HandlerOutcome::Terminal(StatusCode::OK);
267 + }
249 268 let sender = match db::users::get_user_by_email(&state.db, &sender_email).await {
250 269 Ok(Some(u)) if u.email_verified && !u.is_suspended() => u,
251 270 Ok(Some(_)) => {
@@ -1,8 +1,11 @@
1 1 //! Postmark webhook endpoints for bounce/complaint events, inbound patches, and inbound issues.
2 2
3 + mod auth_check;
3 4 mod issues;
4 5 mod patches;
5 6
7 + use auth_check::inbound_sender_trusted;
8 +
6 9 use axum::{
7 10 extract::State,
8 11 http::{HeaderMap, StatusCode},
@@ -67,6 +67,16 @@ pub(super) async fn postmark_inbound(
67 67 return HandlerOutcome::Terminal(StatusCode::OK);
68 68 }
69 69 };
70 + // Do not trust the attacker-controlled `From` as identity unless SPF/DKIM
71 + // aligns with its domain (Run 13 sender-spoofing). Terminal OK: retrying
72 + // can't authenticate a spoof, and a non-committal 200 is no existence oracle.
73 + if !super::inbound_sender_trusted(
74 + state.config.postmark_enforce_sender_auth,
75 + &payload.from_full.email,
76 + &payload.headers,
77 + ) {
78 + return HandlerOutcome::Terminal(StatusCode::OK);
79 + }
70 80 let sender = match db::users::get_user_by_email(&state.db, &sender_email).await {
71 81 Ok(Some(u)) if u.email_verified => u,
72 82 Ok(Some(_)) => {
@@ -339,6 +339,16 @@ async fn checkout_seller_cart(
339 339 use db::promo_codes::PromoApplication;
340 340 // Apply to each eligible paid item; ineligible items (scope/min-price)
341 341 // are skipped so the rest of the cart can still qualify.
342 + //
343 + // A platform-wide credit (the $5 Fan+ renewal credit) is a monetary
344 + // BALANCE spent at most once across the whole cart, NOT a per-line
345 + // coupon: without a budget it would discount the buyer and reimburse the
346 + // seller once per eligible line — an N-times payout from a single-use
347 + // credit (ultra-fuzz Run 13 Payments SERIOUS). `credit_budget` is the
348 + // code's face value (`None` for seller-funded/percentage codes, which
349 + // have no balance to over-spend and stay per-line); each line's credit is
350 + // capped to the remainder and the uncovered discount reverts to the buyer.
351 + let mut credit_budget = validated.platform_credit_budget_cents();
342 352 for item in &paid_items {
343 353 if item.pwyw_enabled {
344 354 continue; // PWYW items can't take a promo (single-item behavior)
@@ -346,10 +356,11 @@ async fn checkout_seller_cart(
346 356 if let PromoApplication::Apply(applied) = db::promo_codes::apply_promo_to_item(
347 357 &validated, item.item_id, item.project_id, item.effective_price_cents(),
348 358 )? {
349 - discounted_prices.insert(item.item_id, applied.price_cents);
350 - let credit = applied.funding.platform_credit_cents();
359 + let (final_price, credit) =
360 + db::promo_codes::cap_line_to_credit_budget(applied, &mut credit_budget);
361 + discounted_prices.insert(item.item_id, final_price);
351 362 if credit > 0 {
352 - platform_credits.insert(item.item_id, credit as i64);
363 + platform_credits.insert(item.item_id, credit);
353 364 }
354 365 }
355 366 }
@@ -327,6 +327,7 @@ impl TestHarness {
327 327 cdn_base_url: opts.cdn_base_url.clone(),
328 328 user_pages_host: std::sync::Arc::from("u.localhost"),
329 329 postmark_inbound_webhook_token: opts.postmark_inbound_webhook_token,
330 + postmark_enforce_sender_auth: true,
330 331 internal_shared_secret: opts.internal_shared_secret.clone(),
331 332 cli_service_token: opts.cli_service_token.clone(),
332 333 wam_url: None,
@@ -78,6 +78,7 @@ pub async fn run(config: LoadConfig) {
78 78 cdn_base_url: None,
79 79 user_pages_host: std::sync::Arc::from("u.localhost"),
80 80 postmark_inbound_webhook_token: None,
81 + postmark_enforce_sender_auth: true,
81 82 internal_shared_secret: None,
82 83 cli_service_token: None,
83 84 wam_url: None,