Phase 2 backend: Google Wallet pass + profile contact form

- cardclaws-wallet::google — inline GenericObject + RS256 `savetowallet` JWT
  (Rs256Signer via jsonwebtoken; FakeGoogleSigner test double)
- POST /v1/cards/{id}/wallet/google → { saveUrl }
- POST /v1/profile/{handle}/contact — validate, rate-limit, record
  contact_form_submission, email the owner (HTML-escaped, Resend)
- WalletConfig gains google issuer id + service account email

84 backend tests; fmt + clippy clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
Omar Sobh
2026-06-04 12:32:21 -05:00
co-authored by Claude Opus 4.8
parent 4770efd861
commit 8ae1d8f263
20 changed files with 545 additions and 1 deletions
@@ -28,6 +28,7 @@ use cardclaws_auth::JwtKeys;
use cardclaws_config::WalletConfig;
use cardclaws_wallet::apple::signer::FakePassSigner;
use cardclaws_wallet::apple::BrandAssets;
use cardclaws_wallet::google::jwt_signer::FakeGoogleSigner;
use cardclaws_wallet::strip_renderer;
/// Apple provider that never returns a usable key — fine for tests that don't
@@ -92,8 +93,11 @@ pub async fn try_setup() -> Option<TestApp> {
apple_pass_type_id: "pass.com.cardclaws.test".into(),
apple_team_id: "TEST123".into(),
organization_name: "CardClaws".into(),
google_issuer_id: "3388000000000000000".into(),
google_service_account_email: "[email protected]".into(),
},
pass_signer: Arc::new(FakePassSigner),
google_signer: Arc::new(FakeGoogleSigner),
brand: Arc::new(BrandAssets {
icon_png: strip_renderer::render_solid(58, 58, "#ff3b30").unwrap(),
logo_png: strip_renderer::render_solid(160, 50, "#ffffff").unwrap(),