reject posts route if token is invalid and send one on login

This commit is contained in:
Guillaume Dorce 2022-08-26 12:24:38 +02:00
parent ece8da877a
commit 3261158588
8 changed files with 82 additions and 12 deletions

View File

@ -5,7 +5,7 @@ generator client {
datasource db { datasource db {
provider = "mysql" provider = "mysql"
url = env("DATABASE_URL") url = env("DB_URL")
referentialIntegrity = "prisma" referentialIntegrity = "prisma"
} }

View File

@ -1,6 +1,6 @@
import { getUser } from '@/controller/UserController'; import { getUser } from '@/controller/UserController';
import UserLoginModel from '@/models/UserLoginModel'; import UserLoginModel from '@/models/UserLoginModel';
import { comparePassword } from '@/controller/AuthController'; import { comparePassword, genToken } from '@/controller/AuthController';
import { Request, Response } from 'express'; import { Request, Response } from 'express';
import { User } from '@prisma/client'; import { User } from '@prisma/client';
@ -15,7 +15,8 @@ const login = async (req: Request, res: Response) => {
if (!isValid) { if (!isValid) {
return res.status(401).send({ error: 'Invalid password' }); return res.status(401).send({ error: 'Invalid password' });
} }
return res.status(200).send(user); const token = genToken(user.id);
return res.status(200).send({ token, userId: user.id });
} catch (error) { } catch (error) {
return res.status(500).send(error); return res.status(500).send(error);
} }

View File

@ -1,13 +1,37 @@
import { Router } from 'express'; import { NextFunction, Request, Response, Router } from 'express';
import getPosts from './getPosts'; import getPosts from './getPosts';
import postPost from './newPost'; import postPost from './newPost';
import putPost from './editPost'; import putPost from './editPost';
import deletePost from './deletePost'; import deletePost from './deletePost';
import likePost from './likePost'; import likePost from './likePost';
import unlikePost from './unlikePost'; import unlikePost from './unlikePost';
import { verifyToken } from '@/controller/AuthController';
import { Token } from '@/models/TokenModel';
const posts = Router(); const posts = Router();
const getToken = (req: Request): string | undefined => {
const token: string | undefined = req.headers.authorization?.substring(7); // remove 'Bearer ' from token
return token;
};
const checkAuth = (req: Request, res: Response, next: NextFunction) => {
const token = getToken(req);
if (token === undefined) {
return res.status(401).send('No token provided');
}
return verifyToken(token)
.then((decodedToken: Token) => {
req.userId = decodedToken.id;
next();
})
.catch(() => {
return res.status(401).send('Invalid token');
});
};
posts.use(checkAuth);
posts.get('/', getPosts); posts.get('/', getPosts);
posts.post('/new', postPost); posts.post('/new', postPost);
posts.put('/edit/:id', putPost); posts.put('/edit/:id', putPost);

View File

@ -1,9 +1,8 @@
import * as jwt from 'jsonwebtoken'; import * as jwt from 'jsonwebtoken';
import * as bcrypt from 'bcrypt'; import * as bcrypt from 'bcrypt';
import * as dotenv from 'dotenv';
import { promisify } from 'util'; import { promisify } from 'util';
import { config } from '..';
dotenv.config(); import { Token } from '@/models/TokenModel';
const saltRounds = 10; const saltRounds = 10;
const hashPromise = promisify(bcrypt.hash); const hashPromise = promisify(bcrypt.hash);
@ -16,4 +15,25 @@ const comparePassword = (password: string, hash: string) => {
return bcrypt.compare(password, hash); return bcrypt.compare(password, hash);
}; };
export { hashPassword, comparePassword }; const genToken = (id: number) => {
return jwt.sign({ id }, config.JWT_SECRET, {
expiresIn: config.JWT_EXPIRES_IN,
});
};
const verifyToken = (token: string): Promise<Token> => {
return new Promise((resolve, reject) => {
jwt.verify(token, config.JWT_SECRET, (err?, decoded?: jwt.JwtPayload | string) => {
if (err) {
reject(err);
} else if (decoded === undefined || typeof decoded === 'string' || decoded.id === undefined) {
reject('Invalid token');
} else {
const decodedToken: Token = { id: decoded.id };
resolve(decodedToken);
}
});
});
};
export { hashPassword, comparePassword, genToken, verifyToken };

View File

@ -1,9 +1,15 @@
import express, { urlencoded, json } from 'express'; import express, { urlencoded, json } from 'express';
import cors from 'cors'; import cors from 'cors';
import api from '@/api'; import api from '@/api';
import { config } from 'dotenv'; import { config as envConfig } from 'dotenv';
config(); envConfig();
interface Config {
[key: string]: string;
}
let config: Config = {};
const checkEnvVars = () => { const checkEnvVars = () => {
const requiredEnvVars = ['PORT', 'DB_URL', 'JWT_SECRET', 'JWT_EXPIRES_IN']; const requiredEnvVars = ['PORT', 'DB_URL', 'JWT_SECRET', 'JWT_EXPIRES_IN'];
@ -12,6 +18,8 @@ const checkEnvVars = () => {
if (process.env[envVar] === undefined) { if (process.env[envVar] === undefined) {
error = true; error = true;
console.log(`${envVar} is undefined`); console.log(`${envVar} is undefined`);
} else {
config[envVar] = process.env[envVar] as string;
} }
}); });
if (error) { if (error) {
@ -20,6 +28,7 @@ const checkEnvVars = () => {
}; };
checkEnvVars(); checkEnvVars();
export { config };
const port = process.env.PORT || 5000; const port = process.env.PORT || 5000;

5
src/models/TokenModel.ts Normal file
View File

@ -0,0 +1,5 @@
type Token = {
id: number;
};
export { Token };

9
src/types/express/index.d.ts vendored Normal file
View File

@ -0,0 +1,9 @@
export {};
declare global {
namespace Express {
export interface Request {
userId?: number;
}
}
}

View File

@ -2,7 +2,6 @@
"compilerOptions": { "compilerOptions": {
"baseUrl": ".", "baseUrl": ".",
"target": "es2017", "target": "es2017",
"module": "CommonJS",
"lib": [ "lib": [
"esnext" "esnext"
], ],
@ -23,7 +22,10 @@
"@@/*": [ "@@/*": [
"./*" "./*"
] ]
} },
"typeRoots": [
"src/types"
],
}, },
"ts-node": { "ts-node": {
"require": [ "require": [