chore: Remove method override middleware (#4315)

* chore: Remove method override middleware

* wip

* CodeQL

* max/min
This commit is contained in:
Tom Moor
2022-10-18 19:03:25 -04:00
committed by GitHub
parent 0da46321b8
commit 87e3f18e6d
32 changed files with 185 additions and 192 deletions

View File

@@ -1,3 +1,4 @@
import { DefaultState } from "koa";
import randomstring from "randomstring";
import ApiKey from "@server/models/ApiKey";
import { buildUser, buildTeam } from "@server/test/factories";
@@ -9,37 +10,35 @@ setupTestDatabase();
describe("Authentication middleware", () => {
describe("with JWT", () => {
it("should authenticate with correct token", async () => {
const state = {};
const state = {} as DefaultState;
const user = await buildUser();
const authMiddleware = auth();
await authMiddleware(
{
// @ts-expect-error ts-migrate(2740) FIXME: Type '{ get: Mock<string, []>; }' is missing the f... Remove this comment to see the full error message
// @ts-expect-error mock request
request: {
get: jest.fn(() => `Bearer ${user.getJwtToken()}`),
},
// @ts-expect-error ts-migrate(2322) FIXME: Type '{}' is not assignable to type 'DefaultState ... Remove this comment to see the full error message
state,
cache: {},
},
jest.fn()
);
// @ts-expect-error ts-migrate(2339) FIXME: Property 'user' does not exist on type '{}'.
expect(state.user.id).toEqual(user.id);
});
it("should return error with invalid token", async () => {
const state = {};
const state = {} as DefaultState;
const user = await buildUser();
const authMiddleware = auth();
try {
await authMiddleware(
{
// @ts-expect-error ts-migrate(2740) FIXME: Type '{ get: Mock<string, []>; }' is missing the f... Remove this comment to see the full error message
// @ts-expect-error mock request
request: {
get: jest.fn(() => `Bearer ${user.getJwtToken()}error`),
},
// @ts-expect-error ts-migrate(2322) FIXME: Type '{}' is not assignable to type 'DefaultState ... Remove this comment to see the full error message
state,
cache: {},
},
@@ -52,7 +51,7 @@ describe("Authentication middleware", () => {
});
describe("with API key", () => {
it("should authenticate user with valid API key", async () => {
const state = {};
const state = {} as DefaultState;
const user = await buildUser();
const authMiddleware = auth();
const key = await ApiKey.create({
@@ -60,31 +59,28 @@ describe("Authentication middleware", () => {
});
await authMiddleware(
{
// @ts-expect-error ts-migrate(2740) FIXME: Type '{ get: Mock<string, []>; }' is missing the f... Remove this comment to see the full error message
// @ts-expect-error mock request
request: {
get: jest.fn(() => `Bearer ${key.secret}`),
},
// @ts-expect-error ts-migrate(2322) FIXME: Type '{}' is not assignable to type 'DefaultState ... Remove this comment to see the full error message
state,
cache: {},
},
jest.fn()
);
// @ts-expect-error ts-migrate(2339) FIXME: Property 'user' does not exist on type '{}'.
expect(state.user.id).toEqual(user.id);
});
it("should return error with invalid API key", async () => {
const state = {};
const state = {} as DefaultState;
const authMiddleware = auth();
try {
await authMiddleware(
{
// @ts-expect-error ts-migrate(2740) FIXME: Type '{ get: Mock<string, []>; }' is missing the f... Remove this comment to see the full error message
// @ts-expect-error mock request
request: {
get: jest.fn(() => `Bearer ${randomstring.generate(38)}`),
},
// @ts-expect-error ts-migrate(2322) FIXME: Type '{}' is not assignable to type 'DefaultState ... Remove this comment to see the full error message
state,
cache: {},
},
@@ -97,17 +93,16 @@ describe("Authentication middleware", () => {
});
it("should return error message if no auth token is available", async () => {
const state = {};
const state = {} as DefaultState;
const authMiddleware = auth();
try {
await authMiddleware(
{
// @ts-expect-error ts-migrate(2740) FIXME: Type '{ get: Mock<string, []>; }' is missing the f... Remove this comment to see the full error message
// @ts-expect-error mock request
request: {
get: jest.fn(() => "error"),
},
// @ts-expect-error ts-migrate(2322) FIXME: Type '{}' is not assignable to type 'DefaultState ... Remove this comment to see the full error message
state,
cache: {},
},
@@ -121,54 +116,49 @@ describe("Authentication middleware", () => {
});
it("should allow passing auth token as a GET param", async () => {
const state = {};
const state = {} as DefaultState;
const user = await buildUser();
const authMiddleware = auth();
await authMiddleware(
{
request: {
// @ts-expect-error ts-migrate(2322) FIXME: Type 'Mock<null, []>' is not assignable to type '(... Remove this comment to see the full error message
// @ts-expect-error mock request
get: jest.fn(() => null),
query: {
token: user.getJwtToken(),
},
},
body: {},
// @ts-expect-error ts-migrate(2322) FIXME: Type '{}' is not assignable to type 'DefaultState ... Remove this comment to see the full error message
state,
cache: {},
},
jest.fn()
);
// @ts-expect-error ts-migrate(2339) FIXME: Property 'user' does not exist on type '{}'.
expect(state.user.id).toEqual(user.id);
});
it("should allow passing auth token in body params", async () => {
const state = {};
const state = {} as DefaultState;
const user = await buildUser();
const authMiddleware = auth();
await authMiddleware(
{
request: {
// @ts-expect-error ts-migrate(2322) FIXME: Type 'Mock<null, []>' is not assignable to type '(... Remove this comment to see the full error message
// @ts-expect-error mock request
get: jest.fn(() => null),
},
body: {
token: user.getJwtToken(),
},
// @ts-expect-error ts-migrate(2322) FIXME: Type '{}' is not assignable to type 'DefaultState ... Remove this comment to see the full error message
},
state,
cache: {},
},
jest.fn()
);
// @ts-expect-error ts-migrate(2339) FIXME: Property 'user' does not exist on type '{}'.
expect(state.user.id).toEqual(user.id);
});
it("should return an error for suspended users", async () => {
const state = {};
const state = {} as DefaultState;
const admin = await buildUser();
const user = await buildUser({
suspendedAt: new Date(),
@@ -180,11 +170,10 @@ describe("Authentication middleware", () => {
try {
await authMiddleware(
{
// @ts-expect-error ts-migrate(2740) FIXME: Type '{ get: Mock<string, []>; }' is missing the f... Remove this comment to see the full error message
// @ts-expect-error mock request
request: {
get: jest.fn(() => `Bearer ${user.getJwtToken()}`),
},
// @ts-expect-error ts-migrate(2322) FIXME: Type '{}' is not assignable to type 'DefaultState ... Remove this comment to see the full error message
state,
cache: {},
},
@@ -201,7 +190,7 @@ describe("Authentication middleware", () => {
});
it("should return an error for deleted team", async () => {
const state = {};
const state = {} as DefaultState;
const team = await buildTeam();
const user = await buildUser({
teamId: team.id,
@@ -213,11 +202,10 @@ describe("Authentication middleware", () => {
try {
await authMiddleware(
{
// @ts-expect-error ts-migrate(2740) FIXME: Type '{ get: Mock<string, []>; }' is missing the f... Remove this comment to see the full error message
// @ts-expect-error mock request
request: {
get: jest.fn(() => `Bearer ${user.getJwtToken()}`),
},
// @ts-expect-error ts-migrate(2322) FIXME: Type '{}' is not assignable to type 'DefaultState ... Remove this comment to see the full error message
state,
cache: {},
},

View File

@@ -43,13 +43,12 @@ export default function auth(options: AuthenticationOptions = {}) {
);
}
} else if (
ctx.body &&
typeof ctx.body === "object" &&
"token" in ctx.body
ctx.request.body &&
typeof ctx.request.body === "object" &&
"token" in ctx.request.body
) {
// @ts-expect-error ts-migrate(2571) FIXME: Object is of type 'unknown'.
token = ctx.body.token;
} else if (ctx.request.query.token) {
token = ctx.request.body.token;
} else if (ctx.request.query?.token) {
token = ctx.request.query.token;
} else {
token = ctx.cookies.get("accessToken");

View File

@@ -1,15 +0,0 @@
import { Context, Next } from "koa";
import queryString from "query-string";
export default function methodOverride() {
return async function methodOverrideMiddleware(ctx: Context, next: Next) {
// TODO: Need to remove this use of ctx.body to enable proper typing of requests
if (ctx.method === "POST") {
ctx.body = ctx.request.body;
} else if (ctx.method === "GET") {
ctx.body = queryString.parse(ctx.querystring);
}
return next();
};
}

View File

@@ -13,7 +13,7 @@ allow(User, "share", Team, (user, team) => {
allow(User, "createTeam", Team, () => {
if (env.DEPLOYMENT !== "hosted") {
throw "createTeam only available on cloud";
throw new Error("createTeam only available on cloud");
}
});

View File

@@ -9,7 +9,7 @@ import pagination from "./middlewares/pagination";
const router = new Router();
router.post("apiKeys.create", auth({ member: true }), async (ctx) => {
const { name } = ctx.body;
const { name } = ctx.request.body;
assertPresent(name, "name is required");
const { user } = ctx.state;
@@ -58,7 +58,7 @@ router.post(
);
router.post("apiKeys.delete", auth({ member: true }), async (ctx) => {
const { id } = ctx.body;
const { id } = ctx.request.body;
assertUuid(id, "id is required");
const { user } = ctx.state;
const key = await ApiKey.findByPk(id);

View File

@@ -19,13 +19,13 @@ const router = new Router();
const AWS_S3_ACL = process.env.AWS_S3_ACL || "private";
router.post("attachments.create", auth(), async (ctx) => {
const isPublic = ctx.body.public;
const isPublic = ctx.request.body.public;
const {
name,
documentId,
contentType = "application/octet-stream",
size,
} = ctx.body;
} = ctx.request.body;
assertPresent(name, "name is required");
assertPresent(size, "size is required");
@@ -118,7 +118,7 @@ router.post("attachments.create", auth(), async (ctx) => {
});
router.post("attachments.delete", auth(), async (ctx) => {
const { id } = ctx.body;
const { id } = ctx.request.body;
assertUuid(id, "id is required");
const { user } = ctx.state;
const attachment = await Attachment.findByPk(id, {
@@ -147,7 +147,7 @@ router.post("attachments.delete", auth(), async (ctx) => {
});
const handleAttachmentsRedirect = async (ctx: ContextWithState) => {
const { id } = ctx.body as { id?: string };
const { id } = (ctx.request.body ?? ctx.request.query) as { id?: string };
assertUuid(id, "id is required");
const { user } = ctx.state;

View File

@@ -12,7 +12,7 @@ import allAuthenticationProviders from "../auth/providers";
const router = new Router();
router.post("authenticationProviders.info", auth(), async (ctx) => {
const { id } = ctx.body;
const { id } = ctx.request.body;
assertUuid(id, "id is required");
const { user } = ctx.state;
const authenticationProvider = await AuthenticationProvider.findByPk(id);
@@ -25,7 +25,7 @@ router.post("authenticationProviders.info", auth(), async (ctx) => {
});
router.post("authenticationProviders.update", auth(), async (ctx) => {
const { id, isEnabled } = ctx.body;
const { id, isEnabled } = ctx.request.body;
assertUuid(id, "id is required");
assertPresent(isEnabled, "isEnabled is required");
const { user } = ctx.state;

View File

@@ -61,8 +61,8 @@ router.post("collections.create", auth(), async (ctx) => {
sharing,
icon,
sort = Collection.DEFAULT_SORT,
} = ctx.body;
let { index } = ctx.body;
} = ctx.request.body;
let { index } = ctx.request.body;
assertPresent(name, "name is required");
if (color) {
@@ -131,7 +131,7 @@ router.post("collections.create", auth(), async (ctx) => {
});
router.post("collections.info", auth(), async (ctx) => {
const { id } = ctx.body;
const { id } = ctx.request.body;
assertPresent(id, "id is required");
const { user } = ctx.state;
const collection = await Collection.scope({
@@ -151,7 +151,10 @@ router.post(
auth(),
rateLimiter(RateLimiterStrategy.TenPerHour),
async (ctx) => {
const { attachmentId, format = FileOperationFormat.MarkdownZip } = ctx.body;
const {
attachmentId,
format = FileOperationFormat.MarkdownZip,
} = ctx.request.body;
assertUuid(attachmentId, "attachmentId is required");
const { user } = ctx.state;
@@ -201,7 +204,11 @@ router.post(
);
router.post("collections.add_group", auth(), async (ctx) => {
const { id, groupId, permission = CollectionPermission.ReadWrite } = ctx.body;
const {
id,
groupId,
permission = CollectionPermission.ReadWrite,
} = ctx.request.body;
assertUuid(id, "id is required");
assertUuid(groupId, "groupId is required");
assertCollectionPermission(permission);
@@ -255,7 +262,7 @@ router.post("collections.add_group", auth(), async (ctx) => {
});
router.post("collections.remove_group", auth(), async (ctx) => {
const { id, groupId } = ctx.body;
const { id, groupId } = ctx.request.body;
assertUuid(id, "id is required");
assertUuid(groupId, "groupId is required");
@@ -290,7 +297,7 @@ router.post(
auth(),
pagination(),
async (ctx) => {
const { id, query, permission } = ctx.body;
const { id, query, permission } = ctx.request.body;
assertUuid(id, "id is required");
const { user } = ctx.state;
@@ -351,7 +358,7 @@ router.post(
);
router.post("collections.add_user", auth(), async (ctx) => {
const { id, userId, permission } = ctx.body;
const { id, userId, permission } = ctx.request.body;
assertUuid(id, "id is required");
assertUuid(userId, "userId is required");
@@ -411,7 +418,7 @@ router.post("collections.add_user", auth(), async (ctx) => {
});
router.post("collections.remove_user", auth(), async (ctx) => {
const { id, userId } = ctx.body;
const { id, userId } = ctx.request.body;
assertUuid(id, "id is required");
assertUuid(userId, "userId is required");
@@ -442,7 +449,7 @@ router.post("collections.remove_user", auth(), async (ctx) => {
});
router.post("collections.memberships", auth(), pagination(), async (ctx) => {
const { id, query, permission } = ctx.body;
const { id, query, permission } = ctx.request.body;
assertUuid(id, "id is required");
const { user } = ctx.state;
@@ -505,7 +512,7 @@ router.post(
auth(),
rateLimiter(RateLimiterStrategy.TenPerHour),
async (ctx) => {
const { id } = ctx.body;
const { id } = ctx.request.body;
assertUuid(id, "id is required");
const { user } = ctx.state;
const team = await Team.findByPk(user.teamId);
@@ -572,7 +579,7 @@ router.post("collections.update", auth(), async (ctx) => {
color,
sort,
sharing,
} = ctx.body;
} = ctx.request.body;
if (color) {
assertHexColor(color, "Invalid hex value (please use format #FFFFFF)");
@@ -724,7 +731,7 @@ router.post("collections.list", auth(), pagination(), async (ctx) => {
});
router.post("collections.delete", auth(), async (ctx) => {
const { id } = ctx.body;
const { id } = ctx.request.body;
const { user } = ctx.state;
assertUuid(id, "id is required");
@@ -768,8 +775,8 @@ router.post("collections.delete", auth(), async (ctx) => {
});
router.post("collections.move", auth(), async (ctx) => {
const id = ctx.body.id;
let index = ctx.body.index;
const id = ctx.request.body.id;
let index = ctx.request.body.index;
assertPresent(index, "index is required");
assertIndexCharacters(index);
assertUuid(id, "id must be a uuid");

View File

@@ -12,10 +12,16 @@ import InviteReminderTask from "@server/queues/tasks/InviteReminderTask";
const router = new Router();
const cronHandler = async (ctx: Context) => {
const { token, limit = 500 } = ctx.body as { token?: string; limit: number };
const { token, limit = 500 } = (ctx.request.body ?? ctx.request.query) as {
token?: string;
limit: number;
};
if (!token || typeof token !== "string") {
throw AuthenticationError("Token is required");
}
if (
!token ||
token.length !== env.UTILS_SECRET.length ||
!crypto.timingSafeEqual(
Buffer.from(env.UTILS_SECRET),

View File

@@ -20,9 +20,9 @@ function dev() {
}
router.post("developer.create_test_users", dev(), auth(), async (ctx) => {
const { count = 10 } = ctx.body;
const { count = 10 } = ctx.request.body;
const { user } = ctx.state;
const invites = Array(count)
const invites = Array(Math.min(count, 100))
.fill(0)
.map(() => {
const rando = randomstring.generate(10);

View File

@@ -51,12 +51,13 @@ import pagination from "./middlewares/pagination";
const router = new Router();
router.post("documents.list", auth(), pagination(), async (ctx) => {
let { sort = "updatedAt" } = ctx.body;
const { template, backlinkDocumentId, parentDocumentId } = ctx.body;
let { sort = "updatedAt" } = ctx.request.body;
const { template, backlinkDocumentId, parentDocumentId } = ctx.request.body;
// collection and user are here for backwards compatibility
const collectionId = ctx.body.collectionId || ctx.body.collection;
const createdById = ctx.body.userId || ctx.body.user;
let direction = ctx.body.direction;
const collectionId =
ctx.request.body.collectionId || ctx.request.body.collection;
const createdById = ctx.request.body.userId || ctx.request.body.user;
let direction = ctx.request.body.direction;
if (direction !== "ASC") {
direction = "DESC";
}
@@ -171,10 +172,10 @@ router.post(
auth({ member: true }),
pagination(),
async (ctx) => {
const { sort = "updatedAt" } = ctx.body;
const { sort = "updatedAt" } = ctx.request.body;
assertSort(sort, Document);
let direction = ctx.body.direction;
let direction = ctx.request.body.direction;
if (direction !== "ASC") {
direction = "DESC";
}
@@ -220,10 +221,10 @@ router.post(
auth({ member: true }),
pagination(),
async (ctx) => {
const { sort = "deletedAt" } = ctx.body;
const { sort = "deletedAt" } = ctx.request.body;
assertSort(sort, Document);
let direction = ctx.body.direction;
let direction = ctx.request.body.direction;
if (direction !== "ASC") {
direction = "DESC";
}
@@ -279,8 +280,8 @@ router.post(
);
router.post("documents.viewed", auth(), pagination(), async (ctx) => {
let { direction } = ctx.body;
const { sort = "updatedAt" } = ctx.body;
let { direction } = ctx.request.body;
const { sort = "updatedAt" } = ctx.request.body;
assertSort(sort, Document);
if (direction !== "ASC") {
@@ -332,8 +333,8 @@ router.post("documents.viewed", auth(), pagination(), async (ctx) => {
});
router.post("documents.drafts", auth(), pagination(), async (ctx) => {
let { direction } = ctx.body;
const { collectionId, dateFilter, sort = "updatedAt" } = ctx.body;
let { direction } = ctx.request.body;
const { collectionId, dateFilter, sort = "updatedAt" } = ctx.request.body;
assertSort(sort, Document);
if (direction !== "ASC") {
@@ -403,7 +404,7 @@ router.post(
optional: true,
}),
async (ctx) => {
const { id, shareId, apiVersion } = ctx.body;
const { id, shareId, apiVersion } = ctx.request.body;
assertPresent(id || shareId, "id or shareId is required");
const { user } = ctx.state;
const { document, share, collection } = await documentLoader({
@@ -441,7 +442,7 @@ router.post(
optional: true,
}),
async (ctx) => {
const { id, shareId } = ctx.body;
const { id, shareId } = ctx.request.body;
assertPresent(id || shareId, "id or shareId is required");
const { user } = ctx.state;
@@ -488,7 +489,7 @@ router.post(
);
router.post("documents.restore", auth({ member: true }), async (ctx) => {
const { id, collectionId, revisionId } = ctx.body;
const { id, collectionId, revisionId } = ctx.request.body;
assertPresent(id, "id is required");
const { user } = ctx.state;
const document = await Document.findByPk(id, {
@@ -585,7 +586,7 @@ router.post("documents.restore", auth({ member: true }), async (ctx) => {
});
router.post("documents.search_titles", auth(), pagination(), async (ctx) => {
const { query } = ctx.body;
const { query } = ctx.request.body;
const { offset, limit } = ctx.state.pagination;
const { user } = ctx.state;
@@ -651,12 +652,18 @@ router.post(
userId,
dateFilter,
shareId,
} = ctx.body;
} = ctx.request.body;
assertNotEmpty(query, "query is required");
const { offset, limit } = ctx.state.pagination;
const snippetMinWords = parseInt(ctx.body.snippetMinWords || 20, 10);
const snippetMaxWords = parseInt(ctx.body.snippetMaxWords || 30, 10);
const snippetMinWords = parseInt(
ctx.request.body.snippetMinWords || 20,
10
);
const snippetMaxWords = parseInt(
ctx.request.body.snippetMaxWords || 30,
10
);
// this typing is a bit ugly, would be better to use a type like ContextWithState
// but that doesn't adequately handle cases when auth is optional
@@ -765,7 +772,7 @@ router.post(
);
router.post("documents.templatize", auth({ member: true }), async (ctx) => {
const { id } = ctx.body;
const { id } = ctx.request.body;
assertPresent(id, "id is required");
const { user } = ctx.state;
@@ -821,7 +828,7 @@ router.post("documents.update", auth(), async (ctx) => {
lastRevision,
templateId,
append,
} = ctx.body;
} = ctx.request.body;
const editorVersion = ctx.headers["x-editor-version"] as string | undefined;
assertPresent(id, "id is required");
if (append) {
@@ -872,7 +879,7 @@ router.post("documents.update", auth(), async (ctx) => {
});
router.post("documents.move", auth(), async (ctx) => {
const { id, collectionId, parentDocumentId, index } = ctx.body;
const { id, collectionId, parentDocumentId, index } = ctx.request.body;
assertUuid(id, "id must be a uuid");
assertUuid(collectionId, "collectionId must be a uuid");
@@ -938,7 +945,7 @@ router.post("documents.move", auth(), async (ctx) => {
});
router.post("documents.archive", auth(), async (ctx) => {
const { id } = ctx.body;
const { id } = ctx.request.body;
assertPresent(id, "id is required");
const { user } = ctx.state;
@@ -967,7 +974,7 @@ router.post("documents.archive", auth(), async (ctx) => {
});
router.post("documents.delete", auth(), async (ctx) => {
const { id, permanent } = ctx.body;
const { id, permanent } = ctx.request.body;
assertPresent(id, "id is required");
const { user } = ctx.state;
@@ -1028,7 +1035,7 @@ router.post("documents.delete", auth(), async (ctx) => {
});
router.post("documents.unpublish", auth(), async (ctx) => {
const { id } = ctx.body;
const { id } = ctx.request.body;
assertPresent(id, "id is required");
const { user } = ctx.state;
@@ -1062,7 +1069,7 @@ router.post("documents.unpublish", auth(), async (ctx) => {
});
router.post("documents.import", auth(), async (ctx) => {
const { publish, collectionId, parentDocumentId, index } = ctx.body;
const { publish, collectionId, parentDocumentId, index } = ctx.request.body;
if (!ctx.is("multipart/form-data")) {
throw InvalidRequestError("Request type must be multipart/form-data");
@@ -1160,7 +1167,7 @@ router.post("documents.create", auth(), async (ctx) => {
templateId,
template,
index,
} = ctx.body;
} = ctx.request.body;
const editorVersion = ctx.headers["x-editor-version"] as string | undefined;
assertUuid(collectionId, "collectionId must be an uuid");

View File

@@ -11,7 +11,7 @@ const router = new Router();
router.post("events.list", auth(), pagination(), async (ctx) => {
const { user } = ctx.state;
let { direction } = ctx.body;
let { direction } = ctx.request.body;
const {
sort = "createdAt",
actorId,
@@ -19,7 +19,7 @@ router.post("events.list", auth(), pagination(), async (ctx) => {
collectionId,
name,
auditLog = false,
} = ctx.body;
} = ctx.request.body;
if (direction !== "ASC") {
direction = "DESC";
}

View File

@@ -15,7 +15,7 @@ import pagination from "./middlewares/pagination";
const router = new Router();
router.post("fileOperations.info", auth({ admin: true }), async (ctx) => {
const { id } = ctx.body;
const { id } = ctx.request.body;
assertUuid(id, "id is required");
const { user } = ctx.state;
const fileOperation = await FileOperation.findByPk(id, {
@@ -34,8 +34,8 @@ router.post(
auth({ admin: true }),
pagination(),
async (ctx) => {
let { direction } = ctx.body;
const { sort = "createdAt", type } = ctx.body;
let { direction } = ctx.request.body;
const { sort = "createdAt", type } = ctx.request.body;
assertIn(type, Object.values(FileOperationType));
assertSort(sort, FileOperation);
@@ -70,7 +70,7 @@ router.post(
);
const handleFileOperationsRedirect = async (ctx: ContextWithState) => {
const { id } = ctx.body as { id?: string };
const { id } = (ctx.request.body ?? ctx.request.query) as { id?: string };
assertUuid(id, "id is required");
const { user } = ctx.state;
@@ -99,7 +99,7 @@ router.post(
);
router.post("fileOperations.delete", auth({ admin: true }), async (ctx) => {
const { id } = ctx.body;
const { id } = ctx.request.body;
assertUuid(id, "id is required");
const { user } = ctx.state;

View File

@@ -16,8 +16,8 @@ import pagination from "./middlewares/pagination";
const router = new Router();
router.post("groups.list", auth(), pagination(), async (ctx) => {
let { direction } = ctx.body;
const { sort = "updatedAt" } = ctx.body;
let { direction } = ctx.request.body;
const { sort = "updatedAt" } = ctx.request.body;
if (direction !== "ASC") {
direction = "DESC";
}
@@ -53,7 +53,7 @@ router.post("groups.list", auth(), pagination(), async (ctx) => {
});
router.post("groups.info", auth(), async (ctx) => {
const { id } = ctx.body;
const { id } = ctx.request.body;
assertUuid(id, "id is required");
const { user } = ctx.state;
@@ -67,7 +67,7 @@ router.post("groups.info", auth(), async (ctx) => {
});
router.post("groups.create", auth(), async (ctx) => {
const { name } = ctx.body;
const { name } = ctx.request.body;
assertPresent(name, "name is required");
const { user } = ctx.state;
@@ -99,7 +99,7 @@ router.post("groups.create", auth(), async (ctx) => {
});
router.post("groups.update", auth(), async (ctx) => {
const { id, name } = ctx.body;
const { id, name } = ctx.request.body;
assertPresent(name, "name is required");
assertUuid(id, "id is required");
@@ -130,7 +130,7 @@ router.post("groups.update", auth(), async (ctx) => {
});
router.post("groups.delete", auth(), async (ctx) => {
const { id } = ctx.body;
const { id } = ctx.request.body;
assertUuid(id, "id is required");
const { user } = ctx.state;
@@ -155,7 +155,7 @@ router.post("groups.delete", auth(), async (ctx) => {
});
router.post("groups.memberships", auth(), pagination(), async (ctx) => {
const { id, query } = ctx.body;
const { id, query } = ctx.request.body;
assertUuid(id, "id is required");
const { user } = ctx.state;
@@ -200,7 +200,7 @@ router.post("groups.memberships", auth(), pagination(), async (ctx) => {
});
router.post("groups.add_user", auth(), async (ctx) => {
const { id, userId } = ctx.body;
const { id, userId } = ctx.request.body;
assertUuid(id, "id is required");
assertUuid(userId, "userId is required");
@@ -260,7 +260,7 @@ router.post("groups.add_user", auth(), async (ctx) => {
});
router.post("groups.remove_user", auth(), async (ctx) => {
const { id, userId } = ctx.body;
const { id, userId } = ctx.request.body;
assertUuid(id, "id is required");
assertUuid(userId, "userId is required");

View File

@@ -4,7 +4,7 @@ import { buildDocument, buildIntegration } from "@server/test/factories";
import { seed, getTestServer } from "@server/test/support";
import * as Slack from "@server/utils/slack";
jest.mock("../../utils/slack", () => ({
jest.mock("@server/utils/slack", () => ({
post: jest.fn(),
}));

View File

@@ -40,9 +40,9 @@ function verifySlackToken(token: string) {
// triggered by a user posting a getoutline.com link in Slack
router.post("hooks.unfurl", async (ctx) => {
const { challenge, token, event } = ctx.body;
const { challenge, token, event } = ctx.request.body;
if (challenge) {
return (ctx.body = ctx.body.challenge);
return (ctx.body = ctx.request.body.challenge);
}
assertPresent(token, "token is required");
@@ -95,11 +95,15 @@ router.post("hooks.unfurl", async (ctx) => {
ts: event.message_ts,
unfurls,
});
ctx.body = {
success: true,
};
});
// triggered by interactions with actions, dialogs, message buttons in Slack
router.post("hooks.interactive", async (ctx) => {
const { payload } = ctx.body;
const { payload } = ctx.request.body;
assertPresent(payload, "payload is required");
const data = JSON.parse(payload);
@@ -137,7 +141,7 @@ router.post("hooks.interactive", async (ctx) => {
// triggered by the /outline command in Slack
router.post("hooks.slack", async (ctx) => {
const { token, team_id, user_id, text = "" } = ctx.body;
const { token, team_id, user_id, text = "" } = ctx.request.body;
assertPresent(token, "token is required");
assertPresent(team_id, "team_id is required");
assertPresent(user_id, "user_id is required");

View File

@@ -5,7 +5,6 @@ import userAgent, { UserAgentContext } from "koa-useragent";
import env from "@server/env";
import { NotFoundError } from "@server/errors";
import errorHandling from "@server/middlewares/errorHandling";
import methodOverride from "@server/middlewares/methodOverride";
import { defaultRateLimiter } from "@server/middlewares/rateLimiter";
import { AuthenticatedState } from "@server/types";
import apiKeys from "./apiKeys";
@@ -52,7 +51,6 @@ api.use(
})
);
api.use<BaseContext, UserAgentContext>(userAgent);
api.use(methodOverride());
api.use(apiWrapper());
api.use(editor());

View File

@@ -20,8 +20,8 @@ import pagination from "./middlewares/pagination";
const router = new Router();
router.post("integrations.list", auth(), pagination(), async (ctx) => {
let { direction } = ctx.body;
const { sort = "updatedAt" } = ctx.body;
let { direction } = ctx.request.body;
const { sort = "updatedAt" } = ctx.request.body;
if (direction !== "ASC") {
direction = "DESC";
}
@@ -44,7 +44,7 @@ router.post("integrations.list", auth(), pagination(), async (ctx) => {
});
router.post("integrations.create", auth({ admin: true }), async (ctx) => {
const { type, service, settings } = ctx.body;
const { type, service, settings } = ctx.request.body;
assertIn(type, Object.values(IntegrationType));
@@ -71,7 +71,7 @@ router.post("integrations.create", auth({ admin: true }), async (ctx) => {
});
router.post("integrations.update", auth({ admin: true }), async (ctx) => {
const { id, events = [], settings } = ctx.body;
const { id, events = [], settings } = ctx.request.body;
assertUuid(id, "id is required");
const { user } = ctx.state;
@@ -100,7 +100,7 @@ router.post("integrations.update", auth({ admin: true }), async (ctx) => {
});
router.post("integrations.delete", auth({ admin: true }), async (ctx) => {
const { id } = ctx.body;
const { id } = ctx.request.body;
assertUuid(id, "id is required");
const { user } = ctx.state;

View File

@@ -7,11 +7,10 @@ export default function apiWrapper() {
const ok = ctx.status < 400;
if (
typeof ctx.body !== "string" &&
typeof ctx.body === "object" &&
!(ctx.body instanceof stream.Readable)
) {
ctx.body = {
// @ts-expect-error ts-migrate(2698) FIXME: Spread types may only be created from object types... Remove this comment to see the full error message
...ctx.body,
status: ctx.status,
ok,

View File

@@ -10,7 +10,7 @@ import { assertPresent, assertUuid } from "@server/validation";
const router = new Router();
router.post("notificationSettings.create", auth(), async (ctx) => {
const { event } = ctx.body;
const { event } = ctx.request.body;
assertPresent(event, "event is required");
const { user } = ctx.state;
@@ -42,7 +42,7 @@ router.post("notificationSettings.list", auth(), async (ctx) => {
});
router.post("notificationSettings.delete", auth(), async (ctx) => {
const { id } = ctx.body;
const { id } = ctx.request.body;
assertUuid(id, "id is required");
const { user } = ctx.state;
@@ -57,7 +57,10 @@ router.post("notificationSettings.delete", auth(), async (ctx) => {
});
const handleUnsubscribe = async (ctx: ContextWithState) => {
const { id, token } = ctx.body as { id?: string; token?: string };
const { id, token } = (ctx.request.body ?? ctx.request.query) as {
id?: string;
token?: string;
};
assertUuid(id, "id is required");
assertPresent(token, "token is required");

View File

@@ -17,8 +17,8 @@ import pagination from "./middlewares/pagination";
const router = new Router();
router.post("pins.create", auth(), async (ctx) => {
const { documentId, collectionId } = ctx.body;
const { index } = ctx.body;
const { documentId, collectionId } = ctx.request.body;
const { index } = ctx.request.body;
assertUuid(documentId, "documentId is required");
const { user } = ctx.state;
@@ -56,7 +56,7 @@ router.post("pins.create", auth(), async (ctx) => {
});
router.post("pins.list", auth(), pagination(), async (ctx) => {
const { collectionId } = ctx.body;
const { collectionId } = ctx.request.body;
const { user } = ctx.state;
const [pins, collectionIds] = await Promise.all([
@@ -99,7 +99,7 @@ router.post("pins.list", auth(), pagination(), async (ctx) => {
});
router.post("pins.update", auth(), async (ctx) => {
const { id, index } = ctx.body;
const { id, index } = ctx.request.body;
assertUuid(id, "id is required");
assertIndexCharacters(index);
@@ -131,7 +131,7 @@ router.post("pins.update", auth(), async (ctx) => {
});
router.post("pins.delete", auth(), async (ctx) => {
const { id } = ctx.body;
const { id } = ctx.request.body;
assertUuid(id, "id is required");
const { user } = ctx.state;

View File

@@ -13,7 +13,7 @@ import pagination from "./middlewares/pagination";
const router = new Router();
router.post("revisions.info", auth(), async (ctx) => {
const { id } = ctx.body;
const { id } = ctx.request.body;
assertUuid(id, "id is required");
const { user } = ctx.state;
const revision = await Revision.findByPk(id, {
@@ -39,7 +39,7 @@ router.post("revisions.info", auth(), async (ctx) => {
});
router.post("revisions.diff", auth(), async (ctx) => {
const { id, compareToId } = ctx.body;
const { id, compareToId } = ctx.request.body;
assertUuid(id, "id is required");
const { user } = ctx.state;
@@ -93,8 +93,8 @@ router.post("revisions.diff", auth(), async (ctx) => {
});
router.post("revisions.list", auth(), pagination(), async (ctx) => {
let { direction } = ctx.body;
const { documentId, sort = "updatedAt" } = ctx.body;
let { direction } = ctx.request.body;
const { documentId, sort = "updatedAt" } = ctx.request.body;
if (direction !== "ASC") {
direction = "DESC";
}

View File

@@ -26,7 +26,7 @@ router.post("searches.list", auth(), pagination(), async (ctx) => {
});
router.post("searches.delete", auth(), async (ctx) => {
const { id, query } = ctx.body;
const { id, query } = ctx.request.body;
assertPresent(id || query, "id or query is required");
if (id) {
assertUuid(id, "id is must be a uuid");

View File

@@ -11,7 +11,7 @@ import pagination from "./middlewares/pagination";
const router = new Router();
router.post("shares.info", auth(), async (ctx) => {
const { id, documentId } = ctx.body;
const { id, documentId } = ctx.request.body;
assertPresent(id || documentId, "id or documentId is required");
if (id) {
assertUuid(id, "id is must be a uuid");
@@ -92,8 +92,8 @@ router.post("shares.info", auth(), async (ctx) => {
});
router.post("shares.list", auth(), pagination(), async (ctx) => {
let { direction } = ctx.body;
const { sort = "updatedAt" } = ctx.body;
let { direction } = ctx.request.body;
const { sort = "updatedAt" } = ctx.request.body;
if (direction !== "ASC") {
direction = "DESC";
}
@@ -162,7 +162,7 @@ router.post("shares.list", auth(), pagination(), async (ctx) => {
});
router.post("shares.update", auth(), async (ctx) => {
const { id, includeChildDocuments, published } = ctx.body;
const { id, includeChildDocuments, published } = ctx.request.body;
assertUuid(id, "id is required");
const { user } = ctx.state;
@@ -211,7 +211,7 @@ router.post("shares.update", auth(), async (ctx) => {
});
router.post("shares.create", auth(), async (ctx) => {
const { documentId } = ctx.body;
const { documentId } = ctx.request.body;
assertPresent(documentId, "documentId is required");
const { user } = ctx.state;
@@ -263,7 +263,7 @@ router.post("shares.create", auth(), async (ctx) => {
});
router.post("shares.revoke", auth(), async (ctx) => {
const { id } = ctx.body;
const { id } = ctx.request.body;
assertUuid(id, "id is required");
const { user } = ctx.state;

View File

@@ -19,8 +19,8 @@ import pagination from "./middlewares/pagination";
const router = new Router();
router.post("stars.create", auth(), async (ctx) => {
const { documentId, collectionId } = ctx.body;
const { index } = ctx.body;
const { documentId, collectionId } = ctx.request.body;
const { index } = ctx.request.body;
const { user } = ctx.state;
assertUuid(
@@ -116,7 +116,7 @@ router.post("stars.list", auth(), pagination(), async (ctx) => {
});
router.post("stars.update", auth(), async (ctx) => {
const { id, index } = ctx.body;
const { id, index } = ctx.request.body;
assertUuid(id, "id is required");
assertIndexCharacters(index);
@@ -139,7 +139,7 @@ router.post("stars.update", auth(), async (ctx) => {
});
router.post("stars.delete", auth(), async (ctx) => {
const { id } = ctx.body;
const { id } = ctx.request.body;
assertUuid(id, "id is required");
const { user } = ctx.state;

View File

@@ -13,7 +13,7 @@ const router = new Router();
router.post("subscriptions.list", auth(), pagination(), async (ctx) => {
const { user } = ctx.state;
const { documentId, event } = ctx.body;
const { documentId, event } = ctx.request.body;
assertUuid(documentId, "documentId is required");
@@ -46,7 +46,7 @@ router.post("subscriptions.list", auth(), pagination(), async (ctx) => {
router.post("subscriptions.info", auth(), async (ctx) => {
const { user } = ctx.state;
const { documentId, event } = ctx.body;
const { documentId, event } = ctx.request.body;
assertUuid(documentId, "documentId is required");
@@ -77,7 +77,7 @@ router.post("subscriptions.info", auth(), async (ctx) => {
router.post("subscriptions.create", auth(), async (ctx) => {
const { user } = ctx.state;
const { documentId, event } = ctx.body;
const { documentId, event } = ctx.request.body;
assertUuid(documentId, "documentId is required");
@@ -111,7 +111,7 @@ router.post("subscriptions.create", auth(), async (ctx) => {
router.post("subscriptions.delete", auth(), async (ctx) => {
const { user } = ctx.state;
const { id } = ctx.body;
const { id } = ctx.request.body;
assertUuid(id, "id is required");

View File

@@ -23,7 +23,7 @@ describe("teams.create", () => {
});
it("requires a cloud hosted deployment", async () => {
env.DEPLOYMENT = "self-hosted";
env.DEPLOYMENT = "";
const team = await buildTeam();
const user = await buildAdmin({ teamId: team.id });
const res = await server.post("/api/teams.create", {

View File

@@ -32,7 +32,7 @@ router.post(
inviteRequired,
allowedDomains,
preferences,
} = ctx.body;
} = ctx.request.body;
const { user } = ctx.state;
const team = await Team.findByPk(user.teamId, {
@@ -78,7 +78,7 @@ router.post(
rateLimiter(RateLimiterStrategy.FivePerHour),
async (ctx) => {
const { user } = ctx.state;
const { name } = ctx.body;
const { name } = ctx.request.body;
const existingTeam = await Team.scope(
"withAuthenticationProviders"

View File

@@ -37,8 +37,8 @@ const router = new Router();
const emailEnabled = !!(env.SMTP_HOST || env.ENVIRONMENT === "development");
router.post("users.list", auth(), pagination(), async (ctx) => {
let { direction } = ctx.body;
const { sort = "createdAt", query, filter, ids } = ctx.body;
let { direction } = ctx.request.body;
const { sort = "createdAt", query, filter, ids } = ctx.request.body;
if (direction !== "ASC") {
direction = "DESC";
}
@@ -162,7 +162,7 @@ router.post("users.count", auth(), async (ctx) => {
});
router.post("users.info", auth(), async (ctx) => {
const { id } = ctx.body;
const { id } = ctx.request.body;
const actor = ctx.state.user;
const user = id ? await User.findByPk(id) : actor;
authorize(actor, "read", user);
@@ -178,7 +178,7 @@ router.post("users.info", auth(), async (ctx) => {
router.post("users.update", auth(), async (ctx) => {
const { user } = ctx.state;
const { name, avatarUrl, language, preferences } = ctx.body;
const { name, avatarUrl, language, preferences } = ctx.request.body;
if (name) {
user.name = name;
}
@@ -216,7 +216,7 @@ router.post("users.update", auth(), async (ctx) => {
// Admin specific
router.post("users.promote", auth(), async (ctx) => {
const userId = ctx.body.id;
const userId = ctx.request.body.id;
const teamId = ctx.state.user.teamId;
const actor = ctx.state.user;
assertPresent(userId, "id is required");
@@ -245,8 +245,8 @@ router.post("users.promote", auth(), async (ctx) => {
});
router.post("users.demote", auth(), async (ctx) => {
const userId = ctx.body.id;
let { to } = ctx.body;
const userId = ctx.request.body.id;
let { to } = ctx.request.body;
const actor = ctx.state.user as User;
assertPresent(userId, "id is required");
@@ -274,7 +274,7 @@ router.post("users.demote", auth(), async (ctx) => {
});
router.post("users.suspend", auth(), async (ctx) => {
const userId = ctx.body.id;
const userId = ctx.request.body.id;
const actor = ctx.state.user;
assertPresent(userId, "id is required");
const user = await User.findByPk(userId, {
@@ -298,7 +298,7 @@ router.post("users.suspend", auth(), async (ctx) => {
});
router.post("users.activate", auth(), async (ctx) => {
const userId = ctx.body.id;
const userId = ctx.request.body.id;
const actor = ctx.state.user;
assertPresent(userId, "id is required");
const user = await User.findByPk(userId, {
@@ -326,7 +326,7 @@ router.post(
auth(),
rateLimiter(RateLimiterStrategy.TenPerHour),
async (ctx) => {
const { invites } = ctx.body;
const { invites } = ctx.request.body;
assertArray(invites, "invites must be an array");
const { user } = ctx.state;
const team = await Team.findByPk(user.teamId);
@@ -348,7 +348,7 @@ router.post(
);
router.post("users.resendInvite", auth(), async (ctx) => {
const { id } = ctx.body;
const { id } = ctx.request.body;
const actor = ctx.state.user;
await sequelize.transaction(async (transaction) => {
@@ -415,7 +415,7 @@ router.post(
auth(),
rateLimiter(RateLimiterStrategy.TenPerHour),
async (ctx) => {
const { id, code = "" } = ctx.body;
const { id, code = "" } = ctx.request.body;
let user: User;
if (id) {

View File

@@ -10,7 +10,7 @@ import { assertUuid } from "@server/validation";
const router = new Router();
router.post("views.list", auth(), async (ctx) => {
const { documentId, includeSuspended = false } = ctx.body;
const { documentId, includeSuspended = false } = ctx.request.body;
assertUuid(documentId, "documentId is required");
const { user } = ctx.state;
@@ -30,7 +30,7 @@ router.post(
auth(),
rateLimiter(RateLimiterStrategy.OneThousandPerHour),
async (ctx) => {
const { documentId } = ctx.body;
const { documentId } = ctx.request.body;
assertUuid(documentId, "documentId is required");
const { user } = ctx.state;

View File

@@ -84,7 +84,7 @@ router.post(
"webhookSubscriptions.delete",
auth({ admin: true }),
async (ctx) => {
const { id } = ctx.body;
const { id } = ctx.request.body;
assertUuid(id, "id is required");
const { user } = ctx.state;
const webhookSubscription = await WebhookSubscription.findByPk(id);
@@ -113,7 +113,7 @@ router.post(
"webhookSubscriptions.update",
auth({ admin: true }),
async (ctx) => {
const { id } = ctx.body;
const { id } = ctx.request.body;
assertUuid(id, "id is required");
const { user } = ctx.state;

View File

@@ -8,7 +8,6 @@ import WelcomeEmail from "@server/emails/templates/WelcomeEmail";
import env from "@server/env";
import { AuthorizationError } from "@server/errors";
import errorHandling from "@server/middlewares/errorHandling";
import methodOverride from "@server/middlewares/methodOverride";
import { rateLimiter } from "@server/middlewares/rateLimiter";
import { User, Team } from "@server/models";
import { signIn } from "@server/utils/authentication";
@@ -22,14 +21,12 @@ export const config = {
enabled: true,
};
router.use(methodOverride());
router.post(
"email",
errorHandling(),
rateLimiter(RateLimiterStrategy.TenPerHour),
async (ctx) => {
const { email } = ctx.body;
const { email } = ctx.request.body;
assertEmail(email, "email is required");
const domain = parseDomain(ctx.request.hostname);