save token in db when generated

This commit is contained in:
Guillaume Dorce 2022-08-26 15:00:24 +02:00
parent 3261158588
commit c2e4aacb28
7 changed files with 59 additions and 15 deletions

View File

@ -18,6 +18,7 @@
"@types/cors": "^2.8.12", "@types/cors": "^2.8.12",
"@types/express": "^4.17.13", "@types/express": "^4.17.13",
"@types/jsonwebtoken": "^8.5.8", "@types/jsonwebtoken": "^8.5.8",
"@types/ms": "^0.7.31",
"@types/node": "^18.7.4", "@types/node": "^18.7.4",
"nodemon": "^2.0.19", "nodemon": "^2.0.19",
"prisma": "^4.2.1", "prisma": "^4.2.1",
@ -32,6 +33,7 @@
"dotenv": "^16.0.1", "dotenv": "^16.0.1",
"express": "^4.18.1", "express": "^4.18.1",
"jsonwebtoken": "^8.5.1", "jsonwebtoken": "^8.5.1",
"ms": "^2.1.3",
"zod": "^3.18.0" "zod": "^3.18.0"
} }
} }

View File

@ -6,12 +6,14 @@ specifiers:
'@types/cors': ^2.8.12 '@types/cors': ^2.8.12
'@types/express': ^4.17.13 '@types/express': ^4.17.13
'@types/jsonwebtoken': ^8.5.8 '@types/jsonwebtoken': ^8.5.8
'@types/ms': ^0.7.31
'@types/node': ^18.7.4 '@types/node': ^18.7.4
bcrypt: ^5.0.1 bcrypt: ^5.0.1
cors: ^2.8.5 cors: ^2.8.5
dotenv: ^16.0.1 dotenv: ^16.0.1
express: ^4.18.1 express: ^4.18.1
jsonwebtoken: ^8.5.1 jsonwebtoken: ^8.5.1
ms: ^2.1.3
nodemon: ^2.0.19 nodemon: ^2.0.19
prisma: ^4.2.1 prisma: ^4.2.1
ts-node: ^10.9.1 ts-node: ^10.9.1
@ -26,6 +28,7 @@ dependencies:
dotenv: 16.0.1 dotenv: 16.0.1
express: 4.18.1 express: 4.18.1
jsonwebtoken: 8.5.1 jsonwebtoken: 8.5.1
ms: 2.1.3
zod: 3.18.0 zod: 3.18.0
devDependencies: devDependencies:
@ -33,6 +36,7 @@ devDependencies:
'@types/cors': 2.8.12 '@types/cors': 2.8.12
'@types/express': 4.17.13 '@types/express': 4.17.13
'@types/jsonwebtoken': 8.5.8 '@types/jsonwebtoken': 8.5.8
'@types/ms': 0.7.31
'@types/node': 18.7.4 '@types/node': 18.7.4
nodemon: 2.0.19 nodemon: 2.0.19
prisma: 4.2.1 prisma: 4.2.1
@ -171,6 +175,10 @@ packages:
resolution: {integrity: sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==} resolution: {integrity: sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==}
dev: true dev: true
/@types/ms/0.7.31:
resolution: {integrity: sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==}
dev: true
/@types/node/18.7.4: /@types/node/18.7.4:
resolution: {integrity: sha512-RzRcw8c0B8LzryWOR4Wj7YOTFXvdYKwvrb6xQQyuDfnlTxwYXGCV5RZ/TEbq5L5kn+w3rliHAUyRcG1RtbmTFg==} resolution: {integrity: sha512-RzRcw8c0B8LzryWOR4Wj7YOTFXvdYKwvrb6xQQyuDfnlTxwYXGCV5RZ/TEbq5L5kn+w3rliHAUyRcG1RtbmTFg==}
dev: true dev: true

View File

@ -18,6 +18,7 @@ model User {
posts Post[] posts Post[]
role Role @default(USER) role Role @default(USER)
likes Like[] likes Like[]
tokens Token[]
} }
enum Role { enum Role {
@ -44,3 +45,12 @@ model Like {
postId Int postId Int
post Post @relation(fields: [postId], references: [id], onDelete: Cascade) post Post @relation(fields: [postId], references: [id], onDelete: Cascade)
} }
model Token {
id Int @id @default(autoincrement())
token String @unique
userId Int
user User @relation(fields: [userId], references: [id])
createdAt DateTime @default(now())
expiresAt DateTime @default(now())
}

View File

@ -15,8 +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' });
} }
const token = genToken(user.id); const token = await genToken(user.id);
return res.status(200).send({ token, userId: user.id }); return res.status(200).send({ token: token.token, userId: user.id, expiresAt: token.expiresAt });
} catch (error) { } catch (error) {
return res.status(500).send(error); return res.status(500).send(error);
} }

View File

@ -6,7 +6,6 @@ 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 { verifyToken } from '@/controller/AuthController';
import { Token } from '@/models/TokenModel';
const posts = Router(); const posts = Router();
@ -21,8 +20,8 @@ const checkAuth = (req: Request, res: Response, next: NextFunction) => {
return res.status(401).send('No token provided'); return res.status(401).send('No token provided');
} }
return verifyToken(token) return verifyToken(token)
.then((decodedToken: Token) => { .then((decodedToken: number) => {
req.userId = decodedToken.id; req.userId = decodedToken;
next(); next();
}) })
.catch(() => { .catch(() => {

View File

@ -2,7 +2,10 @@ import * as jwt from 'jsonwebtoken';
import * as bcrypt from 'bcrypt'; import * as bcrypt from 'bcrypt';
import { promisify } from 'util'; import { promisify } from 'util';
import { config } from '..'; import { config } from '..';
import { Token } from '@/models/TokenModel'; import { PrismaClient } from '@prisma/client';
import ms from 'ms';
const prisma = new PrismaClient();
const saltRounds = 10; const saltRounds = 10;
const hashPromise = promisify(bcrypt.hash); const hashPromise = promisify(bcrypt.hash);
@ -15,13 +18,21 @@ const comparePassword = (password: string, hash: string) => {
return bcrypt.compare(password, hash); return bcrypt.compare(password, hash);
}; };
const genToken = (id: number) => { const genToken = (userId: number) => {
return jwt.sign({ id }, config.JWT_SECRET, { const newToken = jwt.sign({ id: userId }, config.JWT_SECRET, {
expiresIn: config.JWT_EXPIRES_IN, expiresIn: config.JWT_EXPIRES_IN,
}); });
return prisma.token.create({
data: {
userId,
token: newToken,
expiresAt: new Date(Date.now() + ms(config.JWT_EXPIRES_IN)), // 30 days
},
});
}; };
const verifyToken = (token: string): Promise<Token> => { const verifyToken = (token: string): Promise<number> => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
jwt.verify(token, config.JWT_SECRET, (err?, decoded?: jwt.JwtPayload | string) => { jwt.verify(token, config.JWT_SECRET, (err?, decoded?: jwt.JwtPayload | string) => {
if (err) { if (err) {
@ -29,7 +40,7 @@ const verifyToken = (token: string): Promise<Token> => {
} else if (decoded === undefined || typeof decoded === 'string' || decoded.id === undefined) { } else if (decoded === undefined || typeof decoded === 'string' || decoded.id === undefined) {
reject('Invalid token'); reject('Invalid token');
} else { } else {
const decodedToken: Token = { id: decoded.id }; const decodedToken: number = decoded.id;
resolve(decodedToken); resolve(decodedToken);
} }
}); });

View File

@ -1,5 +1,19 @@
type Token = { import { z } from 'zod';
interface Token {
id: number; id: number;
}; userId: number;
token: string;
createdAt: Date;
updatedAt: Date;
}
const Token: z.ZodType<Token> = z.object({
id: z.number(),
userId: z.number(),
token: z.string(),
createdAt: z.date(),
updatedAt: z.date(),
});
export { Token }; export { Token };