Skip to content

Commit 140f6f0

Browse files
committed
feat: Implement OTP email verification and unverified user restrictions
- Added OTP generation and email sending service (using Resend) - Implemented `/send-otp` and `/verify-otp` backend endpoints - Added [OtpVerification](cci:1://file:///e:/Majors/urBackend/frontend/src/pages/OtpVerification.jsx:7:0-175:1) frontend page with auto-send and skip functionality - Updated Signup flow to redirect to OTP verification - Updated Login to store verification status - Added access restrictions for unverified users (Create Project, Create Collection, Storage) - Updated Settings page to show verification status and "Verify Now" option - Fixed module syntax in emailService and implemented token refresh on verification
1 parent 1e74939 commit 140f6f0

26 files changed

Lines changed: 598 additions & 106 deletions

backend/app.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
const express = require('express');
1+
const express = require('express')
2+
const mongoose = require('mongoose')
3+
const cors = require('cors')
4+
const dotenv = require('dotenv');
25
const app = express();
3-
const mongoose = require('mongoose');
4-
const cors = require('cors');
5-
require('dotenv').config();
6+
dotenv.config();
67

78
// Middleware
89
app.use(cors());

backend/middleware/api_usage.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
const rateLimit = require('express-rate-limit');
22
const Log = require('../models/Log');
33

4-
// 1. Rate Limiter (Same as before)
4+
// Rate Limiter
55
const limiter = rateLimit({
66
windowMs: 15 * 60 * 1000,
77
max: 100,
@@ -10,17 +10,16 @@ const limiter = rateLimit({
1010
legacyHeaders: false,
1111
});
1212

13-
// 2. Logger (UPDATED ✅)
13+
// Logger
1414
const logger = (req, res, next) => {
1515
// Check for Data, Storage, AND UserAuth routes
1616
if (
1717
req.originalUrl.startsWith('/api/data') ||
1818
req.originalUrl.startsWith('/api/storage') ||
19-
req.originalUrl.startsWith('/api/userAuth') // <--- Added this
19+
req.originalUrl.startsWith('/api/userAuth')
2020
) {
2121

2222
res.on('finish', async () => {
23-
// verifyApiKey middleware se 'req.project' set hona zaroori hai
2423
if (req.project) {
2524
try {
2625
await Log.create({
Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,36 @@
11
const jwt = require('jsonwebtoken');
22

33
module.exports = function (req, res, next) {
4-
// 1. Poora header mangao
4+
// Retrieve the Authorization header from the request
55
const authHeader = req.header('Authorization');
66

7-
// 2. Check karo header hai ya nahi
8-
if (!authHeader) return res.status(401).send('Access Denied: No Token Provided');
7+
// Check if the Authorization header exists
8+
if (!authHeader) {
9+
return res.status(401).send('Access Denied: No Token Provided');
10+
}
911

10-
// 3. "Bearer " ko hatakar asli token nikalo
11-
// Hum space (' ') se split karte hain aur dusra hissa ([1]) uthate hain.
12+
// Extract the actual token by removing the "Bearer " prefix
13+
// We split the header value by space and take the second part
1214
const token = authHeader.split(' ')[1];
1315

14-
// Agar split karne ke baad token nahi mila (matlab format galat tha)
15-
if (!token) return res.status(401).send('Access Denied: Malformed Token Format');
16+
// If token is missing after splitting, the format is invalid
17+
if (!token) {
18+
return res.status(401).send('Access Denied: Malformed Token Format');
19+
}
1620

1721
try {
22+
// Verify the token using the secret key
1823
const verified = jwt.verify(token, process.env.JWT_SECRET);
24+
25+
// Attach decoded token data to request object
1926
req.user = verified;
27+
28+
// Proceed to the next middleware or route handler
2029
next();
2130
} catch (err) {
22-
console.log(err);
23-
// Error message mein 'err' object bhejna security risk ho sakta hai production mein,
24-
// isliye sirf message bhejte hain.
31+
console.error(err);
32+
33+
// Do not expose detailed error information in production
2534
res.status(400).send('Invalid Token');
2635
}
27-
};
36+
};

backend/middleware/verifyApiKey.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,16 @@ module.exports = async (req, res, next) => {
44
try {
55
const apiKey = req.header('x-api-key');
66

7-
if (!apiKey) return res.status(401).send('API key not found');
7+
if (!apiKey) return res.status(401).json({ error: 'API key not found' });
88

9-
const project = await Project.findOne({ apiKey });
10-
if (!project) return res.status(401).send('Invalid API key');
9+
const project = await Project.findOne({ apiKey })
10+
.populate('owner', 'isVerified');
11+
if (!project) return res.status(401).json({ error: 'Invalid API key' });
1112

13+
if (!project.owner.isVerified) return res.status(401).json({ error: 'Owner not verified', fix: 'Verify your account on https://urbackend.bitbros.in/dashboard' });
1214
req.project = project;
1315
next();
1416
} catch (err) {
15-
res.status(500).send(err.message);
17+
res.status(500).json({ error: err.message });
1618
}
1719
};

backend/middleware/verifyEmail.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module.exports = function (req, res, next) {
2+
const { isVerified } = req.user;
3+
if (!isVerified) return res.status(401).json({ error: "Email not verified" });
4+
next();
5+
};

backend/models/Developer.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ const developerSchema = new mongoose.Schema({
99
password: {
1010
type: String,
1111
required: true
12+
},
13+
isVerified: {
14+
type: Boolean,
15+
default: false
1216
}
1317
}, { timestamps: true });
1418

backend/models/Log.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ const logSchema = new mongoose.Schema({
88
ip: String,
99
timestamp: { type: Date, default: Date.now }
1010
}, {
11-
// ⚠️ IMPORTANT: Capped Collection
12-
// Max size: 50MB (52428800 bytes) ya Max 50,000 logs. Jo pehle poora ho jaye.
11+
// IMPORTANT: Capped Collection
12+
// Max size: 50MB (52428800 bytes)
1313
capped: { size: 52428800, max: 50000 }
1414
});
1515

backend/models/Project.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,11 @@ const projectSchema = new mongoose.Schema({
3333
},
3434
collections: [collectionSchema],
3535

36-
// --- STORAGE LIMITS (Files) ---
36+
// STORAGE LIMITS (Files)
3737
storageUsed: { type: Number, default: 0 }, // in bytes
3838
storageLimit: { type: Number, default: 100 * 1024 * 1024 }, // 100MB for Files
3939

40-
// --- NEW: DATABASE LIMITS (JSON Docs) ---
40+
// DATABASE LIMITS (JSON Docs)
4141
databaseUsed: { type: Number, default: 0 }, // in bytes
4242
databaseLimit: { type: Number, default: 50 * 1024 * 1024 } // 50MB for Data (approx 50,000 large docs)
4343

backend/models/otp.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
const mongoose = require('mongoose');
2+
3+
const otpSchema = new mongoose.Schema({
4+
userId: { type: mongoose.Schema.Types.ObjectId, required: true, ref: 'User' },
5+
otp: { type: String, required: true },
6+
createdAt: { type: Date, default: Date.now, expires: '10m' } // MongoDB TTL index
7+
});
8+
9+
module.exports = mongoose.model('Otp', otpSchema);

backend/package-lock.json

Lines changed: 103 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)