finish
This commit is contained in:
parent
633cf75ee2
commit
7f1a57d1a6
21 changed files with 1176 additions and 155 deletions
134
index.js
134
index.js
|
@ -4,6 +4,10 @@ const express = require('express')
|
|||
const app = express()
|
||||
const port = 8080
|
||||
|
||||
app.set('trust proxy', true)
|
||||
|
||||
const database = require('./src/database.js')
|
||||
|
||||
const rateLimiter = require('express-rate-limit')
|
||||
const limiter = (min, count) => {
|
||||
return rateLimiter({
|
||||
|
@ -21,11 +25,55 @@ app.use(cookieParser())
|
|||
app.use(express.json());
|
||||
app.use(express.static('public'))
|
||||
|
||||
app.use((req, res, next) => {
|
||||
const public = { ... req.body }
|
||||
if (public.password !== undefined) {
|
||||
public.password = '********'
|
||||
}
|
||||
console.log(req.ip, req.method, req.path, public)
|
||||
update(req.ip, req.method, req.path, public)
|
||||
next()
|
||||
})
|
||||
|
||||
app.get('/', (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.redirect('/home')
|
||||
})
|
||||
|
||||
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.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' })
|
||||
})
|
||||
|
||||
|
@ -37,17 +85,95 @@ app.get('/profile', (req, res) => {
|
|||
res.sendFile('profile.html', { root: './public' })
|
||||
})
|
||||
|
||||
app.use('/api', limiter(1,60))
|
||||
app.use('/api/register', limiter(60, 5))
|
||||
app.use('/api/login', limiter(10, 5))
|
||||
|
||||
const api = require('./src/api.js')
|
||||
app.use('/api', api);
|
||||
|
||||
const connections = []
|
||||
app.get('/console', (req, res) => {
|
||||
res.write(`
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="stylesheet" href="css/console.css">
|
||||
<title>XSSBook - Console</title>
|
||||
</head>
|
||||
<body>
|
||||
`)
|
||||
connections.push(res)
|
||||
})
|
||||
|
||||
function color(method) {
|
||||
switch(method) {
|
||||
case 'GET':
|
||||
return '4ae04a'
|
||||
case 'POST':
|
||||
return 'b946db'
|
||||
case 'PUT':
|
||||
return 'ff9705'
|
||||
case 'PATCH':
|
||||
return `42caff`
|
||||
case 'DELETE':
|
||||
return `ff4a4a`
|
||||
case 'HEAD':
|
||||
return '424cff'
|
||||
case 'OPTIONS':
|
||||
return 'ff9757'
|
||||
}
|
||||
}
|
||||
|
||||
function highlight(json) {
|
||||
if (typeof json != 'string') {
|
||||
json = JSON.stringify(json, undefined, 2);
|
||||
}
|
||||
json = json.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
||||
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>';
|
||||
});
|
||||
}
|
||||
|
||||
async function update(ip, method, path, json) {
|
||||
connections.forEach(con => {
|
||||
con.write(`
|
||||
<div>
|
||||
<span class="ip">${ip}</span>
|
||||
<span class="method" style="color: #${color(method)}">${method}</span>
|
||||
<span class="path">${path}</span>
|
||||
<span class="json">${highlight(json)}</span>
|
||||
</div>
|
||||
`)
|
||||
})
|
||||
}
|
||||
|
||||
// app.use('/api', limiter(1,60))
|
||||
// app.use('/api/register', limiter(60, 5))
|
||||
// app.use('/api/login', limiter(10, 5))
|
||||
|
||||
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, () => {
|
||||
console.log(`App listening on port http://127.0.0.1:${port}`)
|
||||
})
|
54
package-lock.json
generated
54
package-lock.json
generated
|
@ -11,6 +11,7 @@
|
|||
"dependencies": {
|
||||
"better-sqlite3": "^8.0.1",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"crypto": "^1.0.1",
|
||||
"dotenv": "^16.0.3",
|
||||
"express": "^4.18.2",
|
||||
"express-rate-limit": "^6.7.0"
|
||||
|
@ -171,9 +172,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/cookie": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
|
||||
"integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==",
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
|
||||
"integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
|
@ -190,19 +191,17 @@
|
|||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cookie-parser/node_modules/cookie": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
|
||||
"integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/cookie-signature": {
|
||||
"version": "1.0.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/debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
|
@ -360,6 +359,14 @@
|
|||
"express": "^4 || ^5"
|
||||
}
|
||||
},
|
||||
"node_modules/express/node_modules/cookie": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
|
||||
"integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/file-uri-to-path": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
||||
|
@ -1145,9 +1152,9 @@
|
|||
"integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="
|
||||
},
|
||||
"cookie": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
|
||||
"integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw=="
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
|
||||
"integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA=="
|
||||
},
|
||||
"cookie-parser": {
|
||||
"version": "1.4.6",
|
||||
|
@ -1156,13 +1163,6 @@
|
|||
"requires": {
|
||||
"cookie": "0.4.1",
|
||||
"cookie-signature": "1.0.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"cookie": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
|
||||
"integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"cookie-signature": {
|
||||
|
@ -1170,6 +1170,11 @@
|
|||
"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=="
|
||||
},
|
||||
"debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
|
@ -1280,6 +1285,13 @@
|
|||
"type-is": "~1.6.18",
|
||||
"utils-merge": "1.0.1",
|
||||
"vary": "~1.1.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"cookie": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
|
||||
"integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"express-rate-limit": {
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
"dependencies": {
|
||||
"better-sqlite3": "^8.0.1",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"crypto": "^1.0.1",
|
||||
"dotenv": "^16.0.3",
|
||||
"express": "^4.18.2",
|
||||
"express-rate-limit": "^6.7.0"
|
||||
|
|
|
@ -5,11 +5,11 @@
|
|||
<link rel="stylesheet" href="css/main.css">
|
||||
<link rel="stylesheet" href="css/404.css">
|
||||
<link rel="stylesheet" href="css/header.css">
|
||||
<title>XSSBook - Home</title>
|
||||
<title>XSSBook - Not Found</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="header">
|
||||
<span class="logo">xssbook</span>
|
||||
<span class="logo"><a href="/">xssbook</a></span>
|
||||
</div>
|
||||
<div class="error">
|
||||
<span class="logo">404</span>
|
||||
|
|
60
public/css/console.css
Normal file
60
public/css/console.css
Normal file
|
@ -0,0 +1,60 @@
|
|||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: #181818;
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: sfpro;
|
||||
src: url("../fonts/sfpro.otf") format("opentype");
|
||||
}
|
||||
|
||||
div {
|
||||
background-color: #282828;
|
||||
font-family: sfpro;
|
||||
margin: 15px;
|
||||
margin-bottom: 0px;
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
width: calc(100% - 50px)
|
||||
}
|
||||
|
||||
span {
|
||||
display: inline-block;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
color: #ffffff;
|
||||
font-family: sfpro;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.json span {
|
||||
display: inline;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.key {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.value {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.boolean {
|
||||
color: aqua;
|
||||
}
|
||||
|
||||
.null {
|
||||
color: blue;
|
||||
}
|
||||
|
||||
.number {
|
||||
color: yellow;
|
||||
}
|
||||
|
||||
.string {
|
||||
color: #4ae04a
|
||||
}
|
|
@ -148,6 +148,11 @@ body {
|
|||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.comment p, .post p {
|
||||
word-break: break-all;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.comment>span {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
|
@ -42,6 +42,14 @@ body {
|
|||
color: #1778f2
|
||||
}
|
||||
|
||||
.error {
|
||||
font-family: sfpro;
|
||||
color: #f02849;
|
||||
padding-top: 10px;
|
||||
margin-bottom: -10px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.gtext {
|
||||
font-family: sfpro;
|
||||
color: #606770
|
||||
|
@ -77,6 +85,7 @@ body {
|
|||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
@ -263,6 +272,7 @@ footer {
|
|||
.hidden {
|
||||
visibility: hidden;
|
||||
pointer-events: none;
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.pfp, .pfp img {
|
||||
|
|
|
@ -1,3 +1,44 @@
|
|||
body {
|
||||
background-color: #f0f2f5;
|
||||
}
|
||||
|
||||
#users {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.person {
|
||||
width: 30em;
|
||||
height: fit-content;
|
||||
background-color: white;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, .05);
|
||||
margin-bottom: 1.5em;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.profile, .profile img {
|
||||
border-radius: 10px 0px 0px 10px;
|
||||
width: 10em;
|
||||
height: 10em;
|
||||
padding: 0;
|
||||
display: block;
|
||||
background-color: #e4e6e8;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.info {
|
||||
margin: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.info span {
|
||||
width: 280px;
|
||||
margin-bottom: 5px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
|
@ -1,3 +1,121 @@
|
|||
body {
|
||||
background-color: #f0f2f5;
|
||||
}
|
||||
|
||||
.spacer {
|
||||
margin-bottom: 3.5em;
|
||||
}
|
||||
|
||||
#top {
|
||||
background-color: white;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, .05);
|
||||
}
|
||||
|
||||
#banner {
|
||||
background-image: linear-gradient(#949494, white, white);
|
||||
height: 30em;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#banner div, #banner img {
|
||||
width: 80em;
|
||||
height: inherit;
|
||||
background-color: #e4e6e8;
|
||||
border-radius: 0px 0px 20px 20px;
|
||||
}
|
||||
|
||||
#info {
|
||||
width: 80em;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.face {
|
||||
background-color: #e4e6e8;
|
||||
height: 12em;
|
||||
width: 12em;
|
||||
border-radius: 7em;
|
||||
border: solid 5px white;
|
||||
margin-top: -2em;
|
||||
margin-left: 2em;
|
||||
margin-right: 2em;
|
||||
}
|
||||
|
||||
.infodata {
|
||||
margin-top: 2em;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.infodata span {
|
||||
margin-bottom: .5em;
|
||||
}
|
||||
|
||||
.profilebuttons {
|
||||
width: 80em;
|
||||
height: 3em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.profilebuttons button {
|
||||
all: unset;
|
||||
font-family: sfprobold;
|
||||
padding: 0px 50px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #606770;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.profilebuttons button:hover {
|
||||
background-color: #dddfe2;
|
||||
}
|
||||
|
||||
.selected {
|
||||
color: #1778f2 !important;
|
||||
border-bottom: 3px solid #1778f2 !important;
|
||||
}
|
||||
|
||||
#about {
|
||||
margin-top: 2em;
|
||||
align-self: center;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
#posts {
|
||||
margin-top: 2em;
|
||||
}
|
||||
|
||||
#about .ltext {
|
||||
border-right: 2px solid #dadde1;
|
||||
padding: 10px;
|
||||
padding-right: 3em;
|
||||
}
|
||||
|
||||
#about .data {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 10px;
|
||||
padding-left: 20px;
|
||||
padding-top: 15px;
|
||||
}
|
||||
|
||||
#about .data span {
|
||||
margin-bottom: 10px;
|
||||
width: 28em;
|
||||
margin-bottom: 5px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
|
@ -10,6 +10,8 @@
|
|||
<body>
|
||||
<script src="./js/main.js"></script>
|
||||
<script src="./js/header.js"></script>
|
||||
<script src="./js/api.js"></script>
|
||||
<script src="./js/home.js"></script>
|
||||
<script>load()</script>
|
||||
</body>
|
||||
</html>
|
57
public/js/api.js
Normal file
57
public/js/api.js
Normal file
|
@ -0,0 +1,57 @@
|
|||
const endpoint = 'https://xssbook.com/api'
|
||||
|
||||
const request = async (url, body, method) => {
|
||||
if (method === undefined) method = 'POST'
|
||||
const response = await fetch(endpoint + url, {
|
||||
method,
|
||||
body: JSON.stringify(body),
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
if (response.status == 401) {
|
||||
location.href = 'login'
|
||||
}
|
||||
const json = await response.json()
|
||||
return { status: response.status, msg: json.msg, json }
|
||||
}
|
||||
|
||||
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 loadposts = async (page) => {
|
||||
return await request('/posts/load', {page})
|
||||
}
|
||||
|
||||
const loadusersposts = async (id) => {
|
||||
return await request('/posts/user', {id})
|
||||
}
|
||||
|
||||
const loadusers = async (ids) => {
|
||||
return await request('/users/load', {ids})
|
||||
}
|
||||
|
||||
const loadallusers = async () => {
|
||||
return await request('/users/all', {})
|
||||
}
|
||||
|
||||
const loadself = async () => {
|
||||
return await request("/auth/self", {})
|
||||
}
|
||||
|
||||
const postcomment = async (id, content) => {
|
||||
return await request('/posts/comment', {id, content}, 'PUT')
|
||||
}
|
||||
|
||||
const postlike = async (id, state) => {
|
||||
return await request('/posts/like', {id, state}, 'PUT')
|
||||
}
|
||||
|
||||
const createpost = async (content) => {
|
||||
return await request('/posts/create', {content})
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
function header(home, people) {
|
||||
const html = `
|
||||
<div id="header">
|
||||
<span class="logo">xssbook</span>
|
||||
<span class="logo"><a href="/">xssbook</a></span>
|
||||
<div class="buttons">
|
||||
<a id="home" ${home ? 'class="selected"' : ''} href="home">
|
||||
<svg viewBox="0 0 28 28" fill="currentColor" height="28" width="28">
|
||||
|
|
|
@ -5,22 +5,24 @@ function parseDate(date) {
|
|||
return months[date.getUTCMonth()] + ' ' + date.getUTCDate() + ', ' + date.getUTCFullYear() + ' ' + date.toLocaleTimeString();
|
||||
}
|
||||
|
||||
function parseComment(data) {
|
||||
function parseComment(comment) {
|
||||
const author = data.users[comment.user]
|
||||
const html = `
|
||||
<div class="comment">
|
||||
<a class="pfp">
|
||||
|
||||
</a>
|
||||
<span>
|
||||
<span class="bold mtext">${data.firstname + ' ' + data.lastname}</span>
|
||||
<p class="mtext">${data.content}</p>
|
||||
<span class="bold mtext">${author.first + ' ' + author.last}</span>
|
||||
<p class="mtext">${comment.content}</p>
|
||||
</span>
|
||||
</div>
|
||||
`
|
||||
return html
|
||||
}
|
||||
|
||||
function parsePost(post, likes) {
|
||||
function parsePost(post) {
|
||||
const author = data.users[post.user]
|
||||
const html = `
|
||||
<div class="post" postid=${post.id}>
|
||||
<div class="postheader">
|
||||
|
@ -28,21 +30,21 @@ function parsePost(post, likes) {
|
|||
|
||||
</a>
|
||||
<div class="postname">
|
||||
<span class="bold">${post.author.firstname + ' ' + post.author.lastname}</span>
|
||||
<span class="gtext mtext">${parseDate(new Date(post.time))}</span>
|
||||
<span class="bold">${author.first + ' ' + author.last}</span>
|
||||
<span class="gtext mtext">${parseDate(new Date(post.date))}</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="mtext">
|
||||
${post.content}
|
||||
${post.content.replace(/\n/g,'<br>')}
|
||||
</p>
|
||||
<span class="gtext mtext">
|
||||
${post.likes} Likes
|
||||
${post.likes.length} Likes
|
||||
</span>
|
||||
<div class="fullline nb"></div>
|
||||
<div class="postbuttons">
|
||||
<span onclick="like(this)">
|
||||
<i class="icons like ${likes.includes(post.id) ? 'blue' : ''}"></i>
|
||||
<span class="bold ${likes.includes(post.id) ? 'blue' : ''}">Like</span>
|
||||
<i class="icons like ${post.likes.includes(data.user.id) ? 'blue' : ''}"></i>
|
||||
<span class="bold ${post.likes.includes(data.user.id) ? 'blue' : ''}">Like</span>
|
||||
</span>
|
||||
<span onclick="this.parentElement.parentElement.getElementsByClassName('newcomment')[0].focus()">
|
||||
<i class="icons comm"></i>
|
||||
|
@ -76,53 +78,60 @@ function getPost(id) {
|
|||
return -1
|
||||
}
|
||||
|
||||
function like(span) {
|
||||
async function like(span) {
|
||||
const id = parseInt(span.parentElement.parentElement.getAttribute('postid'))
|
||||
const index = data.user.likes.indexOf(id)
|
||||
if (index === -1) {
|
||||
data.user.likes.push(id)
|
||||
data.posts[getPost(id)].likes++
|
||||
const post = data.posts[getPost(id)]
|
||||
const index = post.likes.indexOf(data.user.id)
|
||||
const state = index === -1;
|
||||
const response = await postlike(id, state)
|
||||
if (response.status != 200) return;
|
||||
if (index == -1) {
|
||||
post.likes.push(data.user.id)
|
||||
} else {
|
||||
data.user.likes.splice(index, 1)
|
||||
data.posts[getPost(id)].likes--
|
||||
post.likes.splice(index, 1)
|
||||
}
|
||||
load()
|
||||
render()
|
||||
}
|
||||
|
||||
function comment(event) {
|
||||
async function comment(event) {
|
||||
event.preventDefault();
|
||||
const text = event.target.elements.text.value.trim();
|
||||
if (text.length < 1) return;
|
||||
const id = parseInt(event.target.parentElement.parentElement.parentElement.getAttribute('postid'))
|
||||
var index = getPost(id);
|
||||
console.log(index)
|
||||
if (index === -1) return;
|
||||
const response = await postcomment(id, text)
|
||||
if (response.status != 200) return;
|
||||
event.target.elements.text.value = '';
|
||||
data.posts[index].comments.push({
|
||||
firstname: data.user.firstname,
|
||||
lastname: data.user.lastname,
|
||||
user: data.user.id,
|
||||
content: text
|
||||
})
|
||||
load()
|
||||
render()
|
||||
}
|
||||
|
||||
function post(event) {
|
||||
async function post() {
|
||||
const text = document.getElementById("text").value.trim()
|
||||
const error = document.getElementsByClassName('error')[0]
|
||||
if (text.length < 1) return;
|
||||
const response = await createpost(text);
|
||||
if (response.status != 200) {
|
||||
error.innerHTML = response.msg
|
||||
return;
|
||||
}
|
||||
error.innerHTML = '';
|
||||
data.posts.unshift({
|
||||
id: data.posts[0].id + 1,
|
||||
author: {
|
||||
firstname: data.user.firstname,
|
||||
lastname: data.user.lastname
|
||||
},
|
||||
time: Date.now(),
|
||||
user: data.user.id,
|
||||
date: Date.now(),
|
||||
content: text,
|
||||
likes: 0,
|
||||
likes: [],
|
||||
comments: []
|
||||
})
|
||||
load()
|
||||
render()
|
||||
}
|
||||
|
||||
function load() {
|
||||
function render() {
|
||||
const html = `
|
||||
<div id="posts">
|
||||
<div class="create">
|
||||
|
@ -131,11 +140,11 @@ function load() {
|
|||
</a>
|
||||
<button class="pfp">
|
||||
<p class="gtext" onclick="document.getElementById('popup').classList.remove('hidden')">
|
||||
What's on your mind, ${data.user.firstname}?
|
||||
What's on your mind, ${data.user.first}?
|
||||
</p>
|
||||
</button>
|
||||
</div>
|
||||
${data.posts.map(p => parsePost(p, data.user.likes)).join('')}
|
||||
${data.posts.map(p => parsePost(p)).join('')}
|
||||
</div>
|
||||
`
|
||||
|
||||
|
@ -152,11 +161,12 @@ function load() {
|
|||
|
||||
</a>
|
||||
<div class="postname">
|
||||
<span class="bold">${data.user.firstname + ' ' + data.user.lastname}</span>and ho
|
||||
<span class="bold">${data.user.first + ' ' + data.user.last}</span>
|
||||
<span class="gtext mtext">Now</span>
|
||||
</div>
|
||||
</div>
|
||||
<textarea type="text" name="text" id="text" placeholder="What's on your mind, ${data.user.firstname}?"></textarea>
|
||||
<textarea type="text" name="text" id="text" placeholder="What's on your mind, ${data.user.first}?"></textarea>
|
||||
<span class="error ctext" style="padding-bottom: 15px; margin-top: -30px;"></span>
|
||||
<button class="primary" onclick="post(this)">Post</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -165,43 +175,29 @@ function load() {
|
|||
add(popup, 'popup')
|
||||
}
|
||||
|
||||
var data = {
|
||||
user: {
|
||||
firstname: 'John',
|
||||
lastname: 'Doe',
|
||||
likes: [1]
|
||||
},
|
||||
posts: [
|
||||
{
|
||||
id: 1,
|
||||
author: {
|
||||
firstname: 'Joe',
|
||||
lastname: 'Biden',
|
||||
},
|
||||
time: 1674269687905,
|
||||
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.',
|
||||
likes: 2,
|
||||
comments: [],
|
||||
},
|
||||
{
|
||||
id: 0,
|
||||
author: {
|
||||
firstname: 'Amazon',
|
||||
lastname: 'Employee',
|
||||
},
|
||||
time: 0,
|
||||
content: 'I dont like working at amazon >:(',
|
||||
likes: 69,
|
||||
comments: [
|
||||
{
|
||||
firstname: 'Jeff',
|
||||
lastname: 'Bezos',
|
||||
content: 'You\'re fired.'
|
||||
},
|
||||
],
|
||||
}
|
||||
]
|
||||
var page = 0
|
||||
const data = {
|
||||
user: {},
|
||||
users: {},
|
||||
posts: []
|
||||
}
|
||||
|
||||
header(true, false)
|
||||
load();
|
||||
async function load() {
|
||||
header(true, false)
|
||||
const posts = (await loadposts(page)).json
|
||||
page++;
|
||||
data.posts.push(... posts)
|
||||
const batch = []
|
||||
for (const post of posts) {
|
||||
if (data.users[post.user] !== undefined) continue
|
||||
if (batch.includes(post.user)) continue
|
||||
batch.push(post.user)
|
||||
}
|
||||
const users = (await loadusers(batch)).json
|
||||
for (const id in users) {
|
||||
data.users[id] = users[id]
|
||||
}
|
||||
data.user = (await loadself()).json
|
||||
data.users[data.user.id] = data.user
|
||||
render()
|
||||
}
|
29
public/js/login.js
Normal file
29
public/js/login.js
Normal file
|
@ -0,0 +1,29 @@
|
|||
async function onlogin() {
|
||||
const email = document.getElementById('email').value
|
||||
const password = document.getElementById('pass').value
|
||||
const response = await login(email, password)
|
||||
if (response.status !== 200) {
|
||||
const error = document.getElementsByClassName('error')[0]
|
||||
error.innerHTML = response.msg
|
||||
} else {
|
||||
location.href = '/home'
|
||||
}
|
||||
}
|
||||
|
||||
async function onregister() {
|
||||
const first = document.getElementById('firstname').value
|
||||
const last = document.getElementById('lastname').value
|
||||
const email = document.getElementById('newemail').value
|
||||
const pass = document.getElementById('newpass').value
|
||||
const month = document.getElementById('month').value
|
||||
const day = document.getElementById('day').value
|
||||
const year = document.getElementById('year').value
|
||||
const gender = document.querySelector('input[name="gender"]:checked').value
|
||||
const response = await register(first, last, email, pass, gender, month, parseInt(day), parseInt(year))
|
||||
if (response.status !== 200) {
|
||||
const error = document.getElementsByClassName('error')[1]
|
||||
error.innerHTML = response.msg
|
||||
} else {
|
||||
location.href = '/home'
|
||||
}
|
||||
}
|
|
@ -1 +1,47 @@
|
|||
function parseDate(date) {
|
||||
var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
|
||||
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
||||
|
||||
return months[date.getUTCMonth()] + ' ' + date.getUTCDate() + ', ' + date.getUTCFullYear() + ' ' + date.toLocaleTimeString();
|
||||
}
|
||||
|
||||
function parseUser(user) {
|
||||
const html = `
|
||||
<a class="person" href="/profile?id=${user.id}">
|
||||
<div class="profile">
|
||||
|
||||
</div>
|
||||
<div class="info">
|
||||
<span class="bold ltext">${user.first + ' ' + user.last}</span>
|
||||
<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>
|
||||
</div>
|
||||
</a>
|
||||
`
|
||||
return html
|
||||
}
|
||||
|
||||
function render() {
|
||||
const html = `
|
||||
<div id="users">
|
||||
${data.users.map(u => parseUser(u)).join('')}
|
||||
</div>
|
||||
`
|
||||
|
||||
add(html, 'usres')
|
||||
}
|
||||
|
||||
var data = {
|
||||
users: []
|
||||
}
|
||||
|
||||
async function load() {
|
||||
const users = (await loadallusers()).json
|
||||
console.log(users)
|
||||
data.users = users
|
||||
render()
|
||||
}
|
||||
|
||||
header(false, true)
|
||||
load()
|
88
public/js/profile.js
Normal file
88
public/js/profile.js
Normal file
|
@ -0,0 +1,88 @@
|
|||
function render() {
|
||||
const html = `
|
||||
<div id="top">
|
||||
<div id="banner">
|
||||
<div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div id="info">
|
||||
<div class="face">
|
||||
|
||||
</div>
|
||||
<div class="infodata">
|
||||
<span class="bold ltext">${data.user.first + ' ' + data.user.last}</span>
|
||||
<span class="gtext">Joined ${parseDate(new Date(data.user.date))}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="fullline" style="width: 80em; margin-bottom: 0;"></div>
|
||||
<div class="profilebuttons">
|
||||
<button class="${posts ? 'selected' : ''}" onclick="posts = true; render()">
|
||||
Posts
|
||||
</button>
|
||||
<button class="${posts ? '' : 'selected'}" onclick="posts = false; render()">
|
||||
About
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
|
||||
add(html, 'top')
|
||||
|
||||
const postsh = `
|
||||
<div id="posts" class="${posts ? '' : 'hidden'}">
|
||||
${data.posts.map(p => parsePost(p)).join('')}
|
||||
</div>
|
||||
`
|
||||
|
||||
add(postsh, 'posts')
|
||||
|
||||
const about = `
|
||||
<div id="about" class="post ${posts ? 'hidden' : ''}">
|
||||
<span class="bold ltext">About</span>
|
||||
<div class="data">
|
||||
<span class="gtext bold">Name: ${data.user.first + ' ' + data.user.last}</span>
|
||||
<span class="gtext bold">Email: ${data.user.email}</span>
|
||||
<span class="gtext bold">Gender: ${data.user.gender}</span>
|
||||
<span class="gtext bold">Birthday: ${data.user.month + ' ' + data.user.day + ', ' + data.user.year}</span>
|
||||
<span class="gtext bold">User ID: ${data.user.id}</span>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
|
||||
add(about, 'about')
|
||||
}
|
||||
|
||||
var posts = true
|
||||
|
||||
async function load() {
|
||||
header(false, false)
|
||||
|
||||
var params = {};
|
||||
for (const [key, value] of new URLSearchParams(location.search)) {
|
||||
params[key] = value
|
||||
}
|
||||
|
||||
const id = params.id !== undefined && !isNaN(params.id) ? parseInt(params.id) : (await loadself()).json.id
|
||||
const posts = (await loadusersposts(id)).json
|
||||
data.posts.push(... posts)
|
||||
const batch = [id]
|
||||
for (const post of posts) {
|
||||
for(const comment of post.comments) {
|
||||
if (data.users[comment.user] !== undefined) continue
|
||||
if (batch.includes(comment.user)) continue
|
||||
batch.push(comment.user)
|
||||
}
|
||||
if (data.users[post.user] !== undefined) continue
|
||||
if (batch.includes(post.user)) continue
|
||||
batch.push(post.user)
|
||||
}
|
||||
const users = (await loadusers(batch)).json
|
||||
for (const id in users) {
|
||||
data.users[id] = users[id]
|
||||
}
|
||||
data.user = data.users[id]
|
||||
render()
|
||||
}
|
||||
|
||||
load()
|
|
@ -4,6 +4,8 @@
|
|||
<meta charset="UTF-8">
|
||||
<link rel="stylesheet" href="css/main.css">
|
||||
<link rel="stylesheet" href="css/login.css">
|
||||
<script src="./js/api.js"></script>
|
||||
<script src="./js/login.js"></script>
|
||||
<title>XSSBook - Login</title>
|
||||
</head>
|
||||
<body>
|
||||
|
@ -15,7 +17,8 @@
|
|||
<div class="prompt">
|
||||
<input type="text" name="email" id="email" placeholder="Email" autofocus="1">
|
||||
<input type="password" name="pass" id="pass" placeholder="Password">
|
||||
<button class="primary login-button bold" value="1" name="login" type="submit" id="login">Log In</button>
|
||||
<span class="error ctext"></span>
|
||||
<button class="primary login-button bold" value="1" name="login" type="submit" id="login" onclick="onlogin()">Log In</button>
|
||||
<a class="btext ctext">Forgot Password?</a>
|
||||
<div class="line"></div>
|
||||
<button class="success newacc" onclick="document.getElementById('popup').classList.remove('hidden')">Create new account</button>
|
||||
|
@ -35,18 +38,18 @@
|
|||
<span class="label">Birthday</span>
|
||||
<div class="row">
|
||||
<select name="month" id="month" title="Month">
|
||||
<option value="1" selected="1">Jan</option>
|
||||
<option value="2">Feb</option>
|
||||
<option value="3">Mar</option>
|
||||
<option value="4">Apr</option>
|
||||
<option value="5">May</option>
|
||||
<option value="6">Jun</option>
|
||||
<option value="7">Jul</option>
|
||||
<option value="8">Aug</option>
|
||||
<option value="9">Sep</option>
|
||||
<option value="10">Oct</option>
|
||||
<option value="11">Nov</option>
|
||||
<option value="12">Dec</option>
|
||||
<option value="Jan" selected="1">Jan</option>
|
||||
<option value="Feb">Feb</option>
|
||||
<option value="Mar">Mar</option>
|
||||
<option value="Apr">Apr</option>
|
||||
<option value="May">May</option>
|
||||
<option value="Jun">Jun</option>
|
||||
<option value="Jul">Jul</option>
|
||||
<option value="Aug">Aug</option>
|
||||
<option value="Sep">Sep</option>
|
||||
<option value="Oct">Oct</option>
|
||||
<option value="Nov">Nov</option>
|
||||
<option value="Dec">Dec</option>
|
||||
</select>
|
||||
<select name="day" id="day" title="Day">
|
||||
<option value="1" selected="1">1</option>
|
||||
|
@ -142,20 +145,21 @@
|
|||
<div class="radiomenu" data-type="radio" data-name="gender_wrapper">
|
||||
<span>
|
||||
<label class="gtext" for="female">Female</label>
|
||||
<input id="female" type="radio" name="gender" value="1">
|
||||
<input id="female" type="radio" name="gender" value="Female" checked="true">
|
||||
</span>
|
||||
<span>
|
||||
<label class="gtext" for="male">Male</label>
|
||||
<input id="male" type="radio" name="gender" value="2">
|
||||
<input id="male" type="radio" name="gender" value="Male">
|
||||
</span>
|
||||
<span>
|
||||
<label class="gtext" for="lettuce">Lettuce</label>
|
||||
<input id="lettuce" type="radio" name="gender" value="3">
|
||||
<input id="lettuce" type="radio" name="gender" value="Lettuce">
|
||||
</span>
|
||||
</div>
|
||||
<span class="error ctext" style="padding-bottom: 10px;"></span>
|
||||
<span class="label stext">By clicking Sign Up, you agree to have your password stored in plain text and have any javascript run on your pc at any time.</span>
|
||||
<span class="label stext">XSSBook is not responsible for any ones loss of finances, mental state, relationships, or life when using this site.</span>
|
||||
<button class="success signacc">Sign Up</button>
|
||||
<button class="success signacc" onclick="onregister()">Sign Up</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -3,12 +3,13 @@
|
|||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="stylesheet" href="css/main.css">
|
||||
<link rel="stylesheet" href="css/home.css">
|
||||
<link rel="stylesheet" href="css/people.css">
|
||||
<link rel="stylesheet" href="css/header.css">
|
||||
<title>XSSBook - People</title>
|
||||
</head>
|
||||
<body>
|
||||
<script src="./js/main.js"></script>
|
||||
<script src="./js/header.js"></script>
|
||||
<script src="./js/api.js"></script>
|
||||
<script src="./js/people.js"></script>
|
||||
</body>
|
|
@ -3,27 +3,15 @@
|
|||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="stylesheet" href="css/main.css">
|
||||
<link rel="stylesheet" href="css/profile.css">
|
||||
<link rel="stylesheet" href="css/header.css">
|
||||
<link rel="stylesheet" href="css/profile.css">
|
||||
<link rel="stylesheet" href="css/home.css">
|
||||
<title>XSSBook - Profile</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="header">
|
||||
<span class="logo">xssbook</span>
|
||||
<div class="buttons">
|
||||
<a id="home" href="home">
|
||||
<svg viewBox="0 0 28 28" fill="currentColor" height="28" width="28">
|
||||
<path d="M25.825 12.29C25.824 12.289 25.823 12.288 25.821 12.286L15.027 2.937C14.752 2.675 14.392 2.527 13.989 2.521 13.608 2.527 13.248 2.675 13.001 2.912L2.175 12.29C1.756 12.658 1.629 13.245 1.868 13.759 2.079 14.215 2.567 14.479 3.069 14.479L5 14.479 5 23.729C5 24.695 5.784 25.479 6.75 25.479L11 25.479C11.552 25.479 12 25.031 12 24.479L12 18.309C12 18.126 12.148 17.979 12.33 17.979L15.67 17.979C15.852 17.979 16 18.126 16 18.309L16 24.479C16 25.031 16.448 25.479 17 25.479L21.25 25.479C22.217 25.479 23 24.695 23 23.729L23 14.479 24.931 14.479C25.433 14.479 25.921 14.215 26.132 13.759 26.371 13.245 26.244 12.658 25.825 12.29"></path>
|
||||
</svg>
|
||||
</a>
|
||||
<a id="people" href="people">
|
||||
<svg viewBox="0 0 28 28" fill="currentColor" height="28" width="28">
|
||||
<path d="M10.5 4.5c-2.272 0-2.75 1.768-2.75 3.25C7.75 9.542 8.983 11 10.5 11s2.75-1.458 2.75-3.25c0-1.482-.478-3.25-2.75-3.25zm0 8c-2.344 0-4.25-2.131-4.25-4.75C6.25 4.776 7.839 3 10.5 3s4.25 1.776 4.25 4.75c0 2.619-1.906 4.75-4.25 4.75zm9.5-6c-1.41 0-2.125.841-2.125 2.5 0 1.378.953 2.5 2.125 2.5 1.172 0 2.125-1.122 2.125-2.5 0-1.659-.715-2.5-2.125-2.5zm0 6.5c-1.999 0-3.625-1.794-3.625-4 0-2.467 1.389-4 3.625-4 2.236 0 3.625 1.533 3.625 4 0 2.206-1.626 4-3.625 4zm4.622 8a.887.887 0 00.878-.894c0-2.54-2.043-4.606-4.555-4.606h-1.86c-.643 0-1.265.148-1.844.413a6.226 6.226 0 011.76 4.336V21h5.621zm-7.122.562v-1.313a4.755 4.755 0 00-4.749-4.749H8.25A4.755 4.755 0 003.5 20.249v1.313c0 .518.421.938.937.938h12.125c.517 0 .938-.42.938-.938zM20.945 14C24.285 14 27 16.739 27 20.106a2.388 2.388 0 01-2.378 2.394h-5.81a2.44 2.44 0 01-2.25 1.5H4.437A2.44 2.44 0 012 21.562v-1.313A6.256 6.256 0 018.25 14h4.501a6.2 6.2 0 013.218.902A5.932 5.932 0 0119.084 14h1.861z"></path>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
<a id="profile" hreF="profile">
|
||||
|
||||
</a>
|
||||
</div>
|
||||
<script src="./js/main.js"></script>
|
||||
<script src="./js/header.js"></script>
|
||||
<script src="./js/api.js"></script>
|
||||
<script src="./js/home.js"></script>
|
||||
<script src="./js/profile.js"></script>
|
||||
</body>
|
221
src/api.js
221
src/api.js
|
@ -2,13 +2,230 @@ const express = require('express')
|
|||
const router = express.Router()
|
||||
const database = require('./database.js')
|
||||
|
||||
const check = (test, type) => {
|
||||
return text === undefined || text === null || typeof test !== type
|
||||
}
|
||||
|
||||
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('/', (req, res) => {
|
||||
res.status(200).send( {msg: 'xssbook api'} )
|
||||
router.post('/auth/register', (req, res) => {
|
||||
const first = req.body.first;
|
||||
if (text(first, 1, 20)) {
|
||||
res.status(400).send( {msg: 'Invalid first name'} ); return;
|
||||
}
|
||||
const last = req.body.last;
|
||||
if (text(last, 1, 20)) {
|
||||
res.status(400).send( {msg: 'Invalid last name'} ); return;
|
||||
}
|
||||
const email = 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 = req.body.gender;
|
||||
if (text(gender, 1, 100)) {
|
||||
res.status(400).send( {msg: 'Invalid gender'} ); return;
|
||||
}
|
||||
const month = 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).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).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 = req.body.content
|
||||
if (text(content, 1, 420)) {
|
||||
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 = 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;
|
236
src/database.js
236
src/database.js
|
@ -1,5 +1,7 @@
|
|||
const Database = require('better-sqlite3')
|
||||
const db = createDatabase()
|
||||
const crypto = require('crypto')
|
||||
const token = () => { return crypto.randomBytes(32).toString('hex') }
|
||||
|
||||
function createDatabase() {
|
||||
try {
|
||||
|
@ -20,7 +22,7 @@ function createTables(db) {
|
|||
last VARCHAR(20) NOT NULL,
|
||||
email VARCHAR(50) NOT NULL,
|
||||
password VARCHAR(50) NOT NULL,
|
||||
gender VARCHAR(20) NOT NULL,
|
||||
gender VARCHAR(100) NOT NULL,
|
||||
date INTEGER NOT NULL,
|
||||
month VARCHAR(10) NOT NULL,
|
||||
day INTEGER NOT NULL,
|
||||
|
@ -52,9 +54,73 @@ function createTables(db) {
|
|||
`);
|
||||
}
|
||||
|
||||
function register(first, last, email, password, gender, month, day, year) {
|
||||
try {
|
||||
var key = undefined;
|
||||
db.transaction(() => {
|
||||
if (!addUser(first, last, email, password, gender, Date.now(), month, day, year)) {
|
||||
throw new Error('Failed to register user');
|
||||
}
|
||||
const user = getUserByEmail(email);
|
||||
if (user === undefined) {
|
||||
throw new Error('Failed to register user');
|
||||
}
|
||||
key = token()
|
||||
if (!setSession(user.id, key)) {
|
||||
throw new Error('Failed to register user');
|
||||
}
|
||||
})()
|
||||
return key
|
||||
} catch (err) {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
function login(email, pass) {
|
||||
try {
|
||||
var key = undefined
|
||||
db.transaction(() => {
|
||||
const user = getUserByEmail(email);
|
||||
if (user === undefined) {
|
||||
throw new Error('Failed to login user')
|
||||
}
|
||||
if (user.password !== pass) {
|
||||
throw new Error("Failed to login user")
|
||||
}
|
||||
key = token()
|
||||
if (!setSession(user.id, key)) {
|
||||
throw new Error('Failed to login user');
|
||||
}
|
||||
})()
|
||||
return key
|
||||
} catch (err) {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
function auth(token) {
|
||||
try {
|
||||
var user = undefined;
|
||||
db.transaction(() => {
|
||||
const session = getSession(token);
|
||||
if (session === undefined) {
|
||||
throw new Error('Failed to auth user')
|
||||
}
|
||||
const u = getUserById(session.user);
|
||||
if (u === undefined) {
|
||||
throw new Error('Failed to auth user')
|
||||
}
|
||||
user = u;
|
||||
})()
|
||||
return user
|
||||
} catch (err) {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
function addUser(first, last, email, password, gender, date, month, day, year) {
|
||||
try {
|
||||
const stmt = db.prepare('INSERT OR REPLACE INTO users (first, last, email, password, gender, date, month, day, year) VALUES (@first, @last, @email, @password, @gender, @date, @month, @day, @year);')
|
||||
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) {
|
||||
|
@ -63,7 +129,7 @@ function addUser(first, last, email, password, gender, date, month, day, year) {
|
|||
}
|
||||
}
|
||||
|
||||
function getUser(id) {
|
||||
function getUserById(id) {
|
||||
try {
|
||||
const stmt = db.prepare('SELECT * FROM users WHERE id = @id;')
|
||||
const info = stmt.get({id})
|
||||
|
@ -75,6 +141,30 @@ function getUser(id) {
|
|||
}
|
||||
}
|
||||
|
||||
function getUserByEmail(email) {
|
||||
try {
|
||||
const stmt = db.prepare('SELECT * FROM users WHERE email = @email;')
|
||||
const info = stmt.get({email})
|
||||
if (info === undefined) return undefined
|
||||
return info
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
function getUserByPassword(password) {
|
||||
try {
|
||||
const stmt = db.prepare('SELECT * FROM users WHERE password = @password;')
|
||||
const info = stmt.get({password})
|
||||
if (info === undefined) return undefined
|
||||
return info
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
function getUsers(ids) {
|
||||
try {
|
||||
const stmt = db.prepare('SELECT * FROM users WHERE id = @id;')
|
||||
|
@ -90,14 +180,28 @@ function getUsers(ids) {
|
|||
return people
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
return undefined
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
function addPost(user, content, likes, comments, date) {
|
||||
function getAllUsers() {
|
||||
try {
|
||||
const stmt = db.prepare('INSERT OR REPLACE INTO posts (user, content, likes, comments, date) VALUES (@user, @content, @likes, @comments, @date);')
|
||||
const info = stmt.run({user, content, likes, comments, date})
|
||||
const stmt = db.prepare('SELECT * FROM users;')
|
||||
const info = stmt.all({})
|
||||
if (info === undefined) {
|
||||
return []
|
||||
}
|
||||
return info
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
function addPost(user, content) {
|
||||
try {
|
||||
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
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
|
@ -106,10 +210,112 @@ function addPost(user, content, likes, comments, date) {
|
|||
}
|
||||
|
||||
function getPosts(page) {
|
||||
try {
|
||||
const stmt = db.prepare('SELECT * FROM posts ORDER BY id DESC LIMIT @limit OFFSET @offset;')
|
||||
const count = 20
|
||||
const info = stmt.all({limit: count, offset: page * count});
|
||||
console.log(info)
|
||||
if (info === undefined || info === {}) {
|
||||
return []
|
||||
}
|
||||
for (const post of info) {
|
||||
post.likes = JSON.parse(post.likes)
|
||||
post.comments = JSON.parse(post.comments)
|
||||
}
|
||||
return info
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
function getUsersPosts(user) {
|
||||
try {
|
||||
const stmt = db.prepare('SELECT * FROM posts WHERE user = @user ORDER BY id DESC;')
|
||||
const info = stmt.all({user});
|
||||
if (info === undefined || info === {}) {
|
||||
return []
|
||||
}
|
||||
for (const post of info) {
|
||||
post.likes = JSON.parse(post.likes)
|
||||
post.comments = JSON.parse(post.comments)
|
||||
}
|
||||
return info
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
try {
|
||||
const stmt = db.prepare('UPDATE posts SET likes = @likes, comments = @comments WHERE id = @id')
|
||||
const info = stmt.run({likes, comments, id})
|
||||
return info.changes === 1
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function comment(id, user, content) {
|
||||
try {
|
||||
db.transaction(() => {
|
||||
const post = getPost(id)
|
||||
if (post === undefined) {
|
||||
throw new Error('Unable to add comment')
|
||||
}
|
||||
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
|
||||
} catch (err) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
|
@ -122,3 +328,17 @@ function setSession(user, token) {
|
|||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function getSession(token) {
|
||||
try {
|
||||
const stmt = db.prepare('SELECT * FROM sessions WHERE token = @token;')
|
||||
const info = stmt.get({token})
|
||||
if (info === undefined) return undefined
|
||||
return info
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { addUser, getUser: getUserById, getUserByEmail, getUserByPassword, getUsers, addPost, getPosts, setSession, getSession, register, login, auth, comment, updatePost, getPost, like, getAllUsers, getUsersPosts }
|
Loading…
Reference in a new issue