Auth #1
+87
-16
@@ -1,10 +1,20 @@
|
||||
# User API
|
||||
|
||||
#### Create New User
|
||||
#### Create New Customer Account
|
||||
|
||||
Creates a new customer account and sends an email verification link.
|
||||
|
||||
The account is initially created with:
|
||||
|
||||
```text
|
||||
accountType = customer
|
||||
accountStatus = PENDING_VERIFICATION
|
||||
emailVerifiedAt = null
|
||||
```
|
||||
|
||||
**Endpoint**
|
||||
|
||||
```
|
||||
```text
|
||||
POST: http://localhost:3070/api/user
|
||||
```
|
||||
|
||||
@@ -12,33 +22,94 @@ POST: http://localhost:3070/api/user
|
||||
|
||||
```json
|
||||
{
|
||||
"firstName": "Jhon",
|
||||
"lastName": "Doe",
|
||||
"email": "kalanajayasekara@niolla.lk",
|
||||
"role": "System Developer",
|
||||
"accountType": "admin",
|
||||
"department": "IT Department"
|
||||
"firstName": "Isuru",
|
||||
"lastName": "Bimsara",
|
||||
"email": "ibimsara00@gmail.com",
|
||||
"password": "Hello@12346"
|
||||
}
|
||||
```
|
||||
|
||||
**Respond**
|
||||
**Response**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "User Created Successfully",
|
||||
"data": {
|
||||
"id": "usr_763107d9-b56f-4b81-9d86-1889f98a6e6c",
|
||||
"firstName": "Jhon",
|
||||
"lastName": "Doe",
|
||||
"email": "kalanajayasekara@niolla.lk",
|
||||
"accountType": "admin",
|
||||
"role": "System Developer",
|
||||
"department": "IT Department"
|
||||
"id": "usr_ei6i4n49",
|
||||
"firstName": "Isuru",
|
||||
"lastName": "Bimsara",
|
||||
"email": "ibimsara00@gmail.com",
|
||||
"accountType": "customer"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
After registration, the user receives an email containing a verification link.
|
||||
```
|
||||
|
||||
#### Verify Email
|
||||
|
||||
Verifies the customer's email using the raw verification token received by email.
|
||||
|
||||
The backend hashes the received token and compares the resulting hash with the `tokenHash` stored in the `email_verifications` table.
|
||||
|
||||
The token must:
|
||||
|
||||
```text
|
||||
Exist in the database
|
||||
Not have been used
|
||||
Not have expired
|
||||
```
|
||||
|
||||
**Endpoint**
|
||||
|
||||
```text
|
||||
POST: http://localhost:3070/api/user/verify-email
|
||||
```
|
||||
|
||||
**Request Body**
|
||||
|
||||
```json
|
||||
{
|
||||
"token": "RAW_VERIFICATION_TOKEN_FROM_EMAIL"
|
||||
}
|
||||
```
|
||||
|
||||
**Successful Response**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Email verified successfully. Your account is now active."
|
||||
}
|
||||
```
|
||||
|
||||
After successful verification, the user record changes from:
|
||||
|
||||
```text
|
||||
accountStatus = PENDING_VERIFICATION
|
||||
emailVerifiedAt = NULL
|
||||
```
|
||||
|
||||
to:
|
||||
|
||||
```text
|
||||
accountStatus = ACTIVE
|
||||
emailVerifiedAt = <current date and time>
|
||||
```
|
||||
|
||||
The verification record is also updated:
|
||||
|
||||
```text
|
||||
usedAt = <current date and time>
|
||||
```
|
||||
|
||||
This prevents the same verification token from being successfully used again.
|
||||
|
||||
---
|
||||
|
||||
|
||||
#### Get All Users
|
||||
|
||||
**Endpoint**
|
||||
|
||||
@@ -9,14 +9,14 @@
|
||||
|
||||
// app/config/s3.config.js
|
||||
|
||||
const { S3Client } = require("@aws-sdk/client-s3");
|
||||
// const { S3Client } = require("@aws-sdk/client-s3");
|
||||
|
||||
const s3 = new S3Client({
|
||||
region: process.env.AWS_REGION,
|
||||
credentials: {
|
||||
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
|
||||
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
|
||||
},
|
||||
});
|
||||
// const s3 = new S3Client({
|
||||
// region: process.env.AWS_REGION,
|
||||
// credentials: {
|
||||
// accessKeyId: process.env.AWS_ACCESS_KEY_ID,
|
||||
// secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
|
||||
// },
|
||||
// });
|
||||
|
||||
module.exports = s3;
|
||||
// module.exports = s3;
|
||||
@@ -9,14 +9,18 @@
|
||||
|
||||
// app/controllers/user.controller.js
|
||||
|
||||
const { Op } = require("sequelize");
|
||||
const db = require("../models");
|
||||
const { hashPassword } = require("../utils/hashPassword.util");
|
||||
const { validatePassword } = require("../utils/validation/validatePassword.util");
|
||||
const { validateEmail } = require("../utils/validation/validateEmail.util");
|
||||
const { generateUserId, generateId } = require("../utils/idGen.util");
|
||||
const { logActivity } = require("../services/activity.service");
|
||||
const { sendMail } = require("../utils/mail.util");
|
||||
const {generateEmailVerificationToken, hashEmailVerificationToken} = require("../utils/emailVerification.util");
|
||||
const User = db.User;
|
||||
const Profile = db.Profile;
|
||||
const EmailVerification = db.EmailVerification;
|
||||
|
||||
// Create a new user
|
||||
exports.createNewUser = async (req, res) => {
|
||||
@@ -28,9 +32,29 @@ exports.createNewUser = async (req, res) => {
|
||||
lastName,
|
||||
email,
|
||||
password,
|
||||
accountType,
|
||||
} = req.body;
|
||||
|
||||
if (!firstName || !lastName || !email || !password) {
|
||||
await transaction.rollback();
|
||||
|
||||
return res.status(400).send({
|
||||
success: false,
|
||||
message:
|
||||
"First name, last name, email and password are required",
|
||||
});
|
||||
}
|
||||
|
||||
const emailValid = validateEmail(email);
|
||||
|
||||
if (!emailValid) {
|
||||
await transaction.rollback();
|
||||
|
||||
return res.status(400).send({
|
||||
success: false,
|
||||
message: "Invalid email address",
|
||||
});
|
||||
}
|
||||
|
||||
const userExists = await User.findOne({ where: { email } });
|
||||
if (userExists) {
|
||||
await transaction.rollback();
|
||||
@@ -41,15 +65,15 @@ exports.createNewUser = async (req, res) => {
|
||||
});
|
||||
}
|
||||
|
||||
let pass;
|
||||
// let pass;
|
||||
|
||||
if(accountType === "admin" || accountType === "superadmin" || accountType === "manager" || accountType === "support_agent") {
|
||||
pass = process.env.DEFAULT_PASSWORD;
|
||||
}else{
|
||||
pass = password;
|
||||
}
|
||||
// if(accountType === "admin" || accountType === "superadmin" || accountType === "manager" || accountType === "support_agent") {
|
||||
// pass = process.env.DEFAULT_PASSWORD;
|
||||
// }else{
|
||||
// pass = password;
|
||||
// }
|
||||
|
||||
const validatePasswordResult = validatePassword(pass);
|
||||
const validatePasswordResult = validatePassword(password);
|
||||
|
||||
if (!validatePasswordResult) {
|
||||
await transaction.rollback();
|
||||
@@ -59,9 +83,20 @@ exports.createNewUser = async (req, res) => {
|
||||
});
|
||||
}
|
||||
|
||||
const hashedPassword = await hashPassword(pass);
|
||||
const hashedPassword = await hashPassword(password);
|
||||
const userID = generateUserId();
|
||||
const verificationToken = generateEmailVerificationToken();
|
||||
|
||||
const verificationTokenHash =
|
||||
hashEmailVerificationToken(
|
||||
verificationToken
|
||||
);
|
||||
|
||||
const verificationExpiresAt =
|
||||
new Date(
|
||||
Date.now() +
|
||||
30 * 60 * 1000
|
||||
);
|
||||
|
||||
|
||||
const newUser = await User.create(
|
||||
@@ -71,7 +106,9 @@ exports.createNewUser = async (req, res) => {
|
||||
lastName,
|
||||
email,
|
||||
password: hashedPassword,
|
||||
accountType,
|
||||
// accountType,
|
||||
accountStatus: "PENDING_VERIFICATION",
|
||||
emailVerifiedAt: null,
|
||||
},
|
||||
{ transaction },
|
||||
);
|
||||
@@ -91,20 +128,109 @@ exports.createNewUser = async (req, res) => {
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
await sendMail({
|
||||
to: email,
|
||||
subject: "Welcome - Ocenic Titan",
|
||||
templateName: "welcome",
|
||||
templateVars: {
|
||||
firstName: firstName,
|
||||
email: email,
|
||||
password: process.env.DEFAULT_PASSWORD,
|
||||
await EmailVerification.create(
|
||||
{
|
||||
id:
|
||||
generateId(),
|
||||
|
||||
user_id:
|
||||
newUser.id,
|
||||
|
||||
tokenHash:
|
||||
verificationTokenHash,
|
||||
|
||||
expiresAt:
|
||||
verificationExpiresAt,
|
||||
|
||||
usedAt:
|
||||
null,
|
||||
},
|
||||
text: `Hello ${firstName}, your account has been created successfully.`,
|
||||
});
|
||||
{
|
||||
transaction,
|
||||
}
|
||||
);
|
||||
|
||||
await transaction.commit();
|
||||
|
||||
const confirmationLink =
|
||||
`${process.env.FRONTEND_URL}/verify-email?token=${verificationToken}`;
|
||||
|
||||
|
||||
// ==================================================
|
||||
// 18. Send verification email
|
||||
// ==================================================
|
||||
|
||||
try {
|
||||
|
||||
await sendMail({
|
||||
to:
|
||||
email,
|
||||
|
||||
subject:
|
||||
"Confirm Your ZUMRI Account",
|
||||
|
||||
templateName:
|
||||
"emailVerification",
|
||||
|
||||
templateVars: {
|
||||
|
||||
customer_name:
|
||||
firstName,
|
||||
|
||||
confirmation_link:
|
||||
confirmationLink,
|
||||
},
|
||||
|
||||
text:
|
||||
`Hello ${firstName}, please verify your ZUMRI account using this link: ${confirmationLink}`,
|
||||
});
|
||||
|
||||
|
||||
} catch (mailError) {
|
||||
|
||||
// The database transaction is already committed.
|
||||
//
|
||||
// Do NOT delete the user here.
|
||||
//
|
||||
// User remains:
|
||||
// PENDING_VERIFICATION
|
||||
//
|
||||
// Later resend-verification can send another email.
|
||||
|
||||
console.error(
|
||||
"VERIFICATION EMAIL ERROR:",
|
||||
mailError
|
||||
);
|
||||
|
||||
|
||||
return res.status(201).send({
|
||||
success: true,
|
||||
|
||||
message:
|
||||
"Account created, but verification email could not be sent. Please request a new verification email.",
|
||||
|
||||
data: {
|
||||
id:
|
||||
newUser.id,
|
||||
|
||||
firstName:
|
||||
newUser.firstName,
|
||||
|
||||
lastName:
|
||||
newUser.lastName,
|
||||
|
||||
email:
|
||||
newUser.email,
|
||||
|
||||
accountType:
|
||||
newUser.accountType,
|
||||
|
||||
accountStatus:
|
||||
newUser.accountStatus,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
await logActivity({
|
||||
user: req.user,
|
||||
description: `Created New User with ID: ${newUser.id}`,
|
||||
@@ -121,8 +247,8 @@ exports.createNewUser = async (req, res) => {
|
||||
lastName: newUser.lastName,
|
||||
email: newUser.email,
|
||||
accountType: newUser.accountType,
|
||||
role: newUser.role,
|
||||
department: newUser.department,
|
||||
// role: newUser.role,
|
||||
// department: newUser.department,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -136,6 +262,175 @@ exports.createNewUser = async (req, res) => {
|
||||
}
|
||||
};
|
||||
|
||||
// Verify customer email
|
||||
exports.verifyEmail = async (req, res) => {
|
||||
|
||||
const transaction =
|
||||
await db.sequelize.transaction();
|
||||
|
||||
try {
|
||||
|
||||
// 1. Get token from request body
|
||||
const {
|
||||
token
|
||||
} = req.body;
|
||||
|
||||
|
||||
// 2. Token is required
|
||||
if (!token) {
|
||||
|
||||
await transaction.rollback();
|
||||
|
||||
return res.status(400).send({
|
||||
success: false,
|
||||
message:
|
||||
"Verification token is required",
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// 3. Hash the received raw token
|
||||
const tokenHash =
|
||||
hashEmailVerificationToken(
|
||||
token
|
||||
);
|
||||
|
||||
|
||||
// 4. Find matching valid token
|
||||
const verification =
|
||||
await EmailVerification.findOne({
|
||||
|
||||
where: {
|
||||
|
||||
tokenHash:
|
||||
tokenHash,
|
||||
|
||||
usedAt:
|
||||
null,
|
||||
|
||||
expiresAt: {
|
||||
[Op.gt]:
|
||||
new Date(),
|
||||
},
|
||||
},
|
||||
|
||||
transaction,
|
||||
});
|
||||
|
||||
|
||||
// 5. Token invalid / expired / already used
|
||||
if (!verification) {
|
||||
|
||||
await transaction.rollback();
|
||||
|
||||
return res.status(400).send({
|
||||
success: false,
|
||||
message:
|
||||
"Verification token is invalid or expired",
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// 6. Find user
|
||||
const user =
|
||||
await User.findOne({
|
||||
|
||||
where: {
|
||||
id:
|
||||
verification.user_id,
|
||||
},
|
||||
|
||||
transaction,
|
||||
});
|
||||
|
||||
|
||||
// 7. User not found
|
||||
if (!user) {
|
||||
|
||||
await transaction.rollback();
|
||||
|
||||
return res.status(404).send({
|
||||
success: false,
|
||||
message:
|
||||
"User not found",
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// 8. Check already verified
|
||||
if (
|
||||
user.accountStatus === "ACTIVE" &&
|
||||
user.emailVerifiedAt
|
||||
) {
|
||||
|
||||
await transaction.rollback();
|
||||
|
||||
return res.status(400).send({
|
||||
success: false,
|
||||
message:
|
||||
"Email is already verified",
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// 9. Activate account
|
||||
user.accountStatus =
|
||||
"ACTIVE";
|
||||
|
||||
user.emailVerifiedAt =
|
||||
new Date();
|
||||
|
||||
|
||||
await user.save({
|
||||
transaction,
|
||||
});
|
||||
|
||||
|
||||
// 10. Mark token as used
|
||||
verification.usedAt =
|
||||
new Date();
|
||||
|
||||
|
||||
await verification.save({
|
||||
transaction,
|
||||
});
|
||||
|
||||
|
||||
// 11. Commit
|
||||
await transaction.commit();
|
||||
|
||||
|
||||
// 12. Success
|
||||
return res.status(200).send({
|
||||
success: true,
|
||||
message:
|
||||
"Email verified successfully. Your account is now active.",
|
||||
});
|
||||
|
||||
|
||||
} catch (error) {
|
||||
|
||||
if (!transaction.finished) {
|
||||
|
||||
await transaction.rollback();
|
||||
|
||||
}
|
||||
|
||||
|
||||
console.error(
|
||||
"VERIFY EMAIL ERROR:",
|
||||
error
|
||||
);
|
||||
|
||||
|
||||
return res.status(500).send({
|
||||
success: false,
|
||||
message:
|
||||
"Failed to verify email",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Get users with pagination (20 per page)
|
||||
exports.getAllUsers = async (req, res) => {
|
||||
try {
|
||||
|
||||
@@ -41,6 +41,7 @@ db.sequelize = sequelize;
|
||||
|
||||
// User and Authentication
|
||||
db.User = require("./user/user.model")(sequelize, DataTypes);
|
||||
db.EmailVerification = require("./user/emailVerification.model")(sequelize,DataTypes);
|
||||
db.UserActivity = require("./activities/userActivities.model")(sequelize, DataTypes);
|
||||
db.Profile = require("./user/profile.model")(sequelize, DataTypes);
|
||||
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
//app/models/user/emailVerification.model.js
|
||||
module.exports = (sequelize, DataTypes) => {
|
||||
const EmailVerification = sequelize.define(
|
||||
"EmailVerification",
|
||||
{
|
||||
id: {
|
||||
type: DataTypes.STRING,
|
||||
primaryKey: true,
|
||||
},
|
||||
|
||||
user_id: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
|
||||
tokenHash: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
|
||||
expiresAt: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
},
|
||||
|
||||
usedAt: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
tableName: "email_verifications",
|
||||
timestamps: true,
|
||||
}
|
||||
);
|
||||
|
||||
EmailVerification.associate = (db) => {
|
||||
EmailVerification.belongsTo(db.User, {
|
||||
foreignKey: "user_id",
|
||||
as: "user",
|
||||
});
|
||||
};
|
||||
|
||||
return EmailVerification;
|
||||
};
|
||||
@@ -36,9 +36,25 @@ module.exports = (sequelize, DataTypes) => {
|
||||
allowNull: false
|
||||
},
|
||||
accountType: {
|
||||
type: DataTypes.ENUM("user", "admin", "superadmin", "manager", "business_customer", "rider", "customer","support_agent"),
|
||||
defaultValue: "user"
|
||||
}
|
||||
type: DataTypes.ENUM("admin", "superadmin", "manager", "business_customer", "rider", "customer","support_agent"),
|
||||
defaultValue: "customer"
|
||||
},
|
||||
accountStatus: {
|
||||
type: DataTypes.ENUM(
|
||||
"PENDING_VERIFICATION",
|
||||
"ACTIVE",
|
||||
"SUSPENDED",
|
||||
"DEACTIVATED"
|
||||
),
|
||||
allowNull: false,
|
||||
defaultValue: "PENDING_VERIFICATION",
|
||||
},
|
||||
emailVerifiedAt: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
|
||||
},
|
||||
{
|
||||
tableName: "users",
|
||||
@@ -55,6 +71,11 @@ module.exports = (sequelize, DataTypes) => {
|
||||
foreignKey: "user_id",
|
||||
as: "profile",
|
||||
});
|
||||
|
||||
User.hasMany(db.EmailVerification, {
|
||||
foreignKey: "user_id",
|
||||
as: "emailVerifications",
|
||||
});
|
||||
};
|
||||
|
||||
return User;
|
||||
|
||||
@@ -22,11 +22,16 @@ const PERMISSIONS = require("../constants/permissions");
|
||||
|
||||
router.post(
|
||||
"/",
|
||||
authenticate,
|
||||
authorizedAccountType(["admin"]),
|
||||
// authenticate,
|
||||
// authorizedAccountType(["admin"]),
|
||||
userController.createNewUser
|
||||
);
|
||||
|
||||
router.post(
|
||||
"/verify-email",
|
||||
userController.verifyEmail
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/",
|
||||
authenticate,
|
||||
|
||||
@@ -0,0 +1,239 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0"
|
||||
>
|
||||
|
||||
<title>Confirm Your ZUMRI Account</title>
|
||||
</head>
|
||||
|
||||
<body
|
||||
style="
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: #f4f4f4;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
"
|
||||
>
|
||||
|
||||
<table
|
||||
width="100%"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
role="presentation"
|
||||
style="background-color: #f4f4f4; padding: 40px 0;"
|
||||
>
|
||||
<tr>
|
||||
<td align="center">
|
||||
|
||||
<table
|
||||
width="600"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
role="presentation"
|
||||
style="
|
||||
max-width: 600px;
|
||||
width: 100%;
|
||||
background-color: #ffffff;
|
||||
padding: 40px;
|
||||
border-radius: 8px;
|
||||
"
|
||||
>
|
||||
|
||||
<!-- Greeting -->
|
||||
<tr>
|
||||
<td>
|
||||
<p
|
||||
style="
|
||||
font-size: 18px;
|
||||
color: #333333;
|
||||
margin-bottom: 30px;
|
||||
"
|
||||
>
|
||||
Hi {{customer_name}},
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
||||
<!-- Welcome -->
|
||||
<tr>
|
||||
<td>
|
||||
<h2
|
||||
style="
|
||||
color: #222222;
|
||||
margin-bottom: 25px;
|
||||
"
|
||||
>
|
||||
Welcome to ZUMRI!
|
||||
</h2>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
||||
<!-- Description -->
|
||||
<tr>
|
||||
<td>
|
||||
<p
|
||||
style="
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
color: #555555;
|
||||
"
|
||||
>
|
||||
Your account has been successfully registered.
|
||||
To complete your registration and activate your
|
||||
account, please confirm your email address by
|
||||
clicking the button below.
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
||||
<!-- Verification Button -->
|
||||
<tr>
|
||||
<td
|
||||
align="center"
|
||||
style="padding: 30px 0;"
|
||||
>
|
||||
|
||||
<a
|
||||
href="{{confirmation_link}}"
|
||||
style="
|
||||
display: inline-block;
|
||||
padding: 14px 28px;
|
||||
background-color: #222222;
|
||||
color: #ffffff;
|
||||
text-decoration: none;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
border-radius: 6px;
|
||||
"
|
||||
>
|
||||
Confirm Your Email
|
||||
</a>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
||||
<!-- Alternative Link -->
|
||||
<tr>
|
||||
<td>
|
||||
<p
|
||||
style="
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
color: #777777;
|
||||
"
|
||||
>
|
||||
If the button doesn't work, copy and paste
|
||||
the following link into your browser:
|
||||
</p>
|
||||
|
||||
<p
|
||||
style="
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
word-break: break-all;
|
||||
"
|
||||
>
|
||||
<a
|
||||
href="{{confirmation_link}}"
|
||||
style="color: #0066cc;"
|
||||
>
|
||||
{{confirmation_link}}
|
||||
</a>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
||||
<!-- Security Information -->
|
||||
<tr>
|
||||
<td>
|
||||
<p
|
||||
style="
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
color: #777777;
|
||||
margin-top: 30px;
|
||||
"
|
||||
>
|
||||
For security purposes, this confirmation link
|
||||
will expire after a limited period. If you did
|
||||
not create a ZUMRI account, you can safely
|
||||
ignore this email.
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
||||
<!-- Thank You -->
|
||||
<tr>
|
||||
<td>
|
||||
<p
|
||||
style="
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
color: #555555;
|
||||
margin-top: 30px;
|
||||
"
|
||||
>
|
||||
Thank you for joining ZUMRI. We look forward
|
||||
to having you with us!
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
||||
<!-- Footer -->
|
||||
<tr>
|
||||
<td>
|
||||
<p
|
||||
style="
|
||||
font-size: 16px;
|
||||
color: #333333;
|
||||
margin-top: 30px;
|
||||
"
|
||||
>
|
||||
Best regards,<br>
|
||||
<strong>ZUMRI Team</strong>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
||||
<!-- Copyright -->
|
||||
<tr>
|
||||
<td
|
||||
align="center"
|
||||
style="
|
||||
border-top: 1px solid #eeeeee;
|
||||
padding-top: 20px;
|
||||
"
|
||||
>
|
||||
<p
|
||||
style="
|
||||
font-size: 12px;
|
||||
color: #999999;
|
||||
"
|
||||
>
|
||||
© {{currentYear}} ZUMRI.
|
||||
All rights reserved.
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,28 @@
|
||||
//app/utils/emailVerification.util.js
|
||||
const crypto = require("crypto");
|
||||
|
||||
/**
|
||||
* Generate a cryptographically secure
|
||||
* random email-verification token.
|
||||
*/
|
||||
const generateEmailVerificationToken = () => {
|
||||
return crypto.randomBytes(32).toString("hex");
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Hash verification token before
|
||||
* storing it in database.
|
||||
*/
|
||||
const hashEmailVerificationToken = (token) => {
|
||||
return crypto
|
||||
.createHash("sha256")
|
||||
.update(token)
|
||||
.digest("hex");
|
||||
};
|
||||
|
||||
|
||||
module.exports = {
|
||||
generateEmailVerificationToken,
|
||||
hashEmailVerificationToken,
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
const validateEmail = (email) => {
|
||||
// Must be a string
|
||||
if (typeof email !== "string") {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Remove spaces
|
||||
const trimmedEmail = email.trim();
|
||||
|
||||
// Basic email format validation
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
|
||||
return emailRegex.test(trimmedEmail);
|
||||
};
|
||||
const normalizeEmail = (email) => {
|
||||
return email.trim().toLowerCase();
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
validateEmail, normalizeEmail
|
||||
};
|
||||
@@ -1,3 +1,4 @@
|
||||
//app/utils/validation/validatePassword.util.js
|
||||
const validatePassword = (password) => {
|
||||
// Check if password is a string
|
||||
if (typeof password !== "string") {
|
||||
|
||||
Reference in New Issue
Block a user