Skip to content

Commit

Permalink
Merge pull request #20 from mitchwadair/development
Browse files Browse the repository at this point in the history
Add Session Pooling for Private APIs, auth through Twitch
  • Loading branch information
mitchwadair committed May 16, 2020
2 parents 6e61c01 + f7777c0 commit d1869c6
Show file tree
Hide file tree
Showing 5 changed files with 160 additions and 18 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"info": {
"_postman_id": "423a2935-5f5d-4319-8237-5f38517a07c7",
"name": "MtheBot_ Contact API",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"item": [
{
"name": "POST",
"request": {
"method": "POST",
"header": [],
"body": {
"mode": "raw",
"raw": "{\n\t\"name\": \"Bot User\",\n\t\"email\": \"user@example.com\",\n\t\"type\": \"General\",\n\t\"subject\": \"Just a comment\",\n\t\"message\": \"I love your bot!\"\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "localhost:8080/contact",
"host": [
"localhost"
],
"port": "8080",
"path": [
"contact"
]
}
},
"response": []
}
],
"protocolProfileBehavior": {}
}
83 changes: 65 additions & 18 deletions API Server/apiserver.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,24 @@
// https://opensource.org/licenses/MIT

const http = require('http');
const https = require('https');
const url = require('url');
const users = require('./publicAPIs/users');
const commands = require('./privateAPIs/commands');
const timers = require('./privateAPIs/timers');
const events = require('./privateAPIs/events');
const chats = require('./privateAPIs/chats');
const init = require('./privateAPIs/init');
const contact = require('./privateAPIs/contact');

const getChannelFromURL = require('./utils').getChannelFromURL;

module.exports = function(db, actions) {
// API routes
const apiRoutes = {
public: {
'users': users,
'contact': contact,
},
private: {
'commands': commands,
Expand All @@ -27,39 +32,81 @@ module.exports = function(db, actions) {
}
}

let sessionPool = {};

// request handler
const apiRequestHandler = (req, res) => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, DELETE');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');

// prevent CORS issue
if (req.method === 'OPTIONS') {
res.writeHead(200);
res.end();
return;
}

const originHeaderIPs = req.headers['x-forwarded-for'] ? req.headers['x-forwarded-for'].split(', ') : null;
const origin = originHeaderIPs ? originHeaderIPs[originHeaderIPs.length - 2] : null;
const path = url.parse(req.url).pathname.split('/')[1];
//console.log(`** API REQUEST from origin ${origin}`);

const allowedOrigins = process.env.ALLOWED_ORIGINS.split(',');
const isPrivateRequest = Object.keys(apiRoutes.private).includes(path);

if (isPrivateRequest && origin !== null && !allowedOrigins.includes(origin)) {
res.writeHead(401);
res.end('Unauthorized request to private API');
return;
}

const handler = isPrivateRequest ? apiRoutes.private[path] : apiRoutes.public[path];
if (handler) {
isPrivateRequest ? handler(db, actions, req, res) : handler(db, req, res);

if (isPrivateRequest) {
const channel = getChannelFromURL(req.url);
if (sessionPool[channel]) {
clearTimeout(sessionPool[channel].timeout);
sessionPool[channel] = {
timeout: setTimeout(_ => {
clearTimeout(sessionPool[channel].timeout);
delete sessionPool[channel];
}, 300000),
}
if (handler) {
handler(db, actions, req, res);
} else {
res.writeHead(404);
res.end('Not Found');
}
} else {
const headers = {
'Authorization': req.headers.authorization,
'Client-ID': process.env.CLIENT_ID,
}
https.get('https://api.twitch.tv/helix/users', {headers: headers}, r => {
let body = [];
r.on('error', err => {
res.writeHead(500);
res.end(`ERROR: ${err}`);
}).on('data', chunk => {
body.push(chunk);
}).on('end', _ => {
body = JSON.parse(Buffer.concat(body).toString());
if (body.data[0].login !== channel) {
res.writeHead(401);
res.end('Unauthorized request to private API');
} else {
sessionPool[channel] = {
timeout: setTimeout(_ => {
clearTimeout(sessionPool[channel].timeout);
delete sessionPool[channel];
}, 300000),
}
if (handler) {
handler(db, actions, req, res);
} else {
res.writeHead(404);
res.end('Not Found');
}
}
});
});
}
} else {
res.writeHead(404);
res.end('Not Found');
if (handler) {
handler(db, req, res);
} else {
res.writeHead(404);
res.end('Not Found');
}
}
}

Expand Down
52 changes: 52 additions & 0 deletions API Server/privateAPIs/contact.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
const nodemailer = require('nodemailer');

const post = (req, res) => {
let body = [];
req.on('error', err => {
res.writeHead(500);
res.end(`ERROR: ${err}`);
}).on('data', chunk => {
body.push(chunk);
}).on('end', _ => {
body = JSON.parse(Buffer.concat(body).toString());
const emailData = {
from: '', //this is ignored by gmail
to: process.env.GMAIL_USERNAME,
subject: `${body.type}: ${body.subject} from ${body.name}`,
html: `
<p>${body.name}: <a href="mailto:${body.email}?subject=RE: ${body.type}: ${body.subject}&body=\n\nYou said:\n> ${body.message}">${body.email}</a></p></br>
<p>${body.message}</p>
`,
}

const transport = nodemailer.createTransport({
host: 'smtp.gmail.com',
port: 465,
secure: true,
auth: {
user: process.env.GMAIL_USERNAME,
pass: process.env.GMAIL_PASSWORD
},
});
transport.sendMail(emailData, err => {
if (err) {
res.writeHead(500);
res.end(`ERROR: ${err}`);
return;
}
res.writeHead(200);
res.end("contact sent sucessfully");
});
});
}

module.exports = (db, req, res) => {
switch (req.method) {
case 'POST':
post(req, res);
break;
default:
res.writeHead(400);
res.end('Bad Request');
}
}
5 changes: 5 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"dependencies": {
"dotenv": "^8.2.0",
"mysql": "^2.18.1",
"nodemailer": "^6.4.6",
"tmi.js": "^1.5.0"
},
"devDependencies": {},
Expand Down

0 comments on commit d1869c6

Please sign in to comment.