diff --git a/.env b/.env index e4d7f3a..5422409 100644 --- a/.env +++ b/.env @@ -5,7 +5,7 @@ HOST=0.0.0.0 # Database MONGODB_URI=mongodb://turbotrades:20yBBj!0@localhost:27017/turbotrades?authSource=admin - +#MONGODB_URI=mongodb://localhost:27017/turbotrades # Session SESSION_SECRET=5b4051d25c348e06ffebf77bc66fcb8db9c5b32f58eb8c6fac6c937b666fc733 diff --git a/ENV_SETUP.md b/ENV_SETUP.md new file mode 100644 index 0000000..2132d06 --- /dev/null +++ b/ENV_SETUP.md @@ -0,0 +1,214 @@ +# Environment Variables Setup Guide + +This guide explains how to manage environment variables for local development vs. production deployment. + +## 📁 File Structure + +``` +TurboTrades/ +├── .env # Production/deployment settings (COMMITTED to git) +├── .env.local # Local development overrides (NOT committed) +├── .env.example # Template for reference +└── .env.tunnel # Cloudflare tunnel settings (optional) +``` + +## 🔄 How It Works + +The application loads environment variables in this order: + +1. **`.env.local`** - Loaded first (highest priority) +2. **`.env`** - Loaded second (fallback values) + +This means: +- Variables in `.env.local` **override** those in `.env` +- `.env.local` is in `.gitignore` and **never gets pushed** to git +- `.env` contains your production settings and **is committed** to git + +## 🚀 Quick Setup + +### For Local Development + +1. **Copy the template:** + ```bash + # .env.local is already created for you! + # Just add your Steam API key + ``` + +2. **Add your Steam API Key to `.env.local`:** + ```env + STEAM_API_KEY=your_actual_steam_api_key_here + ``` + + Get your key from: https://steamcommunity.com/dev/apikey + +3. **Start the server:** + ```bash + npm run dev + ``` + + The server will automatically use: + - Local MongoDB (`mongodb://localhost:27017/turbotrades`) + - Local frontend (`http://localhost:5173`) + - No HTTPS required (`COOKIE_SECURE=false`) + +### For Production/Deployment + +Your `.env` file should contain production settings: +- Production MongoDB connection (MongoDB Atlas or remote server) +- Production domain for CORS +- Secure cookies enabled (`COOKIE_SECURE=true`) +- Production Steam return URL + +**Never commit sensitive production credentials!** Use environment variables in your hosting platform instead. + +## 🔧 Common Scenarios + +### Scenario 1: Switching Between Local and Production MongoDB + +**Problem:** You keep changing `MONGODB_URI` in `.env` and forgetting to change it back. + +**Solution:** +- Keep production MongoDB URI in `.env` +- Put local MongoDB URI in `.env.local` +- Never edit `.env` for local development! + +```env +# .env (production - committed) +MONGODB_URI=mongodb+srv://user:pass@cluster.mongodb.net/turbotrades + +# .env.local (local dev - not committed) +MONGODB_URI=mongodb://localhost:27017/turbotrades +``` + +### Scenario 2: Testing with Cloudflare Tunnel + +Use `.env.tunnel` for tunnel-specific settings: + +```bash +# Copy tunnel settings +cp .env.tunnel .env.local + +# Or manually edit .env.local with your tunnel URL +``` + +### Scenario 3: Team Development + +**Each developer should:** +1. Keep their own `.env.local` (never commit it!) +2. Share the `.env` file with production-like defaults +3. Update `.env.example` when adding new variables + +## 📝 Environment Variables Reference + +### Database +```env +MONGODB_URI=mongodb://localhost:27017/turbotrades +``` + +### Server +```env +NODE_ENV=development # or 'production' +PORT=3000 +HOST=0.0.0.0 +``` + +### Steam OAuth +```env +STEAM_API_KEY=your_key_here +STEAM_REALM=http://localhost:3000 +STEAM_RETURN_URL=http://localhost:3000/api/auth/steam/return +``` + +### CORS & Cookies +```env +CORS_ORIGIN=http://localhost:5173 +COOKIE_DOMAIN=localhost +COOKIE_SECURE=false # true for production +COOKIE_SAME_SITE=lax +``` + +### JWT & Sessions +```env +JWT_ACCESS_SECRET=your-secret-here +JWT_REFRESH_SECRET=your-secret-here +JWT_ACCESS_EXPIRY=15m +JWT_REFRESH_EXPIRY=7d +SESSION_SECRET=your-session-secret +``` + +### Rate Limiting +```env +RATE_LIMIT_MAX=100 +RATE_LIMIT_TIMEWINDOW=60000 +``` + +## ⚠️ Important Notes + +### ✅ DO: +- Use `.env.local` for local development +- Commit `.env` with safe default values +- Keep sensitive keys in `.env.local` +- Update `.env.example` when adding new variables +- Use your hosting platform's environment variables for production secrets + +### ❌ DON'T: +- Commit `.env.local` to git (it's in `.gitignore`) +- Store real API keys in `.env` (use placeholders) +- Edit `.env` for local development +- Share your `.env.local` file with others +- Commit sensitive credentials to the repository + +## 🔍 Troubleshooting + +### "Authentication failed" MongoDB Error +**Cause:** Using production MongoDB URI with credentials locally. + +**Fix:** Make sure `.env.local` has: +```env +MONGODB_URI=mongodb://localhost:27017/turbotrades +``` + +### "CORS Error" in Browser +**Cause:** Backend and frontend origins don't match. + +**Fix:** Check `.env.local`: +```env +CORS_ORIGIN=http://localhost:5173 +``` + +### "Steam authentication failed" +**Cause:** Missing or invalid Steam API key. + +**Fix:** Add your key to `.env.local`: +```env +STEAM_API_KEY=your_actual_key_from_steam +``` +Get it here: https://steamcommunity.com/dev/apikey + +### Changes not taking effect +**Cause:** Server not restarting or wrong file being edited. + +**Fix:** +1. Make sure you're editing `.env.local` (not `.env`) +2. Restart the server: `npm run dev` +3. Clear `node_modules/.cache` if needed + +## 📚 Additional Resources + +- [dotenv documentation](https://github.com/motdotla/dotenv) +- [Steam Web API documentation](https://steamcommunity.com/dev) +- [MongoDB Connection Strings](https://www.mongodb.com/docs/manual/reference/connection-string/) + +## 🤝 Contributing + +When adding new environment variables: + +1. Add it to `config/index.js` with a sensible default +2. Add it to `.env.example` with a placeholder value +3. Document it in this file +4. Update `API_ENDPOINTS.md` if it affects API behavior + +--- + +**Last Updated:** 2024 +**Maintainer:** TurboTrades Development Team \ No newline at end of file diff --git a/config/index.js b/config/index.js index 795b565..4d75e54 100644 --- a/config/index.js +++ b/config/index.js @@ -1,6 +1,13 @@ import dotenv from "dotenv"; +import { fileURLToPath } from "url"; +import { dirname, join } from "path"; -dotenv.config(); +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +// Load .env.local first (for local development overrides), then .env +dotenv.config({ path: join(__dirname, "..", ".env.local") }); +dotenv.config({ path: join(__dirname, "..", ".env") }); export const config = { // Server diff --git a/frontend/src/components/NavBar.vue b/frontend/src/components/NavBar.vue index 6d4cf22..ebcb17a 100644 --- a/frontend/src/components/NavBar.vue +++ b/frontend/src/components/NavBar.vue @@ -250,12 +250,17 @@ onUnmounted(() => { - diff --git a/frontend/src/stores/auth.js b/frontend/src/stores/auth.js index e2d8e09..4e90017 100644 --- a/frontend/src/stores/auth.js +++ b/frontend/src/stores/auth.js @@ -1,223 +1,238 @@ -import { defineStore } from 'pinia' -import { ref, computed } from 'vue' -import axios from 'axios' -import { useToast } from 'vue-toastification' +import { defineStore } from "pinia"; +import { ref, computed } from "vue"; +import axios from "axios"; +import { useToast } from "vue-toastification"; -const toast = useToast() +const toast = useToast(); -export const useAuthStore = defineStore('auth', () => { +export const useAuthStore = defineStore("auth", () => { // State - const user = ref(null) - const isAuthenticated = ref(false) - const isLoading = ref(false) - const isInitialized = ref(false) + const user = ref(null); + const isAuthenticated = ref(false); + const isLoading = ref(false); + const isInitialized = ref(false); // Computed - const username = computed(() => user.value?.username || 'Guest') - const steamId = computed(() => user.value?.steamId || null) - const avatar = computed(() => user.value?.avatar || null) - const balance = computed(() => user.value?.balance || 0) - const staffLevel = computed(() => user.value?.staffLevel || 0) - const isStaff = computed(() => staffLevel.value > 0) - const isModerator = computed(() => staffLevel.value >= 2) - const isAdmin = computed(() => staffLevel.value >= 3) - const tradeUrl = computed(() => user.value?.tradeUrl || null) - const email = computed(() => user.value?.email?.address || null) - const emailVerified = computed(() => user.value?.email?.verified || false) - const isBanned = computed(() => user.value?.ban?.banned || false) - const banReason = computed(() => user.value?.ban?.reason || null) - const twoFactorEnabled = computed(() => user.value?.twoFactor?.enabled || false) + const username = computed(() => user.value?.username || "Guest"); + const steamId = computed(() => user.value?.steamId || null); + const avatar = computed(() => user.value?.avatar || null); + const balance = computed(() => user.value?.balance || 0); + const staffLevel = computed(() => user.value?.staffLevel || 0); + const isStaff = computed(() => staffLevel.value > 0); + const isModerator = computed(() => staffLevel.value >= 2); + const isAdmin = computed(() => staffLevel.value >= 3); + const tradeUrl = computed(() => user.value?.tradeUrl || null); + const email = computed(() => user.value?.email?.address || null); + const emailVerified = computed(() => user.value?.email?.verified || false); + const isBanned = computed(() => user.value?.ban?.banned || false); + const banReason = computed(() => user.value?.ban?.reason || null); + const twoFactorEnabled = computed( + () => user.value?.twoFactor?.enabled || false + ); // Actions const setUser = (userData) => { - user.value = userData - isAuthenticated.value = !!userData - } + user.value = userData; + isAuthenticated.value = !!userData; + }; const clearUser = () => { - user.value = null - isAuthenticated.value = false - } + user.value = null; + isAuthenticated.value = false; + }; const fetchUser = async () => { - if (isLoading.value) return + if (isLoading.value) return; - isLoading.value = true + isLoading.value = true; try { - const response = await axios.get('/api/auth/me', { + const response = await axios.get("/api/auth/me", { withCredentials: true, - }) + }); if (response.data.success && response.data.user) { - setUser(response.data.user) - return response.data.user + setUser(response.data.user); + return response.data.user; } else { - clearUser() - return null + clearUser(); + return null; } } catch (error) { - console.error('Failed to fetch user:', error) - clearUser() - return null + console.error("Failed to fetch user:", error); + clearUser(); + return null; } finally { - isLoading.value = false - isInitialized.value = true + isLoading.value = false; + isInitialized.value = true; } - } + }; const login = () => { - // Redirect to Steam login - window.location.href = '/api/auth/steam' - } + // Redirect to Steam login on backend API domain + const apiUrl = import.meta.env.VITE_API_URL || "http://localhost:3000"; + window.location.href = `${apiUrl}/api/auth/steam`; + }; const logout = async () => { - isLoading.value = true + isLoading.value = true; try { - await axios.post('/api/auth/logout', {}, { - withCredentials: true, - }) + await axios.post( + "/api/auth/logout", + {}, + { + withCredentials: true, + } + ); - clearUser() - toast.success('Successfully logged out') + clearUser(); + toast.success("Successfully logged out"); // Redirect to home page - if (window.location.pathname !== '/') { - window.location.href = '/' + if (window.location.pathname !== "/") { + window.location.href = "/"; } } catch (error) { - console.error('Logout error:', error) - toast.error('Failed to logout') + console.error("Logout error:", error); + toast.error("Failed to logout"); } finally { - isLoading.value = false + isLoading.value = false; } - } + }; const refreshToken = async () => { try { - await axios.post('/api/auth/refresh', {}, { - withCredentials: true, - }) - return true + await axios.post( + "/api/auth/refresh", + {}, + { + withCredentials: true, + } + ); + return true; } catch (error) { - console.error('Token refresh failed:', error) - clearUser() - return false + console.error("Token refresh failed:", error); + clearUser(); + return false; } - } + }; const updateTradeUrl = async (tradeUrl) => { - isLoading.value = true + isLoading.value = true; try { - const response = await axios.patch('/api/user/trade-url', + const response = await axios.patch( + "/api/user/trade-url", { tradeUrl }, { withCredentials: true } - ) + ); if (response.data.success) { - user.value.tradeUrl = tradeUrl - toast.success('Trade URL updated successfully') - return true + user.value.tradeUrl = tradeUrl; + toast.success("Trade URL updated successfully"); + return true; } - return false + return false; } catch (error) { - console.error('Failed to update trade URL:', error) - toast.error(error.response?.data?.message || 'Failed to update trade URL') - return false + console.error("Failed to update trade URL:", error); + toast.error( + error.response?.data?.message || "Failed to update trade URL" + ); + return false; } finally { - isLoading.value = false + isLoading.value = false; } - } + }; const updateEmail = async (email) => { - isLoading.value = true + isLoading.value = true; try { - const response = await axios.patch('/api/user/email', + const response = await axios.patch( + "/api/user/email", { email }, { withCredentials: true } - ) + ); if (response.data.success) { - user.value.email = { address: email, verified: false } - toast.success('Email updated! Check your inbox for verification link') - return true + user.value.email = { address: email, verified: false }; + toast.success("Email updated! Check your inbox for verification link"); + return true; } - return false + return false; } catch (error) { - console.error('Failed to update email:', error) - toast.error(error.response?.data?.message || 'Failed to update email') - return false + console.error("Failed to update email:", error); + toast.error(error.response?.data?.message || "Failed to update email"); + return false; } finally { - isLoading.value = false + isLoading.value = false; } - } + }; const verifyEmail = async (token) => { - isLoading.value = true + isLoading.value = true; try { const response = await axios.get(`/api/user/verify-email/${token}`, { - withCredentials: true - }) + withCredentials: true, + }); if (response.data.success) { - toast.success('Email verified successfully!') - await fetchUser() // Refresh user data - return true + toast.success("Email verified successfully!"); + await fetchUser(); // Refresh user data + return true; } - return false + return false; } catch (error) { - console.error('Failed to verify email:', error) - toast.error(error.response?.data?.message || 'Failed to verify email') - return false + console.error("Failed to verify email:", error); + toast.error(error.response?.data?.message || "Failed to verify email"); + return false; } finally { - isLoading.value = false + isLoading.value = false; } - } + }; const getUserStats = async () => { try { - const response = await axios.get('/api/user/stats', { - withCredentials: true - }) + const response = await axios.get("/api/user/stats", { + withCredentials: true, + }); if (response.data.success) { - return response.data.stats + return response.data.stats; } - return null + return null; } catch (error) { - console.error('Failed to fetch user stats:', error) - return null + console.error("Failed to fetch user stats:", error); + return null; } - } + }; const getBalance = async () => { try { - const response = await axios.get('/api/user/balance', { - withCredentials: true - }) + const response = await axios.get("/api/user/balance", { + withCredentials: true, + }); if (response.data.success) { - user.value.balance = response.data.balance - return response.data.balance + user.value.balance = response.data.balance; + return response.data.balance; } - return null + return null; } catch (error) { - console.error('Failed to fetch balance:', error) - return null + console.error("Failed to fetch balance:", error); + return null; } - } + }; const updateBalance = (newBalance) => { if (user.value) { - user.value.balance = newBalance + user.value.balance = newBalance; } - } + }; // Initialize on store creation const initialize = async () => { if (!isInitialized.value) { - await fetchUser() + await fetchUser(); } - } + }; return { // State @@ -256,5 +271,5 @@ export const useAuthStore = defineStore('auth', () => { getBalance, updateBalance, initialize, - } -}) + }; +}); diff --git a/frontend/src/views/HomePage.vue b/frontend/src/views/HomePage.vue index 49d7a82..fddaac9 100644 --- a/frontend/src/views/HomePage.vue +++ b/frontend/src/views/HomePage.vue @@ -381,13 +381,14 @@ const getRarityColor = (rarity) => {