Skip to content

Commit

Permalink
Merge pull request #38 from hngprojects/feat/32-Authentication_User_L…
Browse files Browse the repository at this point in the history
…og_In

Authentication - User Log In
  • Loading branch information
Idimmusix authored Jul 20, 2024
2 parents bcabe30 + 424c06e commit 43465de
Show file tree
Hide file tree
Showing 21 changed files with 167 additions and 5,959 deletions.
5,888 changes: 0 additions & 5,888 deletions package-lock.json

This file was deleted.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"scripts": {
"start:dev": "ts-node-dev --respawn --transpile-only ./src/index",
"start": "ts-node --transpile-only src/index.ts",
"test": "jest",
"test": "jest --passWithNoTests",
"typeorm": "typeorm-ts-node-commonjs",
"build": "tsc",
"prod": "node dist/index.js"
Expand All @@ -20,7 +20,7 @@
"@types/node": "^16.18.103",
"@types/supertest": "^6.0.2",
"ts-node": "^10.9.1",
"typescript": "5.5.3"
"typescript": "^5.5.3"
},
"dependencies": {
"@types/bcryptjs": "^2.4.6",
Expand Down
66 changes: 36 additions & 30 deletions src/controllers/AuthController.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,36 @@
import { Request, Response, NextFunction } from "express";

import { AuthService } from "../services";

/*
*TODO: add validation
*/
const authService = new AuthService();
const signUp = async (req: Request, res: Response, next: NextFunction) => {
try {
const { mailSent, newUser, access_token } = await authService.signUp(
req.body
);
res.status(201).json({ mailSent, newUser, access_token });
} catch (error) {
next(error);
}
};

const verifyOtp = async (req: Request, res: Response, next: NextFunction) => {
try {
const { otp, token } = req.body;
const { message } = await authService.verifyEmail(token as string, otp);
res.status(200).json({ message });
} catch (error) {
next(error);
}
};

export { signUp, verifyOtp };
import { Request, Response, NextFunction } from "express";
import { AuthService } from "../services";

const authService = new AuthService();

const signUp = async (req: Request, res: Response, next: NextFunction) => {
try {
const { mailSent, newUser, access_token } = await authService.signUp(
req.body
);
res.status(201).json({ mailSent, newUser, access_token });
} catch (error) {
next(error);
}
};

const verifyOtp = async (req: Request, res: Response, next: NextFunction) => {
try {
const { otp, token } = req.body;
const { message } = await authService.verifyEmail(token as string, otp);
res.status(200).json({ message });
} catch (error) {
next(error);
}
};

const login = async (req: Request, res: Response, next: NextFunction) => {
try {
const { access_token, user } = await authService.login(req.body);
res.status(200).json({ access_token, user });
} catch (error) {
next(error);
}
};

export { signUp, verifyOtp, login };
2 changes: 1 addition & 1 deletion src/controllers/UserController.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// src/controllers/UserController.ts
import { Request, Response } from "express";
import { UserService } from "../services/UserServivce";
import { UserService } from "../services/user.services";

class UserController {
private userService: UserService;
Expand Down
14 changes: 7 additions & 7 deletions src/data-source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ export const AppDataSource = new DataSource({
username: config.DB_USER,
password: config.DB_PASSWORD,
database: config.DB_NAME,
synchronize: true,
synchronize: false,
logging: false,
entities: ["src/models/**/*.ts"],
ssl: false,
extra: {
ssl: {
rejectUnauthorized: false,
},
},
// ssl: false,
// extra: {
// ssl: {
// rejectUnauthorized: false,
// },
// },
});
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// src/index.ts
import "reflect-metadata";
import { AppDataSource } from "./data-source";
import log from "./utils/logger";
Expand Down
8 changes: 3 additions & 5 deletions src/models/article.ts → src/models/helpcentertopic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,11 @@ import {
Column,
CreateDateColumn,
UpdateDateColumn,
ManyToOne,
} from "typeorm";
import ExtendedBaseEntity from "./extended-base-entity";
import { User } from ".";

@Entity()
export class Article extends ExtendedBaseEntity {
export class HelpCenterTopic extends ExtendedBaseEntity {
@PrimaryGeneratedColumn("uuid")
id: string;

Expand All @@ -20,8 +18,8 @@ export class Article extends ExtendedBaseEntity {
@Column()
content: string;

@ManyToOne(() => User, (user) => user.articles)
author: User;
@Column()
author: string;

@CreateDateColumn()
createdAt: Date;
Expand Down
11 changes: 6 additions & 5 deletions src/models/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from "./User";
export * from "./Organization";
export * from "./Profile";
export * from "./Product";
export * from "./article";
export * from "./user";
export * from "./organization";
export * from "./profile";
export * from "./product";
export * from "./helpcentertopic";
export * from "./notification";
21 changes: 21 additions & 0 deletions src/models/notification.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Entity, PrimaryGeneratedColumn, Column, Unique } from "typeorm";
import ExtendedBaseEntity from "./extended-base-entity";

@Entity()
@Unique(["user_id"])
export class NotificationSetting extends ExtendedBaseEntity {
@PrimaryGeneratedColumn()
id: number;

@Column()
user_id: string;

@Column()
email_notifications: boolean;

@Column()
push_notifications: boolean;

@Column()
sms_notifications: boolean;
}
2 changes: 1 addition & 1 deletion src/models/Organization.ts → src/models/organization.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Entity, PrimaryGeneratedColumn, Column, ManyToMany } from "typeorm";
import { User } from "./User";
import { User } from "./user";
import ExtendedBaseEntity from "./extended-base-entity";

@Entity()
Expand Down
2 changes: 1 addition & 1 deletion src/models/Product.ts → src/models/product.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne } from "typeorm";
import { User } from "./User";
import { User } from "./user";
import ExtendedBaseEntity from "./extended-base-entity";

@Entity()
Expand Down
2 changes: 1 addition & 1 deletion src/models/Profile.ts → src/models/profile.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Entity, PrimaryGeneratedColumn, Column, OneToOne } from "typeorm";
import { User } from "./User";
import { User } from "./user";
import ExtendedBaseEntity from "./extended-base-entity";

@Entity()
Expand Down
28 changes: 28 additions & 0 deletions src/models/sms.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
ManyToOne,
} from "typeorm";
import ExtendedBaseEntity from "./extended-base-entity";
import { User } from ".";

@Entity()
export class Sms extends ExtendedBaseEntity {
@PrimaryGeneratedColumn("uuid")
id: string;

@Column()
phone_number: string;

@Column()
message: string;

@Column()
@ManyToOne(() => User, (user) => user.id)
sender_id: User;

@CreateDateColumn()
createdAt: Date;
}
5 changes: 1 addition & 4 deletions src/models/User.ts → src/models/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
CreateDateColumn,
UpdateDateColumn,
} from "typeorm";
import { Profile, Product, Organization, Article } from ".";
import { Profile, Product, Organization } from ".";
import { IsEmail } from "class-validator";
import ExtendedBaseEntity from "./extended-base-entity";
import { getIsInvalidMessage } from "../utils";
Expand Down Expand Up @@ -59,9 +59,6 @@ export class User extends ExtendedBaseEntity {
@JoinTable()
products: Product[];

@OneToMany(() => Article, (article) => article.author, { cascade: true })
articles: Article[];

@ManyToMany(() => Organization, (organization) => organization.users, {
cascade: true,
})
Expand Down
3 changes: 2 additions & 1 deletion src/routes/auth.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { signUp, verifyOtp } from "../controllers";
import { signUp, verifyOtp, login } from "../controllers";
import { Router } from "express";

const authRoute = Router();

authRoute.post("/signup", signUp);
authRoute.post("/verify-otp", verifyOtp);
authRoute.post("/login", login);

export { authRoute };
40 changes: 37 additions & 3 deletions src/services/auth.services.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { AppDataSource } from "../data-source";
import { User, Profile, Organization } from "../models";
import { IAuthService, IUserSignUp } from "../types";
import { Profile, User } from "../models";
import { IAuthService, IUserSignUp, IUserLogin } from "../types";
import { Conflict, HttpError } from "../middleware";
import { hashPassword, generateNumericOTP } from "../utils";
import { hashPassword, generateNumericOTP, comparePassword } from "../utils";
import { Sendmail } from "../utils/mail";
import jwt from "jsonwebtoken";
import { compilerOtp } from "../views/welcome";
Expand Down Expand Up @@ -62,6 +62,7 @@ export class AuthService implements IAuthService {
throw new HttpError(error.status || 500, error.message || error);
}
}

public async verifyEmail(
token: string,
otp: number
Expand Down Expand Up @@ -90,4 +91,37 @@ export class AuthService implements IAuthService {
throw new HttpError(error.status || 500, error.message || error);
}
}

public async login(
payload: IUserLogin
): Promise<{ access_token: string; user: Partial<User> }> {
const { email, password } = payload;

try {
const user = await User.findOne({ where: { email } });

if (!user) {
throw new HttpError(404, "User not found");
}

const isPasswordValid = await comparePassword(password, user.password);
if (!isPasswordValid) {
throw new HttpError(401, "Invalid credentials");
}

if (!user.isverified) {
throw new HttpError(403, "Email not verified");
}

const access_token = jwt.sign({ userId: user.id }, config.TOKEN_SECRET, {
expiresIn: "1d",
});

const { password: _, ...userWithoutPassword } = user;

return { access_token, user: userWithoutPassword };
} catch (error) {
throw new HttpError(error.status || 500, error.message || error);
}
}
}
2 changes: 1 addition & 1 deletion src/services/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export * from "./auth.services";
export * from "./UserServivce";
export * from "./user.services";
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// src/services/UserService.ts
import { User } from "../models/User";
import { User } from "../models/user";
import { IUserService } from "../types";

export class UserService implements IUserService {
Expand Down
9 changes: 7 additions & 2 deletions src/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,21 @@ export interface IRole {
role: "super_admin" | "admin" | "user";
}

interface IUserSignUp {
export interface IUserSignUp {
firstName: string;
lastName: string;
email: string;
password: string;
phone: string;
}

export interface IUserLogin {
email: string;
password: string;
}

export interface IAuthService {
// login(email: string, password: string): Promise<User>;
login(payload: IUserLogin): Promise<unknown>;
signUp(payload: IUserSignUp, res: unknown): Promise<unknown>;
verifyEmail(token: string, otp: number): Promise<{ message: string }>;
}
6 changes: 5 additions & 1 deletion src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,12 @@ export async function hashPassword(password: string): Promise<string> {
return await bcrypt.hash(password, 10);
}

export async function comparePassword(password: string, hashedPassword: string): Promise<boolean> {
return await bcrypt.compare(password, hashedPassword);
}

export async function generateAccessToken(user_id: string) {
return jwt.sign(user_id, config.TOKEN_SECRET, { expiresIn: 60 });
return jwt.sign({ user_id }, config.TOKEN_SECRET, { expiresIn: "1d" });
}

export const generateNumericOTP = (length: number): string => {
Expand Down
10 changes: 5 additions & 5 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -740,7 +740,7 @@

"@types/jest@^29.5.12":
version "29.5.12"
resolved "https://registry.npmjs.org/@types/jest/-/jest-29.5.12.tgz"
resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.12.tgz#7f7dc6eb4cf246d2474ed78744b05d06ce025544"
integrity sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==
dependencies:
expect "^29.0.0"
Expand Down Expand Up @@ -3699,10 +3699,10 @@ typeorm@^0.3.20:
uuid "^9.0.0"
yargs "^17.6.2"

typescript@4.5.2:
version "4.5.2"
resolved "https://registry.npmjs.org/typescript/-/typescript-4.5.2.tgz"
integrity sha512-5BlMof9H1yGt0P8/WF+wPNw6GfctgGjXp5hkblpyT+8rkASSmkUKMXrxR0Xg8ThVCi/JnHQiKXeBaEwCeQwMFw==
typescript@^5.5.3:
version "5.5.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.3.tgz#e1b0a3c394190838a0b168e771b0ad56a0af0faa"
integrity sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==

uglify-js@^3.1.4:
version "3.19.0"
Expand Down

0 comments on commit 43465de

Please sign in to comment.