mirror of
https://github.com/hex248/sprint.git
synced 2026-02-08 10:33:01 +00:00
patched security holes
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { IssueCreateRequestSchema } from "@sprint/shared";
|
||||
import type { AuthedRequest } from "../../auth/middleware";
|
||||
import { createIssue, getProjectByID } from "../../db/queries";
|
||||
import { createIssue, getOrganisationMemberRole, getProjectByID } from "../../db/queries";
|
||||
import { errorResponse, parseJsonBody } from "../../validation";
|
||||
|
||||
export default async function issueCreate(req: AuthedRequest) {
|
||||
@@ -14,6 +14,18 @@ export default async function issueCreate(req: AuthedRequest) {
|
||||
return errorResponse(`project not found: ${projectId}`, "PROJECT_NOT_FOUND", 404);
|
||||
}
|
||||
|
||||
const requesterMember = await getOrganisationMemberRole(project.organisationId, req.userId);
|
||||
if (!requesterMember) {
|
||||
return errorResponse("you are not a member of this organisation", "NOT_MEMBER", 403);
|
||||
}
|
||||
if (requesterMember.role !== "owner" && requesterMember.role !== "admin") {
|
||||
return errorResponse(
|
||||
"only organisation owners and admins can create issues",
|
||||
"PERMISSION_DENIED",
|
||||
403,
|
||||
);
|
||||
}
|
||||
|
||||
const issue = await createIssue(
|
||||
project.id,
|
||||
title,
|
||||
|
||||
@@ -1,18 +1,37 @@
|
||||
import { IssueDeleteRequestSchema } from "@sprint/shared";
|
||||
import type { BunRequest } from "bun";
|
||||
import { deleteIssue } from "../../db/queries";
|
||||
import type { AuthedRequest } from "../../auth/middleware";
|
||||
import { deleteIssue, getIssueByID, getOrganisationMemberRole, getProjectByID } from "../../db/queries";
|
||||
import { errorResponse, parseJsonBody } from "../../validation";
|
||||
|
||||
export default async function issueDelete(req: BunRequest) {
|
||||
export default async function issueDelete(req: AuthedRequest) {
|
||||
const parsed = await parseJsonBody(req, IssueDeleteRequestSchema);
|
||||
if ("error" in parsed) return parsed.error;
|
||||
|
||||
const { id } = parsed.data;
|
||||
|
||||
const result = await deleteIssue(id);
|
||||
if (result.rowCount === 0) {
|
||||
const issue = await getIssueByID(id);
|
||||
if (!issue) {
|
||||
return errorResponse(`no issue with id ${id} found`, "ISSUE_NOT_FOUND", 404);
|
||||
}
|
||||
|
||||
const project = await getProjectByID(issue.projectId);
|
||||
if (!project) {
|
||||
return errorResponse("project not found", "PROJECT_NOT_FOUND", 404);
|
||||
}
|
||||
|
||||
const requesterMember = await getOrganisationMemberRole(project.organisationId, req.userId);
|
||||
if (!requesterMember) {
|
||||
return errorResponse("you are not a member of this organisation", "NOT_MEMBER", 403);
|
||||
}
|
||||
if (requesterMember.role !== "owner" && requesterMember.role !== "admin") {
|
||||
return errorResponse(
|
||||
"only organisation owners and admins can delete issues",
|
||||
"PERMISSION_DENIED",
|
||||
403,
|
||||
);
|
||||
}
|
||||
|
||||
await deleteIssue(id);
|
||||
|
||||
return Response.json({ success: true });
|
||||
}
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
import { type IssueRecord, IssueUpdateRequestSchema } from "@sprint/shared";
|
||||
import type { BunRequest } from "bun";
|
||||
import { getIssueByID, setIssueAssignees, updateIssue } from "../../db/queries";
|
||||
import type { AuthedRequest } from "../../auth/middleware";
|
||||
import {
|
||||
getIssueByID,
|
||||
getOrganisationMemberRole,
|
||||
getProjectByID,
|
||||
setIssueAssignees,
|
||||
updateIssue,
|
||||
} from "../../db/queries";
|
||||
import { errorResponse, parseJsonBody } from "../../validation";
|
||||
|
||||
export default async function issueUpdate(req: BunRequest) {
|
||||
export default async function issueUpdate(req: AuthedRequest) {
|
||||
const parsed = await parseJsonBody(req, IssueUpdateRequestSchema);
|
||||
if ("error" in parsed) return parsed.error;
|
||||
|
||||
@@ -23,7 +29,25 @@ export default async function issueUpdate(req: BunRequest) {
|
||||
const hasIssueFieldUpdates =
|
||||
title !== undefined || description !== undefined || status !== undefined || sprintId !== undefined;
|
||||
|
||||
let issue: IssueRecord | undefined;
|
||||
const existingIssue = await getIssueByID(id);
|
||||
if (!existingIssue) {
|
||||
return errorResponse(`issue not found: ${id}`, "ISSUE_NOT_FOUND", 404);
|
||||
}
|
||||
|
||||
const project = await getProjectByID(existingIssue.projectId);
|
||||
if (!project) {
|
||||
return errorResponse("project not found", "PROJECT_NOT_FOUND", 404);
|
||||
}
|
||||
|
||||
const requesterMember = await getOrganisationMemberRole(project.organisationId, req.userId);
|
||||
if (!requesterMember) {
|
||||
return errorResponse("you are not a member of this organisation", "NOT_MEMBER", 403);
|
||||
}
|
||||
if (requesterMember.role !== "owner" && requesterMember.role !== "admin") {
|
||||
return errorResponse("only organisation owners and admins can edit issues", "PERMISSION_DENIED", 403);
|
||||
}
|
||||
|
||||
let issue: IssueRecord | undefined = existingIssue;
|
||||
if (hasIssueFieldUpdates) {
|
||||
[issue] = await updateIssue(id, {
|
||||
title,
|
||||
@@ -31,8 +55,6 @@ export default async function issueUpdate(req: BunRequest) {
|
||||
sprintId,
|
||||
status,
|
||||
});
|
||||
} else {
|
||||
issue = await getIssueByID(id);
|
||||
}
|
||||
|
||||
if (assigneeIds !== undefined) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ProjectCreateRequestSchema } from "@sprint/shared";
|
||||
import type { AuthedRequest } from "../../auth/middleware";
|
||||
import { createProject, getProjectByKey, getUserById } from "../../db/queries";
|
||||
import { createProject, getOrganisationMemberRole, getProjectByKey, getUserById } from "../../db/queries";
|
||||
import { errorResponse, parseJsonBody } from "../../validation";
|
||||
|
||||
export default async function projectCreate(req: AuthedRequest) {
|
||||
@@ -14,6 +14,14 @@ export default async function projectCreate(req: AuthedRequest) {
|
||||
return errorResponse(`project with key ${key} already exists in this organisation`, "KEY_TAKEN", 400);
|
||||
}
|
||||
|
||||
const membership = await getOrganisationMemberRole(organisationId, req.userId);
|
||||
if (!membership) {
|
||||
return errorResponse("not a member of this organisation", "NOT_MEMBER", 403);
|
||||
}
|
||||
if (membership.role !== "owner" && membership.role !== "admin") {
|
||||
return errorResponse("only owners and admins can create projects", "PERMISSION_DENIED", 403);
|
||||
}
|
||||
|
||||
const creator = await getUserById(req.userId);
|
||||
if (!creator) {
|
||||
return errorResponse(`creator not found`, "CREATOR_NOT_FOUND", 404);
|
||||
|
||||
@@ -10,6 +10,10 @@ import {
|
||||
createTimedSession,
|
||||
getActiveTimedSession,
|
||||
getIssueAssigneeCount,
|
||||
getIssueByID,
|
||||
getOrganisationMemberRole,
|
||||
getProjectByID,
|
||||
isIssueAssignee,
|
||||
} from "../../db/queries";
|
||||
import { parseJsonBody } from "../../validation";
|
||||
|
||||
@@ -19,6 +23,35 @@ export default async function timerToggle(req: AuthedRequest) {
|
||||
|
||||
const { issueId } = parsed.data;
|
||||
|
||||
const issue = await getIssueByID(issueId);
|
||||
if (!issue) {
|
||||
return Response.json(
|
||||
{ error: `issue not found: ${issueId}`, code: "ISSUE_NOT_FOUND" },
|
||||
{ status: 404 },
|
||||
);
|
||||
}
|
||||
|
||||
const project = await getProjectByID(issue.projectId);
|
||||
if (!project) {
|
||||
return Response.json({ error: "project not found", code: "PROJECT_NOT_FOUND" }, { status: 404 });
|
||||
}
|
||||
|
||||
const membership = await getOrganisationMemberRole(project.organisationId, req.userId);
|
||||
if (!membership) {
|
||||
return Response.json(
|
||||
{ error: "you are not a member of this organisation", code: "NOT_MEMBER" },
|
||||
{ status: 403 },
|
||||
);
|
||||
}
|
||||
|
||||
const isAssigned = await isIssueAssignee(issueId, req.userId);
|
||||
if (!isAssigned) {
|
||||
return Response.json(
|
||||
{ error: "you must be assigned to this issue", code: "NOT_ASSIGNEE" },
|
||||
{ status: 403 },
|
||||
);
|
||||
}
|
||||
|
||||
const assigneeCount = await getIssueAssigneeCount(issueId);
|
||||
if (assigneeCount > 1) {
|
||||
return Response.json(
|
||||
|
||||
Reference in New Issue
Block a user