diff --git a/.env.example b/.env.example index ef7d3a5..e58b42d 100644 --- a/.env.example +++ b/.env.example @@ -1,7 +1,20 @@ -# Copy this file to .env and fill in values +# Copy this file to .env and fill in values. +# NOTE: .env is git-ignored. NEVER commit real credentials to this +# open-source repository — anyone can read them. + +# The port the server listens on. Render overrides this automatically. PORT=5000 NODE_ENV=development -# Optional — if omitted, the app falls back to in-memory storage +# --- MongoDB --- +# If omitted, the app falls back to in-memory storage (demo only). # Get a free cluster at https://www.mongodb.com/cloud/atlas -MONGODB_URI=mongodb://localhost:27017/quick-teams +# After creating your cluster, add "0.0.0.0/0" to Network Access so +# your deployed server can connect from anywhere. +MONGODB_URI=mongodb+srv://:@.mongodb.net/?appName= + +# --- CORS (optional, recommend setting for production) --- +# Comma-separated list of origins allowed to call the API. +# Leave empty to allow same-origin requests only. +# Example: CORS_ORIGIN=https://teamup.onrender.com,https://teamup.example.com +# CORS_ORIGIN= diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5100374..223ff2a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,8 +31,8 @@ jobs: - name: Syntax check server & client JS run: | node --check server.js - node --check dashboard.js - node --check script.js + node --check public/dashboard.js + node --check public/script.js node --check scripts/seedData.js node --check scripts/seed.js node --check utils/parser.js diff --git a/.gitignore b/.gitignore index 7f46d80..c0e0af6 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,8 @@ node_modules/ # Environment variables .env +*.env +!*.env.example # Logs logs diff --git a/Procfile b/Procfile new file mode 100644 index 0000000..6f86b16 --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +web: node server.js \ No newline at end of file diff --git a/README.md b/README.md index 1ea1fb1..c31f95e 100644 --- a/README.md +++ b/README.md @@ -125,6 +125,48 @@ curl "http://localhost:5000/api/search/teammates?skills=Machine%20Learning" --- +## 🌍 Deploying for Production + +Teamup is a full-stack Node/Express app with a MongoDB database and file uploads, so it needs a +**real server host** (not GitHub Pages, which only serves static files). Good options that offer a +free tier: **Render**, **Railway**, or **Fly.io**. + +### Prerequisites + +1. **MongoDB Atlas** must accept connections from the deployed server. In Atlas → + **Network Access** → add **`0.0.0.0/0`** (allow from anywhere). Keep strong credentials and store + them only in the host's environment variables — never in the repo. +2. Your code is on GitHub (e.g. `github.com/KRISHNA-24BCS127/Teamup`). + +### Deploy on Render (recommended) + +1. Push this repository to GitHub. +2. Create an account at [render.com](https://render.com) → **New → Web Service** → connect the repo + (or use **New → Blueprint** with the included [`render.yaml`](render.yaml)). +3. Configure the service: + - **Build command:** `npm install` + - **Start command:** `npm start` + - **Environment variables:** + - `MONGODB_URI` = your Atlas connection string, e.g. + `mongodb+srv://:@.mongodb.net/?appName=` + - `CORS_ORIGIN` = your deployed URL, e.g. `https://teamup.onrender.com` +4. Deploy. Render gives you a public `https://your-service.onrender.com` URL. + +> `Procfile` and `render.yaml` are included so the platform knows how to run the app. + +### 🛡️ Production security notes + +- **Never commit `.env` or `atlas-credentials.env`.** They are git-ignored. `.env.example` is the + safe template — fill in your real values only in the host's secret store. +- The server now serves **only the `public/` folder** as static assets. This prevents exposing + source files, `server.js`, or credentials that the old `express.static(__dirname)` setup leaked. +- CORS is restricted to the domains in `CORS_ORIGIN`. Leave it unset for same-origin use. +- For real production auth you should replace the demo SHA-256 password hashing with **bcrypt/argon2** + and stored uploaded resumes in cloud storage (the current `uploads/` disk storage is wiped on every + redeploy and is only suitable for parsing temp files). + +--- + ## 📁 Project Structure ``` @@ -142,8 +184,9 @@ Teamup/ │ └── parser.js ├── uploads/ # Temporary upload storage (.gitkeep only) ├── server.js # Application server & API controller -├── *.html # Landing, login, signup, dashboard pages -└── *.js # Client-side logic +├── public/ # Static frontend served by Express (HTML/CSS/JS/images) +├── Procfile # Platform start command (node server.js) +└── render.yaml # Render blueprint for deployment ``` --- diff --git a/2693171.jpg b/public/2693171.jpg similarity index 100% rename from 2693171.jpg rename to public/2693171.jpg diff --git a/about.html b/public/about.html similarity index 100% rename from about.html rename to public/about.html diff --git a/dashboard-fix.css b/public/dashboard-fix.css similarity index 100% rename from dashboard-fix.css rename to public/dashboard-fix.css diff --git a/dashboard.html b/public/dashboard.html similarity index 100% rename from dashboard.html rename to public/dashboard.html diff --git a/dashboard.js b/public/dashboard.js similarity index 100% rename from dashboard.js rename to public/dashboard.js diff --git a/db-status.html b/public/db-status.html similarity index 100% rename from db-status.html rename to public/db-status.html diff --git a/index.html b/public/index.html similarity index 100% rename from index.html rename to public/index.html diff --git a/login.html b/public/login.html similarity index 100% rename from login.html rename to public/login.html diff --git a/login.png b/public/login.png similarity index 100% rename from login.png rename to public/login.png diff --git a/logo.png b/public/logo.png similarity index 100% rename from logo.png rename to public/logo.png diff --git a/man.png b/public/man.png similarity index 100% rename from man.png rename to public/man.png diff --git a/script.js b/public/script.js similarity index 100% rename from script.js rename to public/script.js diff --git a/signup.html b/public/signup.html similarity index 100% rename from signup.html rename to public/signup.html diff --git a/style.css b/public/style.css similarity index 100% rename from style.css rename to public/style.css diff --git a/render.yaml b/render.yaml new file mode 100644 index 0000000..44ed4b2 --- /dev/null +++ b/render.yaml @@ -0,0 +1,26 @@ +# Blueprint for deploying Teamup on Render. +# Docs: https://render.com/docs/yaml-spec +# Usage: connect your GitHub repo on Render and select "Blueprint" (render.yaml). +# IMPORTANT: set REAL values for MONGODB_URI and CORS_ORIGIN in the Render +# dashboard AFTER creating the service — never commit real secrets to git. + +services: + - type: web + name: teamup + runtime: node + plan: free + buildCommand: npm install + startCommand: node server.js + healthCheckPath: /api/health + autoDeploy: true + envVars: + - key: NODE_ENV + value: production + - key: PORT + value: 10000 + # Filled in the Render dashboard (secret): mongodb+srv://:@cluster... + - key: MONGODB_URI + sync: false + # Comma-separated allowed origins, e.g. https://teamup.onrender.com + - key: CORS_ORIGIN + sync: false \ No newline at end of file diff --git a/server.js b/server.js index e28d1d9..5f1f445 100644 --- a/server.js +++ b/server.js @@ -33,11 +33,41 @@ const upload = multer({ dest: "uploads/" }); const app = express(); -// Middleware -app.use(cors()); +// Security middleware (production-safe defaults) +// - Stops Express from advertising its version (X-Powered-By) +// - Adds basic hardening headers; tuned to work with the static frontend +app.disable("x-powered-by"); +app.use((req, res, next) => { + res.setHeader("X-Content-Type-Options", "nosniff"); + res.setHeader("X-Frame-Options", "DENY"); + res.setHeader("Referrer-Policy", "no-referrer"); + res.setHeader("Permissions-Policy", "camera=(), microphone=(), geolocation=()"); + return next(); +}); + +// CORS — restrict to allowed origins when CORS_ORIGIN is set (comma-separated). +// Defaults to allowing the same-origin requests (no header needed) plus any +// explicitly configured origins. For production, set CORS_ORIGIN to your domain(s). +const allowedOrigins = (process.env.CORS_ORIGIN || "") + .split(",") + .map((o) => o.trim()) + .filter(Boolean); +app.use( + cors({ + origin: (origin, callback) => { + // Allow requests without an Origin (same-origin, curl, server-to-server) + if (!origin) return callback(null, true); + if (allowedOrigins.length === 0 || allowedOrigins.includes(origin)) { + return callback(null, true); + } + return callback(new Error("Not allowed by CORS")); + }, + }) +); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); -app.use(express.static(path.join(__dirname, "/"))); +// Serve ONLY the public/ folder — never the project root (protects .env, source, etc.) +app.use(express.static(path.join(__dirname, "public"))); // Ensure uploads directory exists if (!fs.existsSync(path.join(__dirname, "uploads"))) { @@ -612,12 +642,12 @@ app.get("/api/db-status", authenticateUser, async (req, res) => { // ------------------------------------------------------------- // HTML Page Serving // ------------------------------------------------------------- -app.get("/", (req, res) => res.sendFile(path.join(__dirname, "index.html"))); -app.get("/login", (req, res) => res.sendFile(path.join(__dirname, "login.html"))); -app.get("/signup", (req, res) => res.sendFile(path.join(__dirname, "signup.html"))); -app.get("/dashboard", (req, res) => res.sendFile(path.join(__dirname, "dashboard.html"))); -app.get("/about", (req, res) => res.sendFile(path.join(__dirname, "about.html"))); -app.get("/db-status", (req, res) => res.sendFile(path.join(__dirname, "db-status.html"))); +app.get("/", (req, res) => res.sendFile(path.join(__dirname, "public", "index.html"))); +app.get("/login", (req, res) => res.sendFile(path.join(__dirname, "public", "login.html"))); +app.get("/signup", (req, res) => res.sendFile(path.join(__dirname, "public", "signup.html"))); +app.get("/dashboard", (req, res) => res.sendFile(path.join(__dirname, "public", "dashboard.html"))); +app.get("/about", (req, res) => res.sendFile(path.join(__dirname, "public", "about.html"))); +app.get("/db-status", (req, res) => res.sendFile(path.join(__dirname, "public", "db-status.html"))); // 404 Fallback for unknown API routes app.use("/api", (req, res) => {