feat: Enhance security and user management
Refines Firestore and Storage rules to enforce stricter access controls for signals and user-uploaded files. Updates user data handling to ensure root administrator privileges are correctly applied and enforced. Organizes uploaded scan files into user-specific directories within storage.
This commit is contained in:
+3
-1
@@ -79,7 +79,9 @@ service cloud.firestore {
|
|||||||
|
|
||||||
// --- Signals Collection ---
|
// --- Signals Collection ---
|
||||||
match /signals/{signalId} {
|
match /signals/{signalId} {
|
||||||
allow get, list: if isSignedIn() && (resource.data.userId == request.auth.uid || isDbAdmin());
|
allow get: if isSignedIn() && (resource.data.userId == request.auth.uid || isDbAdmin());
|
||||||
|
allow list: if isSignedIn() && resource.data.userId == request.auth.uid;
|
||||||
|
allow list: if isDbAdmin();
|
||||||
|
|
||||||
function isValidSignal(data) {
|
function isValidSignal(data) {
|
||||||
return data.keys().hasAll(['userId', 'pair', 'timeframe', 'decision', 'score', 'timestamp', 'result'])
|
return data.keys().hasAll(['userId', 'pair', 'timeframe', 'decision', 'score', 'timestamp', 'result'])
|
||||||
|
|||||||
+16
-1
@@ -16,7 +16,7 @@ import { NotificationManager } from './components/NotificationManager';
|
|||||||
import { TrendingUp, ShieldAlert, Ghost, Mail, Lock, UserPlus, LogIn, Loader2, ArrowLeft, User as UserIcon, Eye, EyeOff } from 'lucide-react';
|
import { TrendingUp, ShieldAlert, Ghost, Mail, Lock, UserPlus, LogIn, Loader2, ArrowLeft, User as UserIcon, Eye, EyeOff } from 'lucide-react';
|
||||||
import { motion, AnimatePresence } from 'motion/react';
|
import { motion, AnimatePresence } from 'motion/react';
|
||||||
import { onAuthStateChanged, signInWithEmailAndPassword, createUserWithEmailAndPassword, updateProfile, signOut, sendEmailVerification, sendPasswordResetEmail, User, setPersistence, browserLocalPersistence, browserSessionPersistence } from 'firebase/auth';
|
import { onAuthStateChanged, signInWithEmailAndPassword, createUserWithEmailAndPassword, updateProfile, signOut, sendEmailVerification, sendPasswordResetEmail, User, setPersistence, browserLocalPersistence, browserSessionPersistence } from 'firebase/auth';
|
||||||
import { doc, setDoc, getDoc } from 'firebase/firestore';
|
import { doc, setDoc, getDoc, updateDoc } from 'firebase/firestore';
|
||||||
import { auth, db, handleFirestoreError, OperationType } from './lib/firebase';
|
import { auth, db, handleFirestoreError, OperationType } from './lib/firebase';
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
@@ -68,6 +68,21 @@ export default function App() {
|
|||||||
|
|
||||||
if (userSnap.exists()) {
|
if (userSnap.exists()) {
|
||||||
data = { ...data, ...userSnap.data() };
|
data = { ...data, ...userSnap.data() };
|
||||||
|
|
||||||
|
// Force admin rights for root email
|
||||||
|
if (currentUser.email === "fxbrosinvestments00@gmail.com") {
|
||||||
|
data.isAdmin = true;
|
||||||
|
data.isApproved = true;
|
||||||
|
|
||||||
|
if (userSnap.data().isAdmin !== true || userSnap.data().isApproved !== true) {
|
||||||
|
try {
|
||||||
|
await updateDoc(userRef, { isAdmin: true, isApproved: true });
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to update root admin privileges:", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!data.isApproved && !data.isAdmin) {
|
if (!data.isApproved && !data.isAdmin) {
|
||||||
await signOut(auth);
|
await signOut(auth);
|
||||||
setAuthError('Sua conta foi criada. Aguarde aprovação manual do administrador.');
|
setAuthError('Sua conta foi criada. Aguarde aprovação manual do administrador.');
|
||||||
|
|||||||
@@ -66,13 +66,14 @@ export const AnalysisView: React.FC<{ userData?: any }> = ({ userData }) => {
|
|||||||
try {
|
try {
|
||||||
let downloadUrl = null;
|
let downloadUrl = null;
|
||||||
if (auth.currentUser) {
|
if (auth.currentUser) {
|
||||||
const fileRef = ref(storage, `files/${Date.now()}_${file.name}`);
|
const fileRef = ref(storage, `scans/${auth.currentUser.uid}/${Date.now()}_${file.name}`);
|
||||||
await uploadBytes(fileRef, file);
|
await uploadBytes(fileRef, file);
|
||||||
downloadUrl = await getDownloadURL(fileRef);
|
downloadUrl = await getDownloadURL(fileRef);
|
||||||
}
|
}
|
||||||
|
|
||||||
const base64 = preview.split(',')[1];
|
const base64 = preview.split(',')[1];
|
||||||
const analysis = await analyzeForexChart(base64, undefined, mode);
|
const analysis = await analyzeForexChart(base64, undefined, mode);
|
||||||
|
analysis.score = Math.round(analysis.score);
|
||||||
setResult(analysis);
|
setResult(analysis);
|
||||||
|
|
||||||
if (auth.currentUser) {
|
if (auth.currentUser) {
|
||||||
|
|||||||
@@ -14,5 +14,13 @@ service firebase.storage {
|
|||||||
&& request.resource.contentType.matches('image/.*');
|
&& request.resource.contentType.matches('image/.*');
|
||||||
allow delete: if isSignedIn() && (request.auth.uid == userId || isDbAdmin());
|
allow delete: if isSignedIn() && (request.auth.uid == userId || isDbAdmin());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
match /scans/{userId}/{fileName} {
|
||||||
|
allow read: if isSignedIn() && (request.auth.uid == userId || isDbAdmin());
|
||||||
|
allow create, update: if isSignedIn() && (request.auth.uid == userId || isDbAdmin())
|
||||||
|
&& request.resource.size < 5 * 1024 * 1024 // 5MB limit
|
||||||
|
&& request.resource.contentType.matches('image/.*');
|
||||||
|
allow delete: if isSignedIn() && (request.auth.uid == userId || isDbAdmin());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user