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
90 changes: 90 additions & 0 deletions docs/COMPLIANCE_NON_TRANSACTIONAL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# FinTrack AI: Non-Transactional Boundary Compliance Specification

**Document Version:** 1.0.0
**Status:** Approved & Verified (SCRUM-162)
**System:** FinTrack AI Enterprise & Mobile Platform
**Architecture Classification:** Non-Custodial Financial Information Utility & Expense Ledger

---

## 1. Executive Summary & Purpose

FinTrack AI is an intelligent personal finance tracking, receipt OCR, group bill splitting, and corporate reimbursement approval ecosystem.

This document formally certifies that **FinTrack AI does NOT possess, facilitate, or execute any automated fund movements, bank wire payouts, automated debits, or customer money custody**.

By maintaining a strict **Non-Transactional Boundary**, FinTrack AI complies with the **Reserve Bank of India (RBI) Payment and Settlement Systems Act, 2007**, RBI Guidelines for Payment Aggregators (PA) and Payment Gateways (PG), and international standards (ISO 27001, SOC 2 Type II) by operating exclusively as an **Information Utility and Accounting Ledger**, completely exempt from Payment Aggregator licensing requirements.

---

## 2. Non-Custodial Architecture & Zero-Fund Custody Model

```text
[ FinTrack AI Ecosystem ] [ Real World Banking & Funds ]
┌───────────────────────────┐ ┌─────────────────────────────┐
│ Mobile App (Flutter) │ │ User's Bank Account │
│ • OCR Receipt Scanner │ │ • HDFC / ICICI / SBI / Axis │
│ • Statement Ingestion │ └──────────────┬──────────────┘
│ • Split Debt Ledger │ │
│ • Expense Categorization │ │ Direct UPI Intent
└─────────────┬─────────────┘ │ (Outside FinTrack)
│ ▼
▼ ┌─────────────────────────────┐
┌───────────────────────────┐ │ User's UPI App (3rd-Party) │
│ Backend API (Express.js) │ │ • GPay / PhonePe / Paytm │
│ • Tamper-Evident Ledger │ └─────────────────────────────┘
│ • Claim Status Machine │
│ • Non-Transactional Guard │
└─────────────┬─────────────┘ ┌─────────────────────────────┐
│ │ Corporate Payroll / Banking │
▼ │ • Direct salary disbursement│
┌───────────────────────────┐ │ • Net banking reimbursement │
│ Employer Web Dashboard │ └──────────────▲──────────────┘
│ • Review OCR Receipts │ │ Offline Processing
│ • Approve / Reject Claims ├──────────────────────────────────┘ (No in-app API wire)
└───────────────────────────┘
```

### Key Architectural Tenets:
1. **Zero Balance Custody:** FinTrack AI holds ₹0.00 of user or corporate funds at all times. There are no digital wallets, escrow accounts, or pooled nodal accounts.
2. **Non-Monetary Split Ledger:** Group bill splitting and debt settlements operate purely as a mathematical double-entry tracker. When a debt is marked settled, it records an external settlement confirmation (`"Settled via external UPI/Cash"`).
3. **Offline Corporate Reimbursement:** When a manager approves a corporate claim (`status: "Approved"`), the system stamps `settlementMethod: "OFFLINE_PAYROLL_EXTERNAL"` and `isFundMovementPrevented: true`. No API calls are made to banking or payment gateway payout endpoints. Payouts are handled via existing corporate payroll cycles.
4. **Third-Party UPI Hand-Off:** Any payment link generated uses standard client-side URI schemes (`upi://pay`) that hand off payment execution to authorized NPCI/RBI banking applications on the user's device. FinTrack AI never sees banking credentials, MPINs, or account balances.

---

## 3. Technical Safeguards & Boundary Controls (SCRUM-161)

| Control Layer | Safeguard Implementation | Action on Violation |
| :--- | :--- | :--- |
| **Network & Middleware** | `nonTransactionalSafeguard` ([safeguard.middleware.js](../src/middlewares/safeguard.middleware.js)) intercepts all HTTP requests. | Rejects with **HTTP 403 Forbidden** (`NON_TRANSACTIONAL_VIOLATION`) and logs a tamper-evident audit event. |
| **Forbidden Key Inspection** | Recursively scans payloads for payout parameters (`payoutMethod`, `beneficiaryAccount`, `beneficiaryIfsc`, `initiateTransfer`, `autoDebitMandate`, `disburseFunds`, `paymentGatewaySecret`, etc.). | Parameter rejection; prevents payout payload injection. |
| **Dependency Lock** | [package.json](../package.json) is audited to enforce zero payment gateway payout SDKs (Razorpay, Stripe, Cashfree, PayPal, Dwolla). | Automated test failure in CI/CD pipeline if payout SDK is introduced. |
| **Domain State Machine** | [claim.service.js](../src/services/claim.service.js) explicitly stamps `settlementMethod: 'OFFLINE_PAYROLL_EXTERNAL'` and `isFundMovementPrevented: true` upon approval. | Enforces that claim approval is strictly an accounting approval, not a payout trigger. |
| **Cryptographic Audit Trail** | [audit.service.js](../src/services/audit.service.js) records all state changes in an immutable SHA-256 hash-chained log. | Non-repudiation and forensic auditability. |

---

## 4. Regulatory Mapping Matrix

| Regulation / Framework | Regulatory Mandate | FinTrack AI Compliance Architecture |
| :--- | :--- | :--- |
| **RBI Payment & Settlement Systems Act, 2007** | Entity handling customer funds or settling transactions requires authorization as a Payment System Operator (PSO) or Payment Aggregator (PA). | **Exempt:** FinTrack AI does not handle, clear, or settle funds. It is an information utility / record-keeping software. |
| **RBI Master Directions on Digital Lending** | Disbursal of loans and repayments must execute directly between borrower and regulated entity bank accounts without intermediary pooling. | **Compliant:** No pooling accounts exist; all settlements occur directly between individuals or via enterprise corporate payroll. |
| **Digital Personal Data Protection (DPDP) Act, 2023** | Explicit consent required for financial and personal data processing. | **Compliant:** Granular DPDP consent service ([consent.service.js](../src/services/consent.service.js)) logs consent grant and revocation in tamper-evident audit logs. |
| **App Store Financial Guidelines (Apple & Google)** | Financial apps offering money transfer must hold banking or money transmitter licenses. | **Compliant:** Clearly documented as an expense ledger and bill scanner with non-transactional boundary safeguards. |

---

## 5. Verification & Continuous Compliance (SCRUM-160)

FinTrack AI includes automated test verification suite ([non_transactional.test.js](../../tests/non_transactional.test.js)) running on every build:
1. **Dependency Audit Test:** Asserts zero payment gateway SDKs exist in production or development dependencies.
2. **Route Pattern Audit Test:** Asserts that none of the registered Express route paths contain `/payout`, `/disburse`, `/wire`, `/charge`, or `/withdraw`.
3. **Runtime Interception Test:** Verifies that attempting to send banking payout or auto-debit payloads returns HTTP 403 Forbidden with security audit logging.
4. **State Machine Stamp Test:** Verifies that approved corporate claims are stamped with `OFFLINE_PAYROLL_EXTERNAL` and fund-movement prevention flags.

---

*Certified by:* FinTrack AI Architecture & Compliance Team
*Date of Enforcement:* September 2026
25 changes: 23 additions & 2 deletions src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,27 @@ import helmet from 'helmet';
import morgan from 'morgan';
import routes from './routes/index.js';
import { notFoundHandler, errorHandler } from './middlewares/error.middleware.js';
import { nonTransactionalSafeguard } from './middlewares/safeguard.middleware.js';
import { nonPrivilegedMaskingMiddleware } from './middlewares/masking.middleware.js';
import { tlsGuard } from './middlewares/tlsGuard.middleware.js';
import { config } from './config/env.js';

const app = express();

// Security HTTP headers
app.use(helmet({ crossOriginResourcePolicy: { policy: 'cross-origin' } }));
// Security HTTP headers with TLS 1.2+ HSTS enforcement (SCRUM-151)
app.use(
helmet({
crossOriginResourcePolicy: { policy: 'cross-origin' },
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true,
},
})
);

// SCRUM-151: Ingress TLS 1.2+ transport security guard
app.use(tlsGuard);

// Enable CORS (supports mobile apps, localhost, and configured client domains)
const allowedOrigins = (config.clientUrl || '*')
Expand Down Expand Up @@ -55,6 +70,12 @@ if (config.nodeEnv === 'development') {
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// SCRUM-161: Non-Transactional Boundary Safeguard (blocks fund movement & payout attempts)
app.use(nonTransactionalSafeguard);

// SCRUM-148: Non-Privileged View & Response Data Masking
app.use(nonPrivilegedMaskingMiddleware);

// Root health check for GCP Cloud Run and load balancers
app.get('/health', (req, res) => {
res.status(200).json({
Expand Down
16 changes: 14 additions & 2 deletions src/config/db.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,27 @@
import mongoose from 'mongoose';
import { config } from './env.js';

export const getMongoTlsOptions = () => ({
tls: true,
minTLSVersion: 'TLSv1.2',
});

export const connectDB = async () => {
if (!config.mongoUri) {
console.log('[DB Warning] MONGO_URI is not defined in environment settings.');
return;
}

const isRemote =
config.mongoUri.includes('mongodb+srv') ||
config.mongoUri.includes('ssl=true') ||
config.nodeEnv === 'production';

const connectionOptions = isRemote ? getMongoTlsOptions() : {};

try {
const conn = await mongoose.connect(config.mongoUri);
console.log(`[DB] MongoDB Atlas connected successfully: ${conn.connection.host}`);
const conn = await mongoose.connect(config.mongoUri, connectionOptions);
console.log(`[DB] MongoDB Atlas connected successfully (TLS 1.2+): ${conn.connection.host}`);
} catch (error) {
console.error(`[DB Error] Failed to connect to MongoDB: ${error.message}`);
// Non-fatal fallback for test environments without network access
Expand Down
65 changes: 65 additions & 0 deletions src/controllers/audit.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { auditService } from '../services/audit.service.js';
import { ApiResponse } from '../utils/apiResponse.js';

export const auditController = {
/**
* GET /api/v1/audit/verify
* Cryptographically verifies the audit log chain from end to end (SCRUM-166).
*/
verifyChain: async (req, res) => {
try {
const fromSequence = req.query.from ? parseInt(req.query.from, 10) : 1;
const toSequence = req.query.to ? parseInt(req.query.to, 10) : null;

const result = await auditService.verifyAuditChain({
fromSequence,
toSequence,
});

if (!result.isValid) {
return ApiResponse.error(
res,
'Audit log cryptographic chain verification failed! Tampering detected.',
409,
result
);
}

return ApiResponse.success(
res,
'Audit log cryptographic chain is valid and untampered.',
result
);
} catch (err) {
console.error('[AuditController verifyChain Error]:', err);
return ApiResponse.error(res, err.message, 500);
}
},

/**
* GET /api/v1/audit/logs
* Retrieves paginated audit logs with optional filters.
*/
getLogs: async (req, res) => {
try {
const page = req.query.page ? parseInt(req.query.page, 10) : 1;
const limit = req.query.limit ? parseInt(req.query.limit, 10) : 50;
const { eventType, action, userId, resourceType, resourceId } = req.query;

const logsData = await auditService.getAuditLogs({
page,
limit,
eventType,
action,
userId,
resourceType,
resourceId,
});

return ApiResponse.success(res, 'Audit logs retrieved successfully.', logsData);
} catch (err) {
console.error('[AuditController getLogs Error]:', err);
return ApiResponse.error(res, err.message, 500);
}
},
};
24 changes: 24 additions & 0 deletions src/controllers/auth.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,3 +116,27 @@ export const getProfile = async (req, res, next) => {
next(error);
}
};

/**
* DELETE /api/v1/auth/account
* SCRUM-157: DPDP Act Right to Erasure / User-Requested Early Account Deletion
*/
export const deleteAccount = async (req, res, next) => {
try {
const userId = req.user?.id;
if (!userId) {
return ApiResponse.error(res, 'Authentication required to delete account', 401);
}

const { retentionService } = await import('../services/retention.service.js');
const result = await retentionService.executeUserEarlyDeletion(userId, {
userId,
role: req.user?.role || 'USER',
ip: req.ip,
});

return ApiResponse.success(res, result.message, result.data);
} catch (error) {
next(error);
}
};
4 changes: 2 additions & 2 deletions src/controllers/claim.controller.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { claimService } from '../services/claim.service.js';
import { storageService } from '../services/storage.service.js';
import { ApiResponse } from '../utils/apiResponse.js';
import fs from 'fs';

export const submitClaim = async (req, res, next) => {
try {
Expand Down Expand Up @@ -147,7 +147,7 @@ export const getClaimReceiptImage = async (req, res, next) => {
res.setHeader('Cache-Control', 'public, max-age=86400');
res.setHeader('Content-Disposition', `inline; filename="${originalName}"`);

const stream = fs.createReadStream(filePath);
const stream = storageService.getFileStream(filePath);
stream.pipe(res);
} catch (error) {
if (error.statusCode === 404) {
Expand Down
38 changes: 38 additions & 0 deletions src/controllers/retention.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { retentionService } from '../services/retention.service.js';
import { ApiResponse } from '../utils/apiResponse.js';

export const retentionController = {
/**
* POST /api/v1/retention/purge-job
* Triggers the automated data retention purge job (SCRUM-156).
*/
triggerPurge: async (req, res) => {
try {
const { dryRun, targetDate } = req.body || {};

const result = await retentionService.runPurgeJob({
dryRun: Boolean(dryRun),
targetDate: targetDate ? new Date(targetDate) : new Date(),
});

return ApiResponse.success(res, 'Purge job executed successfully.', result);
} catch (err) {
console.error('[RetentionController triggerPurge Error]:', err);
return ApiResponse.error(res, err.message, 500);
}
},

/**
* GET /api/v1/retention/metrics
* Retrieves retention counts and statutory status.
*/
getMetrics: async (req, res) => {
try {
const metrics = await retentionService.getRetentionMetrics();
return ApiResponse.success(res, 'Retention compliance metrics retrieved.', metrics);
} catch (err) {
console.error('[RetentionController getMetrics Error]:', err);
return ApiResponse.error(res, err.message, 500);
}
},
};
77 changes: 77 additions & 0 deletions src/middlewares/audit.middleware.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { auditService } from '../services/audit.service.js';

/**
* Express Middleware for logging sensitive data access events (SCRUM-165).
* Captures reader identity, target resource, client IP, and user-agent.
* Executes asynchronously to avoid adding latency to read operations.
*
* @param {string} resourceType - e.g. 'CLAIM', 'RECEIPT', 'STATEMENT', 'USER_PROFILE'
* @param {string} [actionName] - Custom action label, defaults to `${resourceType}_ACCESSED`
* @param {Function} [resolveResourceId] - Custom callback to extract resource ID from req
*/
export const auditDataAccess = (
resourceType,
actionName = null,
resolveResourceId = null
) => {
return (req, res, next) => {
// Determine action name
const action = actionName || `${resourceType}_ACCESSED`;

// Extract target ID
let resourceId = '';
if (typeof resolveResourceId === 'function') {
try {
resourceId = resolveResourceId(req);
} catch {
resourceId = '';
}
} else if (req.params.claimId) {
resourceId = req.params.claimId;
} else if (req.params.id) {
resourceId = req.params.id;
} else if (req.params.userId) {
resourceId = req.params.userId;
}

// Resolve client IP
const clientIp =
req.headers['x-forwarded-for']?.split(',')[0].trim() ||
req.socket?.remoteAddress ||
req.ip ||
'';

const actor = {
userId: req.user?.id || req.user?.userId || 'anonymous',
role: req.user?.role || (req.user?.id ? 'USER' : 'ANONYMOUS'),
email: req.user?.email || '',
ip: clientIp,
userAgent: req.headers['user-agent'] || '',
};

const target = {
resourceType,
resourceId: String(resourceId || 'collection'),
};

const metadata = {
path: req.originalUrl || req.url,
method: req.method,
query: req.query || {},
};

// Log event in background to prevent read latency
auditService
.logDataAccess({
action,
actor,
target,
metadata,
})
.catch((err) => {
console.warn(`[Audit Middleware Warning]: Could not log access to ${resourceType}:`, err.message);
});

next();
};
};
Loading
Loading