Skip to content

Conversation

@wanwiset25
Copy link

@wanwiset25 wanwiset25 commented Jan 20, 2026

Add admin APIs to gather nodes information in JSON format. The request must supply the correct ADMIN_SECRET in "x-api-secret" header.

/v2/nodes_info
/v2/nodes_info_verbose
/v2/coinbase_info

Summary by CodeRabbit

  • New Features
    • Added three new API endpoints for retrieving node information: /nodes_info, /nodes_info_verbose, and /coinbase_info
    • Added new API endpoint /forensics/load/latest for retrieving the latest forensics report
    • Introduced API authentication mechanism for protected endpoints using a secret header

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

@coderabbitai
Copy link

coderabbitai bot commented Jan 20, 2026

📝 Walkthrough

Walkthrough

The pull request introduces authenticated node information endpoints to the API. A new configuration field stores the admin secret, while a new route module implements three endpoints that retrieve node data with varying detail levels, protected by header-based authentication.

Changes

Cohort / File(s) Summary
Configuration
config.js
Added adminSecret public configuration field with empty string default; minor formatting adjustment to masternodeUrl
Route Registration
lib/routes/index.js
Imported three node info handlers from new nodesinfo module; registered four new GET endpoints: /nodes_info, /nodes_info_verbose, /coinbase_info, and /forensics/load/latest
Node Info Module
lib/routes/nodesinfo.js
New module implementing three authenticated endpoint handlers (getNodesCoinbase, getNodesInfo, getNodesInfoVerbose) with x-api-secret header validation, collection data retrieval, field filtering, and structured error responses (401 for auth failure, 500 for server errors)

Sequence Diagram(s)

sequenceDiagram
    actor Client
    participant API as API Server
    participant Auth as Auth Check
    participant DB as Node Collection
    participant Formatter as Response

    Client->>API: GET /nodes_info (x-api-secret header)
    API->>Auth: checkAuth(req)
    alt Secret Valid
        Auth-->>API: ✓ Authenticated
        API->>DB: getCollection().all()
        DB-->>API: nodes array
        API->>Formatter: Filter fields (name, node, coinbase, ip)
        Formatter->>Formatter: Calculate stats (active, mining, peers, pending)
        Formatter-->>Client: 200 JSON {filtered data, count, stats}
    else Secret Invalid/Missing
        Auth-->>API: ✗ Auth Failed
        API-->>Client: 401 Unauthorized JSON
    else Collection Error
        DB-->>API: Exception
        API-->>Client: 500 Internal Server Error JSON
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 Hop hop, new secrets to keep!
Admin access, running deep,
Node info endpoints, three in a row,
Data flowing where admins know,
Auth gates standing, secure and tight! 🔐

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add dashboard admin api' accurately describes the main change: three new authenticated admin API endpoints for retrieving node information.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing touches
  • 📝 Generate docstrings

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

@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

🤖 Fix all issues with AI agents
In `@lib/routes/nodesinfo.js`:
- Around line 108-118: Replace the direct string comparison in checkAuth with a
timing-safe check: ensure both providedSecret and config.adminSecret exist,
convert them to Buffers (e.g., Buffer.from(..., 'utf8')), and use
crypto.timingSafeEqual to compare them while guarding against different lengths
(return false if buffer lengths differ) so timingSafeEqual is only called on
equal-length buffers; keep the existing early-false behavior when adminSecret or
providedSecret is missing.
- Around line 85-93: The verbose endpoint currently returns raw node objects
(from getCollection() -> Nodes.all()) including internal fields like spark;
change getNodesInfoVerbose to transform/map allNodes to only include the safe
API fields used by getNodesCoinbase/getNodesInfo (e.g., id, host, port, status,
coinbase, etc.) and explicitly omit internal/socket fields such as spark before
calling sendPrettyJson(res, 200, ...); follow the same field whitelist/shape
used in getNodesCoinbase/getNodesInfo to ensure consistent, serializable
responses.
🧹 Nitpick comments (3)
lib/routes/nodesinfo.js (2)

1-1: Remove unused import.

The get function is imported from grunt but never used in this file. This appears to be dead code.

Suggested fix
-const { get } = require("grunt");
 const { config } = require("../../config");
 const { getCollection } = require("../collection");

5-5: Handlers are declared async but don't use await.

These handler functions are declared async but contain no await expressions. Since getCollection().all() appears synchronous, the async keyword is unnecessary. This is functionally harmless but slightly misleading.

Also applies to: 36-36, 77-77

lib/routes/index.js (1)

13-15: Consider wrapping routes with asyncWrapper for consistency.

The new routes don't use asyncWrapper, unlike the forensics routes (lines 17-20). While the handlers have internal try/catch blocks that should handle errors, wrapping them with asyncWrapper would provide a consistent error-handling pattern and an additional safety net for any uncaught exceptions.

Suggested change for consistency
-router.get('/nodes_info', getNodesInfo);
-router.get('/nodes_info_verbose', getNodesInfoVerbose);
-router.get('/coinbase_info', getNodesCoinbase);
+router.get('/nodes_info', asyncWrapper(getNodesInfo));
+router.get('/nodes_info_verbose', asyncWrapper(getNodesInfoVerbose));
+router.get('/coinbase_info', asyncWrapper(getNodesCoinbase));

Comment on lines +85 to +93
try {
const Nodes = getCollection();
const allNodes = Nodes.all();

sendPrettyJson(res, 200, {
success: true,
count: allNodes.length,
nodes: allNodes,
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, check what fields exist on node objects
rg -n "node\." lib/collection.js -A2 -B2 | head -100

Repository: XinFinOrg/XDCStats

Length of output: 1519


🏁 Script executed:

# Also check the full context of the getNodesInfoVerbose endpoint including auth
cat -n lib/routes/nodesinfo.js | head -100

Repository: XinFinOrg/XDCStats

Length of output: 3288


🏁 Script executed:

# Look for node object creation/definition to understand its structure
rg -n "this\..*=.*node" lib/ -A1 | head -60

Repository: XinFinOrg/XDCStats

Length of output: 44


🏁 Script executed:

# Find Node class definition
find lib -name "*.js" -type f | xargs grep -l "class Node\|function Node" | head -5

Repository: XinFinOrg/XDCStats

Length of output: 44


🏁 Script executed:

# Search for node object property definitions
rg -n "this\.[a-zA-Z]+ =" lib/node.js -A1 | head -80

Repository: XinFinOrg/XDCStats

Length of output: 720


🏁 Script executed:

# Also check if there are internal/socket references in nodes
rg -n "socket|connection|ws|WebSocket" lib/node.js | head -30

Repository: XinFinOrg/XDCStats

Length of output: 44


🏁 Script executed:

# Check what 'spark' is in the context
rg -n "spark" lib/node.js -B2 -A2

Repository: XinFinOrg/XDCStats

Length of output: 149


🏁 Script executed:

# Also check if spark is ever used elsewhere or initialized
rg -n "spark" lib/ -t js | head -20

Repository: XinFinOrg/XDCStats

Length of output: 317


🏁 Script executed:

# Check what JSON.stringify or serialization might do with socket objects
rg -n "JSON.stringify|sendPrettyJson" lib/routes/nodesinfo.js -B2 -A2

Repository: XinFinOrg/XDCStats

Length of output: 1890


🏁 Script executed:

# Check if Node class has a toJSON method for serialization
rg -n "toJSON" lib/node.js

Repository: XinFinOrg/XDCStats

Length of output: 44


🏁 Script executed:

# Check the full Node class to understand all properties
cat -n lib/node.js | head -120

Repository: XinFinOrg/XDCStats

Length of output: 3024


🏁 Script executed:

# Check if spark is ever used/accessed in responses or if it's just stored but not used
rg -n "node.spark|\.spark" lib/routes/ -A2 -B2

Repository: XinFinOrg/XDCStats

Length of output: 44


🏁 Script executed:

# Verify what JSON.stringify does with the spark connection object by checking if there are any serialization guards
rg -n "replacer|toJSON|stringify" lib/ -t js | head -20

Repository: XinFinOrg/XDCStats

Length of output: 244


🏁 Script executed:

# Let's verify what spark actually is by checking the collection initialization
rg -n "spark" lib/collection.js -B5 -A5 | head -50

Repository: XinFinOrg/XDCStats

Length of output: 599


Filter sensitive internal fields from the verbose endpoint response.

The getNodesInfoVerbose endpoint returns raw node objects including the spark field, which is a WebSocket connection object used internally for socket management. This is an implementation detail that shouldn't be exposed via the API and may cause serialization issues. Follow the pattern used in getNodesCoinbase and getNodesInfo by explicitly filtering the node objects to include only safe, API-intended fields before sending the response.

🤖 Prompt for AI Agents
In `@lib/routes/nodesinfo.js` around lines 85 - 93, The verbose endpoint currently
returns raw node objects (from getCollection() -> Nodes.all()) including
internal fields like spark; change getNodesInfoVerbose to transform/map allNodes
to only include the safe API fields used by getNodesCoinbase/getNodesInfo (e.g.,
id, host, port, status, coinbase, etc.) and explicitly omit internal/socket
fields such as spark before calling sendPrettyJson(res, 200, ...); follow the
same field whitelist/shape used in getNodesCoinbase/getNodesInfo to ensure
consistent, serializable responses.

Comment on lines +108 to +118
const checkAuth = (req) => {
const providedSecret = req.headers["x-api-secret"];
if (
!config.adminSecret ||
!providedSecret ||
providedSecret !== config.adminSecret
) {
return false;
}
return true;
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Use timing-safe comparison for secret validation.

Direct string comparison (!==) is vulnerable to timing attacks. An attacker could potentially measure response times to infer the secret character by character. Use crypto.timingSafeEqual for constant-time comparison.

Suggested fix
+const crypto = require("crypto");
 const { config } = require("../../config");
 const { getCollection } = require("../collection");
 const checkAuth = (req) => {
   const providedSecret = req.headers["x-api-secret"];
-  if (
-    !config.adminSecret ||
-    !providedSecret ||
-    providedSecret !== config.adminSecret
-  ) {
+  if (!config.adminSecret || !providedSecret) {
     return false;
   }
-  return true;
+  try {
+    return crypto.timingSafeEqual(
+      Buffer.from(providedSecret),
+      Buffer.from(config.adminSecret)
+    );
+  } catch {
+    return false;
+  }
 };
📝 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 checkAuth = (req) => {
const providedSecret = req.headers["x-api-secret"];
if (
!config.adminSecret ||
!providedSecret ||
providedSecret !== config.adminSecret
) {
return false;
}
return true;
};
const checkAuth = (req) => {
const providedSecret = req.headers["x-api-secret"];
if (!config.adminSecret || !providedSecret) {
return false;
}
try {
return crypto.timingSafeEqual(
Buffer.from(providedSecret),
Buffer.from(config.adminSecret)
);
} catch {
return false;
}
};
🤖 Prompt for AI Agents
In `@lib/routes/nodesinfo.js` around lines 108 - 118, Replace the direct string
comparison in checkAuth with a timing-safe check: ensure both providedSecret and
config.adminSecret exist, convert them to Buffers (e.g., Buffer.from(...,
'utf8')), and use crypto.timingSafeEqual to compare them while guarding against
different lengths (return false if buffer lengths differ) so timingSafeEqual is
only called on equal-length buffers; keep the existing early-false behavior when
adminSecret or providedSecret is missing.

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.

3 participants