Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,416 changes: 1,416 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,22 @@
"author": "",
"license": "ISC",
"dependencies": {
"bcrypt": "^5.0.1",
"body-parser": "^1.20.0",
"class-transformer": "^0.5.1",
"class-validator": "^0.13.2",
"cookie-parser": "^1.4.6",
"dotenv": "^16.0.1",
"envalid": "^7.3.1",
"express": "^4.18.1",
"jsonwebtoken": "^8.5.1",
"mongoose": "^6.5.1",
"ts-node": "^10.9.1",
"typescript": "^4.7.4"
},
"devDependencies": {
"@types/bcrypt": "^5.0.0",
"@types/cookie-parser": "^1.4.3",
"@types/jsonwebtoken": "^8.5.8"
}
}
6 changes: 4 additions & 2 deletions src/app.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as express from "express";
import * as bodyParser from "body-parser";
import * as mongoose from "mongoose";
import * as bodyParser from "body-parser";
import * as cookieParser from "cookie-parser";
import Controller from "./interfaces/controller.interface";
import errorMiddleware from "./middleware/error.middleware";

Expand All @@ -27,7 +28,8 @@ class App {
}

private initializeMiddlewares() {
return this.app.use(bodyParser.json());
this.app.use(bodyParser.json());
this.app.use(cookieParser());
}

private initializeErrorHandling() {
Expand Down
11 changes: 11 additions & 0 deletions src/authentication/LogIn.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { IsString } from "class-validator";

class LogInDto {
@IsString()
public email: string;

@IsString()
public password: string;
}

export default LogInDto;
100 changes: 100 additions & 0 deletions src/authentication/authentication.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import * as bcrypt from "bcrypt";
import * as express from "express";
import userModel from "../users/user.model";
import Controller from "../interfaces/controller.interface";
import WrongCredentialsException from "../exceptions/WrongCredentialsException";
import UserWithThatEmailAlreadyExistsException from "../exceptions/UserWithThatEmailAlreadyExistsException";
import validationMiddleware from "../middleware/validation.middleware";
import LogInDto from "./LogIn.dto";
import CreateUserDto from "../users/user.dto";
import User from "users/user.interface";
import tokenData from "interfaces/toktokenData.interface";
import dataStoredInToken from "interfaces/dataStoredInToken";
import * as jwt from "jsonwebtoken";

class AuthenticationController implements Controller {
public path = "/auth";
public router = express.Router();
private user = userModel;

constructor() {
this.initializeRoutes();
}

private initializeRoutes() {
this.router.post(
`${this.path}/register`,
validationMiddleware(CreateUserDto),
this.registration
);
this.router.post(
`${this.path}/login`,
validationMiddleware(LogInDto),
this.logginIn
);
}

private registration = async (
request: express.Request,
response: express.Response,
next: express.NextFunction
) => {
const userData: CreateUserDto = request.body;
if (await this.user.findOne({ email: userData.email })) {
next(new UserWithThatEmailAlreadyExistsException(userData.email));
} else {
const hashedPassword = await bcrypt.hash(userData.password, 10);
const user = await this.user.create({
...userData,
passowrd: hashedPassword,
});
user.password = undefined;
const tokenData = this.createToken(user);
response.setHeader("Set-Cookie", [this.createCookie(tokenData)]);
return response.send(user);
}
};

private logginIn = async (
request: express.Request,
response: express.Response,
next: express.NextFunction
) => {
const logInData: LogInDto = request.body;
const user = await this.user.findOne({ email: logInData.email });

if (user) {
const isPasswordMatching = await bcrypt.compare(
logInData.password,
user.password
);

if (isPasswordMatching) {
user.password = undefined;
return response.send(user);
} else {
return next(new WrongCredentialsException());
}
} else {
return next(new WrongCredentialsException());
}
};

private createCookie(tokenData: tokenData) {
return `Authorization=${tokenData.token}; HttpOnly; Max-Age=${tokenData.expiresIn}`;
}
private createToken(user: User): tokenData {
const expiresIn = 60 * 60; // an hour
const secret = process.env.JWT_SECRET;
const dataStoredInToken: dataStoredInToken = {
_id: user._id,
};

return {
expiresIn,
token: jwt.sign(dataStoredInToken, secret, { expiresIn }),
};
}
}

export default AuthenticationController;
9 changes: 9 additions & 0 deletions src/exceptions/AuthenticationTokenMissingException.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import HttpException from "./HttpException";

class AuthenticationTokenMissingException extends HttpException {
constructor() {
super(401, "Authentication token missing");
}
}

export default AuthenticationTokenMissingException;
9 changes: 9 additions & 0 deletions src/exceptions/UserWithThatEmailAlreadyExistsException.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import HttpException from "./HttpException";

class UserWithThatEmailAlreadyExistsException extends HttpException {
constructor(email: string) {
super(400, `User with email ${email} already exists`);
}
}

export default UserWithThatEmailAlreadyExistsException;
9 changes: 9 additions & 0 deletions src/exceptions/WrongAuthenticationTokenException.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import HttpException from "./HttpException";

class WrongAuthenticationTokenException extends HttpException {
constructor() {
super(401, "Authentication token missing");
}
}

export default WrongAuthenticationTokenException;
9 changes: 9 additions & 0 deletions src/exceptions/WrongCredentialsException.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import HttpException from "./HttpException";

class WrongCredentialsException extends HttpException {
constructor() {
super(401, "Wrong credentials provided");
}
}

export default WrongCredentialsException;
5 changes: 5 additions & 0 deletions src/interfaces/dataStoredInToken.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
interface dataStoredInToken {
_id: string;
}

export default dataStoredInToken;
8 changes: 8 additions & 0 deletions src/interfaces/requestWithUser.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Request } from "express";
import User from "../users/user.interface";

interface RequestWithUser extends Request {
user: User;
}

export default RequestWithUser;
6 changes: 6 additions & 0 deletions src/interfaces/toktokenData.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
interface tokenData {
token: string;
expiresIn: number;
}

export default tokenData;
40 changes: 40 additions & 0 deletions src/middleware/auth.middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { NextFunction, Response } from "express";
import * as jwt from "jsonwebtoken";
import AuthenticationTokenMissingException from "../exceptions/AuthenticationTokenMissingException";
import WrongAuthenticationTokenException from "../exceptions/WrongAuthenticationTokenException";
import dataStoredInToken from "../interfaces/dataStoredInToken";
import RequestWithUser from "../interfaces/requestWithUser.interface";
import userModel from "../users/user.model";

async function authMiddleware(
request: RequestWithUser,
response: Response,
next: NextFunction
) {
const cookies = request.cookies;

if (cookies && cookies.Authorization) {
const secret = process.env.JWT_SECRET;
try {
const verificationResponse = jwt.verify(
cookies.Authorization,
secret
) as dataStoredInToken;
const id = verificationResponse._id;
const user = await userModel.findById(id);

if (user) {
request.user = user;
return next();
} else {
return next(new WrongAuthenticationTokenException());
}
} catch (error) {
return next(new WrongAuthenticationTokenException());
}
} else {
return next(new WrongAuthenticationTokenException());
}
}

export default authMiddleware;
14 changes: 14 additions & 0 deletions src/users/address.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { IsString } from "class-validator";

class CreateAddressDto {
@IsString()
public street: string;

@IsString()
public city: string;

@IsString()
public country: string;
}

export default CreateAddressDto;
22 changes: 22 additions & 0 deletions src/users/user.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { IsOptional, IsString, ValidateNested } from "class-validator";
import CreateAddressDto from "./address.dto";

class CreateUserDto {
@IsString()
public firstName: string;

@IsString()
public lastName: string;

@IsString()
public email: string;

@IsString()
public password: string;

@IsOptional()
@ValidateNested()
public address?: CreateAddressDto;
}

export default CreateUserDto;
15 changes: 15 additions & 0 deletions src/users/user.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
interface User {
_id: string;
firstName: string;
lastName: string;
fullName: string;
name: string;
email: string;
password: string;
address?: {
street: string;
city: string;
};
}

export default User;
12 changes: 12 additions & 0 deletions src/users/user.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import * as mongoose from "mongoose";
import User from "./user.interface";

const userSchema = new mongoose.Schema({
name: String,
email: String,
password: String,
});

const userModel = mongoose.model<User & mongoose.Document>("User", userSchema);

export default userModel;