diff --git a/src/controllers/authController.js b/src/controllers/authController.js
new file mode 100644
index 00000000..7958f3ca
--- /dev/null
+++ b/src/controllers/authController.js
@@ -0,0 +1,307 @@
+import bcrypt from 'bcrypt';
+import { v4 as uuidv4 } from 'uuid';
+import { pool } from '../db.js';
+import jwt from 'jsonwebtoken';
+import { sendMail } from '../utils/mailService.js';
+
+export const register = async (req, res) => {
+ const { name, email, password } = req.body;
+
+ try {
+ const userCheck = await pool.query('SELECT * FROM users WHERE email = $1', [
+ email,
+ ]);
+
+ if (userCheck.rows.length > 0) {
+ return res
+ .status(400)
+ .json({ message: 'User with this email already exists.' });
+ }
+
+ const passwordRegex = /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$/;
+
+ if (!passwordRegex.test(password)) {
+ return res.status(400).json({
+ message:
+ 'Password must be at least 8 characters' +
+ ' long and contain at least one number.',
+ });
+ }
+
+ const passwordHash = await bcrypt.hash(password, 10);
+
+ const activationToken = uuidv4();
+
+ const query = `
+ INSERT INTO users (name, email, password_hash, is_activated, activation_token)
+ VALUES ($1, $2, $3, $4, $5)
+ RETURNING id;
+ `;
+ const values = [name, email, passwordHash, false, activationToken];
+
+ await pool.query(query, values);
+
+ const activationLink = `http://localhost:3000/api/activate/${activationToken}`;
+
+ await sendMail(
+ email,
+ 'Account activation',
+ `Please activate your account by clicking: ${activationLink}`,
+ );
+
+ res
+ .status(201)
+ .json({ message: 'Registration successful! Please check your email.' });
+ } catch (error) {
+ res.status(500).json({ message: 'Internal server error.' });
+ }
+};
+
+export const activate = async (req, res) => {
+ const { token } = req.params;
+
+ try {
+ const user = await pool.query(
+ 'SELECT * FROM users WHERE activation_token = $1',
+ [token],
+ );
+
+ if (user.rows.length === 0) {
+ return res
+ .status(400)
+ .json({ message: 'Invalid or expired activation link.' });
+ }
+
+ await pool.query(
+ 'UPDATE users SET is_activated = true,' +
+ ' activation_token = null WHERE activation_token = $1',
+ [token],
+ );
+
+ res
+ .status(200)
+ .json({ message: 'Account activated successfully! You can now log in.' });
+ } catch (error) {
+ res.status(500).json({ message: 'Internal server error.' });
+ }
+};
+
+export const login = async (req, res) => {
+ const { email, password } = req.body;
+
+ try {
+ const result = await pool.query('SELECT * FROM users WHERE email = $1', [
+ email,
+ ]);
+ const user = result.rows[0];
+
+ if (!user) {
+ return res.status(404).json({ message: 'User not found.' });
+ }
+
+ if (!user.is_activated) {
+ return res
+ .status(403)
+ .json({ message: 'Please activate your email before logging in.' });
+ }
+
+ const isPasswordValid = await bcrypt.compare(password, user.password_hash);
+
+ if (!isPasswordValid) {
+ return res.status(401).json({ message: 'Invalid email or password.' });
+ }
+
+ const accessToken = jwt.sign(
+ { id: user.id, email: user.email },
+ process.env.JWT_ACCESS_SECRET,
+ { expiresIn: '30m' },
+ );
+
+ res.status(200).json({
+ message: 'Login successful!',
+ accessToken,
+ user: {
+ id: user.id,
+ name: user.name,
+ email: user.email,
+ },
+ });
+ } catch (error) {
+ res.status(500).json({ message: 'Internal server error.' });
+ }
+};
+
+export const forgotPassword = async (req, res) => {
+ const { email } = req.body;
+
+ try {
+ const result = await pool.query('SELECT * FROM users WHERE email = $1', [
+ email,
+ ]);
+ const user = result.rows[0];
+
+ if (!user) {
+ return res
+ .status(404)
+ .json({ message: 'User with this email not found.' });
+ }
+
+ const resetToken = uuidv4();
+
+ await pool.query(
+ 'UPDATE users SET activation_token = $1 WHERE email = $2',
+ [resetToken, email],
+ );
+
+ const resetLink = `http://localhost:5173/reset-password/${resetToken}`;
+
+ await sendMail(
+ email,
+ 'Password Reset Request',
+ `
+
+
Password Reset
+
To reset your password, please click the link below:
+
${resetLink}
+
If you didn't request this, just ignore this email.
+
+ `,
+ );
+
+ res.json({ message: 'Password reset link sent to your email.' });
+ } catch (error) {
+ res.status(500).json({ message: 'Internal server error.' });
+ }
+};
+
+export const resetPassword = async (req, res) => {
+ const { token, newPassword, confirmation } = req.body;
+
+ if (newPassword !== confirmation) {
+ return res.status(400).json({ message: 'Passwords do not match.' });
+ }
+
+ try {
+ const user = await pool.query(
+ 'SELECT * FROM users WHERE activation_token = $1',
+ [token],
+ );
+
+ if (user.rows.length === 0) {
+ return res
+ .status(400)
+ .json({ message: 'Invalid or expired reset token.' });
+ }
+
+ const hashedPath = await bcrypt.hash(newPassword, 10);
+
+ await pool.query(
+ 'UPDATE users SET password_hash = $1, ' +
+ 'activation_token = null WHERE activation_token = $2',
+ [hashedPath, token],
+ );
+
+ res.json({
+ message: 'Password has been reset successfully! You can now log in.',
+ });
+ } catch (error) {
+ res.status(500).json({ message: 'Internal server error.' });
+ }
+};
+
+export const updateName = async (req, res) => {
+ const { name } = req.body;
+ const userId = req.user.id;
+
+ try {
+ await pool.query('UPDATE users SET name = $1 WHERE id = $2', [
+ name,
+ userId,
+ ]);
+ res.json({ message: 'Name updated successfully!' });
+ } catch (error) {
+ res.status(500).json({ message: 'Internal server error.' });
+ }
+};
+
+export const updatePassword = async (req, res) => {
+ const { oldPassword, newPassword, confirmation } = req.body;
+ const userId = req.user.id;
+
+ if (newPassword !== confirmation) {
+ return res.status(400).json({ message: 'New passwords do not match.' });
+ }
+
+ try {
+ const result = await pool.query(
+ 'SELECT password_hash FROM users WHERE id = $1',
+ [userId],
+ );
+ const user = result.rows[0];
+
+ const isMatch = await bcrypt.compare(oldPassword, user.password_hash);
+
+ if (!isMatch) {
+ return res.status(401).json({ message: 'Old password is incorrect.' });
+ }
+
+ const hashedPath = await bcrypt.hash(newPassword, 10);
+
+ await pool.query('UPDATE users SET password_hash = $1 WHERE id = $2', [
+ hashedPath,
+ userId,
+ ]);
+
+ res.json({ message: 'Password updated successfully!' });
+ } catch (error) {
+ res.status(500).json({ message: 'Internal server error.' });
+ }
+};
+
+export const updateEmail = async (req, res) => {
+ const { newEmail, password } = req.body;
+ const userId = req.user.id;
+
+ try {
+ const result = await pool.query(
+ 'SELECT email, password_hash FROM users WHERE id = $1',
+ [userId],
+ );
+ const user = result.rows[0];
+ const oldEmail = user.email;
+
+ const isMatch = await bcrypt.compare(password, user.password_hash);
+
+ if (!isMatch) {
+ return res.status(401).json({ message: 'Invalid password.' });
+ }
+
+ const activationToken = uuidv4();
+
+ await pool.query(
+ 'UPDATE users SET email = $1, ' +
+ 'is_activated = false, activation_token = $2 WHERE id = $3',
+ [newEmail, activationToken, userId],
+ );
+
+ const link = `http://localhost:3000/api/activate/${activationToken}`;
+
+ await sendMail(
+ newEmail,
+ 'Confirm your new email',
+ `Activate here: ${link}`,
+ );
+
+ await sendMail(
+ oldEmail,
+ 'Your email has been changed',
+ `The email address for your account was recently changed to ${newEmail}. If you didn't do this, please contact support.`,
+ );
+
+ res.json({
+ message: 'Email updated! Please check your new email to activate it.',
+ });
+ } catch (error) {
+ res.status(500).json({ message: 'Internal server error.' });
+ }
+};
diff --git a/src/db.js b/src/db.js
new file mode 100644
index 00000000..70f4e624
--- /dev/null
+++ b/src/db.js
@@ -0,0 +1,8 @@
+import { Pool } from 'pg';
+import dotenv from 'dotenv';
+
+dotenv.config();
+
+export const pool = new Pool({
+ connectionString: process.env.DATABASE_URL,
+});
diff --git a/src/index.js b/src/index.js
index ad9a93a7..798b0954 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1 +1,21 @@
-'use strict';
+import express from 'express';
+import dotenv from 'dotenv';
+import authRoutes from './routes/authRoutes';
+
+dotenv.config();
+
+const app = express();
+
+app.use(express.json());
+
+app.use('/api', authRoutes);
+
+app.use((req, res) => {
+ res
+ .status(404)
+ .json({ message: 'Opps! This page was not found on the backend' });
+});
+
+const PORT = process.env.PORT || 3000;
+
+app.listen(PORT, () => {});
diff --git a/src/middlewars/authMiddleware.js b/src/middlewars/authMiddleware.js
new file mode 100644
index 00000000..b29ba2df
--- /dev/null
+++ b/src/middlewars/authMiddleware.js
@@ -0,0 +1,20 @@
+import jwt from 'jsonwebtoken';
+
+export const authMiddleware = (req, res, next) => {
+ const authHeader = req.headers.authorization;
+ const token = authHeader && authHeader.split(' ')[1];
+
+ if (!token) {
+ return res.status(401).json({ message: 'Unauthorized. Please log in.' });
+ }
+
+ try {
+ const userData = jwt.verify(token, process.env.JWT_ACCESS_SECRET);
+
+ req.user = userData;
+
+ next();
+ } catch (error) {
+ return res.status(401).json({ message: 'Invalid or expired token.' });
+ }
+};
diff --git a/src/routes/authRoutes.js b/src/routes/authRoutes.js
new file mode 100644
index 00000000..49a0250d
--- /dev/null
+++ b/src/routes/authRoutes.js
@@ -0,0 +1,49 @@
+import { Router } from 'express';
+import {
+ activate,
+ forgotPassword,
+ login,
+ register,
+ resetPassword,
+ updateEmail,
+ updateName,
+ updatePassword,
+} from '../controllers/authController';
+import { authMiddleware } from '../middlewars/authMiddleware';
+
+const router = Router();
+
+router.post('/register', register);
+
+router.get('/activate/:token', activate);
+
+router.post('/login', login);
+
+router.post('/forgot-password', forgotPassword);
+
+router.post('/reset-password', resetPassword);
+
+router.post('/logout', authMiddleware, (req, res) => {
+ res.json({ message: 'Logout successful. Redirecting to login...' });
+});
+
+router.patch('/update-name', authMiddleware, updateName);
+
+router.patch('/update-password', authMiddleware, updatePassword);
+
+router.patch('/update-email', authMiddleware, updateEmail);
+
+router.get('/test', (req, res) => {
+ res.json({ message: 'API is working!' });
+});
+
+router.get('/profile', authMiddleware, (req, res) => {
+ const user = req.user;
+
+ res.json({
+ message: 'Welcome to your profile!',
+ user,
+ });
+});
+
+export default router;
diff --git a/src/utils/mailService.js b/src/utils/mailService.js
new file mode 100644
index 00000000..0e5a00b6
--- /dev/null
+++ b/src/utils/mailService.js
@@ -0,0 +1,23 @@
+import nodemailer from 'nodemailer';
+import dotenv from 'dotenv';
+
+dotenv.config();
+
+const transporter = nodemailer.createTransport({
+ host: 'smtp.gmail.com',
+ port: 465,
+ secure: true,
+ auth: {
+ user: process.env.EMAIL_USER,
+ pass: process.env.EMAIL_PASS,
+ },
+});
+
+export const sendMail = async (to, subject, html) => {
+ await transporter.sendMail({
+ from: process.env.EMAIL_USER,
+ to,
+ subject,
+ html,
+ });
+};