first commit
This commit is contained in:
commit
d3ab57237a
13 changed files with 1443 additions and 0 deletions
6
.env
Normal file
6
.env
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
PORT=3400
|
||||||
|
MONGO_URI=mongodb://root:example@192.168.1.3:27017/email?authSource=admin
|
||||||
|
EMAIL_USER=admin@patachina.it
|
||||||
|
EMAIL_PASS=EmailMaster6691!!
|
||||||
|
RATE_LIMIT_WINDOW=15
|
||||||
|
RATE_LIMIT_MAX=5
|
||||||
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
node_modules/
|
||||||
73
README.md
Normal file
73
README.md
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
# Server email con verifica email e salvataggio su mongoDB
|
||||||
|
|
||||||
|
utilizza mongoDB per salvare dati di uno user invia una email di verifica e salva tutto dopo la verifica
|
||||||
|
|
||||||
|
il server si avvia su 182.168.1.3:3400
|
||||||
|
|
||||||
|
mongodb come da file .env su 192.168.1.3:27017
|
||||||
|
|
||||||
|
per ui utilizzato mongoexpress su 192.168.1.3:8081
|
||||||
|
|
||||||
|
utilizza email di IONOS admin@patachina.it
|
||||||
|
|
||||||
|
## Installazione
|
||||||
|
|
||||||
|
|
||||||
|
``` sh
|
||||||
|
npm init -y
|
||||||
|
npm install nodemailer body-parser dotenv express-rate-limit express crypto mongoose path
|
||||||
|
```
|
||||||
|
|
||||||
|
avviare con
|
||||||
|
```sh
|
||||||
|
node index.js
|
||||||
|
```
|
||||||
|
|
||||||
|
per testarlo verificare che non ci sia un DB in mongo che si chiami email
|
||||||
|
|
||||||
|
in quanto salva su
|
||||||
|
|
||||||
|
mongodb://root:example@192.168.1.3:27017/email
|
||||||
|
|
||||||
|
per richiedere la registrazione
|
||||||
|
|
||||||
|
|
||||||
|
```sh
|
||||||
|
curl -X POST http://192.168.1.3:3400/api/verifica-email \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"nome": "Fabio",
|
||||||
|
"cognome": "Rossi",
|
||||||
|
"email": "fabio.micheluz@gmail.com",
|
||||||
|
"telefono": "+393331234567",
|
||||||
|
"applicazione": "AppDemo"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
per verificarla inserendo il codice ricevuto tramite email
|
||||||
|
|
||||||
|
```sh
|
||||||
|
curl -X POST http://192.168.1.3:3400/api/conferma-codice \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"email": "fabio.micheluz@gmail.com",
|
||||||
|
"codice": "597751"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
la struttura é la seguente
|
||||||
|
|
||||||
|
```sh
|
||||||
|
email-verification-server/
|
||||||
|
├── .env
|
||||||
|
├── index.js
|
||||||
|
├── config/
|
||||||
|
│ └── db.js
|
||||||
|
├── models/
|
||||||
|
│ └── User.js
|
||||||
|
├── routes/
|
||||||
|
│ └── verify.js
|
||||||
|
├── utils/
|
||||||
|
│ ├── mailer.js
|
||||||
|
│ └── logger.js
|
||||||
|
```
|
||||||
13
config/db.js
Normal file
13
config/db.js
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
const mongoose = require('mongoose');
|
||||||
|
|
||||||
|
const connectDB = async () => {
|
||||||
|
try {
|
||||||
|
await mongoose.connect(process.env.MONGO_URI);
|
||||||
|
console.log('✅ MongoDB connesso');
|
||||||
|
} catch (err) {
|
||||||
|
console.error('❌ Errore MongoDB:', err);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = connectDB;
|
||||||
20
index.js
Normal file
20
index.js
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
require('dotenv').config();
|
||||||
|
const express = require('express');
|
||||||
|
const bodyParser = require('body-parser');
|
||||||
|
const connectDB = require('./config/db');
|
||||||
|
const verifyRoutes = require('./routes/verify');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
app.use(bodyParser.json());
|
||||||
|
|
||||||
|
connectDB();
|
||||||
|
app.use('/api', verifyRoutes);
|
||||||
|
|
||||||
|
// Serve file statici da /public
|
||||||
|
//app.use(express.static(path.join(__dirname, 'public')));
|
||||||
|
app.use('/.well-known', express.static(path.join(__dirname, 'public/.well-known')));
|
||||||
|
|
||||||
|
app.listen(process.env.PORT, () => {
|
||||||
|
console.log(`🚀 Server attivo su http://localhost:${process.env.PORT}`);
|
||||||
|
});
|
||||||
26
models/User.js
Normal file
26
models/User.js
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
const mongoose = require('mongoose');
|
||||||
|
|
||||||
|
const userSchema = new mongoose.Schema({
|
||||||
|
nome: String,
|
||||||
|
cognome: String,
|
||||||
|
email: { type: String, unique: true },
|
||||||
|
telefono: String,
|
||||||
|
applicazione: String,
|
||||||
|
codiceVerifica: String,
|
||||||
|
codiceVerificaCreatoIl: { type: Date, default: Date.now },
|
||||||
|
verificato: { type: Boolean, default: false },
|
||||||
|
timestamp: { type: Date, default: Date.now }
|
||||||
|
});
|
||||||
|
|
||||||
|
// TTL: elimina documenti 10 minuti dopo la creazione del codice ma rimane se l'utente é verificato
|
||||||
|
|
||||||
|
userSchema.index(
|
||||||
|
{ codiceVerificaCreatoIl: 1 },
|
||||||
|
{
|
||||||
|
expireAfterSeconds: 600, // o 60 se vuoi 1 minuto
|
||||||
|
partialFilterExpression: { verificato: false }
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = mongoose.model('User', userSchema);
|
||||||
14
models/User.js.old
Normal file
14
models/User.js.old
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
const mongoose = require('mongoose');
|
||||||
|
|
||||||
|
const userSchema = new mongoose.Schema({
|
||||||
|
nome: String,
|
||||||
|
cognome: String,
|
||||||
|
email: { type: String, unique: true },
|
||||||
|
telefono: String,
|
||||||
|
applicazione: String,
|
||||||
|
codiceVerifica: String,
|
||||||
|
verificato: { type: Boolean, default: false },
|
||||||
|
timestamp: { type: Date, default: Date.now }
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = mongoose.model('User', userSchema);
|
||||||
1143
package-lock.json
generated
Normal file
1143
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
22
package.json
Normal file
22
package.json
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"name": "emailsvr2",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"body-parser": "^2.2.0",
|
||||||
|
"crypto": "^1.0.1",
|
||||||
|
"dotenv": "^17.2.3",
|
||||||
|
"express": "^5.1.0",
|
||||||
|
"express-rate-limit": "^8.2.1",
|
||||||
|
"mongoose": "^8.19.3",
|
||||||
|
"nodemailer": "^7.0.10",
|
||||||
|
"path": "^0.12.7"
|
||||||
|
}
|
||||||
|
}
|
||||||
12
public/.well-known/assetlinks.json
Normal file
12
public/.well-known/assetlinks.json
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"relation": ["delegate_permission/common.handle_all_urls"],
|
||||||
|
"target": {
|
||||||
|
"namespace": "android_app",
|
||||||
|
"package_name": "com.patachina.authsvr",
|
||||||
|
"sha256_cert_fingerprints": [
|
||||||
|
"27:15:d6:f4:b0:a8:ff:6e:e5:70:cc:92:3f:06:25:c6:76:45:9f:5a:8b:e7:16:1d:9f:6f:b6:55:a2:e2:40:34"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
78
routes/verify.js
Normal file
78
routes/verify.js
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
const express = require('express');
|
||||||
|
const crypto = require('crypto');
|
||||||
|
const rateLimit = require('express-rate-limit');
|
||||||
|
const User = require('../models/User');
|
||||||
|
const sendVerificationEmail = require('../utils/mailer');
|
||||||
|
const { log, error } = require('../utils/logger');
|
||||||
|
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
const limiter = rateLimit({
|
||||||
|
windowMs: parseInt(process.env.RATE_LIMIT_WINDOW) * 60 * 1000,
|
||||||
|
max: parseInt(process.env.RATE_LIMIT_MAX),
|
||||||
|
message: 'Troppi tentativi. Riprova più tardi.'
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/verifica-email', limiter, async (req, res) => {
|
||||||
|
const { nome, cognome, email, telefono, applicazione } = req.body;
|
||||||
|
|
||||||
|
if (!nome || !cognome || !email || !telefono || !applicazione) {
|
||||||
|
return res.status(400).json({ error: 'Tutti i campi sono obbligatori' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const codice = crypto.randomInt(100000, 999999).toString();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const nuovoUtente = new User({
|
||||||
|
nome, cognome, email, telefono, applicazione, codiceVerifica: codice
|
||||||
|
});
|
||||||
|
|
||||||
|
await nuovoUtente.save();
|
||||||
|
await sendVerificationEmail({ nome, email, codice, applicazione });
|
||||||
|
|
||||||
|
log(`Codice ${codice} inviato a ${email}`);
|
||||||
|
res.json({ message: 'Codice inviato via email' });
|
||||||
|
} catch (err) {
|
||||||
|
if (err.code === 11000) {
|
||||||
|
res.status(409).json({ error: 'Email già registrata' });
|
||||||
|
} else {
|
||||||
|
error('Errore registrazione:', err);
|
||||||
|
res.status(500).json({ error: 'Errore interno' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/conferma-codice', async (req, res) => {
|
||||||
|
const { email, codice } = req.body;
|
||||||
|
|
||||||
|
const utente = await User.findOne({ email });
|
||||||
|
|
||||||
|
if (!utente) return res.status(404).json({ error: 'Utente non trovato' });
|
||||||
|
|
||||||
|
if (utente.codiceVerifica === codice) {
|
||||||
|
utente.verificato = true;
|
||||||
|
await utente.save();
|
||||||
|
log(`Email ${email} verificata`);
|
||||||
|
res.json({ message: 'Email verificata con successo' });
|
||||||
|
} else {
|
||||||
|
res.status(400).json({ error: 'Codice errato' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/cancella_utente', async (req, res) => {
|
||||||
|
const { email } = req.body;
|
||||||
|
|
||||||
|
const utente = await User.findOne({ email });
|
||||||
|
|
||||||
|
if (!utente) return res.status(404).json({ error: 'Utente non trovato' });
|
||||||
|
|
||||||
|
if (utente.verificato) {
|
||||||
|
await User.deleteMany({ email });
|
||||||
|
log(`Utente cancellato Email ${email}`);
|
||||||
|
res.json({ message: `cancellazione utente ${email}`});
|
||||||
|
} else {
|
||||||
|
res.status(400).json({ error: 'Utente non verificato si cancella da solo entro 60 sec' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
4
utils/logger.js
Normal file
4
utils/logger.js
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
const log = (...args) => console.log('🪵', ...args);
|
||||||
|
const error = (...args) => console.error('❌', ...args);
|
||||||
|
|
||||||
|
module.exports = { log, error };
|
||||||
31
utils/mailer.js
Normal file
31
utils/mailer.js
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
const nodemailer = require('nodemailer');
|
||||||
|
|
||||||
|
const transporter = nodemailer.createTransport({
|
||||||
|
host: 'smtp.ionos.it',
|
||||||
|
port: 587,
|
||||||
|
secure: false,
|
||||||
|
auth: {
|
||||||
|
user: process.env.EMAIL_USER,
|
||||||
|
pass: process.env.EMAIL_PASS
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const sendVerificationEmail = async ({ nome, email, codice, applicazione }) => {
|
||||||
|
const html = `
|
||||||
|
<h2>Ciao ${nome},</h2>
|
||||||
|
<p>Il tuo codice di verifica per <strong>${applicazione}</strong> è:</p>
|
||||||
|
<h1 style="color:#007BFF;">${codice}</h1>
|
||||||
|
<p>Inseriscilo nell'app per completare la verifica.</p>
|
||||||
|
<br>
|
||||||
|
<small>Questa email è generata automaticamente. Non rispondere.</small>
|
||||||
|
`;
|
||||||
|
|
||||||
|
return transporter.sendMail({
|
||||||
|
from: `"Verifica ${applicazione}" <${process.env.EMAIL_USER}>`,
|
||||||
|
to: email,
|
||||||
|
subject: 'Codice di verifica email',
|
||||||
|
html
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = sendVerificationEmail;
|
||||||
Loading…
Reference in a new issue