-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathserver.js
117 lines (106 loc) · 4.07 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
const http = require('http');
const express = require('express');
const cookieParser = require('cookie-parser')
const uuidv4 = require('uuid/v4');
const SSEClient = require('sse').Client;
const admin = require('./admin');
const pubsub = require('@google-cloud/pubsub')();
const app = express();
app.set('etag', false);
app.use(cookieParser());
// Check if user is admin based on the 'flag' cookie, and set the 'admin' flag on the request object
app.use(admin.middleware);
// Check if banned
app.use(function(req, res, next) {
if (req.cookies.banned) {
res.sendStatus(403);
res.end();
} else {
next();
}
});
// Opening redirect and room index
app.get('/', (req, res) => res.redirect(`/room/${uuidv4()}/`));
let roomPath = '/room/:room([0-9a-f-]{36})';
app.get(roomPath + '/', function(req, res) {
res.sendFile(__dirname + '/static/index.html', {
headers: {
'Content-Security-Policy': [
'default-src \'self\'',
'style-src \'unsafe-inline\' \'self\'',
'script-src \'self\' https://www.google.com/recaptcha/ https://www.gstatic.com/recaptcha/',
'frame-src \'self\' https://www.google.com/recaptcha/',
].join('; ')
},
});
});
// Process incoming messages
app.all(roomPath + '/send', async function(req, res) {
let room = req.params.room, {msg, name} = req.query, response = {}, arg;
console.log(`${room} <-- (${name}):`, msg)
if (!(req.headers.referer || '').replace(/^https?:\/\//, '').startsWith(req.headers.host)) {
response = {type: "error", error: 'CSRF protection error'};
} else if (msg[0] != '/') {
broadcast(room, {type: 'msg', name, msg});
} else {
switch (msg.match(/^\/[^ ]*/)[0]) {
case '/name':
if (!(arg = msg.match(/\/name (.+)/))) break;
response = {type: 'rename', name: arg[1]};
broadcast(room, {type: 'name', name: arg[1], old: name});
case '/ban':
if (!(arg = msg.match(/\/ban (.+)/))) break;
if (!req.admin) break;
broadcast(room, {type: 'ban', name: arg[1]});
case '/secret':
if (!(arg = msg.match(/\/secret (.+)/))) break;
res.setHeader('Set-Cookie', 'flag=' + arg[1] + '; Path=/; Max-Age=31536000');
response = {type: 'secret'};
case '/report':
if (!(arg = msg.match(/\/report (.+)/))) break;
var ip = req.headers['x-forwarded-for'];
ip = ip ? ip.split(',')[0] : req.connection.remoteAddress;
response = await admin.report(arg[1], ip, `https://${req.headers.host}/room/${room}/`);
}
}
console.log(`${room} --> (${name}):`, response)
res.json(response);
res.status(200);
res.end();
});
// Process room broadcast messages
const rooms = new Map();
app.get(roomPath + '/receive', function(req, res) {
res.setHeader('X-Accel-Buffering', 'no');
let channel = new SSEClient(req, res);
channel.initialize();
let roomName = req.params.room;
let room = rooms.get(roomName) || new Set();
rooms.set(roomName, room.add(channel))
req.once('close', () => { room.size > 1 ? room.delete(channel) : rooms.delete(roomName) });
});
// Broadcast to all instances using Cloud Pub/Sub. For local testing, it's easy
// to skip by commenting it out and patching the broadcast fn below.
var publisher;
pubsub.createTopic('catchat', function() {
var topic = pubsub.topic('catchat');
publisher = topic.publisher();
topic.createSubscription('catchat-' + uuidv4(), {ackDeadlineSeconds: 10}).then(function(data) {
data[0].on('message', function(msg) {
msg.ack();
var room = msg.attributes.room;
if (!rooms.has(room)) return;
var msg = msg.data.toString('utf-8');
console.log(`${room} ^^^`, msg)
for (let channel of rooms.get(room)) channel.send(msg);
});
});
});
function broadcast(room, msg) {
// for (let channel of (rooms.get(room) || [])) channel.send(JSON.stringify(msg)); // Local broadcast only
publisher.publish(Buffer.from(JSON.stringify(msg)), {room: room}); // Pub/Sub broadcast
}
// Static files
app.get('/server.js', (req, res) => res.sendFile(__filename));
app.use(express.static(__dirname + '/static/', {fallthrough: false}));
app.listen(8080);