feat: full server-side validation
This commit is contained in:
parent
cc6d4e6250
commit
7f55a32f67
|
@ -17,7 +17,7 @@ export const validateSubreddit = (name) => {
|
||||||
return !!name.match(/^[A-Za-z0-9][A-Za-z0-9_]{2,20}$/i);
|
return !!name.match(/^[A-Za-z0-9][A-Za-z0-9_]{2,20}$/i);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const validateUsername = (name) => name.match(/^[\w-]{3,20}/);
|
export const validateUsername = (name) => !!name.match(/^[\w-]{3,20}$/);
|
||||||
|
|
||||||
export const validateChannel = (channel) => {
|
export const validateChannel = (channel) => {
|
||||||
if (!channel) {
|
if (!channel) {
|
||||||
|
|
|
@ -1,10 +1,15 @@
|
||||||
|
import {validateChannel} from '~/common/channel';
|
||||||
import AddFavorite from '~/common/packets/add-favorite.packet';
|
import AddFavorite from '~/common/packets/add-favorite.packet';
|
||||||
|
|
||||||
import {allModels} from '~/server/models/registrar';
|
import ValidationError from './validation-error';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
Packet: AddFavorite,
|
Packet: AddFavorite,
|
||||||
validator: async () => true,
|
validator: async ({data: channel}) => {
|
||||||
|
if (!validateChannel(channel)) {
|
||||||
|
throw new ValidationError({code: 400, reason: 'Malformed channel'});
|
||||||
|
}
|
||||||
|
},
|
||||||
responder: async (packet, socket) => {
|
responder: async (packet, socket) => {
|
||||||
const {req} = socket;
|
const {req} = socket;
|
||||||
await req.user.createFavorite({channel: packet.data});
|
await req.user.createFavorite({channel: packet.data});
|
||||||
|
|
|
@ -10,10 +10,10 @@ export default {
|
||||||
limiter: {points: 20, duration: 60},
|
limiter: {points: 20, duration: 60},
|
||||||
validator: async ({data: {nameOrStatus}}) => {
|
validator: async ({data: {nameOrStatus}}) => {
|
||||||
if (!validateUsername(nameOrStatus)) {
|
if (!validateUsername(nameOrStatus)) {
|
||||||
throw new ValidationError('Invalid username');
|
throw new ValidationError({code: 400, reason: 'Malformed username'});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
responder: async (packet, socket) => {
|
responder: async ({data: {nameOrStatus}}, socket) => {
|
||||||
const {req} = socket;
|
const {req} = socket;
|
||||||
const {
|
const {
|
||||||
Friendship,
|
Friendship,
|
||||||
|
@ -21,8 +21,8 @@ export default {
|
||||||
} = allModels();
|
} = allModels();
|
||||||
const adderId = req.user.id;
|
const adderId = req.user.id;
|
||||||
const user = (
|
const user = (
|
||||||
await User.findOne({where: {redditUsername: packet.data.nameOrStatus}})
|
await User.findOne({where: {redditUsername: nameOrStatus}})
|
||||||
|| await User.create({redditUsername: packet.data.nameOrStatus})
|
|| await User.create({redditUsername: nameOrStatus})
|
||||||
);
|
);
|
||||||
const addeeId = user.id;
|
const addeeId = user.id;
|
||||||
let friendship = await Friendship.findOne({where: {adderId: addeeId, addeeId: adderId}});
|
let friendship = await Friendship.findOne({where: {adderId: addeeId, addeeId: adderId}});
|
||||||
|
|
|
@ -7,9 +7,16 @@ import {allModels} from '~/server/models/registrar';
|
||||||
|
|
||||||
import {removeFavoritedUser} from './remove-favorite';
|
import {removeFavoritedUser} from './remove-favorite';
|
||||||
|
|
||||||
|
import ValidationError from './validation-error';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
Packet: Block,
|
Packet: Block,
|
||||||
validator: async () => true,
|
validator: async ({data: id}) => {
|
||||||
|
const {User} = allModels();
|
||||||
|
if (!await User.count({where: {id}})) {
|
||||||
|
throw new ValidationError({code: 400, reason: 'No such user'});
|
||||||
|
}
|
||||||
|
},
|
||||||
responder: async (packet, socket) => {
|
responder: async (packet, socket) => {
|
||||||
const {req} = socket;
|
const {req} = socket;
|
||||||
const id = packet.data;
|
const id = packet.data;
|
||||||
|
|
|
@ -3,6 +3,7 @@ import {promisify} from 'util';
|
||||||
|
|
||||||
import {ServerSocket} from '@avocado/net/server/socket';
|
import {ServerSocket} from '@avocado/net/server/socket';
|
||||||
|
|
||||||
|
import {validateChannel} from '~/common/channel';
|
||||||
import Join from '~/common/packets/join.packet';
|
import Join from '~/common/packets/join.packet';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -10,6 +11,8 @@ import {
|
||||||
channelUsers,
|
channelUsers,
|
||||||
} from '~/server/entry';
|
} from '~/server/entry';
|
||||||
|
|
||||||
|
import ValidationError from './validation-error';
|
||||||
|
|
||||||
export const userJoin = async (channel, socket) => {
|
export const userJoin = async (channel, socket) => {
|
||||||
const userId = '/r/anonymous' === channel ? 0 : socket.handshake.userId;
|
const userId = '/r/anonymous' === channel ? 0 : socket.handshake.userId;
|
||||||
const users = await channelUsers(socket.handshake, channel);
|
const users = await channelUsers(socket.handshake, channel);
|
||||||
|
@ -21,10 +24,13 @@ export const userJoin = async (channel, socket) => {
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
Packet: Join,
|
Packet: Join,
|
||||||
validator: async () => true,
|
validator: async () => {
|
||||||
responder: async (packet, socket) => {
|
if (!validateChannel()) {
|
||||||
|
throw new ValidationError({code: 400, reason: 'Malformed channel'});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
responder: async ({data: channel}, socket) => {
|
||||||
const {req} = socket;
|
const {req} = socket;
|
||||||
const {channel} = packet.data;
|
|
||||||
await userJoin(channel, socket.socket);
|
await userJoin(channel, socket.socket);
|
||||||
return channelState(req, channel);
|
return channelState(req, channel);
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||||
import {promisify} from 'util';
|
import {promisify} from 'util';
|
||||||
|
|
||||||
|
import {validateChannel} from '~/common/channel';
|
||||||
import Leave from '~/common/packets/leave.packet';
|
import Leave from '~/common/packets/leave.packet';
|
||||||
|
|
||||||
import {channelUserCounts} from '~/server/entry';
|
import {channelUserCounts} from '~/server/entry';
|
||||||
|
|
||||||
|
import ValidationError from './validation-error';
|
||||||
|
|
||||||
export const userLeave = async (channel, socket) => {
|
export const userLeave = async (channel, socket) => {
|
||||||
const userId = '/r/anonymous' === channel ? 0 : socket.req.userId;
|
const userId = '/r/anonymous' === channel ? 0 : socket.req.userId;
|
||||||
await promisify(socket.leave.bind(socket))(channel);
|
await promisify(socket.leave.bind(socket))(channel);
|
||||||
|
@ -16,9 +19,10 @@ export const userLeave = async (channel, socket) => {
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
Packet: Leave,
|
Packet: Leave,
|
||||||
validator: async () => true,
|
validator: async () => {
|
||||||
responder: async (packet, socket) => {
|
if (!validateChannel()) {
|
||||||
const {channel} = packet.data;
|
throw new ValidationError({code: 400, reason: 'Malformed channel'});
|
||||||
return userLeave(channel, socket);
|
}
|
||||||
},
|
},
|
||||||
|
responder: async ({data: channel}, socket) => userLeave(channel, socket),
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,19 +1,28 @@
|
||||||
import {v4 as uuidv4} from 'uuid';
|
import {v4 as uuidv4} from 'uuid';
|
||||||
|
|
||||||
import {parseChannel} from '~/common/channel';
|
import {parseChannel, validateChannel} from '~/common/channel';
|
||||||
import Message from '~/common/packets/message.packet';
|
import Message from '~/common/packets/message.packet';
|
||||||
|
|
||||||
import {allModels} from '~/server/models/registrar';
|
import {allModels} from '~/server/models/registrar';
|
||||||
|
|
||||||
|
import ValidationError from './validation-error';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
Packet: Message,
|
Packet: Message,
|
||||||
limiter: {points: 10, duration: 15},
|
limiter: {points: 10, duration: 15},
|
||||||
validator: async () => true,
|
validator: async ({data: {channel, message}}) => {
|
||||||
responder: async (packet, socket, fn) => {
|
if (!validateChannel(channel)) {
|
||||||
|
throw new ValidationError({code: 400, reason: 'Malformed channel'});
|
||||||
|
}
|
||||||
|
if (message.length > 1024) {
|
||||||
|
throw new ValidationError({code: 400, reason: 'Your message was a bit too long'});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
responder: async ({data}, socket) => {
|
||||||
const {req} = socket;
|
const {req} = socket;
|
||||||
const {pubClient} = req.adapter;
|
const {pubClient} = req.adapter;
|
||||||
const {User} = allModels();
|
const {User} = allModels();
|
||||||
const {channel, message} = packet.data;
|
const {channel, message} = data;
|
||||||
const {name, type} = parseChannel(`/chat${channel}`);
|
const {name, type} = parseChannel(`/chat${channel}`);
|
||||||
const other = await User.findOne({where: {redditUsername: name}});
|
const other = await User.findOne({where: {redditUsername: name}});
|
||||||
const owner = '/r/anonymous' === channel ? 0 : req.userId;
|
const owner = '/r/anonymous' === channel ? 0 : req.userId;
|
||||||
|
@ -26,7 +35,7 @@ export default {
|
||||||
const key = `${serverChannel}:messages:${uuid}`;
|
const key = `${serverChannel}:messages:${uuid}`;
|
||||||
('u' === type ? [`/user/${other.id}`, `/user/${req.userId}`] : [channel]).forEach((room) => (
|
('u' === type ? [`/user/${other.id}`, `/user/${req.userId}`] : [channel]).forEach((room) => (
|
||||||
socket.to(room, new Message({
|
socket.to(room, new Message({
|
||||||
...packet.data,
|
...data,
|
||||||
channel: 'r' === type
|
channel: 'r' === type
|
||||||
? channel
|
? channel
|
||||||
: `/u/${username === room.substr(3) ? name : username}`,
|
: `/u/${username === room.substr(3) ? name : username}`,
|
||||||
|
|
|
@ -1,34 +1,37 @@
|
||||||
|
import {validateChannel} from '~/common/channel';
|
||||||
import RemoveFavorite from '~/common/packets/remove-favorite.packet';
|
import RemoveFavorite from '~/common/packets/remove-favorite.packet';
|
||||||
|
|
||||||
import {allModels} from '~/server/models/registrar';
|
import {allModels} from '~/server/models/registrar';
|
||||||
|
|
||||||
|
import ValidationError from './validation-error';
|
||||||
|
|
||||||
export const removeFavoritedUser = async (socket, user, other) => {
|
export const removeFavoritedUser = async (socket, user, other) => {
|
||||||
const {Favorite} = allModels();
|
const {Favorite} = allModels();
|
||||||
const favorites = await user.getFavorites();
|
const favorite = await Favorite.findOne(
|
||||||
const toRemove = favorites.find(({channel}) => channel === `/u/${other.redditUsername}`);
|
{where: {channel: `/u/${other.redditUsername}`, user_id: user.id}},
|
||||||
if (toRemove) {
|
);
|
||||||
await Favorite.destroy({
|
if (favorite) {
|
||||||
where: {
|
await Favorite.destroy({where: {id: favorite.id}});
|
||||||
id: toRemove.id,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
socket.to(`/user/${user.id}`, new RemoveFavorite(`/u/${other.redditUsername}`));
|
socket.to(`/user/${user.id}`, new RemoveFavorite(`/u/${other.redditUsername}`));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
Packet: RemoveFavorite,
|
Packet: RemoveFavorite,
|
||||||
validator: async () => true,
|
validator: async ({data: channel}, {req: {user}}) => {
|
||||||
|
const {Favorite} = allModels();
|
||||||
|
if (!validateChannel()) {
|
||||||
|
throw new ValidationError({code: 400, reason: 'Malformed channel.'});
|
||||||
|
}
|
||||||
|
if (0 === await Favorite.count({where: {user_id: user.id, channel}})) {
|
||||||
|
throw new ValidationError({code: 400, reason: 'No such favorite existed.'});
|
||||||
|
}
|
||||||
|
},
|
||||||
responder: async (packet, socket) => {
|
responder: async (packet, socket) => {
|
||||||
const {req} = socket;
|
const {req} = socket;
|
||||||
const {Favorite} = allModels();
|
const {Favorite} = allModels();
|
||||||
const favorites = await req.user.getFavorites();
|
const favorite = await Favorite.findOne({where: {channel: packet.data, user_id: req.user.id}});
|
||||||
const toRemove = favorites.find(({channel}) => channel === packet.data);
|
await Favorite.destroy({where: {id: favorite.id}});
|
||||||
await Favorite.destroy({
|
|
||||||
where: {
|
|
||||||
id: toRemove.id,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
socket.to(`/user/${req.userId}`, packet);
|
socket.to(`/user/${req.userId}`, packet);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,12 +6,26 @@ import {allModels} from '~/server/models/registrar';
|
||||||
|
|
||||||
import {removeFavoritedUser} from './remove-favorite';
|
import {removeFavoritedUser} from './remove-favorite';
|
||||||
|
|
||||||
|
import ValidationError from './validation-error';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
Packet: RemoveFriend,
|
Packet: RemoveFriend,
|
||||||
validator: async () => true,
|
validator: async ({data: id}, {req: {userId}}) => {
|
||||||
responder: async (packet, socket) => {
|
const {Friendship} = allModels();
|
||||||
|
const hasFriendship = !!await Friendship.count({
|
||||||
|
where: {
|
||||||
|
[Op.or]: [
|
||||||
|
{[Op.and]: [{addeeId: userId}, {adderId: id}]},
|
||||||
|
{[Op.and]: [{addeeId: id}, {adderId: userId}]},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!hasFriendship) {
|
||||||
|
throw new ValidationError({code: 400, reason: 'Malformed friendship.'});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
responder: async ({data: id}, socket) => {
|
||||||
const {req} = socket;
|
const {req} = socket;
|
||||||
const id = packet.data;
|
|
||||||
const {Friendship, User} = allModels();
|
const {Friendship, User} = allModels();
|
||||||
await Friendship.destroy({
|
await Friendship.destroy({
|
||||||
where: {
|
where: {
|
||||||
|
@ -24,7 +38,9 @@ export default {
|
||||||
socket.to(`/user/${id}`, new RemoveFriend(req.userId));
|
socket.to(`/user/${id}`, new RemoveFriend(req.userId));
|
||||||
socket.to(`/user/${req.userId}`, new RemoveFriend(id));
|
socket.to(`/user/${req.userId}`, new RemoveFriend(id));
|
||||||
const user = await User.findByPk(id);
|
const user = await User.findByPk(id);
|
||||||
removeFavoritedUser(socket, user, req.user);
|
return Promise.all([
|
||||||
removeFavoritedUser(socket, req.user, user);
|
removeFavoritedUser(socket, user, req.user),
|
||||||
|
removeFavoritedUser(socket, req.user, user),
|
||||||
|
]);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,15 +2,28 @@ import Unblock from '~/common/packets/unblock.packet';
|
||||||
|
|
||||||
import {allModels} from '~/server/models/registrar';
|
import {allModels} from '~/server/models/registrar';
|
||||||
|
|
||||||
|
import ValidationError from './validation-error';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
Packet: Unblock,
|
Packet: Unblock,
|
||||||
validator: async () => true,
|
validator: async ({data: blocked}, {req: {userId}}) => {
|
||||||
responder: async (packet, socket) => {
|
const {Block: BlockModel} = allModels();
|
||||||
|
const hasBlock = !!await BlockModel.count({
|
||||||
|
where: {
|
||||||
|
blocked,
|
||||||
|
user_id: userId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!hasBlock) {
|
||||||
|
throw new ValidationError({code: 400, reason: "Wasn't blocking."});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
responder: async ({data: blocked}, socket) => {
|
||||||
const {req} = socket;
|
const {req} = socket;
|
||||||
const {Block: BlockModel} = allModels();
|
const {Block: BlockModel} = allModels();
|
||||||
await BlockModel.destroy({
|
await BlockModel.destroy({
|
||||||
where: {
|
where: {
|
||||||
blocked: packet.data,
|
blocked,
|
||||||
user_id: req.userId,
|
user_id: req.userId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -1 +1,9 @@
|
||||||
export default class ValidationError extends Error {}
|
export default class ValidationError extends Error {
|
||||||
|
|
||||||
|
constructor(...args) {
|
||||||
|
const [payload, ...after] = args;
|
||||||
|
super(...after);
|
||||||
|
this.payload = payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import createLimiter from './limiter';
|
||||||
import * as PacketHandlers from './packet';
|
import * as PacketHandlers from './packet';
|
||||||
import {userJoin} from './packet/join';
|
import {userJoin} from './packet/join';
|
||||||
import {userLeave} from './packet/leave';
|
import {userLeave} from './packet/leave';
|
||||||
|
import ValidationError from './packet/validation-error';
|
||||||
import passport from './passport';
|
import passport from './passport';
|
||||||
import createRedisClient from './redis';
|
import createRedisClient from './redis';
|
||||||
import session from './session';
|
import session from './session';
|
||||||
|
@ -72,6 +73,10 @@ export function createSocketServer(httpServer) {
|
||||||
fn(undefined, await responder(packet, socket));
|
fn(undefined, await responder(packet, socket));
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
|
if (error instanceof ValidationError) {
|
||||||
|
fn(error.payload);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
fn({code: 500});
|
fn({code: 500});
|
||||||
throw error;
|
throw error;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user