Skip to content

Conversation

@coodos
Copy link
Contributor

@coodos coodos commented Nov 21, 2025

Description of change

force signing modal to display on eReputation.

Issue Number

Type of change

  • Fix (a change which fixes an issue)

How the change has been tested

Change checklist

  • I have ensured that the CI Checks pass locally
  • I have removed any unnecessary logic
  • My code is well documented
  • I have signed my commits
  • My code follows the pattern of the application
  • I have self reviewed my code

Summary by CodeRabbit

  • New Features
    • Added a real-time signing workflow for references with QR code and mobile deep link support.
    • Implemented live status tracking during the signing process with status indicators (pending, signed, expired, security violation).
    • Added a 15-minute countdown timer for signing session validity.
    • Included a retry option to reset the signing state.

✏️ Tip: You can customize this high-level summary in your review settings.

@coodos coodos marked this pull request as ready for review November 21, 2025 14:47
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 21, 2025

Walkthrough

This PR introduces a database migration to create a reference_signatures table with signature verification fields, and implements a real-time signing flow in the reference modal component using Server-Sent Events (SSE), featuring a 15-minute countdown timer, QR code display, and mobile deep link support.

Changes

Cohort / File(s) Summary
Database Migration
platforms/eReputation-api/src/database/migrations/1763710576026-migration.ts
Introduces TypeORM migration that creates reference_signatures table with columns (id, referenceId, userId, referenceHash, signature, publicKey, message, createdAt, updatedAt) and establishes foreign key constraints to references and users tables
Reference Modal Signing Flow
platforms/eReputation/client/src/components/modals/reference-modal.tsx
Adds real-time signing flow with SSE-based status monitoring, countdown timer (15-minute validity), device detection for QR code vs. mobile deep-link rendering, signing state management (pending/signed/expired/security_violation), lifecycle cleanup, and adapted submit flow

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Modal as Reference Modal
    participant Backend as Backend API
    participant SSE as SSE Endpoint

    User->>Modal: Submit Reference
    Modal->>Backend: POST /api/references
    Backend-->>Modal: Success + signingSession
    
    rect rgb(200, 220, 255)
        Note over Modal,SSE: Signing Flow Initiated
        Modal->>SSE: EventSource (signingSession)
        Modal->>Modal: Start 15-min Countdown
        Modal->>User: Show QR Code / Deep Link
    end

    User->>User: Sign on Device
    Backend->>SSE: Emit 'signed' status
    SSE-->>Modal: Status update
    
    rect rgb(200, 255, 220)
        Note over Modal: Signing Complete
        Modal->>Modal: Invalidate Queries
        Modal->>User: Show Success
        Modal->>Modal: Cleanup SSE + Reset
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Database migration: Straightforward TypeORM migration; verify foreign key relationships and column types
  • Modal component: Multiple interacting features (SSE connection management, countdown timer logic, device detection, state transitions) require careful review of edge cases, cleanup logic, and error handling paths
  • Extra attention areas:
    • SSE connection lifecycle and cleanup on component unmount/modal close
    • Countdown timer accuracy and expiry behavior
    • Device detection logic and deep-link URL generation
    • State transitions between signing statuses and error recovery

Possibly related PRs

  • chore: force signing modal #456: Adds similar SSE-based signing flow with countdown timer, QR code rendering, and deep-link support to the same reference modal component

Suggested reviewers

  • sosweetham
  • ananyayaya129

Poem

🐰✨ A table takes shape with signatures stored,
Real-time QR codes now beautifully poured,
Countdown clocks tick while mobile devices align,
SSE whispers secrets in messages so fine,
Signing now flows like fresh morning dew!

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings, 1 inconclusive)
Check name Status Explanation Resolution
Description check ⚠️ Warning Description is largely incomplete. It lacks critical details: no issue number provided, testing approach not documented, and all checklist items are unchecked. Complete the description by adding an issue number, documenting testing steps, and checking relevant checklist items to confirm code quality standards were met.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive Title is vague and uses generic phrasing. 'Fix/ereputation signing modal' doesn't clearly convey what was actually fixed or changed in the signing modal. Replace with a more specific title like 'Add real-time signing flow to eReputation signing modal' or 'Implement SSE-based signing status updates for reference signatures'.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/ereputation-signing-modal

Tip

📝 Customizable high-level summaries are now available in beta!

You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.

  • Provide your own instructions using the high_level_summary_instructions setting.
  • Format the summary however you like (bullet lists, tables, multi-section layouts, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example instruction:

"Divide the high-level summary into five sections:

  1. 📝 Description — Summarize the main change in 50–60 words, explaining what was done.
  2. 📓 References — List relevant issues, discussions, documentation, or related PRs.
  3. 📦 Dependencies & Requirements — Mention any new/updated dependencies, environment variable changes, or configuration updates.
  4. 📊 Contributor Summary — Include a Markdown table showing contributions:
    | Contributor | Lines Added | Lines Removed | Files Changed |
  5. ✔️ Additional Notes — Add any extra reviewer context.
    Keep each section concise (under 200 words) and use bullet or numbered lists for clarity."

Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
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: 2

🧹 Nitpick comments (4)
platforms/eReputation-api/src/database/migrations/1763710576026-migration.ts (1)

7-7: Consider adding indexes on foreign key columns.

The referenceId and userId columns will likely be queried frequently (e.g., fetching all signatures for a reference, or all signatures by a user), but no indexes are defined. Without indexes, these queries will require full table scans as the table grows.

Apply this diff to add indexes:

     public async up(queryRunner: QueryRunner): Promise<void> {
         await queryRunner.query(`CREATE TABLE "reference_signatures" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "referenceId" uuid NOT NULL, "userId" uuid NOT NULL, "referenceHash" text NOT NULL, "signature" text NOT NULL, "publicKey" text NOT NULL, "message" text NOT NULL, "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), CONSTRAINT "PK_e13239bee10fe5f7a990d5fbee4" PRIMARY KEY ("id"))`);
+        await queryRunner.query(`CREATE INDEX "IDX_reference_signatures_referenceId" ON "reference_signatures" ("referenceId")`);
+        await queryRunner.query(`CREATE INDEX "IDX_reference_signatures_userId" ON "reference_signatures" ("userId")`);
         await queryRunner.query(`ALTER TABLE "reference_signatures" ADD CONSTRAINT "FK_63d30e071b1bde272650ddd4c50" FOREIGN KEY ("referenceId") REFERENCES "references"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
         await queryRunner.query(`ALTER TABLE "reference_signatures" ADD CONSTRAINT "FK_273a89a0507f9304a2aa1839b9d" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
     }

And update the down method:

     public async down(queryRunner: QueryRunner): Promise<void> {
         await queryRunner.query(`ALTER TABLE "reference_signatures" DROP CONSTRAINT "FK_273a89a0507f9304a2aa1839b9d"`);
         await queryRunner.query(`ALTER TABLE "reference_signatures" DROP CONSTRAINT "FK_63d30e071b1bde272650ddd4c50"`);
+        await queryRunner.query(`DROP INDEX "IDX_reference_signatures_userId"`);
+        await queryRunner.query(`DROP INDEX "IDX_reference_signatures_referenceId"`);
         await queryRunner.query(`DROP TABLE "reference_signatures"`);
     }
platforms/eReputation/client/src/components/modals/reference-modal.tsx (3)

67-71: Signing state, timer, and cleanup are sound; consider simplifying the countdown logic

The signing state model and cleanup paths look correct: eventSource is consistently closed on modal close, unmount, timer expiry, reset, and “Try Again”, which should prevent leaks.

Two small refinements you might consider:

  • Reduce timer churn (Lines 211-229): because timeRemaining is in the dependency array, the effect tears down and re‑creates the interval every second. This is safe but a bit noisy. You could instead drive the effect off signingStatus, signingSession, and maybe eventSource, and rely on the functional setTimeRemaining updater, e.g.:

  • useEffect(() => {

  • if (signingStatus === "pending" && timeRemaining > 0 && signingSession) {

  • useEffect(() => {

  • if (signingStatus === "pending" && signingSession) {
    const timer = setInterval(() => {
    setTimeRemaining(prev => {
    if (prev <= 1) {
    setSigningStatus("expired");
    if (eventSource) {
    eventSource.close();
    }
    return 0;
    }
    return prev - 1;
    });
    }, 1000);

    return () => clearInterval(timer);
    }

  • }, [signingStatus, timeRemaining, signingSession, eventSource]);
  • }, [signingStatus, signingSession, eventSource]);

- **Handle already‑expired sessions more explicitly** (Lines 109-113, 211-221): if `expiresAt` is in the past when the response arrives, `secondsRemaining` becomes `<= 0`, you set `timeRemaining(0)` but leave `signingStatus` as `"pending"`. You might want to treat that as an immediate expiry instead:

```diff
const secondsRemaining = Math.floor((expiresAt.getTime() - now.getTime()) / 1000);
- setTimeRemaining(Math.max(0, secondsRemaining));
- startSSEConnection(data.signingSession.sessionId);
+ const clamped = Math.max(0, secondsRemaining);
+ setTimeRemaining(clamped);
+ if (clamped > 0) {
+   startSSEConnection(data.signingSession.sessionId);
+ } else {
+   setSigningStatus("expired");
+ }

These are refinements only; current behavior is functionally acceptable.

Also applies to: 211-252, 259-271


142-209: Close the SSE connection and surface feedback on onerror

The happy-path and terminal-status handling in startSSEConnection are solid, but the onerror branch leaves the EventSource open and silent from the user’s perspective:

newEventSource.onerror = (error) => {
  console.error("SSE connection error:", error);
  setSigningStatus("error");
};

Two small tweaks will make this more robust:

  • Close the connection on error to avoid repeated reconnect attempts or orphaned sockets:

    newEventSource.onerror = (error) => {
      console.error("SSE connection error:", error);
  • setSigningStatus("error");

  • setSigningStatus("error");
  • newEventSource.close();
    };

- **Optionally show a toast** so the user understands why the signing stalled:

```diff
newEventSource.onerror = (error) => {
  console.error("SSE connection error:", error);
  setSigningStatus("error");
+   toast({
+     title: "Signing Connection Error",
+     description: "We lost connection to the signing service. Please try again.",
+     variant: "destructive",
+   });
+   newEventSource.close();
};

This aligns the error path with the “expired” and “security_violation” cases and keeps the EventSource lifecycle clean.


354-445: Clarify “Try Again” semantics to avoid silent duplicate references and improve error UX

The signing UI is well structured, and the conditional footers behave consistently with signingSession presence. One subtle behavioral point:

  • When a signing attempt expires, hits a security violation, or hits error, “Try Again” (Lines 429-439) clears signingSession and closes the SSE. The user returns to the pre-filled form and can click “Sign & Submit eReference” again.
  • That second click will re-run handleSubmit, which (as currently written) posts to /api/references again and will likely create a new reference, leaving the first, unsigned reference around unless the backend handles it.

If that’s intentional (each attempt is a new reference), it may be worth making it explicit in copy or docs. If not, you may want to:

  • Introduce a separate “recreate signing session” endpoint keyed by the existing reference id, and have “Try Again” call that instead of going back to handleSubmit, or
  • Track the created reference id in state and reuse it, only regenerating the signing session.

Additionally, for signingStatus === "error" you currently show only the “Try Again” button with no message. Consider adding a small inline message similar to the “expired” and “security_violation” blocks—for example, “We couldn’t connect to the signing service. Please try again.”

Overall the flow is coherent; these tweaks would just reduce confusion and possible dangling references.

Also applies to: 429-444, 581-613, 615-632

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 774e2ce and fdbcba3.

📒 Files selected for processing (2)
  • platforms/eReputation-api/src/database/migrations/1763710576026-migration.ts (1 hunks)
  • platforms/eReputation/client/src/components/modals/reference-modal.tsx (6 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
platforms/eReputation-api/src/database/migrations/1763710576026-migration.ts (2)
platforms/eReputation-api/src/database/migrations/1761324658339-migration.ts (1)
  • Migration1761324658339 (3-20)
platforms/group-charter-manager-api/src/database/migrations/1755598750354-migration.ts (1)
  • Migration1755598750354 (3-18)
platforms/eReputation/client/src/components/modals/reference-modal.tsx (1)
platforms/eReputation/client/src/lib/queryClient.ts (1)
  • queryClient (44-57)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (2)
platforms/eReputation-api/src/database/migrations/1763710576026-migration.ts (2)

12-16: Rollback logic is correct.

The down method properly drops foreign key constraints before dropping the table, ensuring a clean rollback.

Note: If you add the recommended indexes, remember to also drop them in this method (as shown in the previous comment).


8-9: I need to examine the complete ReferenceSignature entity definition and check for any duplicate prevention logic. Let me gather more information:

Let me examine the complete ReferenceSignature entity and related code:

Based on the script output and web research, I now have sufficient information to provide a comprehensive rewritten review comment. The evidence confirms both concerns are valid:

  1. ON DELETE NO ACTION pattern: The codebase consistently uses CASCADE for child entities, making NO ACTION an outlier
  2. Missing unique constraint: No unique constraint found on the composite key, and no application-level duplicate checks in the service

Verify deletion behavior and add missing unique constraint on signatures.

Two data integrity issues in the foreign key definitions:

  1. ON DELETE NO ACTION inconsistency: When an author is deleted, the book is also deleted with ON DELETE CASCADE. The migration uses ON DELETE NO ACTION, which will reject the operation if related records exist. The codebase pattern shows CASCADE is used throughout (wishlists, votes, followers, group members). Confirm: should signatures be deleted with CASCADE when a reference/user is deleted, or is orphaning intentional for audit purposes?

  2. No unique constraint on (referenceId, userId): The table allows duplicate signatures for the same user-reference pair. TypeORM supports multi-column unique constraints with @unique(["referenceId", "userId"]) in the entity, but the migration contains no such constraint. Add either a database constraint or document if application-level validation prevents duplicates.

@ananyayaya129 ananyayaya129 merged commit f8b9b1f into main Nov 21, 2025
4 checks passed
@coodos coodos deleted the fix/ereputation-signing-modal branch November 21, 2025 14:57
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.

4 participants