From 4ba36f23fb96f144e7274894c9593ac3f9361866 Mon Sep 17 00:00:00 2001 From: egor Date: Sun, 22 Mar 2026 17:30:44 +0200 Subject: [PATCH 1/3] =?UTF-8?q?=D0=BD=D0=B5=D0=BA=D0=BE=D1=82=D0=BE=D1=80?= =?UTF-8?q?=D1=8B=D0=B5=20=D0=B8=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/package-lock.json | 2 ++ backend/src/app.ts | 2 ++ backend/src/controllers/user.controller.ts | 15 ++------- backend/src/middleware/auth.middleware.ts | 21 +++++++++---- backend/src/routes/auth.routes.ts | 6 ++-- backend/src/routes/user.routes.ts | 2 +- backend/src/services/auth.service.ts | 36 +++++++++++++++++----- backend/src/types/auth-token-payload.ts | 10 ++++++ backend/src/types/express.d.ts | 11 +++++++ backend/tsconfig.json | 5 ++- 10 files changed, 79 insertions(+), 31 deletions(-) create mode 100644 backend/src/types/auth-token-payload.ts create mode 100644 backend/src/types/express.d.ts diff --git a/backend/package-lock.json b/backend/package-lock.json index 166329e..4d0460c 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -204,6 +204,7 @@ "integrity": "sha512-9wLpoeWuBlcbBpOY3XmzSTG3oscB6xjBEEtn+pYXTfhyXhIxC5FsBer2KTopBlvKEiW9l13po9fq+SJY/5lkhw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~7.18.0" } @@ -1878,6 +1879,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/backend/src/app.ts b/backend/src/app.ts index 0ca2bc8..0f54d3a 100644 --- a/backend/src/app.ts +++ b/backend/src/app.ts @@ -2,6 +2,7 @@ import express from 'express'; import cors from 'cors'; import chatRouter from './routes/chat.routes'; import authRouter from './routes/auth.routes'; +import userRouter from './routes/user.routes'; import { authMiddleware } from './middleware/auth.middleware'; const app = express(); @@ -12,6 +13,7 @@ app.use(express.urlencoded({ extended: false })); app.use(express.json()); app.use('/v1/api/auth', authRouter); +app.use('/v1/api/users', authMiddleware, userRouter); app.use('/v1/api/chats', authMiddleware, chatRouter); export default app; \ No newline at end of file diff --git a/backend/src/controllers/user.controller.ts b/backend/src/controllers/user.controller.ts index 8f0258b..bc28693 100644 --- a/backend/src/controllers/user.controller.ts +++ b/backend/src/controllers/user.controller.ts @@ -1,26 +1,17 @@ import { Request, Response } from 'express'; import { UserService } from '../services/user.service'; -import jwt from 'jsonwebtoken'; export class UserController { constructor(private readonly userService: UserService) {} async getUser(req: Request, res: Response): Promise { - const authToken = req.headers.authorization?.split(' ')[1]; - - if (!authToken) { - res.status(401).json({ error: 'Unauthorized' }); - return; - } - - const decoded = jwt.verify(authToken, process.env.JWT_SECRET as string); - - if (!(decoded as any).user?.id) { + const userId = req.authUser?.id; + if (!userId) { res.status(401).json({ error: 'Unauthorized' }); return; } - const user = await this.userService.getUserById((decoded as any).user?.id); + const user = await this.userService.getUserById(userId); if (!user) { res.status(404).json({ error: 'User not found' }); diff --git a/backend/src/middleware/auth.middleware.ts b/backend/src/middleware/auth.middleware.ts index c47a6a0..919845a 100644 --- a/backend/src/middleware/auth.middleware.ts +++ b/backend/src/middleware/auth.middleware.ts @@ -1,17 +1,26 @@ import { Request, Response, NextFunction } from 'express'; import jwt from 'jsonwebtoken'; +import type { AuthTokenPayload } from '../types/auth-token-payload'; export const authMiddleware = (req: Request, res: Response, next: NextFunction) => { const token = req.headers.authorization?.split(' ')[1]; - + if (!token) { return res.status(401).json({ error: 'Unauthorized' }); } - const decoded = jwt.verify(token, process.env.JWT_SECRET as string); - if (!decoded) { + try { + const decoded = jwt.verify(token, process.env.JWT_SECRET as string); + if (typeof decoded === 'string') { + return res.status(401).json({ error: 'Unauthorized' }); + } + const payload = decoded as AuthTokenPayload; + if (!payload.user?.id) { + return res.status(401).json({ error: 'Unauthorized' }); + } + req.authUser = payload.user; + next(); + } catch { return res.status(401).json({ error: 'Unauthorized' }); } - - next(); -} \ No newline at end of file +}; \ No newline at end of file diff --git a/backend/src/routes/auth.routes.ts b/backend/src/routes/auth.routes.ts index fc8695c..09b6d64 100644 --- a/backend/src/routes/auth.routes.ts +++ b/backend/src/routes/auth.routes.ts @@ -8,8 +8,8 @@ const router = Router(); const authService = new AuthService(UserModel); const authController = new AuthController(authService); -router.post('/register', authController.register); -router.post('/login', authController.login); -router.post('/refresh', authController.refreshToken); +router.post('/register', authController.register.bind(authController)); +router.post('/login', authController.login.bind(authController)); +router.post('/refresh', authController.refreshToken.bind(authController)); export default router; \ No newline at end of file diff --git a/backend/src/routes/user.routes.ts b/backend/src/routes/user.routes.ts index 288baaa..55a9dce 100644 --- a/backend/src/routes/user.routes.ts +++ b/backend/src/routes/user.routes.ts @@ -1,5 +1,5 @@ import { Router } from 'express'; -import { UserModel } from 'src/models/user.model'; +import { UserModel } from '../models/user.model'; import { UserController } from '../controllers/user.controller'; import { UserService } from '../services/user.service'; const router = Router(); diff --git a/backend/src/services/auth.service.ts b/backend/src/services/auth.service.ts index 2e7879c..167f9ec 100644 --- a/backend/src/services/auth.service.ts +++ b/backend/src/services/auth.service.ts @@ -2,6 +2,7 @@ import { Model } from "mongoose"; import { UserDocument } from "src/models/user.model"; import bcrypt from 'bcrypt'; import jwt from "jsonwebtoken"; +import type { AuthTokenPayload } from "../types/auth-token-payload"; export class AuthService { constructor(private readonly userModel: Model) {} @@ -14,7 +15,7 @@ export class AuthService { throw new Error('User already exists'); } - const newUser = await this.userModel.create({ name, email, passwordHash }); + const newUser = await this.userModel.create({ name, email, password_hash: passwordHash }); return this.tokenSign(newUser); } @@ -26,9 +27,9 @@ export class AuthService { throw new Error('Invalid login or password'); } - const passwordHash = await bcrypt.hash(password, 10); + const passwordHash = await bcrypt.compare(password, user.password_hash); - if (user.password_hash !== passwordHash) { + if (!passwordHash) { throw new Error('Invalid login or password'); } @@ -36,13 +37,25 @@ export class AuthService { } async refreshToken(refreshToken: string): Promise { - const decoded = jwt.verify(refreshToken, process.env.JWT_REFRESH_TOKEN_SECRET as string); - - if (!decoded) { + const decoded = jwt.verify( + refreshToken, + process.env.JWT_REFRESH_TOKEN_SECRET as string + ); + + if (typeof decoded === 'string') { + throw new Error('Invalid refresh token'); + } + + const payload = decoded as AuthTokenPayload; + const id = payload.user?.id; + const email = payload.user?.email; + if (!id && !email) { throw new Error('Invalid refresh token'); } - const user = await this.userModel.findOne({ email: (decoded as any).user.id }); + const user = id + ? await this.userModel.findById(id) + : await this.userModel.findOne({ email }); if (!user) { throw new Error('User not found'); @@ -52,7 +65,14 @@ export class AuthService { } private async tokenSign(user: UserDocument) { - const payload = { user: {name: user.name, email: user.email, admin: false}}; + const payload: { user: AuthTokenPayload['user'] } = { + user: { + id: user.id, + name: user.name, + email: user.email, + admin: false, + }, + }; return { accessToken: jwt.sign( payload, diff --git a/backend/src/types/auth-token-payload.ts b/backend/src/types/auth-token-payload.ts new file mode 100644 index 0000000..81ec934 --- /dev/null +++ b/backend/src/types/auth-token-payload.ts @@ -0,0 +1,10 @@ +import type { JwtPayload } from 'jsonwebtoken'; + +export type AuthTokenPayload = JwtPayload & { + user: { + id: string; + name: string; + email: string; + admin: boolean; + }; +}; diff --git a/backend/src/types/express.d.ts b/backend/src/types/express.d.ts new file mode 100644 index 0000000..91517f2 --- /dev/null +++ b/backend/src/types/express.d.ts @@ -0,0 +1,11 @@ +import type { AuthTokenPayload } from './auth-token-payload'; + +declare global { + namespace Express { + interface Request { + authUser?: AuthTokenPayload['user']; + } + } +} + +export {}; diff --git a/backend/tsconfig.json b/backend/tsconfig.json index 7bfeda6..1dc23a0 100644 --- a/backend/tsconfig.json +++ b/backend/tsconfig.json @@ -27,5 +27,8 @@ ], // Include files from the src directory "exclude": [ "node_modules" - ] // Exclude the node_modules directory + ], + "ts-node": { + "files": true + } } \ No newline at end of file From f191fdc64f0fa4ef3b65cca46a5c96e05ad1e3fa Mon Sep 17 00:00:00 2001 From: egor Date: Sun, 22 Mar 2026 17:38:52 +0200 Subject: [PATCH 2/3] =?UTF-8?q?=D1=82=D0=B5=D1=81=D1=82=D0=BE=D0=B2=D1=8B?= =?UTF-8?q?=D0=B9=20=D0=BF=D1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/simple-chat | 1 + 1 file changed, 1 insertion(+) create mode 160000 backend/simple-chat diff --git a/backend/simple-chat b/backend/simple-chat new file mode 160000 index 0000000..69ea97b --- /dev/null +++ b/backend/simple-chat @@ -0,0 +1 @@ +Subproject commit 69ea97b5c19a41d46c5fcd47de0a169a00d3bd80 From 88a4bccf17682713f98ce431cd5d57133f5c83dd Mon Sep 17 00:00:00 2001 From: egor Date: Mon, 23 Mar 2026 17:00:51 +0200 Subject: [PATCH 3/3] =?UTF-8?q?=D0=BF=D0=BE=D0=BF=D1=80=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=BE=20=D0=BE=D1=82=D1=81=D1=83=D1=82=D1=81=D1=82?= =?UTF-8?q?=D0=B2=D0=B8=D0=B5=20=D0=B5=D0=BD=D0=B2=D0=B0=20+=20=D0=BB?= =?UTF-8?q?=D0=BE=D0=B3=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BE=D0=BA=20=D0=BC?= =?UTF-8?q?=D0=BE=D0=BD=D0=B3=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/server.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/backend/src/server.ts b/backend/src/server.ts index 0c3e791..1cb97b0 100644 --- a/backend/src/server.ts +++ b/backend/src/server.ts @@ -18,6 +18,12 @@ const envSchema = z.object({ async function startServer() { const env = envSchema.parse(process.env); await mongoose.connect(env.MONGO_URL); + try { + await mongoose.connect(env.MONGO_URL); + } catch (error) { + console.error('Error connecting to MongoDB', error); + process.exit(1); + }; const port = env.PORT ?? 3000; app.listen(port, () => { console.log(`Server on port ${port}`);