feat: upload integrity, file_message event, push mute, device presence#280
Open
0xratnendra wants to merge 2 commits into
Open
Conversation
- Verify ciphertext SHA256 on send_message; reject mismatches (AEAD tag remains primary integrity; server hash is transport check) - Emit file_message WS event for file/image/video/audio content; file bytes flow over HTTP via GET /files/:id, not the socket - Respect mute settings before push dispatch: check conversation_members.isMuted and per-device pushEnabled toggle - Derive user presence from user_devices.lastSeenAt; online if any non-revoked device has recent activity; expose lastSeen via GET /users/:id/presence and presence_update WS event
|
@0xratnendra Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
Owner
|
GM @0xratnendra |
Owner
|
GM @0xratnendra |
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.
closes #217
closes #232
closes #233
closes #238
Summary
Implements four features:
file_messageevent — signals file availability without sending bytes over the socketlastSeen1. Ciphertext SHA256 Verification
What
When the client sends a
send_messagepayload, it may optionally includeciphertextSha256. The server computes SHA-256 over the storedciphertextand rejects the message on mismatch with a clearintegrity_error.Why
This is a transport-corruption check. The primary integrity mechanism remains the AEAD tag inside the ciphertext (verified client-side at decryption time). The server-side hash catches corruption that may occur between the client and database (e.g., faulty proxies, memory errors).
Changes
apps/backend/src/socket/messaging.ts— addedciphertextSha256to the payload type; computes and compares hash before inserting the message; rejects withintegrity_erroron mismatchAcceptance Criteria
2. WebSocket
file_messageEventWhat
When a message with a file-type
contentType(file/*,image/*,video/*,audio/*, orfile) is sent, the server emits afile_messageevent to the conversation room carrying{ messageId, conversationId, fileId }.Why
The WebSocket carries only metadata/availability signals, never file bytes. Recipients use the
file_messageevent to know when to fetch and decrypt a file viaGET /files/:idover HTTP. Delivery and receipts reuse the standard pipeline.Changes
apps/backend/src/socket/messaging.ts— afternew_messagebroadcast, emitsfile_messagefor file-type contentAcceptance Criteria
GET /files/:idafter the event3. Push Notification Mute Respect
What
Before dispatching a push notification for a new message:
conversation_members.isMuted— skips muted conversationsuser_devices.push_enabled— skips devices with push disabledWhy
Muted conversations should not generate push notifications. Each device can also have a global push opt-out. These checks are applied before dispatch to avoid unnecessary wake-ups.
Changes
apps/backend/src/db/schema.ts— addedpushEnabledcolumn touser_devices(defaulttrue)apps/backend/drizzle/0009_push_enabled.sql— migrationapps/backend/drizzle/meta/_journal.json— migration entryapps/backend/src/services/push.ts— new push notification service usingweb-pushwith VAPIDapps/backend/src/socket/messaging.ts— callssendPushForMessageafter message broadcastapps/backend/package.json— addedweb-pushand@types/web-pushdependenciesConfiguration
Set these environment variables to enable push:
Acceptance Criteria
4. Device-Based User Presence
What
User presence is now derived from device activity rather than relying solely on Redis WebSocket tracking:
lastSeenAtwithin a 90-second windowlastSeenreflects the most recentlastSeenAtacross all devicesExposed via:
GET /users/:id/presence— returns{ online: boolean, lastSeen?: string }presence_updateevent — includeslastSeenwhen the user goes offlineHow
user_devices.lastSeenAtis updated on connect, heartbeat, and disconnectderiveDevicePresence()inservices/presence.tschecks if any non-revoked device was active within 90sChanges
apps/backend/src/services/presence.ts— addedderiveDevicePresence()apps/backend/src/services/heartbeat.ts— updatesuser_devices.lastSeenAton heartbeatapps/backend/src/index.ts— updateslastSeenAton connect/disconnect; includeslastSeeninpresence_updateeventsapps/backend/src/routes/users.ts— presence endpoint uses Redis then falls back to device-based presenceapps/backend/src/middleware/socketAuth.ts— exposesidentityPublicKeyon the socket for device lookupsAcceptance Criteria
lastSeenreflects the most recent device activity when offlineTest Results
tsc --noEmit)