Skip to content

Commit

Permalink
Merge pull request #482 from bluewave-labs/467-fix-email-service
Browse files Browse the repository at this point in the history
fix invite and reset passwd email service
  • Loading branch information
erenfn authored Jan 13, 2025
2 parents d92217b + 47b7419 commit de8065a
Show file tree
Hide file tree
Showing 20 changed files with 283 additions and 218 deletions.
4 changes: 2 additions & 2 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ DEV_DB_PORT=5432
# JWT Secret Key
JWT_SECRET="NKrbO2lpCsOpVAlqAPsjZ0tZXzIoKru7gAmYZ7XlHn0=qqwqeq"

EMAIL=autobluewave@gmail.com
EMAIL=bluewaveguidefox@gmail.com
EMAIL_PASSWORD=passwor
EMAIL_HOST=smtp.gmail.com
EMAIL_PORT=465
APP_PASSWORD=password
APP_PASSWORD=ukzwakckupguegiw
EMAIL_ENABLE=false
12 changes: 11 additions & 1 deletion backend/.env
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ DEV_DB_NAME=onboarding_db
DEV_DB_HOST=db
DEV_DB_PORT=5432

EMAIL_ENABLE=false
EMAIL_ENABLE=true

# JWT Secret Key
JWT_SECRET="NKrbO2lpCsOpVAlqAPsjZ0tZXzIoKru7gAmYZ7XlHn0=qqwqeq"
Expand All @@ -19,8 +19,18 @@ TEST_DB_NAME=onboarding_db_test
TEST_DB_HOST=localhost
TEST_DB_PORT=5432

EMAIL=[email protected]
EMAIL_PASSWORD=passwor
EMAIL_HOST=smtp.gmail.com
EMAIL_PORT=465
APP_PASSWORD=ukzwakckupguegiw
EMAIL_ENABLE=true

ENABLE_IP_CHECK=false
# Allowed IP range for the API "baseIp/rangeStart-rangeEnd" (e.g. 192.168.1/1-255) separated by comma
ALLOWED_IP_RANGE=11.22.33/10-200, 192.168.65/1-255
# Allowed IP addresses for the API separated by comma
ALLOWED_IPS=127.0.0.1, 11.22.33.44, 11.22.33.45, 11.22.33.46, 192.168.65.1

# FRONTEND_URL=https://onboarding-demo.bluewavelabs.ca/
FRONTEND_URL=http://localhost:4173/
1 change: 0 additions & 1 deletion backend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions backend/src/controllers/auth.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const sequelize = db.sequelize;
const { generateToken, verifyToken } = require('../utils/jwt.helper');
const crypto = require('crypto');
const { TOKEN_LIFESPAN } = require('../utils/constants.helper');
const { sendSignupEmail, sendPasswordResetEmail, findUserByEmail } = require('../service/email.service');
const { sendPasswordResetEmail, findUserByEmail } = require('../service/email.service');
const settings = require('../../config/settings');
const { decode } = require('../utils/auth.helper');

Expand Down Expand Up @@ -81,8 +81,6 @@ const register = async (req, res) => {

await Token.create({ token, userId: newUser.id, type: 'auth' });

await sendSignupEmail(newUser.email, newUser.name);

res.status(201).json({
user: {
id: newUser.id,
Expand Down Expand Up @@ -160,6 +158,8 @@ const forgetPassword = async (req, res) => {
const user = await findUserByEmail(email);
if (!user) return res.status(400).json({ error: 'User not found' });

await Token.destroy({ where: { userId: user.id, type: 'reset' } });

const resetToken = crypto.randomBytes(32).toString('hex');
const hash = await bcrypt.hash(resetToken, 10);
const expiresAt = new Date(Date.now() + TOKEN_LIFESPAN);
Expand Down
4 changes: 2 additions & 2 deletions backend/src/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const cors = require("cors");
const helmet = require("helmet");
const dotenv = require("dotenv");
const bodyParser = require("body-parser");
const compression = require("compression");
// const compression = require("compression");
const jsonErrorMiddleware = require("./middleware/jsonError.middleware");
const fileSizeValidator = require("./middleware/fileSizeValidator.middleware");
const { MAX_FILE_SIZE } = require("./utils/constants.helper");
Expand Down Expand Up @@ -32,7 +32,7 @@ app.use(cors());
app.options('*', cors()); // this is for preflight requests
app.use(helmet());
app.use(bodyParser.json({ limit: MAX_FILE_SIZE }));
app.use(compression());
// app.use(compression());
app.use(jsonErrorMiddleware);
if (process.env.ENABLE_IP_CHECK === 'true') {
app.use(ipFilter);
Expand Down
20 changes: 16 additions & 4 deletions backend/src/service/email.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,21 @@ const nodemailer = require("nodemailer");
const handlebars = require("handlebars");
const fs = require("fs");
const path = require("path");
const { API_BASE_URL } = require("../utils/constants.helper");
const { API_BASE_URL, FRONTEND_URL } = require('../utils/constants.helper');
const db = require("../models");
const User = db.User;

const transporter = nodemailer.createTransport({
host: process.env.EMAIL_HOST || "localhost",
host: process.env.EMAIL_HOST || 'localhost',
port: process.env.EMAIL_PORT || 465,
secure: true,
auth: {
user: process.env.EMAIL,
pass: process.env.APP_PASSWORD,
},
tls: {
rejectUnauthorized: process.env.NODE_ENV === 'production',
},
});

const readHTMLFile = (filePath) => {
Expand Down Expand Up @@ -59,16 +62,25 @@ const sendSignupEmail = async (email, name) => {
};

const sendPasswordResetEmail = async (email, name, resetToken) => {
const resetLink = `${API_BASE_URL}reset-password?token=${resetToken}`;
await sendEmail(email, "Password Reset", "resetPassword", {
const resetLink = `${FRONTEND_URL}set-new-password?token=${resetToken}`;
await sendEmail(email, 'Reset your password for Guidefox', 'resetPassword', {
name,
resetLink,
});
};

const sendInviteEmail = async (email) => {
const inviteLink = FRONTEND_URL;
await sendEmail(email, 'You’re invited to join Guidefox!', 'invite', {
inviteLink,
});
};


module.exports = {
sendSignupEmail,
sendPasswordResetEmail,
sendInviteEmail,
findUserByEmail,
transporter,
};
3 changes: 3 additions & 0 deletions backend/src/service/invite.service.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const settings = require("../../config/settings");
const { sendInviteEmail } = require("./email.service");
const db = require("../models");
const Invite = db.Invite;
const User = db.User;
Expand All @@ -22,13 +23,15 @@ class InviteService {
invitedBy: userId,
role: settings.user.role[role],
})
sendInviteEmail(invitedEmail);
}
else {
await Invite.create({
invitedBy: userId,
invitedEmail: invitedEmail,
role: settings.user.role[role],
});
sendInviteEmail(invitedEmail);
}
}
catch (err) {
Expand Down
7 changes: 7 additions & 0 deletions backend/src/templates/invite.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<p>Hello,</p>
<p>You’ve been invited to join Guidefox - a platform designed to help you build better user experiences.</p>
<p>To get started, simply click the link below to create your account:</p>
<p><a href="{{inviteLink}}">Join Guidefox</a></p>
<p>Once you’ve created your account, you’ll be able to log in and start exploring all the features we’ve prepared for you.</p>
<p>We’re excited to have you with us and can’t wait for you to experience Guidefox!</p>
<p>--<br>The Guidefox Team</p>
11 changes: 7 additions & 4 deletions backend/src/templates/resetPassword.hbs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
<h1>Password Reset</h1>
<p>Hi {{name}},</p>
<p>Please use the following link to reset your password:</p>
<a href="{{resetLink}}">Reset Password</a>
<p>Hello {{name}},</p>
<p>We received a request to reset your password for your Guidefox account. If you didn’t request a password reset, you can safely ignore this email.</p>
<p>To reset your password, click the link below:</p>
<a href="{{resetLink}}">Reset My Password</a>
<p>Thank you for using Guidefox!</p>
<p>--<br>The Guidefox Team</p>

2 changes: 1 addition & 1 deletion backend/src/test/unit/services/email.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ describe("Test email service", () => {
expect(params).to.deep.equal({
from: process.env.EMAIL,
to: user().build().email,
subject: "Password Reset",
subject: "Reset your password for Guidefox",
html: "html",
});
});
Expand Down
33 changes: 17 additions & 16 deletions backend/src/utils/constants.helper.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
require('dotenv').config();

module.exports = Object.freeze({
JWT_EXPIRES_IN_1H: '1h',
JWT_EXPIRES_IN_20M: '20m',
TOKEN_LIFESPAN: 3600 * 1000,
// API_BASE_URL: 'https://onboarding-demo.bluewavelabs.ca/api/',
API_BASE_URL: 'localhost:3000/api/',
MAX_FILE_SIZE: 3 * 1024 * 1024,
ROLE: {
ADMIN: '1',
MEMBER: '2'
},
MAX_ORG_NAME_LENGTH: 100,
ORG_NAME_REGEX: /^[a-zA-Z0-9\s\-_&.]+$/,
URL_PROTOCOL_REGEX: /^(https?:\/\/)/,
URL_DOMAIN_REGEX: /^https?:\/\/([a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/,
});

JWT_EXPIRES_IN_1H: '1h',
JWT_EXPIRES_IN_20M: '20m',
TOKEN_LIFESPAN: 3600 * 1000,
API_BASE_URL: process.env.API_BASE_URL || 'localhost:3000/api/',
FRONTEND_URL: process.env.FRONTEND_URL,
MAX_FILE_SIZE: 3 * 1024 * 1024,
ROLE: {
ADMIN: '1',
MEMBER: '2'
},
MAX_ORG_NAME_LENGTH: 100,
ORG_NAME_REGEX: /^[a-zA-Z0-9\s\-_&.]+$/,
URL_PROTOCOL_REGEX: /^(https?:\/\/)/,
URL_DOMAIN_REGEX: /^https?:\/\/([a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/,
});
46 changes: 23 additions & 23 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,29 +59,29 @@ services:
- "5432:5432"
volumes:
- pgdata:/var/lib/postgresql/data
frontend:
build: ./frontend
volumes:
- ./frontend:/app
- /app/node_modules
ports:
- "4173:4173"
develop:
watch:
- action: sync
path: ./frontend
target: /app
ignore:
- node_modules/
environment:
- NODE_ENV=${NODE_ENV:-development}
command: >
bash -c "
if [ \"$NODE_ENV\" = \"development\" ]; then
npm run dev;
elif [ \"$NODE_ENV\" != \"development\" ]; then
npm run build && npm run preview;
fi"
# frontend:
# build: ./frontend
# volumes:
# - ./frontend:/app
# - /app/node_modules
# ports:
# - "4173:4173"
# develop:
# watch:
# - action: sync
# path: ./frontend
# target: /app
# ignore:
# - node_modules/
# environment:
# - NODE_ENV=${NODE_ENV:-development}
# command: >
# bash -c "
# if [ \"$NODE_ENV\" = \"development\" ]; then
# npm run dev;
# elif [ \"$NODE_ENV\" != \"development\" ]; then
# npm run build && npm run preview;
# fi"
mailhog:
image: mailhog/mailhog
ports:
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/assets/theme.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ export const lightTheme = createTheme({
},
MuiOutlinedInput: {
styleOverrides: {
input: {
boxSizing: 'border-box'
},
root: {
borderRadius: "8px",
"&:hover .MuiOutlinedInput-notchedOutline": {
Expand Down Expand Up @@ -138,6 +141,9 @@ export const darkTheme = createTheme({
},
MuiOutlinedInput: {
styleOverrides: {
input: {
boxSizing: 'border-box'
},
root: {
borderRadius: "8px",
"&:hover .MuiOutlinedInput-notchedOutline": {
Expand Down
13 changes: 12 additions & 1 deletion frontend/src/components/CustomLink/CustomLink.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,24 @@ import './CustomLinkStyles.css';

const CustomLink = ({
text = 'Default Text',
url = '#',
url = '',
className = '',
underline = 'none',
onClick,
}) => {
const handleClick = (event) => {
if (onClick) {
event.preventDefault();
onClick(event);
}
};

return (
<Link
href={url}
className={`custom-link ${className}`}
underline={underline}
onClick={handleClick}
>
{text}
</Link>
Expand All @@ -25,5 +34,7 @@ CustomLink.propTypes = {
url: PropTypes.string,
className: PropTypes.string,
underline: PropTypes.oneOf(['none', 'hover', 'always']),
onClick: PropTypes.func,
};

export default CustomLink;
36 changes: 30 additions & 6 deletions frontend/src/scenes/login/CheckYourEmailPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,46 @@ import styles from './Login.module.css';
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
import { useLocation, useNavigate } from 'react-router-dom';
import CustomLink from '@components/CustomLink/CustomLink';
import { forgotPassword } from "../../services/loginServices";

const CheckYourEmailPage = () => {
const navigate = useNavigate();
const location = useLocation();
const { email: emailFromState } = location.state || {};
const values = location.state?.values || {};
const { email: emailFromState } = values;

const handleResendClick = () => {
if (emailFromState) {
forgotPassword(values)
.then(response => {
console.log("Password reset link resent successfully:", response);
})
.catch(error => {
console.error("Error resending password reset link:", error);
});
} else {
console.warn("No email provided to resend the password reset link.");
}
};

return (
<div className={styles["login-container"]}>
<h2>Check Your Email</h2>
<h3 style={{margin: "0px"}}>We sent a password reset link to</h3>
<h3 style={{marginTop: "5px", fontWeight: "bold", marginBottom:"10px"}}>{emailFromState}</h3>
<button className={styles["create-account-button"]} style={{marginBottom: "30px"}}>Open email app</button>
<h3 style={{ margin: "0px" }}>We sent a password reset link to</h3>
<h3 style={{ marginTop: "0.25rem", fontWeight: "bold", marginBottom: "1rem" }}>
{emailFromState || "Email not provided"}
</h3>
<div className={styles["sign-up-link"]}>
Didn't receive the email? <CustomLink text="Click to resend" url="#" />
Didn't receive the email? <CustomLink text="Click to resend" onClick={handleResendClick} />
</div>
<button className={styles["back-to-login-button"]} style={{marginTop: "20px"}} onClick={() => navigate('/')}> <ArrowBackIcon style={{fontSize: "18px", marginRight: "5px"}}/>Back to log in</button>
<button
className={styles["back-to-login-button"]}
style={{ marginTop: "20px" }}
onClick={() => navigate('/')}
>
<ArrowBackIcon style={{ fontSize: "18px", marginRight: "5px" }} />
Back to log in
</button>
</div>
);
};
Expand Down
Loading

0 comments on commit de8065a

Please sign in to comment.