Skip to content

Wire inbox api#29

Merged
Aoibheannmangan merged 5 commits into
mainfrom
wire-inbox-api
May 25, 2026
Merged

Wire inbox api#29
Aoibheannmangan merged 5 commits into
mainfrom
wire-inbox-api

Conversation

@sarahmc253
Copy link
Copy Markdown
Owner

@sarahmc253 sarahmc253 commented May 25, 2026

Summary by CodeRabbit

Release Notes

  • New Features

    • Users can now retrieve their received messages from the server.
    • Client-side decryption is automatically applied when encryption metadata is available.
    • Messages without decryption support display with a placeholder indicator.
  • Chores

    • Updated service configuration for production deployment.
    • Added development server utilities.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 25, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: cab99a10-4dde-4d1e-9d22-947e5be59bed

📥 Commits

Reviewing files that changed from the base of the PR and between 32a1400 and aa883a3.

📒 Files selected for processing (3)
  • client-web/js/views.js
  • dev_app.py
  • docs/transcript_sarah.md
💤 Files with no reviewable changes (1)
  • dev_app.py
✅ Files skipped from review due to trivial changes (1)
  • docs/transcript_sarah.md

📝 Walkthrough

Walkthrough

This PR implements end-to-end encrypted message retrieval and client-side decryption. The server adds a database query to return user-received messages; the client obtains and stores a private key during login, then decrypts each message before rendering in the inbox view with graceful fallback to plaintext placeholders.

Changes

Encrypted message flow: server retrieval, client key management, and inbox decryption

Layer / File(s) Summary
Flask server bootstrap
dev_app.py
Minimal Flask app with a root route handler returning "SAS server is running" and development server configuration.
Server-side message retrieval from database
server/app/messages/routes.py, docs/transcript_sarah.md
GET /messages endpoint fetches authenticated user's received messages via SELECT filtered by recipient_id, returns {'messages': rows}; documentation confirms Flask 3.x JSON-serializes MySQL timestamps without manual conversion.
Client-side private key lifecycle
client-web/js/api.js
Module maintains in-memory X25519 private key with setter/getter/clear functions; login parses and decrypts wrapped_private_key using password and stores the imported key; logout clears the in-memory key; decryption failures do not block login.
Client-side message decryption in inbox view
client-web/js/views.js, docs/transcript_sarah.md
Adds decryptMessage import and hexToBytes helper with strict hex-character validation; new tryDecrypt helper conditionally decrypts each message using the in-memory private key and ephemeral public key, falling back to '(encrypted)' placeholder on missing fields or error; renderInbox asynchronously decrypts all messages before rendering from the decrypted objects.
Systemd service deployment configuration
whatsas.service
Updates WorkingDirectory to server subdirectory and adjusts gunicorn ExecStart from server.run:app to run:app to match the new working directory.

Sequence Diagram(s)

sequenceDiagram
  participant LoginForm
  participant login as api.login()
  participant crypto as crypto.subtle
  participant storage as api._sessionPrivateKey
  LoginForm->>login: username, password
  login->>login: parse wrapped_private_key from response
  login->>crypto: decrypt using password
  alt decryption succeeds
    crypto-->>login: decrypted key material
    login->>crypto: importKey(X25519)
    crypto-->>login: CryptoKey
    login->>storage: setPrivateKey(CryptoKey)
  else decryption fails
    login->>login: log error, continue
  end
  login-->>LoginForm: login success
Loading
sequenceDiagram
  participant renderInbox
  participant privateKey as api.getPrivateKey()
  participant crypto as crypto.subtle
  participant decryptMsg as decryptMessage()
  participant messageList as rendered messages
  renderInbox->>renderInbox: map messages to decrypt tasks
  loop for each message
    renderInbox->>privateKey: obtain stored X25519 key
    alt required encryption fields present
      renderInbox->>crypto: importKey(message.ephemeral_pk)
      crypto-->>renderInbox: sender public key
      renderInbox->>decryptMsg: decrypt ciphertext/nonce/pubkey
      alt decryption succeeds
        decryptMsg-->>renderInbox: plaintext content
      else decryption fails
        renderInbox->>renderInbox: use '(encrypted)' placeholder
      end
    else fields missing
      renderInbox->>renderInbox: use '(encrypted)' placeholder
    end
  end
  renderInbox->>messageList: render message cards from decrypted objects
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • sarahmc253/WhatSaS#18: Client-side login flow in this PR uses EncryptedPrivateKey and crypto utilities introduced in that PR for private-key decryption.
  • sarahmc253/WhatSaS#23: That PR implements decryptMessage in the crypto module; this PR imports and uses it for client-side inbox message decryption.
  • sarahmc253/WhatSaS#26: Both PRs update the same whatsas.service systemd unit with overlapping changes to WorkingDirectory and gunicorn ExecStart configuration.

Suggested reviewers

  • sreejita-saha
  • Aoibheannmangan
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'Wire inbox api' accurately captures the main objective of the changeset, which implements end-to-end inbox messaging functionality across server, API layer, and client-side decryption.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch wire-inbox-api

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
client-web/js/api.js (1)

63-84: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Clear stale in-memory private key on every login attempt.

login() currently keeps the previous _sessionPrivateKey when the new response has no key (or unwrap fails), which can leak key state across account switches in the same tab.

Suggested fix
 export async function login(username, password) {
+    clearPrivateKey();
     const data = await request('POST', '/auth/login', {
         body: { username, password }
     });
@@
     if (data.wrapped_private_key) {
         try {
@@
             setPrivateKey(privKey);
         } catch {
+            clearPrivateKey();
             // Placeholder key material (test users) or corrupted key — login still succeeds,
             // inbox will see getPrivateKey() === null and skip decryption.
         }
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@client-web/js/api.js` around lines 63 - 84, The login flow currently can
retain a previous in-memory private key when the new response lacks
wrapped_private_key or unwrap fails; to fix, explicitly clear the session
private key at the start of login (or at least before attempting unwrap) by
calling setPrivateKey(null), and also ensure you call setPrivateKey(null) in the
branch where data.wrapped_private_key is missing and inside the catch block
around decryptPrivateKey/EncryptedPrivateKey.fromJSON so any previous
_sessionPrivateKey is removed on failed or keyless logins.
🧹 Nitpick comments (1)
whatsas.service (1)

1-19: Remember to reload systemd and restart the service.

After modifying the unit file, run the following commands to apply the changes:

sudo systemctl daemon-reload
sudo systemctl restart whatsas.service
sudo systemctl status whatsas.service
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@whatsas.service` around lines 1 - 19, You updated the systemd unit for the
WhatSaS Flask API (whatsas.service) but didn’t apply the change; reload systemd
and restart the service so the new ExecStart, EnvironmentFile, and other
settings take effect by running the systemd daemon-reload and then restart
whatsas.service, and verify it’s running with a status check (i.e., run the
equivalent of sudo systemctl daemon-reload, sudo systemctl restart
whatsas.service, and sudo systemctl status whatsas.service).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app.py`:
- Around line 1-2: The file app.py conflicts with the existing server/app
package and can shadow imports; rename this module (e.g., to dev_app.py) and
update any imports/usages accordingly: move the Flask app definition (the
variable named app and the Flask(__name__) instantiation) into the new module
name and change import sites (for example any "from app import ..." such as the
"from app import create_app" usage in server/run.py) to import from dev_app (or
the chosen new filename) so module resolution is deterministic and does not
collide with the server/app package.

In `@client-web/js/views.js`:
- Around line 32-37: hexToBytes silently accepts non-hex characters causing NaN
-> 0 coercion and misleading failures; validate the input before conversion by
checking the hex string matches /^[0-9a-fA-F]+$/ and has even length (or
validate each pair with /^[0-9a-fA-F]{2}$/) and throw a descriptive Error if
validation fails, and also guard the parseInt result inside hexToBytes (e.g., if
parseInt(...) is NaN) to throw if any byte cannot be parsed; update the function
hexToBytes to perform these checks before creating/assigning into the
Uint8Array.

In `@server/app/messages/routes.py`:
- Around line 31-34: The response for GET /messages is returning the DB column
ephemeral_pk but the inbox decrypt flow expects ephemeral_public_key; update the
messages route that builds the response (the SQL SELECT or response mapping in
routes.py) so that ephemeral_pk is emitted as ephemeral_public_key (e.g., alias
ephemeral_pk AS ephemeral_public_key or rename the dict key produced by the
function that handles the SELECT results) ensuring the field name matches what
the inbox decrypt flow (ephemeral_public_key) consumes.

---

Outside diff comments:
In `@client-web/js/api.js`:
- Around line 63-84: The login flow currently can retain a previous in-memory
private key when the new response lacks wrapped_private_key or unwrap fails; to
fix, explicitly clear the session private key at the start of login (or at least
before attempting unwrap) by calling setPrivateKey(null), and also ensure you
call setPrivateKey(null) in the branch where data.wrapped_private_key is missing
and inside the catch block around decryptPrivateKey/EncryptedPrivateKey.fromJSON
so any previous _sessionPrivateKey is removed on failed or keyless logins.

---

Nitpick comments:
In `@whatsas.service`:
- Around line 1-19: You updated the systemd unit for the WhatSaS Flask API
(whatsas.service) but didn’t apply the change; reload systemd and restart the
service so the new ExecStart, EnvironmentFile, and other settings take effect by
running the systemd daemon-reload and then restart whatsas.service, and verify
it’s running with a status check (i.e., run the equivalent of sudo systemctl
daemon-reload, sudo systemctl restart whatsas.service, and sudo systemctl status
whatsas.service).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 566af6bf-ec0f-4dc5-91d6-bededf7d3d15

📥 Commits

Reviewing files that changed from the base of the PR and between 47e3c7a and 32a1400.

📒 Files selected for processing (6)
  • app.py
  • client-web/js/api.js
  • client-web/js/views.js
  • docs/transcript_sarah.md
  • server/app/messages/routes.py
  • whatsas.service

Comment thread dev_app.py
Comment thread client-web/js/views.js
Comment thread server/app/messages/routes.py
@Aoibheannmangan Aoibheannmangan merged commit 14eafaf into main May 25, 2026
1 check passed
@Aoibheannmangan Aoibheannmangan deleted the wire-inbox-api branch May 25, 2026 13:35
This was referenced Jun 1, 2026
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