Skip to content

Commit

Permalink
Define the login business logic (#750)
Browse files Browse the repository at this point in the history
* chore: define login business logic #715

* chore: update login test #715

* chore: update login controller #715

* Update __tests__/auth.test.js

Co-authored-by: Ijemma Onwuzulike <ijemmaonwuzulike10@gmail.com>

* Update __tests__/auth.test.js

Co-authored-by: Ijemma Onwuzulike <ijemmaonwuzulike10@gmail.com>

* Update src/controllers/auth/login.ts

Co-authored-by: Ijemma Onwuzulike <ijemmaonwuzulike10@gmail.com>

* Update src/controllers/auth/login.ts

Co-authored-by: Ijemma Onwuzulike <ijemmaonwuzulike10@gmail.com>

* chore: sync fixes #750

* Update src/shared/constants/Developers.ts

Co-authored-by: Ijemma Onwuzulike <ijemmaonwuzulike10@gmail.com>

* Update src/controllers/auth/login.ts

Co-authored-by: Ijemma Onwuzulike <ijemmaonwuzulike10@gmail.com>

* Update src/controllers/auth/login.ts

Co-authored-by: Ijemma Onwuzulike <ijemmaonwuzulike10@gmail.com>

* Update src/siteConstants.js

Co-authored-by: Ijemma Onwuzulike <ijemmaonwuzulike10@gmail.com>

* chore: convert siteConstants to typescript

* chore: update dotenv

* chore: update environment variables

* chore: update environment variables

* Update @types/environment.d.ts

Co-authored-by: Ijemma Onwuzulike <ijemmaonwuzulike10@gmail.com>

---------

Co-authored-by: Ijemma Onwuzulike <ijemmaonwuzulike10@gmail.com>
  • Loading branch information
davydocsurg and ijemmao authored Sep 27, 2023
1 parent 4ce635c commit 7a3f647
Show file tree
Hide file tree
Showing 9 changed files with 129 additions and 16 deletions.
1 change: 1 addition & 0 deletions @types/environment.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ declare global {
interface ProcessEnv {
// @ts-expect-error Nodejs process override
readonly NODE_ENV: 'development' | 'production' | 'test' | 'build';
readonly JWT_SECRET: string
}
}
}
Expand Down
7 changes: 4 additions & 3 deletions __tests__/__mocks__/documentData.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import mongoose from 'mongoose';
import { v4 as uuid } from 'uuid';

const { ObjectId } = mongoose.Types;

Expand All @@ -11,9 +12,9 @@ const developerData = {
};

const newDeveloperData = {
name: 'New Developer',
email: 'newdeveloper@example.com',
password: 'password',
name: `${uuid().replace(/-/g, '')}`,
email: `${uuid().replace(/-/g, '')}@testing.com`,
password: `${uuid()}`,
};

const malformedDeveloperData = {
Expand Down
16 changes: 9 additions & 7 deletions __tests__/auth.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@ import { newDeveloperData } from './__mocks__/documentData';
import { createDeveloper, loginDeveloper } from './shared/commands';

describe('login', () => {
it('should hit the login endpoint and return a message', async () => {
const res = await createDeveloper(newDeveloperData);
it('should successfully log a developer in', async () => {
await createDeveloper(newDeveloperData);
const data = {
email: res.body.email,
password: res.body.password,
email: newDeveloperData.email,
password: newDeveloperData.password,
};
const login = await loginDeveloper(data);
expect(login.status).toEqual(200);
expect(login.body.message).toEqual('Logging in...');

const loginRes = await loginDeveloper(data);

expect(loginRes.status).toEqual(200);
expect(loginRes.body.developer).toMatchObject(loginRes.body.developer);
});
});
1 change: 1 addition & 0 deletions env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ declare global {
namespace NodeJS {
export interface ProcessEnv {
NODE_ENV: 'build' | 'development' | 'production' | 'test';
JWT_SECRET: '@developer@NkowaOkwu@secret@key@';
}
}
}
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@
"is-word": "^1.0.4",
"jest": "^29.2.2",
"joi": "^17.4.0",
"jsonwebtoken": "^9.0.2",
"lodash": "^4.17.20",
"migrate-mongo": "^8.1.4",
"mongoose": "^6.7.0",
Expand Down Expand Up @@ -156,6 +157,7 @@
"@types/compression": "^1.7.2",
"@types/express-rate-limit": "^6.0.0",
"@types/jest": "^29.5.4",
"@types/jsonwebtoken": "^9.0.2",
"@types/morgan": "^1.9.4",
"@types/node": "^20.3.0",
"@types/react": "^18.2.11",
Expand Down
99 changes: 95 additions & 4 deletions src/controllers/auth/login.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,101 @@
import { Express } from '../../types';
import { isTest } from '../../config';
import { Response } from 'express';
import jwt from 'jsonwebtoken';
import bcrypt from 'bcrypt';
import { Developer as DeveloperType, Express } from '../../types';
import { isProduction, isTest } from '../../config';
import { createDbConnection, handleCloseConnection } from '../../services/database';
import { developerSchema } from '../../models/Developer';
import { TEST_EMAIL } from '../../shared/constants/Developers';
import { JWT_SECRET, cookieOptions } from '../../siteConstants';

export const login: Express.MiddleWare = (req, res, next) => {
/**
* Compares a hashed password with a plaintext password to check for a match.
*
* @param {string} password - The plaintext password.
* @param {string} hash - The hashed password stored in the database.
* @returns {Promise<boolean>} A Promise that resolves to true if the passwords match, or false if they do not.
* @throws {Error} If an error occurs during the password comparison process.
*/
const checkPassword = async (password: string, hash: string) => {
const result = await bcrypt.compare(password, hash);
return result;
};

/**
* Signs a JSON Web Token (JWT) with the provided data.
*
* @param {string} email - The developer's email.
* @returns {string} The signed JWT token.
* @throws {Error} If an error occurs during the token signing process.
*/
const signToken = (email: string) => {
console.info('JWT_SECRET');
console.log(JWT_SECRET);

const token = jwt.sign({ email }, JWT_SECRET, { expiresIn: '1d' });
return token;
};

/**
* Creates and sends a JSON Web Token (JWT) to the client in a response.
*
* @param {DeveloperType} developer - The developer data to include in the JWT payload.
* @param {Express.Response} res - The Express response object to send the token in.
* @returns {string} The signed JWT token.
* @throws {Error} If an error occurs during the token signing or sending process.
*/
const createSendToken = (developer: DeveloperType, res: Response) => {
const token = signToken(developer.email);
if (isProduction) cookieOptions.secure = true;
res.cookie('jwt', token, cookieOptions);

return token;
};

/**
* Authenticates a developer with the provided email and password.
*
* @param {string} email - The developer's email.
* @param {string} password - The developer's password.
* @returns {Promise<DeveloperType | null>} A Promise that resolves with the authenticated developer or null
* @throws {Error} If an error occurs during the authentication process.
*/
const loginDeveloperWithEmailAndPassword = async (email: string, password: string) => {
const connection = createDbConnection();
const Developer = connection.model('Developer', developerSchema);
const developer = await Developer.findOne({ email }).select('+password');
if ((!developer && email !== TEST_EMAIL) || !(await checkPassword(password, developer!.password))) {
throw new Error('Email or password is incorrect.');
}
const loggedInDev = {
name: developer?.name,
email: developer?.email,
usage: developer?.usage,
};
await handleCloseConnection(connection);
return loggedInDev;
};

/**
* Handles the login process for a developer.
*
* @param {Express.Request} req - The Express request object.
* @param {Express.Response} res - The Express response object.
* @param {Express.NextFunction} next - The next middleware function.
* @returns {Promise<void>} A Promise that resolves when the login process is complete.
* @throws {Error} If an error occurs during the login process.
*/
export const login: Express.MiddleWare = async (req, res, next) => {
try {
const { email, password } = req.body;

const developer = await loginDeveloperWithEmailAndPassword(email, password);
const token = createSendToken(developer as DeveloperType, res);

return res.status(200).send({
message: 'Logging in...',
message: 'Logged in successfully',
developer,
token,
});
} catch (error) {
if (!isTest) {
Expand Down
3 changes: 1 addition & 2 deletions src/controllers/developers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ import { createDbConnection, handleCloseConnection } from '../services/database'
import { Express } from '../types';
import { sendNewDeveloper } from './email';
import { findDeveloper } from './utils/findDeveloper';

const TEST_EMAIL = 'developer@example.com';
import { TEST_EMAIL } from '../shared/constants/Developers';

/* Creates a new apiKey to be associated with a developer */
const generateApiKey = uuid;
Expand Down
1 change: 1 addition & 0 deletions src/shared/constants/Developers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const TEST_EMAIL = 'developer@example.com';
15 changes: 15 additions & 0 deletions src/siteConstants.js → src/siteConstants.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import dotenv from 'dotenv';

dotenv.config();

/* This file includes the constants necessary to power the API homepage */
export const PORT = 8080;
export const API_FROM_EMAIL = 'kedu@nkowaokwu.com';
Expand All @@ -17,3 +21,14 @@ export const YOUTUBE = 'https://www.youtube.com/c/IjemmaOnwuzulike';
// Projects
export const NKOWAOKWU = 'https://nkowaokwu.com';
export const NKOWAOKWU_CHROME = 'https://nkowaokwu.com/chrome';

// Auth
export const JWT_SECRET = process.env.JWT_SECRET || '@developer@NkowaOkwu@secret@key@';
const COOKIE_EXPIRATION_DAYS = 90; // cookie expiration in days
// Calculate the expiration date based on the current time and the number of days until expiration
const expirationDate = new Date(Date.now() + COOKIE_EXPIRATION_DAYS * 24 * 60 * 60 * 1000);
export const cookieOptions = {
expires: expirationDate,
secure: false,
httpOnly: true,
};

0 comments on commit 7a3f647

Please sign in to comment.