Skip to content

Commit

Permalink
update
Browse files Browse the repository at this point in the history
  • Loading branch information
SoumyaranjanX committed Feb 18, 2025
1 parent 2b4b4ef commit 9142da3
Show file tree
Hide file tree
Showing 36 changed files with 3,064 additions and 2,238 deletions.
4 changes: 2 additions & 2 deletions src/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
"lint:fix": "eslint . --ext .ts --fix",
"format": "prettier --write \"src/**/*.ts\"",
"typecheck": "tsc --noEmit",
"migrate": "sequelize-cli db:migrate",
"migrate:undo": "sequelize-cli db:migrate:undo",
"migrate": "NODE_ENV=development ts-node ./node_modules/.bin/sequelize-cli db:migrate",
"migrate:undo": "NODE_ENV=development ts-node ./node_modules/.bin/sequelize-cli db:migrate:undo",
"seed": "sequelize-cli db:seed:all",
"seed:undo": "sequelize-cli db:seed:undo:all",
"docs:generate": "swagger-jsdoc -d swaggerDef.js -o swagger.json",
Expand Down
13 changes: 7 additions & 6 deletions src/backend/src/config/auth.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// @ts-check
import { config } from 'dotenv'; // v16.0.3
import type { ProcessEnv } from '../types/environment';
import { TIMING_CONFIG } from '../constants/timing';

// Load environment variables
config();
Expand All @@ -18,8 +19,8 @@ export const authConfig = {
privateKey: process.env.JWT_PRIVATE_KEY,
publicKey: process.env.JWT_PUBLIC_KEY,
algorithm: 'RS256' as const,
expiresIn: process.env.JWT_EXPIRY || '1h',
refreshTokenExpiry: '14d',
expiresIn: TIMING_CONFIG.AUTH.ACCESS_TOKEN.EXPIRY,
refreshTokenExpiry: TIMING_CONFIG.AUTH.REFRESH_TOKEN.EXPIRY,
issuer: 'startup-metrics-platform',
audience: 'startup-metrics-users',
clockTolerance: 30, // Seconds of clock drift tolerance
Expand Down Expand Up @@ -56,7 +57,7 @@ export const authConfig = {
cookie: {
secure: process.env.NODE_ENV === 'production', // Secure in production
httpOnly: true, // Prevent client-side access
maxAge: 14 * 24 * 60 * 60 * 1000, // 14 days in milliseconds
maxAge: TIMING_CONFIG.AUTH.REFRESH_TOKEN.DURATION_MS, // 14 days in milliseconds
sameSite: 'strict' as const, // Strict same-site policy
domain: process.env.COOKIE_DOMAIN, // Cookie domain
path: '/', // Cookie path
Expand All @@ -70,15 +71,15 @@ export const authConfig = {
redis: {
url: process.env.REDIS_URL,
prefix: 'sess:', // Session key prefix
ttl: 24 * 60 * 60, // Session TTL in seconds (24 hours)
ttl: TIMING_CONFIG.SESSION.TTL_SEC, // Session TTL in seconds (24 hours)
disableTouch: false, // Enable TTL refresh on session access
retry_strategy: (options: { error: Error; total_retry_time: number; times_connected: number; attempt: number }) => {
retry_strategy: (options: any) => {
// Implement exponential backoff with max retry time
if (options.error?.code === 'ECONNREFUSED') {
// End reconnecting on a specific error
return new Error('The server refused the connection');
}
if (options.total_retry_time > 1000 * 60 * 60) {
if (options.total_retry_time > TIMING_CONFIG.AUTH.REFRESH_TOKEN.DURATION_MS) {
// End reconnecting after 1 hour
return new Error('Retry time exhausted');
}
Expand Down
40 changes: 40 additions & 0 deletions src/backend/src/constants/http.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* HTTP Status Code Constants
* Defines standard HTTP status codes used throughout the application
*/
export const HTTP_STATUS = {
// 2xx Success
OK: 200,
CREATED: 201,
ACCEPTED: 202,
NO_CONTENT: 204,

// 3xx Redirection
MOVED_PERMANENTLY: 301,
FOUND: 302,
NOT_MODIFIED: 304,
TEMPORARY_REDIRECT: 307,
PERMANENT_REDIRECT: 308,

// 4xx Client Errors
BAD_REQUEST: 400,
UNAUTHORIZED: 401,
FORBIDDEN: 403,
NOT_FOUND: 404,
METHOD_NOT_ALLOWED: 405,
CONFLICT: 409,
GONE: 410,
PRECONDITION_FAILED: 412,
UNPROCESSABLE_ENTITY: 422,
TOO_MANY_REQUESTS: 429,

// 5xx Server Errors
INTERNAL_SERVER_ERROR: 500,
NOT_IMPLEMENTED: 501,
BAD_GATEWAY: 502,
SERVICE_UNAVAILABLE: 503,
GATEWAY_TIMEOUT: 504
} as const;

// Type for HTTP status codes
export type HttpStatus = typeof HTTP_STATUS[keyof typeof HTTP_STATUS];
74 changes: 74 additions & 0 deletions src/backend/src/constants/timing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/**
* Centralized Timing Configuration
* Defines all timing-related constants for authentication, caching, and sessions
* @version 1.0.0
*/

export const TIMING_CONFIG = {
/**
* Authentication Token Configuration
*/
AUTH: {
ACCESS_TOKEN: {
DURATION_MS: 3600000, // 1 hour
DURATION_SEC: 3600,
REFRESH_THRESHOLD_MS: 300000, // Refresh 5 minutes before expiry
EXPIRY: '1h'
},
REFRESH_TOKEN: {
DURATION_MS: 1209600000, // 14 days
DURATION_SEC: 1209600,
EXPIRY: '14d'
}
},

/**
* Session Management Configuration
*/
SESSION: {
TTL_SEC: 86400, // 24 hours
CHECK_INTERVAL_MS: 300000, // Check every 5 minutes
INACTIVITY_WARNING_MS: 300000, // Show warning after 5 minutes inactivity
VALIDATION_CACHE_TTL_MS: 900000, // 15 minutes
MAX_CONSECUTIVE_FAILURES: 3
},

/**
* Cache Duration Configuration
*/
CACHE: {
SHORT: {
TTL_SEC: 300, // 5 minutes
TTL_MS: 300000,
DESCRIPTION: 'For frequently changing data (metrics, real-time data)'
},
MEDIUM: {
TTL_SEC: 900, // 15 minutes
TTL_MS: 900000,
DESCRIPTION: 'For semi-static data (user profiles, company data)'
},
LONG: {
TTL_SEC: 3600, // 1 hour
TTL_MS: 3600000,
DESCRIPTION: 'For static data (configurations, settings)'
}
},

/**
* Rate Limiting Configuration
*/
RATE_LIMIT: {
WINDOW_MS: 900000, // 15 minutes
MAX_REQUESTS: 100,
DELAY_AFTER_FAILURE_MS: 1000 // 1 second delay after each failure
}
} as const;

/**
* Type definitions for timing configuration
*/
export type TimingConfig = typeof TIMING_CONFIG;
export type AuthTiming = typeof TIMING_CONFIG.AUTH;
export type SessionTiming = typeof TIMING_CONFIG.SESSION;
export type CacheTiming = typeof TIMING_CONFIG.CACHE;
export type RateLimitTiming = typeof TIMING_CONFIG.RATE_LIMIT;
64 changes: 58 additions & 6 deletions src/backend/src/controllers/authController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import { AppError } from '../utils/AppError';
import { logger } from '../utils/logger';
import Redis from 'ioredis';
import User from '../models/User';
import { JsonWebTokenError } from 'jsonwebtoken';
import { HTTP_STATUS } from '../constants/http';

// Initialize Redis client
const redisUrl = process.env.REDIS_URL || 'redis://localhost:6379';
Expand Down Expand Up @@ -153,19 +155,69 @@ export const authController = {
const { refreshToken } = req.body;
if (!refreshToken) {
throw new AppError(
AUTH_ERRORS.INVALID_TOKEN.message,
AUTH_ERRORS.INVALID_TOKEN.httpStatus,
AUTH_ERRORS.INVALID_TOKEN.code
'Refresh token is required',
HTTP_STATUS.BAD_REQUEST,
'AUTH_001'
);
}

const tokens = await googleAuthProvider.refreshToken(refreshToken);
res.json(tokens);
const result = await googleAuthProvider.refreshToken(refreshToken);

// Set secure HTTP-only cookies
const cookieOptions = {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict' as const
};

res.cookie('accessToken', result.data.accessToken, {
...cookieOptions,
maxAge: 3600000 // 1 hour
});

res.cookie('refreshToken', result.data.refreshToken, {
...cookieOptions,
maxAge: 1209600000 // 14 days
});

// Include tokens in response body for client-side storage
res.json({
success: true,
data: {
accessToken: result.data.accessToken,
refreshToken: result.data.refreshToken,
user: result.data.user
}
});
} catch (error) {
logger.error('Token refresh failed:', {
error: error instanceof Error ? error.message : 'Unknown error',
stack: error instanceof Error ? error.stack : undefined
});
next(error);

// Clear cookies on error
res.clearCookie('accessToken');
res.clearCookie('refreshToken');

if (error instanceof AppError) {
next(error);
return;
}

if (error instanceof JsonWebTokenError) {
next(new AppError(
'Invalid refresh token',
HTTP_STATUS.UNAUTHORIZED,
'AUTH_003'
));
return;
}

next(new AppError(
'Token refresh failed',
HTTP_STATUS.UNAUTHORIZED,
'AUTH_006'
));
}
},

Expand Down
10 changes: 6 additions & 4 deletions src/backend/src/controllers/metricsController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -301,15 +301,17 @@ export const getCompanyMetrics = asyncHandler(
throw new AppError('You do not have permission to access these metrics', 403, 'AUTH_004');
}

// Get metrics for the user's company
const metrics = await metricsService.getMetricsForCompany(userId);
// Get metrics for the user's company with cache disabled
const metrics = await metricsService.getMetricsForCompany(userId, { skipCache: true });

// Calculate response time
const [seconds, nanoseconds] = process.hrtime(startTime);
const responseTime = seconds * 1000 + nanoseconds / 1e6;

// Set cache headers
res.set('Cache-Control', `public, max-age=${CACHE_DURATION}`);
// Set no-cache headers
res.set('Cache-Control', 'no-store, no-cache, must-revalidate, private');
res.set('Pragma', 'no-cache');
res.set('Expires', '0');
res.set('X-Response-Time', `${responseTime.toFixed(2)}ms`);
res.set('X-Correlation-ID', correlationId);

Expand Down
88 changes: 45 additions & 43 deletions src/backend/src/db/migrations/000_cleanup_indexes.ts
Original file line number Diff line number Diff line change
@@ -1,43 +1,45 @@
'use strict';

/** @type {import('sequelize-cli').Migration} */
module.exports = {
async up(queryInterface, Sequelize) {
try {
// Drop existing indexes that might cause conflicts
const dropIndexes = [
'data_sources_name_idx',
'data_sources_active_idx',
'data_sources_type_idx',
'idx_benchmark_metric_revenue',
'idx_benchmark_report_date',
'idx_benchmark_source',
'metrics_name_idx',
'metrics_category_idx',
'metrics_active_idx',
'users_email_idx',
'users_role_idx',
];

for (const indexName of dropIndexes) {
await queryInterface.sequelize
.query(
`
DROP INDEX IF EXISTS ${indexName};
`
)
.catch(() => {
// Ignore error if index doesn't exist
console.log(`Note: Index ${indexName} might not exist, skipping...`);
});
}
} catch (error) {
console.error('Migration failed:', error);
throw error;
}
},

async down(queryInterface, Sequelize) {
// Nothing to do in down migration since this is just cleanup
},
};
'use strict';

import { QueryInterface } from 'sequelize';

/** @type {import('sequelize-cli').Migration} */
module.exports = {
async up(queryInterface: QueryInterface) {
try {
// Drop existing indexes that might cause conflicts
const dropIndexes = [
'data_sources_name_idx',
'data_sources_active_idx',
'data_sources_type_idx',
'idx_benchmark_metric_revenue',
'idx_benchmark_report_date',
'idx_benchmark_source',
'metrics_name_idx',
'metrics_category_idx',
'metrics_active_idx',
'users_email_idx',
'users_role_idx',
];

for (const indexName of dropIndexes) {
await queryInterface.sequelize
.query(
`
DROP INDEX IF EXISTS ${indexName};
`
)
.catch(() => {
// Ignore error if index doesn't exist
console.log(`Note: Index ${indexName} might not exist, skipping...`);
});
}
} catch (error) {
console.error('Migration failed:', error);
throw error;
}
},

async down(queryInterface: QueryInterface) {
// Nothing to do in down migration since this is just cleanup
},
};
Loading

0 comments on commit 9142da3

Please sign in to comment.