Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 19 additions & 4 deletions backend/.env.example
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
# Environment Configuration
NODE_ENV=development

# Server Configuration
PORT=5000
MONGODB_URI=mongodb+srv://<username>:<pass>@cluster0.thi5oil.mongodb.net/<databasemame>
JWT_SECRET=123456789
JWT_REFRESH_SECRET=987654321

# Database Configuration
MONGODB_URI=mongodb://localhost:27017/wikisource_verifier

# JWT Configuration
JWT_SECRET=your_super_secure_jwt_secret_key_here
JWT_REFRESH_SECRET=your_super_secure_refresh_secret_key_here
JWT_EXPIRE=15m
JWT_REFRESH_EXPIRE=7d

# Frontend URL (for CORS)
FRONTEND_URL=http://localhost:5173
NODE_ENV=development

# Optional: Rate limiting
RATE_LIMIT_WINDOW=15
RATE_LIMIT_MAX_REQUESTS=100
15 changes: 9 additions & 6 deletions backend/src/config/config.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@


import dotenv from 'dotenv';

dotenv.config();

const config = {
port: process.env.PORT || 5000,
// Fallback to local Mongo for development if env var is not set
mongodbUri: process.env.MONGODB_URI || 'mongodb://127.0.0.1:27017/wsv',
// Development defaults; override in production via environment variables
jwtSecret: process.env.JWT_SECRET || 'dev_jwt_secret_change_me',
jwtExpire: process.env.JWT_EXPIRE || '30d',
mongodbUri: process.env.MONGODB_URI,
jwtSecret: process.env.JWT_SECRET,
jwtRefreshSecret: process.env.JWT_REFRESH_SECRET,
jwtExpire: process.env.JWT_EXPIRE || '15m',
jwtRefreshExpire: process.env.JWT_REFRESH_EXPIRE || '7d',
nodeEnv: process.env.NODE_ENV || 'development',
};

export default config;
export default config;
15 changes: 1 addition & 14 deletions backend/src/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,24 +36,11 @@ app.use((req, res, next) => {
// Security middleware
app.use(helmet());

app.use(
helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:"],
connectSrc: ["'self'"],
objectSrc: ["'none'"],
frameAncestors: ["'none'"]
}
})
);


// CORS configuration
const allowedOrigins = [
'http://localhost:3000',
'http://localhost:3001',
'http://localhost:5173',
process.env.FRONTEND_URL
].filter(Boolean);
Expand Down
16 changes: 9 additions & 7 deletions backend/src/utils/jwt.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@

import jwt from 'jsonwebtoken';
import config from '../config/config.js';

export const generateAccessToken = (userId) => {
return jwt.sign(
{ id: userId },
process.env.JWT_SECRET,
{ expiresIn: process.env.JWT_EXPIRE || '15m' }
config.jwtSecret,
{ expiresIn: config.jwtExpire }
);
};

export const generateRefreshToken = (userId) => {
return jwt.sign(
{ id: userId },
process.env.JWT_REFRESH_SECRET,
{ expiresIn: process.env.JWT_REFRESH_EXPIRE || '7d' }
config.jwtRefreshSecret,
{ expiresIn: config.jwtRefreshExpire }
);
};

export const verifyRefreshToken = (token) => {
try {
return jwt.verify(token, process.env.JWT_REFRESH_SECRET);
return jwt.verify(token, config.jwtRefreshSecret);
} catch (error) {
return null;
}
Expand All @@ -30,8 +32,8 @@ export const sendTokenResponse = (user, statusCode, res) => {

const cookieOptions = {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict',
secure: config.nodeEnv === 'production',
sameSite: config.nodeEnv === 'production' ? 'strict' : 'lax',
maxAge: 7 * 24 * 60 * 60 * 1000 // 7 days
};

Expand Down
6 changes: 5 additions & 1 deletion frontend/.env.example
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
VITE_API_URL=http://localhost:3000/api
# API Configuration
VITE_API_URL=http://localhost:5000/api

# Optional: Development settings
# VITE_DEBUG_MODE=true
90 changes: 44 additions & 46 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
import { useEffect } from 'react';
import { Routes, Route, Link } from 'react-router-dom';
import React, { useEffect } from 'react';
import { Routes, Route, Link, Navigate, BrowserRouter as Router } from 'react-router-dom';
import { AuthProvider } from './lib/auth-context';
import { Navigation } from './components/Navigation';
import {
LandingPage,
AuthPage,
SubmissionForm,
AdminDashboard,
PublicDirectory,
UserProfile
} from './pages';
import { ProtectedRoute } from './components/ProtectedRoute';
import { LandingPage, AuthPage, SubmissionForm, AdminDashboard, PublicDirectory, UserProfile } from './pages';
import { Toaster } from './components/ui/sonner';
import { TooltipProvider } from './components/ui/tooltip';
import { initializeData } from './lib/mock-data';
import { TooltipProvider } from './components/ui/tooltip'; // only if TooltipProvider is needed

function NotFound() {
return (
Expand Down Expand Up @@ -40,10 +35,37 @@ function AppContent() {
<Routes>
<Route path="/" element={<LandingPage />} />
<Route path="/auth" element={<AuthPage />} />
<Route path="/submit" element={<SubmissionForm />} />
<Route path="/admin" element={<AdminDashboard />} />
<Route path="/directory" element={<PublicDirectory />} />
<Route path="/profile" element={<UserProfile />} />

{/* Protected Routes */}
<Route
path="/submit"
element={
<ProtectedRoute>
<SubmissionForm />
</ProtectedRoute>
}
/>
<Route
path="/profile"
element={
<ProtectedRoute>
<UserProfile />
</ProtectedRoute>
}
/>

{/* Admin Route */}
<Route
path="/admin"
element={
<ProtectedRoute requiredRole="admin">
<AdminDashboard />
</ProtectedRoute>
}
/>

{/* 404 */}
<Route path="*" element={<NotFound />} />
</Routes>
</main>
Expand All @@ -57,39 +79,19 @@ function AppContent() {
<div>
<h3 className="mb-3">About WikiSourceVerifier</h3>
<p className="text-sm text-gray-600">
A community-driven platform for verifying Wikipedia
references and maintaining source quality standards.
A community-driven platform for verifying Wikipedia references and maintaining source quality standards.
</p>
</div>

<div>
<h3 className="mb-3">Quick Links</h3>
<ul className="space-y-2 text-sm">
<li>
<Link
to="/directory"
className="text-gray-600 hover:text-gray-900"
>
Browse Directory
</Link>
</li>
<li>
<Link
to="/submit"
className="text-gray-600 hover:text-gray-900"
>
Submit Reference
</Link>
</li>
<li>
<Link
to="/auth"
className="text-gray-600 hover:text-gray-900"
>
Login / Register
</Link>
</li>
<li><Link to="/directory">Browse Directory</Link></li>
<li><Link to="/submit">Submit Reference</Link></li>
<li><Link to="/auth">Login / Register</Link></li>
</ul>
</div>

<div>
<h3 className="mb-3">Resources</h3>
<ul className="space-y-2 text-sm text-gray-600">
Expand All @@ -100,14 +102,10 @@ function AppContent() {
</ul>
</div>
</div>

<div className="mt-8 pt-8 border-t text-center text-sm text-gray-600">
<p>
© 2025 WikiSourceVerifier. Built for the Wikipedia community.
</p>
<p className="mt-2">
This is a demonstration platform. For production use, connect
to a real backend service.
</p>
<p>© 2025 WikiSourceVerifier. Built for the Wikipedia community.</p>
<p className="mt-2">This is a demonstration platform. For production use, connect to a real backend service.</p>
</div>
</div>
</footer>
Expand Down
56 changes: 56 additions & 0 deletions frontend/src/components/ProtectedRoute.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@

import React from 'react';
import { Navigate } from 'react-router-dom';
import { useAuth } from '../lib/auth-context';
import { Loader2 } from 'lucide-react';

interface ProtectedRouteProps {
children: React.ReactNode;
requiredRole?: 'contributor' | 'verifier' | 'admin';
redirectTo?: string;
}

export const ProtectedRoute: React.FC<ProtectedRouteProps> = ({
children,
requiredRole,
redirectTo = '/auth'
}) => {
const { user, loading } = useAuth();

if (loading) {
return (
<div className="min-h-screen flex items-center justify-center">
<div className="flex items-center space-x-2">
<Loader2 className="h-6 w-6 animate-spin" />
<span>Loading...</span>
</div>
</div>
);
}

if (!user) {
return <Navigate to={redirectTo} replace />;
}

if (requiredRole) {
const roleHierarchy = { contributor: 1, verifier: 2, admin: 3 };
const userLevel = roleHierarchy[user.role];
const requiredLevel = roleHierarchy[requiredRole];

if (userLevel < requiredLevel) {
return (
<div className="min-h-screen flex items-center justify-center">
<div className="text-center">
<h2 className="text-2xl font-semibold mb-2">Access Denied</h2>
<p className="text-gray-600 mb-4">
You need {requiredRole} privileges to access this page.
</p>
<Navigate to="/" replace />
</div>
</div>
);
}
}

return <>{children}</>;
};
Loading