From 2b8909c17f25aaabc291637eb44cf82b31430468 Mon Sep 17 00:00:00 2001 From: "oscargonzalezmoreno@gmail.com" Date: Fri, 27 Dec 2024 12:57:14 +0100 Subject: [PATCH] feat: enhance slug generation and validation - Add robust error handling for `generateSlug`, throwing `BadRequestException` on invalid input. - Improve error messages to provide clearer feedback in case of failures. --- package.json | 2 + src/rices/rices.service.ts | 72 +++++++++++++++++++++++++---- src/rices/utils/slug.util.ts | 15 ++++++ test/restclient/01_create_rice.http | 2 +- test/restclient/03_update_rice.http | 2 +- 5 files changed, 81 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index 9c9cea1..381d894 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "@supabase/supabase-js": "^2.47.10", "@types/express": "^5.0.0", "@types/jest": "^29.5.14", + "@types/leo-profanity": "^1.5.4", "@types/node": "^20.3.1", "@types/supertest": "^6.0.2", "@types/uuid": "^10.0.0", @@ -53,6 +54,7 @@ "eslint-config-prettier": "^9.0.0", "eslint-plugin-prettier": "^5.0.0", "jest": "^29.7.0", + "leo-profanity": "^1.7.0", "prettier": "^3.0.0", "source-map-support": "^0.5.21", "supertest": "^7.0.0", diff --git a/src/rices/rices.service.ts b/src/rices/rices.service.ts index 469d162..daa7ac5 100644 --- a/src/rices/rices.service.ts +++ b/src/rices/rices.service.ts @@ -35,6 +35,13 @@ export class RicesService { throw new BadRequestException('The request body must be a string.'); } + try { + this.validateJsonStructure(content); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (e) { + throw new BadRequestException('Invalid json request'); + } + // Validate lengths if (name.length > 75) { throw new BadRequestException( @@ -49,13 +56,11 @@ export class RicesService { } // Parse version and OS from User-Agent - const userAgentRegex = /ZenBrowser\/(\d+\.\d+\.\d+) \((.+)\)/; + const userAgentRegex = /ZenBrowser\/(\d+\.\d+\.\d.\d+) \((.+)\)/; const match = userAgent.match(userAgentRegex); if (!match) { - throw new BadRequestException( - 'Invalid User-Agent format. Expected format: ZenBrowser/ ().', - ); + throw new BadRequestException('Invalid request'); } const [, version, os] = match; @@ -80,9 +85,16 @@ export class RicesService { ); } - const slug = `${generateSlug(name)}-${uuidv4()}`; - const token = uuidv4(); + let slug: string; + try { + slug = `${generateSlug(name)}-${uuidv4()}`; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (e) { + // If generateSlug throws an error, rethrow as a BadRequestException + throw new BadRequestException(`Invalid name provided`); + } + const token = uuidv4(); const encodedContent = Buffer.from(content).toString('base64'); const metadata = { @@ -154,13 +166,18 @@ export class RicesService { } // Parse version and OS from User-Agent - const userAgentRegex = /ZenBrowser\/(\d+\.\d+\.\d+) \((.+)\)/; + const userAgentRegex = /ZenBrowser\/(\d+\.\d+\.\d.\d+) \((.+)\)/; const match = userAgent.match(userAgentRegex); if (!match) { - throw new BadRequestException( - 'Invalid User-Agent format. Expected format: ZenBrowser/ ().', - ); + throw new BadRequestException('Invalid request'); + } + + try { + this.validateJsonStructure(content); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (e) { + throw new BadRequestException('Invalid json request'); } const [, version, os] = match; @@ -280,4 +297,39 @@ export class RicesService { throw new Error('Failed to remove rice by moderation'); } } + + validateJsonStructure(jsonString: string): boolean { + const requiredKeys: string[] = [ + 'userChrome', + 'userContent', + 'enabledMods', + 'preferences', + 'workspaceThemes', + ]; + + let json: Record; + + // Validate JSON string + try { + json = JSON.parse(jsonString); + } catch { + throw new BadRequestException('Invalid JSON string.'); + } + + // Ensure the parsed JSON is an object + if (typeof json !== 'object' || json === null) { + throw new BadRequestException('The parsed JSON is not a valid object.'); + } + + // Check for missing keys + const missingKeys = requiredKeys.filter((key) => !(key in json)); + + if (missingKeys.length > 0) { + throw new BadRequestException( + `The JSON is missing the following required keys: ${missingKeys.join(', ')}`, + ); + } + + return true; + } } diff --git a/src/rices/utils/slug.util.ts b/src/rices/utils/slug.util.ts index cf72eed..0edb5e8 100644 --- a/src/rices/utils/slug.util.ts +++ b/src/rices/utils/slug.util.ts @@ -1,3 +1,5 @@ +import * as leoProfanity from 'leo-profanity'; + export function generateSlug(name: string): string { // Ensure the input is a string and trim whitespace if (typeof name !== 'string') { @@ -5,6 +7,9 @@ export function generateSlug(name: string): string { } const sanitizedInput = name.trim(); + // Configure the profanity filter + leoProfanity.loadDictionary('en'); // Ensure the dictionary is loaded + // Replace accented characters with their unaccented counterparts const normalized = sanitizedInput .normalize('NFD') @@ -21,5 +26,15 @@ export function generateSlug(name: string): string { throw new Error('Generated slug is empty'); } + // Split the slug into individual words + const words = slug.split('-'); + + // Check each word for inappropriate content + words.forEach((word) => { + if (leoProfanity.check(word)) { + throw new Error(`The word "${word}" is inappropriate.`); + } + }); + return slug; } diff --git a/test/restclient/01_create_rice.http b/test/restclient/01_create_rice.http index c365d12..f17486f 100644 --- a/test/restclient/01_create_rice.http +++ b/test/restclient/01_create_rice.http @@ -4,7 +4,7 @@ POST {{baseUrl}}/rices Content-Type: application/json X-Zen-Rice-Name: cool-zenrice-aurora2 X-Zen-Rice-Author: jhon@doe.com -User-Agent: ZenBrowser/1.0.0 (EndeavourOS x86_64) +User-Agent: ZenBrowser/1.0.0.0 (EndeavourOS x86_64) { "userChrome": "", diff --git a/test/restclient/03_update_rice.http b/test/restclient/03_update_rice.http index 4538380..3d9a9fc 100644 --- a/test/restclient/03_update_rice.http +++ b/test/restclient/03_update_rice.http @@ -7,7 +7,7 @@ Content-Type: application/json x-zen-rices-token: {{previous_token}} X-Zen-Rice-Name: cool-zenrice-aurora2 X-Zen-Rice-Author: jhon@doe.com -User-Agent: ZenBrowser/1.0.0 (EndeavourOS x86_64) +User-Agent: ZenBrowser/1.0.0.0 (EndeavourOS x86_64) { "userChrome": "",