-
Notifications
You must be signed in to change notification settings - Fork 22
/
Copy pathbackend.js
150 lines (129 loc) · 4.66 KB
/
backend.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
const fs = require('fs');
const Hapi = require('hapi');
const path = require('path');
const Boom = require('boom');
const color = require('color');
const ext = require('commander');
const jsonwebtoken = require('jsonwebtoken');
// const request = require('request');
// The developer rig uses self-signed certificates. Node doesn't accept them
// by default. Do not use this in production.
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
// Use verbose logging during development. Set this to false for production.
const verboseLogging = true;
const verboseLog = verboseLogging ? console.log.bind(console) : () => { };
// Service state variables
const initialColor = color('#6441A4'); // set initial color; bleedPurple
const bearerPrefix = 'Bearer '; // HTTP authorization headers have this prefix
const colorWheelRotation = 30;
const channelColors = {};
const STRINGS = {
secretEnv: usingValue('secret'),
clientIdEnv: usingValue('client-id'),
serverStarted: 'Server running at %s',
secretMissing: missingValue('secret', 'EXT_SECRET'),
clientIdMissing: missingValue('client ID', 'EXT_CLIENT_ID'),
cyclingColor: 'Cycling color for c:%s on behalf of u:%s',
sendColor: 'Sending color %s to c:%s',
invalidAuthHeader: 'Invalid authorization header',
invalidJwt: 'Invalid JWT'
};
ext.
version(require('../package.json').version).
option('-s, --secret <secret>', 'Extension secret').
option('-c, --client-id <client_id>', 'Extension client ID').
parse(process.argv);
const secret = Buffer.from(getOption('secret', 'ENV_SECRET'), 'base64');
const clientId = getOption('clientId', 'ENV_CLIENT_ID');
const serverOptions = {
host: 'localhost',
port: 8081,
routes: {
cors: {
origin: ['*']
}
}
};
const serverPathRoot = path.resolve(__dirname, '..', 'conf', 'server');
if (fs.existsSync(serverPathRoot + '.crt') && fs.existsSync(serverPathRoot + '.key')) {
serverOptions.tls = {
// If you need a certificate, execute "npm run cert".
cert: fs.readFileSync(serverPathRoot + '.crt'),
key: fs.readFileSync(serverPathRoot + '.key')
};
}
const server = new Hapi.Server(serverOptions);
(async () => {
// Handle a viewer request to cycle the color.
server.route({
method: 'POST',
path: '/color/cycle',
handler: colorCycleHandler
});
// Handle a new viewer requesting the color.
server.route({
method: 'GET',
path: '/color/query',
handler: colorQueryHandler
});
// Start the server.
await server.start();
console.log(STRINGS.serverStarted, server.info.uri);
})();
function usingValue (name) {
return `Using environment variable for ${name}`;
}
function missingValue (name, variable) {
const option = name.charAt(0);
return `Extension ${name} required.\nUse argument "-${option} <${name}>" or environment variable "${variable}".`;
}
// Get options from the command line or the environment.
function getOption (optionName, environmentName) {
const option = (() => {
if (ext[optionName]) {
return ext[optionName];
} else if (process.env[environmentName]) {
console.log(STRINGS[optionName + 'Env']);
return process.env[environmentName];
}
console.log(STRINGS[optionName + 'Missing']);
process.exit(1);
})();
console.log(`Using "${option}" for ${optionName}`);
return option;
}
// Verify the header and the enclosed JWT.
function verifyAndDecode (header) {
if (header.startsWith(bearerPrefix)) {
try {
const token = header.substring(bearerPrefix.length);
return jsonwebtoken.verify(token, secret, { algorithms: ['HS256'] });
}
catch (ex) {
throw Boom.unauthorized(STRINGS.invalidJwt);
}
}
throw Boom.unauthorized(STRINGS.invalidAuthHeader);
}
function colorCycleHandler (req) {
// Verify all requests.
const payload = verifyAndDecode(req.headers.authorization);
const { channel_id: channelId, opaque_user_id: opaqueUserId } = payload;
// Store the color for the channel.
let currentColor = channelColors[channelId] || initialColor;
// Rotate the color as if on a color wheel.
verboseLog(STRINGS.cyclingColor, channelId, opaqueUserId);
currentColor = color(currentColor).rotate(colorWheelRotation).hex();
// Save the new color for the channel.
channelColors[channelId] = currentColor;
return currentColor;
}
function colorQueryHandler (req) {
// Verify all requests.
const payload = verifyAndDecode(req.headers.authorization);
// Get the color for the channel from the payload and return it.
const { channel_id: channelId, opaque_user_id: opaqueUserId } = payload;
const currentColor = color(channelColors[channelId] || initialColor).hex();
verboseLog(STRINGS.sendColor, currentColor, opaqueUserId);
return currentColor;
}