The Rexa system is a comprehensive platform designed to facilitate reward management and redemption within a points-based system. It comprises both a Node.js server and a React-based client application. The server handles user authentication, reward creation and management, transaction processing, and communication with a MongoDB database. The client provides a user interface for browsing rewards, redeeming points, managing profiles, and viewing transaction history. This documentation provides a detailed technical overview of the system's architecture, workflows, and implementation details.
The Rexa system follows a client-server architecture. The server, built with Node.js, Express, and Mongoose, exposes a RESTful API for the client to interact with. The client, built with React, consumes this API to provide a user-friendly interface.
The server-side architecture is structured as follows:
server/src/index.ts
: The entry point of the server application. It initializes the Express app, connects to the MongoDB database, and starts the server.server/src/app.ts
: Configures the Express application with middleware for security (helmet, mongoSanitize, hpp), CORS, and routing.server/src/config/config.ts
: Loads environment variables usingdotenv
and provides a configuration object for the application.server/src/services/logger.ts
: Configures a Winston logger for centralized logging.server/src/services/email.ts
: Implements email sending functionality using Nodemailer.server/src/routes
: Contains route definitions for authentication, rewards, categories, requests, and transactions.server/src/models
: Defines Mongoose schemas for users, rewards, transactions, etc.server/src/scripts/addPointsToExistingUsers.ts
: A script to add initial points to existing users who don't have points assigned.
The client-side architecture is structured as follows:
client/src/main.tsx
: The entry point of the React application. It renders theApp
component.client/src/App.tsx
: Sets up the application's routing usingBrowserRouter
and provides anAuthProvider
to manage authentication state.client/src/config/config.ts
: Defines the API URL based on the environment (development or production).client/src/context
: Contains context providers for authentication (AuthContext
) and dark mode (DarkModeContext
).client/src/pages
: Contains React components for different pages of the application, such asHome
,SignIn
,Register
,Profile
,RewardDetails
,MyRewards
,CreateReward
,EditReward
,TransactionHistory
,NotFound
, andDocumentation
.client/src/components
: Contains reusable React components such asNavbar
,Sidebar
,UserMenu
,RewardCard
,LoadingSpinner
,FloatingActionButton
,EmptyState
,DarkModeToggle
,SearchAndFilter
,PageLayout
,ProfileIcon
,RedeemDialog
,SkeletonLoader
,Toast
,TransactionHistory
, andLogo
.client/src/services/api.ts
: Defines functions for making API requests to the server.client/src/services/mockData.ts
: Provides mock data for development purposes.client/src/types/transaction.ts
: Defines theTransaction
type used in the client.
The user authentication workflow involves the SignIn.tsx
, Register.tsx
, AuthContext.tsx
, and server-side authentication routes.
Workflow:
- Registration:
- User enters registration details in
Register.tsx
. Register.tsx
callsauthApi.register
(defined inclient/src/services/api.ts
) to send the registration data to the server.- The server creates a new user and sends an OTP (One-Time Password) to the user's email via
server/src/services/email.ts
. Register.tsx
then prompts the user to enter the OTP.- The user enters the OTP, and
Register.tsx
callsauthApi.verifyOtp
to verify the OTP. - Upon successful OTP verification, the user is redirected to the sign-in page.
- User enters registration details in
- Sign-in:
- User enters credentials in
SignIn.tsx
. SignIn.tsx
calls thelogin
function fromAuthContext.tsx
.login
function callsauthApi.login
to send the credentials to the server.- The server authenticates the user and returns a JWT (JSON Web Token).
AuthContext.tsx
stores the JWT in local storage and updates the authentication state.- The user is redirected to the home page.
- User enters credentials in
- Profile Fetching:
UserMenu.tsx
andProfile.tsx
callauthApi.getProfile
to fetch the user's profile information from the server.- The server retrieves the user's profile from the database and returns it to the client.
- The client displays the user's profile information.
Data Flow Diagram (User Authentication):
sequenceDiagram
participant User
participant SignIn/Register
participant AuthContext
participant authApi
participant Server
participant Database
User->>SignIn/Register: Enters credentials/registration data
SignIn/Register->>AuthContext: Calls login/register function
AuthContext->>authApi: Calls authApi.login/register
authApi->>Server: Sends credentials/registration data
Server->>Database: Authenticates user/Creates new user
Database-->>Server: Returns user data/success
Server-->>authApi: Returns JWT/success
authApi-->>AuthContext: Returns JWT/success
AuthContext->>AuthContext: Stores JWT in local storage
AuthContext-->>SignIn/Register: Updates authentication state
SignIn/Register-->>User: Redirects to home page
The reward redemption workflow involves the RewardCard.tsx
, RedeemDialog.tsx
, transactionApi.ts
, and server-side transaction routes.
Workflow:
- Initiation:
- User views a reward in
RewardCard.tsx
and clicks the "Redeem" button. RewardCard.tsx
opens theRedeemDialog.tsx
.
- User views a reward in
- Confirmation:
- User confirms the redemption in
RedeemDialog.tsx
. RedeemDialog.tsx
calls theonConfirm
function, which is handled byRewardCard.tsx
.
- User confirms the redemption in
- Transaction Processing:
RewardCard.tsx
callstransactionApi.redeemReward
(defined inclient/src/services/api.ts
) to send a request to the server to redeem the reward.- The server creates a new transaction record in the database, deducts points from the user's account, and updates the reward status.
- The server returns the updated user points to the client.
- Update:
RewardCard.tsx
receives the updated user points and calls theupdatePoints
function fromAuthContext.tsx
to update the user's points in the application state.RewardCard.tsx
closes theRedeemDialog.tsx
and displays a success message.
Data Flow Diagram (Reward Redemption):
sequenceDiagram
participant User
participant RewardCard
participant RedeemDialog
participant transactionApi
participant Server
participant Database
participant AuthContext
User->>RewardCard: Clicks "Redeem" button
RewardCard->>RedeemDialog: Opens RedeemDialog
User->>RedeemDialog: Confirms redemption
RedeemDialog->>RewardCard: Calls onConfirm
RewardCard->>transactionApi: Calls transactionApi.redeemReward
transactionApi->>Server: Sends redemption request
Server->>Database: Creates transaction, updates user points, updates reward status
Database-->>Server: Returns updated user points
Server-->>transactionApi: Returns updated user points
transactionApi-->>RewardCard: Returns updated user points
RewardCard->>AuthContext: Calls updatePoints
AuthContext->>AuthContext: Updates user points in state
RewardCard->>RedeemDialog: Closes RedeemDialog
RewardCard-->>User: Displays success message
The reward creation workflow involves the CreateReward.tsx
, rewardApi.ts
, and server-side reward routes.
Workflow:
- Image Upload and Parsing:
- User uploads an image in
CreateReward.tsx
. CreateReward.tsx
sends the image to an external API (https://dark-lord-chamber-production.up.railway.app/api/process-image
) for OCR and data extraction.- The API returns a JSON response containing the parsed reward data.
- User uploads an image in
- Data Processing and Creation:
CreateReward.tsx
processes the parsed JSON data and displays it to the user.- User can edit the parsed data.
CreateReward.tsx
callsrewardApi.create
(defined inclient/src/services/api.ts
) to send the reward data to the server.- The server creates a new reward record in the database.
- The server returns the created reward data to the client.
- Update:
CreateReward.tsx
receives the created reward data and navigates the user to the rewards page.
Data Flow Diagram (Creating a Reward):
sequenceDiagram
participant User
participant CreateReward
participant ExternalAPI
participant rewardApi
participant Server
participant Database
User->>CreateReward: Uploads image
CreateReward->>ExternalAPI: Sends image for OCR and data extraction
ExternalAPI-->>CreateReward: Returns parsed JSON data
User->>CreateReward: Edits parsed data (optional)
CreateReward->>rewardApi: Calls rewardApi.create
rewardApi->>Server: Sends reward data
Server->>Database: Creates new reward record
Database-->>Server: Returns created reward data
Server-->>rewardApi: Returns created reward data
rewardApi-->>CreateReward: Returns created reward data
CreateReward-->>User: Navigates to rewards page
import nodemailer from 'nodemailer';
interface EmailOptions {
to: string;
subject: string;
html: string;
}
export const sendEmail = async ({ to, subject, html }: EmailOptions): Promise<void> => {
try {
// Create transporter
const transporter = nodemailer.createTransport({
service: 'gmail',
auth: {
user: process.env.EMAIL_USER,
pass: process.env.EMAIL_PASS
}
});
// Email options
const mailOptions = {
from: `rexa <${process.env.EMAIL_USER}>`,
to,
subject,
html
};
// Send email
await transporter.sendMail(mailOptions);
console.log(`Email sent successfully to ${to}`);
} catch (error) {
console.error('Error sending email:', error);
throw new Error('Failed to send email');
}
};
This function uses Nodemailer to send emails. It creates a transporter with Gmail credentials (obtained from environment variables) and sends an email with the provided options.
const AuthContext = createContext<AuthContextType | undefined>(undefined);
export const AuthProvider = ({ children }: { children: ReactNode }) => {
const [user, setUser] = useState<User | null>(() => {
const storedUser = localStorage.getItem('user');
return storedUser ? JSON.parse(storedUser) : null;
});
const [token, setToken] = useState<string | null>(() => localStorage.getItem('token'));
const updatePoints = (newPoints: number) => {
setUser(prevUser => {
if (prevUser) {
const updatedUser = { ...prevUser, points: newPoints };
localStorage.setItem('user', JSON.stringify(updatedUser));
return updatedUser;
}
return prevUser;
});
};
// ... other auth functions
return (
<AuthContext.Provider value={{ user, token, login, logout, isAuthenticated: !!token, updatePoints }}>
{children}
</AuthContext.Provider>
);
};
The updatePoints
function updates the user's points in the application state and local storage. This function is called after a successful reward redemption.
import { useEffect, useState } from 'react';
import { format } from 'date-fns';
import { transactionApi } from '../services/api';
import { Transaction } from '../types/transaction';
import { LoadingSpinner } from './LoadingSpinner';
export const TransactionHistory = () => {
const [transactions, setTransactions] = useState<Transaction[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const fetchTransactions = async () => {
try {
const response = await transactionApi.getHistory();
setTransactions(response.data);
} catch (err) {
console.error('Failed to fetch transactions:', err);
setError('Failed to load transaction history');
} finally {
setIsLoading(false);
}
};
fetchTransactions();
}, []);
if (isLoading) return <LoadingSpinner />;
if (error) return <div className="text-red-500">{error}</div>;
return (
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
<thead className="bg-gray-50 dark:bg-gray-800">
<tr>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Reward
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Date
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Points
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200 dark:divide-gray-700">
{transactions.map(transaction => (
<tr key={transaction._id}>
<td className="px-6 py-4 whitespace-nowrap">
{transaction.reward.title}
</td>
<td className="px-6 py-4 whitespace-nowrap">
{format(new Date(transaction.createdAt), 'yyyy-MM-dd HH:mm:ss')}
</td>
<td className="px-6 py-4 whitespace-nowrap">
-{transaction.reward.points}
</td>
</tr>
))}
</tbody>
</table>
</div>
);
};
This component fetches the transaction history from the server using transactionApi.getHistory
and displays it in a table.
- Install Node.js and npm: Ensure that Node.js and npm are installed on your system.
- Clone the repository: Clone the Rexa repository from GitHub.
- Install server-side dependencies: Navigate to the
server
directory and runnpm install
. - Install client-side dependencies: Navigate to the
client
directory and runnpm install
. - Configure environment variables: Create
.env
files in both theserver
andclient
directories and configure the necessary environment variables (e.g.,MONGODB_URI
,EMAIL_USER
,EMAIL_PASS
,API_URL
). - Start the server: Navigate to the
server
directory and runnpm run dev
. - Start the client: Navigate to the
client
directory and runnpm run dev
.
- Navigate to the "Create Reward" page.
- Upload an image of the coupon or reward.
- The system will attempt to parse the reward details from the image using OCR.
- Review and edit the parsed reward details.
- Click the "Create" button to create the reward.
- Browse the available rewards on the home page.
- Click the "Redeem" button on the reward card.
- Confirm the redemption in the dialog box.
- The system will deduct the reward points from your account and update your transaction history.
- Navigate to the "Profile" page.
- View your profile details, including your name, email, and points balance.
- Click the "Edit" button to edit your profile details.
- Save the changes.
The server uses the cors
middleware to enable Cross-Origin Resource Sharing (CORS). The allowedOrigins
array in server/src/app.ts
specifies the allowed origins. Ensure that the client's URL is included in this array.
The application relies on environment variables for configuration. Ensure that all necessary environment variables are set in the .env
files.
The application implements error handling using try...catch
blocks and the toast
library for displaying error messages to the user.
The application uses several security middleware components, including helmet
, mongoSanitize
, and hpp
, to protect against common web vulnerabilities.
If you encounter CORS errors, ensure that the client's URL is included in the allowedOrigins
array in server/src/app.ts
.
If you encounter database connection errors, ensure that the MONGODB_URI
environment variable is set correctly and that the MongoDB server is running.
If you encounter email sending errors, ensure that the EMAIL_USER
and EMAIL_PASS
environment variables are set correctly and that the Gmail account is configured to allow less secure apps.
If API requests are failing, check the network tab in your browser's developer tools to see the error message returned by the server. Ensure that the server is running and that the API endpoints are configured correctly.
The Winston logger can be customized by modifying the configuration in server/src/services/logger.ts
. You can change the log level, format, and transport.
To add new API endpoints, define the route in the appropriate route file (e.g., server/src/routes/reward.routes.ts
) and implement the corresponding controller logic.
The client-side UI can be customized by modifying the React components in the client/src/components
and client/src/pages
directories.
Optimize database queries by using indexes and efficient query patterns.
Implement caching to reduce the load on the database and improve response times.
Use code splitting to reduce the initial load time of the client-side application.
Optimize images to reduce their file size and improve page load times.
Validate all user inputs to prevent injection attacks.
Implement strong authentication and authorization mechanisms to protect sensitive data.
Encrypt sensitive data at rest and in transit.
Conduct regular security audits to identify and address potential vulnerabilities.
This document provides a comprehensive overview of the rewards and user points system, detailing its architecture, workflows, and implementation. The system allows users to earn points, redeem rewards, and tracks related transactions.
The rewards and user points system comprises several key components:
- User Model: Manages user accounts, including points balance, redeemed rewards, and authentication.
- Reward Model: Defines available rewards with titles, descriptions, and associated images.
- Transaction Model: Records all point transactions, including redemptions, transfers, and other adjustments.
- Reward Redemption Model: Tracks specific instances of reward redemptions by users.
- OTP Model: Manages One-Time Passwords for user verification.
- Category Model: Categorizes rewards.
- Request Model: Manages reward requests between users.
- Validators: Enforces data integrity using Zod schemas.
This system is crucial for incentivizing user engagement, tracking reward distribution, and maintaining a secure and reliable points economy.
The system adopts a modular architecture, with each component responsible for a specific aspect of the rewards and points management.
2.1. Models:
The core of the system revolves around Mongoose models, which define the structure and behavior of data stored in MongoDB.
-
User Model (
server/src/models/user.model.ts
):import mongoose, { Schema, Document } from 'mongoose'; import bcrypt from 'bcryptjs'; export interface IUser extends Document { name: string; email: string; password: string; points: number; redeemedRewards: number; isVerified: boolean; createdAt: Date; updatedAt: Date; comparePassword(candidatePassword: string): Promise<boolean>; } const userSchema = new Schema({ name: { type: String, required: true }, email: { type: String, required: true, unique: true }, password: { type: String, required: true }, points: { type: Number, default: 0 }, redeemedRewards: { type: Number, default: 0 }, isVerified: { type: Boolean, default: false }, }, { timestamps: true }); userSchema.pre('save', async function(next) { if (!this.isModified('password')) return next(); try { const salt = await bcrypt.genSalt(10); this.password = await bcrypt.hash(this.password, salt); next(); } catch (error: any) { next(error); } }); userSchema.methods.comparePassword = async function(candidatePassword: string): Promise<boolean> { try { return await bcrypt.compare(candidatePassword, this.password); } catch (error) { throw new Error('Password comparison failed'); } }; export const User = mongoose.model<IUser>('User', userSchema);
- Defines the
IUser
interface anduserSchema
. - Includes fields for name, email, password, points, redeemed rewards, and verification status.
- Uses
bcryptjs
for password hashing before saving. - Provides a
comparePassword
method for password verification. - Exports the
User
model.
- Defines the
-
Reward Model (
server/src/models/reward.model.ts
):import mongoose, { Schema, Document } from 'mongoose'; export interface IReward extends Document { title: string; image_url : string; description: string; points: number; } const rewardSchema = new Schema({ title: { type: String, required: true }, image_url: { type: String, required: true }, description: { type: String, required: true }, points: { type: Number, required: true }, }); export const Reward = mongoose.model<IReward>('Reward', rewardSchema);
- Defines the
IReward
interface andrewardSchema
. - Includes fields for title, image URL, description, and points cost.
- Exports the
Reward
model.
- Defines the
-
Transaction Model (
server/src/models/transaction.model.ts
):import mongoose, { Schema, Document } from 'mongoose'; export interface ITransaction extends Document { fromUser: mongoose.Types.ObjectId; toUser: mongoose.Types.ObjectId; points: number; reward: mongoose.Types.ObjectId; type: 'redemption'; createdAt: Date; } const transactionSchema = new Schema({ fromUser: { type: Schema.Types.ObjectId, ref: 'User', required: true }, toUser: { type: Schema.Types.ObjectId, ref: 'User', required: true }, points: { type: Number, required: true }, reward: { type: Schema.Types.ObjectId, ref: 'Reward' }, type: { type: String, enum: ['redemption'], required: true }, }, { timestamps: true }); export const Transaction = mongoose.model<ITransaction>('Transaction', transactionSchema);
- Defines the
ITransaction
interface andtransactionSchema
. - Includes fields for
fromUser
,toUser
,points
,reward
, andtype
. - Uses
Schema.Types.ObjectId
to referenceUser
andReward
models. - Exports the
Transaction
model.
- Defines the
-
Reward Redemption Model (
server/src/models/rewardRedemption.model.ts
):import mongoose from 'mongoose'; import User from './User'; import { Reward } from './reward.model'; const rewardRedemptionSchema = new mongoose.Schema({ userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true }, rewardId: { type: mongoose.Schema.Types.ObjectId, ref: 'Reward', required: true }, redeemedAt: { type: Date, default: Date.now }, }); rewardRedemptionSchema.index({ userId: 1, redeemedAt: -1 }); export const RewardRedemption = mongoose.model('RewardRedemption', rewardRedemptionSchema);
- Defines the
rewardRedemptionSchema
. - Includes fields for
userId
,rewardId
, andredeemedAt
. - Uses
mongoose.Schema.Types.ObjectId
to referenceUser
andReward
models. - Exports the
RewardRedemption
model.
- Defines the
-
OTP Model (
server/src/models/otp.model.ts
):import mongoose, { Schema, Document } from 'mongoose'; export interface IOtp extends Document { email: string; otp: string; expiresAt: Date; } const otpSchema = new Schema({ email: { type: String, required: true }, otp: { type: String, required: true }, expiresAt: { type: Date, required: true }, }); export const Otp = mongoose.model<IOtp>('Otp', otpSchema);
- Defines the
IOtp
interface andotpSchema
. - Includes fields for
email
,otp
, andexpiresAt
. - Exports the
Otp
model.
- Defines the
-
Category Model (
server/src/models/category.model.ts
):import mongoose from 'mongoose'; const categorySchema = new mongoose.Schema({ name: { type: String, required: true, unique: true }, slug: { type: String, unique: true }, }); categorySchema.pre('save', function(next) { if (this.isModified('name')) { this.slug = this.name.toLowerCase().replace(/[^a-z0-9]+/g, '-'); } next(); }); export const Category = mongoose.model('Category', categorySchema);
- Defines the
categorySchema
. - Includes fields for
name
andslug
. - Generates a slug from the name before saving.
- Exports the
Category
model.
- Defines the
-
Request Model (
server/src/models/Request.ts
):import mongoose, { Schema, Document } from 'mongoose'; export interface IRequest extends Document { reward: mongoose.Types.ObjectId; sender: mongoose.Types.ObjectId; receiver: mongoose.Types.ObjectId; status: 'pending' | 'accepted' | 'rejected'; message?: string; createdAt: Date; } const RequestSchema = new Schema({ reward: { type: Schema.Types.ObjectId, ref: 'Reward', required: true, }, sender: { type: Schema.Types.ObjectId, ref: 'User', required: true, }, receiver: { type: Schema.Types.ObjectId, ref: 'User', required: true, }, status: { type: String, enum: ['pending', 'accepted', 'rejected'], default: 'pending', }, message: { type: String, }, }, { timestamps: true });
- Defines the
RequestSchema
. - Includes fields for
reward
,sender
,receiver
,status
,message
, andcreatedAt
. - Uses
Schema.Types.ObjectId
to referenceUser
andReward
models.
- Defines the
2.2. Validators:
Zod validators are used to ensure data integrity and consistency.
-
Reward Validator (
server/src/validators/reward.validator.ts
):import { z } from 'zod'; export const rewardSchema = z.object({ title: z.string().min(3, 'Title must be at least 3 characters'), description: z.string().min(10, 'Description must be at least 10 characters'), });
- Defines a Zod schema for validating reward data.
- Ensures that the title is at least 3 characters long and the description is at least 10 characters long.
2.3. Client-Side Types:
Typescript interfaces are used to define the structure of data on the client-side.
-
Transaction Type (
client/src/types/transaction.ts
):export interface Transaction { _id: string; fromUser: { _id: string; username: string; }; toUser: { _id: string; username: string; }; points: number; reward: { _id: string; title: string; }; type: string; createdAt: string; }
- Defines the
Transaction
interface. - Includes fields for
_id
,fromUser
,toUser
,points
,reward
,type
, andcreatedAt
.
- Defines the
-
User Type (
client/src/types/User.ts
):interface User { username: string; // ... other existing properties ... }
- Defines the
User
interface. - Includes a field for
username
.
- Defines the
3.1. User Registration:
- The user submits registration data (name, email, password).
- The server validates the data.
- The server hashes the password using
bcryptjs
. - A new user document is created in the database.
3.2. Reward Creation:
- An administrator submits reward data (title, description, image URL, points).
- The server validates the data using the
rewardSchema
. - A new reward document is created in the database.
3.3. Reward Redemption:
- A user initiates a reward redemption request.
- The server checks if the user has sufficient points.
- A new transaction document is created, recording the point deduction.
- A new reward redemption document is created, linking the user and the reward.
- The user's points balance is updated.
3.4. Requesting a Reward:
- A user initiates a reward request to another user.
- The server validates the data.
- A new request document is created in the database.
sequenceDiagram
participant User
participant Server
participant Database
User->>Server: Initiate Reward Request
Server->>Server: Validate Data
Server->>Database: Create Request Document
Database-->>Server: Confirmation
Server-->>User: Request Confirmation
3.5. Accepting a Reward Request:
- A user accepts a reward request from another user.
- The server validates the data.
- The server checks if the sender has sufficient points.
- A new transaction document is created, recording the point deduction from the sender and addition to the receiver.
- The sender's and receiver's points balance are updated.
- The request status is updated to "accepted".
sequenceDiagram
participant Receiver
participant Server
participant Database
participant Sender
Receiver->>Server: Accept Reward Request
Server->>Server: Validate Data
Server->>Database: Check Sender Points
Database-->>Server: Sender Points
alt Sender has sufficient points
Server->>Database: Create Transaction Documents
Database-->>Server: Confirmation
Server->>Database: Update Sender and Receiver Points
Database-->>Server: Confirmation
Server->>Database: Update Request Status
Database-->>Server: Confirmation
Server-->>Receiver: Request Accepted
Server-->>Sender: Points Deducted
else Sender does not have sufficient points
Server-->>Receiver: Insufficient Points
end
4.1. Creating a New User:
import { User } from '../models/user.model';
async function createUser(name: string, email: string, password: string) {
try {
const newUser = new User({ name, email, password });
await newUser.save();
console.log('User created successfully');
} catch (error) {
console.error('Error creating user:', error);
}
}
4.2. Creating a New Reward:
import { Reward } from '../models/reward.model';
import { rewardSchema } from '../validators/reward.validator';
async function createReward(title: string, description: string, image_url: string, points: number) {
try {
const validatedData = rewardSchema.parse({ title, description }); // Validate data
const newReward = new Reward({ ...validatedData, image_url, points });
await newReward.save();
console.log('Reward created successfully');
} catch (error) {
console.error('Error creating reward:', error);
}
}
4.3. Redeeming a Reward:
import { User } from '../models/user.model';
import { Reward } from '../models/reward.model';
import { Transaction } from '../models/transaction.model';
import { RewardRedemption } from '../models/rewardRedemption.model';
async function redeemReward(userId: string, rewardId: string) {
try {
const user = await User.findById(userId);
const reward = await Reward.findById(rewardId);
if (!user || !reward) {
throw new Error('User or reward not found');
}
if (user.points < reward.points) {
throw new Error('Insufficient points');
}
// Create transaction
const transaction = new Transaction({
fromUser: userId,
toUser: userId, // Redemption is to the same user
points: -reward.points,
reward: rewardId,
type: 'redemption',
});
await transaction.save();
// Create reward redemption record
const rewardRedemption = new RewardRedemption({
userId: userId,
rewardId: rewardId,
});
await rewardRedemption.save();
// Update user points
user.points -= reward.points;
user.redeemedRewards += 1;
await user.save();
console.log('Reward redeemed successfully');
} catch (error) {
console.error('Error redeeming reward:', error);
}
}
5.1. Setting up the Models:
Ensure that Mongoose is connected to your MongoDB database. Import the necessary models and use them to interact with the database.
5.2. Implementing Validation:
Use the Zod schemas to validate data before saving it to the database. This helps prevent invalid data from being stored and ensures data consistency.
5.3. Handling Transactions:
When performing operations that involve transferring points, create transaction records to maintain an audit trail.
- Password Hashing: Always hash passwords using
bcryptjs
before storing them in the database. - Data Validation: Use Zod schemas to validate data before saving it to the database.
- Transaction Management: Ensure that transactions are atomic and consistent.
- Error Handling: Implement proper error handling to prevent unexpected behavior.
- Referential Integrity: Use
Schema.Types.ObjectId
to maintain referential integrity between models.
- Mongoose Connection Errors: Verify that Mongoose is properly connected to the MongoDB database.
- Validation Errors: Check the Zod schemas to ensure that the data is valid.
- Transaction Errors: Ensure that transactions are atomic and consistent.
- Authentication Errors: Verify that the user's credentials are correct.
- Custom Validation Rules: Add custom validation rules to the Zod schemas.
- Custom Transaction Types: Add custom transaction types to the
Transaction
model. - Custom Reward Redemption Logic: Customize the reward redemption logic to suit your specific needs.
- Different Hashing Algorithms: Configure bcrypt to use different hashing rounds.
- Indexing: Add indexes to frequently queried fields to improve query performance.
- Caching: Cache frequently accessed data to reduce database load.
- Pagination: Implement pagination for large datasets to improve performance.
- Database Optimization: Optimize the MongoDB database for performance.
- Password Security: Always hash passwords using
bcryptjs
before storing them in the database. - Data Validation: Use Zod schemas to validate data before saving it to the database.
- Authentication and Authorization: Implement proper authentication and authorization mechanisms to protect sensitive data.
- Input Sanitization: Sanitize user input to prevent cross-site scripting (XSS) attacks.
- Rate Limiting: Implement rate limiting to prevent denial-of-service (DoS) attacks.
This documentation provides a comprehensive overview of the rewards and user points system. By following these guidelines, developers can effectively implement and maintain a secure and reliable points economy.
This document provides a comprehensive overview of the authentication and authorization system, detailing its components, workflows, and usage. The system is designed to manage user authentication, authorization, and related functionalities such as reward redemption and transaction history.
The authentication and authorization system handles user registration, login, profile management, and access control to protected resources. It utilizes JSON Web Tokens (JWT) for authentication and middleware to enforce authorization policies. The system also incorporates rate limiting to prevent abuse and error handling to provide informative feedback to the client.
The system follows a layered architecture, comprising the following key components:
- Routes: Express routes define the API endpoints and map them to controller functions.
- Middleware: Middleware functions handle authentication, authorization, rate limiting, and error handling.
- Controllers: Controller functions implement the business logic for each API endpoint.
- Models: Mongoose models define the data structures and interact with the MongoDB database.
- Services: Services provide reusable utility functions, such as logging.
- Types: TypeScript declaration files define custom types and interfaces.
Component Relationships:
The following diagram illustrates the relationships between the main components:
graph LR
A[Routes] --> B(Middleware)
B --> C{Controllers}
C --> D((Models))
C --> E[Services]
A --> F[Types]
Explanation:
- Routes pass incoming requests to Middleware for processing.
- Middleware can use Controllers to handle specific logic.
- Controllers interact with Models to access and manipulate data.
- Controllers can also use Services for utility functions.
- Routes and Middleware use Types for type safety.
- Registration:
- The user submits registration data (name, email, password) to the
/auth/register
endpoint. - The
register
controller function creates a new user in the database. - An OTP is generated and sent to the user's email for verification.
- The user submits registration data (name, email, password) to the
- Login:
- The user submits login credentials (email, password) to the
/auth/login
endpoint. - The
login
controller function authenticates the user against the database. - A JWT is generated and sent to the client.
- The user submits login credentials (email, password) to the
- OTP Verification:
- The user submits the OTP to the
/auth/verify-otp
endpoint. - The
verifyOtp
controller function verifies the OTP and activates the user account.
- The user submits the OTP to the
Data Flow Diagram:
sequenceDiagram
participant User
participant Client
participant Server
participant Database
User->>Client: Enters registration data
Client->>Server: POST /auth/register
Server->>Database: Create user
Database-->>Server: User data
Server->>Server: Generate OTP, Send Email
Server-->>Client: Success response
User->>Client: Enters OTP
Client->>Server: POST /auth/verify-otp
Server->>Database: Verify OTP
Database-->>Server: Verification result
Server->>Client: Success response
- The user initiates a reward redemption request via the client application.
- The client sends a
POST
request to the/transactions/redeem/:rewardId
endpoint, including the reward ID. - The server's
redeemReward
controller function processes the request. - The controller verifies user authentication and authorization.
- The controller checks if the user has sufficient points to redeem the reward.
- If the user has sufficient points, the controller updates the user's points balance and creates a transaction record.
- The controller returns a success response to the client.
Workflow Visualization:
sequenceDiagram
participant User
participant Client
participant Server
participant Database
User->>Client: Initiates reward redemption
Client->>Server: POST /transactions/redeem/:rewardId
Server->>Server: Authenticate User
alt User is authenticated
Server->>Database: Retrieve user and reward data
Database-->>Server: User and reward data
Server->>Server: Check user points vs. reward points
alt Sufficient points
Server->>Database: Update user points, create transaction
Database-->>Server: Success
Server->>Client: Success response
else Insufficient points
Server->>Client: Error response (insufficient points)
end
else User is not authenticated
Server->>Client: Error response (unauthenticated)
end
- The user initiates a request for a reward via the client application.
- The client sends a
POST
request to the/requests/:rewardId
endpoint, including the reward ID. - The server's
createRequest
controller function processes the request. - The controller verifies user authentication and authorization.
- The controller checks if the reward exists and is available.
- The controller creates a new request record in the database.
- The controller returns a success response to the client.
Workflow Visualization:
sequenceDiagram
participant User
participant Client
participant Server
participant Database
User->>Client: Initiates reward request
Client->>Server: POST /requests/:rewardId
Server->>Server: Authenticate User
alt User is authenticated
Server->>Database: Retrieve reward data
Database-->>Server: Reward data
Server->>Server: Check reward availability
alt Reward is available
Server->>Database: Create request
Database-->>Server: Success
Server->>Client: Success response
else Reward is not available
Server->>Client: Error response (reward unavailable)
end
else User is not authenticated
Server->>Client: Error response (unauthenticated)
end
The express.d.ts
file extends the Express Request
interface to include a user
property, which stores the authenticated user's ID.
import { Request } from 'express';
declare global {
namespace Express {
interface Request {
user?: {
userId: string;
}
}
}
}
This allows middleware and controller functions to access the authenticated user's ID from the req.user
object.
Usage Example:
import { AuthRequest } from '../middleware/auth';
import { Response } from 'express';
export const getTransactionHistory = async (req: AuthRequest, res: Response) => {
const userId = req.user?.userId;
// ...
};
The rateLimiter.ts
file defines rate limiting middleware to prevent abuse of API endpoints.
import rateLimit from 'express-rate-limit';
import { CONFIG } from '../config/config';
export const apiLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100
});
export const authLimiter = rateLimit({
windowMs: 60 * 60 * 1000, // 1 hour
max: 5
});
export const redeemLimiter = CONFIG.RATE_LIMIT_ENABLED
? rateLimit({
windowMs: CONFIG.RATE_LIMIT_WINDOW,
Usage Example:
import { apiLimiter, authLimiter } from '../middleware/rateLimiter';
import express from 'express';
const router = express.Router();
router.post('/login', authLimiter, /* ... */);
router.get('/data', apiLimiter, /* ... */);
The errorHandler.ts
file defines a global error handler that logs errors and sends informative responses to the client.
import { Request, Response, NextFunction } from 'express';
import logger from '../services/logger';
export const errorHandler = (
err: any,
req: Request,
res: Response,
next: NextFunction
) => {
logger.error('Error:', {
message: err.message,
stack: err.stack,
path: req.path,
method: req.method
});
if (err.name === 'ValidationError') {
return res.status(400).json({
message: 'Validation Error',
errors: Object.values(err.errors).map((e: any) => e.message)
});
}
if (err.name === 'MongoError' && err.code === 11000) {
return res.status(400).json({
message: 'Duplicate key error',
Usage Example:
import express from 'express';
import { errorHandler } from '../middleware/errorHandler';
const app = express();
app.get('/error', () => {
throw new Error('This is a test error');
});
app.use(errorHandler);
The auth
middleware is used to protect routes that require authentication.
import { auth } from '../middleware/auth';
import express from 'express';
const router = express.Router();
router.get('/profile', auth, (req, res) => {
// Access req.user.userId here
});
The createReward
controller function handles the creation of new rewards.
import { createReward } from '../controllers/rewardController';
import express from 'express';
import { auth } from '../middleware/auth';
const router = express.Router();
router.post('/', auth, createReward);
The getTransactionHistory
controller function retrieves the transaction history for a user.
import { getTransactionHistory } from '../controllers/transactionController';
import express from 'express';
import { auth } from '../middleware/auth';
const router = express.Router();
router.get('/history', auth, getTransactionHistory);
- JWT Expiration: JWTs have an expiration time. The client must refresh the token before it expires to maintain the session.
- Error Handling: Proper error handling is crucial for providing a good user experience. The
errorHandler
middleware should be used to catch and handle all errors. - Rate Limiting: Rate limiting should be configured appropriately to prevent abuse without impacting legitimate users.
- Database Security: Ensure that the database is properly secured to prevent unauthorized access.
- Authentication Issues:
- Verify that the JWT is valid and has not expired.
- Check that the user's credentials are correct.
- Ensure that the
auth
middleware is properly configured.
- Authorization Issues:
- Verify that the user has the necessary permissions to access the requested resource.
- Check that the authorization middleware is properly configured.
- Rate Limiting Issues:
- If you are being rate limited, try again later.
- If you are a legitimate user and are being rate limited, contact the administrator.
- Database Connection Issues:
- Verify that the database server is running.
- Check that the connection string is correct.
- Ensure that the database user has the necessary permissions.
- JWT Configuration: The JWT secret and expiration time can be configured in the
config.ts
file. - Rate Limiting Configuration: The rate limiting parameters (windowMs, max) can be configured in the
rateLimiter.ts
file. - Logging Configuration: The logging level and output can be configured in the
logger.ts
file.
- Database Queries: Optimize database queries to improve performance. Use indexes where appropriate.
- Caching: Cache frequently accessed data to reduce database load.
- Load Balancing: Use a load balancer to distribute traffic across multiple servers.
- Code Optimization: Optimize code to reduce execution time.
- JWT Security: Protect the JWT secret. Do not store it in the client-side code.
- Input Validation: Validate all user input to prevent injection attacks.
- Output Encoding: Encode all output to prevent cross-site scripting (XSS) attacks.
- Password Hashing: Use a strong password hashing algorithm to protect user passwords.
- Regular Security Audits: Conduct regular security audits to identify and address vulnerabilities.
This documentation provides a comprehensive overview of the authentication and authorization system. By understanding the components, workflows, and best practices, developers can effectively use and maintain this system.
This document provides a comprehensive overview of the authentication system, covering registration, login, profile management, and OTP verification. It details the architecture, workflows, and implementation details necessary for developers to understand and utilize the system effectively.
The authentication system is responsible for managing user accounts, verifying user identities, and controlling access to protected resources. It employs a multi-faceted approach, including:
- Registration: Allows new users to create accounts with email verification.
- Login: Authenticates existing users and grants access to the application.
- Profile Management: Enables users to view and update their profile information.
- OTP Verification: Uses One-Time Passwords (OTPs) for email verification and security.
- JWT Authentication: Employs JSON Web Tokens (JWTs) for secure session management.
This system is crucial for ensuring the security and integrity of the application by verifying user identities and protecting sensitive data.
The authentication system is structured with a clear separation of concerns, utilizing the following components:
- Validators (
auth.validator.ts
): Defines schemas for validating user input during registration and login using Zod. - Routes (
auth.routes.ts
): Defines the API endpoints for authentication-related operations using Express.js. - Middleware (
auth.ts
): Implements authentication middleware to protect routes and verify JWT tokens. - Controllers (
authController.ts
): Handles the business logic for authentication operations, including user creation, OTP generation, and token issuance. - Models (
user.model.ts
,otp.model.ts
- not provided, but implied): Defines the data structures for users and OTPs using Mongoose (assumed). - Configuration (
jwt.config.ts
,config.ts
- partially provided): Stores configuration settings for JWTs and other authentication parameters. - Services (
email.service.ts
- not provided, but implied): Provides email sending functionality for OTP delivery. - Client Services (
auth.service.ts
): Provides client-side methods for interacting with the authentication API. - Client Context (
AuthContext.tsx
): Manages authentication state on the client-side using React Context.
Component Relationships:
auth.routes.ts
importsauthController.ts
to handle route logic andauth.ts
for authentication middleware.authController.ts
importsuser.model.ts
,otp.model.ts
,jwt.config.ts
,auth.ts
, andemail.service.ts
to perform authentication tasks.auth.ts
importsjwt
andconfig.ts
to verify JWT tokens.auth.validator.ts
is used byauthController.ts
(implicitly) to validate request bodies.auth.service.ts
importsapi
(assumed to be an Axios instance) to make API requests to the server.AuthContext.tsx
importsauth.service.ts
to handle login and logout andapi
to fetch user profile.
1. Registration Workflow:
sequenceDiagram
participant User
participant Client
participant Server
participant Database
User->>Client: Enters registration details
Client->>Server: POST /auth/register {name, email, password}
Server->>Server: Validates input
Server->>Database: Checks if email exists
alt Email exists
Server->>Client: 400 Email already registered
else Email does not exist
Server->>Database: Creates new user
Server->>Server: Generates OTP
Server->>Database: Saves OTP
Server->>Server: Sends OTP email
Server->>Client: 200 OK
end
2. Login Workflow:
sequenceDiagram
participant User
participant Client
participant Server
participant Database
User->>Client: Enters login details
Client->>Server: POST /auth/login {email, password}
Server->>Server: Validates input
Server->>Database: Finds user by email
alt User not found
Server->>Client: 401 Invalid credentials
else User found
Server->>Server: Checks if email is verified
alt Email not verified
Server->>Client: 403 Please verify your email first
else Email verified
Server->>Server: Compares password
alt Invalid password
Server->>Client: 401 Invalid credentials
else Valid password
Server->>Server: Generates JWT token
Server->>Client: 200 OK {token}
end
end
end
3. Profile Update Workflow:
sequenceDiagram
participant User
participant Client
participant Server
participant Database
User->>Client: Updates profile details
Client->>Server: PUT /auth/profile {name, email, currentPassword, newPassword}
Server->>Server: Authenticates user (JWT)
Server->>Database: Finds user by ID
alt User not found
Server->>Client: 404 User not found
else User found
Server->>Server: Checks if email is already taken
alt Email already taken
Server->>Client: 400 Email already in use
else Email not taken
Server->>Server: Updates user details
Server->>Database: Saves updated user
Server->>Client: 200 OK {user}
end
end
1. User Registration:
- The user submits their registration details (name, email, password) through the client-side application.
- The client sends a
POST
request to the/auth/register
endpoint on the server. - The server validates the input using the
registerSchema
defined inauth.validator.ts
. - The server checks if the email address is already registered in the database.
- If the email is not registered, the server creates a new user record in the database with
isVerified
set tofalse
. - The server generates a 6-digit OTP using the
generateOtp
function. - The server saves the OTP in the
Otp
model with an expiration time of 10 minutes. - The server sends an email to the user's email address containing the OTP.
- The server responds to the client with a success message.
2. Email Verification (OTP):
- The user receives the OTP in their email and enters it into the client-side application.
- The client sends a
POST
request to the/auth/verify-otp
endpoint on the server, including the user ID and OTP. - The server validates the input.
- The server finds the user by ID.
- The server finds the OTP associated with the user's email.
- The server checks if the OTP has expired.
- If the OTP is valid and not expired, the server updates the user's
isVerified
field totrue
in the database. - The server deletes the OTP record from the database.
- The server generates a JWT token for the user.
- The server responds to the client with a success message and the JWT token.
3. User Login:
- The user submits their login credentials (email, password) through the client-side application.
- The client sends a
POST
request to the/auth/login
endpoint on the server. - The server validates the input using the
loginSchema
defined inauth.validator.ts
. - The server retrieves the user record from the database based on the provided email address.
- The server checks if the user's email is verified (
isVerified
istrue
). - The server compares the provided password with the hashed password stored in the database using
bcrypt.compare
. - If the credentials are valid, the server generates a JWT token for the user.
- The server responds to the client with the JWT token.
4. Profile Management:
- The user accesses their profile page in the client-side application.
- The client retrieves the JWT token from local storage and includes it in the
Authorization
header of subsequent requests. - The client sends a
GET
request to the/auth/profile
endpoint on the server. - The server authenticates the user using the JWT token via the
auth
middleware. - The server retrieves the user's profile information from the database based on the user ID extracted from the JWT token.
- The server responds to the client with the user's profile information (excluding the password).
- The user updates their profile information and submits the changes.
- The client sends a
PUT
request to the/auth/profile
endpoint on the server with the updated profile data. - The server authenticates the user using the JWT token.
- The server updates the user's profile information in the database.
- The server responds to the client with the updated user profile information.
1. Registering a New User (authController.ts
):
export const register = async (req: Request, res: Response) => {
try {
const { name, email, password } = req.body;
// Validate input
if (!name || !email || !password) {
return res.status(400).json({
message: 'Please provide all required fields'
});
}
// Check if email already exists
const existingUser = await User.findOne({ email });
if (existingUser) {
return res.status(400).json({
message: 'Email already registered'
});
}
// Create new user with initial points and isVerified false
const user = new User({
name,
email,
password,
points: 100,
redeemedRewards: 0,
isVerified: false
});
await user.save();
// Generate and save OTP
const otp = generateOtp();
const otpDoc = new Otp({
email,
otp,
expiresAt: new Date(Date.now() + 10 * 60 * 1000) // 10 minutes expiry
});
await otpDoc.save();
// Send OTP email
const htmlContent = `
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: auto; padding: 20px;">
<h2>Welcome to reXa!</h2>
<p>Thank you for registering. Please verify your email using the OTP below:</p>
<h3 style="color: #007bff;">${otp}</h3>
<p>This code is valid for 10 minutes.</p>
</div>
`;
await sendEmail({
to: email,
subject: 'Verify Your Email Address',
html: htmlContent,
});
res.json({
message: 'Registration successful. Please check your email to verify your account.'
});
} catch (error) {
console.error('Registration error:', error);
res.status(500).json({
message: 'Server error during registration'
});
}
};
2. Authenticating a User (authController.ts
):
export const login = async (req: Request, res: Response) => {
try {
const { email, password } = req.body;
const user = await User.findOne({ email });
if (!user) {
return res.status(401).json({ message: 'Invalid credentials' });
}
// Check if email is verified
if (!user.isVerified) {
return res.status(403).json({ message: 'Please verify your email first' });
}
const isValidPassword = await bcrypt.compare(password, user.password);
if (!isValidPassword) {
return res.status(401).json({ message: 'Invalid credentials' });
}
const token = jwt.sign(
{ userId: user._id },
CONFIG.JWT_SECRET!,
{ expiresIn: JWT_CONFIG.expiresIn }
);
res.json({
message: 'Login successful',
token: token,
user: {
id: user._id,
name: user.name,
email: user.email
}
});
} catch (error) {
console.error('Login error:', error);
res.status(500).json({ message: 'Error during login' });
}
};
3. Protecting Routes with Authentication Middleware (auth.ts
):
export const auth = async (req: AuthRequest, res: Response, next: NextFunction) => {
try {
const token = req.header('Authorization')?.replace('Bearer ', '');
if (!token) {
return res.status(401).end();
}
try {
const decoded = jwt.verify(token, CONFIG.JWT_SECRET as string) as { userId: string };
req.user = { userId: decoded.userId };
next();
} catch (jwtError: any) {
if ((jwtError as { name: string }).name === 'TokenExpiredError') {
return res.status(401).json({
code: 'TOKEN_EXPIRED'
});
}
throw jwtError;
}
} catch (error) {
console.error('Auth middleware error:', error);
res.status(401).end();
}
};
4. Client-side Login (auth.service.ts
):
import { api } from './api';
export const authService = {
async login(email: string, password: string) {
const response = await api.post('/auth/login', { email, password });
if (response.data.token) {
localStorage.setItem('token', `Bearer ${response.data.token}`);
}
return response.data;
},
// ... other methods
};
5. Client-side Authentication Context (AuthContext.tsx
):
import React, { createContext, useContext, useState, useEffect } from 'react';
import { api, authApi } from '../services/api';
import { toast } from 'react-hot-toast';
interface User {
_id: string;
name: string;
email: string;
points: number;
}
interface AuthContextType {
user: User | null;
isAuthenticated: boolean;
isLoading: boolean;
login: (email: string, password: string) => Promise<void>;
logout: () => void;
}
const AuthContext = createContext<AuthContextType | undefined>(undefined);
export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
const [user, setUser] = useState<User | null>(() => {
// Initialize user state from localStorage if available
const token = localStorage.getItem('token');
return token ? null : null; // Will be populated by fetchProfile if token exists
});
const [isAuthenticated, setIsAuthenticated] = useState(() => {
// Initialize auth state from localStorage
return !!localStorage.getItem('token');
});
const [isLoading, setIsLoading] = useState(true);
// Function to set auth token in axios headers
const setAuthToken = (token: string | null) => {
if (token) {
localStorage.setItem('token', token);
api.defaults.headers.common['Authorization'] = `Bearer ${token}`;
} else {
localStorage.removeItem('token');
delete api.defaults.headers.common['Authorization'];
}
};
const login = async (email: string, password: string) => {
const response = await authApi.signin({ email, password });
const { token } = response.data;
setAuthToken(token);
await fetchProfile();
};
const logout = () => {
setAuthToken(null);
setUser(null);
setIsAuthenticated(false);
};
const fetchProfile = async () => {
try {
const token = localStorage.getItem('token');
if (!token) {
setIsLoading(false);
return;
}
setAuthToken(token);
const response = await authApi.getProfile();
setUser(response.data);
setIsAuthenticated(true);
} catch (error) {
if (localStorage.getItem('token')) {
console.error('Failed to load profile:', error);
}
logout();
} finally {
setIsLoading(false);
}
};
// Check for token and fetch profile on mount
useEffect(() => {
fetchProfile();
}, []);
const value = {
user,
isAuthenticated,
isLoading,
login,
logout
};
return (
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
);
};
export const useAuth = () => {
const context = useContext(AuthContext);
if (context === undefined) {
throw new Error('useAuth must be used within an AuthProvider');
}
return context;
};
1. Setting up Authentication Routes:
In your app.ts
or main server file, include the authentication routes:
import express from 'express';
import authRoutes from './routes/auth.routes';
const app = express();
app.use('/auth', authRoutes);
2. Protecting Routes with Middleware:
To protect a route, apply the auth
middleware:
import { auth } from './middleware/auth';
router.get('/profile', auth, getProfile);
3. Using the Authentication Context in React:
Wrap your application with the AuthProvider
in AuthContext.tsx
:
import { AuthProvider } from './context/AuthContext';
function App() {
return (
<AuthProvider>
{/* Your application components */}
</AuthProvider>
);
}
Then, use the useAuth
hook in your components:
import { useAuth } from './context/AuthContext';
function ProfilePage() {
const { user, isAuthenticated, isLoading } = useAuth();
if (isLoading) {
return <p>Loading...</p>;
}
if (!isAuthenticated || !user) {
return <p>Please log in.</p>;
}
return (
<div>
<h1>Welcome, {user.name}!</h1>
<p>Email: {user.email}</p>
</div>
);
}
- JWT Secret Management: Ensure the
JWT_SECRET
is stored securely and is not exposed in the codebase. Use environment variables and a secure configuration management system. - OTP Expiration: The OTP expiration time should be carefully chosen to balance security and user convenience. A shorter expiration time increases security but may inconvenience users.
- Password Hashing: Always use a strong password hashing algorithm like bcrypt with a sufficient number of salt rounds.
- Email Sending: Implement robust error handling and retry mechanisms for email sending to ensure reliable OTP delivery.
- CORS Configuration: Configure CORS properly to allow requests from your client-side application.
- Token Expiration: Consider implementing token refresh mechanisms to improve user experience and security.
- Input Validation: Always validate user input on both the client-side and server-side to prevent security vulnerabilities.
- Database Security: Secure your database by using strong passwords, limiting access, and regularly backing up data.
- "Invalid Credentials" Error:
- Verify that the email and password are correct.
- Check if the user's email is verified.
- Inspect the server logs for any errors during password comparison.
- "TokenExpiredError":
- Implement a token refresh mechanism to automatically renew expired tokens.
- Redirect the user to the login page if the token cannot be refreshed.
- "OTP has expired":
- Allow the user to request a new OTP if the current one has expired.
- Ensure that the OTP expiration time is reasonable.
- "Email already registered":
- Prompt the user to log in if they already have an account with the provided email address.
- "Failed to send email":
- Check the email server configuration and credentials.
- Implement error logging and retry mechanisms for email sending.
- CORS Errors:
- Configure CORS on the server to allow requests from the client's origin.
- JWT Expiration Time: Customize the
expiresIn
option injwt.config.ts
to adjust the JWT expiration time. - OTP Length: Modify the
generateOtp
function to generate OTPs of different lengths. - Email Templates: Customize the email templates used for OTP delivery and other notifications.
- Password Complexity Requirements: Implement password complexity requirements (e.g., minimum length, special characters) during registration.
- Rate Limiting: Implement rate limiting to prevent brute-force attacks on the login endpoint.
- Two-Factor Authentication: Integrate two-factor authentication (2FA) for enhanced security.
- Database Queries: Optimize database queries to improve performance. Use indexes and avoid unnecessary data retrieval.
- Caching: Implement caching for frequently accessed data, such as user profiles and configuration settings.
- Load Balancing: Use load balancing to distribute traffic across multiple servers.
- Code Optimization: Optimize code for performance by using efficient algorithms and data structures.
- Gzip Compression: Enable Gzip compression to reduce the size of HTTP responses.
- CDN: Use a Content Delivery Network (CDN) to serve static assets.
- Protect Against Cross-Site Scripting (XSS) Attacks: Sanitize user input to prevent XSS attacks.
- Protect Against Cross-Site Request Forgery (CSRF) Attacks: Use CSRF tokens to protect against CSRF attacks.
- Protect Against SQL Injection Attacks: Use parameterized queries or an ORM to prevent SQL injection attacks.
- Use HTTPS: Always use HTTPS to encrypt communication between the client and server.
- Regularly Update Dependencies: Keep dependencies up-to-date to patch security vulnerabilities.
- Implement Security Audits: Conduct regular security audits to identify and address potential vulnerabilities.
- Follow the Principle of Least Privilege: Grant users only the minimum necessary permissions.
- Monitor Security Logs: Monitor security logs for suspicious activity.
- Store Sensitive Data Securely: Store sensitive data, such as passwords and API keys, securely using encryption and access controls.
- Properly Handle Errors: Avoid exposing sensitive information in error messages.
This documentation provides a comprehensive guide to the authentication system, covering its architecture, workflows, implementation details, and security considerations. By following these guidelines, developers can effectively utilize and maintain the system to ensure secure and reliable user authentication.