feat: attachments, webhooks, annual contrast fix, inbox auth probe#5
Merged
Conversation
text-ghost (#2a3a4e) was nearly invisible against the dark slab background; switch annual price line and plan picker labels to text-muted/text-white-70.
text-white/70 has no theme-clean override, rendering as 70% white on the light background (invisible). Switch to /80 which has #3a352f override.
Processor now walks the MIME tree and stores attachment metadata (filename,
content type, size, sha256, index) alongside the message. The bytes stay in
the raw .eml that SES already drops in S3 with a 1-day lifecycle, so no
duplicate storage.
Adds GET /inbox/{addr}/messages/{id}/attachments/{idx} (and the admin twin)
which re-parses the .eml on demand, returns the indexed attachment with
Content-Disposition: attachment + Content-Type: application/octet-stream +
X-Content-Type-Options: nosniff so browsers always download (never render
inline).
Hard limits applied at parse time: 10MB per attachment, 25MB total per
message, plus an extension blocklist (exe/bat/scr/msi/dll/js/vbs/...).
API Lambda now base64-encodes binary responses for API Gateway v2; Terraform
gains the new routes.
Frontend lists attachments under the message body with per-file size and a
download button that calls the new endpoint.
Premium users can register an https URL via PUT /user/webhook. Each delivery is signed with HMAC-SHA256 over the JSON body using a per-user secret generated server-side; receivers verify with the X-Ephemask-Signature header. Processor fires the webhook synchronously after StoreMessage with a 5s timeout, no retries (best-effort) — the inbox always has the message regardless of delivery success. Payload carries metadata only (inbox_address, message_id, from, subject, received_at, attachment_count); the email body is never included so consumers fetch via the API. Adds GET/PUT/DELETE /user/webhook endpoints (premium-gated), webhook UI in the account page (URL input, secret reveal with copy, remove button), and docs section explaining the headers and signature verification. CORS allow-methods now includes PUT.
The /inbox/{address} page would render the layout for any address typed
into the URL. The poll fetch silently failed because no inbox token was
stored, but the UI still showed the address as if it belonged to the
visitor — confusing at best, dangerous if someone shared the link.
Now: when there is no saved token for the address, fall back to the
logged-in user's API key. The backend authenticateInbox accepts the user
api key when the inbox belongs to that user, so premium users navigating
to their own inboxes from the account page still work. With neither
token nor matching user key, redirect to home where the visitor can
create their own inbox.
The previous fix fell back to user.apiKey without verifying it actually authorized for this inbox. Anyone with any saved api key would see the inbox UI render while the polls silently 401'd in the background. Now: do a single getInbox call with the candidate token (saved inbox token or user api key) before showing the UI. On 401 / any error, redirect home; on success, seed messages from the probe response so the first poll already has data.
echo -n adds CR/LF on Git Bash and several other Windows shells, which silently breaks the signature comparison.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
GET /inbox/{addr}/messages/{id}/attachments/{idx}(and admin twin) re-parses the raw.emlalready in S3 and streams the indexed file withContent-Disposition: attachment+nosniffso browsers always download. Hard limits at parse time (10 MB per file, 25 MB per message) and an extension blocklist (exe/bat/scr/msi/dll/js/vbs/...). Lambda now base64-encodes binary responses for API Gateway v2.PUT/GET/DELETE /user/webhookwith HMAC-SHA256 signing. Processor fires synchronously afterStoreMessagewith a 5 s timeout, no retry; metadata-only payload (no email body) so consumers fetch via the API. Account page exposes URL input + secret reveal;/docsdocuments the headers, payload, and verification recipe (printf '%s'+openssl dgst, sinceecho -nadds CR/LF on Git Bash).text-ghostwas nearly invisible on the dark slab andtext-white/70had no light-theme override; switched totext-mutedandtext-white/80./inbox/{addr}without a saved token would render the shell as if the visitor owned the inbox while polls silently 401'd. Now the page probes the API once before rendering and falls back to the user's API key for premium users navigating from their account; backendauthenticateInboxaccepts the user api key wheninbox.UserID == user.UserID.Test plan
pnpm buildsucceeds andgo test ./...passes.exe→ silently dropped from the metadataX-Ephemask-Signature: sha256=...; recompute HMAC locally and confirm match/inbox/random@ephemask.comas anon or as a different user → redirects to home (no shell flash)/account → Active Inboxes→ loads even without sessionStorage token (api-key fallback)\$4/moand\$40/yr · Save 17%with readable contrast in both dark and light themes