diff --git a/.devcontainer/SLSA3 Workflow yml b/.devcontainer/SLSA3 Workflow yml new file mode 100644 index 0000000..6574283 --- /dev/null +++ b/.devcontainer/SLSA3 Workflow yml @@ -0,0 +1,516 @@ +I'll provide you with comprehensive Firebase security implementation guidance. Here's a detailed security setup: + +## 1. Firebase Security Rules & Configuration + +### Firebase Security Rules (`firebase.json`) +```json +{ + "firestore": { + "rules": "firestore.rules", + "indexes": "firestore.indexes.json" + }, + "storage": { + "rules": "storage.rules" + }, + "hosting": { + "public": "public", + "ignore": ["firebase.json", "**/.*", "**/node_modules/**"], + "headers": [ + { + "source": "**", + "headers": [ + { + "key": "X-Frame-Options", + "value": "DENY" + }, + { + "key": "X-Content-Type-Options", + "value": "nosniff" + }, + { + "key": "X-XSS-Protection", + "value": "1; mode=block" + }, + { + "key": "Strict-Transport-Security", + "value": "max-age=31536000; includeSubDomains" + } + ] + } + ], + "rewrites": [ + { + "source": "**", + "destination": "/index.html" + } + ] + }, + "emulators": { + "auth": { + "port": 9099 + }, + "firestore": { + "port": 8080 + }, + "storage": { + "port": 9199 + }, + "ui": { + "enabled": true + } + } +} +``` + +## 2. Firestore Security Rules + +### `firestore.rules` +```javascript +rules_version = '2'; +service cloud.firestore { + match /databases/{database}/documents { + // Global read/write control - adjust as needed + match /{document=**} { + allow read, write: if request.auth != null; + } + + // Users can only access their own user data + match /users/{userId} { + allow read, write: if request.auth != null && request.auth.uid == userId; + allow create: if request.auth != null && + request.auth.uid == userId && + request.resource.data.keys().hasAll(['email', 'createdAt']) && + request.resource.data.email is string && + request.resource.data.createdAt is timestamp; + } + + // Posts collection - users can read all, but only write their own + match /posts/{postId} { + allow read: if request.auth != null; + allow create: if request.auth != null && + request.auth.uid == request.resource.data.authorId && + request.resource.data.keys().hasAll(['title', 'content', 'createdAt']) && + request.resource.data.title is string && + request.resource.data.content is string && + request.resource.data.createdAt is timestamp; + + allow update, delete: if request.auth != null && + request.auth.uid == resource.data.authorId; + } + + // Comments - users can read all, but only write their own + match /posts/{postId}/comments/{commentId} { + allow read: if request.auth != null; + allow create: if request.auth != null && + request.auth.uid == request.resource.data.authorId && + request.resource.data.keys().hasAll(['content', 'createdAt']) && + request.resource.data.content is string && + request.resource.data.createdAt is timestamp; + + allow update, delete: if request.auth != null && + request.auth.uid == resource.data.authorId; + } + + // Admin role check function + function isAdmin() { + return request.auth.token.admin == true; + } + + // Admin-only collections + match /admin/{document=**} { + allow read, write: if isAdmin(); + } + + // Audit trail - read only for admins + match /audit/{document=**} { + allow read: if isAdmin(); + allow write: if request.auth != null; + } + } +} +``` + +## 3. Firebase Storage Security Rules + +### `storage.rules` +```javascript +rules_version = '2'; +service firebase.storage { + match /b/{bucket}/o { + // Users can only upload to their own folder + match /users/{userId}/{allPaths=**} { + allow read: if request.auth != null; + allow write: if request.auth != null && request.auth.uid == userId; + } + + // Profile pictures - users can read all, but only write their own + match /profile_pictures/{userId} { + allow read: if request.auth != null; + allow write: if request.auth != null && request.auth.uid == userId; + } + + // Post images - readable by all authenticated users + match /post_images/{postId}/{imageId} { + allow read: if request.auth != null; + allow write: if request.auth != null && + exists(/databases/$(database)/documents/posts/$(postId)) && + get(/databases/$(database)/documents/posts/$(postId)).data.authorId == request.auth.uid; + } + + // Public read-only files + match /public/{allPaths=**} { + allow read: if true; + allow write: if request.auth != null && isAdmin(); + } + + function isAdmin() { + return request.auth.token.admin == true; + } + } +} +``` + +## 4. Firebase Authentication Security + +### Security Configuration +```javascript +// firebase-auth-security.js +import { initializeApp } from 'firebase/app'; +import { + getAuth, + createUserWithEmailAndPassword, + signInWithEmailAndPassword, + signOut, + updateProfile, + sendEmailVerification, + sendPasswordResetEmail, + setPersistence, + browserSessionPersistence, + browserLocalPersistence, + onAuthStateChanged +} from 'firebase/auth'; + +const firebaseConfig = { + apiKey: process.env.REACT_APP_FIREBASE_API_KEY, + authDomain: process.env.REACT_APP_FIREBASE_AUTH_DOMAIN, + projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID, + storageBucket: process.env.REACT_APP_FIREBASE_STORAGE_BUCKET, + messagingSenderId: process.env.REACT_APP_FIREBASE_MESSAGING_SENDER_ID, + appId: process.env.REACT_APP_FIREBASE_APP_ID +}; + +const app = initializeApp(firebaseConfig); +const auth = getAuth(app); + +class FirebaseAuthSecurity { + constructor() { + this.auth = auth; + this.setupSecurityPolicies(); + } + + setupSecurityPolicies() { + // Set session persistence based on user preference + setPersistence(this.auth, browserLocalPersistence); + + // Monitor auth state for security + onAuthStateChanged(this.auth, (user) => { + if (user) { + this.logSecurityEvent('user_signed_in', user.uid); + this.validateUserSession(user); + } else { + this.logSecurityEvent('user_signed_out'); + } + }); + } + + async validateUserSession(user) { + // Check if email is verified + if (!user.emailVerified) { + await sendEmailVerification(user); + throw new Error('Please verify your email before proceeding'); + } + + // Check if token is recent (less than 1 hour old) + const tokenTime = user.metadata.lastSignInTime; + const currentTime = new Date(); + const timeDiff = (currentTime - new Date(tokenTime)) / (1000 * 60 * 60); + + if (timeDiff > 24) { + await this.auth.signOut(); + throw new Error('Session expired. Please sign in again.'); + } + } + + async secureSignUp(email, password, displayName) { + try { + // Validate password strength + this.validatePassword(password); + + const userCredential = await createUserWithEmailAndPassword( + this.auth, email, password + ); + + // Update profile + await updateProfile(userCredential.user, { + displayName: displayName + }); + + // Send email verification + await sendEmailVerification(userCredential.user); + + // Log security event + this.logSecurityEvent('user_registered', userCredential.user.uid); + + return userCredential; + } catch (error) { + this.logSecurityEvent('registration_failed', null, error.message); + throw error; + } + } + + async secureSignIn(email, password) { + try { + const userCredential = await signInWithEmailAndPassword( + this.auth, email, password + ); + + // Validate session + await this.validateUserSession(userCredential.user); + + this.logSecurityEvent('login_success', userCredential.user.uid); + return userCredential; + } catch (error) { + this.logSecurityEvent('login_failed', null, error.message); + throw error; + } + } + + validatePassword(password) { + const minLength = 8; + const hasUpperCase = /[A-Z]/.test(password); + const hasLowerCase = /[a-z]/.test(password); + const hasNumbers = /\d/.test(password); + const hasSpecialChar = /[!@#$%^&*(),.?":{}|<>]/.test(password); + + if (password.length < minLength) { + throw new Error('Password must be at least 8 characters long'); + } + if (!hasUpperCase) { + throw new Error('Password must contain at least one uppercase letter'); + } + if (!hasLowerCase) { + throw new Error('Password must contain at least one lowercase letter'); + } + if (!hasNumbers) { + throw new Error('Password must contain at least one number'); + } + if (!hasSpecialChar) { + throw new Error('Password must contain at least one special character'); + } + } + + async logSecurityEvent(eventType, userId = null, details = null) { + // Log to Firestore for audit trail + const securityLog = { + eventType, + userId, + timestamp: new Date(), + userAgent: navigator.userAgent, + ipAddress: await this.getClientIP(), + details + }; + + // In a real app, you'd write this to Firestore + console.log('Security Event:', securityLog); + } + + async getClientIP() { + // This would typically be handled by a cloud function + // that can see the real IP address + return 'client-ip-not-available-in-client'; + } +} + +export default FirebaseAuthSecurity; +``` + +## 5. Firebase App Check Implementation + +```javascript +// firebase-app-check.js +import { initializeAppCheck, ReCaptchaV3Provider } from 'firebase/app-check'; + +const appCheck = initializeAppCheck(app, { + provider: new ReCaptchaV3Provider('your-recaptcha-v3-site-key'), + isTokenAutoRefreshEnabled: true +}); +``` + +## 6. Firebase Security Functions + +### Cloud Functions for Security +```javascript +// functions/security.js +const functions = require('firebase-functions'); +const admin = require('firebase-admin'); +admin.initializeApp(); + +// Security: Monitor for suspicious activities +exports.monitorSuspiciousActivities = functions.firestore + .document('audit/{auditId}') + .onCreate(async (snapshot, context) => { + const auditData = snapshot.data(); + + // Check for multiple failed logins + if (auditData.eventType === 'login_failed') { + const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000); + + const failedLogins = await admin.firestore() + .collection('audit') + .where('eventType', '==', 'login_failed') + .where('timestamp', '>', oneHourAgo) + .where('userId', '==', auditData.userId) + .get(); + + if (failedLogins.size > 5) { + // Suspend account or send alert + await admin.firestore() + .collection('security_alerts') + .add({ + type: 'suspicious_login_attempts', + userId: auditData.userId, + count: failedLogins.size, + timestamp: new Date() + }); + } + } + }); + +// Security: Clean up old audit logs +exports.cleanupOldLogs = functions.pubsub + .schedule('every 24 hours') + .onRun(async (context) => { + const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000); + + const oldLogs = await admin.firestore() + .collection('audit') + .where('timestamp', '<', thirtyDaysAgo) + .get(); + + const batch = admin.firestore().batch(); + oldLogs.docs.forEach(doc => { + batch.delete(doc.ref); + }); + + await batch.commit(); + console.log(`Cleaned up ${oldLogs.size} old audit logs`); + }); +``` + +## 7. Environment Security Configuration + +### `.env` file +```env +# Firebase Configuration +REACT_APP_FIREBASE_API_KEY=your_api_key_here +REACT_APP_FIREBASE_AUTH_DOMAIN=your-project.firebaseapp.com +REACT_APP_FIREBASE_PROJECT_ID=your-project-id +REACT_APP_FIREBASE_STORAGE_BUCKET=your-project.appspot.com +REACT_APP_FIREBASE_MESSAGING_SENDER_ID=your_sender_id +REACT_APP_FIREBASE_APP_ID=your_app_id + +# Security +REACT_APP_RECAPTCHA_SITE_KEY=your_recaptcha_site_key +REACT_APP_SECURITY_LOG_RETENTION_DAYS=30 +``` + +## 8. Security Best Practices Implementation + +```javascript +// security-best-practices.js +export class SecurityBestPractices { + static async validateFileUpload(file) { + // Check file size (max 5MB) + const maxSize = 5 * 1024 * 1024; + if (file.size > maxSize) { + throw new Error('File size too large. Maximum 5MB allowed.'); + } + + // Check file type + const allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'application/pdf']; + if (!allowedTypes.includes(file.type)) { + throw new Error('File type not allowed.'); + } + + // Check for potential malicious files + if (file.name.includes('..') || file.name.includes('/')) { + throw new Error('Invalid file name.'); + } + } + + static sanitizeUserInput(input) { + // Basic XSS prevention + return input + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, ''') + .replace(/\//g, '/'); + } + + static validateEmail(email) { + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + return emailRegex.test(email); + } + + static generateSecureToken(length = 32) { + const array = new Uint8Array(length); + crypto.getRandomValues(array); + return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join(''); + } +} +``` + +## 9. Usage Example + +```javascript +// app.js +import FirebaseAuthSecurity from './firebase-auth-security'; +import { SecurityBestPractices } from './security-best-practices'; + +const authSecurity = new FirebaseAuthSecurity(); + +// Secure user registration +async function registerUser(email, password, displayName) { + try { + await SecurityBestPractices.validateEmail(email); + const userCredential = await authSecurity.secureSignUp(email, password, displayName); + console.log('User registered securely:', userCredential.user.uid); + } catch (error) { + console.error('Registration failed:', error.message); + } +} + +// Secure file upload +async function uploadFile(file, userId) { + try { + await SecurityBestPractices.validateFileUpload(file); + // Proceed with Firebase Storage upload + } catch (error) { + console.error('File upload rejected:', error.message); + } +} +``` + +This comprehensive Firebase security implementation provides: + +- ✅ **Firestore Security Rules** with role-based access +- ✅ **Storage Security Rules** with user isolation +- ✅ **Authentication Security** with email verification and session management +- ✅ **App Check** for API protection +- ✅ **Security Monitoring** with audit trails +- ✅ **Input Validation** and sanitization +- ✅ **File Upload Security** with type and size validation +- ✅ **Security Headers** for web hosting + +The implementation follows Firebase security best practices and provides multiple layers of protection for your application.