Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature #4: Set up Users and Authentication #11

Open
wants to merge 8 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export STORMPATH_CLIENT_APIKEY_ID={YOUR STORMPATH API KEY ID}
export STORMPATH_CLIENT_APIKEY_SECRET={YOUR STORMPATH API KEY SECRET}
export STORMPATH_APPLICATION_HREF={YOUR STORMPATH APP HREF}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
node_modules
.env
6 changes: 3 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
language: node_js
node_js:
- "0.12"
- "4.2.4"
- "5.3.0"
- 5.3.0
services:
- mongodb
before_script:
- npm install -g gulp
script: gulp test
env:
- secure: TC+7a0TzOIBpDKFRMBsFPewb6L5rMqEogw3MZfr2jEyChyLz+qt7a076tCuKZAvH/oaAJqs1GuCHyMmbvo8Sjod/QeU4ncFTqE3DNTQr5GqPaaYdxeq0qkS6eC1UjRU9lFWbbqTeGi2E9EfjpMZ75bkQ+1Pgw8kBivTkNaGn+PavP/IVf3K2BxIA8juWlJLyZIqhIHda+LAqId8UDmcTM32wdt2HjP6TG8xzI6i0urP85t/izomzBjWipgtBtKXj6zmC8ebn5N4aPvRNBqsaY/MIkF9VtB9Qrj22bkYbFN0Hb0Tj21bZjAUhsWMVIE3v7LteVtwBXvU62Sib85fqOm1CBtH/8B+qxr+Ah91YQY5UNf8OsMbqYkba7hleZxstYQGo4/yuNFaO3WM3ZL6kFSQvj9VuozUskQWrhyuKbfoeh3Q0jr6Q4hytAFehrSqB8e84HV5DsgvieVX+pEufDZRv+EF7nyQVqBu1U5HLN5jPubV/VEn/xtDmA6kJvdo4iSbOqvJLobwd07FPMqMdIhai987R8lbm1X1tYKO8SrXxLgnnSa1DCoqaZDOgwl4uk+hC2dkFlwBCDmmfyKB10G3lwsuteLkdj4V8K1YEqnf0OPI1S9stPdhkUS+7QvoXK/x3hPlgUmcVH2GiFBG0dSX0xpaQUm/tON8FoimCGCw=
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@

# WHFNP Content API

Content API for the WHFNP Website & CMS.
Content API and CMS for Women Hack for Non-Profits.

API home and documentation: {your URL}
CMS home: {your URL}/admin
Current API version root: {your URL}/api/v1

NB: User signup has been disabled for accounts to be created directly in Stormpath.

## Setup

Expand All @@ -12,6 +18,7 @@ Content API for the WHFNP Website & CMS.
4. Enter the directory and install the dependencies: `cd whfnp-content-api && npm install`
5. Run tests: `gulp test`
6. Run the app: `gulp`
7. We use the `stormpath-express` package for user management. Follow instructions to set yourself up locally using this [Stormpath Express Quickstart](http://docs.stormpath.com/express/quickstart/) guide

---

Expand Down
12 changes: 12 additions & 0 deletions config.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ var settings = {
},
'database': {
'url': 'mongodb://localhost/whfnp-content-dev'
},
'stormpath': {
'key_id': process.env.STORMPATH_CLIENT_APIKEY_ID,
'key_secret': process.env.STORMPATH_CLIENT_APIKEY_SECRET
}
},
'test': {
Expand All @@ -18,6 +22,10 @@ var settings = {
},
'database': {
'url': 'mongodb://localhost/whfnp-content-test'
},
'stormpath': {
'key_id': process.env.STORMPATH_CLIENT_APIKEY_ID,
'key_secret': process.env.STORMPATH_CLIENT_APIKEY_SECRET
}
},
'production': {
Expand All @@ -27,6 +35,10 @@ var settings = {
},
'database': {
'url': 'mongodb://localhost/whfnp-content'
},
'stormpath': {
'key_id': process.env.STORMPATH_CLIENT_APIKEY_ID,
'key_secret': process.env.STORMPATH_CLIENT_APIKEY_SECRET
}
}
}
Expand Down
41 changes: 0 additions & 41 deletions config.js.example

This file was deleted.

125 changes: 125 additions & 0 deletions controllers/admin/profile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
var bodyParser = require('body-parser');
var cookieParser = require('cookie-parser');
var csurf = require('csurf');
var express = require('express');
var extend = require('xtend');
var forms = require('forms');

var collectFormErrors = require('express-stormpath/lib/helpers').collectFormErrors;

// Declare the schema of our form:

var profileForm = forms.create({
givenName: forms.fields.string({
required: true
}),
surname: forms.fields.string({ required: true })
});

// A render function that will render our form and
// provide the values of the fields, as well
// as any situation-specific locals

function renderForm(req,res,locals){
res.render('admin/profile', extend({
title: 'My Profile',
csrfToken: req.csrfToken(),
givenName: req.user.givenName,
surname: req.user.surname
},locals||{}));
}

// Export a function which will create the
// router and return it

module.exports = function profile(){

var router = express.Router();

router.use(cookieParser());

router.use(bodyParser.urlencoded({ extended: true }));

router.use(csurf({ cookie: true }));

// Capture all requests, the form library will negotiate
// between GET and POST requests

router.all('/', function(req, res) {
profileForm.handle(req,{
success: function(form){
// The form library calls this success method if the
// form is being POSTED and does not have errors

// The express-stormpath library will populate req.user,
// all we have to do is set the properties that we care
// about and then cal save() on the user object:
req.user.givenName = form.data.givenName;
req.user.surname = form.data.surname;
req.user.customData.save(function (err) {
if (err) {
res.status(400).end('Oops! There was an error: ' + err.userMessage);
}
});
req.user.save(function(err){
if(err){
if(err.developerMessage){
console.error(err);
}
renderForm(req,res,{
errors: [{
error: err.userMessage ||
err.message || String(err)
}]
});
}else{
renderForm(req,res,{
saved:true
});
}
});
},
error: function(form){
// The form library calls this method if the form
// has validation errors. We will collect the errors
// and render the form again, showing the errors
// to the user
renderForm(req,res,{
errors: collectFormErrors(form)
});
},
empty: function(){
// The form library calls this method if the
// method is GET - thus we just need to render
// the form
renderForm(req,res);
}
});
});

// This is an error handler for this router

router.use(function (err, req, res, next) {
// This handler catches errors for this router
if (err.code === 'EBADCSRFTOKEN'){
// The csurf library is telling us that it can't
// find a valid token on the form
if(req.user){
// session token is invalid or expired.
// render the form anyways, but tell them what happened
renderForm(req,res,{
errors:[{error:'Your form has expired. Please try again.'}]
});
}else{
// the user's cookies have been deleted, we dont know
// their intention is - send them back to the home page
res.redirect('/');
}
}else{
// Let the parent app handle the error
return next(err);
}
});

return router;
};
9 changes: 8 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,15 @@
"dependencies": {
"async": "^1.5.2",
"body-parser": "^1.15.0",
"cookie-parser": "^1.4.1",
"csurf": "^1.8.3",
"express": "^4.13.4",
"express-stormpath": "^2.4.0",
"forms": "^1.1.4",
"jade": "^1.11.0",
"mongoose": "^4.4.3",
"morgan": "^1.6.1"
"morgan": "^1.6.1",
"stormpath": "^0.17.1",
"xtend": "^4.0.1"
}
}
57 changes: 53 additions & 4 deletions routes.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,63 @@
module.exports = function(app, port) {
module.exports = function(app, stormpath) {
var express = require('express'),
router = express.Router(),
conf = require('./config');


router.get('/', function(req, res) {
res.render('index', {
title: 'Welcome to the WHFNP Content API.'
});
});

/**
* Default API route
* Admin routes
*/
router.get('/', function(req, res) {
res.json({ success: true, message: "Hai, I'm the API."});
router.get('/admin', function(req, res) {
res.render('admin/index', {
title: 'Welcome to the WHFNP CMS.'
});
});

/**
* API routes
*/
router.get('/api', function(req, res) {
res.json({ success: true, message: "Hai, I'm the WHFNP Content API root."});
});

router.get('/api/v1', function(req, res) {
res.json({ success: true, message: "Hai, I'm the WHFNP Content API v1 root."});
});

/**
* Test secure route
*/
router.get('/api/secure-me', stormpath.apiAuthenticationRequired, function(req, res) {
res.json({ success: true, message: "Hai, I'm a secure API endpoint."});
});

/**
* Stormpath routes, noted here for dev reference
*/

/*
// Get secure access token
POST /oauth/token
body:
grant_type: 'password'
username: '[email protected]'
password: 'somepassword'

sample result:
{
"refresh_token": "eyJraWQiOiI2...",
"stormpath_access_token_href": "https://api.stormpath.com/v1/accessTokens/3bBAHmSuTJ64DM574awVen",
"token_type": "Bearer",
"access_token": "eyJraWQiOiI2Nl...",
"expires_in": 3600
}
*/

app.use(router);
}
43 changes: 40 additions & 3 deletions server.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* Get all our dependencies
*/
var express = require('express'),
stormpath = require('express-stormpath'),
bodyParser = require('body-parser'),
morgan = require('morgan'),
mongoose = require('mongoose'),
Expand All @@ -21,19 +22,55 @@ mongoose.connect(conf.database.url, function(err) {
}
});

app.set('views', './views');
app.set('view engine', 'jade');

/**
* All the middlewares
*/
// use body parser to easily read requests
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());

// use morgan to log requests to the console
app.use(morgan('dev'));

// set up routes
require('./routes')(app);
// use stormpath for users and authentication management
app.use(stormpath.init(app, {
debug: 'info, error',
expand: {
customData: true
},
web: {
register: {
enabled: false
},
login: {
enabled: true
},
logout: {
enabled: true
},
me: {
enabled: true
}
}
}));

/**
* Routes setup
*/
require('./routes')(app, stormpath);
app.use('/admin/profile',stormpath.loginRequired,require('./controllers/admin/profile')());

/**
* Start the server
*/
if(!module.parent){ app.listen(port); } // wrapper to prevent EADDRESSINUSE conflict with tests
if(!module.parent){ // wrapper to prevent EADDRESSINUSE conflict with tests
app.on('stormpath.ready', function() {
app.listen(port);
});
}

console.log('Find all the things at ' + conf.url);
module.exports = app;
Loading