diff --git a/packages/api/package.json b/packages/api/package.json index 2a8ea63..96f04d6 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -13,7 +13,8 @@ "fastify": "^4.26.2", "node-fetch": "^2.7.0", "octokit": "^3.2.1", - "ua-parser-js": "^1.0.37" + "ua-parser-js": "^1.0.37", + "yup": "^1.4.0" }, "devDependencies": { "@tsconfig/node20": "^20.1.4", diff --git a/packages/api/src/routes/v2/report.ts b/packages/api/src/routes/v2/report.ts index 6d1a313..1aea105 100644 --- a/packages/api/src/routes/v2/report.ts +++ b/packages/api/src/routes/v2/report.ts @@ -1,39 +1,33 @@ import { FastifyInstance, RouteShorthandOptions } from 'fastify'; import environment from 'services/environment'; import { octokit } from 'services/octokit'; +import { validatorCompiler } from 'services/validation'; import { UAParser } from 'ua-parser-js'; +import * as yup from 'yup'; -type PostReportBody = { - reason?: string; - url: string; - userAgent?: string; - version: string; -}; +interface PostReportBody { + readonly reason?: string; + readonly url: string; + readonly userAgent?: string; + readonly version: string; +} export default (server: FastifyInstance, options: RouteShorthandOptions, done: () => void) => { server.post<{ Body: PostReportBody }>( '/report/', { schema: { - body: { - properties: { - reason: { - type: 'string', - }, - url: { - type: 'string', - }, - userAgent: { - type: 'string', - }, - version: { - type: 'string', - }, - }, - required: ['url', 'version'], - type: 'object', - }, + body: yup.object().shape({ + reason: yup.string().min(10).required(), + url: yup.string().url().required(), + userAgent: yup.string(), + version: yup + .string() + .matches(/^\d+(\.\d+){0,3}$/) + .required(), + }), }, + validatorCompiler, }, async (request, reply) => { try { diff --git a/packages/api/src/routes/v3/report.ts b/packages/api/src/routes/v3/report.ts index ab1b204..830c2d1 100644 --- a/packages/api/src/routes/v3/report.ts +++ b/packages/api/src/routes/v3/report.ts @@ -1,7 +1,9 @@ import { FastifyInstance, RouteShorthandOptions } from 'fastify'; import environment from 'services/environment'; import { octokit } from 'services/octokit'; +import { validatorCompiler } from 'services/validation'; import { UAParser } from 'ua-parser-js'; +import * as yup from 'yup'; interface PostReportBody { readonly reason?: string; @@ -15,25 +17,17 @@ export default (server: FastifyInstance, _options: RouteShorthandOptions, done: '/report/', { schema: { - body: { - properties: { - reason: { - type: 'string', - }, - url: { - type: 'string', - }, - userAgent: { - type: 'string', - }, - version: { - type: 'string', - }, - }, - required: ['reason', 'url', 'version'], - type: 'object', - }, + body: yup.object().shape({ + reason: yup.string().min(10).required(), + url: yup.string().url().required(), + userAgent: yup.string(), + version: yup + .string() + .matches(/^\d+(\.\d+){0,3}$/) + .required(), + }), }, + validatorCompiler, }, async (request, reply) => { const ua = new UAParser(request.body.userAgent ?? '').getResult(); @@ -109,7 +103,7 @@ function generateText(body: PostReportBody, ua: UAParser.IResult): string { ? ['#### 📱 Device', `${ua.device.vendor} (${ua.device.type})`] : []), '#### 📝 Reason', - body.reason ?? '-', + body.reason, '#### 🔗 URL', body.url, '#### 🏷️ Version', diff --git a/packages/api/src/services/validation.ts b/packages/api/src/services/validation.ts new file mode 100644 index 0000000..61e91a3 --- /dev/null +++ b/packages/api/src/services/validation.ts @@ -0,0 +1,7 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +import type { FastifyRouteSchemaDef } from 'fastify/types/schema'; + +export function validatorCompiler({ schema }: FastifyRouteSchemaDef) { + return (data: any) => schema.validate(data); +} diff --git a/yarn.lock b/yarn.lock index f34dc51..c8909eb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1329,6 +1329,7 @@ __metadata: tsconfig-paths: "npm:^4.2.0" typescript: "npm:^5.4.5" ua-parser-js: "npm:^1.0.37" + yup: "npm:^1.4.0" languageName: unknown linkType: soft @@ -4456,6 +4457,13 @@ __metadata: languageName: node linkType: hard +"property-expr@npm:^2.0.5": + version: 2.0.6 + resolution: "property-expr@npm:2.0.6" + checksum: 10c0/69b7da15038a1146d6447c69c445306f66a33c425271235bb20507f1846dbf9577a8f9dfafe8acbfcb66f924b270157f155248308f026a68758f35fc72265b3c + languageName: node + linkType: hard + "proxy-addr@npm:^2.0.7": version: 2.0.7 resolution: "proxy-addr@npm:2.0.7" @@ -5242,6 +5250,13 @@ __metadata: languageName: node linkType: hard +"tiny-case@npm:^1.0.3": + version: 1.0.3 + resolution: "tiny-case@npm:1.0.3" + checksum: 10c0/c0cbed35884a322265e2cd61ff435168d1ea523f88bf3864ce14a238ae9169e732649776964283a66e4eb882e655992081d4daf8c865042e2233425866111b35 + languageName: node + linkType: hard + "to-regex-range@npm:^5.0.1": version: 5.0.1 resolution: "to-regex-range@npm:5.0.1" @@ -5258,6 +5273,13 @@ __metadata: languageName: node linkType: hard +"toposort@npm:^2.0.2": + version: 2.0.2 + resolution: "toposort@npm:2.0.2" + checksum: 10c0/ab9ca91fce4b972ccae9e2f539d755bf799a0c7eb60da07fd985fce0f14c159ed1e92305ff55697693b5bc13e300f5417db90e2593b127d421c9f6c440950222 + languageName: node + linkType: hard + "touch@npm:^3.1.0": version: 3.1.0 resolution: "touch@npm:3.1.0" @@ -5378,7 +5400,7 @@ __metadata: languageName: node linkType: hard -"type-fest@npm:^2.13.0": +"type-fest@npm:^2.13.0, type-fest@npm:^2.19.0": version: 2.19.0 resolution: "type-fest@npm:2.19.0" checksum: 10c0/a5a7ecf2e654251613218c215c7493574594951c08e52ab9881c9df6a6da0aeca7528c213c622bc374b4e0cb5c443aa3ab758da4e3c959783ce884c3194e12cb @@ -5686,3 +5708,15 @@ __metadata: checksum: 10c0/856117aa15cf5103d2a2fb173f0ab4acb12b4b4d0ed3ab249fdbbf612e55d1cadfd27a6110940e24746fb0a78cf640b522cc8bca76f30a3b00b66e90cf82abe0 languageName: node linkType: hard + +"yup@npm:^1.4.0": + version: 1.4.0 + resolution: "yup@npm:1.4.0" + dependencies: + property-expr: "npm:^2.0.5" + tiny-case: "npm:^1.0.3" + toposort: "npm:^2.0.2" + type-fest: "npm:^2.19.0" + checksum: 10c0/fe142141365eed0f78fb2e18bdd2f10bf101385dae12a5f9de14884448067bdca16a54b547fc0bffec04a098dd70b4519ff366422f3da006fd11a0717a7863ac + languageName: node + linkType: hard