Compare commits

...

3 Commits
main ... dev

Author SHA1 Message Date
Chandresh Kerkar 471f4153ce Reddis Aws MD 2025-12-21 23:14:45 +05:30
Chandresh Kerkar da921f870a Latest code 2025-12-21 15:09:21 +05:30
Chandresh Kerkar 145566ad7d Updated Code With Verify Logic Need to test 2025-12-21 02:15:23 +05:30
9 changed files with 560 additions and 16 deletions

View File

@ -0,0 +1,333 @@
# AWS ElastiCache for Redis Setup Guide
This guide walks you through setting up Redis on AWS using ElastiCache, which is AWS's managed Redis service.
## Prerequisites
- AWS Account with appropriate permissions
- AWS CLI configured (optional, but helpful)
- Your application running on AWS (EC2, ECS, Lambda, etc.)
- VPC (Virtual Private Cloud) set up in AWS
## Step-by-Step Setup
### Step 1: Create a VPC and Subnet Group (if not already done)
ElastiCache requires a VPC. If you don't have one:
1. **Go to AWS Console → VPC Dashboard**
2. **Create VPC** (if needed):
- Click "Create VPC"
- Choose "VPC and more"
- Name: `your-app-vpc`
- IPv4 CIDR: `10.0.0.0/16`
- Create public and private subnets
- Enable DNS hostnames
3. **Create Subnet Group for ElastiCache**:
- Go to **ElastiCache → Subnet Groups**
- Click "Create subnet group"
- Name: `redis-subnet-group`
- VPC: Select your VPC
- Availability Zones: Select at least 2 zones
- Subnets: Select private subnets (recommended for security)
### Step 2: Create Security Group
1. **Go to EC2 → Security Groups**
2. **Create Security Group**:
- Name: `redis-security-group`
- Description: `Security group for ElastiCache Redis`
- VPC: Select your VPC
- **Inbound Rules**: Add rule
- Type: `Custom TCP`
- Port: `6379`
- Source: Select your application's security group (or specific IP/CIDR)
- **Outbound Rules**: Default (All traffic)
### Step 3: Create ElastiCache Redis Cluster
1. **Go to AWS Console → ElastiCache**
2. **Click "Create" → Choose "Redis"**
3. **Configure Cluster Settings**:
**Cluster Settings**:
- **Name**: `your-app-redis` (must be unique)
- **Description**: `Redis cache for rate limiting and OTP`
- **Engine version**: Latest Redis 7.x (recommended)
- **Port**: `6379` (default)
- **Parameter group**: `default.redis7` (or create custom)
**Node Type**:
- **Node type**: Choose based on your needs:
- `cache.t3.micro` - Free tier eligible, 0.5 GB RAM
- `cache.t3.small` - 1.37 GB RAM (~$15/month)
- `cache.t3.medium` - 3.09 GB RAM (~$30/month)
- For production: `cache.t4g.medium` or larger
**Network & Security**:
- **VPC**: Select your VPC
- **Subnet group**: Select the subnet group created in Step 1
- **Availability Zone(s)**:
- Single AZ (cheaper, less resilient)
- Multi-AZ (recommended for production, automatic failover)
- **Security groups**: Select `redis-security-group` created in Step 2
**Backup & Maintenance**:
- **Automatic backups**: Enable (recommended)
- **Backup retention**: 1-7 days (your choice)
- **Backup window**: Choose low-traffic time
- **Maintenance window**: Choose low-traffic time
**Encryption**:
- **Encryption in-transit**: Enable (recommended for production)
- **Encryption at-rest**: Enable (recommended for production)
- **Auth token**:
- **Enable**: Recommended for production
- **Auth token**: Generate a strong password (save this!)
4. **Review and Create**
- Review all settings
- Click "Create"
### Step 4: Wait for Cluster Creation
- ElastiCache takes 5-15 minutes to create
- Status will change from "creating" to "available"
- Note the **Primary Endpoint** (e.g., `your-app-redis.xxxxx.cache.amazonaws.com:6379`)
### Step 5: Configure Your Application
Update your `.env` file with the ElastiCache endpoint:
#### Option A: Without Auth Token (Not Recommended for Production)
```env
REDIS_URL=redis://your-app-redis.xxxxx.cache.amazonaws.com:6379
```
#### Option B: With Auth Token (Recommended)
```env
REDIS_URL=redis://:your-auth-token@your-app-redis.xxxxx.cache.amazonaws.com:6379
```
#### Option C: Using Separate Variables
```env
REDIS_HOST=your-app-redis.xxxxx.cache.amazonaws.com
REDIS_PORT=6379
REDIS_PASSWORD=your-auth-token
```
#### Option D: With SSL/TLS (If Encryption in-transit is enabled)
```env
REDIS_URL=rediss://:your-auth-token@your-app-redis.xxxxx.cache.amazonaws.com:6379
```
**Note**: `rediss://` (with double 's') indicates SSL/TLS connection.
### Step 6: Update Security Group (If Needed)
If your application can't connect:
1. **Check Security Group Rules**:
- Ensure your application's security group can access port 6379
- Or add your application's security group ID to Redis security group inbound rules
2. **Test Connection** (from EC2 instance):
```bash
# Install redis-cli
sudo yum install redis -y # Amazon Linux
# or
sudo apt-get install redis-tools -y # Ubuntu
# Test connection
redis-cli -h your-app-redis.xxxxx.cache.amazonaws.com -p 6379 -a your-auth-token ping
# Should return: PONG
```
### Step 7: Verify Connection
1. **Restart your application**
2. **Check logs** for:
```
✅ Redis Client: Ready
```
3. **If you see errors**, check:
- Security group rules
- VPC routing
- Auth token is correct
- Endpoint URL is correct
## Cost Optimization
### Free Tier
- AWS Free Tier includes 750 hours/month of `cache.t2.micro` or `cache.t3.micro`
- Perfect for development/testing
### Cost-Saving Tips
1. **Use smaller instance types** for development
2. **Disable automatic backups** for non-production
3. **Use single-AZ** for development (multi-AZ costs more)
4. **Stop/Delete** clusters when not in use
5. **Reserved Instances** for production (save up to 55%)
## High Availability Setup
### Multi-AZ Configuration
1. **Enable Multi-AZ** during cluster creation
2. **Automatic failover** if primary node fails
3. **Read Replicas** for read scaling:
- Go to ElastiCache → Your cluster → Actions → Add replica
- Choose availability zones
- Replicas can be promoted to primary if needed
### Cluster Mode (Redis Cluster)
For larger scale:
1. **Enable Cluster Mode** during creation
2. **Multiple shards** for horizontal scaling
3. **Automatic sharding** across nodes
## Security Best Practices
1. **✅ Enable Auth Token** (password authentication)
2. **✅ Enable Encryption in-transit** (SSL/TLS)
3. **✅ Enable Encryption at-rest**
4. **✅ Use Private Subnets** (not public subnets)
5. **✅ Restrict Security Groups** (only allow your application)
6. **✅ Use VPC Endpoints** (if accessing from Lambda)
7. **✅ Regular Security Updates** (AWS handles this)
## Monitoring and Alerts
### CloudWatch Metrics
1. **Go to ElastiCache → Your Cluster → Monitoring**
2. **Key Metrics to Monitor**:
- `CPUUtilization` - Should be < 80%
- `MemoryUtilization` - Should be < 80%
- `NetworkBytesIn/Out` - Network traffic
- `CacheHits/CacheMisses` - Cache performance
- `Evictions` - Memory pressure indicator
### Set Up Alarms
1. **Go to CloudWatch → Alarms**
2. **Create alarms for**:
- High CPU (> 80%)
- High Memory (> 80%)
- Connection failures
- Failover events
## Troubleshooting
### Connection Timeout
**Problem**: Application can't connect to Redis
**Solutions**:
1. Check security group allows traffic from your application
2. Verify VPC routing tables
3. Ensure both are in same VPC
4. Check if endpoint URL is correct
5. Verify auth token is correct
### Authentication Failed
**Problem**: `NOAUTH Authentication required` or `WRONGPASS`
**Solutions**:
1. Verify auth token in `.env` matches ElastiCache auth token
2. Check if auth token is enabled in ElastiCache
3. Use format: `redis://:password@host:port`
### High Memory Usage
**Problem**: Memory utilization > 90%
**Solutions**:
1. Upgrade to larger node type
2. Enable eviction policy (already enabled by default)
3. Review what data is stored in Redis
4. Set TTL on keys (your code already does this)
### Slow Performance
**Problem**: High latency or slow responses
**Solutions**:
1. Check CPU utilization
2. Enable read replicas for read-heavy workloads
3. Upgrade node type
4. Check network latency (use same region as application)
5. Monitor `CacheHits` vs `CacheMisses` ratio
## Alternative: AWS MemoryDB for Redis
For even higher durability (data persisted to disk):
1. **Go to MemoryDB** (separate service)
2. **Similar setup** to ElastiCache
3. **Better durability** (multi-AZ with automatic failover)
4. **Higher cost** than ElastiCache
5. **Use when**: Data persistence is critical
## Alternative: Self-Hosted on EC2
If you prefer more control:
1. **Launch EC2 instance** (Amazon Linux or Ubuntu)
2. **Install Redis**:
```bash
sudo yum install redis -y # Amazon Linux
sudo systemctl start redis
sudo systemctl enable redis
```
3. **Configure security group** (port 6379)
4. **Set up Redis password** in `/etc/redis.conf`
5. **Use EC2 private IP** in your `.env`
**Note**: You'll need to manage backups, updates, and scaling yourself.
## Quick Reference
### Get Endpoint URL
```bash
aws elasticache describe-cache-clusters \
--cache-cluster-id your-app-redis \
--show-cache-node-info \
--query 'CacheClusters[0].CacheNodes[0].Endpoint.Address'
```
### Get Auth Token
- Go to ElastiCache → Your Cluster → Configuration
- Auth token is shown (or set during creation)
### Update Auth Token
1. Go to ElastiCache → Your Cluster
2. Actions → Modify
3. Change auth token
4. Apply immediately or schedule
### Delete Cluster
1. Go to ElastiCache → Your Cluster
2. Actions → Delete
3. Confirm deletion
4. **Warning**: This deletes all data!
## Next Steps
1. ✅ Create ElastiCache Redis cluster
2. ✅ Update `.env` with endpoint and auth token
3. ✅ Update security groups
4. ✅ Restart application
5. ✅ Verify connection: `✅ Redis Client: Ready`
6. ✅ Set up CloudWatch alarms
7. ✅ Monitor performance
## Support
- **AWS Documentation**: https://docs.aws.amazon.com/elasticache/
- **AWS Support**: AWS Console → Support Center
- **Pricing Calculator**: https://calculator.aws/

View File

@ -53,10 +53,12 @@ REDIS_URL=redis://localhost:6379
### Option 3: Use Cloud Redis (Production)
For production, use a managed Redis service:
- **AWS ElastiCache**: `redis://your-elasticache-endpoint:6379`
- **AWS ElastiCache**: `redis://your-elasticache-endpoint:6379` - [📖 Complete AWS Setup Guide](./AWS_REDIS_SETUP.md)
- **Redis Cloud**: `redis://user:password@redis-cloud-host:6379`
- **Azure Cache for Redis**: `redis://your-cache.redis.cache.windows.net:6380?ssl=true`
**For AWS deployments, see the detailed guide**: [AWS Redis Setup Guide](./AWS_REDIS_SETUP.md)
Add to your `.env` file:
```env

8
node_modules/.package-lock.json generated vendored
View File

@ -2206,12 +2206,12 @@
}
},
"node_modules/jws": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
"integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/jws/-/jws-3.2.3.tgz",
"integrity": "sha512-byiJ0FLRdLdSVSReO/U4E7RoEyOCKnEnEPMjq3HxWtvzLsV08/i5RQKsFVNkCldrCaPr2vDNAOMsfs8T/Hze7g==",
"license": "MIT",
"dependencies": {
"jwa": "^1.4.1",
"jwa": "^1.4.2",
"safe-buffer": "^5.0.1"
}
},

19
node_modules/jws/CHANGELOG.md generated vendored
View File

@ -1,8 +1,20 @@
# Change Log
All notable changes to this project will be documented in this file.
## [3.0.0]
## [3.2.3]
### Changed
- Fix advisory GHSA-869p-cjfg-cm3x: createSign and createVerify now require
that a non empty secret is provided (via opts.secret, opts.privateKey or opts.key)
when using HMAC algorithms.
- Upgrading JWA version to 1.4.2, adressing a compatibility issue for Node >= 25.
## [3.0.0]
### Changed
- **BREAKING**: `jwt.verify` now requires an `algorithm` parameter, and
`jws.createVerify` requires an `algorithm` option. The `"alg"` field
signature headers is ignored. This mitigates a critical security flaw
@ -12,7 +24,9 @@ All notable changes to this project will be documented in this file.
for details.
## [2.0.0] - 2015-01-30
### Changed
- **BREAKING**: Default payload encoding changed from `binary` to
`utf8`. `utf8` is a is a more sensible default than `binary` because
many payloads, as far as I can tell, will contain user-facing
@ -21,14 +35,13 @@ All notable changes to this project will be documented in this file.
- Code reorganization, thanks [@fearphage]! (<code>[7880050]</code>)
### Added
- Option in all relevant methods for `encoding`. For those few users
that might be depending on a `binary` encoding of the messages, this
is for them. (<code>[6b6de48]</code>)
[unreleased]: https://github.com/brianloveswords/node-jws/compare/v2.0.0...HEAD
[2.0.0]: https://github.com/brianloveswords/node-jws/compare/v1.0.1...v2.0.0
[7880050]: https://github.com/brianloveswords/node-jws/commit/7880050
[6b6de48]: https://github.com/brianloveswords/node-jws/commit/6b6de48
[@fearphage]: https://github.com/fearphage

View File

@ -34,7 +34,12 @@ function jwsSign(opts) {
}
function SignStream(opts) {
var secret = opts.secret||opts.privateKey||opts.key;
var secret = opts.secret;
secret = secret == null ? opts.privateKey : secret;
secret = secret == null ? opts.key : secret;
if (/^hs/i.test(opts.header.alg) === true && secret == null) {
throw new TypeError('secret must be a string or buffer or a KeyObject')
}
var secretStream = new DataStream(secret);
this.readable = true;
this.header = opts.header;

View File

@ -79,7 +79,12 @@ function jwsDecode(jwsSig, opts) {
function VerifyStream(opts) {
opts = opts || {};
var secretOrKey = opts.secret||opts.publicKey||opts.key;
var secretOrKey = opts.secret;
secretOrKey = secretOrKey == null ? opts.publicKey : secretOrKey;
secretOrKey = secretOrKey == null ? opts.key : secretOrKey;
if (/^hs/i.test(opts.algorithm) === true && secretOrKey == null) {
throw new TypeError('secret must be a string or buffer or a KeyObject')
}
var secretStream = new DataStream(secretOrKey);
this.readable = true;
this.algorithm = opts.algorithm;

4
node_modules/jws/package.json generated vendored
View File

@ -1,6 +1,6 @@
{
"name": "jws",
"version": "3.2.2",
"version": "3.2.3",
"description": "Implementation of JSON Web Signatures",
"main": "index.js",
"directories": {
@ -24,7 +24,7 @@
"readmeFilename": "readme.md",
"gitHead": "c0f6b27bcea5a2ad2e304d91c2e842e4076a6b03",
"dependencies": {
"jwa": "^1.4.1",
"jwa": "^1.4.2",
"safe-buffer": "^5.0.1"
},
"devDependencies": {

8
package-lock.json generated
View File

@ -2240,12 +2240,12 @@
}
},
"node_modules/jws": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
"integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/jws/-/jws-3.2.3.tgz",
"integrity": "sha512-byiJ0FLRdLdSVSReO/U4E7RoEyOCKnEnEPMjq3HxWtvzLsV08/i5RQKsFVNkCldrCaPr2vDNAOMsfs8T/Hze7g==",
"license": "MIT",
"dependencies": {
"jwa": "^1.4.1",
"jwa": "^1.4.2",
"safe-buffer": "^5.0.1"
}
},

View File

@ -1184,4 +1184,190 @@ router.post(
}
);
// POST /auth/validate-token
// Endpoint for other services (like BuySellService) to validate JWT tokens
// This allows centralized token validation
router.post('/validate-token', async (req, res) => {
const clientIp = req.ip || req.headers['x-forwarded-for']?.split(',')[0]?.trim() || req.headers['x-real-ip'] || 'unknown';
const userAgent = req.headers['user-agent'] || 'unknown';
console.log(`[Auth Service] POST /auth/validate-token - Request received`);
console.log(`[Auth Service] Client IP: ${clientIp}`);
console.log(`[Auth Service] User-Agent: ${userAgent.substring(0, 80)}`);
console.log(`[Auth Service] Request body contains token: ${!!req.body?.token}`);
try {
const { token } = req.body;
if (!token) {
console.log(`[Auth Service] ❌ FAILED: Token is missing in request body`);
return res.status(400).json({
valid: false,
error: 'Token is required'
});
}
console.log(`[Auth Service] Token received (length: ${token.length} characters)`);
// Use the auth middleware logic to validate token
const jwt = require('jsonwebtoken');
const { getKeySecret, getAllKeys, validateTokenClaims } = require('../services/jwtKeys');
const db = require('../db');
// Decode token to get key ID from header
let decoded;
try {
decoded = jwt.decode(token, { complete: true });
if (!decoded || !decoded.header) {
console.log(`[Auth Service] ❌ FAILED: Invalid token format - unable to decode header`);
return res.json({ valid: false, error: 'Invalid token format' });
}
console.log(`[Auth Service] Token decoded successfully`);
console.log(`[Auth Service] Token header:`, {
alg: decoded.header.alg,
kid: decoded.header.kid || 'NOT SET',
typ: decoded.header.typ,
});
} catch (err) {
console.log(`[Auth Service] ❌ FAILED: Error decoding token - ${err.message}`);
return res.json({ valid: false, error: 'Invalid token' });
}
// Get secret for the key ID (if present) or try all keys
const keyId = decoded.header.kid;
let payload = null;
let verified = false;
let verificationMethod = null;
if (keyId) {
console.log(`[Auth Service] Key ID found in token: ${keyId}`);
const secret = getKeySecret(keyId);
if (secret) {
console.log(`[Auth Service] Attempting verification with key ID: ${keyId}`);
try {
payload = jwt.verify(token, secret);
verified = true;
verificationMethod = `keyId: ${keyId}`;
console.log(`[Auth Service] ✅ Token verified successfully using key ID: ${keyId}`);
} catch (err) {
console.log(`[Auth Service] ❌ Verification failed with key ID ${keyId}: ${err.message}`);
return res.json({ valid: false, error: 'Invalid or expired token' });
}
} else {
console.log(`[Auth Service] ⚠️ Key ID ${keyId} not found in available keys, will try all keys`);
}
} else {
console.log(`[Auth Service] ⚠️ No key ID in token header, trying all available keys`);
}
// If key ID not found or not specified, try all keys (for rotation support)
if (!verified) {
const allKeys = getAllKeys();
console.log(`[Auth Service] Attempting verification with ${Object.keys(allKeys).length} available keys`);
for (const [kid, keySecret] of Object.entries(allKeys)) {
try {
payload = jwt.verify(token, keySecret);
verified = true;
verificationMethod = `allKeys: ${kid}`;
console.log(`[Auth Service] ✅ Token verified successfully using key: ${kid}`);
break;
} catch (err) {
// Try next key silently
continue;
}
}
if (!verified) {
console.log(`[Auth Service] ❌ FAILED: Token verification failed with all available keys`);
}
}
if (!verified || !payload) {
console.log(`[Auth Service] ❌ FINAL RESULT: Token is invalid or expired`);
return res.json({ valid: false, error: 'Invalid or expired token' });
}
console.log(`[Auth Service] Token payload extracted:`, {
userId: payload.sub,
role: payload.role,
userType: payload.user_type,
exp: payload.exp ? new Date(payload.exp * 1000).toISOString() : 'NOT SET',
iat: payload.iat ? new Date(payload.iat * 1000).toISOString() : 'NOT SET',
tokenVersion: payload.token_version || 1,
});
// Validate JWT claims (iss, aud, exp, iat, nbf)
console.log(`[Auth Service] Validating JWT claims...`);
const claimsValidation = validateTokenClaims(payload);
if (!claimsValidation.valid) {
console.log(`[Auth Service] ❌ FAILED: Claims validation failed - ${claimsValidation.reason}`);
return res.json({ valid: false, error: 'Invalid token claims' });
}
console.log(`[Auth Service] ✅ Claims validation passed`);
// Validate token_version to ensure token hasn't been invalidated by logout-all-devices
console.log(`[Auth Service] Checking token version in database for user: ${payload.sub}`);
try {
const { rows } = await db.query(
`SELECT COALESCE(token_version, 1) as token_version FROM users WHERE id = $1`,
[payload.sub]
);
if (rows.length === 0) {
console.log(`[Auth Service] ❌ FAILED: User not found in database: ${payload.sub}`);
return res.json({ valid: false, error: 'User not found' });
}
const userTokenVersion = rows[0].token_version;
const tokenVersion = payload.token_version || 1;
console.log(`[Auth Service] Token version check:`, {
tokenVersion,
userTokenVersion,
match: tokenVersion === userTokenVersion,
});
// If token version doesn't match, token has been invalidated
if (tokenVersion !== userTokenVersion) {
console.log(`[Auth Service] ❌ FAILED: Token version mismatch - token has been invalidated`);
return res.json({ valid: false, error: 'Token has been invalidated' });
}
console.log(`[Auth Service] ✅ Token version check passed`);
} catch (dbErr) {
// Handle missing token_version column gracefully
if (dbErr.code === '42703' && dbErr.message && dbErr.message.includes('token_version')) {
console.warn('[Auth Service] ⚠️ token_version column not found in database, skipping version check');
// Continue without token version validation
} else {
console.error('[Auth Service] ❌ Error validating token version:', dbErr);
return res.status(500).json({ valid: false, error: 'Internal server error' });
}
}
// Token is valid - return user info
console.log(`[Auth Service] ✅ FINAL RESULT: Token is VALID`);
console.log(`[Auth Service] Returning user info:`, {
userId: payload.sub,
role: payload.role,
userType: payload.user_type,
verificationMethod,
});
return res.json({
valid: true,
payload: {
sub: payload.sub,
role: payload.role,
user_type: payload.user_type,
phone_number: payload.phone_number,
token_version: payload.token_version || 1,
high_assurance: payload.high_assurance || false,
},
});
} catch (err) {
console.error('[Auth Service] ❌ UNEXPECTED ERROR in validate-token:', err);
console.error('[Auth Service] Error stack:', err.stack);
return res.status(500).json({ valid: false, error: 'Internal server error' });
}
});
module.exports = router;