Skip to content

Latest commit

 

History

8 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 

Repository files navigation

JobPilot — Split Architecture (Frontend + Backend)

Strict Authentication System: MongoDB + JWT + OAuth (Google, GitHub, Email)

This repository has been split into dedicated frontend (/client) and backend (/server) with a strict login system. Frontend no longer talks to Supabase directly; all data flows through the backend API secured by JWT + httpOnly cookies.

accio-job-market/
├─ client/   # React + Vite frontend (port 5173)
└─ server/   # Express + MongoDB backend (port 5000)

🔐 Strict Auth Overview

Layer Implementation
Database MongoDB (Mongoose) — User, Job, Application, SavedJob
JWT Access (15m) + Refresh (7d) via jsonwebtoken
Email POST /api/auth/register + POST /api/auth/login with bcrypt (12 rounds)
OAuth Google (passport-google-oauth20) + GitHub (passport-github2) → JWT + redirect to CLIENT_URL/oauth/callback?accessToken=...
Cookies httpOnly accessToken (15m) + refreshToken (7d, path /api/auth)
Validation express-validator strict (name 2-50, email, password 8-64 + upper/lower/num/special)
Security helmet, cors whitelist, express-rate-limit (auth 20/15m, login 10/15m), account lockout (5 fails → 15 min), cookie-parser, body limit 10kb
Middleware authenticate (Bearer or cookie, verify + user existence + lock check), authorize(role)
Frontend AuthContext + api.js (axios, Bearer, auto-refresh on 401 TOKEN_EXPIRED), ProtectedRoute / PublicRoute, Login / Register / OAuthCallback screens

Token Flow

  1. POST /api/auth/login → sets httpOnly cookies + returns accessToken
  2. Frontend stores accessToken in localStorage, sends Authorization: Bearer <token>
  3. On 401 TOKEN_EXPIRED, axios interceptor calls POST /api/auth/refresh (cookie) → new accessToken → retry
  4. On refresh failure, clear storage + redirect to /login?error=session_expired
  5. POST /api/auth/logout clears both cookies

Password Policy (Strict)

  • 8-64 chars
  • Requires: /(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&.#^_-])/
  • Bcrypt salt 12, select: false on password field

Lockout

  • MAX_LOGIN_ATTEMPTS=5, LOCK_TIME_MS=900000 (15 min)
  • loginAttempts increment on fail, lockUntil set when threshold hit, reset on success
  • OAuth users without password must use OAuth (provider !== 'local')

📁 Project Split

Client (/client) — Frontend

  • Stack: React 18, React Router 6, Axios, Vite, Tailwind, Chart.js
  • Env: VITE_API_URL=http://localhost:5000 (see .env.example)
  • No Direct Supabase: src/lib/supabase.js is now a deprecated shim (throws if used). All data via src/lib/api.js → backend.
  • Auth: src/context/AuthContext.jsx, src/components/ProtectedRoute.jsx
  • App Data: src/context/AppContext.jsx now calls GET /api/profile/dashboard, POST /api/jobs/import, etc., with _id → id normalization
  • Routes:
    • Public: /login, /register, /oauth/callback
    • Protected (via Layout): /dashboard, /explorer, /source, /resume, /recent, /trending
    • Layout.jsx shows user (initials, provider badge) + logout, strict badge
cd client
cp .env.example .env   # set VITE_API_URL
npm install
npm run dev            # http://localhost:5173
npm run build

Vite proxies /api → VITE_API_URL (see vite.config.js).

Server (/server) — Backend

  • Stack: Express 5, Mongoose 8, Passport (Google/GitHub), JWT, bcryptjs, helmet, cors, cookie-parser
  • Env: see .env.example (MONGO_URI, JWT secrets, OAuth creds, CLIENT_URL)
  • Entry: src/index.js (helmet, cors whitelist, passport init, /api/auth, /api/jobs, /api/profile)
  • Models: User.js (with lockout), Job.js, Application.js, SavedJob.js
  • Routes:
    • GET /, GET /api/health
    • POST /api/auth/register, POST /api/auth/login, POST /api/auth/refresh, POST /api/auth/logout, GET /api/auth/me, PUT /api/auth/profile, GET /api/auth/google, GET /api/auth/google/callback, GET /api/auth/github, GET /api/auth/github/callback, GET /api/auth/health
    • GET /api/jobs, POST /api/jobs/import, GET /api/jobs/saved, POST /api/jobs/:id/save, DELETE /api/jobs/:id/save, GET /api/jobs/applications/all, PUT /api/jobs/:id/application, POST /api/jobs/live-search
    • GET /api/profile, PUT /api/profile, GET /api/profile/dashboard, POST /api/profile/resume-upload, POST /api/profile/send-job-emails
cd server
cp .env.example .env   # set MONGO_URI, JWT secrets, OAuth creds
npm install
npm run dev            # http://localhost:5000 (nodemon)
npm start              # production

Tested: npm run dev connects to mongodb://localhost:27017/jobpilot, auth flow works, 401 without token, lockout after 5 fails.


🚀 Quick Start (Both)

# Terminal 1 — Backend
cd server && npm install && npm run dev
# → [DB] MongoDB connected: localhost / jobpilot
# → [Server] Running on http://localhost:5000

# Terminal 2 — Frontend
cd client && npm install && npm run dev
# → Vite dev server at http://localhost:5173
  • Register at http://localhost:5173/register (strict password) or login via Google/GitHub (requires OAuth creds in server .env)
  • All /dashboard etc. are protected — unauthenticated redirect to /login

🔑 OAuth Setup

Google: https://console.cloud.google.com/apis/credentials

  • Create OAuth 2.0 Client ID (Web)
  • Authorized redirect URI: http://localhost:5000/api/auth/google/callback (or prod GOOGLE_CALLBACK_URL)
  • Set GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, GOOGLE_CALLBACK_URL in server/.env

GitHub: https://github.com/settings/developers

  • New OAuth App → Callback URL: http://localhost:5000/api/auth/github/callback
  • Set GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET, GITHUB_CALLBACK_URL

If not configured, /api/auth/health shows google: false, github: false, and /api/auth/google returns 503 with message.


🛡️ Strict Login Checks

  • Missing token → 401 Authentication required
  • Expired → 401 TOKEN_EXPIRED → auto-refresh
  • Invalid → 401 TOKEN_INVALID
  • Locked → 423 Account locked...
  • Rate-limit → 429 Too many requests
  • Weak password → 400 Password must contain uppercase, ...
  • Duplicate email → 409 Email already registered
  • OAuth user password login → 400 Please login with google/github OAuth

📦 Migration from Supabase

  • Removed VITE_SUPABASE_URL/VITE_SUPABASE_ANON_KEY direct usage
  • src/lib/supabase.js now shim; ResumeUploadScreen & JobSourceScreen now use api + backend endpoints
  • supabase/ functions kept for reference but not used; backend re-implements live-search and send-job-emails as POST /api/jobs/live-search and POST /api/profile/send-job-emails (demo stubs, integrate JSearch/Adzuna/Nodemailer)
  • Jobs/profile data now per-user (user: ObjectId filter, JWT scoped)

🧪 Verified

  • client: npm run build ✓ (1.3M chunk, 2297 modules)
  • server: GET /api/health, GET /api/auth/health ✓
  • POST /api/auth/register → 201 + JWT, weak password → 400, duplicate → 409 ✓
  • POST /api/auth/login → 200 + httpOnly cookies, wrong pass → 401 with remaining attempts, 5th fail → lock 423 ✓
  • GET /api/auth/me with Bearer → 200, without → 401 ✓
  • POST /api/auth/refresh via cookie → new accessToken ✓
  • GET /api/jobs & GET /api/profile/dashboard protected, per-user isolation ✓
  • Frontend routes protected, OAuth callback handling ✓

📄 Env Templates

server/.env.example includes MONGO_URI, JWT_ACCESS_SECRET, JWT_REFRESH_SECRET, CLIENT_URL, GOOGLE_*, GITHUB_*, MAX_LOGIN_ATTEMPTS, LOCK_TIME_MS

client/.env.example includes VITE_API_URL

Never commit real .env; rotate JWT_*_SECRET (≥32 chars) for production, set NODE_ENV=production + secure: true cookies.


📚 Next

  • Implement real live-search provider (JSearch/Adzuna) behind POST /api/jobs/live-search
  • Add email nodemailer + SMTP in POST /api/profile/send-job-emails
  • Add refresh token rotation + DB denylist, email verification, 2FA for stricter auth
  • Add frontend ForgotPassword flow (backend already prepared for extension)

About

JobPilot - Smart Job Management

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages