Webhook endpoint with secret in config
This commit is contained in:
@@ -52,11 +52,29 @@ git push -u origin master
|
|||||||
|
|
||||||
**5. Refresh content with a webhook (optional)**
|
**5. Refresh content with a webhook (optional)**
|
||||||
|
|
||||||
At first start, a `git_secret` file will be generated, use it to create a new webhook as following :
|
Create a webhook on your git source (On GitHub, in the `Settings/Webhooks` part of the repository.) with the following parameters :
|
||||||
|
|
||||||
* Payload URL : `https://<url_of_your_server>/webhook`
|
* Payload URL : `https://<url_of_your_server>/webhook`
|
||||||
* Content type : `application/json`
|
* Content type : `application/json`
|
||||||
* Secret : `<content of the git_secret file>`
|
|
||||||
* Events : Just the push event
|
* Events : Just the push event
|
||||||
|
|
||||||
On GitHub, webhooks can be created in the `Settings/Webhooks` part of the repository.
|
**6. Securize your webhook (optional)**
|
||||||
|
|
||||||
|
Here are the steps for Github, if you use another platform adapt it your way (header format on the config) :
|
||||||
|
|
||||||
|
* Create a password or random secret
|
||||||
|
* Calculate it's SHA1
|
||||||
|
* Edit your configuration to add webhook info
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
...
|
||||||
|
"webhook": {
|
||||||
|
"secret_value": "sha1=<value>",
|
||||||
|
"secret_header": "X-Hub-Signature"
|
||||||
|
},
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
* Launch the server
|
||||||
|
* Update your webhook on github to include the secret
|
||||||
|
* Check if Github successfully reached the endpoint
|
||||||
+14
-42
@@ -14,16 +14,6 @@ const cons = {
|
|||||||
error: '\x1b[31m✘\x1b[0m %s',
|
error: '\x1b[31m✘\x1b[0m %s',
|
||||||
};
|
};
|
||||||
|
|
||||||
const randStr = (length) => {
|
|
||||||
let result = '';
|
|
||||||
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
||||||
const charactersLength = characters.length;
|
|
||||||
for (let i = 0; i < length; i++) {
|
|
||||||
result += characters.charAt(Math.floor(Math.random() * charactersLength));
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = (config) => {
|
module.exports = (config) => {
|
||||||
const fw = require('./file_walker')(config);
|
const fw = require('./file_walker')(config);
|
||||||
const renderer = require('./renderer')(config);
|
const renderer = require('./renderer')(config);
|
||||||
@@ -35,7 +25,6 @@ module.exports = (config) => {
|
|||||||
|
|
||||||
const articles = {};
|
const articles = {};
|
||||||
let lastRSS = '';
|
let lastRSS = '';
|
||||||
let webhookSecret;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch articles from the data folder and send success as a response
|
* Fetch articles from the data folder and send success as a response
|
||||||
@@ -64,35 +53,6 @@ module.exports = (config) => {
|
|||||||
if (config['test'])
|
if (config['test'])
|
||||||
app.reload = reload;
|
app.reload = reload;
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch or create secret token for git webhook
|
|
||||||
* @param success
|
|
||||||
* @param error
|
|
||||||
*/
|
|
||||||
const checkSecret = (success, error) => {
|
|
||||||
if (!config['modules']['webhook'])
|
|
||||||
success();
|
|
||||||
fs.readFile(config['webhook']['secret_file'], {encoding: 'UTF-8'}, (err, data) => {
|
|
||||||
if (err) {
|
|
||||||
webhookSecret = randStr(32);
|
|
||||||
fs.writeFile(config['webhook']['secret_file'], webhookSecret, {encoding: 'UTF-8'}, (err) => {
|
|
||||||
if (err) {
|
|
||||||
console.error(cons.error, 'error creating secret : ' + err);
|
|
||||||
return error ? error() : null;
|
|
||||||
}
|
|
||||||
console.log(cons.ok,'created git secret at '+config['webhook']['secret_file']);
|
|
||||||
success();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
webhookSecret = data;
|
|
||||||
console.log(cons.ok,'loaded git secret from '+config['webhook']['secret_file']);
|
|
||||||
success();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
if (config['test'])
|
|
||||||
app.checkSecret = checkSecret;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render the page with the view engine and catch errors
|
* Render the page with the view engine and catch errors
|
||||||
* @param res
|
* @param res
|
||||||
@@ -164,6 +124,20 @@ module.exports = (config) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//webhook endpoint
|
||||||
|
app.post(config['webhook']['endpoint'], (req, res) => {
|
||||||
|
if (config['modules']['webhook']) {
|
||||||
|
if (config['webhook']['secret_header'] && req.get(config['webhook']['secret_header']) !== config['webhook']['secret_value']) {
|
||||||
|
res.sendStatus(403);
|
||||||
|
} else {
|
||||||
|
res.sendStatus(200);
|
||||||
|
//TODO reload
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
res.sendStatus(400);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// catch all article urls and render them
|
// catch all article urls and render them
|
||||||
app.get('*', (req, res, next) => {
|
app.get('*', (req, res, next) => {
|
||||||
if (/^\/\d{4}\/\d{2}\/\d{2}\/(\w*\/)?$/.test(req.path)) {
|
if (/^\/\d{4}\/\d{2}\/\d{2}\/(\w*\/)?$/.test(req.path)) {
|
||||||
@@ -222,12 +196,10 @@ module.exports = (config) => {
|
|||||||
// must be use in a server.js to start the server
|
// must be use in a server.js to start the server
|
||||||
app.start = () => {
|
app.start = () => {
|
||||||
reload(() => {
|
reload(() => {
|
||||||
checkSecret(() => {
|
|
||||||
app.listen(config['node_port'], () => {
|
app.listen(config['node_port'], () => {
|
||||||
console.log(cons.ok, `gitblog.md server listening on port ${config['node_port']}`);
|
console.log(cons.ok, `gitblog.md server listening on port ${config['node_port']}`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return app;
|
return app;
|
||||||
|
|||||||
@@ -30,7 +30,8 @@
|
|||||||
},
|
},
|
||||||
"webhook": {
|
"webhook": {
|
||||||
"endpoint": "/webhook",
|
"endpoint": "/webhook",
|
||||||
"secret_file": "git_secret"
|
"secret_value": "",
|
||||||
|
"secret_header": ""
|
||||||
},
|
},
|
||||||
"showdown": {
|
"showdown": {
|
||||||
"parseImgDimensions": true,
|
"parseImgDimensions": true,
|
||||||
|
|||||||
+36
-46
@@ -19,6 +19,7 @@ config['article']['template'] = testTemplate;
|
|||||||
config['home']['hidden'].push('.test');
|
config['home']['hidden'].push('.test');
|
||||||
config['rss']['endpoint'] = '/rsstest';
|
config['rss']['endpoint'] = '/rsstest';
|
||||||
config['rss']['length'] = 2;
|
config['rss']['length'] = 2;
|
||||||
|
config['webhook']['endpoint'] = '/webhooktest';
|
||||||
|
|
||||||
const app = require('../src/app')(config);
|
const app = require('../src/app')(config);
|
||||||
|
|
||||||
@@ -74,52 +75,6 @@ describe('Test root path', () => {
|
|||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
}, fail);
|
}, fail);
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Test check secret', () => {
|
|
||||||
const secretFile = 'git_secret';
|
|
||||||
const tmpSecretFile = 'tmp_git_secret';
|
|
||||||
beforeEach(() => {
|
|
||||||
if (fs.existsSync(secretFile))
|
|
||||||
fs.renameSync(secretFile, tmpSecretFile);
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
if (fs.existsSync(tmpSecretFile)) {
|
|
||||||
fs.renameSync(tmpSecretFile, secretFile);
|
|
||||||
} else if (fs.existsSync(secretFile)) {
|
|
||||||
fs.unlinkSync(secretFile); //remove secret file if remaining
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test('no check if not activated', (done, fail) => {
|
|
||||||
config['modules']['webhook'] = false;
|
|
||||||
app.checkSecret(() => {
|
|
||||||
config['modules']['webhook'] = true;
|
|
||||||
done();
|
|
||||||
}, () => {
|
|
||||||
config['modules']['webhook'] = true;
|
|
||||||
fail();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
test('create if not exists', (done, fail) => {
|
|
||||||
if (fs.existsSync(secretFile))
|
|
||||||
fs.unlinkSync(secretFile);
|
|
||||||
app.checkSecret(() => {
|
|
||||||
expect(fs.existsSync(secretFile)).toBe(true);
|
|
||||||
expect(fs.readFileSync(secretFile).length).toBeGreaterThan(0);
|
|
||||||
done();
|
|
||||||
}, fail);
|
|
||||||
});
|
|
||||||
test('read if exists', (done, fail) => {
|
|
||||||
fs.writeFileSync(secretFile,'secret value');
|
|
||||||
app.checkSecret(() => {
|
|
||||||
expect(fs.existsSync(secretFile)).toBe(true);
|
|
||||||
expect(fs.readFileSync(secretFile, {encoding:'UTF-8'})).toBe('secret value');
|
|
||||||
done();
|
|
||||||
}, fail);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -180,6 +135,41 @@ describe('Test RSS feed', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Test webhook', () => {
|
||||||
|
test('400 webhook deactivated', (done) => {
|
||||||
|
config['modules']['webhook'] = false;
|
||||||
|
request(app).post('/webhooktest').then((response) => {
|
||||||
|
expect(response.statusCode).toBe(400);
|
||||||
|
config['modules']['webhook'] = true;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test('200 no secret', (done) => {
|
||||||
|
request(app).post('/webhooktest').then((response) => {
|
||||||
|
expect(response.statusCode).toBe(200);
|
||||||
|
//TODO test reload
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test('403 wrong secret', (done) => {
|
||||||
|
config['webhook']['secret_header'] = 'testheader';
|
||||||
|
config['webhook']['secret_value'] = 'testvalue';
|
||||||
|
request(app).post('/webhooktest').set('testheader','testvalue2').then((response) => {
|
||||||
|
expect(response.statusCode).toBe(403);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test('200 valid secret', (done) => {
|
||||||
|
config['webhook']['secret_header'] = 'testheader';
|
||||||
|
config['webhook']['secret_value'] = 'testvalue';
|
||||||
|
request(app).post('/webhooktest').set('testheader','testvalue').then((response) => {
|
||||||
|
expect(response.statusCode).toBe(200);
|
||||||
|
//TODO test reload
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('Test articles rendering', () => {
|
describe('Test articles rendering', () => {
|
||||||
test('404 article not found', (done) => {
|
test('404 article not found', (done) => {
|
||||||
request(app).get('/2019/05/06/untitled/').then((response) => {
|
request(app).get('/2019/05/06/untitled/').then((response) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user