Added celebreate validation

This commit is contained in:
Max Campbell 2022-03-20 14:44:44 -10:00
parent c9f84fe69c
commit 7b2baad782
7 changed files with 306 additions and 210 deletions

View file

@ -13,6 +13,7 @@
"dependencies": { "dependencies": {
"bcrypt": "^5.0.1", "bcrypt": "^5.0.1",
"body-parser": "^1.18.2", "body-parser": "^1.18.2",
"celebrate": "^15.0.1",
"cors": "^2.8.5", "cors": "^2.8.5",
"dotenv": "^16.0.0", "dotenv": "^16.0.0",
"express": "^4.16.2", "express": "^4.16.2",

View file

@ -1,85 +1,104 @@
import { Router } from 'express' import { Router } from "express";
import { genSalt, hash, compare } from "bcrypt" import { genSalt, hash, compare } from "bcrypt";
import { User } from '../../lib/models/User' import { User } from "../../lib/models/User";
import { sign } from 'jsonwebtoken' import { sign } from "jsonwebtoken";
import config from '../../lib/config' import config from "../../lib/config";
import jwt from '../../lib/middleware/jwt' import jwt from "../../lib/middleware/jwt";
import { celebrate, Joi } from "celebrate";
const NO_EMPTY_SPACE_REGEX = /^\S*$/ const NO_EMPTY_SPACE_REGEX = /^\S*$/;
export const auth = Router() export const auth = Router();
const validateAuthPayload = (username: string, password: string): void => { const validateAuthPayload = (username: string, password: string): void => {
if (!NO_EMPTY_SPACE_REGEX.test(username) || password.length < 6) { if (!NO_EMPTY_SPACE_REGEX.test(username) || password.length < 6) {
throw new Error("Authentication data does not fulfill requirements") throw new Error("Authentication data does not fulfill requirements");
} }
} };
auth.post('/signup', async (req, res, next) => { auth.post(
"/signup",
celebrate({
params: {
username: Joi.string().required(),
password: Joi.string().required(),
},
}),
async (req, res, next) => {
try { try {
validateAuthPayload(req.body.username, req.body.password) validateAuthPayload(req.body.username, req.body.password);
const username = req.body.username.toLowerCase(); const username = req.body.username.toLowerCase();
const existingUser = await User.findOne({ where: { username: username } }) const existingUser = await User.findOne({
if (existingUser) { where: { username: username },
throw new Error("Username already exists") });
} if (existingUser) {
throw new Error("Username already exists");
}
const salt = await genSalt(10) const salt = await genSalt(10);
const user = { const user = {
username: username as string, username: username as string,
password: await hash(req.body.password, salt) password: await hash(req.body.password, salt),
} };
const created_user = await User.create(user); const created_user = await User.create(user);
const token = generateAccessToken(created_user.id); const token = generateAccessToken(created_user.id);
res.status(201).json({ token: token, userId: created_user.id }) res.status(201).json({ token: token, userId: created_user.id });
} catch (e) { } catch (e) {
next(e); next(e);
} }
}); }
);
auth.post('/signin', async (req, res, next) => { auth.post(
const error = "User does not exist or password is incorrect" "/signin",
celebrate({
params: {
username: Joi.string().required(),
password: Joi.string().required(),
},
}),
async (req, res, next) => {
const error = "User does not exist or password is incorrect";
const errorToThrow = new Error(error); const errorToThrow = new Error(error);
try { try {
if (!req.body.username || !req.body.password) { if (!req.body.username || !req.body.password) {
throw errorToThrow throw errorToThrow;
} }
const username = req.body.username.toLowerCase(); const username = req.body.username.toLowerCase();
const user = await User.findOne({ where: { username: username } }); const user = await User.findOne({ where: { username: username } });
if (!user) { if (!user) {
throw errorToThrow throw errorToThrow;
} }
const password_valid = await compare(req.body.password, user.password);
if (password_valid) {
const token = generateAccessToken(user.id);
res.status(200).json({ token: token, userId: user.id });
} else {
throw errorToThrow
}
const password_valid = await compare(req.body.password, user.password);
if (password_valid) {
const token = generateAccessToken(user.id);
res.status(200).json({ token: token, userId: user.id });
} else {
throw errorToThrow;
}
} catch (e) { } catch (e) {
next(e); next(e);
} }
}); }
);
function generateAccessToken(id: string) { function generateAccessToken(id: string) {
return sign({ id: id }, config.jwt_secret, { expiresIn: '2d' }); return sign({ id: id }, config.jwt_secret, { expiresIn: "2d" });
} }
auth.get("/verify-token", jwt, async (req, res, next) => { auth.get("/verify-token", jwt, async (req, res, next) => {
try { try {
res.status(200).json({ res.status(200).json({
message: "You are authenticated" message: "You are authenticated",
}) });
} } catch (e) {
catch (e) { next(e);
next(e); }
} });
})

View file

@ -1,29 +1,36 @@
import { Router } from 'express' import { celebrate, Joi } from "celebrate";
import { Router } from "express";
// import { Movie } from '../models/Post' // import { Movie } from '../models/Post'
import { File } from '../../lib/models/File' import { File } from "../../lib/models/File";
export const files = Router() export const files = Router();
files.get("/raw/:id", async (req, res, next) => { files.get(
"/raw/:id",
celebrate({
params: {
id: Joi.string().required(),
},
}),
async (req, res, next) => {
try { try {
const file = await File.findOne({ const file = await File.findOne({
where: { where: {
id: req.params.id id: req.params.id,
}, },
attributes: ["title", "content"], attributes: ["title", "content"],
}) });
// TODO: fix post inclusion // TODO: fix post inclusion
// if (file?.post.visibility === 'public' || file?.post.visibility === 'unlisted') { // if (file?.post.visibility === 'public' || file?.post.visibility === 'unlisted') {
res.setHeader("Cache-Control", "public, max-age=86400"); res.setHeader("Cache-Control", "public, max-age=86400");
res.json(file); res.json(file);
// } else { // } else {
// TODO: should this be `private, `? // TODO: should this be `private, `?
// res.setHeader("Cache-Control", "max-age=86400"); // res.setHeader("Cache-Control", "max-age=86400");
// res.json(file); // res.json(file);
// } // }
} catch (e) {
next(e);
} }
catch (e) { }
next(e); );
}
});

View file

@ -1,4 +1,4 @@
export { auth } from './auth'; export { auth } from "./auth";
export { posts } from './posts'; export { posts } from "./posts";
export { users } from './users'; export { users } from "./users";
export { files } from './files'; export { files } from "./files";

View file

@ -1,97 +1,116 @@
import { Router } from 'express' import { Router } from "express";
// import { Movie } from '../models/Post' // import { Movie } from '../models/Post'
import { File } from '../../lib/models/File' import { File } from "../../lib/models/File";
import { Post } from '../../lib/models/Post'; import { Post } from "../../lib/models/Post";
import jwt, { UserJwtRequest } from '../../lib/middleware/jwt'; import jwt, { UserJwtRequest } from "../../lib/middleware/jwt";
import * as crypto from "crypto"; import * as crypto from "crypto";
import { User } from '../../lib/models/User'; import { User } from "../../lib/models/User";
import { celebrate, Joi } from "celebrate";
export const posts = Router() export const posts = Router();
posts.post('/create', jwt, async (req, res, next) => { posts.post(
"/create",
jwt,
celebrate({
body: {
title: Joi.string().required(),
files: Joi.any().required(),
visibility: Joi.string().required(),
userId: Joi.string().required(),
},
}),
async (req, res, next) => {
console.log(req.body);
try { try {
if (!req.body.files) { // Create the "post" object
throw new Error("Please provide files.") const newPost = new Post({
} title: req.body.title,
visibility: req.body.visibility,
});
if (!req.body.title) { await newPost.save();
throw new Error("Please provide a title.") await newPost.$add("users", req.body.userId);
} const newFiles = await Promise.all(
req.body.files.map(async (file) => {
// Establish a "file" for each file in the request
const newFile = new File({
title: file.title,
content: file.content,
sha: crypto
.createHash("sha256")
.update(file.content)
.digest("hex")
.toString(),
});
if (!req.body.userId) { await newFile.$set("user", req.body.userId);
throw new Error("No user id provided.") await newFile.$set("post", newPost.id);
} await newFile.save();
return newFile;
if (!req.body.visibility) {
throw new Error("Please provide a visibility.")
}
// Create the "post" object
const newPost = new Post({
title: req.body.title,
visibility: req.body.visibility,
}) })
);
await newPost.save() await Promise.all(
await newPost.$add('users', req.body.userId); newFiles.map((file) => {
const newFiles = await Promise.all(req.body.files.map(async (file) => { newPost.$add("files", file.id);
// Establish a "file" for each file in the request newPost.save();
const newFile = new File({ })
title: file.title, );
content: file.content,
sha: crypto.createHash('sha256').update(file.content).digest('hex').toString(),
})
await newFile.$set("user", req.body.userId); res.json(newPost);
await newFile.$set("post", newPost.id);
await newFile.save();
return newFile;
}))
await Promise.all(newFiles.map((file) => {
newPost.$add("files", file.id);
newPost.save();
}))
res.json(newPost);
} catch (e) { } catch (e) {
next(e); next(e);
} }
}); }
);
posts.get("/:id", async (req: UserJwtRequest, res, next) => { posts.get(
"/:id",
celebrate({
params: {
id: Joi.string().required(),
},
}),
async (req: UserJwtRequest, res, next) => {
try { try {
const post = await Post.findOne({ const post = await Post.findOne({
where: { where: {
id: req.params.id id: req.params.id,
}, },
include: [ include: [
{ {
model: File, model: File,
as: "files", as: "files",
attributes: ["id", "title", "content", "sha", "createdAt", "updatedAt"], attributes: [
}, "id",
{ "title",
model: User, "content",
as: "users", "sha",
attributes: ["id", "username"], "createdAt",
}, "updatedAt",
] ],
}) },
{
model: User,
as: "users",
attributes: ["id", "username"],
},
],
});
if (post?.visibility === 'public' || post?.visibility === 'unlisted') { if (post?.visibility === "public" || post?.visibility === "unlisted") {
res.setHeader("Cache-Control", "public, max-age=86400"); res.setHeader("Cache-Control", "public, max-age=86400");
res.json(post); res.json(post);
} else { } else {
// TODO: should this be `private, `? // TODO: should this be `private, `?
res.setHeader("Cache-Control", "max-age=86400"); res.setHeader("Cache-Control", "max-age=86400");
jwt(req, res, () => { jwt(req, res, () => {
res.json(post); res.json(post);
}); });
} }
} catch (e) {
next(e);
} }
catch (e) { }
next(e); );
}
});

View file

@ -1,46 +1,47 @@
import { Router } from 'express' import { Router } from "express";
// import { Movie } from '../models/Post' import { User } from "../../lib/models/User";
import { User } from '../../lib/models/User' import { File } from "../../lib/models/File";
import { File } from '../../lib/models/File' import jwt, { UserJwtRequest } from "../../lib/middleware/jwt";
import jwt, { UserJwtRequest } from '../../lib/middleware/jwt' import { Post } from "../../lib/models/Post";
import { Post } from '../../lib/models/Post'
export const users = Router() export const users = Router();
users.get('/', jwt, async (req, res, next) => { users.get("/", jwt, async (req, res, next) => {
try { try {
const allUsers = await User.findAll() const allUsers = await User.findAll();
res.json(allUsers) res.json(allUsers);
} catch (error) { } catch (error) {
next(error) next(error);
} }
}) });
users.get("/mine", jwt, async (req: UserJwtRequest, res, next) => { users.get("/mine", jwt, async (req: UserJwtRequest, res, next) => {
if (!req.user) { if (!req.user) {
return res.status(401).json({ error: "Unauthorized" }) return res.status(401).json({ error: "Unauthorized" });
} }
try { try {
const user = await User.findByPk(req.user.id, { const user = await User.findByPk(req.user.id, {
include: [ include: [
{ {
model: Post, model: Post,
as: "posts", as: "posts",
include: [ include: [
{ {
model: File, model: File,
as: "files" as: "files",
} },
] ],
}, },
], ],
}) });
if (!user) { if (!user) {
return res.status(404).json({ error: "User not found" }) return res.status(404).json({ error: "User not found" });
}
return res.json(user.posts?.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime()))
} catch (error) {
next(error)
} }
}) return res.json(
user.posts?.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime())
);
} catch (error) {
next(error);
}
});

View file

@ -35,6 +35,18 @@
dependencies: dependencies:
"@cspotcode/source-map-consumer" "0.8.0" "@cspotcode/source-map-consumer" "0.8.0"
"@hapi/hoek@^9.0.0":
version "9.2.1"
resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.2.1.tgz#9551142a1980503752536b5050fd99f4a7f13b17"
integrity sha512-gfta+H8aziZsm8pZa0vj04KO6biEiisppNgA1kbJvFrrWu9Vm7eaUEy76DIxsuTaWvti5fkJVhllWc6ZTE+Mdw==
"@hapi/topo@^5.0.0":
version "5.1.0"
resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-5.1.0.tgz#dc448e332c6c6e37a4dc02fd84ba8d44b9afb012"
integrity sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==
dependencies:
"@hapi/hoek" "^9.0.0"
"@mapbox/node-pre-gyp@^1.0.0": "@mapbox/node-pre-gyp@^1.0.0":
version "1.0.8" version "1.0.8"
resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.8.tgz#32abc8a5c624bc4e46c43d84dfb8b26d33a96f58" resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.8.tgz#32abc8a5c624bc4e46c43d84dfb8b26d33a96f58"
@ -50,6 +62,23 @@
semver "^7.3.5" semver "^7.3.5"
tar "^6.1.11" tar "^6.1.11"
"@sideway/address@^4.1.3":
version "4.1.3"
resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.3.tgz#d93cce5d45c5daec92ad76db492cc2ee3c64ab27"
integrity sha512-8ncEUtmnTsMmL7z1YPB47kPUq7LpKWJNFPsRzHiIajGC5uXlWGn+AmkYPcHNl8S4tcEGx+cnORnNYaw2wvL+LQ==
dependencies:
"@hapi/hoek" "^9.0.0"
"@sideway/formula@^3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.0.tgz#fe158aee32e6bd5de85044be615bc08478a0a13c"
integrity sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg==
"@sideway/pinpoint@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@sideway/pinpoint/-/pinpoint-2.0.0.tgz#cff8ffadc372ad29fd3f78277aeb29e632cc70df"
integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==
"@sindresorhus/is@^0.14.0": "@sindresorhus/is@^0.14.0":
version "0.14.0" version "0.14.0"
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea"
@ -475,6 +504,15 @@ caseless@~0.12.0:
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=
celebrate@^15.0.1:
version "15.0.1"
resolved "https://registry.yarnpkg.com/celebrate/-/celebrate-15.0.1.tgz#767fa6268f7446b473ea69cd6326ce4dffee4d1e"
integrity sha512-K2y221k10u+K2t9w25802qXh8h1mVWZf+6pl7zHdlhhwzrOSQFnnw+GsR8k17oyn4Y3fVErBGsO/+CeW8N7aRQ==
dependencies:
escape-html "1.0.3"
joi "17.x.x"
lodash "4.17.x"
chalk@^2.0.0, chalk@^2.3.0, chalk@^2.4.2: chalk@^2.0.0, chalk@^2.3.0, chalk@^2.4.2:
version "2.4.2" version "2.4.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
@ -822,7 +860,7 @@ escape-goat@^2.0.0:
resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675" resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675"
integrity sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q== integrity sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==
escape-html@~1.0.3: escape-html@1.0.3, escape-html@~1.0.3:
version "1.0.3" version "1.0.3"
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=
@ -1377,6 +1415,17 @@ jake@^10.6.1:
filelist "^1.0.1" filelist "^1.0.1"
minimatch "^3.0.4" minimatch "^3.0.4"
joi@17.x.x:
version "17.6.0"
resolved "https://registry.yarnpkg.com/joi/-/joi-17.6.0.tgz#0bb54f2f006c09a96e75ce687957bd04290054b2"
integrity sha512-OX5dG6DTbcr/kbMFj0KGYxuew69HPcAE3K/sZpEV2nP6e/j/C0HV+HNiBPCASxdx5T7DMoa0s8UeHWMnb6n2zw==
dependencies:
"@hapi/hoek" "^9.0.0"
"@hapi/topo" "^5.0.0"
"@sideway/address" "^4.1.3"
"@sideway/formula" "^3.0.0"
"@sideway/pinpoint" "^2.0.0"
js-tokens@^4.0.0: js-tokens@^4.0.0:
version "4.0.0" version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
@ -1521,7 +1570,7 @@ lodash.once@^4.0.0:
resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w= integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=
lodash@^4.17.20, lodash@^4.17.21: lodash@4.17.x, lodash@^4.17.20, lodash@^4.17.21:
version "4.17.21" version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==