Skip to content
This repository was archived by the owner on Sep 18, 2024. It is now read-only.

feature: Created a middleware for checking permissions and token validity #25

Merged
merged 3 commits into from
Mar 2, 2024
Merged
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
22 changes: 5 additions & 17 deletions src/core/application/controllers/assistant/assistantController.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { Elysia, t } from "elysia";
import { ulid } from "ulid";

import { getTokenPermissions, parseToken } from "../../services/tokenService";
import { UNAUTHORIZED_NO_PERMISSION_CREATE_ASSISTANT } from "./returnValues";
import { UNAUTHORIZED_MISSING_TOKEN } from "../../ports/returnValues";
import { parseToken } from "../../services/tokenService";
import {
assignRole,
createUser,
} from "@/core/application/services/userService";
import { createAssistant } from "@/core/application/services/assistantService";
import { AuthMiddleware } from "../../middlewares/authorizationMiddleware";

type AssistantDecorator = {
request: {
Expand All @@ -23,21 +22,9 @@ export const assistants = new Elysia<"/assistant", AssistantDecorator>();

assistants.post(
"/assistant",
async ({ bearer, set, body }) => {
if (!bearer) {
set.status = 401;
return UNAUTHORIZED_MISSING_TOKEN;
}
const permissions = await getTokenPermissions(bearer!);
async ({ bearer, body }) => {
const decodedToken = await parseToken(bearer!);

if (
!permissions?.some((p) => p.key === "create_assistant" || p.key === "*")
) {
set.status = 403;
return UNAUTHORIZED_NO_PERMISSION_CREATE_ASSISTANT;
}

if (decodedToken) {
const { userId } = decodedToken;
const assistantId = ulid();
Expand Down Expand Up @@ -69,5 +56,6 @@ assistants.post(
body: t.Object({
name: t.String(),
}),
}
beforeHandle: AuthMiddleware(["create_assistant", "*"]),
},
);
168 changes: 71 additions & 97 deletions src/core/application/controllers/thread/threadController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,12 @@ import {
} from "@/core/application/services/threadService";
import {
THREAD_DELETED_SUCCESSFULLY,
UNAUTHORIZED_NO_PERMISSION_CREATE,
UNAUTHORIZED_NO_PERMISSION_DELETE,
UNAUTHORIZED_NO_PERMISSION_READ,
UNAUTHORIZED_USER_NOT_OWNER,
UNAUTHORIZED_USER_NOT_PARTICIPANT,
} from "./returnValues";
import { UNAUTHORIZED_MISSING_TOKEN } from "@/core/application/ports/returnValues";
import { createMessage } from "../../services/messageService";
import { AuthMiddleware } from "../../middlewares/authorizationMiddleware";

type ThreadDecorator = {
request: {
Expand All @@ -30,50 +28,42 @@ type ThreadDecorator = {

export const threads = new Elysia<"/thread", ThreadDecorator>();

threads.post("/thread", async ({ bearer, set }) => {
if (!bearer) {
set.status = 401;
return UNAUTHORIZED_MISSING_TOKEN;
}
const permissions = await getTokenPermissions(bearer!);
const decodedToken = await parseToken(bearer!);

if (!permissions?.some((p) => p.key === "create_thread" || p.key === "*")) {
set.status = 403;
return UNAUTHORIZED_NO_PERMISSION_CREATE;
}

if (decodedToken) {
const { userId } = decodedToken;
// // Create a new thread
const threadId = await createThread({
id: ulid(), // Generate a unique ID for the thread
createdBy: userId,
participants: [], // Initialize the participants list
messageIds: [], // Initialize the message IDs list
});
threads.post(
"/thread",
async ({ bearer }) => {
const decodedToken = await parseToken(bearer!);

// If the thread was created successfully, return it in the response
if (threadId) {
return {
id: threadId,
};
if (decodedToken) {
const { userId } = decodedToken;
// // Create a new thread
const threadId = await createThread({
id: ulid(), // Generate a unique ID for the thread
createdBy: userId,
participants: [], // Initialize the participants list
messageIds: [], // Initialize the message IDs list
});

// If the thread was created successfully, return it in the response
if (threadId) {
return {
id: threadId,
};
}
}
}
});

threads.delete("/thread/:id", async ({ params, bearer, set }) => {
const permissions = await getTokenPermissions(bearer!);
const decodedToken = await parseToken(bearer!);
},
{
beforeHandle: AuthMiddleware(["create_thread", "*"]),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this a way of saying all actions under create_thread ?

},
);

if (decodedToken) {
const { userId } = decodedToken;
const threadId = params.id;
threads.delete(
"/thread/:id",
async ({ params, bearer, set }) => {
const decodedToken = await parseToken(bearer!);

// Check if the user has the permission to delete their own thread or * permission
if (
permissions?.some((p) => p.key === "delete_own_thread" || p.key === "*")
) {
if (decodedToken) {
const { userId, permissions } = decodedToken;
const threadId = params.id;
// If the user has * permission, delete the thread without checking ownership
if (permissions.some((p) => p.key === "*")) {
await deleteThread(threadId, userId);
Expand All @@ -87,89 +77,73 @@ threads.delete("/thread/:id", async ({ params, bearer, set }) => {
set.status = 403;
return UNAUTHORIZED_USER_NOT_OWNER;
}
} else {
set.status = 403;
return UNAUTHORIZED_NO_PERMISSION_DELETE;
}
}
set.status = 401;
return { message: "Unauthorized: Invalid or missing token", code: 401 };
});
},
{
beforeHandle: AuthMiddleware(["delete_own_thread", "*"]),
},
);

threads.get("/thread/:id", async ({ params, bearer, set }) => {
const permissions = await getTokenPermissions(bearer!);
const decodedToken = await parseToken(bearer!);
threads.get(
"/thread/:id",
async ({ params, bearer, set }) => {
const decodedToken = await parseToken(bearer!);

if (decodedToken) {
const { userId } = decodedToken;
const threadId = params.id;
if (decodedToken) {
const { userId } = decodedToken;
const threadId = params.id;
const isParticipant = await userOwnsOrParticipatesInThread(
threadId,
userId,
);
const isSuperUser = decodedToken.permissions.some((p) => p.key === "*");

// Check if the user has the permission to see their own threads or * permission
if (
permissions?.some((p) => p.key === "view_own_threads" || p.key === "*")
) {
// If the user has * permission or is a participant in the thread, get the thread
if (
permissions.some((p) => p.key === "*") ||
(await userOwnsOrParticipatesInThread(threadId, userId))
) {
if (isParticipant || isSuperUser) {
const thread = await getThread(threadId);
return thread;
} else {
set.status = 403;
return UNAUTHORIZED_USER_NOT_PARTICIPANT;
}
} else {
set.status = 403;
return UNAUTHORIZED_NO_PERMISSION_READ;
}
}

set.status = 401;
return UNAUTHORIZED_MISSING_TOKEN;
});
},
{
beforeHandle: AuthMiddleware(["view_own_threads", "*"]),
},
);

threads.post(
"/thread/:id/message",
async ({ params, bearer, set, body }) => {
const permissions = await getTokenPermissions(bearer!);
const decodedToken = await parseToken(bearer!);

if (decodedToken) {
const { userId } = decodedToken;
const { userId, permissions } = decodedToken;
const threadId = params.id;
const isSuperUser = permissions.some((p) => p.key === "*");
const isParticipant = await userOwnsOrParticipatesInThread(
threadId,
userId,
);

// Check if the user has the permission to add a message
if (
permissions?.some(
(p) => p.key === "create_message_in_own_thread" || p.key === "*"
)
) {
// if the user has * they can send a message anywhere, if not they need to be in conversation
if (
permissions.some((p) => p.key === "*") ||
(await userOwnsOrParticipatesInThread(threadId, userId))
) {
const { message } = body;
const res = await createMessage(userId, threadId, message);
set.status = 200;
return res;
} else {
set.status = 403;
return UNAUTHORIZED_USER_NOT_PARTICIPANT;
}
// if the user has * they can send a message anywhere, if not they need to be in conversation
if (isSuperUser || isParticipant) {
const { message } = body;
const res = await createMessage(userId, threadId, message);
set.status = 200;
return res;
} else {
set.status = 403;
return UNAUTHORIZED_NO_PERMISSION_READ;
return UNAUTHORIZED_USER_NOT_PARTICIPANT;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch!

}
}

set.status = 401;
return UNAUTHORIZED_MISSING_TOKEN;
},
{
body: t.Object({
message: t.String(),
}),
}
beforeHandle: AuthMiddleware(["create_message_in_own_thread", "*"]),
},
);
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ describe.only("threadController", async () => {
const request = new Request("http://localhost:8080/thread", {
headers: {
authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
method: "POST",
});
Expand Down
Loading
Loading