Skip to content
This repository has been archived by the owner on Apr 19, 2018. It is now read-only.

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
jedireza committed Apr 2, 2016
0 parents commit 29260e4
Show file tree
Hide file tree
Showing 19 changed files with 1,232 additions and 0 deletions.
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
dev-certs
node_modules
npm-debug.log
spacekit.json
spacekit.log*
spacekit-service.json
spacekit-service.log*
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# spacekit-service
6 changes: 6 additions & 0 deletions bin/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/usr/bin/env node
'use strict';
const Config = require('../lib/config');
const SpaceKitService = require('../lib');

module.exports = new SpaceKitService(Config);
19 changes: 19 additions & 0 deletions create-logger.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
'use strict';
const Bunyan = require('bunyan');
const Path = require('path');

const log = Bunyan.createLogger({
name: 'SpaceKitService',
streams: [{
stream: process.stdout
}, {
type: 'rotating-file',
path: Path.resolve(process.cwd(), 'spacekit-service.log'),
period: '1d', // daily
count: 3 // three rotations
}]
});

module.exports = function createLogger (name) {
return log.child({ module: name });
};
37 changes: 37 additions & 0 deletions lib/api/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
'use strict';
const BodyParser = require('body-parser');
const Express = require('express');
const Uuid = require('node-uuid');

const CreateLogger = require('../../create-logger');
const Db = require('../util/db');
const Mailer = require('../util/mailer');
const Recover = require('./recover');
const Reset = require('./reset');
const SignUp = require('./signup');

const log = CreateLogger('ApiApp');

module.exports = function (config) {
const api = Express();

api.mailer = new Mailer(config);
api.db = new Db(config);
api.use(BodyParser.json());
api.use(BodyParser.urlencoded({ extended: true }));

api.use(function (req, res, next) {
req.log = log.child({ reqId: Uuid.v4() });
next();
});

api.get('/', (req, res, next) => {
res.json({ message: 'Welcome to the SpaceKit api.' });
});

api.post('/recover', Recover);
api.post('/reset', Reset);
api.post('/signup', SignUp);

return api;
};
110 changes: 110 additions & 0 deletions lib/api/recover.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
'use strict';
const Async = require('async');
const Bcrypt = require('bcrypt');
const ValidEmail = require('email-validator').validate;
const Uuid = require('node-uuid');

module.exports = function Recover (req, res) {
let reply = {
success: false,
errors: [],
message: null
};

Async.auto({
validate: function (done) {
if (!req.body.hasOwnProperty('email')) {
reply.errors.push('`email` is required');
} else if (!ValidEmail(req.body.email)) {
reply.errors.push('`email` has an invalid format');
}

done();
},
replyImmediately: ['validate', function (done, results) {
// we reply immediatly with a generic response so we don't leak facts

if (reply.errors.length) {
res.json(reply);
return done(Error('Validation errors.'));
}

reply.success = true;
reply.message = 'If that email address matched an account, ' +
'an email will be sent with instructions.';

res.json(reply);

done();
}],
userLookup: ['replyImmediately', function (done, results) {
let email = req.body.email;
let query = 'SELECT id FROM users WHERE email = $1';

req.app.db.run(query, [email], (err, result) => {
if (err) {
return done(err);
}

if (result.rows.length === 0) {
return done(Error('User not found.'));
}

done(null, result.rows[0]);
});
}],
resetToken: ['userLookup', function (done, results) {
let uuid = Uuid.v4();

Async.auto({
salt: function (done) {
Bcrypt.genSalt(10, done);
},
hash: ['salt', function (done, results) {
Bcrypt.hash(uuid, results.salt, done);
}]
}, (err, results) => {
if (err) {
return done(err);
}

done(null, {
plain: uuid,
hash: results.hash
});
});
}],
saveToken: ['resetToken', function (done, results) {
let query = `
UPDATE users
SET reset_token = $1, reset_expires = $2
WHERE id = $3
`;
let params = [
results.resetToken.hash,
new Date(Date.now() + 10000000),
results.userLookup.id
];

req.app.db.run(query, params, done);
}],
sendMail: ['saveToken', function (done, results) {
let emailOpts = {
subject: 'Reset your SpaceKit API key',
to: req.body.email
};
let template = 'recover-api-key';
let context = {
resetToken: results.resetToken.plain
};

req.app.mailer.sendEmail(emailOpts, template, context, done);
}]
}, (err, results) => {
if (err) {
req.log.error(err);
}

// do nothing, we already completed the request
});
};
117 changes: 117 additions & 0 deletions lib/api/reset.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
'use strict';
const Async = require('async');
const Bcrypt = require('bcrypt');
const ValidEmail = require('email-validator').validate;
const Uuid = require('node-uuid');

module.exports = function Reset (req, res) {
let reply = {
success: false,
errors: [],
apikey: null
};

Async.auto({
validate: function (done) {
if (!req.body.hasOwnProperty('email')) {
reply.errors.push('`email` is required');
} else if (!ValidEmail(req.body.email)) {
reply.errors.push('`email` has an invalid format');
}

if (!req.body.hasOwnProperty('token')) {
reply.errors.push('`token` is required');
}

done(reply.errors.length === 0 ? null : Error('Validation errors.'));
},
userLookup: ['validate', function (done, results) {
let query = `
SELECT id, reset_token FROM users
WHERE email = $1 AND reset_expires > $2
`;
let params = [
req.body.email,
new Date()
];

req.app.db.run(query, params, (err, result) => {
if (err) {
reply.errors.push('exception during user lookup');
return done(err);
}

let failMessage = 'either the reset token is invalid ' +
'or the email address is incorrect';

if (result.rows.length === 0) {
reply.errors.push(failMessage);
return done(Error('User not found.'));
}

let token = req.body.token;
let tokenHash = result.rows[0].reset_token;

Bcrypt.compare(token, tokenHash, (err, pass) => {
if (err) {
reply.errors.push('exception during bcrypt compare');
return done(err);
}

if (!pass) {
reply.errors.push(failMessage);
return done(Error('Bcrypt compare failed.'));
}

done(null, result.rows[0]);
});
});
}],
apikey: ['userLookup', function (done, results) {
let uuid = Uuid.v4();

Async.auto({
salt: function (done) {
Bcrypt.genSalt(10, done);
},
hash: ['salt', function (done, results) {
Bcrypt.hash(uuid, results.salt, done);
}]
}, (err, results) => {
if (err) {
return done(err);
}

done(null, {
plain: uuid,
hash: results.hash
});
});
}],
updateUser: ['apikey', function (done, results) {
let query = 'UPDATE users SET api_key = $1 WHERE id = $2';
let params = [
results.apikey.hash,
results.userLookup.id
];

req.app.db.run(query, params, (err, result) => {
if (err) {
reply.errors.push('exception during user update');
return done(err);
}

reply.success = true;
reply.apikey = results.apikey.plain;

done();
});
}]
}, (err, results) => {
if (err) {
req.log.error(err);
}

res.json(reply);
});
};
Loading

0 comments on commit 29260e4

Please sign in to comment.