Skip to content

Commit

Permalink
Use centralized config management (schn4ck#62)
Browse files Browse the repository at this point in the history
* feat(conf): WIP use nconf to manage configuration

* refactor(config): use a central config management

* fix(config): rewrite config hierarchy

* refactor(config): fix small linting problem

* fix(config): don't need date_format in globals
  • Loading branch information
g-div authored Feb 23, 2018
1 parent 9e36d01 commit 59e2cea
Show file tree
Hide file tree
Showing 9 changed files with 425 additions and 1,421 deletions.
1,681 changes: 320 additions & 1,361 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"lodash.countby": "^4.6.0",
"marked": "^0.3.9",
"moment": "^2.18.1",
"nconf": "^0.10.0",
"passport": "^0.4.0",
"passport-facebook": "^2.1.1",
"passport-github2": "^0.1.11",
Expand Down
47 changes: 25 additions & 22 deletions src/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,20 @@ const GoogleStrategy = require('passport-google-oauth').OAuth2Strategy;
const FacebookStrategy = require('passport-facebook').Strategy;

const queries = require('./db/queries');
const config = require('../config.json');
const config = require('./config');
const authConfig = config.get('oauth');
const trustConfig = config.get('trust');
const schnack_host = config.get('schnack_host');

const providers = [];

function init(app, db, domain) {
app.use(session({
resave: false,
saveUninitialized: false,
secret: config.oauth.secret,
secret: authConfig.secret,
cookie: { domain: `.${domain}` },
store: new SQLiteStore({ db: config.database.sessions || 'sessions.db' })
store: new SQLiteStore({ db: config.get('database').sessions })
}));

app.use(passport.initialize());
Expand All @@ -28,9 +31,9 @@ function init(app, db, domain) {
if (row) return done(null, row); // welcome back
// nice to meet you, new user!
// check if id shows up in auto-trust config
var trusted = config.trust &&
config.trust[user.provider] &&
config.trust[user.provider].indexOf(user.id) > -1 ? 1 : 0;
var trusted = trustConfig &&
trustConfig[user.provider] &&
trustConfig[user.provider].indexOf(user.id) > -1 ? 1 : 0;
const c_args = [user.provider, user.id, user.displayName, user.username || user.displayName, trusted];
db.run(queries.create_user, c_args, (err, res) => {
if (err) return console.error(err);
Expand All @@ -50,12 +53,12 @@ function init(app, db, domain) {
});

// twitter auth
if (config.oauth.twitter) {
if (authConfig.twitter) {
providers.push({ id: 'twitter', name: 'Twitter' });
passport.use(new TwitterStrategy({
consumerKey: config.oauth.twitter.consumer_key,
consumerSecret: config.oauth.twitter.consumer_secret,
callbackURL: `${config.schnack_host}/auth/twitter/callback`
consumerKey: authConfig.twitter.consumer_key,
consumerSecret: authConfig.twitter.consumer_secret,
callbackURL: `${schnack_host}/auth/twitter/callback`
}, (token, tokenSecret, profile, done) => {
done(null, profile);
}));
Expand All @@ -74,12 +77,12 @@ function init(app, db, domain) {
}

// github auth
if (config.oauth.github) {
if (authConfig.github) {
providers.push({ id: 'github', name: 'Github' });
passport.use(new GitHubStrategy({
clientID: config.oauth.github.client_id,
clientSecret: config.oauth.github.client_secret,
callbackURL: `${config.schnack_host}/auth/github/callback`
clientID: authConfig.github.client_id,
clientSecret: authConfig.github.client_secret,
callbackURL: `${schnack_host}/auth/github/callback`
}, (accessToken, refreshToken, profile, done) => {
done(null, profile);
}));
Expand All @@ -100,12 +103,12 @@ function init(app, db, domain) {
}

// google oauth
if (config.oauth.google) {
if (authConfig.google) {
providers.push({ id: 'google', name: 'Google' });
passport.use(new GoogleStrategy({
clientID: config.oauth.google.client_id,
clientSecret: config.oauth.google.client_secret,
callbackURL: `${config.schnack_host}/auth/google/callback`
clientID: authConfig.google.client_id,
clientSecret: authConfig.google.client_secret,
callbackURL: `${schnack_host}/auth/google/callback`
}, (accessToken, refreshToken, profile, done) => {
done(null, profile);
}));
Expand All @@ -126,12 +129,12 @@ function init(app, db, domain) {
}

// facebook oauth
if (config.oauth.facebook) {
if (authConfig.facebook) {
providers.push({ id: 'facebook', name: 'Facebook' });
passport.use(new FacebookStrategy({
clientID: client.oauth.facebook.client_id,
clientSecret: config.oauth.facebook.client_secret,
callbackURL: `${config.schnack_host}/auth/facebook/callback`
clientID: authConfig.facebook.client_id,
clientSecret: authConfig.facebook.client_secret,
callbackURL: `${schnack_host}/auth/facebook/callback`
}, (accessToken, refreshToken, profile, done) => {
done(null, profile);
}));
Expand Down
33 changes: 33 additions & 0 deletions src/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
const nconf = require('nconf');
const crypto = require('crypto');
const webpush = require('web-push');
const os = require('os');

// VAPID keys should only be generated only once.
const vapidKeys = webpush.generateVAPIDKeys();

nconf.argv()
.file({ file: './config.json' })
.env()
.defaults({
admins: [1],
schnack_host: `http://localhost`,
database: {
comments: 'comments.db',
sessions: 'sessions.db'
},
port: 3000,
date_format: 'MMMM DD, YYYY - h:mm a',
notification_interval: 300000,
oauth: {
secret: crypto.randomBytes(64).toString('hex')
},
notify: {
webpush: {
vapid_public_key: vapidKeys.publicKey,
vapid_private_key: vapidKeys.privateKey
}
}
});

module.exports = nconf;
8 changes: 5 additions & 3 deletions src/db/index.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
const fs = require('fs');
const path = require('path');
const db = require('sqlite');
const config = require('../../config.json');
const dbname = (typeof config.database == 'string' ? config.database : config.database.comments) || 'comments.db';
const dbpath = path.resolve(__dirname, `../../${dbname}`);
const config = require('../config');
const conf = config.get('database');

// returns promise that passes db obj
function init() {
const dbname = conf.comments || conf;
const dbpath = path.resolve(process.cwd(), dbname);

return Promise.resolve(db.open(dbpath, { Promise }))
.then(db => db.migrate({
// force: process.env.NODE_ENV === 'development' ? 'last' : false
Expand Down
27 changes: 15 additions & 12 deletions src/helper.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const fs = require('fs');
const url = require('url');
const config = require('../config.json');
const queries = require('./db/queries');
const config = require('./config');

const schnack_domain = getSchnackDomain();
function send_file(file, admin_only) {
Expand Down Expand Up @@ -34,13 +34,13 @@ function error(err, request, reply, code) {
}

function getUser(request) {
if (config.dev) return { id: 1, name: 'Dev', display_name: 'Dev', admin: true, trusted: 1};
if (config.get('dev')) return { id: 1, name: 'Dev', display_name: 'Dev', admin: true, trusted: 1};
const { user } = request.session.passport || {};
return user;
}

function isAdmin(user) {
return user && user.id && config.admins.indexOf(user.id) > -1;
return user && user.id && config.get('admins').indexOf(user.id) > -1;
}

function checkOrigin(origin, callback) {
Expand All @@ -66,18 +66,21 @@ function checkValidComment(db, slug, user_id, comment, replyTo, callback) {
}

function getSchnackDomain() {
const schnack_url = url.parse(config.schnack_host);
if (!schnack_url.hostname) {
console.error(`"${config.schnack_host}" doesn't appear to be a proper URL. Did you forget "http://"?`);
const schnack_host = config.get('schnack_host');
try {
const schnack_url = url.parse(schnack_host);

if (schnack_url.hostname === 'localhost') {
return schnack_url.hostname;
} else {
const schnack_domain = schnack_url.hostname.split('.').slice(1).join('.');
return schnack_domain;
}
} catch (error) {
console.error(`The schnack_host value "${schnack_host}" doesn't appear to be a proper URL. Did you forget "http://"?`);
process.exit(-1);
}

if (schnack_url.hostname === 'localhost') {
return schnack_url.hostname;
} else {
const schnack_domain = schnack_url.hostname.split('.').slice(1).join('.');
return schnack_domain;
}
}

module.exports = {
Expand Down
24 changes: 13 additions & 11 deletions src/push/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,36 @@ const fs = require('fs');
const countBy = require('lodash.countby');
const webpush = require('web-push');
const Pushover = require('pushover-notifications');
const config = require('../../config.json');
const queries = require('../db/queries');
const slack = require('./slack');
const {
send_file,
send_string,
error
} = require('../helper');
const config = require('../config');
const notify = config.get('notify');
const schnack_host = config.get('schnack_host');

function init(app, db, awaiting_moderation) {
// push notification apps
const notifier = [];

// each notification app could hook into the
// the notifier array
if (config.notify.pushover) {
if (notify.pushover) {
const push = new Pushover({
token: config.notify.pushover.app_token,
user: config.notify.pushover.user_key
token: notify.pushover.app_token,
user: notify.pushover.user_key
});
notifier.push((msg, callback) => push.send(msg, callback));
}

if (config.notify.webpush) {
if (notify.webpush) {
webpush.setVapidDetails(
config.schnack_host,
config.notify.webpush.vapid_public_key,
config.notify.webpush.vapid_private_key
schnack_host,
notify.webpush.vapid_public_key,
notify.webpush.vapid_private_key
);

notifier.push((msg, callback) => {
Expand Down Expand Up @@ -76,14 +78,14 @@ function init(app, db, awaiting_moderation) {
}, 1000);
});
}
}, config.notification_interval || 300000); // five minutes
}, config.get('notification_interval'));

// serve static js files
app.use('/embed.js', send_file('build/embed.js'));
app.use('/client.js', send_file('build/client.js'));
app.use('/push.js', send_string(fs.readFileSync('src/embed/push.js', 'utf-8')
.replace('%VAPID_PUBLIC_KEY%', config.notify.webpush.vapid_public_key)
.replace('%SCHNACK_HOST%', config.schnack_host), true));
.replace('%VAPID_PUBLIC_KEY%', notify.webpush.vapid_public_key)
.replace('%SCHNACK_HOST%', schnack_host), true));

// push notifications
app.post('/subscribe', (request, reply) => {
Expand Down
10 changes: 6 additions & 4 deletions src/push/slack.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@

const request = require('request');
const config = require('../../config.json');
const config = require('../config');
const schnackEvents = require('../events');

if (config.notify.slack) {
const notify = config.get('notify');

if (notify.slack) {
schnackEvents.on('new-comment', (event) => {
try {
const post_url = config.page_url.replace('%SLUG%', event.slug)+'#comment-'+event.id;
const post_url = config.get('page_url').replace('%SLUG%', event.slug)+'#comment-'+event.id;
const comment = event.comment.split(/\n+/).map(s => s ? `> _${s}_` : '>').join('\n>\n');
const text = `A <${post_url}|new comment> was posted by ${event.user.display_name || event.user.name} under *${event.slug}*:\n\n${comment}`;
request({
url: config.notify.slack.webhook_url,
url: notify.slack.webhook_url,
method: 'post',
json: true,
body: { text }
Expand Down
15 changes: 7 additions & 8 deletions src/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ const {
getSchnackDomain
} = require('./helper');

const config = require('../config.json');
const config = require('./config');

const awaiting_moderation = [];

marked.setOptions({ sanitize: true });
Expand Down Expand Up @@ -58,11 +59,12 @@ function run(db) {
args.length = 1;
}

const date_format = config.get('date_format');
db.all(query, args, (err, comments) => {
if (error(err, request, reply)) return;
comments.forEach((c) => {
const m = moment.utc(c.created_at);
c.created_at_s = config.date_format ? m.format(config.date_format) : m.fromNow();
c.created_at_s = date_format ? m.format(date_format) : m.fromNow();
c.comment = marked(c.comment.trim());
c.author_url = auth.getAuthorUrl(c);
});
Expand Down Expand Up @@ -124,7 +126,7 @@ function run(db) {
app.get('/feed', (request, reply) => {
var feed = new RSS({
title: 'Awaiting moderation',
site_url: config.allow_origin[0]
site_url: config.allow_origin[0] // @FIXME
});
db.each(queries.awaiting_moderation, (err, row) => {
if (err) console.error(err.message);
Expand Down Expand Up @@ -158,15 +160,12 @@ function run(db) {
});
});

if (config.dev) {
if (config.get('dev')) {
// create dev user for testing purposes
db.run('INSERT OR IGNORE INTO user (id,name,blocked,trusted,created_at) VALUES (1,"dev",0,1,datetime())');
}

const port = config.port || process.env.PORT || 3000;
const host = config.host || process.env.HOST || "127.0.0.1";

var server = app.listen(port, host, (err) => {
var server = app.listen(config.get('port'), config.get('host'), (err) => {
if (err) throw err;
console.log(`server listening on ${server.address().port}`);
});
Expand Down

0 comments on commit 59e2cea

Please sign in to comment.