diff --git a/api.planx.uk/docs/index.ts b/api.planx.uk/docs/index.ts index 11d4f9d5e1..f12939a0b6 100644 --- a/api.planx.uk/docs/index.ts +++ b/api.planx.uk/docs/index.ts @@ -8,6 +8,13 @@ const securitySchemes = { scheme: "bearer", bearerFormat: "JWT", }, + fileAPIKeyAuth: { + type: "apiKey", + in: "header", + name: "api-key", + description: + "API key granted to third-party integration partners to access files uploaded by users as part of their application", + }, hasuraAuth: { type: "apiKey", in: "header", diff --git a/api.planx.uk/modules/file/controller.ts b/api.planx.uk/modules/file/controller.ts index ba77a1a26e..0e718e133e 100644 --- a/api.planx.uk/modules/file/controller.ts +++ b/api.planx.uk/modules/file/controller.ts @@ -77,7 +77,7 @@ export type DownloadController = ValidatedRequestHandler< >; export const publicDownloadController: DownloadController = async ( - req, + _req, res, next, ) => { @@ -99,7 +99,7 @@ export const publicDownloadController: DownloadController = async ( }; export const privateDownloadController: DownloadController = async ( - req, + _req, res, next, ) => { diff --git a/api.planx.uk/modules/file/docs.yaml b/api.planx.uk/modules/file/docs.yaml index e69de29bb2..0315cf0424 100644 --- a/api.planx.uk/modules/file/docs.yaml +++ b/api.planx.uk/modules/file/docs.yaml @@ -0,0 +1,99 @@ +openapi: 3.1.0 +info: + title: Planâś• API + version: 0.1.0 +tags: + - name: file + description: Endpoints for uploading and downloading files +components: + parameters: + fileKey: + in: path + name: fileKey + type: string + required: true + fileName: + in: path + name: fileName + type: string + required: true + schemas: + UploadFile: + type: object + properties: + filename: + type: string + required: true + file: + type: string + format: binary + responses: + UploadFile: + type: object + properties: + fileType: + oneOf: + - type: string + - type: "null" + fileUrl: + type: string + DownloadFile: + description: Successful response + content: + application/octet-stream: + schema: + type: string + format: binary +paths: + /file/private/upload: + post: + tags: ["file"] + security: + - bearerAuth: [] + requestBody: + content: + multipart/form-data: + schema: + $ref: "#/components/schemas/UploadFile" + responses: + "200": + $ref: "#/components/responses/UploadFile" + "500": + $ref: "#/components/responses/ErrorMessage" + /file/public/upload: + post: + tags: ["file"] + requestBody: + content: + multipart/form-data: + schema: + $ref: "#/components/schemas/UploadFile" + responses: + "200": + $ref: "#/components/responses/UploadFile" + "500": + $ref: "#/components/responses/ErrorMessage" + /file/public/{fileKey}/{fileName}: + get: + tags: ["file"] + parameters: + - $ref: "#/components/parameters/fileKey" + - $ref: "#/components/parameters/fileName" + responses: + "200": + $ref: "#/components/responses/DownloadFile" + "500": + $ref: "#/components/responses/ErrorMessage" + /file/private/{fileKey}/{fileName}: + get: + tags: ["file"] + parameters: + - $ref: "#/components/parameters/fileKey" + - $ref: "#/components/parameters/fileName" + security: + - fileAPIKeyAuth: [] + responses: + "200": + $ref: "#/components/responses/DownloadFile" + "500": + $ref: "#/components/responses/ErrorMessage" diff --git a/api.planx.uk/modules/file/file.test.ts b/api.planx.uk/modules/file/file.test.ts index 084ba3ad33..5364319cb8 100644 --- a/api.planx.uk/modules/file/file.test.ts +++ b/api.planx.uk/modules/file/file.test.ts @@ -2,6 +2,7 @@ import supertest from "supertest"; import app from "../../server"; import { deleteFilesByURL } from "./service/deleteFile"; +import { authHeader } from "../../tests/mockJWT"; let mockPutObject: jest.Mocked<() => void>; let mockGetObject: jest.Mocked<() => void>; @@ -42,10 +43,23 @@ describe("File upload", () => { describe("Private", () => { const ENDPOINT = "/file/private/upload"; + const auth = authHeader({ role: "teamEditor" }); + + it("returns an error if authorization headers are not set", async () => { + await supertest(app) + .post("/flows/1/move/new-team") + .expect(401) + .then((res) => { + expect(res.body).toEqual({ + error: "No authorization token was found", + }); + }); + }); it("should not upload without filename", async () => { await supertest(app) .post(ENDPOINT) + .set(auth) .field("filename", "") .attach("file", Buffer.from("some data"), "some_file.txt") .expect(400) @@ -59,6 +73,7 @@ describe("File upload", () => { it("should not upload without file", async () => { await supertest(app) .post(ENDPOINT) + .set(auth) .field("filename", "some filename") .expect(500) .then((res) => { @@ -70,6 +85,7 @@ describe("File upload", () => { it("should upload file", async () => { await supertest(app) .post(ENDPOINT) + .set(auth) .field("filename", "some_file.txt") .attach("file", Buffer.from("some data"), "some_file.txt") .then((res) => { @@ -91,6 +107,7 @@ describe("File upload", () => { await supertest(app) .post("/file/private/upload") + .set(auth) .field("filename", "some_file.txt") .attach("file", Buffer.from("some data"), "some_file.txt") .expect(500) diff --git a/api.planx.uk/modules/file/routes.ts b/api.planx.uk/modules/file/routes.ts index 1a0e26ce1d..239687f474 100644 --- a/api.planx.uk/modules/file/routes.ts +++ b/api.planx.uk/modules/file/routes.ts @@ -1,7 +1,7 @@ import { Router } from "express"; import multer from "multer"; -import { useFilePermission } from "../auth/middleware"; +import { useFilePermission, useTeamEditorAuth } from "../auth/middleware"; import { downloadFileSchema, privateDownloadController, @@ -15,17 +15,18 @@ import { validate } from "../../shared/middleware/validate"; const router = Router(); router.post( - "/private/upload", + "/public/upload", multer().single("file"), validate(uploadFileSchema), - privateUploadController, + publicUploadController, ); router.post( - "/public/upload", + "/private/upload", multer().single("file"), + useTeamEditorAuth, validate(uploadFileSchema), - publicUploadController, + privateUploadController, ); router.get(