Skip to content

Commit

Permalink
final ✅
Browse files Browse the repository at this point in the history
  • Loading branch information
sauravhathi committed Sep 5, 2023
0 parents commit 51bd5b0
Show file tree
Hide file tree
Showing 11 changed files with 1,132 additions and 0 deletions.
6 changes: 6 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
PORT =
MONGODB_URI =
OTP_VALIDITY_PERIOD_MINUTES = 5
OTP_SIZE = 4
# every 2 minutes
# CRON_SCHEDULE = */2 * * * *
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
.env
111 changes: 111 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# 📱 OTP Service

This is a free OTP (One-Time Password) service built with Node.js, Express.js, Mongoose, and node-cron for handling OTP generation, verification, and automatic expiration.

## Features

✨ Generate a one-time password (OTP) for a given email.

🔐 Verify an OTP for a given email.

⏰ Automatic OTP expiration and cleanup using cron jobs.

⚙️ Configurable OTP size and validity period.

🚀 Error handling for invalid OTPs and expired OTPs.

## Installation

1. Clone the repository:

```bash
git clone https://github.com/sauravhathi/otp-service.git
cd otp-service
```

2. Install dependencies:

```bash
npm install

# or

yarn
```

3. Set up your environment variables by creating a `.env` file in the project root directory and configuring the following variables:

```env
MONGODB_URI=<your-mongodb-connection-uri>
OTP_VALIDITY_PERIOD_MINUTES=2
OTP_SIZE=4
CRON_SCHEDULE=*/2 * * * *
```

- `MONGODB_URI`: MongoDB connection URI.
- `OTP_VALIDITY_PERIOD_MINUTES`: Validity period for OTPs in minutes.
- `OTP_SIZE`: Size of the OTP (number of digits).
- `CRON_SCHEDULE`: Cron schedule for automatic OTP cleanup.

4. Start the server:

```bash
npm dev

# or

yarn dev
```

## API Endpoints

### Generate OTP 🚀

- **Endpoint**: POST `/api/otp`
- **Request Body**:
```json
{
"email": "[email protected]"
}
```
- **Response**:
```json
{
"otp": 1234
}
```

### Verify OTP 🔐

- **Endpoint**: POST `/api/otp/verify`
- **Request Body**:
```json
{
"email": "[email protected]",
"otp": 1234
}
```
- **Response**:
```json
{
"message": "OTP is valid"
}
```

## Scheduled OTP Cleanup ⏰

The service automatically clears expired OTPs based on the configured cron schedule.

## Donate ☕

If you find this project useful and want to support its development, consider buying us a coffee!

<img src="https://github.com/sauravhathi/myperfectice-extension/assets/61316762/274f2172-8dcc-4fe9-aa51-fd3542429c3e" alt="support" style="width: 200px">

Donate: `saurav.34@paytm`

<a href="https://www.buymeacoffee.com/sauravhathi" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/arial-yellow.png" alt="Buy Me A Coffee" style="height: 60px !important;width: 217px !important;" ></a>

## License

This project is licensed under the MIT License. See the [LICENSE](https://github.com/sauravhathi/otp-service/blob/main/LICENSE) file for details.
78 changes: 78 additions & 0 deletions app/controllers/otpController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
const Otp = require('../models/otpModel');
const validityPeriodMinutes = process.env.OTP_VALIDITY_PERIOD_MINUTES;

const generateOTP = (size) => {
if (size < 1 || size > 10) {
throw new Error('OTP size must be between 1 and 10 digits.');
}

const min = 10 ** (size - 1);
const max = 10 ** size - 1;
return Math.floor(Math.random() * (max - min + 1)) + min;
}

const otpController = {
generateOtp: async (email) => {
try {
// Check if an OTP has already been generated for this email
const existingOtp = await Otp.findOne({
email: email,
createdAt: {
$gte: new Date(new Date() - validityPeriodMinutes * 60 * 1000), // Calculate the time window
},
});

if (existingOtp) {
return existingOtp.otp;
}

const otp = generateOTP(process.env.OTP_SIZE);

const otpDocument = new Otp({
id: new Date().getTime(),
email: email,
otp: otp,
createdAt: new Date(),
});

await otpDocument.save();

return otp;
} catch (error) {
throw new Error('Failed to generate OTP');
}
},
verifyOtp: async (email, otp) => {
try {

if (otp.toString().length !== parseInt(process.env.OTP_SIZE)) {
throw new Error('Invalid OTP');
}

const otpDocument = await Otp.findOneAndDelete({
email: email,
otp: otp,
createdAt: { $gte: new Date(new Date() - 1000 * 60 * validityPeriodMinutes) }
});

if (!otpDocument) {
throw new Error('Invalid OTP');
}

return true;
} catch (error) {
throw new Error(error.message);
}
},
clearExpiredOtps: async () => {
try {
// Clear expired OTPs
const cutoffTime = new Date(new Date() - 1000 * 60 * validityPeriodMinutes);
await Otp.deleteMany({ createdAt: { $lt: cutoffTime } });
} catch (error) {
throw new Error('Failed to clear expired OTPs');
}
},
};

module.exports = otpController;
13 changes: 13 additions & 0 deletions app/controllers/schedulerController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const cron = require('node-cron');
const otpController = require('./otpController');
const cronSchedule = process.env.CRON_SCHEDULE || '0 0 * * *';

// Clear expired OTPs every day at midnight by default
cron.schedule(cronSchedule, async () => {
try {
await otpController.clearExpiredOtps();
console.log('Cleared expired OTPs');
} catch (error) {
console.log(error.message);
}
});
12 changes: 12 additions & 0 deletions app/models/otpModel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const mongoose = require('mongoose');

const otpSchema = new mongoose.Schema({
id: String,
email: String,
otp: Number,
createdAt: Date,
});

const Otp = mongoose.model('Otp', otpSchema);

module.exports = Otp;
30 changes: 30 additions & 0 deletions app/routes/otpRoutes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
const express = require('express');
const otpController = require('../controllers/otpController');

const router = express.Router();

router.post('/otp', async (req, res) => {
try {const { email } = req.body;

const otp = await otpController.generateOtp(email);

console.log("generate otp", otp);

res.status(200).json({ otp });
} catch (error) {
res.status(400).json({ error: error.message });
}
});

router.post('/otp/verify', async (req, res) => {
try {
const { email, otp } = req.body;
await otpController.verifyOtp(email, otp);

res.status(200).json({ message: 'OTP is valid' });
} catch (error) {
res.status(400).json({ error: error.message });
}
});

module.exports = router;
6 changes: 6 additions & 0 deletions app/utils/validator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const isValidEmail = (email) => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}

module.exports = {isValidEmail}
37 changes: 37 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
const express = require('express');
const cors = require('cors');
const mongoose = require('mongoose');
const dotenv = require('dotenv');
dotenv.config();

const app = express();

mongoose.connect(process.env.MONGODB_URI, { useNewUrlParser: true, useUnifiedTopology: true });
const db = mongoose.connection;
db.on('error', console.error.bind(console, 'MongoDB connection error:'));
db.once('open', () => {
console.log('Connected to MongoDB');
});

app.use(cors());
app.use(express.json());

const {isValidEmail} = require('./app/utils/validator');
const otpRoutes = require('./app/routes/otpRoutes');
const schedulerController = require('./app/controllers/schedulerController');

const middleware = (req, res, next) => {
const { email } = req.body;
if (!isValidEmail(email)) {
console.log('middleware');
return res.status(400).json({ error: 'Invalid email' });
}
next();
};

app.use('/api', middleware, otpRoutes);

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
23 changes: 23 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"name": "otp-service",
"version": "1.0.0",
"main": "index.js",
"description": "This is a free OTP (One-Time Password) service built with Node.js, Express.js, Mongoose, and node-cron for handling OTP generation, verification, and automatic expiration.",
"author": "Saurav Hathi",
"repository": "https://github.com/sauravhathi/otp-service",
"license": "MIT",
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js"
},
"dependencies": {
"cors": "^2.8.5",
"dotenv": "^16.3.1",
"express": "^4.18.2",
"mongoose": "^7.5.0",
"node-cron": "^3.0.2"
},
"devDependencies": {
"nodemon": "^3.0.1"
}
}
Loading

0 comments on commit 51bd5b0

Please sign in to comment.