Skip to content

Commit e2f2f38

Browse files
committed
Refactoring github code: Webhook
Only validate request ips in production so we can test locally. Webhook will now return 4xx status codes if something goes wrong. Seperate github importing logic from the controller. Move several controllers to their own file. Moved /user/* routes to /account/* Add github webhook test. Broke /account/github/import
1 parent f9ae9b7 commit e2f2f38

13 files changed

+580
-319
lines changed

controllers/githubHook.js

+98
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
var async = require('async');
2+
var _ = require('underscore');
3+
var User = require('../models/user').User;
4+
var githubImporter = require('../libs/githubImporter');
5+
6+
// GitHub calls this on a push if a webhook is setup
7+
// This controller makes sure we have the latest version of a script
8+
module.exports = function (aReq, aRes, aNext) {
9+
// var RepoManager = require('../libs/repoManager');
10+
// var username = null;
11+
// var reponame = null;
12+
// var repos = {};
13+
// var repo = null;
14+
15+
if (!aReq.body.payload)
16+
return aRes.send(400, 'Payload required.');
17+
18+
if (process.env.NODE_ENV === 'production') {
19+
// Test for know GH webhook ips: https://api.github.com/meta
20+
var reqIP = aReq.headers['x-forwarded-for'] || aReq.connection.remoteAddress;
21+
if (!/192\.30\.25[2-5]\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])$/.test(reqIP))
22+
return;
23+
}
24+
25+
26+
var payload = JSON.parse(aReq.body.payload);
27+
28+
// Only accept commits to the master branch
29+
if (!payload || payload.ref !== 'refs/heads/master')
30+
return aRes.send(400, 'payload.ref !== refs/heads/master');
31+
32+
var githubUserName = payload.repository.owner.name;
33+
var githubRepoName = payload.repository.name;
34+
35+
36+
User.findOne({
37+
ghUsername: githubUserName
38+
}, function (aErr, aUser) {
39+
if (!aUser)
40+
return aRes.send(400, 'No account linked to GitHub username ' + username);
41+
42+
// Gather the modified user scripts
43+
var jsFilenames = {};
44+
var mdFilenames = {};
45+
payload.commits.forEach(function (aCommit) {
46+
aCommit.modified.forEach(function (aFilename) {
47+
switch (aFilename.substr(-3)) {
48+
case '.js':
49+
jsFilenames[aFilename] = aFilename;
50+
break;
51+
case '.md':
52+
mdFilenames[aFilename] = aFilename;
53+
break;
54+
}
55+
});
56+
});
57+
jsFilenames = Object.keys(jsFilenames);
58+
mdFilenames = Object.keys(mdFilenames);
59+
60+
console.log('jsFilenames', jsFilenames);
61+
console.log('mdFilenames', mdFilenames);
62+
63+
// Update
64+
async.series([
65+
// Update script code first.
66+
function(aCallback) {
67+
async.map(jsFilenames, function(jsFilename, aCallback) {
68+
console.log(jsFilename);
69+
70+
// var repoManager = RepoManager.getManager(null, aUser, repos);
71+
// repoManager.loadScripts(function () { }, true);
72+
githubImporter.importJavasciptBlob({
73+
user: aUser,
74+
githubUserId: githubUserName,
75+
githubRepoName: githubRepoName,
76+
githubBlobPath: jsFilename,
77+
updateOnly: false
78+
}, aCallback);
79+
}, aCallback);
80+
},
81+
82+
// Update markdown next
83+
function(aCallback) {
84+
async.map(mdFilenames, function(mdFilename, aCallback) {
85+
console.log(mdFilename);
86+
aCallback(null, mdFilename);
87+
}, aCallback);
88+
}
89+
], function(aError, aResults) {
90+
if (aError) {
91+
console.error(aError);
92+
return aRes.send(500, 'Error while updating.');
93+
}
94+
95+
aRes.send(200, aResults);
96+
});
97+
});
98+
};

controllers/githubImport.js

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
var githubImporter = require('../libs/githubImporter');
2+
var modelParser = require('../libs/modelParser');
3+
var statusCodePage = require('../libs/templateHelpers').statusCodePage;
4+
5+
module.exports = function (aReq, aRes, aNext) {
6+
var authedUser = aReq.session.user;
7+
8+
if (!authedUser) return aRes.redirect('/login');
9+
10+
//
11+
var options = {};
12+
var tasks = [];
13+
14+
// Session
15+
authedUser = options.authedUser = modelParser.parseUser(authedUser);
16+
options.isMod = authedUser && authedUser.isMod;
17+
options.isAdmin = authedUser && authedUser.isAdmin;
18+
19+
// GitHub
20+
var githubUserId = options.githubUserId = aReq.body.user || aReq.query.user || authedUser.ghUsername || authedUser.githubUserId();
21+
var githubRepoName = options.githubRepoName = aReq.body.repo || aReq.query.repo;
22+
var githubBlobPath = options.githubBlobPath = aReq.body.path || aReq.query.path;
23+
24+
if (!(githubUserId && githubRepoName && githubBlobPath)) {
25+
return statusCodePage(aReq, aRes, aNext, {
26+
statusCode: 400,
27+
statusMessage: 'Bad Request. Require <code>user</code>, <code>repo</code>, and <code>path</code> to be set.'
28+
});
29+
}
30+
31+
githubImporter.importJavasciptBlob({
32+
user: authedUser,
33+
githubUserId: githubUserId,
34+
githubRepoName: githubRepoName,
35+
githubBlobPath: githubBlobPath,
36+
updateOnly: false
37+
}, function (aErr) {
38+
if (aErr) {
39+
console.error(aErr);
40+
console.error(githubUserId, githubRepoName, githubBlobPath);
41+
return statusCodePage(aReq, aRes, aNext, {
42+
statusCode: 400,
43+
statusMessage: aErr
44+
});
45+
}
46+
47+
var script = modelParser.parseScript(options.script);
48+
aRes.redirect(script.scriptPageUrl);
49+
});
50+
};

controllers/scriptMeta.js

Whitespace-only changes.

controllers/scriptSource.js

Whitespace-only changes.

controllers/scriptStorage.js

+1-162
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ var AWS = require('aws-sdk');
55
var Script = require('../models/script').Script;
66
var User = require('../models/user').User;
77

8+
var scriptParser = require('../libs/scriptParser');
89
var cleanFilename = require('../libs/helpers').cleanFilename;
910
var findDeadorAlive = require('../libs/remove').findDeadorAlive;
1011
var userRoles = require('../models/userRoles.json');
@@ -125,120 +126,6 @@ exports.sendMeta = function (aReq, aRes, aNext) {
125126
});
126127
};
127128

128-
// Modified from Count Issues (http://userscripts.org/scripts/show/69307)
129-
// By Marti Martz (http://userscripts.org/users/37004)
130-
function parseMeta(aString, aNormalize) {
131-
var rLine = /\/\/ @(\S+)(?:\s+(.*))?/;
132-
var headers = {};
133-
var name = null;
134-
var prefix = null;
135-
var key = null;
136-
var value = null;
137-
var line = null;
138-
var lineMatches = null;
139-
var lines = {};
140-
var uniques = {
141-
'description': true,
142-
'icon': true,
143-
'name': true,
144-
'namespace': true,
145-
'version': true,
146-
'oujs:author': true
147-
};
148-
var unique = null;
149-
var one = null;
150-
var matches = null;
151-
152-
lines = aString.split(/[\r\n]+/).filter(function (aElement, aIndex, aArray) {
153-
return (aElement.match(rLine));
154-
});
155-
156-
for (line in lines) {
157-
var header = null;
158-
159-
lineMatches = lines[line].replace(/\s+$/, '').match(rLine);
160-
name = lineMatches[1];
161-
value = lineMatches[2];
162-
if (aNormalize) {
163-
// Upmix from...
164-
switch (name) {
165-
case 'homepage':
166-
case 'source':
167-
case 'website':
168-
name = 'homepageURL';
169-
break;
170-
case 'defaulticon':
171-
case 'iconURL':
172-
name = 'icon';
173-
break;
174-
case 'licence':
175-
name = 'license';
176-
break;
177-
}
178-
}
179-
name = name.split(/:/).reverse();
180-
key = name[0];
181-
prefix = name[1];
182-
if (key) {
183-
unique = {};
184-
if (prefix) {
185-
if (!headers[prefix]) {
186-
headers[prefix] = {};
187-
}
188-
header = headers[prefix];
189-
if (aNormalize) {
190-
for (one in uniques) {
191-
matches = one.match(/(.*):(.*)$/);
192-
if (uniques[one] && matches && matches[1] === prefix) {
193-
unique[matches[2]] = true;
194-
}
195-
}
196-
}
197-
} else {
198-
header = headers;
199-
if (aNormalize) {
200-
for (one in uniques) {
201-
if (uniques[one] && !/:/.test(one)) {
202-
unique[one] = true;
203-
}
204-
}
205-
}
206-
}
207-
if (!header[key] || aNormalize && unique[key]) {
208-
header[key] = value || '';
209-
} else if (!aNormalize || header[key] !== (value || '')
210-
&& !(header[key] instanceof Array && header[key].indexOf(value) > -1)) {
211-
if (!(header[key] instanceof Array)) {
212-
header[key] = [header[key]];
213-
}
214-
header[key].push(value || '');
215-
}
216-
}
217-
}
218-
return headers;
219-
}
220-
exports.parseMeta = parseMeta;
221-
222-
exports.getMeta = function (aChunks, aCallback) {
223-
// We need to convert the array of buffers to a string to
224-
// parse the header. But strings are memory inefficient compared
225-
// to buffers so we only convert the least number of chunks to
226-
// get the user script header.
227-
var str = '';
228-
var i = 0;
229-
var len = aChunks.length;
230-
231-
for (; i < aChunks.length; ++i) {
232-
var header = null;
233-
str += aChunks[i];
234-
header = /^\/\/ ==UserScript==([\s\S]*?)^\/\/ ==\/UserScript==/m.exec(str);
235-
236-
if (header && header[1]) { return aCallback(parseMeta(header[1], true)); }
237-
}
238-
239-
aCallback(null);
240-
};
241-
242129
exports.storeScript = function (aUser, aMeta, aBuf, aCallback, aUpdate) {
243130
var s3 = new AWS.S3();
244131
var scriptName = null;
@@ -374,51 +261,3 @@ exports.deleteScript = function (aInstallName, aCallback) {
374261
});
375262
};
376263

377-
// GitHub calls this on a push if a webhook is setup
378-
// This controller makes sure we have the latest version of a script
379-
exports.webhook = function (aReq, aRes) {
380-
var RepoManager = require('../libs/repoManager');
381-
var payload = null;
382-
var username = null;
383-
var reponame = null;
384-
var repos = {};
385-
var repo = null;
386-
387-
aRes.end(); // Close connection
388-
389-
// Test for know GH webhook ips: https://api.github.com/meta
390-
if (!aReq.body.payload ||
391-
!/192\.30\.25[2-5]\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])$/
392-
.test(aReq.headers['x-forwarded-for'] || aReq.connection.remoteAddress)) {
393-
return;
394-
}
395-
396-
payload = JSON.parse(aReq.body.payload);
397-
398-
// Only accept commits to the master branch
399-
if (!payload || payload.ref !== 'refs/heads/master') { return; }
400-
401-
// Gather all the info for the RepoManager
402-
username = payload.repository.owner.name;
403-
reponame = payload.repository.name;
404-
405-
repo = repos[reponame] = {};
406-
407-
// Find the user that corresponds the repo owner
408-
User.findOne({ ghUsername: username }, function (aErr, aUser) {
409-
if (!aUser) { return; }
410-
411-
// Gather the modified user scripts
412-
payload.commits.forEach(function (aCommit) {
413-
aCommit.modified.forEach(function (aFilename) {
414-
if (aFilename.substr(-8) === '.user.js') {
415-
repo[aFilename] = '/' + encodeURI(aFilename);
416-
}
417-
});
418-
});
419-
420-
// Update modified scripts
421-
var repoManager = RepoManager.getManager(null, aUser, repos);
422-
repoManager.loadScripts(function () { }, true);
423-
});
424-
};

0 commit comments

Comments
 (0)