-
Notifications
You must be signed in to change notification settings - Fork 7
/
server.js
237 lines (196 loc) · 7.35 KB
/
server.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
require('dotenv').config()
const express = require('express')
const session = require('express-session')
const MongoStore = require('connect-mongo')
const webpush = require('web-push')
const app = express()
const http = require('http').Server(app);
const mongoose = require('mongoose')
const helmet = require("helmet")
const compression = require('compression')
const socketio = require("socket.io")
const crypto = require('crypto')
const updateEnv = require('./my_modules/updateenv')
const other = require('./my_modules/other')
// MongoDB setup
//Loads up all models
require('./models')
const ForumSettings = mongoose.model("ForumSettings")
const Accounts = mongoose.model("Accounts")
//Database cleanup
async function CleanMongoDatabase(){
//Deletes old active user counts
await mongoose.model("ActiveUsers").deleteMany({time: {$lt: Date.now() - 60000*15}})
//Deletes forum audit logs older than 30 days
await mongoose.model("ForumAuditLogs").deleteMany({time: {$lt: Date.now() - 1000*60*60*24*30}})
//Deletes messages older than 90 days
await mongoose.model("Messages").deleteMany({time: {$lt: Date.now() - 1000*60*60*24*90}})
//Removes premium from expired Crypto payers
let expiredPremiumMembers = await Accounts.find({premium_expires: {$lt: new Date()}})
for(let expiredPremiumMember of expiredPremiumMembers){
let roles = other.StringToArray(expiredPremiumMember.roles)
//Removes their patron role
let index = roles.indexOf("patron")
if(index != -1) roles.splice(index, 1);
//Give them the VIP role as a token of appreciation
if(roles.indexOf("vip") === -1) roles.push("vip")
//Save changes
expiredPremiumMember.roles = JSON.stringify(roles)
await expiredPremiumMember.save()
}
}
//Connect to database
let mongoURL = `mongodb://127.0.0.1:27017/${process.env.DATABASE_NAME || "db_powrum"}`
mongoose.set('strictQuery', false)
mongoose.connect(mongoURL)
.then(async ()=> {
console.log("MongoDB database connected")
//Automatic database setup for required documents or placeholder documents
{
let settings = await ForumSettings.find().lean()
//Sets default description
if(!settings.find(setting => setting.type === "description")) {
await new ForumSettings({
type: "description",
value: "An online community powered by Powrum"
}).save()
}
//Much of this stores into process.env so the process does not need to query the database everytime for highly reused data
// Manages web-push configuration
//Generates push notification VAPID keys if the private or public vapid key is missing
if(!process.env.PRIVATE_VAPID_KEY || !process.env.PUBLIC_VAPID_KEY) {
let vapidKeys = webpush.generateVAPIDKeys()
updateEnv({
PRIVATE_VAPID_KEY: vapidKeys.privateKey,
PUBLIC_VAPID_KEY: vapidKeys.publicKey,
})
}
webpush.setVapidDetails(`mailto:${process.env.SUPPORT_EMAIL_ADDRESS}`, process.env.PUBLIC_VAPID_KEY, process.env.PRIVATE_VAPID_KEY);
//Creates original(bot) account if it doesn't exist
//I say original because uid assumes the identity of this forum
//If there are zero accounts, create an account
if(!await Accounts.countDocuments()){
await new Accounts({
username: "BOT"
})
.save()
}
}
//Clean database on every launch
CleanMongoDatabase()
//Clean database every 24 hours
setInterval(CleanMongoDatabase, 1000 * 60 * 60 * 24)
})
// Express.js configuration
//Helps protect from some well-known web vulnerabilities by setting HTTP headers appropriately.
app.use(helmet())
//Compress all responses
app.use(compression())
//Trust first proxy
app.set('trust proxy', 1)
//set the view engine to ejs
app.set('view engine', 'ejs')
//gibe me kreditz
app.use((req, res, next) => {
res.append('X-Forum-Software', 'Powrum');
next();
});
//In case nginx doesn't send for some reason...
app.use(express.static('public'))
app.use(express.static('public', { extensions: ['html'] }))
// Login session initialization
//Generate session secret if one does not exist
if(!process.env.SESSION_SECRET) {
updateEnv({SESSION_SECRET: crypto.randomBytes(64).toString('hex')})
}
//Express routes will get sessions through session
let sessionConf = {
secret: process.env.SESSION_SECRET,
name: process.env.SESSION_COOKIE_NAME || '_PFSec',
store: MongoStore.create({
mongoUrl: mongoURL,
stringify: false,
}),
saveUninitialized: false, //Prevents every single request from being recognized as a session
rolling: true, //Resets expiration date
resave: true, //Resaves cookie on server. Necessary because of the expiration date being reset
cookie: {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
maxAge: 1000*60*60*24*365,
sameSite: process.env.NODE_ENV === "production" ? 'none' : 'strict'
}
}
if(process.env.COOKIE_DOMAIN) sessionConf.cookie.domain = process.env.COOKIE_DOMAIN
let sessionMiddleware = session(sessionConf)
//Express server use session
app.use(sessionMiddleware)
// Define what HTTP routes to listen for
//Handle the API
app.use('/api/', require('./routes/api/router'))
//Handle everything else. Aka the view
function isSetup(){
if(!process.env.FORUM_URL) return false
if(!process.env.MAILGUN_DOMAIN || !process.env.MAILGUN_APIKEY) return false
return true
}
module.exports.isSetup = isSetup
let wwwRouter = require("./routes/install/index")
app.use("/", (req, res, next) => wwwRouter(req, res, next))
if(isSetup()) wwwRouter = require('./routes/www/router')
//No route matched? Default route -> Send 404 page
app.use(function(req, res, next){
res.status(404).render("404")
})
//Express.js exception handling
app.use(function(err, req, res, next) {
try {
let isAPIRoute = req.originalUrl.split("/")[1] == "api"
if (typeof err === "string") {
if(!isAPIRoute) res.status(400).render("400", {reason: err})
else res.status(400).json({success: false, reason: err})
}
else if(err.name === "URIError") {
if(!isAPIRoute) res.status(400).render("400", {reason: "Bad request: Invalid URI"})
else res.status(400).json({success: false, reason: "Bad request: Invalid URI"})
}
else{
console.log(`Express.js error at path: [${req.method}]${req.originalUrl}\n`, err)
if(!isAPIRoute) res.status(500).render("500", {reason: "The server has errored... This will be fixed when the admins have noticed"})
else res.status(500).json({success: false, reason: "The server has errored... This will be fixed when the admins have noticed"})
}
}
catch(e){
console.log("Exception handler just errored: ", e)
}
})
//Starts HTTP server
http.listen(process.env.PORT || 8087, () => {
console.log(`Powrum server started on http://localhost:${process.env.PORT || 8087}`)
})
// Socket.io configuration
//Create Socket.io server
const io = new socketio.Server(http, {
cors: {
origin: (origin, callback) => {
if (!origin) {
// Allow requests from file:// (Tauri) or no-origin requests (undefined or null)
callback(null, true);
} else {
const allowedOrigins = [process.env.FORUM_URL, "http://localhost:1420", "http://localhost:3000", "https://tauri.localhost", "http://tauri.localhost",];
if (allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
}
},
methods: ["GET", "POST"],
credentials: true // This allows cookies and authentication headers to be sent
}
});
module.exports.io = io
//Websocket server use session
io.engine.use(sessionMiddleware)
//Listen to websocket requests
io.on('connection', require('./my_modules/websocket'))