Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions backend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions backend/simple-chat
Submodule simple-chat added at 69ea97
2 changes: 2 additions & 0 deletions backend/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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;
15 changes: 3 additions & 12 deletions backend/src/controllers/user.controller.ts
Original file line number Diff line number Diff line change
@@ -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<void> {
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' });
Expand Down
21 changes: 15 additions & 6 deletions backend/src/middleware/auth.middleware.ts
Original file line number Diff line number Diff line change
@@ -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();
}
};
6 changes: 3 additions & 3 deletions backend/src/routes/auth.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
2 changes: 1 addition & 1 deletion backend/src/routes/user.routes.ts
Original file line number Diff line number Diff line change
@@ -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();
Expand Down
6 changes: 6 additions & 0 deletions backend/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}`);
Expand Down
36 changes: 28 additions & 8 deletions backend/src/services/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<UserDocument>) {}
Expand All @@ -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);
}
Expand All @@ -26,23 +27,35 @@ 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');
}

return this.tokenSign(user);
}

async refreshToken(refreshToken: string): Promise<any> {
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');
Expand All @@ -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,
Expand Down
10 changes: 10 additions & 0 deletions backend/src/types/auth-token-payload.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type { JwtPayload } from 'jsonwebtoken';

export type AuthTokenPayload = JwtPayload & {
user: {
id: string;
name: string;
email: string;
admin: boolean;
};
};
11 changes: 11 additions & 0 deletions backend/src/types/express.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { AuthTokenPayload } from './auth-token-payload';

declare global {
namespace Express {
interface Request {
authUser?: AuthTokenPayload['user'];
}
}
}

export {};
5 changes: 4 additions & 1 deletion backend/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,8 @@
], // Include files from the src directory
"exclude": [
"node_modules"
] // Exclude the node_modules directory
],
"ts-node": {
"files": true
}
}