From 12dc3ccf42bb6e00e773d2a949a8b717322c2f5f Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Mon, 23 Sep 2024 23:34:08 +0530 Subject: [PATCH 01/48] chore(deps): install auth dependencies --- package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/package.json b/package.json index bb991aa6..2452e818 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "@testing-library/jest-dom": "^5.16.4", "@testing-library/react": "^13.1.1", "@testing-library/user-event": "^13.5.0", + "@types/csurf": "^1.11.5", "@types/jest": "^27.4.1", "@types/node": "^16.11.27", "@types/react": "^18.0.5", @@ -23,8 +24,10 @@ "axios": "^0.26.0", "buffer": "^6.0.3", "bullmq": "^5.12.15", + "cookie-parser": "^1.4.6", "cors": "^2.8.5", "cross-fetch": "^4.0.0", + "csurf": "^1.11.0", "dotenv": "^16.0.0", "express": "^4.17.2", "fortawesome": "^0.0.1-security", From 5a7ea2af8f96b46afcdc4d614f73ee787a0347e5 Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Mon, 23 Sep 2024 23:36:15 +0530 Subject: [PATCH 02/48] chore(deps): install jsonwebtoken @types/jsonwebtoken --- package.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.json b/package.json index 2452e818..a08fac87 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "@testing-library/user-event": "^13.5.0", "@types/csurf": "^1.11.5", "@types/jest": "^27.4.1", + "@types/jsonwebtoken": "^9.0.7", "@types/node": "^16.11.27", "@types/react": "^18.0.5", "@types/react-dom": "^18.0.1", @@ -35,6 +36,7 @@ "googleapis": "^144.0.0", "ioredis": "^5.4.1", "joi": "^17.6.0", + "jsonwebtoken": "^9.0.2", "loglevel": "^1.8.0", "loglevel-plugin-remote": "^0.6.8", "moment-timezone": "^0.5.45", From c22614a40f65b041c1c1c2f63a8ddf5e72c627b4 Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Mon, 23 Sep 2024 23:40:02 +0530 Subject: [PATCH 03/48] feat: verify jwt token --- server/src/middlewares/auth.ts | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 server/src/middlewares/auth.ts diff --git a/server/src/middlewares/auth.ts b/server/src/middlewares/auth.ts new file mode 100644 index 00000000..98143c08 --- /dev/null +++ b/server/src/middlewares/auth.ts @@ -0,0 +1,29 @@ +import { Request, Response } from "express"; +import { verify } from "jsonwebtoken"; + +declare module "express-serve-static-core" { + interface Request { + user?: any; + } +} + +export const requireSignIn = (req: Request, res: Response, next: any) => { + const token = req.cookies && req.cookies.token ? req.cookies.token : null; + + if (token === null) return res.sendStatus(401); + + const secret = process.env.JWT_SECRET; + if (!secret) { + return res.sendStatus(500); // Internal Server Error if secret is not defined + } + + verify(token, secret, (err: any, user: any) => { + console.log(err) + + if (err) return res.sendStatus(403) + + req.user = user; + + next() + }) +}; From e6c43361b6e19173f34842311e5742bef7498d23 Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Mon, 23 Sep 2024 23:42:37 +0530 Subject: [PATCH 04/48] chore(deps): install bcrypt @types/bcrypt --- package.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.json b/package.json index a08fac87..be5b1968 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "@testing-library/jest-dom": "^5.16.4", "@testing-library/react": "^13.1.1", "@testing-library/user-event": "^13.5.0", + "@types/bcrypt": "^5.0.2", "@types/csurf": "^1.11.5", "@types/jest": "^27.4.1", "@types/jsonwebtoken": "^9.0.7", @@ -23,6 +24,7 @@ "@types/uuid": "^8.3.4", "@wbr-project/wbr-interpret": "^0.9.3-marketa.1", "axios": "^0.26.0", + "bcrypt": "^5.1.1", "buffer": "^6.0.3", "bullmq": "^5.12.15", "cookie-parser": "^1.4.6", From 4186c9572e1dfdfd95c4a7029e269630003f6dd4 Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Mon, 23 Sep 2024 23:44:21 +0530 Subject: [PATCH 05/48] feat: hash password --- server/src/utils/auth.ts | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 server/src/utils/auth.ts diff --git a/server/src/utils/auth.ts b/server/src/utils/auth.ts new file mode 100644 index 00000000..d46fad29 --- /dev/null +++ b/server/src/utils/auth.ts @@ -0,0 +1,24 @@ +import bcrypt from "bcrypt"; + +const hashPassword = (password) => { + return new Promise((resolve, reject) => { + bcrypt.genSalt(12, (err, salt) => { + if (err) { + reject(err) + } + bcrypt.hash(password, salt, (err, hash) => { + if (err) { + reject(err) + } + resolve(hash) + }) + }) + }) +} + +// password from frontend and hash from database +const comparePassword = (password, hash) => { + return bcrypt.compare(password, hash) +} + +module.exports = { hashPassword, comparePassword } \ No newline at end of file From 4c045683ff46f14ef1c52924099ae5111af0d313 Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Mon, 23 Sep 2024 23:47:10 +0530 Subject: [PATCH 06/48] feat: use csrf protection --- server/src/server.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/server/src/server.ts b/server/src/server.ts index 3e553367..f228597b 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -4,10 +4,14 @@ import cors from 'cors'; import 'dotenv/config'; import { record, workflow, storage, auth, integration } from './routes'; import { BrowserPool } from "./browser-management/classes/BrowserPool"; -import logger from './logger' +import logger from './logger'; +import cookieParser from 'cookie-parser'; +import csrf from 'csurf'; import { SERVER_PORT } from "./constants/config"; import { Server } from "socket.io"; +const csrfProtection = csrf({ cookie: true }) + const app = express(); app.use(cors()); app.use(express.json()); @@ -25,6 +29,9 @@ export const io = new Server(server); */ export const browserPool = new BrowserPool(); + +app.use(csrfProtection) + app.use('/record', record); app.use('/workflow', workflow); app.use('/storage', storage); From 56a58d9c54fad1456055a35ec7a00e44e27d35bf Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Mon, 23 Sep 2024 23:47:35 +0530 Subject: [PATCH 07/48] feat: parse cookies --- server/src/server.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/src/server.ts b/server/src/server.ts index f228597b..6c82b154 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -29,7 +29,8 @@ export const io = new Server(server); */ export const browserPool = new BrowserPool(); - +// parse cookies - "cookie" is true in csrfProtection +app.use(cookieParser()) app.use(csrfProtection) app.use('/record', record); From e51ce53c686f7f428afae7a6f9210ddb08ad6d56 Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Mon, 23 Sep 2024 23:48:35 +0530 Subject: [PATCH 08/48] chore: code cleanup --- server/src/routes/auth.ts | 92 --------------------------------------- 1 file changed, 92 deletions(-) diff --git a/server/src/routes/auth.ts b/server/src/routes/auth.ts index 00f51aa2..e69de29b 100644 --- a/server/src/routes/auth.ts +++ b/server/src/routes/auth.ts @@ -1,92 +0,0 @@ -import { Router } from 'express';; -import { google, sheets_v4 } from "googleapis"; -import { OAuth2Client } from 'google-auth-library' - -export const router = Router() - -const oauth2Client = new OAuth2Client( - '_CLIENT_ID', - '_CLIENT_SECRET', - '_REDIRECT_URI' -); - -// initialize Google OAuth 2.0 flow -router.get('/auth/google', (req, res) => { - const url = oauth2Client.generateAuthUrl({ - access_type: 'offline', - scope: [ - 'https://www.googleapis.com/auth/userinfo.profile', - 'https://www.googleapis.com/auth/drive.file', - 'https://www.googleapis.com/auth/spreadsheets' - ] - }); - res.redirect(url); -}); - -// Callback route for Google OAuth 2.0 -router.get('/auth/google/callback', async (req, res) => { - const code = req.query.code; - if (typeof code !== 'string') { - res.status(400).send('Invalid authorization code'); - return; - } - try { - const { tokens } = await oauth2Client.getToken(code); - oauth2Client.setCredentials(tokens); - // TODO: Store tokens securely (e.g., in a database) - res.send('Authentication successful'); - } catch (error) { - console.error('Error during authentication:', error); - res.status(500).send('Authentication failed'); - } - }); - -router.get('/sheets', async (req, res) => { - try { - const drive = google.drive({ version: 'v3', auth: oauth2Client }); - const response = await drive.files.list({ - q: "mimeType='application/vnd.google-apps.spreadsheet'", - fields: 'files(id, name)' - }); - res.json(response.data.files); - } catch (error) { - console.error('Error listing sheets:', error); - res.status(500).send('Failed to list sheets'); - } -}); - -router.get('/sheets/:sheetId', async (req, res) => { - try { - const sheets = google.sheets({ version: 'v4', auth: oauth2Client }); - const response = await sheets.spreadsheets.values.get({ - spreadsheetId: req.params.sheetId, - range: 'Sheet1', // Adjust range as needed - }); - res.json(response.data.values); - } catch (error) { - console.error('Error reading sheet:', error); - res.status(500).send('Failed to read sheet'); - } -}); - -router.post('/sheets/:sheetId', async (req, res) => { - try { - const sheets = google.sheets({ version: 'v4', auth: oauth2Client }); - - const request: sheets_v4.Params$Resource$Spreadsheets$Values$Append = { - spreadsheetId: req.params.sheetId, - range: 'Sheet1', // Adjust range as needed - valueInputOption: 'USER_ENTERED', - requestBody: { - values: [req.body.values], // Expect an array of values in the request body - }, - }; - - const response = await sheets.spreadsheets.values.append(request); - - res.json(response.data); - } catch (error) { - console.error('Error writing to sheet:', error); - res.status(500).send('Failed to write to sheet'); - } - }); \ No newline at end of file From 8b8b23351095e28381f1b6deab86935e395b49c7 Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Mon, 23 Sep 2024 23:53:35 +0530 Subject: [PATCH 09/48] fix: missinge xports --- server/src/utils/auth.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/server/src/utils/auth.ts b/server/src/utils/auth.ts index d46fad29..d7eb5751 100644 --- a/server/src/utils/auth.ts +++ b/server/src/utils/auth.ts @@ -1,6 +1,6 @@ import bcrypt from "bcrypt"; -const hashPassword = (password) => { +export const hashPassword = (password) => { return new Promise((resolve, reject) => { bcrypt.genSalt(12, (err, salt) => { if (err) { @@ -17,8 +17,6 @@ const hashPassword = (password) => { } // password from frontend and hash from database -const comparePassword = (password, hash) => { +export const comparePassword = (password, hash) => { return bcrypt.compare(password, hash) -} - -module.exports = { hashPassword, comparePassword } \ No newline at end of file +} \ No newline at end of file From 89feed52d4c2772a4c7af922c6c3414a5dcd0082 Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Mon, 23 Sep 2024 23:54:19 +0530 Subject: [PATCH 10/48] feat: register controller --- server/src/routes/auth.ts | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/server/src/routes/auth.ts b/server/src/routes/auth.ts index e69de29b..34465bf9 100644 --- a/server/src/routes/auth.ts +++ b/server/src/routes/auth.ts @@ -0,0 +1,38 @@ +import { hashPassword, comparePassword } from '../utils/auth'; +import bcrypt from 'bcrypt'; +import jwt from 'jsonwebtoken'; +// Todo: DB + +const register = async (req, res) => { + try { + const { email, password } = req.body + + if (!email) return res.status(400).send('Email is required') + if (!password || password.length < 6) return res.status(400).send('Password is required and must be at least 6 characters') + + let userExist = await User.findOne({ email }).exec() + if (userExist) return res.status(400).send('User already exists') + + const hashedPassword = await hashPassword(password) + + // register user + const user = new User({ + email, + password: hashedPassword + }) + await user.save() + const token = jwt.sign({ + _id: user._id + }, process.env.JWT_SECRET as string, { + expiresIn: '3d' + }) + user.password = undefined + res.cookie('token', token, { + httpOnly: true + }) + res.json(user) + } catch (error) { + res.status(500).send(`Could not register user - ${error.message}`) + } +} + From ab6838721fa1ca2168a74aa874d361ac8978619f Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Mon, 23 Sep 2024 23:55:23 +0530 Subject: [PATCH 11/48] feat: login controller --- server/src/routes/auth.ts | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/server/src/routes/auth.ts b/server/src/routes/auth.ts index 34465bf9..5f707fb5 100644 --- a/server/src/routes/auth.ts +++ b/server/src/routes/auth.ts @@ -36,3 +36,29 @@ const register = async (req, res) => { } } +const login = async (req, res) => { + try { + const { email, password } = req.body; + if (!email || !password) return res.status(400).send('Email and password are required') + if (password.length < 6) return res.status(400).send('Password must be at least 6 characters') + + let user = await User.findOne({ email }).exec() + const match = await comparePassword(password, user.password) + if (!match) return res.status(400).send('Invalid email or password') + + // create signed jwt + const token = jwt.sign({ + _id: user._id + }, process.env.JWT_SECRET as string, { + expiresIn: '3d' + }) + // return user and token to client, exclude hashed password + user.password = undefined + res.cookie('token', token, { + httpOnly: true + }) + res.json(user) + } catch (error) { + res.status(400).send(`Could not login user - ${error.message}`) + } +} From 2758d0504009a502992d0139aefc531cdb423945 Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Mon, 23 Sep 2024 23:55:41 +0530 Subject: [PATCH 12/48] feat: logout controller --- server/src/routes/auth.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/server/src/routes/auth.ts b/server/src/routes/auth.ts index 5f707fb5..6c2dd58c 100644 --- a/server/src/routes/auth.ts +++ b/server/src/routes/auth.ts @@ -62,3 +62,12 @@ const login = async (req, res) => { res.status(400).send(`Could not login user - ${error.message}`) } } + +const logout = async (req, res) => { + try { + res.clearCookie('token') + return res.json({ message: 'Logout successful' }) + } catch (error) { + res.status(500).send(`Could not logout user - ${error.message}`) + } +} From 29f92cd0940c2036a5da76676575e895f13a4641 Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Mon, 23 Sep 2024 23:55:53 +0530 Subject: [PATCH 13/48] feat: get current user --- server/src/routes/auth.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/server/src/routes/auth.ts b/server/src/routes/auth.ts index 6c2dd58c..dabd4855 100644 --- a/server/src/routes/auth.ts +++ b/server/src/routes/auth.ts @@ -71,3 +71,12 @@ const logout = async (req, res) => { res.status(500).send(`Could not logout user - ${error.message}`) } } + +const currentUser = async (req, res) => { + try { + const user = await User.findById(req.user._id).select('-password').exec(); + return res.status(200).json({ ok: true }); + } catch (error) { + return res.status(500).send(`Could not fetch current user : ${error.message}.`); + } +}; \ No newline at end of file From ca175fdcae12105bcd51bced53b420b36cc1f714 Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Mon, 23 Sep 2024 23:57:12 +0530 Subject: [PATCH 14/48] feat: register route --- server/src/routes/auth.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/server/src/routes/auth.ts b/server/src/routes/auth.ts index dabd4855..bc2d9bbb 100644 --- a/server/src/routes/auth.ts +++ b/server/src/routes/auth.ts @@ -1,9 +1,11 @@ +import { Router } from 'express'; import { hashPassword, comparePassword } from '../utils/auth'; import bcrypt from 'bcrypt'; import jwt from 'jsonwebtoken'; // Todo: DB +export const router = Router(); -const register = async (req, res) => { +router.post('/register', async (req, res) => { try { const { email, password } = req.body @@ -34,7 +36,7 @@ const register = async (req, res) => { } catch (error) { res.status(500).send(`Could not register user - ${error.message}`) } -} +}) const login = async (req, res) => { try { From 72ce72304f849454fa82c44e4f7a5e32d530b624 Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Mon, 23 Sep 2024 23:57:43 +0530 Subject: [PATCH 15/48] feat: login route --- server/src/routes/auth.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/routes/auth.ts b/server/src/routes/auth.ts index bc2d9bbb..4a6c1114 100644 --- a/server/src/routes/auth.ts +++ b/server/src/routes/auth.ts @@ -38,7 +38,7 @@ router.post('/register', async (req, res) => { } }) -const login = async (req, res) => { +router.post('/login', async (req, res) => { try { const { email, password } = req.body; if (!email || !password) return res.status(400).send('Email and password are required') @@ -63,7 +63,7 @@ const login = async (req, res) => { } catch (error) { res.status(400).send(`Could not login user - ${error.message}`) } -} +}) const logout = async (req, res) => { try { From 74ed86ba4bffacab9b850440dca17fdbe5818690 Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Mon, 23 Sep 2024 23:58:14 +0530 Subject: [PATCH 16/48] feat: logout route --- server/src/routes/auth.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/routes/auth.ts b/server/src/routes/auth.ts index 4a6c1114..9a50cd3c 100644 --- a/server/src/routes/auth.ts +++ b/server/src/routes/auth.ts @@ -65,14 +65,14 @@ router.post('/login', async (req, res) => { } }) -const logout = async (req, res) => { +router.get('/logout', async (req, res) => { try { res.clearCookie('token') return res.json({ message: 'Logout successful' }) } catch (error) { res.status(500).send(`Could not logout user - ${error.message}`) } -} +}) const currentUser = async (req, res) => { try { From 404eb797b94418aaf8d487d096b8beac04967a24 Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 24 Sep 2024 00:01:32 +0530 Subject: [PATCH 17/48] feat: current user route --- server/src/routes/auth.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/routes/auth.ts b/server/src/routes/auth.ts index 9a50cd3c..d1e103d1 100644 --- a/server/src/routes/auth.ts +++ b/server/src/routes/auth.ts @@ -74,11 +74,11 @@ router.get('/logout', async (req, res) => { } }) -const currentUser = async (req, res) => { +router.get('/current-user', async (req, res) => { try { const user = await User.findById(req.user._id).select('-password').exec(); return res.status(200).json({ ok: true }); } catch (error) { return res.status(500).send(`Could not fetch current user : ${error.message}.`); } -}; \ No newline at end of file +}); \ No newline at end of file From c80f04c0a91c114e897d6713c96df925c83bba9b Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 24 Sep 2024 00:05:22 +0530 Subject: [PATCH 18/48] feat: get csrf token --- server/src/server.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/server/src/server.ts b/server/src/server.ts index 6c82b154..c7f8e88a 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -43,4 +43,8 @@ app.get('/', function (req, res) { return res.send('Maxun server started 🚀'); }); +app.get('/csrf-token', (req, res) => { + res.json({ csrfToken: req.csrfToken() }) + }) + server.listen(SERVER_PORT, () => logger.log('info', `Server listening on port ${SERVER_PORT}`)); From b2c1babcfcec5489ddd9e8bb32c5c3f2a27df827 Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 24 Sep 2024 12:07:32 +0530 Subject: [PATCH 19/48] feat: auth context --- src/context/auth.tsx | 98 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 src/context/auth.tsx diff --git a/src/context/auth.tsx b/src/context/auth.tsx new file mode 100644 index 00000000..8b17886a --- /dev/null +++ b/src/context/auth.tsx @@ -0,0 +1,98 @@ +import { useReducer, createContext, useEffect } from 'react'; +import axios from 'axios'; +import { useNavigate } from 'react-router-dom'; + +interface ProviderProps { + children: React.ReactNode; +} + +type InitialStateType = { + user: any; +}; + +const initialState = { + user: null, +}; + +const Context = createContext<{ + state: InitialStateType; + dispatch: any; +}>({ + state: initialState, + dispatch: () => null, +}); + +const reducer = (state: InitialStateType, action) => { + switch (action.type) { + case 'LOGIN': + return { + ...state, + user: action.payload, + }; + case 'LOGOUT': + return { + ...state, + user: null, + }; + default: + return state; + } +}; + +const Provider = ({ children }: ProviderProps) => { + const [state, dispatch] = useReducer(reducer, initialState); + + const navigate = useNavigate(); + + // get user info from local storage + useEffect(() => { + dispatch({ + type: 'LOGIN', + payload: JSON.parse(window.localStorage.getItem('user')), + }); + }, []); + + axios.interceptors.response.use( + function (response) { + // any status code that lies within the range of 2XX causes this function to trigger + return response; + }, + function (error) { + // any status codes that fall outside the range of 2XX cause this function to trigger + let res = error.response; + if (res.status === 401 && res.config && !res.config.__isRetryRequest) { + return new Promise((resolve, reject) => { + axios + .get('/api/logout') + .then((data) => { + console.log('/401 error > logout'); + dispatch({ type: 'LOGOUT' }); + window.localStorage.removeItem('user'); + navigate('/login'); // Replace router.push with navigate + }) + .catch((err) => { + console.log('AXIOS INTERCEPTORS ERROR:', err); + reject(error); + }); + }); + } + return Promise.reject(error); + } + ); + + // csrf - include tokens in the axios header every time a request is made + useEffect(() => { + const getCsrfToken = async () => { + const { data } = await axios.get('/api/csrf-token'); + console.log('CSRFFFFF =>>>>', data); + axios.defaults.headers['X-CSRF-TOKEN'] = data.getCsrfToken; + }; + getCsrfToken(); + }, []); + + return ( + {children} + ); +}; + +export { Context, Provider }; From 2131b5307fddff15fc796a93d43ea2d5874b8c11 Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 24 Sep 2024 12:08:19 +0530 Subject: [PATCH 20/48] feat: create ActionType context --- src/context/auth.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/context/auth.tsx b/src/context/auth.tsx index 8b17886a..83c75b57 100644 --- a/src/context/auth.tsx +++ b/src/context/auth.tsx @@ -6,6 +6,11 @@ interface ProviderProps { children: React.ReactNode; } +interface ActionType { + type: 'LOGIN' | 'LOGOUT'; + payload?: any; + } + type InitialStateType = { user: any; }; @@ -22,7 +27,7 @@ const Context = createContext<{ dispatch: () => null, }); -const reducer = (state: InitialStateType, action) => { +const reducer = (state: InitialStateType, action: ActionType) => { switch (action.type) { case 'LOGIN': return { From 1dd1b6fbdb82f0aca5160c135da54e35cc76627c Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 24 Sep 2024 12:09:01 +0530 Subject: [PATCH 21/48] fix: pass null if not found --- src/context/auth.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/context/auth.tsx b/src/context/auth.tsx index 83c75b57..98fa4b49 100644 --- a/src/context/auth.tsx +++ b/src/context/auth.tsx @@ -53,7 +53,7 @@ const Provider = ({ children }: ProviderProps) => { useEffect(() => { dispatch({ type: 'LOGIN', - payload: JSON.parse(window.localStorage.getItem('user')), + payload: JSON.parse(window.localStorage.getItem('user') || 'null'), }); }, []); From 044031260c9283a81d82b7592fa80174016f35d1 Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 24 Sep 2024 12:09:50 +0530 Subject: [PATCH 22/48] feat: extend HeadersDefaults type to include X-CSRF-TOKEN property --- src/context/auth.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/context/auth.tsx b/src/context/auth.tsx index 98fa4b49..c66652ac 100644 --- a/src/context/auth.tsx +++ b/src/context/auth.tsx @@ -90,7 +90,7 @@ const Provider = ({ children }: ProviderProps) => { const getCsrfToken = async () => { const { data } = await axios.get('/api/csrf-token'); console.log('CSRFFFFF =>>>>', data); - axios.defaults.headers['X-CSRF-TOKEN'] = data.getCsrfToken; + (axios.defaults.headers as any)['X-CSRF-TOKEN'] = data.getCsrfToken; }; getCsrfToken(); }, []); From 4ad5b46fd3a387db81860de20c729fa82f47080f Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 24 Sep 2024 12:10:04 +0530 Subject: [PATCH 23/48] chore: lint --- src/context/auth.tsx | 142 +++++++++++++++++++++---------------------- 1 file changed, 71 insertions(+), 71 deletions(-) diff --git a/src/context/auth.tsx b/src/context/auth.tsx index c66652ac..7d0d6c05 100644 --- a/src/context/auth.tsx +++ b/src/context/auth.tsx @@ -3,101 +3,101 @@ import axios from 'axios'; import { useNavigate } from 'react-router-dom'; interface ProviderProps { - children: React.ReactNode; + children: React.ReactNode; } interface ActionType { type: 'LOGIN' | 'LOGOUT'; payload?: any; - } +} type InitialStateType = { - user: any; + user: any; }; const initialState = { - user: null, + user: null, }; const Context = createContext<{ - state: InitialStateType; - dispatch: any; + state: InitialStateType; + dispatch: any; }>({ - state: initialState, - dispatch: () => null, + state: initialState, + dispatch: () => null, }); const reducer = (state: InitialStateType, action: ActionType) => { - switch (action.type) { - case 'LOGIN': - return { - ...state, - user: action.payload, - }; - case 'LOGOUT': - return { - ...state, - user: null, - }; - default: - return state; - } + switch (action.type) { + case 'LOGIN': + return { + ...state, + user: action.payload, + }; + case 'LOGOUT': + return { + ...state, + user: null, + }; + default: + return state; + } }; const Provider = ({ children }: ProviderProps) => { - const [state, dispatch] = useReducer(reducer, initialState); + const [state, dispatch] = useReducer(reducer, initialState); - const navigate = useNavigate(); + const navigate = useNavigate(); - // get user info from local storage - useEffect(() => { - dispatch({ - type: 'LOGIN', - payload: JSON.parse(window.localStorage.getItem('user') || 'null'), - }); - }, []); - - axios.interceptors.response.use( - function (response) { - // any status code that lies within the range of 2XX causes this function to trigger - return response; - }, - function (error) { - // any status codes that fall outside the range of 2XX cause this function to trigger - let res = error.response; - if (res.status === 401 && res.config && !res.config.__isRetryRequest) { - return new Promise((resolve, reject) => { - axios - .get('/api/logout') - .then((data) => { - console.log('/401 error > logout'); - dispatch({ type: 'LOGOUT' }); - window.localStorage.removeItem('user'); - navigate('/login'); // Replace router.push with navigate - }) - .catch((err) => { - console.log('AXIOS INTERCEPTORS ERROR:', err); - reject(error); - }); + // get user info from local storage + useEffect(() => { + dispatch({ + type: 'LOGIN', + payload: JSON.parse(window.localStorage.getItem('user') || 'null'), }); - } - return Promise.reject(error); - } - ); - - // csrf - include tokens in the axios header every time a request is made - useEffect(() => { - const getCsrfToken = async () => { - const { data } = await axios.get('/api/csrf-token'); - console.log('CSRFFFFF =>>>>', data); - (axios.defaults.headers as any)['X-CSRF-TOKEN'] = data.getCsrfToken; - }; - getCsrfToken(); - }, []); + }, []); - return ( - {children} - ); + axios.interceptors.response.use( + function (response) { + // any status code that lies within the range of 2XX causes this function to trigger + return response; + }, + function (error) { + // any status codes that fall outside the range of 2XX cause this function to trigger + let res = error.response; + if (res.status === 401 && res.config && !res.config.__isRetryRequest) { + return new Promise((resolve, reject) => { + axios + .get('/api/logout') + .then((data) => { + console.log('/401 error > logout'); + dispatch({ type: 'LOGOUT' }); + window.localStorage.removeItem('user'); + navigate('/login'); // Replace router.push with navigate + }) + .catch((err) => { + console.log('AXIOS INTERCEPTORS ERROR:', err); + reject(error); + }); + }); + } + return Promise.reject(error); + } + ); + + // csrf - include tokens in the axios header every time a request is made + useEffect(() => { + const getCsrfToken = async () => { + const { data } = await axios.get('/api/csrf-token'); + console.log('CSRFFFFF =>>>>', data); + (axios.defaults.headers as any)['X-CSRF-TOKEN'] = data.getCsrfToken; + }; + getCsrfToken(); + }, []); + + return ( + {children} + ); }; export { Context, Provider }; From 6a50edff22b789f48d8626bc2884f94203fa1d19 Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 24 Sep 2024 12:31:57 +0530 Subject: [PATCH 24/48] fix: login & csrf api routes --- src/context/auth.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/context/auth.tsx b/src/context/auth.tsx index 7d0d6c05..1d410f0c 100644 --- a/src/context/auth.tsx +++ b/src/context/auth.tsx @@ -68,7 +68,7 @@ const Provider = ({ children }: ProviderProps) => { if (res.status === 401 && res.config && !res.config.__isRetryRequest) { return new Promise((resolve, reject) => { axios - .get('/api/logout') + .get('http://localhost:8080/auth/logout') .then((data) => { console.log('/401 error > logout'); dispatch({ type: 'LOGOUT' }); @@ -88,7 +88,7 @@ const Provider = ({ children }: ProviderProps) => { // csrf - include tokens in the axios header every time a request is made useEffect(() => { const getCsrfToken = async () => { - const { data } = await axios.get('/api/csrf-token'); + const { data } = await axios.get('http://localhost:8080/csrf-token'); console.log('CSRFFFFF =>>>>', data); (axios.defaults.headers as any)['X-CSRF-TOKEN'] = data.getCsrfToken; }; From d291f43c6199ed27311ab689ff8687f5652847f7 Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 24 Sep 2024 17:17:04 +0530 Subject: [PATCH 25/48] chore(deps): install pg sequelize sequelize-typescript --- package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/package.json b/package.json index be5b1968..b434c476 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "loglevel-plugin-remote": "^0.6.8", "moment-timezone": "^0.5.45", "node-cron": "^3.0.3", + "pg": "^8.13.0", "playwright": "^1.20.1", "playwright-extra": "^4.3.6", "prismjs": "^1.28.0", @@ -54,6 +55,8 @@ "react-scripts": "5.0.1", "react-simple-code-editor": "^0.11.2", "react-transition-group": "^4.4.2", + "sequelize": "^6.37.3", + "sequelize-typescript": "^2.1.6", "socket.io": "^4.4.1", "socket.io-client": "^4.4.1", "styled-components": "^5.3.3", From 31be279168ead8ac26b1150fa4ad749b5cd83902 Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 24 Sep 2024 17:19:49 +0530 Subject: [PATCH 26/48] feat: postgres config --- server/src/db/config.ts | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 server/src/db/config.ts diff --git a/server/src/db/config.ts b/server/src/db/config.ts new file mode 100644 index 00000000..34e6048e --- /dev/null +++ b/server/src/db/config.ts @@ -0,0 +1,26 @@ +import { Sequelize } from 'sequelize'; +import dotenv from 'dotenv'; + +dotenv.config(); + +const sequelize = new Sequelize( + process.env.DB_NAME as string, + process.env.DB_USER as string, + process.env.DB_PASSWORD as string, + { + host: process.env.DB_HOST, + dialect: 'postgres', + logging: false, + } +); + +export const connectDB = async () => { + try { + await sequelize.authenticate(); + console.log('Database connected successfully'); + } catch (error) { + console.error('Unable to connect to the database:', error); + } +}; + +export default sequelize; \ No newline at end of file From df998b4304aa3b4ac7948ba82a2a29f343912fc6 Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 24 Sep 2024 17:23:53 +0530 Subject: [PATCH 27/48] feat: create User model --- server/src/models/User.ts | 58 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 server/src/models/User.ts diff --git a/server/src/models/User.ts b/server/src/models/User.ts new file mode 100644 index 00000000..82909785 --- /dev/null +++ b/server/src/models/User.ts @@ -0,0 +1,58 @@ +import { DataTypes, Model, Optional } from 'sequelize'; +import bcrypt from 'bcrypt'; +import sequelize from '../db/config'; + +interface UserAttributes { + id: number; + email: string; + password: string; +} + +// Optional fields for creating a new user +interface UserCreationAttributes extends Optional {} + +class User extends Model implements UserAttributes { + public id!: number; + public email!: string; + public password!: string; + + public async isValidPassword(password: string): Promise { + return bcrypt.compare(password, this.password); + } +} + +User.init( + { + id: { + type: DataTypes.INTEGER.UNSIGNED, + autoIncrement: true, + primaryKey: true, + }, + email: { + type: DataTypes.STRING, + allowNull: false, + unique: true, + validate: { + isEmail: true, + }, + }, + password: { + type: DataTypes.STRING, + allowNull: false, + }, + }, + { + sequelize, + tableName: 'user', + hooks: { + beforeCreate: async (user: User) => { + if (user.password) { + const salt = await bcrypt.genSalt(10); + user.password = await bcrypt.hash(user.password, salt); + } + }, + }, + } +); + +export default User; From 87fa644ecffe63df4dbc32c10c43bafdbd6624f4 Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 24 Sep 2024 17:24:11 +0530 Subject: [PATCH 28/48] chore: lint --- server/src/models/User.ts | 80 +++++++++++++++++++-------------------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/server/src/models/User.ts b/server/src/models/User.ts index 82909785..8e944e2c 100644 --- a/server/src/models/User.ts +++ b/server/src/models/User.ts @@ -3,56 +3,56 @@ import bcrypt from 'bcrypt'; import sequelize from '../db/config'; interface UserAttributes { - id: number; - email: string; - password: string; + id: number; + email: string; + password: string; } // Optional fields for creating a new user -interface UserCreationAttributes extends Optional {} +interface UserCreationAttributes extends Optional { } class User extends Model implements UserAttributes { - public id!: number; - public email!: string; - public password!: string; + public id!: number; + public email!: string; + public password!: string; - public async isValidPassword(password: string): Promise { - return bcrypt.compare(password, this.password); - } + public async isValidPassword(password: string): Promise { + return bcrypt.compare(password, this.password); + } } User.init( - { - id: { - type: DataTypes.INTEGER.UNSIGNED, - autoIncrement: true, - primaryKey: true, + { + id: { + type: DataTypes.INTEGER.UNSIGNED, + autoIncrement: true, + primaryKey: true, + }, + email: { + type: DataTypes.STRING, + allowNull: false, + unique: true, + validate: { + isEmail: true, + }, + }, + password: { + type: DataTypes.STRING, + allowNull: false, + }, }, - email: { - type: DataTypes.STRING, - allowNull: false, - unique: true, - validate: { - isEmail: true, - }, - }, - password: { - type: DataTypes.STRING, - allowNull: false, - }, - }, - { - sequelize, - tableName: 'user', - hooks: { - beforeCreate: async (user: User) => { - if (user.password) { - const salt = await bcrypt.genSalt(10); - user.password = await bcrypt.hash(user.password, salt); - } - }, - }, - } + { + sequelize, + tableName: 'user', + hooks: { + beforeCreate: async (user: User) => { + if (user.password) { + const salt = await bcrypt.genSalt(10); + user.password = await bcrypt.hash(user.password, salt); + } + }, + }, + } ); export default User; From 24881daa6e4b62510fb6d69ae4d4c35739127a27 Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 24 Sep 2024 17:24:24 +0530 Subject: [PATCH 29/48] chore: lint --- server/src/db/config.ts | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/server/src/db/config.ts b/server/src/db/config.ts index 34e6048e..78a785d2 100644 --- a/server/src/db/config.ts +++ b/server/src/db/config.ts @@ -4,23 +4,23 @@ import dotenv from 'dotenv'; dotenv.config(); const sequelize = new Sequelize( - process.env.DB_NAME as string, - process.env.DB_USER as string, - process.env.DB_PASSWORD as string, - { - host: process.env.DB_HOST, - dialect: 'postgres', - logging: false, - } + process.env.DB_NAME as string, + process.env.DB_USER as string, + process.env.DB_PASSWORD as string, + { + host: process.env.DB_HOST, + dialect: 'postgres', + logging: false, + } ); export const connectDB = async () => { - try { - await sequelize.authenticate(); - console.log('Database connected successfully'); - } catch (error) { - console.error('Unable to connect to the database:', error); - } + try { + await sequelize.authenticate(); + console.log('Database connected successfully'); + } catch (error) { + console.error('Unable to connect to the database:', error); + } }; export default sequelize; \ No newline at end of file From 21a1ea3a5c204e3cc65078639d59bf6e6b115166 Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 24 Sep 2024 17:29:48 +0530 Subject: [PATCH 30/48] feat: use User for register --- server/src/routes/auth.ts | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/server/src/routes/auth.ts b/server/src/routes/auth.ts index d1e103d1..c5510a36 100644 --- a/server/src/routes/auth.ts +++ b/server/src/routes/auth.ts @@ -1,5 +1,6 @@ import { Router } from 'express'; import { hashPassword, comparePassword } from '../utils/auth'; +import User from '../models/User'; import bcrypt from 'bcrypt'; import jwt from 'jsonwebtoken'; // Todo: DB @@ -12,23 +13,15 @@ router.post('/register', async (req, res) => { if (!email) return res.status(400).send('Email is required') if (!password || password.length < 6) return res.status(400).send('Password is required and must be at least 6 characters') - let userExist = await User.findOne({ email }).exec() + let userExist = await User.findOne({ where: { email } }); if (userExist) return res.status(400).send('User already exists') const hashedPassword = await hashPassword(password) - // register user - const user = new User({ - email, - password: hashedPassword - }) - await user.save() - const token = jwt.sign({ - _id: user._id - }, process.env.JWT_SECRET as string, { - expiresIn: '3d' - }) - user.password = undefined + const user = await User.create({ email, password }); + + const token = jwt.sign({ id: user.id }, process.env.JWT_SECRET as string, { expiresIn: '1h' }); + // user.password = undefined res.cookie('token', token, { httpOnly: true }) From f26f8300d3bf558b0846d67a8f51be373fc3681d Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 24 Sep 2024 17:31:56 +0530 Subject: [PATCH 31/48] feat: use User for login --- server/src/routes/auth.ts | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/server/src/routes/auth.ts b/server/src/routes/auth.ts index c5510a36..a221a07c 100644 --- a/server/src/routes/auth.ts +++ b/server/src/routes/auth.ts @@ -37,18 +37,14 @@ router.post('/login', async (req, res) => { if (!email || !password) return res.status(400).send('Email and password are required') if (password.length < 6) return res.status(400).send('Password must be at least 6 characters') - let user = await User.findOne({ email }).exec() - const match = await comparePassword(password, user.password) + let user = await User.findOne({ where: { email } }); + const match = await user?.isValidPassword(password); if (!match) return res.status(400).send('Invalid email or password') - // create signed jwt - const token = jwt.sign({ - _id: user._id - }, process.env.JWT_SECRET as string, { - expiresIn: '3d' - }) + const token = jwt.sign({ id: user?.id }, process.env.JWT_SECRET as string, { expiresIn: '1h' }); + // return user and token to client, exclude hashed password - user.password = undefined + // user.password = undefined res.cookie('token', token, { httpOnly: true }) From 7c36bd7245edc01c5c882767656a2d9dae6c6d5d Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 24 Sep 2024 17:33:51 +0530 Subject: [PATCH 32/48] feat: use User for current user --- server/src/routes/auth.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/server/src/routes/auth.ts b/server/src/routes/auth.ts index a221a07c..4c578db8 100644 --- a/server/src/routes/auth.ts +++ b/server/src/routes/auth.ts @@ -65,7 +65,9 @@ router.get('/logout', async (req, res) => { router.get('/current-user', async (req, res) => { try { - const user = await User.findById(req.user._id).select('-password').exec(); + const user = await User.findByPk(req.user.id, { + attributes: { exclude: ['password'] }, + }); return res.status(200).json({ ok: true }); } catch (error) { return res.status(500).send(`Could not fetch current user : ${error.message}.`); From d82171f7e3799de60f613339252729e81438fc9c Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 24 Sep 2024 17:39:50 +0530 Subject: [PATCH 33/48] fix: extend request to include user --- server/src/routes/auth.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/server/src/routes/auth.ts b/server/src/routes/auth.ts index 4c578db8..b743543e 100644 --- a/server/src/routes/auth.ts +++ b/server/src/routes/auth.ts @@ -1,4 +1,4 @@ -import { Router } from 'express'; +import { Router, Request, Response } from 'express'; import { hashPassword, comparePassword } from '../utils/auth'; import User from '../models/User'; import bcrypt from 'bcrypt'; @@ -6,6 +6,10 @@ import jwt from 'jsonwebtoken'; // Todo: DB export const router = Router(); +interface AuthenticatedRequest extends Request { + user?: { id: string }; +} + router.post('/register', async (req, res) => { try { const { email, password } = req.body @@ -63,8 +67,11 @@ router.get('/logout', async (req, res) => { } }) -router.get('/current-user', async (req, res) => { +router.get('/current-user', async (req: AuthenticatedRequest, res) => { try { + if (!req.user) { + return res.status(401).send('Unauthorized'); + } const user = await User.findByPk(req.user.id, { attributes: { exclude: ['password'] }, }); From f0e685f3397244bc383c5aaf26fd2386b165ac19 Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 24 Sep 2024 17:40:20 +0530 Subject: [PATCH 34/48] chore: lint --- server/src/routes/auth.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/routes/auth.ts b/server/src/routes/auth.ts index b743543e..f88d4f5d 100644 --- a/server/src/routes/auth.ts +++ b/server/src/routes/auth.ts @@ -73,8 +73,8 @@ router.get('/current-user', async (req: AuthenticatedRequest, res) => { return res.status(401).send('Unauthorized'); } const user = await User.findByPk(req.user.id, { - attributes: { exclude: ['password'] }, - }); + attributes: { exclude: ['password'] }, + }); return res.status(200).json({ ok: true }); } catch (error) { return res.status(500).send(`Could not fetch current user : ${error.message}.`); From f5fa79faa9f0663642c190a41c0d2ca7ec4c3965 Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 24 Sep 2024 17:40:55 +0530 Subject: [PATCH 35/48] chore: remove todo & unused dependencies --- server/src/routes/auth.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/server/src/routes/auth.ts b/server/src/routes/auth.ts index f88d4f5d..3a95775d 100644 --- a/server/src/routes/auth.ts +++ b/server/src/routes/auth.ts @@ -1,9 +1,7 @@ import { Router, Request, Response } from 'express'; import { hashPassword, comparePassword } from '../utils/auth'; import User from '../models/User'; -import bcrypt from 'bcrypt'; import jwt from 'jsonwebtoken'; -// Todo: DB export const router = Router(); interface AuthenticatedRequest extends Request { From 26b08e510e6361b5711e0a68144201be34e31bd3 Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 24 Sep 2024 17:43:28 +0530 Subject: [PATCH 36/48] feat: use hashPassword function --- server/src/models/User.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/models/User.ts b/server/src/models/User.ts index 8e944e2c..878c5656 100644 --- a/server/src/models/User.ts +++ b/server/src/models/User.ts @@ -1,6 +1,7 @@ import { DataTypes, Model, Optional } from 'sequelize'; import bcrypt from 'bcrypt'; import sequelize from '../db/config'; +import { hashPassword } from '../utils/auth'; interface UserAttributes { id: number; @@ -47,8 +48,7 @@ User.init( hooks: { beforeCreate: async (user: User) => { if (user.password) { - const salt = await bcrypt.genSalt(10); - user.password = await bcrypt.hash(user.password, salt); + user.password = await hashPassword(user.password) as string; } }, }, From 37dab830bfc4a6afe1bd705462ac8bc5ef5fc7d5 Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 24 Sep 2024 17:45:05 +0530 Subject: [PATCH 37/48] fix: remove hashedPassword var --- server/src/routes/auth.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/server/src/routes/auth.ts b/server/src/routes/auth.ts index 3a95775d..8ebb92a5 100644 --- a/server/src/routes/auth.ts +++ b/server/src/routes/auth.ts @@ -18,8 +18,6 @@ router.post('/register', async (req, res) => { let userExist = await User.findOne({ where: { email } }); if (userExist) return res.status(400).send('User already exists') - const hashedPassword = await hashPassword(password) - const user = await User.create({ email, password }); const token = jwt.sign({ id: user.id }, process.env.JWT_SECRET as string, { expiresIn: '1h' }); From fd3d65e632cf5145ee0327129850e74ead246ed2 Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 24 Sep 2024 17:46:35 +0530 Subject: [PATCH 38/48] feat: exclude hashed password when return user --- server/src/routes/auth.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/server/src/routes/auth.ts b/server/src/routes/auth.ts index 8ebb92a5..f645b92e 100644 --- a/server/src/routes/auth.ts +++ b/server/src/routes/auth.ts @@ -21,7 +21,7 @@ router.post('/register', async (req, res) => { const user = await User.create({ email, password }); const token = jwt.sign({ id: user.id }, process.env.JWT_SECRET as string, { expiresIn: '1h' }); - // user.password = undefined + user.password = undefined as unknown as string res.cookie('token', token, { httpOnly: true }) @@ -44,7 +44,9 @@ router.post('/login', async (req, res) => { const token = jwt.sign({ id: user?.id }, process.env.JWT_SECRET as string, { expiresIn: '1h' }); // return user and token to client, exclude hashed password - // user.password = undefined + if (user) { + user.password = undefined as unknown as string; + } res.cookie('token', token, { httpOnly: true }) From 33a3d71c93e234504133174d69ee05c61b778d84 Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 24 Sep 2024 17:47:30 +0530 Subject: [PATCH 39/48] feat: use comparePassword from utils --- server/src/models/User.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/models/User.ts b/server/src/models/User.ts index 878c5656..7227accb 100644 --- a/server/src/models/User.ts +++ b/server/src/models/User.ts @@ -1,7 +1,7 @@ import { DataTypes, Model, Optional } from 'sequelize'; import bcrypt from 'bcrypt'; import sequelize from '../db/config'; -import { hashPassword } from '../utils/auth'; +import { hashPassword, comparePassword } from '../utils/auth'; interface UserAttributes { id: number; @@ -18,7 +18,7 @@ class User extends Model implements User public password!: string; public async isValidPassword(password: string): Promise { - return bcrypt.compare(password, this.password); + return comparePassword(password, this.password); } } From 1f2f505ac5ca1a0044f848679cbc3ce2458a267b Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 24 Sep 2024 17:47:44 +0530 Subject: [PATCH 40/48] chore: remove unused import --- server/src/models/User.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/server/src/models/User.ts b/server/src/models/User.ts index 7227accb..67b944b9 100644 --- a/server/src/models/User.ts +++ b/server/src/models/User.ts @@ -1,5 +1,4 @@ import { DataTypes, Model, Optional } from 'sequelize'; -import bcrypt from 'bcrypt'; import sequelize from '../db/config'; import { hashPassword, comparePassword } from '../utils/auth'; From 1bcc8d372b811540a23e2ee63e46137ee8d81de7 Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 24 Sep 2024 17:48:01 +0530 Subject: [PATCH 41/48] chore: remove unused import --- server/src/routes/auth.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/server/src/routes/auth.ts b/server/src/routes/auth.ts index f645b92e..4740853a 100644 --- a/server/src/routes/auth.ts +++ b/server/src/routes/auth.ts @@ -1,5 +1,4 @@ import { Router, Request, Response } from 'express'; -import { hashPassword, comparePassword } from '../utils/auth'; import User from '../models/User'; import jwt from 'jsonwebtoken'; export const router = Router(); From f67101444405cf75fcee4bf77765f2514184bc01 Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 24 Sep 2024 18:00:25 +0530 Subject: [PATCH 42/48] feat: sync database --- server/src/db/config.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/server/src/db/config.ts b/server/src/db/config.ts index 78a785d2..0e6df6d8 100644 --- a/server/src/db/config.ts +++ b/server/src/db/config.ts @@ -23,4 +23,14 @@ export const connectDB = async () => { } }; +export const syncDB = async () => { + try { + await sequelize.sync({ force: false }); // force: true will drop and recreate tables on every run + console.log('Database synced successfully!'); + } catch (error) { + console.error('Failed to sync database:', error); + } + }; + + export default sequelize; \ No newline at end of file From 6c06cbf1d30a84cfc06424e987ec449b00d94501 Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 24 Sep 2024 18:00:38 +0530 Subject: [PATCH 43/48] chore: lint --- server/src/db/config.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/server/src/db/config.ts b/server/src/db/config.ts index 0e6df6d8..9b2221f2 100644 --- a/server/src/db/config.ts +++ b/server/src/db/config.ts @@ -25,12 +25,12 @@ export const connectDB = async () => { export const syncDB = async () => { try { - await sequelize.sync({ force: false }); // force: true will drop and recreate tables on every run - console.log('Database synced successfully!'); + await sequelize.sync({ force: false }); // force: true will drop and recreate tables on every run + console.log('Database synced successfully!'); } catch (error) { - console.error('Failed to sync database:', error); + console.error('Failed to sync database:', error); } - }; - +}; + export default sequelize; \ No newline at end of file From e6e54eee7f039f66ad5a160e04287aa6ca3aba4a Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 24 Sep 2024 18:03:26 +0530 Subject: [PATCH 44/48] feat: connect && sync db --- server/src/server.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/server/src/server.ts b/server/src/server.ts index c7f8e88a..849ef834 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -5,6 +5,7 @@ import 'dotenv/config'; import { record, workflow, storage, auth, integration } from './routes'; import { BrowserPool } from "./browser-management/classes/BrowserPool"; import logger from './logger'; +import { connectDB, syncDB } from './db/config'; import cookieParser from 'cookie-parser'; import csrf from 'csurf'; import { SERVER_PORT } from "./constants/config"; @@ -47,4 +48,8 @@ app.get('/csrf-token', (req, res) => { res.json({ csrfToken: req.csrfToken() }) }) -server.listen(SERVER_PORT, () => logger.log('info', `Server listening on port ${SERVER_PORT}`)); +server.listen(SERVER_PORT, async () => { + await connectDB(); + await syncDB(); + logger.log('info', `Server listening on port ${SERVER_PORT}`); +}); From b39b9d192bee776cd3a358b65731b9c555709f20 Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 24 Sep 2024 18:59:49 +0530 Subject: [PATCH 45/48] chore: ignore .env --- .gitignore | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..74279fc9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,18 @@ +# dependencies +/node_modules + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local +.env + +/.idea + +/server/logs + +/build + +package-lock.json From 506b21da2b1c2c26751ae7817ab427bcc640f91c Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 24 Sep 2024 19:06:04 +0530 Subject: [PATCH 46/48] chore(deps): install types for cookie parser --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index b434c476..fe5a48f9 100644 --- a/package.json +++ b/package.json @@ -95,6 +95,7 @@ ] }, "devDependencies": { + "@types/cookie-parser": "^1.4.7", "@types/express": "^4.17.13", "@types/loglevel": "^1.6.3", "@types/node": "^17.0.15", From e9d3b9fcb07403900b19ebea6fb0ce727ed16b94 Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 24 Sep 2024 19:07:02 +0530 Subject: [PATCH 47/48] fix: set error type explicitly any --- server/src/routes/auth.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/server/src/routes/auth.ts b/server/src/routes/auth.ts index 4740853a..e8d376e0 100644 --- a/server/src/routes/auth.ts +++ b/server/src/routes/auth.ts @@ -25,7 +25,7 @@ router.post('/register', async (req, res) => { httpOnly: true }) res.json(user) - } catch (error) { + } catch (error: any) { res.status(500).send(`Could not register user - ${error.message}`) } }) @@ -50,7 +50,7 @@ router.post('/login', async (req, res) => { httpOnly: true }) res.json(user) - } catch (error) { + } catch (error: any) { res.status(400).send(`Could not login user - ${error.message}`) } }) @@ -59,7 +59,7 @@ router.get('/logout', async (req, res) => { try { res.clearCookie('token') return res.json({ message: 'Logout successful' }) - } catch (error) { + } catch (error: any) { res.status(500).send(`Could not logout user - ${error.message}`) } }) @@ -73,7 +73,7 @@ router.get('/current-user', async (req: AuthenticatedRequest, res) => { attributes: { exclude: ['password'] }, }); return res.status(200).json({ ok: true }); - } catch (error) { + } catch (error: any) { return res.status(500).send(`Could not fetch current user : ${error.message}.`); } }); \ No newline at end of file From a2e4c2dde086c8a336164f16433c6abd6be3c57e Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 24 Sep 2024 19:16:51 +0530 Subject: [PATCH 48/48] fix: ts errors --- server/src/utils/auth.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/utils/auth.ts b/server/src/utils/auth.ts index d7eb5751..4cc45644 100644 --- a/server/src/utils/auth.ts +++ b/server/src/utils/auth.ts @@ -1,6 +1,6 @@ import bcrypt from "bcrypt"; -export const hashPassword = (password) => { +export const hashPassword = (password: any) => { return new Promise((resolve, reject) => { bcrypt.genSalt(12, (err, salt) => { if (err) { @@ -17,6 +17,6 @@ export const hashPassword = (password) => { } // password from frontend and hash from database -export const comparePassword = (password, hash) => { +export const comparePassword = (password: any, hash: any) => { return bcrypt.compare(password, hash) } \ No newline at end of file