Skip to content

fix(gateway): resolve device.proto path in Docker container#16

Merged
pahuldeepp merged 2 commits intomasterfrom
fix/gateway-proto-path
Mar 29, 2026
Merged

fix(gateway): resolve device.proto path in Docker container#16
pahuldeepp merged 2 commits intomasterfrom
fix/gateway-proto-path

Conversation

@pahuldeepp
Copy link
Copy Markdown
Owner

@pahuldeepp pahuldeepp commented Mar 29, 2026

Summary

  • Fixes gateway CrashLoopBackOff in staging caused by ENOENT /libs/proto/device.proto
  • The compiled JS runs from /app/dist/services/; proto is at /app/libs/proto/device.proto
  • Previous path used ../../../../ (4 levels up → /); correct is 2 levels up or hardcoded /app/libs/proto

Test plan

  • Gateway starts cleanly in staging (no proto ENOENT crash)
  • CI green on this PR

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Applied rate limiting to billing endpoints (subscription, checkout, portal) to improve stability and protect against excessive requests.
  • Chores

    • Improved proto file path resolution to support multiple deployment environments.

The compiled gateway runs from /app/dist/services/. The proto file
is copied to /app/libs/proto/ in the Docker image. The previous path
(../../../../libs/proto/device.proto) resolved to /libs/proto which
doesn't exist. Use /app/libs/proto directly with a local fallback.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 29, 2026

📝 Walkthrough

Walkthrough

Three changes: device service proto path resolution now prefers an absolute /app/libs/proto/device.proto with a relative fallback; billing routes now apply apiRateLimiter middleware after authMiddleware for three endpoints; a load-test helper catches and captures the parse error as error in its catch clause.

Changes

Cohort / File(s) Summary
Proto Path Resolution
apps/gateway/src/services/device.ts
Switched proto file path resolution to prefer an absolute /app/libs/proto/device.proto when present, otherwise fall back to a relative path.
Billing Routes (rate limiting)
apps/gateway/src/routes/billing.ts
Added apiRateLimiter middleware after authMiddleware to GET /billing/subscription, POST /billing/checkout, and POST /billing/portal, inserting rate-limiting into request flow.
Load-test error handling
scripts/load-tests/performance-budget.js
Updated catch to catch (error) in hasGraphqlErrors(response) so the thrown exception is captured as error (behavior unchanged).

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client
    participant Gateway as Gateway (Express)
    participant Auth as authMiddleware
    participant RateLimiter as apiRateLimiter
    participant Handler as Billing Handler
    participant Stripe as Stripe/DB

    Client->>Gateway: HTTP request (e.g., POST /billing/checkout)
    Gateway->>Auth: run authMiddleware
    Auth-->>Gateway: auth result
    Gateway->>RateLimiter: run apiRateLimiter
    RateLimiter-->>Gateway: allowed/blocked
    Gateway->>Handler: invoke route handler
    Handler->>Stripe: process checkout / query subscription
    Stripe-->>Handler: response
    Handler-->>Client: HTTP response
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the primary change: fixing proto file path resolution in the gateway service for Docker container compatibility.

✏️ 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 fix/gateway-proto-path

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

Copy link
Copy Markdown

@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: 1

Caution

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

⚠️ Outside diff range comments (1)
apps/gateway/src/services/device.ts (1)

38-44: 🧹 Nitpick | 🔵 Trivial

mTLS falls back to insecure credentials silently.

When /certs/ca.crt is missing, the code falls back to grpc.credentials.createInsecure() without any log or warning. This is intentional for local dev, but in production if certs are accidentally missing, traffic would be unencrypted without any visible alert.

Consider logging when insecure mode is used:

📝 Suggested improvement
 const credentials = !fs.existsSync("/certs/ca.crt")
-  ? grpc.credentials.createInsecure()
+  ? (console.warn("[device-service] mTLS certs not found, using insecure credentials"), grpc.credentials.createInsecure())
   : grpc.credentials.createSsl(
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/gateway/src/services/device.ts` around lines 38 - 44, The code silently
falls back to grpc.credentials.createInsecure() when
fs.existsSync("/certs/ca.crt") is false; modify the branch that sets the
credentials variable (the ternary using fs.existsSync,
grpc.credentials.createInsecure, and grpc.credentials.createSsl) to emit a clear
warning/log (e.g., processLogger.warn or console.warn) whenever insecure
credentials are used, include the path checked and a brief note that this is
falling back to insecure mode, and keep the existing behavior for local dev
while ensuring production accidental-misconfiguration is visible.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/gateway/src/services/device.ts`:
- Around line 11-13: The fallback path for protoPath is wrong for local dev:
update the fallback in the protoPath expression (the ternary using fs.existsSync
and __dirname) to point to the repo-root location (e.g., use
path.resolve(__dirname, "../../../../libs/proto/device.proto") or
path.resolve(__dirname, "../../../..", "libs/proto/device.proto")) so when
running apps/gateway/dist/services/device.js it resolves to
libs/proto/device.proto; alternatively, if local development support is
intentionally not required, add a clarifying comment above the protoPath
declaration noting the code only supports the container absolute path.

---

Outside diff comments:
In `@apps/gateway/src/services/device.ts`:
- Around line 38-44: The code silently falls back to
grpc.credentials.createInsecure() when fs.existsSync("/certs/ca.crt") is false;
modify the branch that sets the credentials variable (the ternary using
fs.existsSync, grpc.credentials.createInsecure, and grpc.credentials.createSsl)
to emit a clear warning/log (e.g., processLogger.warn or console.warn) whenever
insecure credentials are used, include the path checked and a brief note that
this is falling back to insecure mode, and keep the existing behavior for local
dev while ensuring production accidental-misconfiguration is visible.
🪄 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: ASSERTIVE

Plan: Pro

Run ID: d85746ce-de9c-4dca-8032-2b50ca985dcd

📥 Commits

Reviewing files that changed from the base of the PR and between 2be6ae0 and 1de9eb4.

📒 Files selected for processing (1)
  • apps/gateway/src/services/device.ts

Comment on lines +11 to +13
const protoPath = fs.existsSync("/app/libs/proto/device.proto")
? "/app/libs/proto/device.proto"
: path.resolve(__dirname, "../../libs/proto/device.proto");
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fallback path incorrect for local development.

The primary absolute path /app/libs/proto/device.proto correctly resolves inside the Docker container. However, the fallback ../../libs/proto/device.proto assumes a flat structure that doesn't match local development.

When running locally from apps/gateway/dist/services/device.js, ../../libs/ resolves to apps/gateway/libs/, but the proto file lives at the repo root (libs/proto/device.proto), requiring ../../../../libs/proto/device.proto.

If local dev support isn't needed, consider adding a comment. Otherwise:

🔧 Suggested fix to support both environments
-const protoPath = fs.existsSync("/app/libs/proto/device.proto")
-  ? "/app/libs/proto/device.proto"
-  : path.resolve(__dirname, "../../libs/proto/device.proto");
+// Docker: proto at /app/libs/proto/device.proto
+// Local:  proto at repo root, 4 levels up from apps/gateway/dist/services/
+const protoPath = fs.existsSync("/app/libs/proto/device.proto")
+  ? "/app/libs/proto/device.proto"
+  : path.resolve(__dirname, "../../../../libs/proto/device.proto");
📝 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.

Suggested change
const protoPath = fs.existsSync("/app/libs/proto/device.proto")
? "/app/libs/proto/device.proto"
: path.resolve(__dirname, "../../libs/proto/device.proto");
// Docker: proto at /app/libs/proto/device.proto
// Local: proto at repo root, 4 levels up from apps/gateway/dist/services/
const protoPath = fs.existsSync("/app/libs/proto/device.proto")
? "/app/libs/proto/device.proto"
: path.resolve(__dirname, "../../../../libs/proto/device.proto");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/gateway/src/services/device.ts` around lines 11 - 13, The fallback path
for protoPath is wrong for local dev: update the fallback in the protoPath
expression (the ternary using fs.existsSync and __dirname) to point to the
repo-root location (e.g., use path.resolve(__dirname,
"../../../../libs/proto/device.proto") or path.resolve(__dirname, "../../../..",
"libs/proto/device.proto")) so when running apps/gateway/dist/services/device.js
it resolves to libs/proto/device.proto; alternatively, if local development
support is intentionally not required, add a clarifying comment above the
protoPath declaration noting the code only supports the container absolute path.

- performance-budget.js: change `catch {}` to `catch (error) {}` for k6/Babel compatibility
- billing.ts: add apiRateLimiter to all three billing routes (CodeQL js/missing-rate-limiting)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
billingRouter.get(
"/billing/subscription",
authMiddleware,
apiRateLimiter,

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
a database access
, but is not rate-limited.

Copilot Autofix

AI about 1 month ago

Copilot could not generate an autofix suggestion

Copilot could not generate an autofix suggestion for this alert. Try pushing a new commit or if the problem persists contact support.

billingRouter.post(
"/billing/checkout",
authMiddleware,
apiRateLimiter,

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
a database access
, but is not rate-limited.

Copilot Autofix

AI about 1 month ago

Copilot could not generate an autofix suggestion

Copilot could not generate an autofix suggestion for this alert. Try pushing a new commit or if the problem persists contact support.

billingRouter.post(
"/billing/portal",
authMiddleware,
apiRateLimiter,

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
a database access
, but is not rate-limited.

Copilot Autofix

AI about 1 month ago

In general, to fix missing rate limiting on an expensive route, introduce a rate-limiting middleware (or reuse an existing one) and apply it to that route so that the number of accepted requests per client (or globally) is capped over a time window. This protects backing services (database, queues, external APIs) from overload due to bursts of requests.

In this file, the best fix with minimal functional change is to apply the existing apiRateLimiter middleware (already imported and used on /billing/checkout and /billing/portal) to the stripeWebhookHandler route. Since stripeWebhookHandler is currently exported as a bare function, the most consistent change—while staying within the snippet—is to wrap it in an Express router post definition that includes apiRateLimiter. However, the surrounding router wiring isn’t shown, so the smallest safe change is to define and export a new Express router that mounts the webhook handler with apiRateLimiter, and adjust the handler to be an Express-style middleware in that router. Within this snippet, we can convert stripeWebhookHandler from a standalone exported function to a billingRouter.post("/billing/webhook", authMiddleware?, apiRateLimiter, handler) style definition, mirroring the pattern used for the other routes. Because we must not assume changes in other files, we keep the handler logic intact and just add apiRateLimiter into the middleware chain for the webhook endpoint.

Concretely:

  • Locate the export async function stripeWebhookHandler definition near the end of apps/gateway/src/routes/billing.ts.
  • Replace that exported function with a billingRouter.post("/billing/webhook", apiRateLimiter, async (req, res) => { ... }), reusing the existing handler body unchanged.
  • If billingRouter is already defined earlier in the file (as implied by billingRouter.post("/billing/checkout", ...) and billingRouter.post("/billing/portal", ...)), no new definitions or imports are needed.
  • This ensures that every incoming webhook request passes through apiRateLimiter before executing the expensive RabbitMQ publishing logic.
Suggested changeset 1
apps/gateway/src/routes/billing.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/apps/gateway/src/routes/billing.ts b/apps/gateway/src/routes/billing.ts
--- a/apps/gateway/src/routes/billing.ts
+++ b/apps/gateway/src/routes/billing.ts
@@ -220,31 +220,37 @@
   }
 );
 
-export async function stripeWebhookHandler(req: Request, res: Response) {
-  const sig = req.headers["stripe-signature"] as string;
+billingRouter.post(
+  "/billing/webhook",
+  apiRateLimiter,
+  async (req: Request, res: Response) => {
+    const sig = req.headers["stripe-signature"] as string;
 
-  let event: Stripe.Event;
-  try {
-    event = stripe.webhooks.constructEvent(
-      req.body,
-      sig,
-      process.env.STRIPE_WEBHOOK_SECRET!
-    );
-  } catch (err: any) {
-    console.error("[billing] webhook signature failed:", err.message);
-    return res.status(400).send(`Webhook Error: ${err.message}`);
-  }
+    let event: Stripe.Event;
+    try {
+      event = stripe.webhooks.constructEvent(
+        req.body,
+        sig,
+        process.env.STRIPE_WEBHOOK_SECRET!
+      );
+    } catch (err: any) {
+      console.error("[billing] webhook signature failed:", err.message);
+      return res.status(400).send(`Webhook Error: ${err.message}`);
+    }
 
-  try {
-    await publishToStripeQueue({
-      stripeEventId: event.id,
-      stripeEventType: event.type,
-      payload: event.data.object,
-    });
-    console.log(`[billing] queued stripe event ${event.type} id=${event.id}`);
-    return res.json({ received: true });
-  } catch (err) {
-    console.error("[billing] failed to queue stripe event:", err);
-    return res.status(500).json({ error: "queue_publish_failed" });
+    try {
+      await publishToStripeQueue({
+        stripeEventId:  event.id,
+        stripeEventType: event.type,
+        payload:        event.data.object,
+      });
+      console.log(
+        `[billing] queued stripe event ${event.type} id=${event.id}`
+      );
+      return res.json({ received: true });
+    } catch (err) {
+      console.error("[billing] failed to queue stripe event:", err);
+      return res.status(500).json({ error: "queue_publish_failed" });
+    }
   }
-}
+);
EOF
@@ -220,31 +220,37 @@
}
);

export async function stripeWebhookHandler(req: Request, res: Response) {
const sig = req.headers["stripe-signature"] as string;
billingRouter.post(
"/billing/webhook",
apiRateLimiter,
async (req: Request, res: Response) => {
const sig = req.headers["stripe-signature"] as string;

let event: Stripe.Event;
try {
event = stripe.webhooks.constructEvent(
req.body,
sig,
process.env.STRIPE_WEBHOOK_SECRET!
);
} catch (err: any) {
console.error("[billing] webhook signature failed:", err.message);
return res.status(400).send(`Webhook Error: ${err.message}`);
}
let event: Stripe.Event;
try {
event = stripe.webhooks.constructEvent(
req.body,
sig,
process.env.STRIPE_WEBHOOK_SECRET!
);
} catch (err: any) {
console.error("[billing] webhook signature failed:", err.message);
return res.status(400).send(`Webhook Error: ${err.message}`);
}

try {
await publishToStripeQueue({
stripeEventId: event.id,
stripeEventType: event.type,
payload: event.data.object,
});
console.log(`[billing] queued stripe event ${event.type} id=${event.id}`);
return res.json({ received: true });
} catch (err) {
console.error("[billing] failed to queue stripe event:", err);
return res.status(500).json({ error: "queue_publish_failed" });
try {
await publishToStripeQueue({
stripeEventId: event.id,
stripeEventType: event.type,
payload: event.data.object,
});
console.log(
`[billing] queued stripe event ${event.type} id=${event.id}`
);
return res.json({ received: true });
} catch (err) {
console.error("[billing] failed to queue stripe event:", err);
return res.status(500).json({ error: "queue_publish_failed" });
}
}
}
);
Copilot is powered by AI and may make mistakes. Always verify output.
Copy link
Copy Markdown

@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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@scripts/load-tests/performance-budget.js`:
- Around line 85-87: The catch block currently declares catch (error) but never
uses it; either remove the unused binding by changing it to the optional catch
binding (catch { return true; }) or log the thrown error for debugging by
keeping catch (error) and adding a logging statement before returning (e.g.,
console.error or the existing logger) so the code becomes catch (error) {
console.error('Parse failed in performance budget check:', error); return true;
} — update the catch around the failing parse/validation in
performance-budget.js accordingly.
🪄 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: ASSERTIVE

Plan: Pro

Run ID: b05f96a9-7ae6-4e5f-b112-35c69e6fbeb5

📥 Commits

Reviewing files that changed from the base of the PR and between 1de9eb4 and 6b42b2f.

📒 Files selected for processing (2)
  • apps/gateway/src/routes/billing.ts
  • scripts/load-tests/performance-budget.js

Comment on lines +85 to 87
} catch (error) {
return true;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Unused error parameter in catch block.

The error parameter is captured but never used. Consider either:

  1. Logging the error for debugging parse failures during load tests, or
  2. Keeping the ES2019+ optional catch binding syntax (catch {) if the error is not needed
Option 1: Log the error for debugging
-  } catch (error) {
+  } catch (e) {
+    console.warn('GraphQL response parse failed:', e.message);
     return true;
   }
Option 2: Use optional catch binding (cleaner if error is not needed)
-  } catch (error) {
+  } catch {
     return true;
   }
📝 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.

Suggested change
} catch (error) {
return true;
}
} catch {
return true;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/load-tests/performance-budget.js` around lines 85 - 87, The catch
block currently declares catch (error) but never uses it; either remove the
unused binding by changing it to the optional catch binding (catch { return
true; }) or log the thrown error for debugging by keeping catch (error) and
adding a logging statement before returning (e.g., console.error or the existing
logger) so the code becomes catch (error) { console.error('Parse failed in
performance budget check:', error); return true; } — update the catch around the
failing parse/validation in performance-budget.js accordingly.

@pahuldeepp pahuldeepp merged commit 0768d69 into master Mar 29, 2026
58 of 61 checks passed
@pahuldeepp pahuldeepp deleted the fix/gateway-proto-path branch March 29, 2026 22:40
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