diff --git a/backend/collab-service/.env.sample b/backend/collab-service/.env.sample index 94020b37a8..65939ae031 100644 --- a/backend/collab-service/.env.sample +++ b/backend/collab-service/.env.sample @@ -4,6 +4,9 @@ SERVICE_PORT=3003 # Origins for cors ORIGINS=http://localhost:5173,http://127.0.0.1:5173 +# Other service APIs +USER_SERVICE_URL=http://user-service:3001/api + # Redis configuration REDIS_URI=redis://collab-service-redis:6379 diff --git a/backend/communication-service/src/utils/userServiceApi.ts b/backend/collab-service/src/api/userService.ts similarity index 100% rename from backend/communication-service/src/utils/userServiceApi.ts rename to backend/collab-service/src/api/userService.ts diff --git a/backend/collab-service/src/middlewares/basicAccessControl.ts b/backend/collab-service/src/middlewares/basicAccessControl.ts new file mode 100644 index 0000000000..727ee6783e --- /dev/null +++ b/backend/collab-service/src/middlewares/basicAccessControl.ts @@ -0,0 +1,19 @@ +import { ExtendedError, Socket } from "socket.io"; +import { verifyToken } from "../api/userService.ts"; + +export const verifyUserToken = ( + socket: Socket, + next: (err?: ExtendedError) => void +) => { + const token = + socket.handshake.headers.authorization || socket.handshake.auth.token; + verifyToken(token) + .then(() => { + console.log("Valid credentials"); + next(); + }) + .catch((err) => { + console.error(err); + next(new Error("Unauthorized")); + }); +}; diff --git a/backend/collab-service/src/server.ts b/backend/collab-service/src/server.ts index c1d11c7333..1a00c9c42c 100644 --- a/backend/collab-service/src/server.ts +++ b/backend/collab-service/src/server.ts @@ -3,8 +3,10 @@ import app, { allowedOrigins } from "./app.ts"; import { handleWebsocketCollabEvents } from "./handlers/websocketHandler.ts"; import { Server, Socket } from "socket.io"; import { connectRedis } from "./config/redis.ts"; +import { verifyUserToken } from "./middlewares/basicAccessControl.ts"; const server = http.createServer(app); + export const io = new Server(server, { cors: { origin: allowedOrigins, @@ -13,6 +15,8 @@ export const io = new Server(server, { connectionStateRecovery: {}, }); +io.use(verifyUserToken); + io.on("connection", (socket: Socket) => { handleWebsocketCollabEvents(socket); }); diff --git a/backend/communication-service/src/api/userService.ts b/backend/communication-service/src/api/userService.ts new file mode 100644 index 0000000000..88442a3b6c --- /dev/null +++ b/backend/communication-service/src/api/userService.ts @@ -0,0 +1,15 @@ +import axios from "axios"; + +const USER_SERVICE_URL = + process.env.USER_SERVICE_URL || "http://localhost:3001/api"; + +const userClient = axios.create({ + baseURL: USER_SERVICE_URL, + withCredentials: true, +}); + +export const verifyToken = (token: string | undefined) => { + return userClient.get("/auth/verify-token", { + headers: { authorization: token }, + }); +}; diff --git a/backend/communication-service/src/middlewares/basicAccessControl.ts b/backend/communication-service/src/middlewares/basicAccessControl.ts new file mode 100644 index 0000000000..15088e9a86 --- /dev/null +++ b/backend/communication-service/src/middlewares/basicAccessControl.ts @@ -0,0 +1,19 @@ +import { ExtendedError, Socket } from "socket.io"; +import { verifyToken } from "../api/userService"; + +export const verifyUserToken = ( + socket: Socket, + next: (err?: ExtendedError) => void +) => { + const token = + socket.handshake.headers.authorization || socket.handshake.auth.token; + verifyToken(token) + .then(() => { + console.log("Valid credentials"); + next(); + }) + .catch((err) => { + console.error(err); + next(new Error("Unauthorized")); + }); +}; diff --git a/backend/communication-service/src/server.ts b/backend/communication-service/src/server.ts index 4c2ebd4140..5421fe6f17 100644 --- a/backend/communication-service/src/server.ts +++ b/backend/communication-service/src/server.ts @@ -2,7 +2,7 @@ import app, { allowedOrigins } from "./app"; import { createServer } from "http"; import { Server } from "socket.io"; import { handleWebsocketCommunicationEvents } from "./handlers/websocketHandler"; -import { verifyToken } from "./utils/userServiceApi"; +import { verifyUserToken } from "./middlewares/basicAccessControl"; const PORT = process.env.SERVICE_PORT || 3005; @@ -13,19 +13,7 @@ export const io = new Server(server, { connectionStateRecovery: {}, }); -io.use((socket, next) => { - const token = - socket.handshake.headers.authorization || socket.handshake.auth.token; - verifyToken(token) - .then(() => { - console.log("Valid credentials"); - next(); - }) - .catch((err) => { - console.error(err); - next(new Error("Unauthorized")); - }); -}); +io.use(verifyUserToken); io.on("connection", handleWebsocketCommunicationEvents); diff --git a/backend/matching-service/.env.sample b/backend/matching-service/.env.sample index 83217a9e53..044f09d289 100644 --- a/backend/matching-service/.env.sample +++ b/backend/matching-service/.env.sample @@ -7,6 +7,7 @@ ORIGINS=http://localhost:5173,http://127.0.0.1:5173 # Other service APIs QUESTION_SERVICE_URL=http://question-service:3000/api/questions QN_HISTORY_SERVICE_URL=http://qn-history-service:3006/api/qnhistories +USER_SERVICE_URL=http://user-service:3001/api # RabbitMq configuration RABBITMQ_DEFAULT_USER=admin diff --git a/backend/matching-service/src/api/questionHistoryService.ts b/backend/matching-service/src/api/questionHistoryService.ts new file mode 100644 index 0000000000..0a17c16703 --- /dev/null +++ b/backend/matching-service/src/api/questionHistoryService.ts @@ -0,0 +1,31 @@ +import axios from "axios"; + +const QN_HISTORY_SERVICE_URL = + process.env.QN_HISTORY_SERVICE_URL || + "http://qn-history-service:3006/api/qnhistories"; + +const qnHistoryService = axios.create({ + baseURL: QN_HISTORY_SERVICE_URL, + headers: { + "Content-Type": "application/json", + }, +}); + +export const createQuestionHistory = ( + questionId: string, + title: string, + submissionStatus: string, + language: string, + ...userIds: string[] +) => { + const dateAttempted = new Date(); + return qnHistoryService.post("/", { + userIds, + questionId, + title, + submissionStatus, + language, + dateAttempted, + timeTaken: 0, + }); +}; diff --git a/backend/matching-service/src/api/questionService.ts b/backend/matching-service/src/api/questionService.ts new file mode 100644 index 0000000000..7599df3bc2 --- /dev/null +++ b/backend/matching-service/src/api/questionService.ts @@ -0,0 +1,16 @@ +import axios from "axios"; + +const QUESTION_SERVICE_URL = + process.env.QUESTION_SERVICE_URL || + "http://question-service:3000/api/questions"; + +const questionClient = axios.create({ + baseURL: QUESTION_SERVICE_URL, + headers: { + "Content-Type": "application/json", + }, +}); + +export const getRandomQuestion = (complexity: string, category: string) => { + return questionClient.get("/random", { params: { complexity, category } }); +}; diff --git a/backend/matching-service/src/api/userService.ts b/backend/matching-service/src/api/userService.ts new file mode 100644 index 0000000000..88442a3b6c --- /dev/null +++ b/backend/matching-service/src/api/userService.ts @@ -0,0 +1,15 @@ +import axios from "axios"; + +const USER_SERVICE_URL = + process.env.USER_SERVICE_URL || "http://localhost:3001/api"; + +const userClient = axios.create({ + baseURL: USER_SERVICE_URL, + withCredentials: true, +}); + +export const verifyToken = (token: string | undefined) => { + return userClient.get("/auth/verify-token", { + headers: { authorization: token }, + }); +}; diff --git a/backend/matching-service/src/config/rabbitmq.ts b/backend/matching-service/src/config/rabbitmq.ts index 966e98cbd6..0623f88b02 100644 --- a/backend/matching-service/src/config/rabbitmq.ts +++ b/backend/matching-service/src/config/rabbitmq.ts @@ -1,6 +1,6 @@ import amqplib, { Connection } from "amqplib"; import dotenv from "dotenv"; -import { matchUsers } from "../utils/mq_utils"; +import { matchUsers } from "../utils/messageQueue"; import { MatchRequestItem } from "../handlers/matchHandler"; import { Complexities, Categories, Languages } from "../utils/constants"; diff --git a/backend/matching-service/src/handlers/websocketHandler.ts b/backend/matching-service/src/handlers/websocketHandler.ts index a738530bfc..74cdd526e9 100644 --- a/backend/matching-service/src/handlers/websocketHandler.ts +++ b/backend/matching-service/src/handlers/websocketHandler.ts @@ -11,7 +11,8 @@ import { } from "./matchHandler"; import { io } from "../server"; import { v4 as uuidv4 } from "uuid"; -import { qnHistoryService, questionService } from "../utils/api"; +import { getRandomQuestion } from "../api/questionService"; +import { createQuestionHistory } from "../api/questionHistoryService"; enum MatchEvents { // Receive @@ -130,28 +131,23 @@ export const handleWebsocketMatchEvents = (socket: Socket) => { } const { complexity, category, language } = match; - questionService - .get("/random", { params: { complexity, category } }) - .then((res) => { - const qnId = res.data.question.id; - qnHistoryService - .post("/", { - userIds: [userId1, userId2], - questionId: qnId, - title: res.data.question.title, - submissionStatus: "Attempted", - dateAttempted: new Date(), - timeTaken: 0, - language: language, - }) - .then((res) => { - io.to(matchId).emit( - MatchEvents.MATCH_SUCCESSFUL, - qnId, - res.data.qnHistory.id - ); - }); + getRandomQuestion(complexity, category).then((res) => { + const qnId = res.data.question.id; + createQuestionHistory( + qnId, + res.data.question.title, + "Attempted", + language, + userId1, + userId2 + ).then((res) => { + io.to(matchId).emit( + MatchEvents.MATCH_SUCCESSFUL, + qnId, + res.data.qnHistory.id + ); }); + }); } } ); diff --git a/backend/matching-service/src/middlewares/basicAccessControl.ts b/backend/matching-service/src/middlewares/basicAccessControl.ts new file mode 100644 index 0000000000..15088e9a86 --- /dev/null +++ b/backend/matching-service/src/middlewares/basicAccessControl.ts @@ -0,0 +1,19 @@ +import { ExtendedError, Socket } from "socket.io"; +import { verifyToken } from "../api/userService"; + +export const verifyUserToken = ( + socket: Socket, + next: (err?: ExtendedError) => void +) => { + const token = + socket.handshake.headers.authorization || socket.handshake.auth.token; + verifyToken(token) + .then(() => { + console.log("Valid credentials"); + next(); + }) + .catch((err) => { + console.error(err); + next(new Error("Unauthorized")); + }); +}; diff --git a/backend/matching-service/src/server.ts b/backend/matching-service/src/server.ts index fc627d78ca..0c420ce87a 100644 --- a/backend/matching-service/src/server.ts +++ b/backend/matching-service/src/server.ts @@ -3,8 +3,11 @@ import app, { allowedOrigins } from "./app.ts"; import { handleWebsocketMatchEvents } from "./handlers/websocketHandler.ts"; import { Server } from "socket.io"; import { connectToRabbitMq } from "./config/rabbitmq.ts"; +import { verifyToken } from "./api/userService.ts"; +import { verifyUserToken } from "./middlewares/basicAccessControl.ts"; const server = http.createServer(app); + export const io = new Server(server, { cors: { origin: allowedOrigins, @@ -13,6 +16,8 @@ export const io = new Server(server, { connectionStateRecovery: {}, }); +io.use(verifyUserToken); + io.on("connection", (socket) => { handleWebsocketMatchEvents(socket); }); diff --git a/backend/matching-service/src/utils/api.ts b/backend/matching-service/src/utils/api.ts deleted file mode 100644 index 1b088f4c05..0000000000 --- a/backend/matching-service/src/utils/api.ts +++ /dev/null @@ -1,23 +0,0 @@ -import axios from "axios"; - -const QUESTION_SERVICE_URL = - process.env.QUESTION_SERVICE_URL || - "http://question-service:3000/api/questions"; - -const QN_HISTORY_SERVICE_URL = - process.env.QN_HISTORY_SERVICE_URL || - "http://qn-history-service:3006/api/qnhistories"; - -export const questionService = axios.create({ - baseURL: QUESTION_SERVICE_URL, - headers: { - "Content-Type": "application/json", - }, -}); - -export const qnHistoryService = axios.create({ - baseURL: QN_HISTORY_SERVICE_URL, - headers: { - "Content-Type": "application/json", - }, -}); diff --git a/backend/matching-service/src/utils/mq_utils.ts b/backend/matching-service/src/utils/messageQueue.ts similarity index 100% rename from backend/matching-service/src/utils/mq_utils.ts rename to backend/matching-service/src/utils/messageQueue.ts diff --git a/frontend/src/utils/collabSocket.ts b/frontend/src/utils/collabSocket.ts index 4fe50bafe3..1d252552cc 100644 --- a/frontend/src/utils/collabSocket.ts +++ b/frontend/src/utils/collabSocket.ts @@ -33,9 +33,13 @@ export type CollabSessionData = { }; const COLLAB_SOCKET_URL = "http://localhost:3003"; + export const collabSocket = io(COLLAB_SOCKET_URL, { reconnectionAttempts: 3, autoConnect: false, + auth: { + token: `Bearer ${localStorage.getItem("token")}`, + }, }); let doc: Doc; diff --git a/frontend/src/utils/matchSocket.ts b/frontend/src/utils/matchSocket.ts index e79801a6b4..4ef4d2038b 100644 --- a/frontend/src/utils/matchSocket.ts +++ b/frontend/src/utils/matchSocket.ts @@ -5,4 +5,7 @@ const MATCH_SOCKET_URL = "http://localhost:3002"; export const matchSocket = io(MATCH_SOCKET_URL, { reconnectionAttempts: 3, autoConnect: false, + auth: { + token: `Bearer ${localStorage.getItem("token")}`, + }, });