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

finishes 187354245 logout api #44

Closed
wants to merge 1 commit into from
Closed
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
44 changes: 27 additions & 17 deletions src/controllers/authController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import bcrypt from 'bcrypt';
import User, { UserAttributes } from '../database/models/user';
import { sendInternalErrorResponse, validateFields } from '../validations';
import logger from '../logs/config';
import { extractTokenMiddleware } from '../helpers/tokenExtractor';

const authenticateViaGoogle = (req: Request, res: Response, next: NextFunction) => {
passport.authenticate('google', (err: unknown, user: UserAttributes | null) => {
Expand All @@ -17,14 +18,7 @@ const authenticateViaGoogle = (req: Request, res: Response, next: NextFunction)
return;
}

const token = jwt.sign({ id: user.id }, process.env.SECRET_KEY as string, {
expiresIn: process.env.JWT_EXPIRATION as string,
});

res.status(200).json({
ok: true,
token: token,
});
generateAndSendToken(user.id, res, req); // Pass req here
})(req, res, next);
};

Expand Down Expand Up @@ -78,20 +72,36 @@ const login = async (req: Request, res: Response): Promise<void> => {
return;
}

// Authenticate user with jwt
const token = jwt.sign({ id: user.id }, process.env.SECRET_KEY as string, {
expiresIn: process.env.JWT_EXPIRATION as string,
});

res.status(200).json({
ok: true,
token: token,
});
generateAndSendToken(user.id, res, req);
} catch (err: any) {
const message = (err as Error).message;
logger.error(message);
sendInternalErrorResponse(res, err);
}
};

const generateAndSendToken = (userId: string, res: Response, req: Request) => {
extractTokenMiddleware(req, res, () => {
// Pass the req object here
const token = (res as any).token;
jwt.verify(token, process.env.JWT_SECRET as string, (err: any) => {
if (err) {
res.status(401).json({
ok: false,
message: 'Invalid token',
});
return;
}
const generatedToken = jwt.sign({ id: userId }, process.env.SECRET_KEY as string, {
expiresIn: process.env.JWT_EXPIRATION as string,
});

res.status(200).json({
ok: true,
token: generatedToken,
});
});
});
};

export { login, authenticateViaGoogle };
41 changes: 41 additions & 0 deletions src/controllers/logoutController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Request, Response } from 'express';
import jwt from 'jsonwebtoken';
import { sendInternalErrorResponse } from '../validations';
import logger from '../logs/config';
import BlacklistedToken from '../database/models/blackListedToken';
import { extractTokenMiddleware } from '../helpers/tokenExtractor'; // Import the middleware

const logout = async (req: Request, res: Response): Promise<void> => {
try {
extractTokenMiddleware(req, res, () => {
const token = (req as any).token;

jwt.verify(token, process.env.JWT_SECRET as string, (err: any) => {
if (err) {
res.status(401).json({
ok: false,
message: 'Invalid token',
});
return;
}

BlacklistedToken.create({ token })
.then(() => {
res.status(200).json({
ok: true,
message: 'Logged out successfully',
});
})
.catch(error => {
throw error;
});
});
});
} catch (err: any) {
const message = (err as Error).message;
logger.error(message);
sendInternalErrorResponse(res, err);
}
};

export { logout, extractTokenMiddleware };
28 changes: 28 additions & 0 deletions src/database/migrations/20240428210949-create-blacklisted-token.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
'use strict';
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.createTable('BlacklistedTokens', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER,
},
token: {
type: Sequelize.STRING,
allowNull: false,
},
createdAt: {
allowNull: false,
type: Sequelize.DATE,
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE,
},
});
},
down: async (queryInterface, Sequelize) => {
await queryInterface.dropTable('BlacklistedTokens');
},
};
33 changes: 33 additions & 0 deletions src/database/models/blackListedToken.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Model, DataTypes, Sequelize } from 'sequelize';
import sequelize from './index';

class BlacklistedToken extends Model {
public id!: number;
public token!: string;

// timestamps!
public readonly createdAt!: Date;
public readonly updatedAt!: Date;
}

BlacklistedToken.init(
{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
token: {
type: DataTypes.STRING,
allowNull: false,
},
},
{
sequelize: sequelize as Sequelize,
modelName: 'BlacklistedToken',
tableName: 'blacklisted_tokens',
timestamps: true,
}
);

export default BlacklistedToken;
54 changes: 51 additions & 3 deletions src/docs/auth.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,54 @@ paths:
500:
description: Internal server error

tags:
- name: Login
description: Login a user

/auth/logout:
post:
summary: Logout User
tags:
- Logout
description: Invalidates the authentication token of a logged-in user, effectively logging them out.
consumes:
- application/json
produces:
- application/json
parameters:
- in: header
name: Authorization
description: Bearer token obtained during login
required: true
type: string
responses:
'200':
description: User logged out successfully
schema:
type: object
properties:
success:
type: boolean
example: true
message:
type: string
example: User logged out successfully
'400':
description: Bad request, token not provided
schema:
type: object
properties:
success:
type: boolean
example: false
error:
type: string
example: Token not provided
'401':
description: Unauthorized, invalid or expired token
schema:
type: object
properties:
success:
type: boolean
example: false
error:
type: string
example: Invalid or expired token
16 changes: 16 additions & 0 deletions src/helpers/tokenExtractor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Request, Response, NextFunction } from 'express';

const extractTokenMiddleware = (req: Request, res: Response, next: NextFunction) => {
const token = req.headers.authorization?.split(' ')[1];
if (!token) {
res.status(400).json({
ok: false,
message: 'Token not provided',
});
return;
}
(req as any).token = token;
next();
};

export { extractTokenMiddleware };
6 changes: 6 additions & 0 deletions src/routes/authRoute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { Router } from 'express';
import passport from 'passport';
import { authenticateViaGoogle } from '../controllers/authController';
import { login } from '../controllers/authController';
import { logout } from '../controllers/logoutController';
import { signupUser } from '../controllers/userController';

const router = Router();
// redirect user to google for authentication
Expand All @@ -15,7 +17,11 @@ router.get(
// authenticated user and store user data
router.get('/google/callback', authenticateViaGoogle);

//signup a user
router.post('/signup', signupUser);

// Route to login a user
router.post('/login', login);
router.post('/logout', logout);

export default router;
Loading