diff --git a/backend/.env.example b/backend/.env.example index 7fa91722..6b8248aa 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -42,4 +42,12 @@ RAZORPAY_KEY_SECRET=your_razorpay_secret # GEMINI KEY (Optional) # =========================================== # Get these from google ai studio -GEMINI_API_KEY=YOUR_GEMINI_API_KEY \ No newline at end of file +GEMINI_API_KEY=YOUR_GEMINI_API_KEY + +# =========================================== +# Email / OTP (Required for signup) +# =========================================== +# Use a Gmail account with an App Password: +# https://myaccount.google.com/apppasswords +EMAIL_USER=your_gmail@gmail.com +EMAIL_PASS=your_app_password \ No newline at end of file diff --git a/backend/config/sendEmail.js b/backend/config/sendEmail.js index 30db05dd..02804d7e 100644 --- a/backend/config/sendEmail.js +++ b/backend/config/sendEmail.js @@ -3,23 +3,41 @@ import dotenv from "dotenv"; dotenv.config(); +const emailConfigured = + Boolean(process.env.EMAIL_USER) && Boolean(process.env.EMAIL_PASS); + const transporter = nodemailer.createTransport({ service: "gmail", auth: { user: process.env.EMAIL_USER, pass: process.env.EMAIL_PASS, }, + connectionTimeout: 10000, + greetingTimeout: 10000, + socketTimeout: 15000, }); -transporter.verify((err) => { - if (err) { - console.log("SMTP ERROR =>", err); - } else { - console.log("SMTP SERVER READY"); - } -}); +if (!emailConfigured) { + console.error( + "EMAIL_USER and EMAIL_PASS must be set for OTP email delivery.", + ); +} else { + transporter.verify((err) => { + if (err) { + console.log("SMTP ERROR =>", err); + } else { + console.log("SMTP SERVER READY"); + } + }); +} + +export const isEmailConfigured = () => emailConfigured; export const sendMail = async (email, htmlContent) => { + if (!emailConfigured) { + throw new Error("Email service is not configured"); + } + try { const info = await transporter.sendMail({ from: `"RIVETO" <${process.env.EMAIL_USER}>`, @@ -33,4 +51,4 @@ export const sendMail = async (email, htmlContent) => { console.error("Error sending email"); throw new Error("Email could not be sent"); } -}; \ No newline at end of file +}; diff --git a/backend/controller/authcontroller.js b/backend/controller/authcontroller.js index ab0d157f..6ba92cc4 100644 --- a/backend/controller/authcontroller.js +++ b/backend/controller/authcontroller.js @@ -2,7 +2,7 @@ import User from "../model/userModel.js"; import validator from "validator"; import bcrypt from "bcryptjs"; import { genToken, genToken1 } from "../config/Token.js"; -import { sendMail } from "../config/sendEmail.js"; +import { sendMail, isEmailConfigured } from "../config/sendEmail.js"; import generateOTP from "../utils/otp.js"; import TempUser from "../model/tempUserModel.js"; import { otpTemplate } from "../utils/otpTemplet.js"; @@ -40,6 +40,16 @@ export const sendOTP = async (req, res) => { otp, otpExpire: new Date(Date.now() + 5 * 60 * 1000), }); + + if (!isEmailConfigured()) { + await TempUser.deleteOne({ email }); + + return res.status(503).json({ + success: false, + message: "Email service is not configured", + }); + } + try { await sendMail(email, otpTemplate(otp)); } catch (_error) { diff --git a/frontend/public/_redirects b/frontend/public/_redirects new file mode 100644 index 00000000..ad37e2c2 --- /dev/null +++ b/frontend/public/_redirects @@ -0,0 +1 @@ +/* /index.html 200 diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 9acf5750..65843b0c 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -231,6 +231,7 @@ function App() { {/* Public routes - Legal pages should be accessible without login */} } /> + } /> } /> } /> } /> diff --git a/frontend/src/pages/LandingPage.jsx b/frontend/src/pages/LandingPage.jsx index b85868dc..aa6944ef 100644 --- a/frontend/src/pages/LandingPage.jsx +++ b/frontend/src/pages/LandingPage.jsx @@ -1121,7 +1121,7 @@ function LandingPage() {
    {[ - { label: 'Privacy Policy', path: '/privacy' }, + { label: 'Privacy Policy', path: '/privacypolicy' }, { label: 'Terms of Service', path: '/terms' }, { label: 'Cookie Policy', path: '/cookie-policy' }, { label: 'Size Guide', path: '/size-guide' }, @@ -1147,7 +1147,7 @@ function LandingPage() {
    + + {submitError && ( +

    + {submitError} +

    + )} {/* Login Link */} diff --git a/frontend/src/utils/apiConfig.js b/frontend/src/utils/apiConfig.js index a176dbe8..d4ddc5d6 100644 --- a/frontend/src/utils/apiConfig.js +++ b/frontend/src/utils/apiConfig.js @@ -26,6 +26,7 @@ const MESSAGE_MAP = { const apiConfig = axios.create({ baseURL: `${serverURL}/api`, withCredentials: true, + timeout: 30000, }); apiConfig.interceptors.response.use(