Skip to content

fix: isolate delivery OTP into dedicated delivery_otps table with RLS(#597)#618

Open
ionfwsrijan wants to merge 3 commits into
KanishJebaMathewM:mainfrom
ionfwsrijan:fix/issue-597-delivery-otp-isolation
Open

fix: isolate delivery OTP into dedicated delivery_otps table with RLS(#597)#618
ionfwsrijan wants to merge 3 commits into
KanishJebaMathewM:mainfrom
ionfwsrijan:fix/issue-597-delivery-otp-isolation

Conversation

@ionfwsrijan

@ionfwsrijan ionfwsrijan commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Problem

Delivery OTPs were stored as columns on the orders table (delivery_otp, otp_verified, otp_generated_at). Any SELECT on orders — through broad RLS policies, a service-role bypass, or autogenerated APIs — exposed active and historical OTPs. Drivers could read past OTPs from completed deliveries, and a compromised service key could extract all OTPs in one query.

Solution

  1. Migration SQL — Created delivery_otps table with FK to orders, expires_at, verified, verified_at. Enables RLS: customers SELECT their own OTPs, drivers SELECT assigned orders' OTPs, service_role INSERT/UPDATE only. Drops old OTP columns from orders.
  2. notificationService.js — Added storeDeliveryOtp(), getActiveDeliveryOtp(), verifyDeliveryOtp(), expireDeliveryOtps() with expires_at-based filtering
  3. orderRoutes.js — Milestone route stores OTP via storeDeliveryOtp() instead of writing to orders. Verify-delivery reads from delivery_otps via getActiveDeliveryOtp(), calls verifyDeliveryOtp() on success. Cancel route checks delivery_otps for verified OTP before allowing cancellation.

Files changed

  • docs/migration_add_delivery_otp.sql — new delivery_otps table + RLS policies
  • backend/api/src/services/notificationService.js — OTP persistence functions
  • backend/api/src/routes/orderRoutes.js — OTP generation, verification, cancellation

Closes #597

Summary by CodeRabbit

  • New Features
    • Added dedicated delivery OTP lifecycle handling for order milestone transitions (generation, activation, expiration, verification).
    • Updated delivery verification to require an active OTP before releasing payment.
    • Tightened cancellation rules to block cancellation when a delivery OTP has already been verified.
  • Bug Fixes
    • Adjusted order/milestone responses so delivery OTP fields are no longer inconsistently stripped or masked.
  • Chores
    • Simplified delivery push notification persistence.
    • Logout now signs out immediately based on current session state, while still attempting a backend logout when available.

@github-actions

Copy link
Copy Markdown
Contributor

🎉 Thank you for your contribution! Your pull request has been received and will be reviewed shortly.

If you enjoy the project, please consider giving the repository a ⭐. You can also follow my GitHub profile to stay updated on future open-source projects.

Thanks for being part of the community! 🚀

@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Delivery OTP storage is moved from inline orders columns into a dedicated delivery_otps table with RLS policies. A SQL migration creates the table, drops legacy columns, and applies access restrictions. Four OTP lifecycle helpers are added to notificationService to manage persistence and verification. The milestone, verify-delivery, and cancel route handlers are rewired to use these helpers and enforce stricter terminal-status guards. Additionally, minor client-side refactors update authentication flow and Realtime event handling.

Changes

Delivery OTP Isolation

Layer / File(s) Summary
delivery_otps table schema and migration
docs/migration_add_delivery_otp.sql
Replaces OTP columns on orders with a dedicated delivery_otps table; adds RLS policies (customer/driver SELECT, service_role INSERT/UPDATE), indexes on order_id and expires_at, and drops delivery_otp, otp_verified, otp_generated_at from orders.
OTP lifecycle helpers in notificationService
backend/api/src/services/notificationService.js
Exports storeDeliveryOtp, getActiveDeliveryOtp, verifyDeliveryOtp, and expireDeliveryOtps that persist to and query delivery_otps; each logs Supabase errors and returns null/false without throwing. Removes notification persistence from sendPushNotification and updates sendDeliveryOtpNotification text.
Milestone route: OTP generation via new helpers
backend/api/src/routes/orderRoutes.js
Expands the notification service import; the In Transit handler calls getActiveDeliveryOtp/expireDeliveryOtps/storeDeliveryOtp instead of writing OTP fields directly to orders; removes delivery_otp stripping from the milestone and GET /:id responses.
verify-delivery route: OTP lookup and status-constrained update
backend/api/src/routes/orderRoutes.js
Loads a minimal order record, fetches the active OTP record, rejects on absence or mismatch, calls verifyDeliveryOtp on success, performs a terminal-status-excluding order update, branches escrow release on the pre-fetched order.escrow_status, and returns only a success message.
cancel route: verified-OTP guard and terminal-status exclusion
backend/api/src/routes/orderRoutes.js
Queries delivery_otps for an already-verified OTP before allowing cancellation; rejects with HTTP 409 if found; adds terminal-status exclusion to the orders update constraint.
Tests: OTP visibility and milestone generation
backend/api/test/integration/orders.test.js
Updates visibility assertions to confirm delivery_otp is not exposed on returned order objects; updates milestone test fixtures and assertions to seed delivery_otps records and validate OTP creation/notification content via the isolated table.
Tests: OTP verification, expiry, and lockout scenarios
backend/api/test/integration/orders.test.js
Reworks verify-delivery test fixtures to seed delivery_otps (with otp, expires_at, verified fields) instead of orders OTP columns; updates invalid/expired/lockout test setup and assertions; adjusts lockout-bypass logic to extend delivery_otps.expires_at; updates lockout-reset and regeneration tests to manipulate the isolated OTP record.
Tests: Redis resilience and escrow setup
backend/api/test/integration/orders.test.js, backend/api/test/integration/bids.test.js
Updates Redis active/failure resilience test fixtures to seed delivery_otps and changes OTP expiration trigger to mutate delivery_otps.expires_at. Updates bids.test.js to provide polygon wallet addresses and escrow deposit mock stubs for bidding tests.

Client-Side Cleanup and Refactors

Layer / File(s) Summary
Customer app logout refactor
apps/customer/lib/services/profile_service.dart
Refactors logout() to replace token-based and explicit header setup with a simplified x-user-id/x-user-role header approach; removes timeout and Bearer token logic while retaining error logging and always-sign-out-locally behavior.
Driver app import cleanup and Realtime guard narrowing
apps/driver/lib/screens/profile_screen.dart, apps/driver/lib/services/marketplace_repository.dart
Removes duplicate http import from profile_screen.dart; narrows the Supabase Realtime insert callback guard in marketplace_repository.dart to check only newRecord.isNotEmpty instead of also checking null.

Sequence Diagram(s)

sequenceDiagram
    participant Driver
    participant OrderRoutes
    participant notificationService
    participant delivery_otps
    participant orders

    rect rgba(100, 149, 237, 0.5)
        Note over Driver,orders: In Transit milestone — OTP generation
        Driver->>OrderRoutes: PUT /:id/milestones {milestone: "In Transit"}
        OrderRoutes->>notificationService: getActiveDeliveryOtp(orderId)
        notificationService->>delivery_otps: SELECT unverified, unexpired OTP
        delivery_otps-->>notificationService: null (no active OTP)
        OrderRoutes->>notificationService: expireDeliveryOtps(orderId)
        OrderRoutes->>notificationService: storeDeliveryOtp(orderId, otp)
        notificationService->>delivery_otps: INSERT otp with expires_at
        OrderRoutes-->>Driver: 200 milestone updated
    end

    rect rgba(144, 238, 144, 0.5)
        Note over Driver,orders: Delivery verification
        Driver->>OrderRoutes: POST /:id/verify-delivery {otp}
        OrderRoutes->>orders: SELECT id, escrow_status, driver_id
        OrderRoutes->>notificationService: getActiveDeliveryOtp(orderId)
        notificationService->>delivery_otps: SELECT latest unverified, unexpired
        delivery_otps-->>notificationService: otpRecord
        OrderRoutes->>notificationService: verifyDeliveryOtp(orderId)
        notificationService->>delivery_otps: UPDATE verified=true, verified_at=now
        OrderRoutes->>orders: UPDATE status (excluding terminal statuses)
        OrderRoutes-->>Driver: 200 Delivery verified
    end

    rect rgba(255, 160, 122, 0.5)
        Note over Driver,orders: Cancellation guard
        Driver->>OrderRoutes: POST /:id/cancel
        OrderRoutes->>delivery_otps: SELECT verified OTP for order
        delivery_otps-->>OrderRoutes: verified record found
        OrderRoutes-->>Driver: 409 Cannot cancel — delivery already verified
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

  • #597: This PR directly addresses the linked issue by creating a dedicated delivery_otps table with RLS policies, removing OTP columns from orders, and rewiring route handlers to use isolated OTP helpers — implementing the core of Fix 1 and Fix 4 described in the issue.

Possibly related PRs

  • KanishJebaMathewM/Truxify#500: Modifies the same In Transit OTP generation block in orderRoutes.js and extends notificationService.js for OTP delivery, making it a direct predecessor to this PR's rework.
  • KanishJebaMathewM/Truxify#550: Both PRs modify POST /:id/verify-delivery and POST /:id/cancel in orderRoutes.js around escrow_status branching and state-transition guards.
  • KanishJebaMathewM/Truxify#516: Overlaps on the In Transit OTP generation and verifyDeliveryOtp flow in orderRoutes.js, introducing OTP state-clearing logic in the same code paths.

Suggested labels

`backend`, `testing`, `security`

🐇 A new table was dug in the ground,
Where OTPs hide safe and sound.
No driver shall peek at the code,
RLS guards every row bestowed.
The orders table, light and clean —
The hoppiest schema I have seen! 🥕

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: isolating delivery OTP into a dedicated table with RLS, which is the primary objective of this PR.
Linked Issues check ✅ Passed The PR addresses the four critical security gaps identified in #597: OTP isolation in dedicated table with RLS [#597 Gap 1], ephemeral one-time-use handling via new OTP lifecycle functions [#597 Gap 2], backend validation constraints [#597 Gap 3], and prevention of OTP re-triggering via one-time generation logic [#597 Gap 4].
Out of Scope Changes check ✅ Passed Minor out-of-scope changes include removing a duplicate import from profile_screen.dart and refactoring logout() in profile_service.dart, which are unrelated to the OTP isolation objective but are acknowledged in commit messages.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (1)
backend/api/src/routes/orderRoutes.js (1)

736-736: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

responseOrder is undefined — this will throw a ReferenceError at runtime.

The variable responseOrder is referenced but never defined. Line 726 defines updatedOrder from the database update. This appears to be a regression from removing the OTP-stripping logic.

🐛 Proposed fix
-    const response = { message: 'Milestone updated successfully.', order: responseOrder, milestone, status };
+    const response = { message: 'Milestone updated successfully.', order: updatedOrder, milestone, status };
🤖 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 `@backend/api/src/routes/orderRoutes.js` at line 736, The response object on
line 736 references the undefined variable `responseOrder`. Replace
`responseOrder` with `updatedOrder` (the variable defined on line 726 that
contains the result from the database update) in the response object
construction to fix the ReferenceError.
🧹 Nitpick comments (1)
backend/api/src/routes/orderRoutes.js (1)

22-22: 💤 Low value

expireDeliveryOtps is imported but never used.

The function is imported but not called anywhere in this file. Either remove the unused import, or if the intent was to expire old OTPs before storing a new one in the milestone handler (lines 715-724), add the call there.

🤖 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 `@backend/api/src/routes/orderRoutes.js` at line 22, The function
`expireDeliveryOtps` is imported in the import statement at the top of the file
but is never called anywhere in the file. Either remove `expireDeliveryOtps`
from the import statement at the top of the file to clean up unused imports, or
if the intent was to expire old OTPs before storing a new one, add a call to
`expireDeliveryOtps` in the milestone handler section where `storeDeliveryOtp`
is being invoked to ensure old OTPs are expired before new ones are created.
🤖 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 `@docs/migration_add_delivery_otp.sql`:
- Around line 33-39: Remove the entire CREATE POLICY statement for
driver_select_delivery_otp (the policy that allows SELECT operations on
delivery_otps where order_id matches orders assigned to the current driver).
This policy exposes OTP verification secrets to drivers, which violates the
security requirement that drivers should have no access to OTP data. Delete the
complete policy block from the migration file.
- Line 8: The migration file stores OTP in plaintext by using a column named
`otp`, which poses a security risk since OTP secrets should never be stored in
recoverable form. Rename the `otp TEXT NOT NULL` column to `otp_hash TEXT NOT
NULL` in the migration_add_delivery_otp.sql file to properly indicate that this
column should store hashed OTP values rather than raw plaintext OTP. Ensure all
references to this column in the application code are updated to match the new
column name.

---

Outside diff comments:
In `@backend/api/src/routes/orderRoutes.js`:
- Line 736: The response object on line 736 references the undefined variable
`responseOrder`. Replace `responseOrder` with `updatedOrder` (the variable
defined on line 726 that contains the result from the database update) in the
response object construction to fix the ReferenceError.

---

Nitpick comments:
In `@backend/api/src/routes/orderRoutes.js`:
- Line 22: The function `expireDeliveryOtps` is imported in the import statement
at the top of the file but is never called anywhere in the file. Either remove
`expireDeliveryOtps` from the import statement at the top of the file to clean
up unused imports, or if the intent was to expire old OTPs before storing a new
one, add a call to `expireDeliveryOtps` in the milestone handler section where
`storeDeliveryOtp` is being invoked to ensure old OTPs are expired before new
ones are created.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: dd770585-2784-4394-9699-23b004be3b40

📥 Commits

Reviewing files that changed from the base of the PR and between 4656ed1 and 922b9f9.

📒 Files selected for processing (3)
  • backend/api/src/routes/orderRoutes.js
  • backend/api/src/services/notificationService.js
  • docs/migration_add_delivery_otp.sql

Comment thread docs/migration_add_delivery_otp.sql
Comment thread docs/migration_add_delivery_otp.sql
@ionfwsrijan ionfwsrijan force-pushed the fix/issue-597-delivery-otp-isolation branch from 922b9f9 to 611c38b Compare June 18, 2026 15:50

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (1)
backend/api/src/services/notificationService.js (1)

100-120: ⚡ Quick win

Use logger instead of console.error / console.log for consistency.

The OTP lifecycle helpers use console.error and console.log while the rest of this file uses the imported logger. This creates inconsistent log formatting and makes log aggregation harder.

♻️ Suggested fix
 export async function storeDeliveryOtp(orderId, otp, ttlMinutes = 15) {
   const expiresAt = new Date(Date.now() + ttlMinutes * 60 * 1000).toISOString();

   const { data, error } = await supabase
     .from('delivery_otps')
     .insert({
       order_id: orderId,
       otp,
       expires_at: expiresAt,
     })
     .select('id')
     .single();

   if (error) {
-    console.error('[NotificationService] Failed to store OTP:', error.message);
+    logger.error(`[NotificationService] Failed to store OTP: ${error.message}`);
     return null;
   }

-  console.log(`[NotificationService] OTP stored for order ${orderId}, expires at ${expiresAt}`);
+  logger.info(`[NotificationService] OTP stored for order ${orderId}, expires at ${expiresAt}`);
   return data;
 }

Apply similar changes to getActiveDeliveryOtp, verifyDeliveryOtp, and expireDeliveryOtps.

Also applies to: 128-145, 153-169, 177-187

🤖 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 `@backend/api/src/services/notificationService.js` around lines 100 - 120,
Replace all console.error and console.log calls with the logger instance that is
already imported and used elsewhere in the file. In the storeDeliveryOtp
function, replace the console.error call with logger.error when handling the
error case, and replace the console.log call with an appropriate logger method
(such as logger.info) for the success case. Apply the same pattern to the
getActiveDeliveryOtp, verifyDeliveryOtp, and expireDeliveryOtps functions to
ensure consistent logging throughout the OTP lifecycle helpers and maintain
uniform log formatting across the 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 `@backend/api/src/services/notificationService.js`:
- Around line 201-214: The plaintext OTP is being persisted directly in the
notification body variable that gets inserted into the notifications table,
creating an unnecessary security vulnerability if the notifications table has
weaker RLS policies. Replace the body template string to remove the embedded OTP
value and instead use a generic reference message like "Your delivery OTP has
been generated. Open the app to view it." This way the actual OTP remains
isolated in the delivery_otps table and must be fetched through its own secured
access path.

In `@docs/migration_add_delivery_otp.sql`:
- Line 56: The comment on line 56 stating "no OTP changes needed" for the
complete_trip_tx RPC is misleading because the RPC does reference OTP fields
such as otp_verified being set to true. Since the otp_verified column is being
dropped in this migration, update the comment to accurately reflect that OTP
changes ARE required for complete_trip_tx, and either describe what those
changes are or remove the misleading statement entirely.
- Around line 42-49: The service role access control in the delivery_otps
policies is inconsistent with the project's standard pattern. Replace the
auth.role() function checks in both service_insert_delivery_otp and
service_update_delivery_otp policies with role-based grants using the TO
service_role clause instead. For the INSERT policy, add TO service_role and
change the WITH CHECK condition to (true). For the UPDATE policy, add TO
service_role and change both the USING and WITH CHECK conditions to (true). This
aligns with the pattern used for other tables like profiles, orders, and
driver_details.
- Around line 52-54: The migration_add_delivery_otp.sql is dropping the
otp_verified column from the orders table, but the complete_trip_tx RPC function
in migration_complete_trip_update.sql (at line 71) still contains a SET
otp_verified = true statement. Since migrations run alphabetically, this will
cause a runtime error when the function tries to reference a dropped column.
Remove the SET otp_verified = true line from the complete_trip_tx function in
migration_complete_trip_update.sql to align with the stated intention that the
function only uses orders.total_amount.

---

Nitpick comments:
In `@backend/api/src/services/notificationService.js`:
- Around line 100-120: Replace all console.error and console.log calls with the
logger instance that is already imported and used elsewhere in the file. In the
storeDeliveryOtp function, replace the console.error call with logger.error when
handling the error case, and replace the console.log call with an appropriate
logger method (such as logger.info) for the success case. Apply the same pattern
to the getActiveDeliveryOtp, verifyDeliveryOtp, and expireDeliveryOtps functions
to ensure consistent logging throughout the OTP lifecycle helpers and maintain
uniform log formatting across the 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 48737a56-e257-453e-9424-b669735bfaa5

📥 Commits

Reviewing files that changed from the base of the PR and between 922b9f9 and 611c38b.

📒 Files selected for processing (3)
  • backend/api/src/routes/orderRoutes.js
  • backend/api/src/services/notificationService.js
  • docs/migration_add_delivery_otp.sql
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/api/src/routes/orderRoutes.js

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 4

🧹 Nitpick comments (1)
backend/api/src/services/notificationService.js (1)

100-120: ⚡ Quick win

Use logger instead of console.error / console.log for consistency.

The OTP lifecycle helpers use console.error and console.log while the rest of this file uses the imported logger. This creates inconsistent log formatting and makes log aggregation harder.

♻️ Suggested fix
 export async function storeDeliveryOtp(orderId, otp, ttlMinutes = 15) {
   const expiresAt = new Date(Date.now() + ttlMinutes * 60 * 1000).toISOString();

   const { data, error } = await supabase
     .from('delivery_otps')
     .insert({
       order_id: orderId,
       otp,
       expires_at: expiresAt,
     })
     .select('id')
     .single();

   if (error) {
-    console.error('[NotificationService] Failed to store OTP:', error.message);
+    logger.error(`[NotificationService] Failed to store OTP: ${error.message}`);
     return null;
   }

-  console.log(`[NotificationService] OTP stored for order ${orderId}, expires at ${expiresAt}`);
+  logger.info(`[NotificationService] OTP stored for order ${orderId}, expires at ${expiresAt}`);
   return data;
 }

Apply similar changes to getActiveDeliveryOtp, verifyDeliveryOtp, and expireDeliveryOtps.

Also applies to: 128-145, 153-169, 177-187

🤖 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 `@backend/api/src/services/notificationService.js` around lines 100 - 120,
Replace all console.error and console.log calls with the logger instance that is
already imported and used elsewhere in the file. In the storeDeliveryOtp
function, replace the console.error call with logger.error when handling the
error case, and replace the console.log call with an appropriate logger method
(such as logger.info) for the success case. Apply the same pattern to the
getActiveDeliveryOtp, verifyDeliveryOtp, and expireDeliveryOtps functions to
ensure consistent logging throughout the OTP lifecycle helpers and maintain
uniform log formatting across the 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 `@backend/api/src/services/notificationService.js`:
- Around line 201-214: The plaintext OTP is being persisted directly in the
notification body variable that gets inserted into the notifications table,
creating an unnecessary security vulnerability if the notifications table has
weaker RLS policies. Replace the body template string to remove the embedded OTP
value and instead use a generic reference message like "Your delivery OTP has
been generated. Open the app to view it." This way the actual OTP remains
isolated in the delivery_otps table and must be fetched through its own secured
access path.

In `@docs/migration_add_delivery_otp.sql`:
- Line 56: The comment on line 56 stating "no OTP changes needed" for the
complete_trip_tx RPC is misleading because the RPC does reference OTP fields
such as otp_verified being set to true. Since the otp_verified column is being
dropped in this migration, update the comment to accurately reflect that OTP
changes ARE required for complete_trip_tx, and either describe what those
changes are or remove the misleading statement entirely.
- Around line 42-49: The service role access control in the delivery_otps
policies is inconsistent with the project's standard pattern. Replace the
auth.role() function checks in both service_insert_delivery_otp and
service_update_delivery_otp policies with role-based grants using the TO
service_role clause instead. For the INSERT policy, add TO service_role and
change the WITH CHECK condition to (true). For the UPDATE policy, add TO
service_role and change both the USING and WITH CHECK conditions to (true). This
aligns with the pattern used for other tables like profiles, orders, and
driver_details.
- Around line 52-54: The migration_add_delivery_otp.sql is dropping the
otp_verified column from the orders table, but the complete_trip_tx RPC function
in migration_complete_trip_update.sql (at line 71) still contains a SET
otp_verified = true statement. Since migrations run alphabetically, this will
cause a runtime error when the function tries to reference a dropped column.
Remove the SET otp_verified = true line from the complete_trip_tx function in
migration_complete_trip_update.sql to align with the stated intention that the
function only uses orders.total_amount.

---

Nitpick comments:
In `@backend/api/src/services/notificationService.js`:
- Around line 100-120: Replace all console.error and console.log calls with the
logger instance that is already imported and used elsewhere in the file. In the
storeDeliveryOtp function, replace the console.error call with logger.error when
handling the error case, and replace the console.log call with an appropriate
logger method (such as logger.info) for the success case. Apply the same pattern
to the getActiveDeliveryOtp, verifyDeliveryOtp, and expireDeliveryOtps functions
to ensure consistent logging throughout the OTP lifecycle helpers and maintain
uniform log formatting across the 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 48737a56-e257-453e-9424-b669735bfaa5

📥 Commits

Reviewing files that changed from the base of the PR and between 922b9f9 and 611c38b.

📒 Files selected for processing (3)
  • backend/api/src/routes/orderRoutes.js
  • backend/api/src/services/notificationService.js
  • docs/migration_add_delivery_otp.sql
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/api/src/routes/orderRoutes.js
🛑 Comments failed to post (4)
backend/api/src/services/notificationService.js (1)

201-214: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Raw OTP is persisted in the notifications table body.

Line 202 embeds the plaintext OTP in the notification body that gets inserted into the notifications table. If notifications has weaker RLS than delivery_otps, this creates an alternative access path to the OTP—undermining the isolation goal.

Consider storing only a reference (e.g., "Your delivery OTP has been generated. Open the app to view it.") and letting the client fetch the OTP through the secured delivery_otps path.

🔒 Suggested fix
   const title = 'Delivery Verification OTP';
-  const body  = `Your delivery OTP for order ${orderDisplayId} is ${otp}. Share this with the driver only after verifying your cargo has arrived safely.`;
+  const body  = `Your delivery OTP for order ${orderDisplayId} is ready. Open the app to view it. Share with the driver only after verifying your cargo has arrived safely.`;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  const title = 'Delivery Verification OTP';
  const body  = `Your delivery OTP for order ${orderDisplayId} is ready. Open the app to view it. Share with the driver only after verifying your cargo has arrived safely.`;

  // 1. Database Notification Persistence (always attempted first)
  try {
    const { error } = await supabase
      .from('notifications')
      .insert({
        user_id: customerId,
        title,
        body,
        notif_type: 'order_update',
        metadata: { order_display_id: orderDisplayId },
      });
🤖 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 `@backend/api/src/services/notificationService.js` around lines 201 - 214, The
plaintext OTP is being persisted directly in the notification body variable that
gets inserted into the notifications table, creating an unnecessary security
vulnerability if the notifications table has weaker RLS policies. Replace the
body template string to remove the embedded OTP value and instead use a generic
reference message like "Your delivery OTP has been generated. Open the app to
view it." This way the actual OTP remains isolated in the delivery_otps table
and must be fetched through its own secured access path.
docs/migration_add_delivery_otp.sql (3)

42-49: ⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if auth.role() is defined as a custom function or used elsewhere
rg -n 'auth\.role\(\)' --type sql
rg -n 'CREATE.*FUNCTION.*auth\.role' --type sql

Repository: KanishJebaMathewM/Truxify

Length of output: 315


🏁 Script executed:

# Search for other RLS policy definitions to understand patterns
rg -n 'CREATE POLICY' --type sql -A 2

# Search for auth.jwt() usage for role checking
rg -n 'auth\.jwt\(\)' --type sql

# Search for current_setting usage
rg -n 'current_setting' --type sql

Repository: KanishJebaMathewM/Truxify

Length of output: 17049


Use TO service_role role grant instead of auth.role() = 'service_role'.

The delivery_otp policies use auth.role() = 'service_role', which is inconsistent with how the rest of the project implements service role access. All other tables (profiles, orders, driver_details, load_offers, etc.) grant service role access using FOR ALL TO service_role USING (true) WITH CHECK (true). Replace the non-standard auth.role() checks with role-based grants via the TO clause:

Suggested change
CREATE POLICY service_insert_delivery_otp ON delivery_otps
  FOR INSERT
  TO service_role
  WITH CHECK (true);

CREATE POLICY service_update_delivery_otp ON delivery_otps
  FOR UPDATE
  TO service_role
  USING (true)
  WITH CHECK (true);
🤖 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 `@docs/migration_add_delivery_otp.sql` around lines 42 - 49, The service role
access control in the delivery_otps policies is inconsistent with the project's
standard pattern. Replace the auth.role() function checks in both
service_insert_delivery_otp and service_update_delivery_otp policies with
role-based grants using the TO service_role clause instead. For the INSERT
policy, add TO service_role and change the WITH CHECK condition to (true). For
the UPDATE policy, add TO service_role and change both the USING and WITH CHECK
conditions to (true). This aligns with the pattern used for other tables like
profiles, orders, and driver_details.

52-54: ⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find all SQL files that reference otp_verified to assess migration impact
rg -n 'otp_verified' --type sql

Repository: KanishJebaMathewM/Truxify

Length of output: 414


🏁 Script executed:

cat -n docs/migration_complete_trip_update.sql | head -80

Repository: KanishJebaMathewM/Truxify

Length of output: 3035


🏁 Script executed:

cat -n docs/migration_add_delivery_otp.sql

Repository: KanishJebaMathewM/Truxify

Length of output: 2425


🏁 Script executed:

git ls-files docs/*.sql | sort

Repository: KanishJebaMathewM/Truxify

Length of output: 609


Dropping otp_verified will break existing complete_trip_tx RPC.

The migration migration_complete_trip_update.sql:71 still contains SET otp_verified = true, but migration_add_delivery_otp.sql:53 drops this column. If migrations run in alphabetical order (as typical), the column will be dropped before the function attempts to use it, causing SQL runtime errors.

The comment in migration_add_delivery_otp.sql incorrectly states "Update complete_trip_tx RPC (no OTP changes needed — it uses orders.total_amount only)", but the actual function still references the column.

Either update complete_trip_tx to remove the SET otp_verified = true line, or ensure migration_complete_trip_update.sql runs before migration_add_delivery_otp.sql.

🤖 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 `@docs/migration_add_delivery_otp.sql` around lines 52 - 54, The
migration_add_delivery_otp.sql is dropping the otp_verified column from the
orders table, but the complete_trip_tx RPC function in
migration_complete_trip_update.sql (at line 71) still contains a SET
otp_verified = true statement. Since migrations run alphabetically, this will
cause a runtime error when the function tries to reference a dropped column.
Remove the SET otp_verified = true line from the complete_trip_tx function in
migration_complete_trip_update.sql to align with the stated intention that the
function only uses orders.total_amount.

56-56: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Comment claims no OTP changes needed, but complete_trip_tx does reference OTP fields.

Line 56 states "no OTP changes needed" for complete_trip_tx, but the context snippet shows it explicitly sets otp_verified = true. This comment is misleading given the column is being dropped.

🤖 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 `@docs/migration_add_delivery_otp.sql` at line 56, The comment on line 56
stating "no OTP changes needed" for the complete_trip_tx RPC is misleading
because the RPC does reference OTP fields such as otp_verified being set to
true. Since the otp_verified column is being dropped in this migration, update
the comment to accurately reflect that OTP changes ARE required for
complete_trip_tx, and either describe what those changes are or remove the
misleading statement entirely.

- Fix milestone handler using undefined responseOrder variable
  (responseOrder was removed with OTP-stripping code, use updatedOrder)
- Add explicit verified: false to storeDeliveryOtp insert payload
- Fix test pollution by resetting delivery_otps between OTP tests
- Update 8 OTP integration tests to seed delivery_otps table
- Add wallet addresses + escrow mock to two bid-acceptance tests
  (upstream wallet validation gate returns 422 when wallets missing)
- Replace undefined _client/_httpClient/_apiBaseUrl references in
  customer ProfileService.logout() with SupabaseService.client and _apiClient
- Remove duplicate http import in driver profile_screen.dart
- Remove unnecessary null check on RealtimePostgresChangesPayload.newRecord
  (non-nullable type)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (3)
backend/api/test/integration/orders.test.js (3)

1114-1117: ⚡ Quick win

Add assertion for verified_at field.

The test only validates that verified is set to true, but does not check that verified_at is populated. According to the verifyDeliveryOtp implementation (notificationService.js:151-156), both fields should be set upon successful verification.

📋 Proposed enhancement
 // OTP record should be marked as verified in isolated table
 const otpRecord = m.store.delivery_otps.find(o => o.order_id === 'order-1');
 expect(otpRecord.verified).toBe(true);
+expect(otpRecord.verified_at).toBeDefined();
+expect(new Date(otpRecord.verified_at).getTime()).toBeGreaterThan(Date.now() - 5000); // within last 5s
🤖 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 `@backend/api/test/integration/orders.test.js` around lines 1114 - 1117, Add an
assertion to verify that the verified_at field is populated when an OTP record
is successfully verified. After the existing expect assertion for
otpRecord.verified being true, add another assertion to check that
otpRecord.verified_at is not null, undefined, or empty. This ensures the test
validates both the verified flag and the timestamp field that the
verifyDeliveryOtp implementation sets together according to the
notificationService.js logic.

1005-1005: ⚡ Quick win

Consider validating the expires_at timestamp range.

The test only checks that expires_at is defined, but doesn't verify it's set to a future time within the expected 15-minute window. Based on the storeDeliveryOtp implementation (which sets ttlMinutes = 15 by default), consider adding an assertion like:

const expiresAt = new Date(otpRecord.expires_at);
const now = Date.now();
expect(expiresAt.getTime()).toBeGreaterThan(now);
expect(expiresAt.getTime()).toBeLessThanOrEqual(now + 15 * 60 * 1000 + 1000); // +1s for clock skew

This ensures the expiry window is correctly computed and prevents regressions if the TTL logic changes.

🤖 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 `@backend/api/test/integration/orders.test.js` at line 1005, The current
assertion for otpRecord.expires_at only verifies the field is defined but does
not validate the actual timestamp value. Replace the toBeDefined check with
additional assertions that verify the expires_at timestamp is a future time and
falls within the expected 15-minute TTL window from the current time (accounting
for small clock skew). This ensures the OTP expiry logic is working correctly
and prevents regressions if the TTL calculation changes in the storeDeliveryOtp
implementation.

1332-1338: ⚡ Quick win

Consider more robust new OTP identification.

Line 1333 identifies the new OTP by filtering for records where o.otp !== '123456'. While statistically unlikely with 6-digit OTPs, there's a theoretical chance the newly generated OTP could be '123456' again, causing the test to fail intermittently.

Consider identifying the new OTP by created_at timestamp or by excluding the known old OTP id:

const newOtps = m.store.delivery_otps.filter(o => o.order_id === orderId && o.id !== 'otp-regen-old');

This approach is deterministic and eliminates any chance of flakiness.

🤖 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 `@backend/api/test/integration/orders.test.js` around lines 1332 - 1338, The
newOtps filter at line 1333 uses a value-based comparison (o.otp !== '123456')
which is not deterministic and could fail if the newly generated OTP happens to
be '123456' again. Replace this filtering logic with a more robust approach by
either tracking and excluding the id of the previously known OTP, or by
identifying the new OTP using the created_at timestamp to select the most recent
entry. This will ensure the test is deterministic and eliminate any possibility
of flakiness.
🤖 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 `@apps/customer/lib/services/profile_service.dart`:
- Around line 45-56: The backend logout request in the try block can stall
indefinitely, blocking the local sign-out that occurs in the finally block. Add
a timeout boundary to the _apiClient.post call so that if the backend logout
takes too long to respond, it fails fast and allows the
SupabaseService.client.auth.signOut() in the finally block to proceed without
unnecessary delays, preventing customers from being stuck waiting for the
backend to respond.

---

Nitpick comments:
In `@backend/api/test/integration/orders.test.js`:
- Around line 1114-1117: Add an assertion to verify that the verified_at field
is populated when an OTP record is successfully verified. After the existing
expect assertion for otpRecord.verified being true, add another assertion to
check that otpRecord.verified_at is not null, undefined, or empty. This ensures
the test validates both the verified flag and the timestamp field that the
verifyDeliveryOtp implementation sets together according to the
notificationService.js logic.
- Line 1005: The current assertion for otpRecord.expires_at only verifies the
field is defined but does not validate the actual timestamp value. Replace the
toBeDefined check with additional assertions that verify the expires_at
timestamp is a future time and falls within the expected 15-minute TTL window
from the current time (accounting for small clock skew). This ensures the OTP
expiry logic is working correctly and prevents regressions if the TTL
calculation changes in the storeDeliveryOtp implementation.
- Around line 1332-1338: The newOtps filter at line 1333 uses a value-based
comparison (o.otp !== '123456') which is not deterministic and could fail if the
newly generated OTP happens to be '123456' again. Replace this filtering logic
with a more robust approach by either tracking and excluding the id of the
previously known OTP, or by identifying the new OTP using the created_at
timestamp to select the most recent entry. This will ensure the test is
deterministic and eliminate any possibility of flakiness.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7c824c34-98b9-4d47-8078-5f8d9b8b726f

📥 Commits

Reviewing files that changed from the base of the PR and between 611c38b and d43a5c2.

📒 Files selected for processing (7)
  • apps/customer/lib/services/profile_service.dart
  • apps/driver/lib/screens/profile_screen.dart
  • apps/driver/lib/services/marketplace_repository.dart
  • backend/api/src/routes/orderRoutes.js
  • backend/api/src/services/notificationService.js
  • backend/api/test/integration/bids.test.js
  • backend/api/test/integration/orders.test.js
💤 Files with no reviewable changes (1)
  • apps/driver/lib/screens/profile_screen.dart
🚧 Files skipped from review as they are similar to previous changes (2)
  • backend/api/src/services/notificationService.js
  • backend/api/src/routes/orderRoutes.js

Comment thread apps/customer/lib/services/profile_service.dart
@ionfwsrijan

Copy link
Copy Markdown
Contributor Author

@KanishJebaMathewM Please review this and add labels

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Delivery OTP Stored in Orders Table Without Isolated Access Controls — Drivers Can Access OTP via Supabase RLS Bypass or Database Migrations

1 participant