diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 09901be..6332c51 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -5,7 +5,7 @@ generator client { datasource db { provider = "mysql" - url = env("DATABASE_URL") + url = env("DB_URL") referentialIntegrity = "prisma" } diff --git a/src/api/auth/login.ts b/src/api/auth/login.ts index 295b622..f941112 100644 --- a/src/api/auth/login.ts +++ b/src/api/auth/login.ts @@ -1,6 +1,6 @@ import { getUser } from '@/controller/UserController'; import UserLoginModel from '@/models/UserLoginModel'; -import { comparePassword } from '@/controller/AuthController'; +import { comparePassword, genToken } from '@/controller/AuthController'; import { Request, Response } from 'express'; import { User } from '@prisma/client'; @@ -15,7 +15,8 @@ const login = async (req: Request, res: Response) => { if (!isValid) { 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) { return res.status(500).send(error); } diff --git a/src/api/posts/index.ts b/src/api/posts/index.ts index 1538bd2..01d17dc 100644 --- a/src/api/posts/index.ts +++ b/src/api/posts/index.ts @@ -1,13 +1,37 @@ -import { Router } from 'express'; +import { NextFunction, Request, Response, Router } from 'express'; import getPosts from './getPosts'; import postPost from './newPost'; import putPost from './editPost'; import deletePost from './deletePost'; import likePost from './likePost'; import unlikePost from './unlikePost'; +import { verifyToken } from '@/controller/AuthController'; +import { Token } from '@/models/TokenModel'; 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.post('/new', postPost); posts.put('/edit/:id', putPost); diff --git a/src/controller/AuthController.ts b/src/controller/AuthController.ts index 22a7ee8..536adf3 100644 --- a/src/controller/AuthController.ts +++ b/src/controller/AuthController.ts @@ -1,9 +1,8 @@ import * as jwt from 'jsonwebtoken'; import * as bcrypt from 'bcrypt'; -import * as dotenv from 'dotenv'; import { promisify } from 'util'; - -dotenv.config(); +import { config } from '..'; +import { Token } from '@/models/TokenModel'; const saltRounds = 10; const hashPromise = promisify(bcrypt.hash); @@ -16,4 +15,25 @@ const comparePassword = (password: string, hash: string) => { 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 => { + 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 }; diff --git a/src/index.ts b/src/index.ts index 6f0f211..bbe1aaf 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,9 +1,15 @@ import express, { urlencoded, json } from 'express'; import cors from 'cors'; 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 requiredEnvVars = ['PORT', 'DB_URL', 'JWT_SECRET', 'JWT_EXPIRES_IN']; @@ -12,6 +18,8 @@ const checkEnvVars = () => { if (process.env[envVar] === undefined) { error = true; console.log(`${envVar} is undefined`); + } else { + config[envVar] = process.env[envVar] as string; } }); if (error) { @@ -20,6 +28,7 @@ const checkEnvVars = () => { }; checkEnvVars(); +export { config }; const port = process.env.PORT || 5000; diff --git a/src/models/TokenModel.ts b/src/models/TokenModel.ts new file mode 100644 index 0000000..598f6d5 --- /dev/null +++ b/src/models/TokenModel.ts @@ -0,0 +1,5 @@ +type Token = { + id: number; +}; + +export { Token }; diff --git a/src/types/express/index.d.ts b/src/types/express/index.d.ts new file mode 100644 index 0000000..91b1e6f --- /dev/null +++ b/src/types/express/index.d.ts @@ -0,0 +1,9 @@ +export {}; + +declare global { + namespace Express { + export interface Request { + userId?: number; + } + } +} diff --git a/tsconfig.json b/tsconfig.json index 31f8ca8..4cfb852 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,7 +2,6 @@ "compilerOptions": { "baseUrl": ".", "target": "es2017", - "module": "CommonJS", "lib": [ "esnext" ], @@ -23,7 +22,10 @@ "@@/*": [ "./*" ] - } + }, + "typeRoots": [ + "src/types" + ], }, "ts-node": { "require": [