Skip to content

Sanitize email user attribute before sending to checkout API#462

Merged
yusuftor merged 4 commits intosuperwall:developfrom
MathisDetourbet:fix/sanitize-email-user-attribute
Apr 28, 2026
Merged

Sanitize email user attribute before sending to checkout API#462
yusuftor merged 4 commits intosuperwall:developfrom
MathisDetourbet:fix/sanitize-email-user-attribute

Conversation

@MathisDetourbet
Copy link
Copy Markdown
Contributor

@MathisDetourbet MathisDetourbet commented Apr 16, 2026

Summary

  • Introduce an Email type 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,}$)
  • Sanitize the email user attribute in mergeAttributes — invalid values are replaced with nil so the server receives null instead of a malformed string
  • Log a warning when an invalid email is dropped so app developers get feedback

Context

When an app sets the email user attribute to a placeholder like "none" (e.g. when the user hasn't provided an email yet), the checkout API rejects the request with an HttpApiDecodeError because "none" is neither a valid email nor null. 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

  • Unit tests on Email type (valid addresses, parameterized invalid inputs including "none", "", "null", "N/A")
  • Unit tests on sanitizeAttribute (valid passthrough, invalid → nil, non-email keys untouched, non-string values untouched)
  • Manual: set email attribute to "none" → verify Stripe checkout opens (previously broken)
  • Manual: set email attribute to a real email → verify it's forwarded correctly

🤖 Generated with Claude Code

Greptile Summary

This PR introduces an Email value type that validates against the checkout API's regex, and applies it in mergeAttributes so that invalid email strings (e.g. "none") are silently dropped instead of forwarded to the server. The change is focused and well-tested with a parameterized test suite covering both the type and the sanitizer.

Confidence Score: 5/5

Safe to merge — change is well-scoped, correctly integrated, and fully covered by unit tests.

No P0 or P1 issues found. The Email type is correctly anchored with \z, the sanitizer integrates cleanly with existing nil-drop semantics in mergeAttributes, and the test suite covers the key invalid placeholders and edge cases.

No files require special attention.

Important Files Changed

Filename Overview
Sources/SuperwallKit/Identity/Email.swift New value type with regex-backed failable initializer; correctly uses \z anchor and force-try with lint suppression comment.
Sources/SuperwallKit/Identity/UserAttributes.swift Adds sanitizeAttribute static helper that validates email values; integrates cleanly into existing mergeAttributes flow with correct nil-drop semantics.
Tests/SuperwallKitTests/Identity/EmailTests.swift Comprehensive parameterized tests covering valid addresses, known invalid placeholders (none, null, N/A, trailing newline), and the sanitizer helper.
CHANGELOG.md Adds fix entry under the upcoming release section.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["setUserAttributes(['email': value])"] --> B[mergeAttributes]
    B --> C{value is nil?}
    C -- yes --> D[skip key]
    C -- no --> E{isValidJSONObject?}
    E -- no --> D
    E -- yes --> F["sanitizeAttribute(key:value:)"]
    F --> G{key == 'email'?}
    G -- no --> H[return value unchanged]
    G -- yes --> I{value is String?}
    I -- no --> H
    I -- yes --> J{Email init succeeds?}
    J -- yes --> K[return email.rawValue]
    J -- no --> L[log warning\nreturn nil]
    L --> M[key removed from customAttributes]
    K --> N[customAttributes updated]
    H --> N
    N --> O[identityManager.mergeUserAttributes]
    M --> O
    O --> P[IdentityLogic.mergeAttributes\nmerges with stored attrs]
    P --> Q[Server receives valid email or null]
Loading

Reviews (3): Last reviewed commit: "Add CHANGELOG entry for email user attri..." | Re-trigger Greptile

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>
Comment thread Sources/SuperwallKit/Identity/Email.swift Outdated
Comment thread Sources/SuperwallKit/Identity/Email.swift Outdated
yusuftor and others added 3 commits April 28, 2026 12:04
ICU treats $ as matching before a final \n, so user@example.com\n was
slipping through. Switch to \z and add a regression case.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@yusuftor yusuftor merged commit 98bfde3 into superwall:develop Apr 28, 2026
1 check passed
@yusuftor
Copy link
Copy Markdown
Collaborator

Thanks for submitting!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants