Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Logout feature #26

Merged
merged 12 commits into from
May 31, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
67 changes: 32 additions & 35 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ This is the backend for E-Commerce-Ninjas, written in Node.js with TypeScript.
[![CircleCI](https://dl.circleci.com/status-badge/img/gh/atlp-rwanda/e-commerce-ninjas-bn/tree/develop.svg?style=svg)](https://dl.circleci.com/status-badge/redirect/gh/atlp-rwanda/e-commerce-ninjas-bn/tree/develop)
[![codecov](https://codecov.io/gh/atlp-rwanda/e-commerce-ninjas-bn/graph/badge.svg?token=6ZWudFPM1S)](https://codecov.io/gh/atlp-rwanda/e-commerce-ninjas-bn)


## HOSTED SERVER URL

[https://e-commerce-ninjas-backend.onrender.com/](https://e-commerce-ninjas-backend.onrender.com/)
Expand All @@ -22,27 +21,25 @@ This is the backend for E-Commerce-Ninjas, written in Node.js with TypeScript.

[https://github.com/atlp-rwanda/e-commerce-ninjas-bn](https://github.com/atlp-rwanda/e-commerce-ninjas-bn)


## COMPLETED FEATURES

- Welcome Endpoint
- Register Endpoint
- Verification Email Endpoint
- Resend verification Endpoint
- Login Endpoint
- Logout Endpoint

## TABLE OF API ENDPOINTS SPECIFICATION AND DESCRIPTION


| No | VERBS | ENDPOINTS | STATUS | ACCESS | DESCRIPTION |
|----|-------|------------------------------|-------------|--------|---------------------------|
| 1 | GET | / | 200 OK | public | Show welcome message |
| 2 | POST | /api/auth/register | 201 CREATED | public | create user account |
| 3 | GET | /api/auth/verify-email/:token| 200 OK | public | Verifying email |
| 4 | POST | /api/auth/send-verify-email | 200 OK | public | Resend verification email |
| 4 | POST | /api/auth/login | 200 OK | public | Login with Email and Password |


| No | VERBS | ENDPOINTS | STATUS | ACCESS | DESCRIPTION |
| --- | ----- | ----------------------------- | ----------- | ------ | ----------------------------- |
| 1 | GET | / | 200 OK | public | Show welcome message |
| 2 | POST | /api/auth/register | 201 CREATED | public | create user account |
| 3 | GET | /api/auth/verify-email/:token | 200 OK | public | Verifying email |
| 4 | POST | /api/auth/send-verify-email | 200 OK | public | Resend verification email |
| 5 | POST | /api/auth/login | 200 OK | public | Login with Email and Password |
| 6 | POST | /api/auth/logout | 200 OK | public | Logout user |
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The access is not Public, should be Private. Because you can't access Logout without Logged-In.


## INSTALLATION

Expand Down Expand Up @@ -89,34 +86,34 @@ This is the backend for E-Commerce-Ninjas, written in Node.js with TypeScript.
## INITILIAZE SEQUELIZE CLI

1. Initialize Sequelize CLI:
```sh
npx sequelize-cli init
```
```sh
npx sequelize-cli init
```
2. Generate Seeder:
```sh
npx sequelize-cli seed:generate --name name-of-your-seeder
```
```sh
npx sequelize-cli seed:generate --name name-of-your-seeder
```
3. Generate Migrations:
```sh
npx sequelize-cli migration:generate --name name-of-your-migration
```
```sh
npx sequelize-cli migration:generate --name name-of-your-migration
```
4. Define Migration:
Edit the generated migration file to include the tables you want to create.
Edit the generated migration file to include the tables you want to create.
5. Define Seeder Data:
Edit the generated seeder file to include the data you want to insert.
Edit the generated seeder file to include the data you want to insert.
6. Run the Seeder:
```sh
npm run createAllSeeders
```
```sh
npm run createAllSeeders
```
7. Run the Migration:
```sh
npm run createAllTables
```
```sh
npm run createAllTables
```
8. Delete the Seeder:
```sh
npm run deleteAllSeeders
```
```sh
npm run deleteAllSeeders
```
9. Delete the Migration:
```sh
npm run deleteAllTables
```
```sh
npm run deleteAllTables
```
70 changes: 70 additions & 0 deletions src/databases/models/tokens.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/* eslint-disable require-jsdoc */
import { Model, DataTypes, Sequelize } from "sequelize";

interface TokenAttributes {
id: number;
userId: number;
device: string;
accessToken: string;
createdAt: Date;
updatedAt: Date;
expiresAt: Date;
}

module.exports = (sequelize: Sequelize) => {
class Tokens extends Model<TokenAttributes> implements TokenAttributes {
declare id: number;
declare userId: number;
declare device: string;
declare accessToken: string;
declare createdAt: Date;
declare updatedAt: Date;
declare expiresAt: Date;
}

Tokens.init(
{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true
},
userId: {
type: new DataTypes.INTEGER(),
allowNull: false
},
device: {
type: new DataTypes.STRING(280),
allowNull: false
},
accessToken: {
type: new DataTypes.STRING(280),
allowNull: false
},
createdAt: {
field: "createdAt",
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW
},
updatedAt: {
field: "updatedAt",
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW
},
expiresAt: {
type: DataTypes.DATE,
allowNull: false
}
},
{
sequelize,
tableName: "tokens",
timestamps: true,
modelName: "Tokens"
}
);

return Tokens;
};
17 changes: 16 additions & 1 deletion src/middlewares/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,21 @@ const verifyUserCredentials = async (req: Request, res: Response, next: NextFunc

}

const isUserLoggedIn = (req: Request, res: Response, next: NextFunction) => {
const authHeader = req.headers["authorization"];
console.log(authHeader)
if (!authHeader) {
return res.status(httpStatus.UNAUTHORIZED).json({ status: httpStatus.UNAUTHORIZED, message: "No Token provided" });
}
(req as IRequest).token = authHeader.split(" ")[1];
next();
}


export { validation, isUserExist, isAccountVerified, verifyUserCredentials };
export {
validation,
isUserExist,
isAccountVerified,
verifyUserCredentials,
isUserLoggedIn,
};
144 changes: 104 additions & 40 deletions src/modules/auth/controller/authControllers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,51 +10,115 @@ import authRepositories from "../repository/authRepositories";
import { sendVerificationEmail } from "../../../services/sendEmail";

const registerUser = async (req: Request, res: Response): Promise<void> => {
try {
const register: UsersAttributes = await userRepositories.createUser(req.body);
const token: string = generateToken(register.id);
const session = { userId: register.id, device: req.headers["user-device"], token: token, otp: null };
await authRepositories.createSession(session);
await sendVerificationEmail(register.email, "Verification Email", `${process.env.SERVER_URL_PRO}/api/auth/verify-email/${token}`);
res.status(httpStatus.CREATED).json({ message: "Account created successfully. Please check email to verify account.", data: { user: register } })
} catch (error) {
res.status(httpStatus.INTERNAL_SERVER_ERROR).json({ status: httpStatus.INTERNAL_SERVER_ERROR, message: error.message });
}

}
try {
const register: UsersAttributes = await userRepositories.createUser(
req.body
);
const token: string = generateToken(register.id);
const session = {
userId: register.id,
device: req.headers["user-device"],
token: token,
otp: null,
};
await authRepositories.createSession(session);
await sendVerificationEmail(
register.email,
"Verification Email",
`${process.env.SERVER_URL_PRO}/api/auth/verify-email/${token}`
);
res.status(httpStatus.CREATED).json({
message:
"Account created successfully. Please check email to verify account.",
data: { user: register },
});
} catch (error) {
res.status(httpStatus.INTERNAL_SERVER_ERROR).json({
status: httpStatus.INTERNAL_SERVER_ERROR,
message: error.message,
});
}
};

const sendVerifyEmail = async (req: any, res: Response) => {
try {
await sendVerificationEmail(req.user.email, "Verification Email", `${process.env.SERVER_URL_PRO}/api/auth/verify-email/${req.session.token}`);
res.status(httpStatus.OK).json({ status: httpStatus.OK, message: "Verification email sent successfully." });
} catch (error) {
return res.status(httpStatus.INTERNAL_SERVER_ERROR).json({ status: httpStatus.INTERNAL_SERVER_ERROR, message: error.message });
}
}
try {
await sendVerificationEmail(
req.user.email,
"Verification Email",
`${process.env.SERVER_URL_PRO}/api/auth/verify-email/${req.session.token}`
);
res.status(httpStatus.OK).json({
status: httpStatus.OK,
message: "Verification email sent successfully.",
});
} catch (error) {
return res.status(httpStatus.INTERNAL_SERVER_ERROR).json({
status: httpStatus.INTERNAL_SERVER_ERROR,
message: error.message,
});
}
};

const verifyEmail = async (req: any, res: Response) => {
try {
await authRepositories.destroySession(req.user.id, req.session.token)
await authRepositories.UpdateUserByAttributes("isVerified", true, "id", req.user.id);
res.status(httpStatus.OK).json({ status: httpStatus.OK, message: "Account verified successfully, now login." });
} catch (error) {
return res.status(httpStatus.INTERNAL_SERVER_ERROR).json({ status: httpStatus.INTERNAL_SERVER_ERROR, message: error.message });
}
}

try {
await authRepositories.destroySession(req.user.id, req.session.token);
await authRepositories.UpdateUserByAttributes(
"isVerified",
true,
"id",
req.user.id
);
res.status(httpStatus.OK).json({
status: httpStatus.OK,
message: "Account verified successfully, now login.",
});
} catch (error) {
return res.status(httpStatus.INTERNAL_SERVER_ERROR).json({
status: httpStatus.INTERNAL_SERVER_ERROR,
message: error.message,
});
}
};

const loginUser = async (req: Request, res: Response) => {
try {
const userId = (req as IRequest).loginUserId;
const token = generateToken(userId);
const session = { userId, device: req.headers["user-device"], token: token, otp: null };
await userRepositories.createSession(session);
res.status(httpStatus.OK).json({ message: "Logged in successfully", data: { token } });
}
catch (err) {
return res.status(httpStatus.INTERNAL_SERVER_ERROR).json({ message: "Server error", data: err.message });
}
}
try {
const userId = (req as IRequest).loginUserId;
const token = generateToken(userId);
const session = {
userId,
device: req.headers["user-device"],
token: token,
otp: null,
};
await userRepositories.createSession(session);
res
.status(httpStatus.OK)
.json({ message: "Logged in successfully", data: { token } });
} catch (err) {
return res
.status(httpStatus.INTERNAL_SERVER_ERROR)
.json({ message: "Server error", data: err.message });
}
};

const logoutUser = async (req: Request, res: Response) => {
try {
await authRepositories.invalidateToken((req as IRequest).token);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You have to use destroySession function not invalidateToken function.

res.status(httpStatus.OK).json({ message: "Successfully logged out" });
} catch (err) {
return res
.status(httpStatus.INTERNAL_SERVER_ERROR)
.json({
status: httpStatus.INTERNAL_SERVER_ERROR,
message: "Server error",
});
}
};

export default { registerUser, sendVerifyEmail, verifyEmail, loginUser }
export default {
registerUser,
sendVerifyEmail,
verifyEmail,
loginUser,
logoutUser,
};
14 changes: 13 additions & 1 deletion src/modules/auth/repository/authRepositories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,16 @@ const destroySession = async (userId: number, token:string) =>{
return await Session.destroy({ where: {userId, token } });
}

export default { createUser, createSession, findUserByAttributes, destroySession, UpdateUserByAttributes, findSessionByUserId }
const invalidateToken = async (token: string) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove this invalidateToken function from a repository is not required.

return await Session.destroy({ where: { token } });
};

export default {
createUser,
createSession,
findUserByAttributes,
destroySession,
UpdateUserByAttributes,
findSessionByUserId,
invalidateToken,
};
Loading