From 72e213f72af168e8679c5042038a4942237d12dd Mon Sep 17 00:00:00 2001 From: Maria Adriana <97130795+mariadriana-deemaze@users.noreply.github.com> Date: Sat, 9 Nov 2024 22:50:22 +0000 Subject: [PATCH] [SOA-17] Sanitize posts text content (#18) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat 🎸 (be): add save/update hook for basic post content sanitization * test 🧪: added post model content parsing functional tests --- app/models/post.ts | 10 ++++- app/utils/index.ts | 13 +++++++ tests/functional/posts/parsing.spec.ts | 54 ++++++++++++++++++++++++++ 3 files changed, 75 insertions(+), 2 deletions(-) create mode 100644 tests/functional/posts/parsing.spec.ts diff --git a/app/models/post.ts b/app/models/post.ts index 6b19e4d..7f3ebd8 100644 --- a/app/models/post.ts +++ b/app/models/post.ts @@ -1,9 +1,9 @@ import { DateTime } from 'luxon' -import { BaseModel, belongsTo, column, computed } from '@adonisjs/lucid/orm' +import { BaseModel, beforeSave, beforeUpdate, belongsTo, column, computed } from '@adonisjs/lucid/orm' import User from '#models/user' import type { BelongsTo } from '@adonisjs/lucid/types/relations' import type { UUID } from 'crypto' -import { extractFirstLink } from '#utils/index' +import { extractFirstLink, sanitizePostContent } from '#utils/index' export default class Post extends BaseModel { @column({ isPrimary: true }) @@ -28,4 +28,10 @@ export default class Post extends BaseModel { get link(): string | null { return extractFirstLink(this.content) } + + @beforeSave() + @beforeUpdate() + static sanitizeContent(post: Post) { + post.content = sanitizePostContent(post.content) + } } diff --git a/app/utils/index.ts b/app/utils/index.ts index ba4ac61..8b8849b 100644 --- a/app/utils/index.ts +++ b/app/utils/index.ts @@ -19,3 +19,16 @@ export function extractFirstLink(content: string): string | null { const matches = content.match(urlRegex) return matches ? matches[0] : null } + +/** + * + */ +export function sanitizePostContent(content: string): string { + const map = { + '&': '&', + '<': '<', + '>': '>', + } + // @ts-ignore + return content.replace(/[&<>]/g, (m) => map[m]) +} diff --git a/tests/functional/posts/parsing.spec.ts b/tests/functional/posts/parsing.spec.ts new file mode 100644 index 0000000..b369afd --- /dev/null +++ b/tests/functional/posts/parsing.spec.ts @@ -0,0 +1,54 @@ +import { UserFactory } from '#database/factories/user_factory' +import Post from '#models/post' +import { faker } from '@faker-js/faker' +import { test } from '@japa/runner' + +test.group('Posts content parsing', () => { + const link = 'https://www.youtube.com/watch?v=jEeQC6I8nlY' + const contentWithLink = faker.lorem.sentence() + ' ' + link + + const contentWithScript = "" + + test('Sucessfully parses the post content on create', async ({ assert }) => { + const user = await UserFactory.create() + + const postWithScript = await Post.create({ + userId: user.id, + content: contentWithScript, + }) + + const postWithLink = await Post.create({ + userId: user.id, + content: contentWithLink, + }) + + assert.equal(postWithScript.content, "<body onload=alert('test1')>") + assert.equal(postWithLink.content, contentWithLink) + assert.equal(postWithLink.link, link) + }) + + test('Sucessfully parses the post content on update', async ({ assert }) => { + const user = await UserFactory.create() + + const postWithScript = await Post.create({ + userId: user.id, + content: faker.lorem.sentence(), + }) + + const postWithLink = await Post.create({ + userId: user.id, + content: faker.lorem.sentence(), + }) + + postWithScript.content = contentWithScript + + postWithLink.content = contentWithLink + + await postWithScript.save() + await postWithLink.save() + + assert.equal(postWithScript.content, "&lt;body onload=alert('test1')&gt;") + assert.equal(postWithLink.content, contentWithLink) + assert.equal(postWithLink.link, link) + }) +})