Security
Comprehensive security architecture and best practices for AsaHome Cloud.
Multi-Layer Security
AsaHome Cloud implements defense-in-depth with multiple security layers:
Network Layer
TLS/SSL Encryption
All traffic is encrypted using TLS 1.3:
# Nginx SSL configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers off;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;
HSTS (HTTP Strict Transport Security)
Forces browsers to use HTTPS:
add_header Strict-Transport-Security "max-age=63072000" always;
WebSocket Security
WebSocket connections use WSS (WebSocket Secure):
wss://cloud.asahome.io/tunnel
Transport Layer
Security Headers
Nginx injects security headers on all responses:
# Prevent clickjacking
add_header X-Frame-Options "SAMEORIGIN" always;
# Prevent MIME sniffing
add_header X-Content-Type-Options "nosniff" always;
# XSS Protection
add_header X-XSS-Protection "1; mode=block" always;
# Content Security Policy
add_header Content-Security-Policy "default-src 'self'; script-src 'self'" always;
# Referrer Policy
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
Rate Limiting
Protection against brute force and DDoS:
Nginx Level
# Limit requests per IP
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
limit_req zone=api burst=20 nodelay;
# Limit connections per IP
limit_conn_zone $binary_remote_addr zone=conn:10m;
limit_conn conn 10;
Application Level
// NestJS throttler
@Module({
imports: [
ThrottlerModule.forRoot({
ttl: 60,
limit: 100,
}),
],
})
Per-endpoint throttling:
@Throttle(5, 60) // 5 requests per 60 seconds
@Post('login')
async login() { ... }
CORS Policy
Restrict cross-origin requests:
app.enableCors({
origin: process.env.CORS_ORIGINS?.split(','),
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
credentials: true,
allowedHeaders: ['Authorization', 'Content-Type'],
});
Application Layer
JWT Authentication
Token Security
| Feature | Implementation |
|---|---|
| Algorithm | HS256 (HMAC-SHA256) |
| Secret | 256-bit random key |
| Access Token TTL | 15 minutes |
| Refresh Token TTL | 30 days |
| Refresh Rotation | Yes (each use) |
Token Validation
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(private configService: ConfigService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: configService.get('JWT_SECRET'),
});
}
async validate(payload: JwtPayload) {
// Validate user exists and is active
const user = await this.usersService.findById(payload.sub);
if (!user || !user.isActive) {
throw new UnauthorizedException();
}
return user;
}
}
Password Security
Hashing
Passwords are hashed using bcrypt with 12 rounds:
const saltRounds = 12;
const hashedPassword = await bcrypt.hash(plainPassword, saltRounds);
Validation
const isValid = await bcrypt.compare(plainPassword, hashedPassword);
Password Requirements
- Minimum 8 characters
- At least one uppercase letter
- At least one lowercase letter
- At least one number
@IsString()
@MinLength(8)
@Matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/, {
message: 'Password must contain uppercase, lowercase, and number',
})
password: string;
Input Validation
All inputs are validated using class-validator:
export class LoginDto {
@IsEmail()
@Transform(({ value }) => value.toLowerCase().trim())
email: string;
@IsString()
@MinLength(8)
password: string;
}
Global validation pipe:
app.useGlobalPipes(
new ValidationPipe({
whitelist: true, // Strip unknown properties
forbidNonWhitelisted: true, // Error on unknown properties
transform: true, // Auto-transform types
transformOptions: {
enableImplicitConversion: false,
},
}),
);
Role-Based Access Control
User Roles
| Role | Permissions |
|---|---|
user | Access own devices, standard API |
admin | Full access, user management |
Device Roles
| Role | Permissions |
|---|---|
owner | Full control, delete, share |
editor | Control devices, modify settings |
viewer | View status only |
Implementation
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin')
@Get('admin/users')
async getAllUsers() { ... }
Data Layer
SQL Injection Prevention
TypeORM uses parameterized queries:
// Safe - parameterized
const user = await this.userRepository.findOne({
where: { email: userInput }
});
// Also safe - query builder with parameters
const users = await this.userRepository
.createQueryBuilder('user')
.where('user.email = :email', { email: userInput })
.getMany();
Refresh Token Storage
Refresh tokens are hashed before storage:
import * as crypto from 'crypto';
// Hash token before storing
const hashedToken = crypto
.createHash('sha256')
.update(refreshToken)
.digest('hex');
await this.refreshTokenRepository.save({
userId: user.id,
token: hashedToken,
expiresAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000),
});
Sensitive Data Handling
| Data Type | Storage Method |
|---|---|
| Passwords | bcrypt hash (12 rounds) |
| Refresh Tokens | SHA-256 hash |
| Access Tokens | Not stored (stateless) |
| API Keys | SHA-256 hash |
| Device Secrets | AES-256 encrypted |
WebSocket Security
Authentication
WebSocket connections require JWT authentication:
@WebSocketGateway()
export class TunnelGateway {
async handleConnection(client: Socket) {
try {
const token = client.handshake.auth?.token;
const payload = this.jwtService.verify(token);
// Attach user to socket
client.data.user = payload;
} catch (error) {
client.emit('error', { message: 'Unauthorized' });
client.disconnect();
}
}
}
Message Validation
All WebSocket messages are validated:
@SubscribeMessage('tunnel:message')
async handleMessage(
@ConnectedSocket() client: Socket,
@MessageBody(new ValidationPipe()) data: TunnelMessageDto,
) {
// Message is validated before processing
}
Rate Limiting
Per-connection rate limiting:
const rateLimiter = new Map<string, number[]>();
function checkRateLimit(clientId: string): boolean {
const now = Date.now();
const windowMs = 1000; // 1 second
const maxRequests = 10;
const timestamps = rateLimiter.get(clientId) || [];
const recent = timestamps.filter(t => now - t < windowMs);
if (recent.length >= maxRequests) {
return false;
}
recent.push(now);
rateLimiter.set(clientId, recent);
return true;
}
Security Checklist
Before Deployment
- Change default JWT_SECRET
- Set strong DB_PASSWORD
- Configure CORS_ORIGINS for your domains
- Enable HTTPS with valid SSL certificate
- Review and restrict firewall rules
- Set up log monitoring
Ongoing
- Rotate JWT secrets periodically
- Update dependencies regularly
- Monitor failed login attempts
- Review audit logs
- Run security scans (npm audit)
Environment Variables
Never commit secrets. Use environment variables:
# Generate secure secrets
JWT_SECRET=$(openssl rand -base64 32)
DB_PASSWORD=$(openssl rand -base64 24)
INTERNAL_API_KEYS=$(openssl rand -base64 32)
Incident Response
Suspected Breach
- Rotate Secrets: Immediately rotate JWT_SECRET and API keys
- Revoke Tokens: Clear all refresh tokens from database
- Review Logs: Check audit logs for unauthorized access
- Notify Users: If user data is affected, notify users
- Post-mortem: Document and fix the vulnerability
Force All Users to Re-authenticate
-- Revoke all refresh tokens
UPDATE refresh_tokens SET is_revoked = true;
Security Headers Reference
| Header | Value | Purpose |
|---|---|---|
Strict-Transport-Security | max-age=63072000 | Force HTTPS |
X-Frame-Options | SAMEORIGIN | Prevent clickjacking |
X-Content-Type-Options | nosniff | Prevent MIME sniffing |
X-XSS-Protection | 1; mode=block | XSS filter |
Content-Security-Policy | default-src 'self' | Restrict resources |
Referrer-Policy | strict-origin-when-cross-origin | Control referrer |
Next Steps
- Configuration - Security-related settings
- Deployment - Production security setup
- Authentication - Token management