Arctickey is designed with security as a priority. Here's how we protect your data and how you can enhance security further.
All connections to Arctickey use TLS 1.3 by default:
// TLS is automatic with rediss:// URLs import Redis from 'ioredis'; const redis = new Redis(process.env.ARCTICKEY_URL); // URL format: rediss://default:password@host:port // Or explicitly configure TLS const redis = new Redis({ host: 'your-db.arctickey.com', port: 6379, password: 'your-password', tls: { // TLS options (usually not needed) }, });
Every database has a unique, strong password:
// ✅ Good: Use environment variables const redis = new Redis(process.env.ARCTICKEY_URL); // ❌ Bad: Hardcoded credentials const redis = new Redis('rediss://default:secret@host:6379');
Store credentials securely:
If you have multiple services accessing Redis:
Never use user input directly in Redis keys:
// ❌ Bad: User input in key const key = `user:${req.params.id}`; // Could be "user:*" or "user:../admin" await redis.get(key); // ✅ Good: Validate and sanitize const userId = parseInt(req.params.id); if (!Number.isInteger(userId) || userId < 0) { throw new Error('Invalid user ID'); } const key = `user:${userId}`; await redis.get(key);
Don't store sensitive data indefinitely:
// Session data - expire after inactivity await redis.setex(`session:${sessionId}`, 3600, JSON.stringify(session)); // Sensitive tokens - short TTL await redis.setex(`reset:${token}`, 600, userId); // 10 minutes // Refresh on access (sliding expiration) await redis.expire(`session:${sessionId}`, 3600);
import crypto from 'crypto'; class SessionStore { private ttl = 24 * 60 * 60; // 24 hours async createSession(userId: string, data: object) { const sessionId = crypto.randomBytes(32).toString('hex'); await redis.setex( `session:${sessionId}`, this.ttl, JSON.stringify({ userId, ...data, createdAt: Date.now() }) ); return sessionId; } async getSession(sessionId: string) { // Validate session ID format if (!/^[a-f0-9]{64}$/.test(sessionId)) { return null; } const data = await redis.get(`session:${sessionId}`); if (!data) return null; // Refresh TTL on access await redis.expire(`session:${sessionId}`, this.ttl); return JSON.parse(data); } async destroySession(sessionId: string) { await redis.del(`session:${sessionId}`); } async destroyAllUserSessions(userId: string) { // Track user's sessions in a set const sessions = await redis.smembers(`user:${userId}:sessions`); if (sessions.length > 0) { await redis.del(...sessions.map(s => `session:${s}`)); await redis.del(`user:${userId}:sessions`); } } }
class TokenStore { // Store refresh token with user association async storeRefreshToken(token: string, userId: string, ttlDays: number = 30) { const key = `refresh:${token}`; const ttl = ttlDays * 24 * 60 * 60; await redis.pipeline() .setex(key, ttl, userId) .sadd(`user:${userId}:tokens`, token) .expire(`user:${userId}:tokens`, ttl) .exec(); } // Validate and get user ID async validateRefreshToken(token: string): Promise<string | null> { return redis.get(`refresh:${token}`); } // Revoke a specific token async revokeToken(token: string) { const userId = await redis.get(`refresh:${token}`); if (userId) { await redis.pipeline() .del(`refresh:${token}`) .srem(`user:${userId}:tokens`, token) .exec(); } } // Revoke all tokens for a user (logout everywhere) async revokeAllTokens(userId: string) { const tokens = await redis.smembers(`user:${userId}:tokens`); if (tokens.length > 0) { const pipeline = redis.pipeline(); tokens.forEach(t => pipeline.del(`refresh:${t}`)); pipeline.del(`user:${userId}:tokens`); await pipeline.exec(); } } }
class AuthRateLimiter { // Limit login attempts per IP async checkLoginAttempt(ip: string): Promise<{ allowed: boolean; remaining: number }> { const key = `ratelimit:login:${ip}`; const limit = 5; const window = 15 * 60; // 15 minutes const current = await redis.incr(key); if (current === 1) { await redis.expire(key, window); } return { allowed: current <= limit, remaining: Math.max(0, limit - current), }; } // Lock account after too many failures async recordFailedLogin(userId: string): Promise<boolean> { const key = `failed:login:${userId}`; const lockThreshold = 10; const window = 60 * 60; // 1 hour const failures = await redis.incr(key); if (failures === 1) { await redis.expire(key, window); } if (failures >= lockThreshold) { // Lock the account await redis.setex(`locked:${userId}`, window, '1'); return true; // Account is now locked } return false; } async isAccountLocked(userId: string): Promise<boolean> { return await redis.exists(`locked:${userId}`) === 1; } async clearFailedAttempts(userId: string) { await redis.del(`failed:login:${userId}`); } }
Arctickey helps you comply with GDPR:
Implement data retention policies:
// Set TTL on all user data await redis.setex(`user:${userId}`, 365 * 24 * 60 * 60, userData); // Or use EXPIRE on existing keys await redis.expire(`user:${userId}`, 365 * 24 * 60 * 60); // Scan and clean old data async function cleanOldData(pattern: string, maxAge: number) { let cursor = '0'; do { const [nextCursor, keys] = await redis.scan(cursor, 'MATCH', pattern, 'COUNT', 100); cursor = nextCursor; for (const key of keys) { const ttl = await redis.ttl(key); if (ttl === -1) { // No expiry set await redis.expire(key, maxAge); } } } while (cursor !== '0'); }
Track sensitive operations:
class AuditLog { async log(event: { action: string; userId?: string; resource: string; details?: object; ip?: string; }) { const entry = { ...event, timestamp: new Date().toISOString(), }; // Store in a stream for durability await redis.xadd( 'audit:log', 'MAXLEN', '~', 100000, // Keep ~100k entries '*', 'data', JSON.stringify(entry) ); } async getRecentLogs(count: number = 100) { const entries = await redis.xrevrange('audit:log', '+', '-', 'COUNT', count); return entries.map(([id, fields]) => ({ id, ...JSON.parse(fields[1]), })); } } // Usage await auditLog.log({ action: 'user.login', userId: user.id, resource: 'auth', ip: req.ip, });
rediss://
Security
Arctickey is designed with security as a priority. Here's how we protect your data and how you can enhance security further.
Built-In Security#
TLS Encryption#
All connections to Arctickey use TLS 1.3 by default:
// TLS is automatic with rediss:// URLs import Redis from 'ioredis'; const redis = new Redis(process.env.ARCTICKEY_URL); // URL format: rediss://default:password@host:port // Or explicitly configure TLS const redis = new Redis({ host: 'your-db.arctickey.com', port: 6379, password: 'your-password', tls: { // TLS options (usually not needed) }, });Password Authentication#
Every database has a unique, strong password:
Network Isolation#
Best Practices#
Secure Your Connection String#
// ✅ Good: Use environment variables const redis = new Redis(process.env.ARCTICKEY_URL); // ❌ Bad: Hardcoded credentials const redis = new Redis('rediss://default:secret@host:6379');Store credentials securely:
Rotate Credentials Regularly#
Use Minimal Permissions#
If you have multiple services accessing Redis:
Validate Input#
Never use user input directly in Redis keys:
// ❌ Bad: User input in key const key = `user:${req.params.id}`; // Could be "user:*" or "user:../admin" await redis.get(key); // ✅ Good: Validate and sanitize const userId = parseInt(req.params.id); if (!Number.isInteger(userId) || userId < 0) { throw new Error('Invalid user ID'); } const key = `user:${userId}`; await redis.get(key);Set Appropriate TTLs#
Don't store sensitive data indefinitely:
// Session data - expire after inactivity await redis.setex(`session:${sessionId}`, 3600, JSON.stringify(session)); // Sensitive tokens - short TTL await redis.setex(`reset:${token}`, 600, userId); // 10 minutes // Refresh on access (sliding expiration) await redis.expire(`session:${sessionId}`, 3600);Authentication Patterns#
Session Storage#
import crypto from 'crypto'; class SessionStore { private ttl = 24 * 60 * 60; // 24 hours async createSession(userId: string, data: object) { const sessionId = crypto.randomBytes(32).toString('hex'); await redis.setex( `session:${sessionId}`, this.ttl, JSON.stringify({ userId, ...data, createdAt: Date.now() }) ); return sessionId; } async getSession(sessionId: string) { // Validate session ID format if (!/^[a-f0-9]{64}$/.test(sessionId)) { return null; } const data = await redis.get(`session:${sessionId}`); if (!data) return null; // Refresh TTL on access await redis.expire(`session:${sessionId}`, this.ttl); return JSON.parse(data); } async destroySession(sessionId: string) { await redis.del(`session:${sessionId}`); } async destroyAllUserSessions(userId: string) { // Track user's sessions in a set const sessions = await redis.smembers(`user:${userId}:sessions`); if (sessions.length > 0) { await redis.del(...sessions.map(s => `session:${s}`)); await redis.del(`user:${userId}:sessions`); } } }JWT Refresh Token Storage#
class TokenStore { // Store refresh token with user association async storeRefreshToken(token: string, userId: string, ttlDays: number = 30) { const key = `refresh:${token}`; const ttl = ttlDays * 24 * 60 * 60; await redis.pipeline() .setex(key, ttl, userId) .sadd(`user:${userId}:tokens`, token) .expire(`user:${userId}:tokens`, ttl) .exec(); } // Validate and get user ID async validateRefreshToken(token: string): Promise<string | null> { return redis.get(`refresh:${token}`); } // Revoke a specific token async revokeToken(token: string) { const userId = await redis.get(`refresh:${token}`); if (userId) { await redis.pipeline() .del(`refresh:${token}`) .srem(`user:${userId}:tokens`, token) .exec(); } } // Revoke all tokens for a user (logout everywhere) async revokeAllTokens(userId: string) { const tokens = await redis.smembers(`user:${userId}:tokens`); if (tokens.length > 0) { const pipeline = redis.pipeline(); tokens.forEach(t => pipeline.del(`refresh:${t}`)); pipeline.del(`user:${userId}:tokens`); await pipeline.exec(); } } }Rate Limiting for Auth#
class AuthRateLimiter { // Limit login attempts per IP async checkLoginAttempt(ip: string): Promise<{ allowed: boolean; remaining: number }> { const key = `ratelimit:login:${ip}`; const limit = 5; const window = 15 * 60; // 15 minutes const current = await redis.incr(key); if (current === 1) { await redis.expire(key, window); } return { allowed: current <= limit, remaining: Math.max(0, limit - current), }; } // Lock account after too many failures async recordFailedLogin(userId: string): Promise<boolean> { const key = `failed:login:${userId}`; const lockThreshold = 10; const window = 60 * 60; // 1 hour const failures = await redis.incr(key); if (failures === 1) { await redis.expire(key, window); } if (failures >= lockThreshold) { // Lock the account await redis.setex(`locked:${userId}`, window, '1'); return true; // Account is now locked } return false; } async isAccountLocked(userId: string): Promise<boolean> { return await redis.exists(`locked:${userId}`) === 1; } async clearFailedAttempts(userId: string) { await redis.del(`failed:login:${userId}`); } }Compliance#
GDPR#
Arctickey helps you comply with GDPR:
Data Retention#
Implement data retention policies:
// Set TTL on all user data await redis.setex(`user:${userId}`, 365 * 24 * 60 * 60, userData); // Or use EXPIRE on existing keys await redis.expire(`user:${userId}`, 365 * 24 * 60 * 60); // Scan and clean old data async function cleanOldData(pattern: string, maxAge: number) { let cursor = '0'; do { const [nextCursor, keys] = await redis.scan(cursor, 'MATCH', pattern, 'COUNT', 100); cursor = nextCursor; for (const key of keys) { const ttl = await redis.ttl(key); if (ttl === -1) { // No expiry set await redis.expire(key, maxAge); } } } while (cursor !== '0'); }Audit Logging#
Track sensitive operations:
class AuditLog { async log(event: { action: string; userId?: string; resource: string; details?: object; ip?: string; }) { const entry = { ...event, timestamp: new Date().toISOString(), }; // Store in a stream for durability await redis.xadd( 'audit:log', 'MAXLEN', '~', 100000, // Keep ~100k entries '*', 'data', JSON.stringify(entry) ); } async getRecentLogs(count: number = 100) { const entries = await redis.xrevrange('audit:log', '+', '-', 'COUNT', count); return entries.map(([id, fields]) => ({ id, ...JSON.parse(fields[1]), })); } } // Usage await auditLog.log({ action: 'user.login', userId: user.id, resource: 'auth', ip: req.ip, });Security Checklist#
rediss://(TLS) connection URL