diff --git a/LICENSE b/LICENSE index 2adba61b78b600..77348e8efd4cb0 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2013 Sahat Yalkabov +Copyright (c) 2013 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/README.md b/README.md deleted file mode 100755 index 4355728d634a4f..00000000000000 --- a/README.md +++ /dev/null @@ -1,157 +0,0 @@ -# MEAN Stack - -MEAN is a boilerplate that provides a nice starting point for [MongoDB](http://www.mongodb.org/), [Node.js](http://www.nodejs.org/), [Express](http://expressjs.com/), and [AngularJS](http://angularjs.org/) based applications. It is designed to give you quick and organized way to start developing of MEAN based web apps with useful modules like mongoose and passport pre-bundled and configured. We mainly try to take care of the connection points between existing popular frameworks and solve common integration problems. - -## Prerequisites -* Node.js - Download and Install [Node.js](http://www.nodejs.org/download/). You can also follow [this gist](https://gist.github.com/isaacs/579814) for a quick and easy way to install Node.js and npm -* MongoDB - Download and Install [MongoDB](http://www.mongodb.org/downloads) - Make sure it's running on the default port (27017). - -### Tools Prerequisites -* NPM - Node.js package manager, should be installed when you install node.js. -* Bower - Web package manager, installing [Bower](http://bower.io/) is simple when you have npm: - -``` -$ npm install -g bower -``` - -### Optional -* Grunt - Download and Install [Grunt](http://gruntjs.com). - -## Additional Packages -* Express - Defined as npm module in the [package.json](package.json) file. -* Mongoose - Defined as npm module in the [package.json](package.json) file. -* Passport - Defined as npm module in the [package.json](package.json) file. -* AngularJS - Defined as bower module in the [bower.json](bower.json) file. -* Twitter Bootstrap - Defined as bower module in the [bower.json](bower.json) file. -* UI Bootstrap - Defined as bower module in the [bower.json](bower.json) file. - -## Quick Install - The quickest way to get started with MEAN is to clone the project and utilize it like this: - - Install dependencies: - - $ npm install - - We recommend using [Grunt](https://github.com/gruntjs/grunt-cli) to start the server: - - $ grunt - - When not using grunt you can use: - - $ node server - - Then open a browser and go to: - - http://localhost:3000 - - -## Troubleshooting -During install some of you may encounter some issues, most of this issues can be solved by one of the following tips. -If you went through all this and still can't solve the issue, feel free to contact me(Amos), via the repository issue tracker or the links provided below. - -#### Update NPM, Bower or Grunt -Sometimes you may find there is a weird error during install like npm's *Error: ENOENT*, usually updating those tools to the latest version solves the issue. - -Updating NPM: -``` -$ npm update -g npm -``` - -Updating Grunt: -``` -$ npm update -g grunt-cli -``` - -Updating Bower: -``` -$ npm update -g bower -``` - -#### Cleaning NPM and Bower cache -NPM and Bower has a caching system for holding packages that you already installed. -We found that often cleaning the cache solves some troubles this system creates. - -NPM Clean Cache: -``` -$ npm cache clean -``` - -Bower Clean Cache: -``` -$ bower cache clean -``` - - -## Configuration -All configuration is specified in the [config](config/) folder, particularly the [config.js](config/config.js) file and the [env](config/env/) files. Here you will need to specify your application name, database name, as well as hook up any social app keys if you want integration with Twitter, Facebook, GitHub or Google. - -### Environmental Settings - -There are three environments provided by default, __development__, __test__, and __production__. Each of these environments has the following configuration options: -* __db__ - This is the name of the MongoDB database to use, and is set by default to __mean-dev__ for the development environment. -* __app.name__ - This is the name of your app or website, and can be different for each environment. You can tell which environment you are running by looking at the TITLE attribute that your app generates. -* __Social OAuth Keys__ - Facebook, GitHub, Google, Twitter. You can specify your own social application keys here for each platform: - * __clientID__ - * __clientSecret__ - * __callbackURL__ - -To run with a different environment, just specify NODE_ENV as you call grunt: - - $ NODE_ENV=test grunt - -If you are using node instead of grunt, it is very similar: - - $ NODE_ENV=test node server - -> NOTE: Running Node.js applications in the __production__ environment enables caching, which is disabled by default in all other environments. - -## Getting Started - We pre-included an article example, check it out: - * [The Model](https://github.com/linnovate/mean/blob/master/app/models/article.js) - Where we define our object schema. - * [The Controller](https://github.com/linnovate/mean/blob/master/app/controllers/articles.js) - Where we take care of our backend logic. - * [NodeJS Routes](https://github.com/linnovate/mean/blob/master/config/routes.js) - Where we define our REST service routes. - * [AngularJs Routes](https://github.com/linnovate/mean/blob/master/public/js/config.js) - Where we define our CRUD routes. - * [The AngularJs Service](https://github.com/linnovate/mean/blob/master/public/js/services/articles.js) - Where we connect to our REST service. - * [The AngularJs Controller](https://github.com/linnovate/mean/blob/master/public/js/controllers/articles.js) - Where we take care of our frontend logic. - * [The AngularJs Views Folder](https://github.com/linnovate/mean/blob/master/public/views/articles) - Where we keep our CRUD views. - -## Heroku Quick Deployment -Before you start make sure you have heroku toolbelt installed and an accessible mongo db instance - you can try mongohq which have an easy setup ) - -```bash -git init -git add . -git commit -m "initial version" -heroku apps:create -git push heroku master -``` - -## More Information - * Contact Amos Haviv on any issue via [E-Mail](mailto:mail@amoshaviv.com), [Facebook](http://www.facebook.com/amoshaviv), or [Twitter](http://www.twitter.com/amoshaviv). - * Visit us at [Linnovate.net](http://www.linnovate.net/). - * Visit our [Ninja's Zone](http://www.meanleanstartupmachine.com/) for extended support. - -## Credits -Inspired by the great work of [Madhusudhan Srinivasa](https://github.com/madhums/) - -## License -(The MIT License) - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -'Software'), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/app/models/article.js b/app/models/article.js deleted file mode 100755 index ff1e1868083c70..00000000000000 --- a/app/models/article.js +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Module dependencies. - */ -var mongoose = require('mongoose'), - config = require('../../config/config'), - Schema = mongoose.Schema; - - -/** - * Article Schema - */ -var ArticleSchema = new Schema({ - created: { - type: Date, - default: Date.now - }, - title: { - type: String, - default: '', - trim: true - }, - content: { - type: String, - default: '', - trim: true - }, - user: { - type: Schema.ObjectId, - ref: 'User' - } -}); - -/** - * Validations - */ -ArticleSchema.path('title').validate(function(title) { - return title.length; -}, 'Title cannot be blank'); - -/** - * Statics - */ -ArticleSchema.statics = { - load: function(id, cb) { - this.findOne({ - _id: id - }).populate('user', 'name username').exec(cb); - } -}; - -mongoose.model('Article', ArticleSchema); \ No newline at end of file diff --git a/app/models/user.js b/app/models/user.js deleted file mode 100755 index 8c960a1207ae3c..00000000000000 --- a/app/models/user.js +++ /dev/null @@ -1,124 +0,0 @@ -/** - * Module dependencies. - */ -var mongoose = require('mongoose'), - Schema = mongoose.Schema, - crypto = require('crypto'), - _ = require('underscore'), - authTypes = ['github', 'twitter', 'facebook', 'google']; - - -/** - * User Schema - */ -var UserSchema = new Schema({ - name: String, - email: String, - username: { - type: String, - unique: true - }, - provider: String, - hashed_password: String, - salt: String, - facebook: {}, - twitter: {}, - github: {}, - google: {} -}); - -/** - * Virtuals - */ -UserSchema.virtual('password').set(function(password) { - this._password = password; - this.salt = this.makeSalt(); - this.hashed_password = this.encryptPassword(password); -}).get(function() { - return this._password; -}); - -/** - * Validations - */ -var validatePresenceOf = function(value) { - return value && value.length; -}; - -// the below 4 validations only apply if you are signing up traditionally -UserSchema.path('name').validate(function(name) { - // if you are authenticating by any of the oauth strategies, don't validate - if (authTypes.indexOf(this.provider) !== -1) return true; - return name.length; -}, 'Name cannot be blank'); - -UserSchema.path('email').validate(function(email) { - // if you are authenticating by any of the oauth strategies, don't validate - if (authTypes.indexOf(this.provider) !== -1) return true; - return email.length; -}, 'Email cannot be blank'); - -UserSchema.path('username').validate(function(username) { - // if you are authenticating by any of the oauth strategies, don't validate - if (authTypes.indexOf(this.provider) !== -1) return true; - return username.length; -}, 'Username cannot be blank'); - -UserSchema.path('hashed_password').validate(function(hashed_password) { - // if you are authenticating by any of the oauth strategies, don't validate - if (authTypes.indexOf(this.provider) !== -1) return true; - return hashed_password.length; -}, 'Password cannot be blank'); - - -/** - * Pre-save hook - */ -UserSchema.pre('save', function(next) { - if (!this.isNew) return next(); - - if (!validatePresenceOf(this.password) && authTypes.indexOf(this.provider) === -1) - next(new Error('Invalid password')); - else - next(); -}); - -/** - * Methods - */ -UserSchema.methods = { - /** - * Authenticate - check if the passwords are the same - * - * @param {String} plainText - * @return {Boolean} - * @api public - */ - authenticate: function(plainText) { - return this.encryptPassword(plainText) === this.hashed_password; - }, - - /** - * Make salt - * - * @return {String} - * @api public - */ - makeSalt: function() { - return Math.round((new Date().valueOf() * Math.random())) + ''; - }, - - /** - * Encrypt password - * - * @param {String} password - * @return {String} - * @api public - */ - encryptPassword: function(password) { - if (!password) return ''; - return crypto.createHmac('sha1', this.salt).update(password).digest('hex'); - } -}; - -mongoose.model('User', UserSchema); \ No newline at end of file diff --git a/app/views/404.jade b/app/views/404.jade deleted file mode 100755 index 2f0d9e86632226..00000000000000 --- a/app/views/404.jade +++ /dev/null @@ -1,13 +0,0 @@ -extends layouts/default - -block main - h1 Oops something went wrong - br - span 404 - -block content - #error-message-box - #error-stack-trace - pre - code!= error - diff --git a/app/views/500.jade b/app/views/500.jade deleted file mode 100755 index 491b00084eae94..00000000000000 --- a/app/views/500.jade +++ /dev/null @@ -1,12 +0,0 @@ -extends layouts/default - -block main - h1 Oops something went wrong - br - span 500 - -block content - #error-message-box - #error-stack-trace - pre - code!= error diff --git a/app/views/includes/foot.jade b/app/views/includes/foot.jade deleted file mode 100755 index 09b1b54d0b4cef..00000000000000 --- a/app/views/includes/foot.jade +++ /dev/null @@ -1,29 +0,0 @@ -//AngularJS -script(type='text/javascript', src='/lib/angular/angular.js') -script(type='text/javascript', src='/lib/angular-cookies/angular-cookies.js') -script(type='text/javascript', src='/lib/angular-resource/angular-resource.js') - -//Angular UI -script(type='text/javascript', src='/lib/angular-bootstrap/ui-bootstrap-tpls.js') -script(type='text/javascript', src='/lib/angular-bootstrap/ui-bootstrap.js') -script(type='text/javascript', src='/lib/angular-ui-utils/modules/route/route.js') - -//Application Init -script(type='text/javascript', src='/js/app.js') -script(type='text/javascript', src='/js/config.js') -script(type='text/javascript', src='/js/directives.js') -script(type='text/javascript', src='/js/filters.js') - -//Application Services -script(type='text/javascript', src='/js/services/global.js') -script(type='text/javascript', src='/js/services/articles.js') - -//Application Controllers -script(type='text/javascript', src='/js/controllers/articles.js') -script(type='text/javascript', src='/js/controllers/index.js') -script(type='text/javascript', src='/js/controllers/header.js') -script(type='text/javascript', src='/js/init.js') - -if (req.host='localhost') - //Livereload script rendered - script(type='text/javascript', src='http://localhost:35729/livereload.js') diff --git a/app/views/includes/head.jade b/app/views/includes/head.jade deleted file mode 100755 index 8daa225acf6fba..00000000000000 --- a/app/views/includes/head.jade +++ /dev/null @@ -1,29 +0,0 @@ -head - meta(charset='utf-8') - meta(http-equiv='X-UA-Compatible', content='IE=edge,chrome=1') - meta(name='viewport', content='width=device-width,initial-scale=1') - - title= appName+' - '+title - meta(http-equiv='Content-type', content='text/html;charset=UTF-8') - meta(name="keywords", content="node.js, express, mongoose, mongodb, angularjs") - meta(name="description", content="MEAN - A Modern Stack: MongoDB, ExpressJS, AngularJS, NodeJS. (BONUS: Passport User Support).") - - link(href='/img/icons/favicon.ico', rel='shortcut icon', type='image/x-icon') - - meta(property='fb:app_id', content='APP_ID') - meta(property='og:title', content='#{appName} - #{title}') - meta(property='og:description', content='MEAN - A Modern Stack: MongoDB, ExpressJS, AngularJS, NodeJS. (BONUS: Passport User Support).') - meta(property='og:type', content='website') - meta(property='og:url', content='APP_URL') - meta(property='og:image', content='APP_LOGO') - meta(property='og:site_name', content='MEAN - A Modern Stack') - meta(property='fb:admins', content='APP_ADMIN') - - link(rel='stylesheet', href='/lib/bootstrap/docs/assets/css/bootstrap.css') - //- link(rel='stylesheet', href='/lib/bootstrap/dist/css/bootstrap-responsive.css') - link(rel='stylesheet', href='/css/common.css') - - link(rel='stylesheet', href='/css/views/articles.css') - - //if lt IE 9 - script(src='http://html5shim.googlecode.com/svn/trunk/html5.js') diff --git a/app/views/index.jade b/app/views/index.jade deleted file mode 100755 index c121898992a297..00000000000000 --- a/app/views/index.jade +++ /dev/null @@ -1,6 +0,0 @@ -extends layouts/default - -block content - section(data-ng-view) - script(type="text/javascript"). - window.user = !{user}; diff --git a/app/views/layouts/default.jade b/app/views/layouts/default.jade deleted file mode 100755 index 5e242b17a69743..00000000000000 --- a/app/views/layouts/default.jade +++ /dev/null @@ -1,9 +0,0 @@ -!!! 5 -html(lang='en', xmlns='http://www.w3.org/1999/xhtml', xmlns:fb='https://www.facebook.com/2008/fbml', itemscope='itemscope', itemtype='http://schema.org/Product') - include ../includes/head - body - .navbar.navbar-inverse.navbar-fixed-top(data-ng-include="'views/header.html'", data-role="navigation") - section.content - section.container - block content - include ../includes/foot diff --git a/app/views/users/auth.jade b/app/views/users/auth.jade deleted file mode 100755 index de604899873900..00000000000000 --- a/app/views/users/auth.jade +++ /dev/null @@ -1,22 +0,0 @@ -extends ../layouts/default - -block content - .row - .offset1.span5 - a(href="/auth/facebook") - img(src="/img/icons/facebook.png") - a(href="/auth/github") - img(src="/img/icons/github.png") - a(href="/auth/twitter") - img(src="/img/icons/twitter.png") - a(href="/auth/google") - img(src="/img/icons/google.png") - .span6 - if (typeof errors !== 'undefined') - .fade.in.alert.alert-block.alert-error - a.close(data-dismiss="alert", href="javascript:void(0)") x - ul - each error in errors - li= error.type - - block auth diff --git a/app/views/users/signin.jade b/app/views/users/signin.jade deleted file mode 100755 index ef96cb67f4a015..00000000000000 --- a/app/views/users/signin.jade +++ /dev/null @@ -1,20 +0,0 @@ -extends auth - -block auth - form.signin.form-horizontal(action="/users/session", method="post") - p.error= message - .control-group - label.control-label(for='email') Email - .controls - input#email(type='text', name="email", placeholder='Email') - - .control-group - label.control-label(for='password') Password - .controls - input#password(type='password', name="password", placeholder='Password') - - .form-actions - button.btn.btn-primary(type='submit') Sign in -   - | or  - a.show-signup(href="/signup") Sign up diff --git a/app/views/users/signup.jade b/app/views/users/signup.jade deleted file mode 100755 index b51229bccc8305..00000000000000 --- a/app/views/users/signup.jade +++ /dev/null @@ -1,29 +0,0 @@ -extends auth - -block auth - form.signup.form-horizontal(action="/users", method="post") - .control-group - label.control-label(for='name') Full name - .controls - input#name(type='text', name="name", placeholder='Full name', value=user.name) - - .control-group - label.control-label(for='email') Email - .controls - input#email(type='text', name="email", placeholder='Email', value=user.email) - - .control-group - label.control-label(for='username') Username - .controls - input#username(type='text', name="username", placeholder='Username', value=user.username) - - .control-group - label.control-label(for='password') Password - .controls - input#password(type='password', name="password", placeholder='Password') - - .form-actions - button.btn.btn-primary(type='submit') Sign up -   - | or  - a.show-login(href="/signin") login diff --git a/bower.json b/bower.json deleted file mode 100755 index 58fc25b0882691..00000000000000 --- a/bower.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "angular-quickstart", - "version": "0.1.0", - "dependencies": { - "bootstrap": "2.3.2", - "angular": "1.0.6", - "angular-resource": "1.0.6", - "angular-cookies": "1.0.6", - "angular-bootstrap": "0.6.0", - "angular-ui-utils": "0.0.4" - } -} diff --git a/conf.json b/conf.json new file mode 100644 index 00000000000000..79a742519e9870 --- /dev/null +++ b/conf.json @@ -0,0 +1,18 @@ +{ + "db": "mongodb://localhost", + "facebook": { + "clientID": "APP_ID", + "clientSecret": "APP_SECRET", + "callbackURL": "http://localhost:3000/auth/facebook/callback" + }, + "twitter": { + "clientID": "CONSUMER_KEY", + "clientSecret": "CONSUMER_SECRET", + "callbackURL": "http://localhost:3000/auth/twitter/callback" + }, + "google": { + "clientID": "APP_ID", + "clientSecret": "APP_SECRET", + "callbackURL": "http://localhost:3000/auth/google/callback" + } +} \ No newline at end of file diff --git a/config/env/all.js b/config/env/all.js deleted file mode 100755 index fd5f16fabd2802..00000000000000 --- a/config/env/all.js +++ /dev/null @@ -1,8 +0,0 @@ -var path = require('path'), -rootPath = path.normalize(__dirname + '/../..'); - -module.exports = { - root: rootPath, - port: process.env.PORT || 3000, - db: process.env.MONGOHQ_URL -} diff --git a/config/env/development.json b/config/env/development.json deleted file mode 100755 index cc3055f41553fa..00000000000000 --- a/config/env/development.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "db": "mongodb://localhost/mean-dev", - "app": { - "name": "MEAN - A Modern Stack - Development" - }, - "facebook": { - "clientID": "APP_ID", - "clientSecret": "APP_SECRET", - "callbackURL": "http://localhost:3000/auth/facebook/callback" - }, - "twitter": { - "clientID": "CONSUMER_KEY", - "clientSecret": "CONSUMER_SECRET", - "callbackURL": "http://localhost:3000/auth/twitter/callback" - }, - "github": { - "clientID": "APP_ID", - "clientSecret": "APP_SECRET", - "callbackURL": "http://localhost:3000/auth/github/callback" - }, - "google": { - "clientID": "APP_ID", - "clientSecret": "APP_SECRET", - "callbackURL": "http://localhost:3000/auth/google/callback" - } -} \ No newline at end of file diff --git a/config/env/production.json b/config/env/production.json deleted file mode 100755 index f12ffb51e000ff..00000000000000 --- a/config/env/production.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "db": "mongodb://localhost/mean", - "app": { - "name": "MEAN - A Modern Stack - Production" - }, - "facebook": { - "clientID": "APP_ID", - "clientSecret": "APP_SECRET", - "callbackURL": "http://localhost:3000/auth/facebook/callback" - }, - "twitter": { - "clientID": "CONSUMER_KEY", - "clientSecret": "CONSUMER_SECRET", - "callbackURL": "http://localhost:3000/auth/twitter/callback" - }, - "github": { - "clientID": "APP_ID", - "clientSecret": "APP_SECRET", - "callbackURL": "http://localhost:3000/auth/github/callback" - }, - "google": { - "clientID": "APP_ID", - "clientSecret": "APP_SECRET", - "callbackURL": "http://localhost:3000/auth/google/callback" - } -} \ No newline at end of file diff --git a/config/env/test.json b/config/env/test.json deleted file mode 100755 index 9fa16467ceb47a..00000000000000 --- a/config/env/test.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "db": "mongodb://localhost/mean-test", - "port": 3001, - "app": { - "name": "MEAN - A Modern Stack - Test" - }, - "facebook": { - "clientID": "APP_ID", - "clientSecret": "APP_SECRET", - "callbackURL": "http://localhost:3000/auth/facebook/callback" - }, - "twitter": { - "clientID": "CONSUMER_KEY", - "clientSecret": "CONSUMER_SECRET", - "callbackURL": "http://localhost:3000/auth/twitter/callback" - }, - "github": { - "clientID": "APP_ID", - "clientSecret": "APP_SECRET", - "callbackURL": "http://localhost:3000/auth/github/callback" - }, - "google": { - "clientID": "APP_ID", - "clientSecret": "APP_SECRET", - "callbackURL": "http://localhost:3000/auth/google/callback" - } -} \ No newline at end of file diff --git a/config/env/travis.json b/config/env/travis.json deleted file mode 100755 index 2a2ccc980c9d7e..00000000000000 --- a/config/env/travis.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "db": "mongodb://localhost/mean-travis", - "port": 3001, - "app": { - "name": "MEAN - A Modern Stack - Test on travis" - }, - "facebook": { - "clientID": "APP_ID", - "clientSecret": "APP_SECRET", - "callbackURL": "http://localhost:3000/auth/facebook/callback" - }, - "twitter": { - "clientID": "CONSUMER_KEY", - "clientSecret": "CONSUMER_SECRET", - "callbackURL": "http://localhost:3000/auth/twitter/callback" - }, - "github": { - "clientID": "APP_ID", - "clientSecret": "APP_SECRET", - "callbackURL": "http://localhost:3000/auth/github/callback" - }, - "google": { - "clientID": "APP_ID", - "clientSecret": "APP_SECRET", - "callbackURL": "http://localhost:3000/auth/google/callback" - } -} \ No newline at end of file diff --git a/config/express.js b/config/express.js deleted file mode 100755 index e69de29bb2d1d6..00000000000000 diff --git a/config/middlewares/authorization.js b/config/middlewares/authorization.js deleted file mode 100755 index 9732488c0d8587..00000000000000 --- a/config/middlewares/authorization.js +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Generic require login routing middleware - */ -exports.requiresLogin = function(req, res, next) { - if (!req.isAuthenticated()) { - return res.send(401, 'User is not authorized'); - } - next(); -}; - -/** - * User authorizations routing middleware - */ -exports.user = { - hasAuthorization: function(req, res, next) { - if (req.profile.id != req.user.id) { - return res.send(401, 'User is not authorized'); - } - next(); - } -}; - -/** - * Article authorizations routing middleware - */ -exports.article = { - hasAuthorization: function(req, res, next) { - if (req.article.user.id != req.user.id) { - return res.send(401, 'User is not authorized'); - } - next(); - } -}; \ No newline at end of file diff --git a/config/passport.js b/config/passport.js deleted file mode 100755 index 731c5ce2e12785..00000000000000 --- a/config/passport.js +++ /dev/null @@ -1,172 +0,0 @@ -var mongoose = require('mongoose'), - LocalStrategy = require('passport-local').Strategy, - TwitterStrategy = require('passport-twitter').Strategy, - FacebookStrategy = require('passport-facebook').Strategy, - GitHubStrategy = require('passport-github').Strategy, - GoogleStrategy = require('passport-google-oauth').OAuth2Strategy, - User = mongoose.model('User'), - config = require('./config'); - - -module.exports = function(passport) { - //Serialize sessions - passport.serializeUser(function(user, done) { - done(null, user.id); - }); - - passport.deserializeUser(function(id, done) { - User.findOne({ - _id: id - }, function(err, user) { - done(err, user); - }); - }); - - //Use local strategy - passport.use(new LocalStrategy({ - usernameField: 'email', - passwordField: 'password' - }, - function(email, password, done) { - User.findOne({ - email: email - }, function(err, user) { - if (err) { - return done(err); - } - if (!user) { - return done(null, false, { - message: 'Unknown user' - }); - } - if (!user.authenticate(password)) { - return done(null, false, { - message: 'Invalid password' - }); - } - return done(null, user); - }); - } - )); - - //Use twitter strategy - passport.use(new TwitterStrategy({ - consumerKey: config.twitter.clientID, - consumerSecret: config.twitter.clientSecret, - callbackURL: config.twitter.callbackURL - }, - function(token, tokenSecret, profile, done) { - User.findOne({ - 'twitter.id_str': profile.id - }, function(err, user) { - if (err) { - return done(err); - } - if (!user) { - user = new User({ - name: profile.displayName, - username: profile.username, - provider: 'twitter', - twitter: profile._json - }); - user.save(function(err) { - if (err) console.log(err); - return done(err, user); - }); - } else { - return done(err, user); - } - }); - } - )); - - //Use facebook strategy - passport.use(new FacebookStrategy({ - clientID: config.facebook.clientID, - clientSecret: config.facebook.clientSecret, - callbackURL: config.facebook.callbackURL - }, - function(accessToken, refreshToken, profile, done) { - User.findOne({ - 'facebook.id': profile.id - }, function(err, user) { - if (err) { - return done(err); - } - if (!user) { - user = new User({ - name: profile.displayName, - email: profile.emails[0].value, - username: profile.username, - provider: 'facebook', - facebook: profile._json - }); - user.save(function(err) { - if (err) console.log(err); - return done(err, user); - }); - } else { - return done(err, user); - } - }); - } - )); - - //Use github strategy - passport.use(new GitHubStrategy({ - clientID: config.github.clientID, - clientSecret: config.github.clientSecret, - callbackURL: config.github.callbackURL - }, - function(accessToken, refreshToken, profile, done) { - User.findOne({ - 'github.id': profile.id - }, function(err, user) { - if (!user) { - user = new User({ - name: profile.displayName, - email: profile.emails[0].value, - username: profile.username, - provider: 'github', - github: profile._json - }); - user.save(function(err) { - if (err) console.log(err); - return done(err, user); - }); - } else { - return done(err, user); - } - }); - } - )); - - //Use google strategy - passport.use(new GoogleStrategy({ - clientID: config.google.clientID, - clientSecret: config.google.clientSecret, - callbackURL: config.google.callbackURL - }, - function(accessToken, refreshToken, profile, done) { - User.findOne({ - 'google.id': profile.id - }, function(err, user) { - if (!user) { - user = new User({ - name: profile.displayName, - email: profile.emails[0].value, - username: profile.username, - provider: 'google', - google: profile._json - }); - user.save(function(err) { - if (err) console.log(err); - return done(err, user); - }); - } else { - return done(err, user); - } - }); - } - )); -}; \ No newline at end of file diff --git a/config/routes.js b/config/routes.js deleted file mode 100755 index 173f6475d05da1..00000000000000 --- a/config/routes.js +++ /dev/null @@ -1,78 +0,0 @@ -module.exports = function(app, passport, auth) { - //User Routes - var users = require('../app/controllers/users'); - app.get('/signin', users.signin); - app.get('/signup', users.signup); - app.get('/signout', users.signout); - - //Setting up the users api - app.post('/users', users.create); - - app.post('/users/session', passport.authenticate('local', { - failureRedirect: '/signin', - failureFlash: 'Invalid email or password.' - }), users.session); - - app.get('/users/me', users.me); - app.get('/users/:userId', users.show); - - //Setting the facebook oauth routes - app.get('/auth/facebook', passport.authenticate('facebook', { - scope: ['email', 'user_about_me'], - failureRedirect: '/signin' - }), users.signin); - - app.get('/auth/facebook/callback', passport.authenticate('facebook', { - failureRedirect: '/signin' - }), users.authCallback); - - //Setting the github oauth routes - app.get('/auth/github', passport.authenticate('github', { - failureRedirect: '/signin' - }), users.signin); - - app.get('/auth/github/callback', passport.authenticate('github', { - failureRedirect: '/signin' - }), users.authCallback); - - //Setting the twitter oauth routes - app.get('/auth/twitter', passport.authenticate('twitter', { - failureRedirect: '/signin' - }), users.signin); - - app.get('/auth/twitter/callback', passport.authenticate('twitter', { - failureRedirect: '/signin' - }), users.authCallback); - - //Setting the google oauth routes - app.get('/auth/google', passport.authenticate('google', { - failureRedirect: '/signin', - scope: [ - 'https://www.googleapis.com/auth/userinfo.profile', - 'https://www.googleapis.com/auth/userinfo.email' - ] - }), users.signin); - - app.get('/auth/google/callback', passport.authenticate('google', { - failureRedirect: '/signin' - }), users.authCallback); - - //Finish with setting up the userId param - app.param('userId', users.user); - - //Article Routes - var articles = require('../app/controllers/articles'); - app.get('/articles', articles.all); - app.post('/articles', auth.requiresLogin, articles.create); - app.get('/articles/:articleId', articles.show); - app.put('/articles/:articleId', auth.requiresLogin, auth.article.hasAuthorization, articles.update); - app.del('/articles/:articleId', auth.requiresLogin, auth.article.hasAuthorization, articles.destroy); - - //Finish with setting up the articleId param - app.param('articleId', articles.article); - - //Home route - var index = require('../app/controllers/index'); - app.get('/', index.render); - -}; diff --git a/app/controllers/articles.js b/controllers/articles.js similarity index 100% rename from app/controllers/articles.js rename to controllers/articles.js diff --git a/app/controllers/index.js b/controllers/index.js similarity index 100% rename from app/controllers/index.js rename to controllers/index.js diff --git a/app/controllers/users.js b/controllers/users.js similarity index 100% rename from app/controllers/users.js rename to controllers/users.js diff --git a/models/article.js b/models/article.js new file mode 100755 index 00000000000000..b29e96578197e3 --- /dev/null +++ b/models/article.js @@ -0,0 +1,7 @@ +/** + * Module dependencies. + */ +var mongoose = require('mongoose'), + Schema = mongoose.Schema; + + diff --git a/models/user.js b/models/user.js new file mode 100755 index 00000000000000..2ae9d6063ed38c --- /dev/null +++ b/models/user.js @@ -0,0 +1,124 @@ +/** + * Module dependencies. + */ +var mongoose = require('mongoose'), + Schema = mongoose.Schema, + crypto = require('crypto'), + _ = require('underscore'), + authTypes = ['github', 'twitter', 'facebook', 'google']; + + +/** + * User Schema + */ +var UserSchema = new Schema({ + name: String, + email: String, + username: { + type: String, + unique: true + }, + provider: String, + hashed_password: String, + salt: String, + facebook: {}, + twitter: {}, + github: {}, + google: {} +}); + +/** + * Virtuals + */ +UserSchema.virtual('password').set(function(password) { + this._password = password; + this.salt = this.makeSalt(); + this.hashed_password = this.encryptPassword(password); +}).get(function() { + return this._password; + }); + +/** + * Validations + */ +var validatePresenceOf = function(value) { + return value && value.length; +}; + +// the below 4 validations only apply if you are signing up traditionally +UserSchema.path('name').validate(function(name) { + // if you are authenticating by any of the oauth strategies, don't validate + if (authTypes.indexOf(this.provider) !== -1) return true; + return name.length; +}, 'Name cannot be blank'); + +UserSchema.path('email').validate(function(email) { + // if you are authenticating by any of the oauth strategies, don't validate + if (authTypes.indexOf(this.provider) !== -1) return true; + return email.length; +}, 'Email cannot be blank'); + +UserSchema.path('username').validate(function(username) { + // if you are authenticating by any of the oauth strategies, don't validate + if (authTypes.indexOf(this.provider) !== -1) return true; + return username.length; +}, 'Username cannot be blank'); + +UserSchema.path('hashed_password').validate(function(hashed_password) { + // if you are authenticating by any of the oauth strategies, don't validate + if (authTypes.indexOf(this.provider) !== -1) return true; + return hashed_password.length; +}, 'Password cannot be blank'); + + +/** + * Pre-save hook + */ +UserSchema.pre('save', function(next) { + if (!this.isNew) return next(); + + if (!validatePresenceOf(this.password) && authTypes.indexOf(this.provider) === -1) + next(new Error('Invalid password')); + else + next(); +}); + +/** + * Methods + */ +UserSchema.methods = { + /** + * Authenticate - check if the passwords are the same + * + * @param {String} plainText + * @return {Boolean} + * @api public + */ + authenticate: function(plainText) { + return this.encryptPassword(plainText) === this.hashed_password; + }, + + /** + * Make salt + * + * @return {String} + * @api public + */ + makeSalt: function() { + return Math.round((new Date().valueOf() * Math.random())) + ''; + }, + + /** + * Encrypt password + * + * @param {String} password + * @return {String} + * @api public + */ + encryptPassword: function(password) { + if (!password) return ''; + return crypto.createHmac('sha1', this.salt).update(password).digest('hex'); + } +}; + +mongoose.model('User', UserSchema); \ No newline at end of file diff --git a/server.js b/server.js index bbb031acf623fb..c8a0daf3baca21 100755 --- a/server.js +++ b/server.js @@ -1,9 +1,10 @@ var express = require('express'), + mongoose = require('mongoose'), mongoStore = require('connect-mongo')(express), fs = require('fs'), flash = require('connect-flash'), helpers = require('view-helpers'), - config = require('./config'), + config = require('./conf'), passport = require('passport'), logger = require('mean-logger'); @@ -14,33 +15,377 @@ var express = require('express'), //Load configurations //if test env, load example file -var env = process.env.NODE_ENV = process.env.NODE_ENV || 'development', - config = require('./config/config'), - auth = require('./config/middlewares/authorization'), - mongoose = require('mongoose'); +var env = process.env.NODE_ENV = process.env.NODE_ENV || 'development'; + +/** + * Generic require login routing middleware + */ +var requiresLogin = function(req, res, next) { + if (!req.isAuthenticated()) { + return res.send(401, 'User is not authorized'); + } + next(); +}; + +/** + * User authorizations routing middleware + */ +var hasAuthorization = function(req, res, next) { + if (req.profile.id != req.user.id) { + return res.send(401, 'User is not authorized'); + } + next(); +}; + +/** + * Article authorizations routing middleware + */ +var hasAuthorization = function(req, res, next) { + if (req.article.user.id != req.user.id) { + return res.send(401, 'User is not authorized'); + } + next(); +}; //Bootstrap db connection var db = mongoose.connect(config.db); -//Bootstrap models -var models_path = __dirname + '/app/models'; -var walk = function(path) { - fs.readdirSync(path).forEach(function(file) { - var newPath = path + '/' + file; - var stat = fs.statSync(newPath); - if (stat.isFile()) { - if (/(.*)\.(js$|coffee$)/.test(file)) { - require(newPath); - } - } else if (stat.isDirectory()) { - walk(newPath); - } - }); +/** + * Module dependencies. + */ +var mongoose = require('mongoose'), + Schema = mongoose.Schema, + crypto = require('crypto'), + _ = require('underscore'), + authTypes = ['github', 'twitter', 'facebook', 'google']; + + +/** + * User Schema + */ +var UserSchema = new Schema({ + name: String, + email: String, + username: { + type: String, + unique: true + }, + provider: String, + hashed_password: String, + salt: String, + facebook: {}, + twitter: {}, + github: {}, + google: {} +}); + +/** + * Virtuals + */ +UserSchema.virtual('password').set(function(password) { + this._password = password; + this.salt = this.makeSalt(); + this.hashed_password = this.encryptPassword(password); +}).get(function() { + return this._password; + }); + +/** + * Validations + */ +var validatePresenceOf = function(value) { + return value && value.length; +}; + +// the below 4 validations only apply if you are signing up traditionally +UserSchema.path('name').validate(function(name) { + // if you are authenticating by any of the oauth strategies, don't validate + if (authTypes.indexOf(this.provider) !== -1) return true; + return name.length; +}, 'Name cannot be blank'); + +UserSchema.path('email').validate(function(email) { + // if you are authenticating by any of the oauth strategies, don't validate + if (authTypes.indexOf(this.provider) !== -1) return true; + return email.length; +}, 'Email cannot be blank'); + +UserSchema.path('username').validate(function(username) { + // if you are authenticating by any of the oauth strategies, don't validate + if (authTypes.indexOf(this.provider) !== -1) return true; + return username.length; +}, 'Username cannot be blank'); + +UserSchema.path('hashed_password').validate(function(hashed_password) { + // if you are authenticating by any of the oauth strategies, don't validate + if (authTypes.indexOf(this.provider) !== -1) return true; + return hashed_password.length; +}, 'Password cannot be blank'); + + +/** + * Pre-save hook + */ +UserSchema.pre('save', function(next) { + if (!this.isNew) return next(); + + if (!validatePresenceOf(this.password) && authTypes.indexOf(this.provider) === -1) + next(new Error('Invalid password')); + else + next(); +}); + +/** + * Methods + */ +UserSchema.methods = { + /** + * Authenticate - check if the passwords are the same + * + * @param {String} plainText + * @return {Boolean} + * @api public + */ + authenticate: function(plainText) { + return this.encryptPassword(plainText) === this.hashed_password; + }, + + /** + * Make salt + * + * @return {String} + * @api public + */ + makeSalt: function() { + return Math.round((new Date().valueOf() * Math.random())) + ''; + }, + + /** + * Encrypt password + * + * @param {String} password + * @return {String} + * @api public + */ + encryptPassword: function(password) { + if (!password) return ''; + return crypto.createHmac('sha1', this.salt).update(password).digest('hex'); + } +}; + +mongoose.model('User', UserSchema); + +/** + * Article Schema + */ +var ArticleSchema = new Schema({ + created: { + type: Date, + default: Date.now + }, + title: { + type: String, + default: '', + trim: true + }, + content: { + type: String, + default: '', + trim: true + }, + user: { + type: Schema.ObjectId, + ref: 'User' + } +}); + +/** + * Validations + */ +ArticleSchema.path('title').validate(function(title) { + return title.length; +}, 'Title cannot be blank'); + +/** + * Statics + */ +ArticleSchema.statics = { + load: function(id, cb) { + this.findOne({ + _id: id + }).populate('user', 'name username').exec(cb); + } }; -walk(models_path); + +mongoose.model('Article', ArticleSchema); //bootstrap passport config -require('./config/passport')(passport); +var LocalStrategy = require('passport-local').Strategy, + TwitterStrategy = require('passport-twitter').Strategy, + FacebookStrategy = require('passport-facebook').Strategy, + GitHubStrategy = require('passport-github').Strategy, + GoogleStrategy = require('passport-google-oauth').OAuth2Strategy; + +//Serialize sessions +passport.serializeUser(function(user, done) { + done(null, user.id); +}); + +passport.deserializeUser(function(id, done) { + User.findOne({ + _id: id + }, function(err, user) { + done(err, user); + }); +}); + +//Use local strategy +passport.use(new LocalStrategy({ + usernameField: 'email', + passwordField: 'password' + }, + function(email, password, done) { + User.findOne({ + email: email + }, function(err, user) { + if (err) { + return done(err); + } + if (!user) { + return done(null, false, { + message: 'Unknown user' + }); + } + if (!user.authenticate(password)) { + return done(null, false, { + message: 'Invalid password' + }); + } + return done(null, user); + }); + } +)); + +//Use twitter strategy +passport.use(new TwitterStrategy({ + consumerKey: config.twitter.clientID, + consumerSecret: config.twitter.clientSecret, + callbackURL: config.twitter.callbackURL + }, + function(token, tokenSecret, profile, done) { + User.findOne({ + 'twitter.id_str': profile.id + }, function(err, user) { + if (err) { + return done(err); + } + if (!user) { + user = new User({ + name: profile.displayName, + username: profile.username, + provider: 'twitter', + twitter: profile._json + }); + user.save(function(err) { + if (err) console.log(err); + return done(err, user); + }); + } else { + return done(err, user); + } + }); + } +)); + +//Use facebook strategy +passport.use(new FacebookStrategy({ + clientID: config.facebook.clientID, + clientSecret: config.facebook.clientSecret, + callbackURL: config.facebook.callbackURL + }, + function(accessToken, refreshToken, profile, done) { + User.findOne({ + 'facebook.id': profile.id + }, function(err, user) { + if (err) { + return done(err); + } + if (!user) { + user = new User({ + name: profile.displayName, + email: profile.emails[0].value, + username: profile.username, + provider: 'facebook', + facebook: profile._json + }); + user.save(function(err) { + if (err) console.log(err); + return done(err, user); + }); + } else { + return done(err, user); + } + }); + } +)); + +//Use github strategy +passport.use(new GitHubStrategy({ + clientID: config.github.clientID, + clientSecret: config.github.clientSecret, + callbackURL: config.github.callbackURL + }, + function(accessToken, refreshToken, profile, done) { + User.findOne({ + 'github.id': profile.id + }, function(err, user) { + if (!user) { + user = new User({ + name: profile.displayName, + email: profile.emails[0].value, + username: profile.username, + provider: 'github', + github: profile._json + }); + user.save(function(err) { + if (err) console.log(err); + return done(err, user); + }); + } else { + return done(err, user); + } + }); + } +)); + +//Use google strategy +passport.use(new GoogleStrategy({ + clientID: config.google.clientID, + clientSecret: config.google.clientSecret, + callbackURL: config.google.callbackURL + }, + function(accessToken, refreshToken, profile, done) { + User.findOne({ + 'google.id': profile.id + }, function(err, user) { + if (!user) { + user = new User({ + name: profile.displayName, + email: profile.emails[0].value, + username: profile.username, + provider: 'google', + google: profile._json + }); + user.save(function(err) { + if (err) console.log(err); + return done(err, user); + }); + } else { + return done(err, user); + } + }); + } +)); var app = express(); @@ -48,6 +393,7 @@ var app = express(); * Express Settings */ app.set('showStackError', true); +app.set('port', process.env.PORT || 3000); app.locals.pretty = true; app.use(express.compress({ filter: function(req, res) { @@ -90,23 +436,61 @@ app.use(function(err, req, res, next) { }); app.use(function(req, res, next) { - res.status(404).render('404', { - url: req.originalUrl, - error: 'Not found' - }); + res.status(404).render('404', { + url: req.originalUrl, + error: 'Not found' + }); }); -//Bootstrap routes -require('./config/routes')(app, passport, auth); +//User Routes +app.post('/users', users.create); +app.get('/users/me', users.me); +app.get('/users/:userId', users.show); + +//Setting the facebook oauth routes +app.get('/auth/facebook', passport.authenticate('facebook', { + scope: ['email', 'user_about_me'], + failureRedirect: '/signin' +}), users.signin); + +app.get('/auth/facebook/callback', passport.authenticate('facebook', { + failureRedirect: '/signin' +}), users.authCallback); + +//Setting the github oauth routes +app.get('/auth/github', passport.authenticate('github', { failureRedirect: '/signin' }), users.signin); +app.get('/auth/github/callback', passport.authenticate('github', { failureRedirect: '/signin' }), users.authCallback); + +//Setting the twitter oauth routes +app.get('/auth/twitter', passport.authenticate('twitter', { failureRedirect: '/signin' }), users.signin); +app.get('/auth/twitter/callback', passport.authenticate('twitter', { failureRedirect: '/signin' }), users.authCallback); + +//Setting the google oauth routes +app.get('/auth/google', passport.authenticate('google', { + failureRedirect: '/signin', + scope: [ + 'https://www.googleapis.com/auth/userinfo.profile', + 'https://www.googleapis.com/auth/userinfo.email' + ] +}), users.signin); +app.get('/auth/google/callback', passport.authenticate('google', { failureRedirect: '/signin' }), users.authCallback); + +//Finish with setting up the userId param +app.param('userId', users.user); + +//Article Routes +var articles = require('../app/controllers/articles'); +app.get('/articles', articles.all); +app.post('/articles', auth.requiresLogin, articles.create); +app.get('/articles/:articleId', articles.show); +app.put('/articles/:articleId', auth.requiresLogin, auth.article.hasAuthorization, articles.update); +app.del('/articles/:articleId', auth.requiresLogin, auth.article.hasAuthorization, articles.destroy); -//Start the app by listening on -var port = process.env.PORT || config.port; -app.listen(port); -console.log('Express app started on port ' + port); -//Initializing logger -logger.init(app, passport, mongoose); +//Home route +var index = require('../app/controllers/index'); +app.get('/', index.render); -//expose app -exports = module.exports = app; +console.log('Express app started on port ' + app.get('port')); +app.listen(app.get('port')); diff --git a/test/article/model.js b/test/article/model.js deleted file mode 100755 index 9d1fd034c28bc5..00000000000000 --- a/test/article/model.js +++ /dev/null @@ -1,65 +0,0 @@ -/** - * Module dependencies. - */ -var should = require('should'), - app = require('../../server'), - mongoose = require('mongoose'), - User = mongoose.model('User'), - Article = mongoose.model('Article'); - -//Globals -var user; -var article; - -//The tests -describe('', function() { - describe('Model Article:', function() { - beforeEach(function(done) { - user = new User({ - name: 'Full name', - email: 'test@test.com', - username: 'user', - password: 'password' - }); - - user.save(function(err) { - article = new Article({ - title: 'Article Title', - content: 'Article Content', - user: user - }); - - done(); - }); - }); - - describe('Method Save', function() { - it('should be able to save without problems', function(done) { - return article.save(function(err) { - should.not.exist(err); - done(); - }); - }); - - it('should be able to show an error when try to save without title', function(done) { - article.title = ''; - - return article.save(function(err) { - should.exist(err); - done(); - }); - }); - }); - - afterEach(function(done) { - Article.remove({}); - User.remove({}); - done(); - }); - after(function(done){ - Article.remove().exec(); - User.remove().exec(); - done(); - }); - }); -}); diff --git a/test/user/model.js b/test/user/model.js deleted file mode 100755 index 0ec6820a1f5ae5..00000000000000 --- a/test/user/model.js +++ /dev/null @@ -1,66 +0,0 @@ -/** - * Module dependencies. - */ -var should = require('should'), - app = require('../../server'), - mongoose = require('mongoose'), - User = mongoose.model('User'); - -//Globals -var user; - -//The tests -describe('', function() { - describe('Model User:', function() { - before(function(done) { - user = new User({ - name: 'Full name', - email: 'test@test.com', - username: 'user', - password: 'password' - }); - user2 = new User({ - name: 'Full name', - email: 'test@test.com', - username: 'user', - password: 'password' - }); - - done(); - }); - - describe('Method Save', function() { - it('should begin with no users', function(done) { - User.find({}, function(err, users) { - users.should.have.length(0); - done(); - }); - }); - - it('should be able to save whithout problems', function(done) { - user.save(done); - }); - - it('should fail to save an existing user again', function(done) { - user.save(); - return user2.save(function(err) { - should.exist(err); - done(); - }); - }); - - it('should be able to show an error when try to save without name', function(done) { - user.name = ''; - return user.save(function(err) { - should.exist(err); - done(); - }); - }); - }); - - after(function(done) { - User.remove().exec(); - done(); - }); - }); -}); \ No newline at end of file