Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 17 additions & 36 deletions PULL_REQUEST_DESCRIPTION.md
Original file line number Diff line number Diff line change
@@ -1,50 +1,31 @@
# Pull Request Description

## Title
`feat(frontend): improve accessibility, enable optimistic user permissions, and enhance network status dashboard controls`
`feat(backend): add cryptographic signature verification to Audit Logger`

## Overview
This PR introduces frontend accessibility enhancements, state refactoring, and polished visual micro-interactions across the Pluto dashboard framework. It successfully addresses four frontend tasks (#821, #822, #823, and #824) while ensuring total system compile stability.

---
This PR implements cryptographic signature verification and payload integrity checks for the Audit Logger module during log retrieval. It enhances the platform's security posture and tamper-evidence guarantees, fulfilling the backend system optimization requirements.

## Detailed Changes

### 1. Network Status Indicator (`ApiHealthBadge.tsx`)
* **Interactive Controls**: Refactored the container from a passive `div` to a semantic `<button>` to enable keyboard focus and manual rechecking.
* **Optimistic Update**: Transitioning immediately to a `"loading"` state upon mouse click or Enter keypress for a responsive feedback loop, before initiating the real backend health endpoint check.
* **Advanced Accessibility (Screen Readers & Keyboard)**:
- Added descriptive `aria-live="polite"` tags to report status updates to screen readers dynamically.
- Linked detailed status/degradation alerts using `aria-describedby` referencing the hover/focus tooltip.
- Formulated precise dynamic `aria-label` definitions announcing the connection quality.
- Enabled tooltips on tab focus using standard Tailwind `group-focus-visible` styles.

### 2. Premium User Permissions Manager (`UserPermissionsManager.tsx` & `settings/page.tsx`)
* **Premium Settings Panel**: Integrated a new high-fidelity **Permissions & Team** tab under merchant settings complete with clean user group icons and layout selectors.
* **Optimistic State Actions & Rollbacks**:
- Implemented persistent browser storage (`localStorage`) synced with memory.
- Invites, role transitions, and member revoking are computed **optimistically**. Rows update instantly in the UI with state snapshots saved beforehand.
- In the event of a simulated API failure, the component runs automatic rollback procedures to revert the UI state cleanly and prompts the user via toast notifications.
* **Dynamic Animations**:
- Integrated Framer Motion's `<AnimatePresence>` to animate layout shifts, spring updates, and fade transitions during addition, removal, or filtering of rows.

### 3. Dashboard Webpack Compile Fix (`dashboard/page.tsx`)
* Corrected a pre-existing webpack path resolution error where `dashboard/page.tsx` attempted to load `WithdrawModal` instead of `WithdrawalModal`. The build compiles cleanly with no fatal errors or runtime blockages.

### 4. Code Cleanup & Lints (`WalletSelector.tsx`)
* Removed an unused `useMemo` React import that triggered pre-existing linter warnings.
### 1. Database Query Enhancements (`backend/src/services/auditService.js`)
* Updated the SQL SELECT query in `getAuditLogs` to retrieve necessary integrity fields: `merchant_id`, `status`, `payload_hash`, and `signature`.
* Preserved index utilization by ordering by `timestamp DESC` and filtering on `merchant_id` to prevent performance degradation on large tables.

---
### 2. Dual-Layer Log Integrity Verification (`backend/src/services/auditService.js`)
* Implemented payload reconstruction logic distinguishing between login attempt events and regular administrative events.
* Added deterministic SHA-256 hash comparison against the stored `payload_hash` to detect any field tampering.
* Implemented HMAC-SHA256 signature verification via `verifyAuditSignature` using constant-time comparison to guard against timing attacks.
* Exposes `hash_verified` and `signature_verified` flags for each log entry.
* Logs warnings/errors if any tampering is detected (e.g. hash or signature mismatch).

## Verification & Build Log
* **ESLint Check**: Passed with `✔ No ESLint warnings or errors`.
* **Next.js Production Compilation**: Verified via `pnpm run build` compiling `20/20` pages successfully with zero failures.
### 3. Comprehensive Unit Testing (`backend/src/services/auditService.test.js`)
* Added unit tests covering:
- Verification of matching payload hashes and signatures.
- Tamper detection with mismatching hashes and signatures.
- Graceful handling of legacy unsigned logs or missing environment secrets.

---

## Linked Issues

Closes #821
Closes #822
Closes #823
Closes #824
Closes #769
86 changes: 86 additions & 0 deletions backend/src/services/auditService.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ vi.mock("../lib/audit-security.js", () => ({
sanitizeAuditValue: vi.fn((v) => v),
signAuditPayload: mockSignPayload,
validateAuditAction: mockValidateAuditAction,
verifyAuditSignature: mockVerifySignature,
}));

import { auditService, _resetSvcCircuitForTests } from "./auditService.js";
Expand All @@ -41,6 +42,7 @@ describe("auditService", () => {
mockSignPayload.mockReset();
mockValidateAuditAction.mockReset();
mockValidateAuditAction.mockReturnValue(true);
mockVerifySignature.mockReset();
_resetSvcCircuitForTests();
});

Expand Down Expand Up @@ -167,6 +169,90 @@ describe("auditService", () => {
expect(result.page).toBe(1);
});

it("verifies matching payload hash and signature during retrieval", async () => {
process.env.AUDIT_LOG_SIGNING_SECRET = "test-secret";
mockQuery.mockResolvedValueOnce({
rows: [
{
id: "log-1",
merchant_id: "merchant-1",
action: "update",
field_changed: "email",
old_value: "a@b.com",
new_value: "c@d.com",
ip_address: "1.2.3.4",
user_agent: "ua",
timestamp: new Date(),
payload_hash: "calculated-hash",
signature: "valid-signature",
total_count: "1",
},
],
});

mockHashPayload.mockReturnValue("calculated-hash");
mockVerifySignature.mockReturnValue(true);

const result = await auditService.getAuditLogs("merchant-1", 1, 10);
expect(result.logs[0].hash_verified).toBe(true);
expect(result.logs[0].signature_verified).toBe(true);
});

it("detects mismatching/tampered hash and signature during retrieval", async () => {
process.env.AUDIT_LOG_SIGNING_SECRET = "test-secret";
mockQuery.mockResolvedValueOnce({
rows: [
{
id: "log-2",
merchant_id: "merchant-1",
action: "update",
field_changed: "email",
old_value: "a@b.com",
new_value: "c@d.com",
ip_address: "1.2.3.4",
user_agent: "ua",
timestamp: new Date(),
payload_hash: "calculated-hash",
signature: "invalid-signature",
total_count: "1",
},
],
});

mockHashPayload.mockReturnValue("different-hash");
mockVerifySignature.mockReturnValue(false);

const result = await auditService.getAuditLogs("merchant-1", 1, 10);
expect(result.logs[0].hash_verified).toBe(false);
expect(result.logs[0].signature_verified).toBe(false);
});

it("handles missing/null signatures or unset signing secret gracefully", async () => {
delete process.env.AUDIT_LOG_SIGNING_SECRET;
mockQuery.mockResolvedValueOnce({
rows: [
{
id: "log-3",
merchant_id: "merchant-1",
action: "update",
field_changed: "email",
old_value: "a@b.com",
new_value: "c@d.com",
ip_address: "1.2.3.4",
user_agent: "ua",
timestamp: new Date(),
payload_hash: null,
signature: null,
total_count: "1",
},
],
});

const result = await auditService.getAuditLogs("merchant-1", 1, 10);
expect(result.logs[0].hash_verified).toBeNull();
expect(result.logs[0].signature_verified).toBeNull();
});

// ── Action validation (issue #772) ────────────────────────────────────────

it("drops logEvent calls with disallowed action values", async () => {
Expand Down
Loading