This repository has been archived by the owner on Apr 19, 2018. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 29260e4
Showing
19 changed files
with
1,232 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
# spacekit-service |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 }); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
}); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
}); | ||
}; |
Oops, something went wrong.