Strip HTML Tags Via Backend

This commit is contained in:
ADAMJR 2021-11-15 18:20:44 +00:00
parent a232fd3b8f
commit 3f190622d3
16 changed files with 179 additions and 29 deletions

View File

@ -36,6 +36,7 @@
"re2": "^1.16.0",
"socket.io": "^4.0.0",
"socket.io-client": "^4.0.0",
"striptags": "^3.2.0",
"ts-node": "^9.1.1",
"typescript": "^4.2.3",
"un": "^0.0.0",
@ -8152,6 +8153,11 @@
"node": ">=8"
}
},
"node_modules/striptags": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/striptags/-/striptags-3.2.0.tgz",
"integrity": "sha512-g45ZOGzHDMe2bdYMdIvdAfCQkCTDMGBazSw1ypMowwGIee7ZQ5dU0rBJ8Jqgl+jAKIv4dbeE1jscZq9wid1Tkw=="
},
"node_modules/superagent": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/superagent/-/superagent-6.1.0.tgz",
@ -15405,6 +15411,11 @@
"integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
"dev": true
},
"striptags": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/striptags/-/striptags-3.2.0.tgz",
"integrity": "sha512-g45ZOGzHDMe2bdYMdIvdAfCQkCTDMGBazSw1ypMowwGIee7ZQ5dU0rBJ8Jqgl+jAKIv4dbeE1jscZq9wid1Tkw=="
},
"superagent": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/superagent/-/superagent-6.1.0.tgz",

View File

@ -40,6 +40,7 @@
"re2": "^1.16.0",
"socket.io": "^4.0.0",
"socket.io-client": "^4.0.0",
"striptags": "^3.2.0",
"ts-node": "^9.1.1",
"typescript": "^4.2.3",
"un": "^0.0.0",

View File

@ -25,7 +25,7 @@ export default class Messages extends DBWrapper<string, MessageDocument> {
});
}
public async createSystem(guildId: string, content: string) {
public async createSystem(guildId: string, content: string, type?: MessageTypes.Type) {
const { systemChannelId: channelId } = await deps.guilds.get(guildId);
if (!channelId)
throw new TypeError('No system channel configured');
@ -35,6 +35,7 @@ export default class Messages extends DBWrapper<string, MessageDocument> {
channelId,
content,
system: true,
type,
});
}

View File

@ -39,6 +39,7 @@ export const Message = model<MessageDocument>('message', new Schema({
url: String,
}),
system: Boolean,
type: String,
updatedAt: Date,
}, { toJSON: { getters: true } })
.method('toClient', useId));

View File

@ -1,11 +1,8 @@
import { Socket } from 'socket.io';
import GuildMembers from '../../data/guild-members';
import Guilds from '../../data/guilds';
import Invites from '../../data/invites';
import { GuildDocument } from '../../data/models/guild';
import { InviteDocument } from '../../data/models/invite';
import Users from '../../data/users';
import { SelfUserDocument } from '../../data/models/user';
import { WS } from '../../types/ws';
import { WSRooms } from '../modules/ws-rooms';
import { WebSocket } from '../websocket';
import { WSEvent, } from './ws-event';
@ -44,7 +41,17 @@ export default class implements WSEvent<'GUILD_MEMBER_ADD'> {
await client.join(guild.id);
await deps.messages.createSystem(guild.id, `<@${selfUser.id}> joined the guild.`);
await this.joinGuildMessage(guild, selfUser, ws);
}
private async joinGuildMessage(guild: GuildDocument, selfUser: SelfUserDocument, ws: WebSocket) {
try {
const sysMessage = await deps.messages.createSystem(guild.id, `<@${selfUser.id}> joined the guild.`, 'GUILD_MEMBER_JOIN');
ws.io
.to(guild.systemChannelId!)
.emit('MESSAGE_CREATE', { message: sysMessage } as WS.Args.MessageCreate);
} catch {}
}
private async handleInvite(invite: InviteDocument) {

View File

@ -3,7 +3,7 @@ import Guilds from '../../data/guilds';
import { Channel } from '../../data/models/channel';
import { Guild, GuildDocument } from '../../data/models/guild';
import { GuildMember } from '../../data/models/guild-member';
import { User } from '../../data/models/user';
import { SelfUserDocument, User } from '../../data/models/user';
import Users from '../../data/users';
import { PermissionTypes } from '../../types/permission-types';
import { WS } from '../../types/ws';
@ -52,8 +52,18 @@ export default class implements WSEvent<'GUILD_MEMBER_REMOVE'> {
const userClient = ws.io.sockets.sockets.get(userId);
if (userClient)
await this.leaveGuildRooms(userClient, guild);
await deps.messages.createSystem(guildId, `<@${member.userId}> left the guild.`);
await this.leaveGuildMessage(guild, user, ws);
}
private async leaveGuildMessage(guild: GuildDocument, user: SelfUserDocument, ws: WebSocket) {
try {
const sysMessage = await deps.messages.createSystem(guild.id, `<@${user.id}> left the guild.`, 'GUILD_MEMBER_LEAVE');
ws.io
.to(guild.systemChannelId!)
.emit('MESSAGE_CREATE', { message: sysMessage } as WS.Args.MessageCreate);
} catch {}
}
private async leaveGuildRooms(client: Socket, guild: GuildDocument) {

View File

@ -3,6 +3,7 @@ import { WebSocket } from '../websocket';
import { WSEvent, } from './ws-event';
import { WS } from '../../types/ws';
import { Channel } from '../../data/models/channel';
import striptags from 'striptags';
export default class implements WSEvent<'MESSAGE_CREATE'> {
on = 'MESSAGE_CREATE' as const;
@ -12,7 +13,11 @@ export default class implements WSEvent<'MESSAGE_CREATE'> {
const [_, message, author] = await Promise.all([
deps.wsGuard.validateCanInChannel(client, channelId, 'SEND_MESSAGES'),
deps.messages.create(authorId, channelId, { attachmentURLs, content, embed }),
deps.messages.create(authorId, channelId, {
attachmentURLs,
content: (content) ? striptags(content) : '',
embed,
}),
deps.users.getSelf(authorId),
]);

View File

@ -19,7 +19,9 @@ export default class implements WSEvent<'USER_DELETE'> {
await user.updateOne(partialUser);
client.emit('USER_DELETE');
ws.io.emit('USER_UPDATE', { userId: user.id, partialUser } as WS.Args.UserUpdate);
ws.io
.to(userId)
.emit('USER_UPDATE', { userId: user.id, partialUser } as WS.Args.UserUpdate);
client.disconnect();
}
}

View File

@ -12,6 +12,7 @@
"@fortawesome/fontawesome-svg-core": "^1.2.35",
"@fortawesome/free-solid-svg-icons": "^5.15.3",
"@fortawesome/react-fontawesome": "^0.1.14",
"@paypal/react-paypal-js": "^7.4.2",
"@reduxjs/toolkit": "^1.6.1",
"@testing-library/react": "^11.2.7",
"@testing-library/user-event": "^12.8.3",
@ -2978,6 +2979,37 @@
"node": ">=10"
}
},
"node_modules/@paypal/paypal-js": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/@paypal/paypal-js/-/paypal-js-4.1.0.tgz",
"integrity": "sha512-fBPpNBfzIpn4hWgks+EmZmuwKopkkMRBAtvCuEkH0RQIUobH7Dtf3enyFAt1+u8ZdgdIvueSBB58lRCSP2RNkA==",
"dependencies": {
"promise-polyfill": "^8.2.0"
}
},
"node_modules/@paypal/react-paypal-js": {
"version": "7.4.2",
"resolved": "https://registry.npmjs.org/@paypal/react-paypal-js/-/react-paypal-js-7.4.2.tgz",
"integrity": "sha512-NYE5SopmMK80h0/hYIr3//iaIjGNHTFxRE9aTo2LzNik75WHu/skUggowHwZHpYWgLi6NYvz7WawRSnesjk6Sw==",
"dependencies": {
"@paypal/paypal-js": "^4.1.0",
"@paypal/sdk-constants": "^1.0.110"
},
"peerDependencies": {
"react": ">=16.3.0",
"react-dom": ">=16.3.0"
}
},
"node_modules/@paypal/sdk-constants": {
"version": "1.0.110",
"resolved": "https://registry.npmjs.org/@paypal/sdk-constants/-/sdk-constants-1.0.110.tgz",
"integrity": "sha512-W/JvynHo68yVbR5+H3xXncFvucbVmt4PSBifBuGGH8NqDUq8kwOFNaHt0xmUIvWoK77pQjtaulYssty3rnztTw==",
"dependencies": {
"cross-domain-utils": "^2.0.10",
"hi-base32": "^0.5.0",
"zalgo-promise": "^1.0.28"
}
},
"node_modules/@reduxjs/toolkit": {
"version": "1.6.2",
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.6.2.tgz",
@ -6899,6 +6931,14 @@
"sha.js": "^2.4.8"
}
},
"node_modules/cross-domain-utils": {
"version": "2.0.38",
"resolved": "https://registry.npmjs.org/cross-domain-utils/-/cross-domain-utils-2.0.38.tgz",
"integrity": "sha512-zZfi3+2EIR9l4chrEiXI2xFleyacsJf8YMLR1eJ0Veb5FTMXeJ3DpxDjZkto2FhL/g717WSELqbptNSo85UJDw==",
"dependencies": {
"zalgo-promise": "^1.0.11"
}
},
"node_modules/cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@ -11157,6 +11197,11 @@
"resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz",
"integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ=="
},
"node_modules/hi-base32": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/hi-base32/-/hi-base32-0.5.1.tgz",
"integrity": "sha512-EmBBpvdYh/4XxsnUybsPag6VikPYnN30td+vQk+GI3qpahVEG9+gTkG0aXVxTjBqQ5T6ijbWIu77O+C5WFWsnA=="
},
"node_modules/highlight.js": {
"version": "11.3.1",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.3.1.tgz",
@ -19602,6 +19647,11 @@
"resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz",
"integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM="
},
"node_modules/promise-polyfill": {
"version": "8.2.1",
"resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.2.1.tgz",
"integrity": "sha512-3p9zj0cEHbp7NVUxEYUWjQlffXqnXaZIMPkAO7HhFh8u5636xLRDHOUo2vpWSK0T2mqm6fKLXYn1KP6PAZ2gKg=="
},
"node_modules/prompts": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.0.tgz",
@ -26480,6 +26530,11 @@
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/zalgo-promise": {
"version": "1.0.48",
"resolved": "https://registry.npmjs.org/zalgo-promise/-/zalgo-promise-1.0.48.tgz",
"integrity": "sha512-LLHANmdm53+MucY9aOFIggzYtUdkSBFxUsy4glTTQYNyK6B3uCPWTbfiGvSrEvLojw0mSzyFJ1/RRLv+QMNdzQ=="
}
},
"dependencies": {
@ -28574,6 +28629,33 @@
}
}
},
"@paypal/paypal-js": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/@paypal/paypal-js/-/paypal-js-4.1.0.tgz",
"integrity": "sha512-fBPpNBfzIpn4hWgks+EmZmuwKopkkMRBAtvCuEkH0RQIUobH7Dtf3enyFAt1+u8ZdgdIvueSBB58lRCSP2RNkA==",
"requires": {
"promise-polyfill": "^8.2.0"
}
},
"@paypal/react-paypal-js": {
"version": "7.4.2",
"resolved": "https://registry.npmjs.org/@paypal/react-paypal-js/-/react-paypal-js-7.4.2.tgz",
"integrity": "sha512-NYE5SopmMK80h0/hYIr3//iaIjGNHTFxRE9aTo2LzNik75WHu/skUggowHwZHpYWgLi6NYvz7WawRSnesjk6Sw==",
"requires": {
"@paypal/paypal-js": "^4.1.0",
"@paypal/sdk-constants": "^1.0.110"
}
},
"@paypal/sdk-constants": {
"version": "1.0.110",
"resolved": "https://registry.npmjs.org/@paypal/sdk-constants/-/sdk-constants-1.0.110.tgz",
"integrity": "sha512-W/JvynHo68yVbR5+H3xXncFvucbVmt4PSBifBuGGH8NqDUq8kwOFNaHt0xmUIvWoK77pQjtaulYssty3rnztTw==",
"requires": {
"cross-domain-utils": "^2.0.10",
"hi-base32": "^0.5.0",
"zalgo-promise": "^1.0.28"
}
},
"@reduxjs/toolkit": {
"version": "1.6.2",
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.6.2.tgz",
@ -31701,6 +31783,14 @@
"sha.js": "^2.4.8"
}
},
"cross-domain-utils": {
"version": "2.0.38",
"resolved": "https://registry.npmjs.org/cross-domain-utils/-/cross-domain-utils-2.0.38.tgz",
"integrity": "sha512-zZfi3+2EIR9l4chrEiXI2xFleyacsJf8YMLR1eJ0Veb5FTMXeJ3DpxDjZkto2FhL/g717WSELqbptNSo85UJDw==",
"requires": {
"zalgo-promise": "^1.0.11"
}
},
"cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@ -34946,6 +35036,11 @@
"resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz",
"integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ=="
},
"hi-base32": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/hi-base32/-/hi-base32-0.5.1.tgz",
"integrity": "sha512-EmBBpvdYh/4XxsnUybsPag6VikPYnN30td+vQk+GI3qpahVEG9+gTkG0aXVxTjBqQ5T6ijbWIu77O+C5WFWsnA=="
},
"highlight.js": {
"version": "11.3.1",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.3.1.tgz",
@ -41462,6 +41557,11 @@
"resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz",
"integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM="
},
"promise-polyfill": {
"version": "8.2.1",
"resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.2.1.tgz",
"integrity": "sha512-3p9zj0cEHbp7NVUxEYUWjQlffXqnXaZIMPkAO7HhFh8u5636xLRDHOUo2vpWSK0T2mqm6fKLXYn1KP6PAZ2gKg=="
},
"prompts": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.0.tgz",
@ -46902,6 +47002,11 @@
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="
},
"zalgo-promise": {
"version": "1.0.48",
"resolved": "https://registry.npmjs.org/zalgo-promise/-/zalgo-promise-1.0.48.tgz",
"integrity": "sha512-LLHANmdm53+MucY9aOFIggzYtUdkSBFxUsy4glTTQYNyK6B3uCPWTbfiGvSrEvLojw0mSzyFJ1/RRLv+QMNdzQ=="
}
}
}

View File

@ -8,6 +8,7 @@
"@fortawesome/fontawesome-svg-core": "^1.2.35",
"@fortawesome/free-solid-svg-icons": "^5.15.3",
"@fortawesome/react-fontawesome": "^0.1.14",
"@paypal/react-paypal-js": "^7.4.2",
"@reduxjs/toolkit": "^1.6.1",
"@testing-library/react": "^11.2.7",
"@testing-library/user-event": "^12.8.3",

View File

@ -23,7 +23,7 @@ const MessageBoxInput: React.FunctionComponent<MessageBoxInputProps> = (props) =
const [content, setContent] = props.contentState;
const onKeyUp = (event: React.KeyboardEvent<HTMLDivElement>) => {
setContent(striptags(event.currentTarget!.innerText));
setContent(event.currentTarget!.innerText);
handleEscape(event);
dispatch(startTyping(channel.id));

View File

@ -2,8 +2,8 @@ import MessageBox from '../message-box/message-box';
import { FunctionComponent } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { previewImage } from '../../../store/ui';
import useMentions from '../../../hooks/use-mentions';
import useFormat from '../../../hooks/use-format';
import striptags from 'striptags';
interface MessageContentProps {
message: Entity.Message;
@ -14,10 +14,8 @@ const MessageContent: FunctionComponent<MessageContentProps> = ({ message }) =>
const format = useFormat();
const editingMessageId = useSelector((s: Store.AppState) => s.ui.editingMessageId);
// TODO: refactor to useMentions -> mention-service
// FIXME: add striptags
const messageHTML =
((message.content) ? format(message.content) : '')
((message.content) ? striptags(format(message.content), 'a') : '')
+ ((message.updatedAt && message.content)
? `<span
class="select-none muted edited text-xs ml-1"

View File

@ -1,5 +1,6 @@
import { faBug, faGavel, faRocket, faSun, faVideo } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import classNames from 'classnames';
import moment from 'moment';
import { FunctionComponent, useState } from 'react';
import { useSelector, useStore } from 'react-redux';
@ -16,7 +17,12 @@ const UserProfile: FunctionComponent = () => {
const [tab, setTab] = useState('info');
type BadgeIcon = {
[k in UserTypes.Badge]: { color: string, icon: any, title: string }
[k in UserTypes.Badge]: {
color: string,
icon: any,
title: string,
styles?: React.CSSProperties,
};
};
const badgeIcons: BadgeIcon = {
'BUG_1': {
@ -57,14 +63,15 @@ const UserProfile: FunctionComponent = () => {
};
const UserBadges = () => (user) ? (
<div className="px-3 pt-2">
{user.badges.map(b => {
const { color, icon, title } = badgeIcons[b];
<div className="float-right -mt-16">
{user.badges.map((b, i) => {
const { color, icon, title, styles } = badgeIcons[b];
return (
<FontAwesomeIcon
key={i}
title={title}
className="pr-3"
style={{ color }}
className={classNames('pr-3')}
style={{ ...styles, color }}
size="2x"
icon={icon} />
);

View File

@ -1,6 +1,6 @@
import { useDispatch, useSelector, useStore } from 'react-redux';
import ws from '../services/ws-service';
import { useEffect, useState } from 'react';
import { useEffect } from 'react';
import { useHistory } from 'react-router-dom';
import { actions as users, getUser } from '../store/users';
import { actions as meta } from '../store/meta';
@ -46,6 +46,8 @@ const WSListener: React.FunctionComponent = () => {
events.on('dialog', handleDialog);
events.on('openUserProfile', (userId: string) => {
const user = getUser(userId)(store.getState());
if (!user.discriminator) return;
dispatch(openUserProfile(user));
});

View File

@ -54,8 +54,3 @@ body {
nav[role='menu'] {
z-index: 10;
}
/* messages */
.message p {
display: inline;
}

4
types/entity.d.ts vendored
View File

@ -51,6 +51,7 @@ declare namespace Entity {
content?: string;
createdAt: Date;
embed?: MessageTypes.Embed;
type?: MessageTypes.Type;
updatedAt?: Date;
system?: boolean;
}
@ -128,6 +129,9 @@ declare namespace MessageTypes {
title: string;
url: string;
}
export type Type = undefined
| 'GUILD_MEMBER_JOIN'
| 'GUILD_MEMBER_LEAVE';
}
declare namespace UserTypes {