Zod schemas for routes under /plugins (#6378)

* fix: schema for slack routes

* fix: slack.post

* fix: email
This commit is contained in:
Apoorv Mishra
2024-01-13 10:55:30 +05:30
committed by GitHub
parent 7e61a519f1
commit 3561b79d65
7 changed files with 186 additions and 77 deletions

View File

@@ -0,0 +1,31 @@
import isEmpty from "lodash/isEmpty";
import { z } from "zod";
import { BaseSchema } from "@server/routes/api/schema";
export const SlackCommandsSchema = BaseSchema.extend({
query: z
.object({
code: z.string().nullish(),
state: z.string().uuid().nullish(),
error: z.string().nullish(),
})
.refine((req) => !(isEmpty(req.code) && isEmpty(req.error)), {
message: "one of code or error is required",
}),
});
export type SlackCommandsReq = z.infer<typeof SlackCommandsSchema>;
export const SlackPostSchema = BaseSchema.extend({
query: z
.object({
code: z.string().nullish(),
state: z.string().uuid().nullish(),
error: z.string().nullish(),
})
.refine((req) => !(isEmpty(req.code) && isEmpty(req.error)), {
message: "one of code or error is required",
}),
});
export type SlackPostReq = z.infer<typeof SlackPostSchema>;

View File

@@ -0,0 +1,39 @@
import { getTestServer } from "@server/test/support";
const server = getTestServer();
describe("#slack.commands", () => {
it("should fail with status 400 bad request if query param state is not a uuid", async () => {
const res = await server.get("/auth/slack.commands?state=123");
const body = await res.json();
expect(res.status).toEqual(400);
expect(body.message).toEqual("state: Invalid uuid");
});
it("should fail with status 400 bad request when both code and error are missing in query params", async () => {
const res = await server.get(
"/auth/slack.commands?state=182d14d5-0dbd-4521-ac52-25484c25c96e"
);
const body = await res.json();
expect(res.status).toEqual(400);
expect(body.message).toEqual("query: one of code or error is required");
});
});
describe("#slack.post", () => {
it("should fail with status 400 bad request if query param state is not a uuid", async () => {
const res = await server.get("/auth/slack.post?state=123");
const body = await res.json();
expect(res.status).toEqual(400);
expect(body.message).toEqual("state: Invalid uuid");
});
it("should fail with status 400 bad request when both code and error are missing in query params", async () => {
const res = await server.get(
"/auth/slack.post?state=182d14d5-0dbd-4521-ac52-25484c25c96e"
);
const body = await res.json();
expect(res.status).toEqual(400);
expect(body.message).toEqual("query: one of code or error is required");
});
});

View File

@@ -9,6 +9,7 @@ import accountProvisioner from "@server/commands/accountProvisioner";
import env from "@server/env";
import auth from "@server/middlewares/authentication";
import passportMiddleware from "@server/middlewares/passport";
import validate from "@server/middlewares/validate";
import {
IntegrationAuthentication,
Collection,
@@ -16,14 +17,14 @@ import {
Team,
User,
} from "@server/models";
import { AppContext, AuthenticationResult } from "@server/types";
import { APIContext, AuthenticationResult } from "@server/types";
import {
getClientFromContext,
getTeamFromContext,
StateStore,
} from "@server/utils/passport";
import { assertPresent, assertUuid } from "@server/validation";
import * as Slack from "../slack";
import * as T from "./schema";
type SlackProfile = Profile & {
team: {
@@ -132,10 +133,10 @@ if (env.SLACK_CLIENT_ID && env.SLACK_CLIENT_SECRET) {
auth({
optional: true,
}),
async (ctx: AppContext) => {
const { code, state, error } = ctx.request.query;
validate(T.SlackCommandsSchema),
async (ctx: APIContext<T.SlackCommandsReq>) => {
const { code, state: teamId, error } = ctx.input.query;
const { user } = ctx.state.auth;
assertPresent(code || error, "code is required");
if (error) {
ctx.redirect(integrationSettingsPath(`slack?error=${error}`));
@@ -146,9 +147,9 @@ if (env.SLACK_CLIENT_ID && env.SLACK_CLIENT_SECRET) {
// access authentication for subdomains. We must forward to the appropriate
// subdomain to complete the oauth flow
if (!user) {
if (state) {
if (teamId) {
try {
const team = await Team.findByPk(String(state), {
const team = await Team.findByPk(teamId, {
rejectOnEmpty: true,
});
return redirectOnClient(
@@ -168,7 +169,8 @@ if (env.SLACK_CLIENT_ID && env.SLACK_CLIENT_SECRET) {
}
const endpoint = `${env.URL}/auth/slack.commands`;
const data = await Slack.oauthAccess(String(code), endpoint);
// validation middleware ensures that code is non-null at this point
const data = await Slack.oauthAccess(code!, endpoint);
const authentication = await IntegrationAuthentication.create({
service: IntegrationService.Slack,
userId: user.id,
@@ -195,14 +197,10 @@ if (env.SLACK_CLIENT_ID && env.SLACK_CLIENT_SECRET) {
auth({
optional: true,
}),
async (ctx: AppContext) => {
const { code, error, state } = ctx.request.query;
validate(T.SlackPostSchema),
async (ctx: APIContext<T.SlackPostReq>) => {
const { code, error, state: collectionId } = ctx.input.query;
const { user } = ctx.state.auth;
assertPresent(code || error, "code is required");
// FIX ME! What about having zod like schema in place here?
const collectionId = state as string;
assertUuid(collectionId, "collectionId must be an uuid");
if (error) {
ctx.redirect(integrationSettingsPath(`slack?error=${error}`));
@@ -213,21 +211,24 @@ if (env.SLACK_CLIENT_ID && env.SLACK_CLIENT_SECRET) {
// access authentication for subdomains. We must forward to the
// appropriate subdomain to complete the oauth flow
if (!user) {
try {
const collection = await Collection.findOne({
where: {
id: String(state),
},
rejectOnEmpty: true,
});
const team = await Team.findByPk(collection.teamId, {
rejectOnEmpty: true,
});
return redirectOnClient(
ctx,
`${team.url}/auth/slack.post?${ctx.request.querystring}`
);
} catch (err) {
if (collectionId) {
try {
const collection = await Collection.findByPk(collectionId, {
rejectOnEmpty: true,
});
const team = await Team.findByPk(collection.teamId, {
rejectOnEmpty: true,
});
return redirectOnClient(
ctx,
`${team.url}/auth/slack.post?${ctx.request.querystring}`
);
} catch (err) {
return ctx.redirect(
integrationSettingsPath(`slack?error=unauthenticated`)
);
}
} else {
return ctx.redirect(
integrationSettingsPath(`slack?error=unauthenticated`)
);
@@ -235,7 +236,8 @@ if (env.SLACK_CLIENT_ID && env.SLACK_CLIENT_SECRET) {
}
const endpoint = `${env.URL}/auth/slack.post`;
const data = await Slack.oauthAccess(code as string, endpoint);
// validation middleware ensures that code is non-null at this point
const data = await Slack.oauthAccess(code!, endpoint);
const authentication = await IntegrationAuthentication.create({
service: IntegrationService.Slack,
userId: user.id,