i changed a lot of shit

This commit is contained in:
Tyler Murphy 2023-01-23 21:40:41 -05:00
parent 4b0a9dd484
commit 15f2b32511
17 changed files with 1068 additions and 563 deletions

153
index.js
View file

@ -1,96 +1,135 @@
const express = require('express')
const cookie = require('cookie-parser')
const app = express()
const port = 8080
const cache = require('./src/cache')
const con = require('./src/console')
const auth = require('./src/api/auth')
const pages = require('./src/api/pages')
const posts = require('./src/api/posts')
const users = require('./src/api/users')
app.set('trust proxy', true)
app.use(cookie())
app.use(express.json());
app.use(express.static('public'))
app.use(require('cookie-parser')())
app.use(express.json());
const database = require('./src/database.js')
const con = require('./src/console.js')
const api = require('./src/api.js')
app.use((req, res, next) => {
var ip = req.headers['x-real-ip'] || req.connection.remoteAddress;
var ip = req.headers['x-real-ip'] || req.socket.remoteAddress;
if (req.path !== '/console') {
const public = { ... req.body }
if (public.password !== undefined) {
public.password = '********'
let body = { ...req.body }
if (body.password !== undefined) {
body.password = '********'
}
console.log(ip, req.method, req.path, public)
con.requests.push({ip: ip, method: req.method, path: req.path, body: public})
con.log(
ip,
req.method,
req.path,
body
)
}
next()
})
app.get('/', (req, res) => {
const cookies = req.cookies;
app.use((req, res, next) => {
if (req.path.startsWith('/api/auth')) {
next()
return
}
const cookies = req.cookies
if (cookies === undefined || cookies.auth === undefined) {
res.redirect('/login')
if (req.method !== 'GET' && req.path.startsWith('/api')) {
res.status(401).send({msg: 'Unauthorized'})
return
}
const user = database.auth(req.cookies.auth)
if (user === undefined) {
res.redirect('/login')
next()
return
}
res.redirect('/home')
const user = cache.auth(req.cookies.auth)
if (user !== undefined) {
res.locals.user = user
} else if (req.method !== 'GET' && req.path.startsWith('/api')) {
res.status(401).send({msg: 'Unauthorized'})
return
}
next()
})
app.get('/login', (req, res) => {
const cookies = req.cookies;
if (cookies === undefined || cookies.auth === undefined) {
res.sendFile('login.html', { root: './public' })
return
}
const user = database.auth(req.cookies.auth)
if (user === undefined) {
res.sendFile('login.html', { root: './public' })
return
}
res.redirect('/home')
app.use('/api/auth', auth)
app.use('/api/posts', posts)
app.use('/api/users', users)
app.use('/', pages)
app.get('/console', (req, res) => {
res.send(con.render())
})
app.get('/home', (req, res) => {
const cookies = req.cookies;
if (cookies === undefined || cookies.auth === undefined) {
res.redirect('/login')
return
}
const user = database.auth(req.cookies.auth)
if (user === undefined) {
res.redirect('/login')
return
}
res.sendFile('home.html', { root: './public' })
})
app.get('/people', (req, res) => {
res.sendFile('people.html', { root: './public' })
})
app.get('/profile', (req, res) => {
res.sendFile('profile.html', { root: './public' })
})
app.use('/api', api);
app.use('/console', con.router);
app.use((req, res, next) => {
res.status(404).sendFile('404.html', { root: './public' })
})
app.use((err, req, res, next) => {
if (err instanceof SyntaxError && err.status === 400 && 'body' in err) {
res.status(400).send({ msg: 'Invalid json body' })
return
}
console.error(err)
res.status(500).send({ msg: 'Internal server error' })
})
app.listen(port, () => {
const cron = require('node-cron').schedule('*/5 * * * *', () => {
con.msg('Writing cache to database')
cache.dump()
})
const port = 8080
const server = app.listen(port, () => {
console.log(`App listening on port http://127.0.0.1:${port}`)
})
const close = () => {
console.log('Writing cache to database')
cache.dump()
console.log('Stopping cron jobs')
cron.stop()
server.close(() => {
console.log('HTTP server closed')
})
}
process.on('SIGINT', close)
process.on('SIGTERM', close)
process.on('SIGQUIT', close)

61
package-lock.json generated
View file

@ -12,10 +12,9 @@
"better-sqlite3": "^8.0.1",
"cheerio": "^1.0.0-rc.12",
"cookie-parser": "^1.4.6",
"crypto": "^1.0.1",
"dotenv": "^16.0.3",
"express": "^4.18.2",
"express-rate-limit": "^6.7.0"
"express-rate-limit": "^6.7.0",
"node-cron": "^3.0.2"
}
},
"node_modules/accepts": {
@ -238,12 +237,6 @@
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
},
"node_modules/crypto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz",
"integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==",
"deprecated": "This package is no longer supported. It's now a built-in Node module. If you've depended on crypto, you should switch to the one that's built-in."
},
"node_modules/css-select": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz",
@ -376,14 +369,6 @@
"url": "https://github.com/fb55/domutils?sponsor=1"
}
},
"node_modules/dotenv": {
"version": "16.0.3",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz",
"integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==",
"engines": {
"node": ">=12"
}
},
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@ -781,6 +766,17 @@
"node": ">=10"
}
},
"node_modules/node-cron": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.2.tgz",
"integrity": "sha512-iP8l0yGlNpE0e6q1o185yOApANRe47UPbLf4YxfbiNHt/RU5eBcGB/e0oudruheSf+LQeDMezqC5BVAb5wwRcQ==",
"dependencies": {
"uuid": "8.3.2"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/nth-check": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
@ -1207,6 +1203,14 @@
"node": ">= 0.4.0"
}
},
"node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
@ -1384,11 +1388,6 @@
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
},
"crypto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz",
"integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig=="
},
"css-select": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz",
@ -1475,11 +1474,6 @@
"domhandler": "^5.0.1"
}
},
"dotenv": {
"version": "16.0.3",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz",
"integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ=="
},
"ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@ -1766,6 +1760,14 @@
"semver": "^7.3.5"
}
},
"node-cron": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.2.tgz",
"integrity": "sha512-iP8l0yGlNpE0e6q1o185yOApANRe47UPbLf4YxfbiNHt/RU5eBcGB/e0oudruheSf+LQeDMezqC5BVAb5wwRcQ==",
"requires": {
"uuid": "8.3.2"
}
},
"nth-check": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
@ -2068,6 +2070,11 @@
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
"integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="
},
"uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
},
"vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",

View file

@ -12,9 +12,8 @@
"better-sqlite3": "^8.0.1",
"cheerio": "^1.0.0-rc.12",
"cookie-parser": "^1.4.6",
"crypto": "^1.0.1",
"dotenv": "^16.0.3",
"express": "^4.18.2",
"express-rate-limit": "^6.7.0"
"express-rate-limit": "^6.7.0",
"node-cron": "^3.0.2"
}
}

View file

@ -180,14 +180,3 @@ body {
background-color: #f0f2f5;
font-family: sfpro;
}
#load {
width: 100%;
display: flex;
justify-content: center;
padding-bottom: 20px;
}
#load a:hover {
border-bottom: #606770 1px solid;
}

View file

@ -294,3 +294,14 @@ form {
margin-left: 10px;
width: 100%;
}
#load {
width: 100%;
display: flex;
justify-content: center;
padding-bottom: 20px;
}
#load a:hover {
border-bottom: #606770 1px solid;
}

View file

@ -20,8 +20,8 @@ const login = async (email, password) => {
return await request('/auth/login', {email, password})
}
const register = async (first, last, email, password, gender, month, day, year) => {
return await request('/auth/register', {first, last, email, password, gender, month, day, year})
const register = async (firstname, lastname, email, password, gender, month, day, year) => {
return await request('/auth/register', {firstname, lastname, email, password, gender, month, day, year})
}
const loadposts = async (page) => {
@ -36,12 +36,12 @@ const loadusers = async (ids) => {
return await request('/users/load', {ids})
}
const loadallusers = async () => {
return await request('/users/all', {})
const loaduserspage = async (page) => {
return await request('/users/page', {page})
}
const loadself = async () => {
return await request("/auth/self", {})
return await request("/users/self", {})
}
const postcomment = async (id, content) => {

View file

@ -44,13 +44,13 @@ function parsePost(post) {
${post.content.replace(/\n/g,'<br>')}
</p>
<span class="gtext mtext">
${post.likes.length} Likes
${Object.keys(post.likes).length} Likes
</span>
<div class="fullline nb"></div>
<div class="postbuttons">
<span onclick="like(this)">
<i class="icons like ${post.likes.includes(data.user.id) ? 'blue' : ''}"></i>
<span class="bold ${post.likes.includes(data.user.id) ? 'blue' : ''}">Like</span>
<i class="icons like ${post.likes[data.user.id] ? 'blue' : ''}"></i>
<span class="bold ${post.likes[data.user.id] ? 'blue' : ''}">Like</span>
</span>
<span onclick="this.parentElement.parentElement.getElementsByClassName('newcomment')[0].focus()">
<i class="icons comm"></i>
@ -87,15 +87,10 @@ function getPost(id) {
async function like(span) {
const id = parseInt(span.parentElement.parentElement.getAttribute('postid'))
const post = data.posts[getPost(id)]
const index = post.likes.indexOf(data.user.id)
const state = index === -1;
const response = await postlike(id, state)
const current = post.likes[data.user.id]
const response = await postlike(id, !current)
if (response.status != 200) return;
if (index == -1) {
post.likes.push(data.user.id)
} else {
post.likes.splice(index, 1)
}
post.likes[data.user.id] = !current
render()
}
@ -127,7 +122,7 @@ async function post() {
}
error.innerHTML = '';
data.posts.unshift({
id: data.posts[0].id + 1,
id: response.msg,
user: data.user.id,
date: Date.now(),
content: text,
@ -182,7 +177,7 @@ function render() {
const load = `
<div id="load">
<a class="bold gtext" onclick="load()">Load More posts</a>
<a class="bold gtext" onclick="load()">Load more posts</a>
</div>
`
@ -203,9 +198,9 @@ const data = {
async function load() {
const posts = (await loadposts(page)).json
if (posts.length === 0) {
page = -1;
page = -1
} else {
page++;
page++
}
data.posts.push(... posts)
const batch = []

View file

@ -16,6 +16,7 @@ function parseUser(user) {
<span class="gtext">Joined ${parseDate(new Date(user.date))}</span>
<span class="gtext">Gender: ${user.gender}</span>
<span class="gtext">Birthday: ${user.month + ' ' + user.day + ', ' + user.year}</span>
<span class="gtext" style="margin-bottom: -100px;">User ID: ${user.id}</span>
</div>
</a>
`
@ -29,17 +30,34 @@ function render() {
</div>
`
add(html, 'usres')
add(html, 'users')
const load = `
<div id="load">
<a class="bold gtext" onclick="load()">Load more users</a>
</div>
`
if (page !== -1) {
add(load, 'load')
} else {
remove('load')
}
}
var page = 0
var data = {
users: []
}
async function load() {
const users = (await loadallusers()).json
console.log(users)
data.users = users
const users = (await loaduserspage(page)).json
if (users.length === 0) {
page = -1
} else {
page++
}
data.users.push(... users)
render()
}

View file

@ -1,238 +0,0 @@
const express = require('express')
const router = express.Router()
const database = require('./database.js')
const cheerio = require('cheerio');
const check = (test, type) => {
return text === undefined || text === null || typeof test !== type
}
const parseText = (test) => {
if (typeof test !== 'string') return undefined;
const $ = cheerio.load(test)
return $("body").html()
}
const text = (test, min, max) => {
return check(test, 'string') || test.length > max || test.length < min
}
router.get('/', (req, res) => {
res.status(200).send( {msg: 'xssbook api'} )
})
router.post('/auth/register', (req, res) => {
const first = parseText(req.body.first);
if (text(first, 1, 20)) {
res.status(400).send( {msg: 'Invalid first name'} ); return;
}
const last = parseText(req.body.last);
if (text(last, 1, 20)) {
res.status(400).send( {msg: 'Invalid last name'} ); return;
}
const email = parseText(req.body.email);
if (text(email, 1, 50)) {
res.status(400).send( {msg: 'Invalid email'} ); return;
}
const password = req.body.password;
if (text(password, 1, 50)) {
res.status(400).send( {msg: 'Invalid password'} ); return;
}
const gender = parseText(req.body.gender);
if (text(gender, 1, 100)) {
res.status(400).send( {msg: 'Invalid gender'} ); return;
}
const month = parseText(req.body.month);
if (text(month, 1, 10)) {
res.status(400).send( {msg: 'Invalid month'} ); return;
}
const day = req.body.day;
if (check(day, 'number')) {
res.status(400).send( {msg: 'Invalid day'} ); return;
}
const year = req.body.year;
if (check(year, 'number')) {
res.status(400).send( {msg: 'Invalid year'} ); return;
}
let exists = database.getUserByEmail(email);
if (exists !== undefined) {
res.status(400).send( {msg: 'Email is already in use'} ); return;
}
exists = database.getUserByPassword(password);
if (exists !== undefined) {
res.status(400).send( {msg: `Password is already in use by ${exists.email}`} ); return;
}
const key = database.register(first, last, email, password, gender, month, day, year);
if (key === undefined) {
res.status(500).send( {msg: 'Failed to register user'} ); return;
}
res.status(200).cookie('auth', key, { maxAge: 365 * 24 * 60 * 60 * 1000, sameSite: 'strict' }).send({msg: 'Successfully registered new user'})
})
router.post('/auth/login', (req, res) => {
const email = req.body.email
if (check(email, 'string')) {
res.status(400).send( {msg: 'Invalid email'} ); return;
}
const password = req.body.password
if (check(password, 'string')) {
res.status(400).send( {msg: 'Invalid password'} ); return;
}
const key = database.login(email, password)
if (key === undefined) {
res.status(400).send( {msg: 'Invalid login combination'} ); return;
}
res.status(200).cookie('auth', key, { maxAge: 365 * 24 * 60 * 60 * 1000, sameSite: 'strict' }).send({msg: 'Successfully logged in'})
})
router.post('/auth/self', (req, res) => {
const cookies = req.cookies;
if (cookies === undefined || cookies.auth === undefined) {
res.status(401).send({msg: 'Unauthorized'}); return;
}
const user = database.auth(req.cookies.auth)
if (user === undefined) {
res.status(401).send({msg: 'Unauthorized'}); return;
}
delete user.password
res.status(200).send(user)
})
router.post('/posts/create', (req, res) => {
const content = parseText(req.body.content)
if (text(content, 1, 1000)) {
res.status(400).send({msg: 'Invalid content'}); return;
}
const cookies = req.cookies;
if (cookies === undefined || cookies.auth === undefined) {
res.status(401).send({msg: 'Unauthorized'}); return;
}
const user = database.auth(req.cookies.auth)
if (user === undefined) {
res.status(401).send({msg: 'Unauthorized'}); return;
}
const success = database.addPost(user.id, content)
if (!success) {
res.status(500).send({msg: 'Failed to create post'})
}
res.status(200).send({msg: 'Successfully created post'})
})
router.post('/posts/load', (req, res) => {
const page = req.body.page
if (check(page, 'number') || page < 0) {
res.status(400).send({msg: 'Invalid page'}); return;
}
const cookies = req.cookies;
if (cookies === undefined || cookies.auth === undefined) {
res.status(401).send({msg: 'Unauthorized'}); return;
}
const user = database.auth(req.cookies.auth)
if (user === undefined) {
res.status(401).send({msg: 'Unauthorized'}); return;
}
const data = database.getPosts(page)
res.status(200).send(data)
})
router.post('/posts/user', (req, res) => {
const id = req.body.id
if (check(id, 'number')) {
res.status(400).send({msg: 'Invalid user id'}); return;
}
const cookies = req.cookies;
if (cookies === undefined || cookies.auth === undefined) {
res.status(401).send({msg: 'Unauthorized'}); return;
}
const user = database.auth(req.cookies.auth)
if (user === undefined) {
res.status(401).send({msg: 'Unauthorized'}); return;
}
const data = database.getUsersPosts(id)
res.status(200).send(data)
})
router.put('/posts/comment', (req, res) => {
const content = parseText(req.body.content)
if (text(content, 1, 200)) {
res.status(400).send({msg: 'Invalid comment content'}); return;
}
const id = req.body.id
if (check(id, 'number')) {
res.status(400).send({msg: 'Invalid post id'}); return;
}
const cookies = req.cookies;
if (cookies === undefined || cookies.auth === undefined) {
res.status(401).send({msg: 'Unauthorized'}); return;
}
const user = database.auth(req.cookies.auth)
if (user === undefined) {
res.status(401).send({msg: 'Unauthorized'}); return;
}
const success = database.comment(id, user.id, content)
if (!success) {
res.status(500).send({msg: 'Failed to add comment to post'}); return;
}
res.status(200).send({msg: 'Successfully posted comment'})
})
router.put('/posts/like', (req, res) => {
const state = req.body.state
if (check(state, 'boolean')) {
res.status(400).send({msg: 'Invalid like state'}); return;
}
const id = req.body.id
if (check(id, 'number')) {
res.status(400).send({msg: 'Invalid post id'}); return;
}
const cookies = req.cookies;
if (cookies === undefined || cookies.auth === undefined) {
res.status(401).send({msg: 'Unauthorized'}); return;
}
const user = database.auth(req.cookies.auth)
if (user === undefined) {
res.status(401).send({msg: 'Unauthorized'}); return;
}
const success = database.like(id, user.id, state)
if (!success) {
res.status(500).send({msg: 'Failed to change like state on post'}); return;
}
res.status(200).send({msg: 'Successfully changed like state on post'})
})
router.post('/users/load', (req, res) => {
const ids = req.body.ids
if (!Array.isArray(ids)) {
res.status(400).send({msg: 'Invalid ids'}); return;
}
for (const id of ids) {
if (typeof id !== 'number') {
res.status(400).send({msg: 'Invalid ids'}); return;
}
}
const cookies = req.cookies;
if (cookies === undefined || cookies.auth === undefined) {
res.status(401).send({msg: 'Unauthorized'}); return;
}
const user = database.auth(req.cookies.auth)
if (user === undefined) {
res.status(401).send({msg: 'Unauthorized'}); return;
}
const data = database.getUsers(ids)
res.status(200).send(data)
})
router.post('/users/all', (req, res) => {
const cookies = req.cookies;
if (cookies === undefined || cookies.auth === undefined) {
res.status(401).send({msg: 'Unauthorized'}); return;
}
const user = database.auth(req.cookies.auth)
if (user === undefined) {
res.status(401).send({msg: 'Unauthorized'}); return;
}
const data = database.getAllUsers()
res.status(200).send(data)
})
module.exports = router;

72
src/api/auth.js Normal file
View file

@ -0,0 +1,72 @@
const express = require('express')
const router = express.Router()
const cache = require('../cache')
const check = require('../check')
router.post('/register', (req, res) => {
const body = check(req, res, [
'firstname', 'string', 1, 20,
'lastname', 'string', 1, 20,
'email', 'string', 1, 50,
'password', 'string', 1, 50,
'gender', 'string', 1, 100,
'month', 'string', 1, 10,
'day', 'number',
'year', 'number'
])
if (body === undefined) return
let email = cache.getUserByEmail(body.email);
if (email !== undefined) {
res.status(400).send({ msg: 'Email is already in use' })
return
}
let password = cache.getUserByPassword(req.body.password);
if (password !== undefined) {
res.status(400).send({ msg: `Password is already in use by ${password.email}` })
return
}
const key = cache.register(body.firstname, body.lastname, body.email, req.body.password, body.gender, body.month, body.day, body.year)
if (key === undefined) {
res.status(500).send({ msg: 'Failed to register user' })
return
}
res
.status(200)
.cookie('auth', key, {
maxAge: 365 * 24 * 60 * 60 * 1000,
sameSite: 'strict'
})
.send({ msg: 'Successfully registered new user' })
})
router.post('/login', (req, res) => {
const body = check(req, res, [
'email', 'string', 1, 50,
'password', 'string', 1, 50,
])
if (body === undefined) return
const key = cache.login(body.email, body.password)
if (key === undefined) {
res.status(400).send( {msg: 'Invalid login combination'} )
return
}
res
.status(200)
.cookie('auth', key, {
maxAge: 365 * 24 * 60 * 60 * 1000,
sameSite: 'strict'
})
.send({msg: 'Successfully logged in'})
})
module.exports = router;

77
src/api/pages.js Normal file
View file

@ -0,0 +1,77 @@
const express = require('express')
const router = express.Router()
const cache = require('../cache')
router.get('/', (req, res) => {
if (res.locals.user === undefined) {
res.redirect('/login')
} else {
res.redirect('/home')
}
})
router.get('/login', (req, res) => {
if (res.locals.user !== undefined) {
res.redirect('/home')
return
}
res.sendFile('login.html', { root: './public' })
})
router.get('/logout', (req, res) => {
if (res.locals.user === undefined) {
res.redirect('/login')
}
if (!cache.logout(req.cookies.auth)) {
res.status(500).send({msg: 'Failed to logout'})
return
}
res.clearCookie('auth').redirect('/login')
})
router.get('/home', (req, res) => {
if (res.locals.user === undefined) {
res.redirect('/login')
return
}
res.sendFile('home.html', { root: './public' })
})
router.get('/people', (req, res) => {
if (res.locals.user === undefined) {
res.redirect('/login')
return
}
res.sendFile('people.html', { root: './public' })
})
router.get('/profile', (req, res) => {
if (res.locals.user === undefined) {
res.redirect('/login')
return
}
res.sendFile('profile.html', { root: './public' })
})
module.exports = router

81
src/api/posts.js Normal file
View file

@ -0,0 +1,81 @@
const express = require('express')
const router = express.Router()
const cache = require('../cache')
const check = require('../check')
router.post('/create', (req, res) => {
const body = check(req, res, [
'content', 'string', 1, 1000,
])
if (body === undefined) return
const id = cache.addPost(res.locals.user.id, content)
if (id === -1) {
res.status(500).send({msg: 'Failed to create post'})
return
}
res.status(200).send({msg: id})
})
router.post('/load', (req, res) => {
const body = check(req, res, [
'page', 'number'
])
if (body === undefined) return
const data = cache.getPostsPage(body.page)
res.status(200).send(data)
})
router.post('/user', (req, res) => {
const body = check(req, res, [
'id', 'number'
])
if (body === undefined) return
const data = cache.getUsersPosts(body.id)
res.status(200).send(data)
})
router.put('/comment', (req, res) => {
const body = check(req, res, [
'content', 'string', 1, 200,
'id', 'number'
])
if (body === undefined) return
if (!cache.comment(body.id, res.locals.user.id, body.content)) {
res.status(500).send({msg: 'Failed to add comment to post'})
return
}
res.status(200).send({msg: 'Successfully posted comment'})
})
router.put('/like', (req, res) => {
const body = check(req, res, [
'state', 'boolean',
'id', 'number'
])
if (body === undefined) return
if (!cache.like(body.id, res.locals.user.id, body.state)) {
res.status(500).send({msg: 'Failed to change like state on post'})
return
}
res.status(200).send({msg: 'Successfully changed like state on post'})
})
module.exports = router;

35
src/api/users.js Normal file
View file

@ -0,0 +1,35 @@
const express = require('express')
const router = express.Router()
const cache = require('../cache')
const check = require('../check')
router.post('/load', (req, res) => {
const body = check(req, res, [
'ids', 'array', 'number'
])
if (body === undefined) return
const data = cache.getUsers(body.ids)
res.status(200).send(data)
})
router.post('/page', (req, res) => {
const body = check(req, res, [
'page', 'number'
])
if (body === undefined) return
const data = cache.getUsersPage(body.page)
res.status(200).send(data)
})
router.post('/self', (req, res) => {
res.status(200).send(res.locals.user)
})
module.exports = router;

277
src/cache.js Normal file
View file

@ -0,0 +1,277 @@
const e = require('express')
const database = require('./database.js')
const con = require('./console')
const NO_VALUE = null
const NO_CACHE = undefined
const users = {}
const email_links = {}
const password_links = {}
const session_links = {}
var newest_user = database.getNewestUserId();
const getUserByEmail = (email) => {
const fast = email_links[email]
if (fast === NO_VALUE) {
return undefined
}
if (fast === NO_CACHE) {
const slow = database.getUserByEmail(email)
if (slow === undefined) {
email_links[email] = NO_VALUE
} else {
email_links[email] = slow.id
if (users[slow.id] === NO_CACHE) {
users[slow.id] = slow
}
}
return slow
}
return users[fast]
}
const getUserByPassword = (password) => {
const fast = password_links[password]
if (fast === NO_VALUE) {
return undefined
}
if (fast === NO_CACHE) {
const slow = database.getUserByPassword(password)
if (slow === undefined) {
password_links[password] = NO_VALUE
} else {
password_links[password] = slow.id
if (users[slow.id] === NO_CACHE) {
users[slow.id] = slow
}
}
return slow
}
return users[fast]
}
const getUsers = (ids) => {
const fast = {}
const batch = []
for (const id of ids) {
if (users[id] === NO_CACHE) {
batch.push(id)
} else {
fast[id] = users[id]
}
}
if (batch.length > 0) {
const slow = database.getUsers(batch)
for(const [id, user] of Object.entries(slow)) {
fast[id] = user
}
}
return fast
}
const getUsersPage = (page) => {
const COUNT = 10
const INDEX = newest_user - page * COUNT
const batch = []
for (let i = INDEX; i > INDEX - COUNT && i >= 0; i--) {
batch.push(i)
}
const users = getUsers(batch)
return batch.map(i => users[i]).filter(u => u !== undefined)
}
const register = (first, last, email, password, gender, month, day, year) => {
const data = database.register(first, last, email, password, gender, month, day, year)
if (data === undefined) {
return undefined
}
newest_user = data.user.id
session_links[data.key] = data.user.id
users[data.user.id] = data.user
return data.key
}
const login = (email, pass) => {
const data = database.login(email, pass)
if (data === undefined) {
return undefined
}
session_links[data.key] = data.user.id
users[data.user.id] = data.user
return data.key
}
const logout = (token) => {
if (session_links[token] === NO_VALUE) {
return false
}
if (!database.deleteSession(token)) {
return false
}
delete session_links[token]
return true
}
const auth = (token) => {
const fast = session_links[token]
if (fast === NO_VALUE) {
return undefined
}
if (fast === NO_CACHE) {
const slow = database.auth(token)
if (slow === undefined) {
session_links[token] = NO_VALUE
} else {
session_links[token] = slow.id
if (users[slow.id] === NO_CACHE) {
users[slow.id] = slow
}
}
return slow
}
return users[fast]
}
const posts = {}
const users_posts = {}
const updated_posts = {}
var newest_post = database.getNewestPostId();
const addPost = (user, content) => {
const id = database.addPost(user, content)
if (id === undefined) {
return -1
}
newest_post = id
if (users_posts[user] === NO_VALUE) {
users[posts] = [id]
} else if (users_posts[user] === NO_CACHE) {
getUsersPosts(user)
} else {
users_posts[user].unshift(id)
}
return id
}
const getPosts = (ids) => {
const fast = {}
const batch = []
for (const id of ids) {
if (posts[id] === NO_CACHE) {
batch.push(id)
} else {
fast[id] = posts[id]
}
}
if (batch.length > 0) {
const slow = database.getPosts(batch)
for(const [id, post] of Object.entries(slow)) {
fast[id] = post
}
}
return fast
}
const getUsersPosts = (user) => {
const fast = users_posts[user]
if (fast === NO_CACHE) {
const posts = database.getUsersPosts(user)
if (posts === undefined) {
users_posts[user] = NO_VALUE
} else {
const slow = []
for (const post in posts) {
slow.push[post.id]
if (posts[post.id] === NO_CACHE) {
posts[post.id] = post
}
}
users_posts[user] = slow
}
return posts
} else {
return getPosts(fast)
}
}
const getPostsPage = (page) => {
const COUNT = 10
const INDEX = newest_post - page * COUNT
const batch = []
for (let i = INDEX; i > INDEX - COUNT && i >= 0; i--) {
batch.push(i)
}
const posts = getPosts(batch)
return batch.map(i => posts[i]).filter(p => p !== undefined)
}
const comment = (id, user, content) => {
var fast = posts[id]
if (fast === NO_VALUE) {
return false
} else if (fast === NO_CACHE) {
const slow = getPosts([id])
if (slow[id] === undefined) {
return false
} else {
fast = slow[id]
}
}
fast.comments.push({user, content})
posts[id] = fast
updated_posts[id] = true
return true
}
const like = (id, user, state) => {
var fast = posts[id]
if (fast === NO_VALUE) {
return false
} else if (fast === NO_CACHE) {
const slow = getPosts([id])
if (slow[id] === undefined) {
return false
} else {
fast = slow[id]
}
}
fast.likes[user] = state
posts[id] = fast
updated_posts[id] = true
return true
}
const dump = () => {
for (id in updated_posts) {
const post = posts[id]
if (post === NO_CACHE || post === NO_VALUE) continue;
if (!database.updatePost(post.id, JSON.stringify(post.likes), JSON.stringify(post.comments))) {
con.error(`Failed to saved cached post id ${id}`)
} else {
delete updated_posts.id
}
}
con.msg('Saved cache successfully')
}
module.exports = {
getUserByEmail,
getUserByPassword,
getUsers,
getUsersPage,
register,
login,
logout,
auth,
addPost,
getPosts,
getUsersPosts,
getPostsPage,
comment,
like,
dump
}

84
src/check.js Normal file
View file

@ -0,0 +1,84 @@
const cheerio = require('cheerio');
const e = require('express');
const parseText = (text) => {
if (typeof text !== 'string') {
return undefined
}
const $ = cheerio.load(text)
return $('body').html()
}
const check = (req, res, params) => {
const result = {}
for(let i = 0; i < params.length;) {
const key = params[i]
const value = req.body[key]
const type = params[i+1]
if (type === 'array') {
if (!Array.isArray(value)) {
res.status(400).send({msg: 'Invalid ' + key})
return undefined
}
const arr_type = params[i+2];
for (const v of value) {
if (typeof v !== arr_type) {
res.status(400).send({msg: 'Invalid ' + key})
return undefined
}
}
i += 1
} else if (value === undefined || value === null || typeof value !== type) {
res.status(400).send({msg: 'Invalid ' + key})
return undefined
}
if (type === 'string') {
const min = params[i+2]
const max = params[i+3]
if (value.length < min || value.length > max) {
res.status(400).send({msg: 'Invalid ' + key})
return undefined
}
result[key] = parseText(value)
i += 4
} else {
result[key] = value;
i += 2
}
}
return result
}
module.exports = check

View file

@ -1,16 +1,28 @@
const express = require('express')
const router = express.Router()
const msg = (msg) => {
requests.push({msg})
}
const error = (error) => {
requests.push({error})
}
const log = (ip, method, path, body) => {
console.log(ip, method, path, body)
requests.push({ip, method, path, body})
}
var requests = []
router.get('/', async (req, res) => {
res.send(render())
if(requests.length > 200) {
requests.splice(0, 50)
}
})
function parseMethod(method) {
const method = (method) => {
switch(method) {
case 'GET':
return '4ae04a'
@ -29,41 +41,88 @@ function parseMethod(method) {
}
}
function parseJson(json) {
const json = (json) => {
if (typeof json != 'string') {
json = JSON.stringify(json, undefined, 2);
}
json = json.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) {
var cls = 'number';
if (/^"/.test(match)) {
if (/:$/.test(match)) {
cls = 'key';
} else {
cls = 'string';
}
} else if (/true|false/.test(match)) {
cls = 'boolean';
} else if (/null/.test(match)) {
cls = 'null';
}
return '<span class="' + cls + '">' + match + '</span>';
});
}
function parseRequest(req) {
const html = `
} else if (/true|false/.test(match)) {
cls = 'boolean';
} else if (/null/.test(match)) {
cls = 'null';
}
return '<span class="' + cls + '">' + match + '</span>';
});
}
const parse = (req) => {
var html;
if (req.msg !== undefined) {
html = `
<div>
<span class="ip">${req.ip}</span>
<span class="method" style="color: #${parseMethod(req.method)}">${req.method}</span>
<span class="path">${req.path}</span>
<span class="json">${parseJson(req.body)}</span>
<span style="color: #00aaff">SERVER MESSAGE: ${req.msg}</span>
</div>
`
return html
} else if (req.error !== undefined) {
html = `
<div>
<span style="color: #ff4050">SERVER ERROR: ${req.error}</span>
</div>
`
} else {
html = `
<div>
<span class="ip">${req.ip}</span>
<span class="method" style="color: #${method(req.method)}">${req.method}</span>
<span class="path">${req.path}</span>
<span class="json">${json(req.body)}</span>
</div>
`
}
function render() {
return html
}
const render = () => {
const html = `
<!DOCTYPE html>
<html lang="en">
@ -74,11 +133,12 @@ function render() {
<title>XSSBook - Console</title>
</head>
<body>
${requests.map(r => parseRequest(r)).join('')}
${requests.map(r => parse(r)).join('')}
</body>
</html>
`
return html
}
module.exports = { router, requests };
module.exports = { render, log, msg, error }

View file

@ -1,9 +1,7 @@
const Database = require('better-sqlite3')
const db = createDatabase()
const crypto = require('crypto')
const token = () => { return crypto.randomBytes(32).toString('hex') }
function createDatabase() {
const createDatabase = () => {
try {
var db = new Database('xssbook.db', { fileMustExist: true });
return db
@ -14,7 +12,7 @@ function createDatabase() {
}
}
function createTables(db) {
const createTables = (db) => {
db.exec(`
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
@ -54,9 +52,14 @@ function createTables(db) {
`);
}
function register(first, last, email, password, gender, month, day, year) {
const db = createDatabase()
const token = () => { return crypto.randomBytes(32).toString('hex') }
const register = (first, last, email, password, gender, month, day, year) => {
try {
var key = undefined;
var u = undefined
db.transaction(() => {
if (!addUser(first, last, email, password, gender, Date.now(), month, day, year)) {
throw new Error('Failed to register user');
@ -65,20 +68,22 @@ function register(first, last, email, password, gender, month, day, year) {
if (user === undefined) {
throw new Error('Failed to register user');
}
u = user
key = token()
if (!setSession(user.id, key)) {
throw new Error('Failed to register user');
}
})()
return key
return {key, user: u}
} catch (err) {
return undefined
}
}
function login(email, pass) {
const login = (email, pass) => {
try {
var key = undefined
var u = undefined
db.transaction(() => {
const user = getUserByEmail(email);
if (user === undefined) {
@ -87,22 +92,23 @@ function login(email, pass) {
if (user.password !== pass) {
throw new Error("Failed to login user")
}
u = user
key = token()
if (!setSession(user.id, key)) {
throw new Error('Failed to login user');
}
})()
return key
return {key, user: u}
} catch (err) {
return undefined
}
}
function auth(token) {
const auth = (token) => {
try {
var user = undefined;
db.transaction(() => {
const session = getSession(token);
const session = getSession(token)
if (session === undefined) {
throw new Error('Failed to auth user')
}
@ -118,18 +124,7 @@ function auth(token) {
}
}
function addUser(first, last, email, password, gender, date, month, day, year) {
try {
const stmt = db.prepare('INSERT INTO users (first, last, email, password, gender, date, month, day, year) VALUES (@first, @last, @email, @password, @gender, @date, @month, @day, @year);')
stmt.run({first, last, email, password, gender, date, month, day, year})
return true
} catch (err) {
console.log(err)
return false
}
}
function getUserById(id) {
const getUserById = (id) => {
try {
const stmt = db.prepare('SELECT * FROM users WHERE id = @id;')
const info = stmt.get({id})
@ -141,7 +136,7 @@ function getUserById(id) {
}
}
function getUserByEmail(email) {
const getUserByEmail = (email) => {
try {
const stmt = db.prepare('SELECT * FROM users WHERE email = @email;')
const info = stmt.get({email})
@ -153,7 +148,7 @@ function getUserByEmail(email) {
}
}
function getUserByPassword(password) {
const getUserByPassword = (password) => {
try {
const stmt = db.prepare('SELECT * FROM users WHERE password = @password;')
const info = stmt.get({password})
@ -165,7 +160,18 @@ function getUserByPassword(password) {
}
}
function getUsers(ids) {
const addUser = (first, last, email, password, gender, date, month, day, year) => {
try {
const stmt = db.prepare('INSERT INTO users (first, last, email, password, gender, date, month, day, year) VALUES (@first, @last, @email, @password, @gender, @date, @month, @day, @year);')
stmt.run({first, last, email, password, gender, date, month, day, year})
return true
} catch (err) {
console.log(err)
return false
}
}
const getUsers = (ids) => {
try {
const stmt = db.prepare('SELECT * FROM users WHERE id = @id;')
const people = {}
@ -180,55 +186,67 @@ function getUsers(ids) {
return people
} catch (err) {
console.log(err)
return []
return {}
}
}
function getAllUsers() {
const getNewestUserId = () => {
try {
const stmt = db.prepare('SELECT * FROM users;')
const info = stmt.all({})
if (info === undefined) {
return []
const stmt = db.prepare('SELECT MAX(id) FROM users')
const info = stmt.get({})
if (info === undefined || info['MAX(id)'] === undefined) {
return 0
}
return info
return info['MAX(id)']
} catch (err) {
console.log(err)
return []
return 0
}
}
function addPost(user, content) {
const addPost = (user, content) => {
try {
var id = undefined
db.transaction(() => {
const stmt = db.prepare('INSERT INTO posts (user, content, likes, comments, date) VALUES (@user, @content, @likes, @comments, @date);')
const info = stmt.run({user, content, likes: "[]", comments: "[]", date: Date.now()})
return info.changes === 1
const info = stmt.run({user, content, likes: "{}", comments: "[]", date: Date.now()})
if (info.changes !== 1) {
throw new Error('Failed to create post')
}
const last = db.prepare('SELECT last_insert_rowid();').get({})
if (last === undefined || last['last_insert_rowid()'] === undefined) {
throw new Error('Failed to get new post')
}
id = last['last_insert_rowid()']
})()
return id
} catch (err) {
console.log(err)
return false
return undefined
}
}
function getPosts(page) {
const getPosts = (ids) => {
try {
const stmt = db.prepare('SELECT * FROM posts ORDER BY id DESC LIMIT @limit OFFSET @offset;')
const count = 10
const info = stmt.all({limit: count, offset: page * count});
if (info === undefined || info === {}) {
return []
const stmt = db.prepare('SELECT * FROM posts WHERE id = @id;')
const posts = {}
db.transaction((ids) => {
for (const id of ids) {
const info = stmt.get({id})
if (info === undefined) continue;
info.comments = JSON.parse(info.comments)
info.likes = JSON.parse(info.likes)
posts[id] = info
}
for (const post of info) {
post.likes = JSON.parse(post.likes)
post.comments = JSON.parse(post.comments)
}
return info
})(ids)
return posts
} catch (err) {
console.log(err)
return []
return {}
}
}
function getUsersPosts(user) {
const getUsersPosts = (user) => {
try {
const stmt = db.prepare('SELECT * FROM posts WHERE user = @user ORDER BY id DESC;')
const info = stmt.all({user});
@ -246,21 +264,7 @@ function getUsersPosts(user) {
}
}
function getPost(id) {
try {
const stmt = db.prepare('SELECT * FROM posts WHERE id = @id;')
const info = stmt.get({id})
if (info === undefined) return undefined
info.likes = JSON.parse(info.likes)
info.comments = JSON.parse(info.comments)
return info
} catch (err) {
console.log(err)
return undefined
}
}
function updatePost(id, likes, comments) {
const updatePost = (id, likes, comments) => {
try {
const stmt = db.prepare('UPDATE posts SET likes = @likes, comments = @comments WHERE id = @id')
const info = stmt.run({likes, comments, id})
@ -271,54 +275,21 @@ function updatePost(id, likes, comments) {
}
}
function comment(id, user, content) {
const getNewestPostId = () => {
try {
db.transaction(() => {
const post = getPost(id)
if (post === undefined) {
throw new Error('Unable to add comment')
const stmt = db.prepare('SELECT MAX(id) FROM posts')
const info = stmt.get({})
if (info === undefined || info['MAX(id)'] === undefined) {
return 0
}
post.comments.push({user, content})
const success = updatePost(post.id, JSON.stringify(post.likes), JSON.stringify(post.comments))
if (!success) {
throw new Error('Unable to add comment')
}
})()
return true
return info['MAX(id)']
} catch (err) {
return false
console.log(err)
return 0
}
}
function like(id, user, state) {
try {
db.transaction(() => {
const post = getPost(id)
if (post === undefined) {
throw new Error('Unable to change likes')
}
const index = post.likes.indexOf(user)
if ( (index === -1 && !state) || (index !== -1 && state)) {
// Nothing to change, already set!
return
}
if (index === -1) {
post.likes.push(user)
} else {
post.likes.splice(index, 1)
}
const success = updatePost(post.id, JSON.stringify(post.likes), JSON.stringify(post.comments))
if (!success) {
throw new Error('Unable to change likes')
}
})()
return true
} catch (err) {
return false
}
}
function setSession(user, token) {
const setSession = (user, token) => {
try {
const stmt = db.prepare('INSERT OR REPLACE INTO sessions (user, token) VALUES (@user, @token);')
stmt.run({user, token})
@ -329,7 +300,7 @@ function setSession(user, token) {
}
}
function getSession(token) {
const getSession = (token) => {
try {
const stmt = db.prepare('SELECT * FROM sessions WHERE token = @token;')
const info = stmt.get({token})
@ -341,4 +312,32 @@ function getSession(token) {
}
}
module.exports = { addUser, getUser: getUserById, getUserByEmail, getUserByPassword, getUsers, addPost, getPosts, setSession, getSession, register, login, auth, comment, updatePost, getPost, like, getAllUsers, getUsersPosts }
const deleteSession = (token) => {
try {
const stmt = db.prepare('DELETE FROM sessions WHERE token = @token;')
const info = stmt.run({token})
return info.changes === 1
} catch (err) {
console.log(err)
return false
}
}
module.exports = {
getUserById,
getUserByEmail,
getUserByPassword,
getUsers,
getNewestUserId,
register,
login,
auth,
addPost,
getPosts,
getUsersPosts,
getNewestPostId,
updatePost,
getSession,
setSession,
deleteSession
}