Sanitize email user attribute before sending to checkout API#462
Sanitize email user attribute before sending to checkout API#462MathisDetourbet wants to merge 1 commit intosuperwall:developfrom
Conversation
The checkout API rejects `context.identity.email` unless it is a valid email address or null. Apps that set a placeholder like `"none"` when the user has no email silently break the Stripe checkout flow because the server returns a validation error and no checkout session is created. Introduce an `Email` domain primitive with a failable initializer that validates against the same regex the API enforces. When merging user attributes, the SDK now parses the `email` value through `Email` and drops it (sends null) when invalid, with a warning log. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
| let rawValue: String | ||
|
|
||
| private static let regex = try! NSRegularExpression( | ||
| pattern: #"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"# |
There was a problem hiding this comment.
$ anchor may match before a trailing newline
ICU regular expressions (used by NSRegularExpression) treat $ as matching at the end of the string or just before a final \n character, even without .anchorsMatchLines. This means "user@example.com\n" would pass validation and be stored with the trailing newline, potentially causing a server rejection. Use \z to strictly anchor at the end of the string in all cases.
| pattern: #"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"# | |
| pattern: #"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}\z"# |
Prompt To Fix With AI
This is a comment left during a code review.
Path: Sources/SuperwallKit/Identity/Email.swift
Line: 18
Comment:
**`$` anchor may match before a trailing newline**
ICU regular expressions (used by `NSRegularExpression`) treat `$` as matching at the end of the string *or* just before a final `\n` character, even without `.anchorsMatchLines`. This means `"user@example.com\n"` would pass validation and be stored with the trailing newline, potentially causing a server rejection. Use `\z` to strictly anchor at the end of the string in all cases.
```suggestion
pattern: #"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}\z"#
```
How can I resolve this? If you propose a fix, please make it concise.| private static let regex = try! NSRegularExpression( | ||
| pattern: #"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"# | ||
| ) |
There was a problem hiding this comment.
Consider adding a comment or explicit assertion for
try!
The pattern is a compile-time literal and will never throw, so the force-try is safe. Adding a short comment (or a preconditionFailure wrapper) makes that intent explicit and prevents future maintainers from worrying about it.
| private static let regex = try! NSRegularExpression( | |
| pattern: #"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"# | |
| ) | |
| // Pattern is a validated literal — initialization is guaranteed to succeed. | |
| private static let regex = try! NSRegularExpression( // swiftlint:disable:this force_try | |
| pattern: #"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"# | |
| ) |
Prompt To Fix With AI
This is a comment left during a code review.
Path: Sources/SuperwallKit/Identity/Email.swift
Line: 17-19
Comment:
**Consider adding a comment or explicit assertion for `try!`**
The pattern is a compile-time literal and will never throw, so the force-try is safe. Adding a short comment (or a `preconditionFailure` wrapper) makes that intent explicit and prevents future maintainers from worrying about it.
```suggestion
// Pattern is a validated literal — initialization is guaranteed to succeed.
private static let regex = try! NSRegularExpression( // swiftlint:disable:this force_try
pattern: #"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"#
)
```
How can I resolve this? If you propose a fix, please make it concise.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Summary
Emailtype with a failable initializer that validates against the regex enforced by the checkout API (^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$)emailuser attribute inmergeAttributes— invalid values are replaced withnilso the server receivesnullinstead of a malformed stringContext
When an app sets the
emailuser attribute to a placeholder like"none"(e.g. when the user hasn't provided an email yet), the checkout API rejects the request with anHttpApiDecodeErrorbecause"none"is neither a valid email nornull. This silently breaks the Stripe checkout — the payment sheet never opens and the user sees no feedback.The fix parses the email value at the SDK boundary so the server always receives either a valid email or
null, regardless of what the app sends.Test plan
Emailtype (valid addresses, parameterized invalid inputs including"none","","null","N/A")sanitizeAttribute(valid passthrough, invalid → nil, non-email keys untouched, non-string values untouched)emailattribute to"none"→ verify Stripe checkout opens (previously broken)emailattribute to a real email → verify it's forwarded correctly🤖 Generated with Claude Code
Greptile Summary
Introduces an
Emailvalue type with a regex-backed failable initialiser and wires it intomergeAttributesvia a newsanitizeAttributehelper, so the checkout API always receives either a well-formed email ornullinstead of placeholder strings like"none". The approach is clean and well-tested; the only notable edge case is that ICU's$anchor (used byNSRegularExpression) matches just before a trailing, which\zwould close.Confidence Score: 5/5
Safe to merge; all findings are minor style/edge-case suggestions with no blocking issues.
Both inline comments are P2: the
$vs\zanchor edge case is unlikely in practice (trailing newlines in email strings are rare), and thetry!concern is cosmetic. No logic bugs, data-loss risk, or missing test coverage for the primary fix path.Email.swift — the
$anchor andtry!points are both in the regex initialisation block.Important Files Changed
$anchor may accept trailing-newline emails —\zwould be safer.sanitizeAttributeto drop invalid email strings before they reach the checkout API; nil return correctly propagates removal throughIdentityLogic.mergeAttributes.sanitizeAttributepass-through; a trailing-newline case is absent but would reveal the$anchor issue.Prompt To Fix All With AI
Reviews (1): Last reviewed commit: "Sanitize email user attribute before sen..." | Re-trigger Greptile