Conversation
- Add .cursorrules with repo summary, Node backend rules, security and workflow guidance - Add .cursor/agents for: node_backend, openhands_migrated_agents, auth0, codereview, security, github, schema_generator, feature_lifecycle - Preserve .openhands/ unchanged; mirror agents for Cursor discovery - Ensure Cursor understands stack, layout, and conventions for MWAP backend
…, .cursor/, .specstory/)
There was a problem hiding this comment.
Pull Request Overview
This refactoring pull request updates import/export statements across the codebase from .js to .ts extensions, adds comprehensive testing infrastructure, and implements security hardening improvements. The changes focus on modernizing the TypeScript module system while maintaining backward compatibility and adding production-ready safeguards.
- Updated import statements from
.jsto.tsextensions across test files and OpenAPI endpoints - Implemented minimal Heroku-optimized testing strategy with critical test suites and release-phase validation
- Added input sanitization, database indexing, and production error handling improvements
Reviewed Changes
Copilot reviewed 38 out of 42 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/utils/tenants.service.test.ts | New unit tests for tenant service with mocking setup |
| tests/utils/projects.service.test.ts | New unit tests for projects service functionality |
| tests/setupTests.ts | Expanded test setup with comprehensive mocking and service stubs |
| tests/openapiendpoint/*.ts | Updated import paths from .js to .ts extensions |
| tests/oauth/oauthCallbackSecurity.test.ts | Fixed import paths and improved async test handling |
| tests/middleware/auth.test.ts | Updated imports and enhanced test reliability |
| tests/integration/test_cloud_providers.py | Simplified to basic endpoint protection validation |
| src/utils/validate.ts | Added string sanitization function |
| src/utils/logger.ts | Added correlation ID generation for better traceability |
| src/schemas/*.ts | Applied input sanitization to string fields |
| src/middleware/validateRequest.ts | New reusable validation middleware |
| src/middleware/errorHandler.ts | Added production-safe error logging |
| src/middleware/authorization.ts | Added unified permission checking system |
| src/features/projects/projects.service.ts | Fixed ObjectId query usage |
| src/config/env.ts | Removed debug logging for production safety |
| src/app.ts | Enhanced rate limiting with endpoint-specific rules |
| scripts/create-indexes.ts | New database index creation script |
| package.json | Added critical test runner and index creation to build |
| docs/**/*.md | Updated documentation to reflect current architecture |
| .cursorrules | Added comprehensive development guidelines |
| Procfile | Added release-phase validation gate |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
| db.collection.mockReturnValueOnce({ findOne: vi.fn().mockResolvedValue(null) }) // owner check | ||
| .mockReturnValueOnce({ findOne: vi.fn().mockResolvedValue(null), insertOne: vi.fn().mockResolvedValue({ acknowledged: true }) }); // name check + insert |
There was a problem hiding this comment.
The chained mock setup is difficult to read and maintain. Consider extracting the mock configurations into named variables or helper functions to improve clarity and reusability.
| db.collection.mockReturnValueOnce({ findOne: vi.fn().mockResolvedValue(null) }) // owner check | |
| .mockReturnValueOnce({ findOne: vi.fn().mockResolvedValue(null), insertOne: vi.fn().mockResolvedValue({ acknowledged: true }) }); // name check + insert | |
| // Extracted mock configurations for clarity | |
| const ownerCheckMock = { findOne: vi.fn().mockResolvedValue(null) }; | |
| const nameCheckAndInsertMock = { | |
| findOne: vi.fn().mockResolvedValue(null), | |
| insertOne: vi.fn().mockResolvedValue({ acknowledged: true }) | |
| }; | |
| db.collection | |
| .mockReturnValueOnce(ownerCheckMock) // owner check | |
| .mockReturnValueOnce(nameCheckAndInsertMock); // name check + insert |
| }); | ||
|
|
||
| // Ensure mocks also match other specifiers used by tests/services | ||
| vi.mock('../../src/config/db', () => { |
There was a problem hiding this comment.
There are three identical mock implementations for the same module with different specifiers ('../../src/config/db', '../../src/config/db.js', '../../src/config/db'). This code duplication should be consolidated into a single mock definition or extracted into a shared helper function.
| return { getDB: () => db, db }; | ||
| }); | ||
|
|
||
| vi.mock('../../src/config/db.js', () => { |
There was a problem hiding this comment.
There are three identical mock implementations for the same module with different specifiers ('../../src/config/db', '../../src/config/db.js', '../../src/config/db'). This code duplication should be consolidated into a single mock definition or extracted into a shared helper function.
| vi.mock('../../src/utils/logger', () => ({ | ||
| logInfo: vi.fn(), | ||
| logError: vi.fn(), | ||
| logAudit: vi.fn() | ||
| })); | ||
|
|
||
| vi.mock('../../src/features/cloud-integrations/cloudIntegrations.service.js', () => ({ | ||
| vi.mock('../../src/features/cloud-integrations/cloudIntegrations.service', () => ({ | ||
| CloudIntegrationsService: vi.fn().mockImplementation(() => ({ | ||
| findById: vi.fn() | ||
| })) | ||
| })); | ||
|
|
||
| vi.mock('../../src/features/cloud-providers/cloudProviders.service.js', () => ({ | ||
| vi.mock('../../src/features/cloud-providers/cloudProviders.service', () => ({ |
There was a problem hiding this comment.
Mock paths are inconsistent - some use full paths with extensions, others don't. For consistency and maintainability, standardize the mock path format throughout the test files.
| vi.mock('../../src/utils/logger', () => ({ | ||
| logInfo: vi.fn(), | ||
| logError: vi.fn(), | ||
| logAudit: vi.fn() | ||
| })); | ||
|
|
||
| vi.mock('../../src/features/cloud-integrations/cloudIntegrations.service.js', () => ({ | ||
| vi.mock('../../src/features/cloud-integrations/cloudIntegrations.service', () => ({ | ||
| CloudIntegrationsService: vi.fn().mockImplementation(() => ({ | ||
| findById: vi.fn() | ||
| })) | ||
| })); | ||
|
|
||
| vi.mock('../../src/features/cloud-providers/cloudProviders.service.js', () => ({ | ||
| vi.mock('../../src/features/cloud-providers/cloudProviders.service', () => ({ |
There was a problem hiding this comment.
Mock paths are inconsistent - some use full paths with extensions, others don't. For consistency and maintainability, standardize the mock path format throughout the test files.
| expect(logAudit).toHaveBeenCalledWith( | ||
| 'auth.failed', | ||
| undefined, | ||
| 'unknown', |
There was a problem hiding this comment.
The test expects 'unknown' as the actor parameter, but this should likely be 'undefined' or the actual user ID when available. The hardcoded 'unknown' string may not match the actual behavior in production.
| 'unknown', | |
| undefined, |
|
|
||
| export function sanitizeString(input: string): string { | ||
| return input.trim().replace(/[<>]/g, ''); |
There was a problem hiding this comment.
The sanitization function only removes angle brackets but may not be sufficient for comprehensive XSS prevention. Consider using a more robust sanitization library or expanding the character filtering to include other potentially dangerous characters like quotes, scripts, and HTML entities.
| export function sanitizeString(input: string): string { | |
| return input.trim().replace(/[<>]/g, ''); | |
| import sanitizeHtml from 'sanitize-html'; | |
| export function sanitizeString(input: string): string { | |
| return sanitizeHtml(input.trim(), { allowedTags: [], allowedAttributes: {} }); |
| function getCorrelationId(): string { | ||
| // Simple correlation ID generator; can be extended to read from headers later | ||
| return `req_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`; | ||
| } |
There was a problem hiding this comment.
Generating a new correlation ID for every log entry can be inefficient and may not provide proper request tracing. Consider generating the correlation ID once per request (e.g., in middleware) and passing it through the request context instead of creating a new one for each log call.
| if (permission.startsWith('tenant:')) { | ||
| // For tenant-scoped permissions, we reuse existing ownership checks | ||
| // and superadmin bypass where applicable. | ||
| const param = req.params.tenantId || req.params.id; |
There was a problem hiding this comment.
[nitpick] Using fallback parameter selection (tenantId || id) makes the permission logic implicit and harder to debug. Consider making the parameter name explicit or documenting which routes use which parameter pattern.
| tasks.push(db.collection('tenants').createIndex({ ownerId: 1 }, { name: 'tenants_ownerId_idx', unique: true }).catch(() => {})); | ||
|
|
||
| // Projects: tenant and members lookups | ||
| tasks.push(db.collection('projects').createIndex({ tenantId: 1 }, { name: 'projects_tenantId_idx' }).catch(() => {})); | ||
| tasks.push(db.collection('projects').createIndex({ 'members.userId': 1 }, { name: 'projects_members_userId_idx' }).catch(() => {})); | ||
|
|
||
| // Superadmins: presence check | ||
| tasks.push(db.collection('superadmins').createIndex({ userId: 1 }, { name: 'superadmins_userId_idx', unique: true }).catch(() => {})); |
There was a problem hiding this comment.
Silently catching and ignoring all errors with .catch(() => {}) can hide important indexing failures. Consider logging the errors or at least distinguishing between 'already exists' errors and actual failures.
| tasks.push(db.collection('tenants').createIndex({ ownerId: 1 }, { name: 'tenants_ownerId_idx', unique: true }).catch(() => {})); | |
| // Projects: tenant and members lookups | |
| tasks.push(db.collection('projects').createIndex({ tenantId: 1 }, { name: 'projects_tenantId_idx' }).catch(() => {})); | |
| tasks.push(db.collection('projects').createIndex({ 'members.userId': 1 }, { name: 'projects_members_userId_idx' }).catch(() => {})); | |
| // Superadmins: presence check | |
| tasks.push(db.collection('superadmins').createIndex({ userId: 1 }, { name: 'superadmins_userId_idx', unique: true }).catch(() => {})); | |
| tasks.push( | |
| db.collection('tenants') | |
| .createIndex({ ownerId: 1 }, { name: 'tenants_ownerId_idx', unique: true }) | |
| .catch((err) => { | |
| // Ignore "already exists" errors, log others | |
| if (!(err && (err.code === 85 || (typeof err.message === 'string' && err.message.includes('already exists'))))) { | |
| // eslint-disable-next-line no-console | |
| console.error('Error creating tenants_ownerId_idx:', err); | |
| } | |
| }) | |
| ); | |
| // Projects: tenant and members lookups | |
| tasks.push( | |
| db.collection('projects') | |
| .createIndex({ tenantId: 1 }, { name: 'projects_tenantId_idx' }) | |
| .catch((err) => { | |
| if (!(err && (err.code === 85 || (typeof err.message === 'string' && err.message.includes('already exists'))))) { | |
| // eslint-disable-next-line no-console | |
| console.error('Error creating projects_tenantId_idx:', err); | |
| } | |
| }) | |
| ); | |
| tasks.push( | |
| db.collection('projects') | |
| .createIndex({ 'members.userId': 1 }, { name: 'projects_members_userId_idx' }) | |
| .catch((err) => { | |
| if (!(err && (err.code === 85 || (typeof err.message === 'string' && err.message.includes('already exists'))))) { | |
| // eslint-disable-next-line no-console | |
| console.error('Error creating projects_members_userId_idx:', err); | |
| } | |
| }) | |
| ); | |
| // Superadmins: presence check | |
| tasks.push( | |
| db.collection('superadmins') | |
| .createIndex({ userId: 1 }, { name: 'superadmins_userId_idx', unique: true }) | |
| .catch((err) => { | |
| if (!(err && (err.code === 85 || (typeof err.message === 'string' && err.message.includes('already exists'))))) { | |
| // eslint-disable-next-line no-console | |
| console.error('Error creating superadmins_userId_idx:', err); | |
| } | |
| }) | |
| ); |
|
Looks like there are a few issues preventing this PR from being merged!
If you'd like me to help, just leave a comment, like Feel free to include any additional details that might help me get this PR into a better state. You can manage your notification settings |
Pull Request
Description
Brief description of the changes in this PR.
Type of Change
Related Issues
Closes #(issue number)
Changes Made
Documentation Impact
Testing
npm run docs:validateto check documentation linksSecurity Considerations
Architecture Compliance
Checklist
Screenshots (if applicable)
Add screenshots to help explain your changes.
Additional Notes
Any additional information that reviewers should know.