Skip to content

Commit

Permalink
Add authorization to the API (#48)
Browse files Browse the repository at this point in the history
  • Loading branch information
benmartin-coforma authored Nov 6, 2024
1 parent 95e46d7 commit bdfdbf6
Show file tree
Hide file tree
Showing 18 changed files with 558 additions and 358 deletions.
24 changes: 19 additions & 5 deletions services/app-api/handlers/reports/create.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
import { StatusCodes } from "../../libs/response-lib";
import { proxyEvent } from "../../testing/proxyEvent";
import { APIGatewayProxyEvent } from "../../types/types";
import { APIGatewayProxyEvent, UserRoles } from "../../types/types";
import { canWriteState } from "../../utils/authorization";
import { createReport } from "./create";

jest.mock("../../utils/authentication", () => ({
authenticatedUser: jest.fn().mockResolvedValue({
role: UserRoles.STATE_USER,
state: "PA",
}),
}));

jest.mock("../../utils/authorization", () => ({
isAuthenticated: jest.fn().mockResolvedValue(true),
canWriteState: jest.fn().mockReturnValue(true),
}));

jest.mock("./buildReport", () => ({
Expand All @@ -27,22 +35,28 @@ describe("Test create report handler", () => {
...proxyEvent,
pathParameters: {},
} as APIGatewayProxyEvent;
const res = await createReport(badTestEvent, null);
const res = await createReport(badTestEvent);
expect(res.statusCode).toBe(StatusCodes.BadRequest);
});

it("should return 403 if user is not authorized", async () => {
(canWriteState as jest.Mock).mockReturnValueOnce(false);
const response = await createReport(testEvent);
expect(response.statusCode).toBe(StatusCodes.Forbidden);
});

test("Test missing body", async () => {
const emptyBodyEvent = {
...proxyEvent,
pathParameters: { reportType: "QM", state: "PA" },
body: null,
} as APIGatewayProxyEvent;
const res = await createReport(emptyBodyEvent, null);
const res = await createReport(emptyBodyEvent);
expect(res.statusCode).toBe(StatusCodes.BadRequest);
});

test("Test Successful create", async () => {
const res = await createReport(testEvent, null);
const res = await createReport(testEvent);

expect(res.statusCode).toBe(StatusCodes.Ok);
});
Expand Down
36 changes: 20 additions & 16 deletions services/app-api/handlers/reports/create.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
import handler from "../../libs/handler-lib";
import { handler } from "../../libs/handler-lib";
import { parseReportTypeAndState } from "../../libs/param-lib";
import { badRequest, ok } from "../../libs/response-lib";
import { badRequest, forbidden, ok } from "../../libs/response-lib";
import { canWriteState } from "../../utils/authorization";
import { error } from "../../utils/constants";
import { buildReport } from "./buildReport";

export const createReport = handler(async (event) => {
const { allParamsValid, reportType, state } = parseReportTypeAndState(event);
if (!allParamsValid) {
return badRequest("Invalid path parameters");
}
export const createReport = handler(
parseReportTypeAndState,
async (request) => {
const { reportType, state } = request.parameters;
const user = request.user;

// TODO: Auth
const user = "";
if (!canWriteState(user, state)) {
return forbidden(error.UNAUTHORIZED);
}

if (!event?.body) {
return badRequest("Invalid request");
}
// const options = JSON.parse(event.body);
if (!request?.body) {
return badRequest("Invalid request");
}
// const options = JSON.parse(event.body);

const report = await buildReport(reportType, state, [], user);
const report = await buildReport(reportType, state, [], user.full_name);

return ok(report);
});
return ok(report);
}
);
32 changes: 26 additions & 6 deletions services/app-api/handlers/reports/get.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
import { StatusCodes } from "../../libs/response-lib";
import { proxyEvent } from "../../testing/proxyEvent";
import { APIGatewayProxyEvent } from "../../types/types";
import { APIGatewayProxyEvent, UserRoles } from "../../types/types";
import { canReadState } from "../../utils/authorization";
import { getReport, getReportsForState } from "./get";

jest.mock("../../utils/authentication", () => ({
authenticatedUser: jest.fn().mockResolvedValue({
role: UserRoles.STATE_USER,
state: "PA",
}),
}));

jest.mock("../../utils/authorization", () => ({
isAuthenticated: jest.fn().mockResolvedValue(true),
canReadState: jest.fn().mockReturnValue(true),
}));

jest.mock("../../storage/reports", () => ({
Expand All @@ -29,12 +37,18 @@ describe("Test get report handler", () => {
...proxyEvent,
headers: { "cognito-identity-id": "test" },
};
const res = await getReport(badTestEvent, null);
const res = await getReport(badTestEvent);
expect(res.statusCode).toBe(StatusCodes.BadRequest);
});

it("should return 403 if user is not authorized", async () => {
(canReadState as jest.Mock).mockReturnValueOnce(false);
const response = await getReport(testEvent);
expect(response.statusCode).toBe(StatusCodes.Forbidden);
});

test("Test Successful get", async () => {
const res = await getReport(testEvent, null);
const res = await getReport(testEvent);

expect(res.statusCode).toBe(StatusCodes.Ok);
});
Expand All @@ -46,12 +60,18 @@ describe("Test get report handler", () => {
...proxyEvent,
headers: { "cognito-identity-id": "test" },
};
const res = await getReportsForState(badTestEvent, null);
const res = await getReportsForState(badTestEvent);
expect(res.statusCode).toBe(StatusCodes.BadRequest);
});

it("should return 403 if user is not authorized", async () => {
(canReadState as jest.Mock).mockReturnValueOnce(false);
const response = await getReportsForState(testEvent);
expect(response.statusCode).toBe(StatusCodes.Forbidden);
});

test("Test Successful get", async () => {
const res = await getReportsForState(testEvent, null);
const res = await getReportsForState(testEvent);

expect(res.statusCode).toBe(StatusCodes.Ok);
});
Expand Down
40 changes: 22 additions & 18 deletions services/app-api/handlers/reports/get.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,41 @@
import handler from "../../libs/handler-lib";
import { handler } from "../../libs/handler-lib";
import {
parseReportParameters,
parseReportTypeAndState,
} from "../../libs/param-lib";
import { badRequest, ok } from "../../libs/response-lib";
import { forbidden, ok } from "../../libs/response-lib";
import {
getReport as getReportFromDatabase,
queryReportsForState,
} from "../../storage/reports";
import { canReadState } from "../../utils/authorization";
import { error } from "../../utils/constants";

export const getReport = handler(async (event) => {
const { allParamsValid, reportType, state, id } =
parseReportParameters(event);
if (!allParamsValid) {
return badRequest("Invalid path parameters");
}
export const getReport = handler(parseReportParameters, async (request) => {
const { reportType, state, id } = request.parameters;
const user = request.user;

// TODO: Auth
if (!canReadState(user, state)) {
return forbidden(error.UNAUTHORIZED);
}

const report = await getReportFromDatabase(reportType, state, id);

return ok(report);
});

export const getReportsForState = handler(async (event) => {
const { allParamsValid, reportType, state } = parseReportTypeAndState(event);
if (!allParamsValid) {
return badRequest("Invalid path parameters");
}
export const getReportsForState = handler(
parseReportTypeAndState,
async (request) => {
const { reportType, state } = request.parameters;
const user = request.user;

// TODO: Auth
if (!canReadState(user, state)) {
return forbidden(error.UNAUTHORIZED);
}

const reports = await queryReportsForState(reportType, state);
const reports = await queryReportsForState(reportType, state);

return ok(reports);
});
return ok(reports);
}
);
34 changes: 24 additions & 10 deletions services/app-api/handlers/reports/update.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
import { StatusCodes } from "../../libs/response-lib";
import { proxyEvent } from "../../testing/proxyEvent";
import { APIGatewayProxyEvent } from "../../types/types";
import { APIGatewayProxyEvent, UserRoles } from "../../types/types";
import { canWriteState } from "../../utils/authorization";
import { updateReport } from "./update";

jest.mock("../../utils/authentication", () => ({
authenticatedUser: jest.fn().mockResolvedValue({
role: UserRoles.STATE_USER,
state: "PA",
}),
}));

jest.mock("../../utils/authorization", () => ({
isAuthenticated: jest.fn().mockResolvedValue(true),
canWriteState: jest.fn().mockReturnValue(true),
}));

jest.mock("../../storage/reports", () => ({
Expand All @@ -31,17 +39,23 @@ describe("Test update report handler", () => {
...proxyEvent,
pathParameters: {},
} as APIGatewayProxyEvent;
const res = await updateReport(badTestEvent, null);
const res = await updateReport(badTestEvent);
expect(res.statusCode).toBe(StatusCodes.BadRequest);
});

it("should return 403 if user is not authorized", async () => {
(canWriteState as jest.Mock).mockReturnValueOnce(false);
const response = await updateReport(testEvent);
expect(response.statusCode).toBe(StatusCodes.Forbidden);
});

test("Test missing body", async () => {
const emptyBodyEvent = {
...proxyEvent,
pathParameters: { reportType: "QM", state: "PA", id: "QMPA123" },
body: null,
} as APIGatewayProxyEvent;
const res = await updateReport(emptyBodyEvent, null);
const res = await updateReport(emptyBodyEvent);
expect(res.statusCode).toBe(StatusCodes.BadRequest);
});

Expand All @@ -53,25 +67,25 @@ describe("Test update report handler", () => {
} as APIGatewayProxyEvent;
const badState = {
...proxyEvent,
pathParameters: { reportType: "QM", state: "OR", id: "QMPA123" },
body: report,
pathParameters: { reportType: "QM", state: "PA", id: "QMPA123" },
body: JSON.stringify({ ...reportObj, state: "OR" }),
} as APIGatewayProxyEvent;
const badId = {
...proxyEvent,
pathParameters: { reportType: "QM", state: "PA", id: "ZZOR1234" },
body: report,
} as APIGatewayProxyEvent;

const resType = await updateReport(badType, null);
const resType = await updateReport(badType);
expect(resType.statusCode).toBe(StatusCodes.BadRequest);
const resState = await updateReport(badState, null);
const resState = await updateReport(badState);
expect(resState.statusCode).toBe(StatusCodes.BadRequest);
const resId = await updateReport(badId, null);
const resId = await updateReport(badId);
expect(resId.statusCode).toBe(StatusCodes.BadRequest);
});

test("Test Successful update", async () => {
const res = await updateReport(testEvent, null);
const res = await updateReport(testEvent);

expect(res.statusCode).toBe(StatusCodes.Ok);
});
Expand Down
23 changes: 12 additions & 11 deletions services/app-api/handlers/reports/update.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
import handler from "../../libs/handler-lib";
import { handler } from "../../libs/handler-lib";
import { parseReportParameters } from "../../libs/param-lib";
import { badRequest, ok } from "../../libs/response-lib";
import { badRequest, forbidden, ok } from "../../libs/response-lib";
import { putReport } from "../../storage/reports";
import { Report, ReportStatus } from "../../types/reports";
import { canWriteState } from "../../utils/authorization";
import { error } from "../../utils/constants";

export const updateReport = handler(async (event) => {
const { allParamsValid, reportType, state, id } =
parseReportParameters(event);
if (!allParamsValid) {
return badRequest("Invalid path parameters");
}
export const updateReport = handler(parseReportParameters, async (request) => {
const { reportType, state, id } = request.parameters;
const user = request.user;

// TODO: Auth
if (!canWriteState(user, state)) {
return forbidden(error.UNAUTHORIZED);
}

if (!event?.body) {
if (!request?.body) {
return badRequest("Invalid request");
}

const report = JSON.parse(event.body) as Report;
const report = request.body as Report;
if (
reportType !== report.type ||
state !== report.state ||
Expand Down
Loading

0 comments on commit bdfdbf6

Please sign in to comment.