├── .editorconfig ├── .gitignore ├── .sailsrc ├── Gruntfile.js ├── LICENSE ├── README.md ├── api ├── controllers │ ├── .gitkeep │ ├── AuthController.js │ └── FlashController.js ├── models │ ├── .gitkeep │ ├── Passport.js │ └── User.js ├── policies │ ├── bearerAuth.js │ ├── passport.js │ └── sessionAuth.js ├── responses │ ├── badRequest.js │ ├── forbidden.js │ ├── notFound.js │ ├── ok.js │ └── serverError.js └── services │ ├── .gitkeep │ ├── passport.js │ └── protocols │ ├── index.js │ ├── local.js │ ├── oauth.js │ ├── oauth2.js │ └── openid.js ├── app.js ├── assets ├── favicon.ico ├── images │ └── .gitkeep ├── js │ └── dependencies │ │ └── sails.io.js ├── robots.txt ├── styles │ └── importer.less └── templates │ └── .gitkeep ├── config ├── blueprints.js ├── bootstrap.js ├── connections.js ├── cors.js ├── csrf.js ├── globals.js ├── http.js ├── i18n.js ├── locales │ ├── _README.md │ ├── de.json │ ├── en.json │ ├── es.json │ └── fr.json ├── log.js ├── models.js ├── passport.js ├── policies.js ├── routes.js ├── session.js ├── sockets.js └── views.js ├── package.json ├── tasks ├── README.md ├── config │ ├── clean.js │ ├── coffee.js │ ├── concat.js │ ├── copy.js │ ├── cssmin.js │ ├── jst.js │ ├── less.js │ ├── sails-linker.js │ ├── sync.js │ ├── uglify.js │ └── watch.js ├── pipeline.js └── register │ ├── build.js │ ├── buildProd.js │ ├── compileAssets.js │ ├── default.js │ ├── linkAssets.js │ ├── linkAssetsBuild.js │ ├── linkAssetsBuildProd.js │ ├── prod.js │ └── syncAssets.js └── views ├── 403.ejs ├── 404.ejs ├── 500.ejs ├── auth ├── login.ejs └── register.ejs ├── homepage.ejs └── layout.ejs /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################################################ 2 | ############### .gitignore ################## 3 | ################################################ 4 | # 5 | # This file is only relevant if you are using git. 6 | # 7 | # Files which match the splat patterns below will 8 | # be ignored by git. This keeps random crap and 9 | # and sensitive credentials from being uploaded to 10 | # your repository. It allows you to configure your 11 | # app for your machine without accidentally 12 | # committing settings which will smash the local 13 | # settings of other developers on your team. 14 | # 15 | # Some reasonable defaults are included below, 16 | # but, of course, you should modify/extend/prune 17 | # to fit your needs! 18 | ################################################ 19 | 20 | 21 | 22 | 23 | ################################################ 24 | # Local Configuration 25 | # 26 | # Explicitly ignore files which contain: 27 | # 28 | # 1. Sensitive information you'd rather not push to 29 | # your git repository. 30 | # e.g., your personal API keys or passwords. 31 | # 32 | # 2. Environment-specific configuration 33 | # Basically, anything that would be annoying 34 | # to have to change every time you do a 35 | # `git pull` 36 | # e.g., your local development database, or 37 | # the S3 bucket you're using for file uploads 38 | # development. 39 | # 40 | ################################################ 41 | 42 | config/local.js 43 | 44 | 45 | 46 | 47 | 48 | ################################################ 49 | # Dependencies 50 | # 51 | # When releasing a production app, you may 52 | # consider including your node_modules and 53 | # bower_components directory in your git repo, 54 | # but during development, its best to exclude it, 55 | # since different developers may be working on 56 | # different kernels, where dependencies would 57 | # need to be recompiled anyway. 58 | # 59 | # More on that here about node_modules dir: 60 | # http://www.futurealoof.com/posts/nodemodules-in-git.html 61 | # (credit Mikeal Rogers, @mikeal) 62 | # 63 | # About bower_components dir, you can see this: 64 | # http://addyosmani.com/blog/checking-in-front-end-dependencies/ 65 | # (credit Addy Osmani, @addyosmani) 66 | # 67 | ################################################ 68 | 69 | node_modules 70 | bower_components 71 | 72 | 73 | 74 | 75 | ################################################ 76 | # Sails.js / Waterline / Grunt 77 | # 78 | # Files generated by Sails and Grunt, or related 79 | # tasks and adapters. 80 | ################################################ 81 | .tmp 82 | dump.rdb 83 | 84 | 85 | 86 | 87 | 88 | ################################################ 89 | # Node.js / NPM 90 | # 91 | # Common files generated by Node, NPM, and the 92 | # related ecosystem. 93 | ################################################ 94 | lib-cov 95 | *.seed 96 | *.log 97 | *.out 98 | *.pid 99 | npm-debug.log 100 | 101 | 102 | 103 | 104 | 105 | ################################################ 106 | # Miscellaneous 107 | # 108 | # Common files generated by text editors, 109 | # operating systems, file systems, etc. 110 | ################################################ 111 | 112 | *~ 113 | *# 114 | .DS_STORE 115 | .netbeans 116 | nbproject 117 | .idea 118 | .node_history 119 | -------------------------------------------------------------------------------- /.sailsrc: -------------------------------------------------------------------------------- 1 | { 2 | "generators": { 3 | "modules": {} 4 | } 5 | } -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Gruntfile 3 | * 4 | * This Node script is executed when you run `grunt` or `sails lift`. 5 | * It's purpose is to load the Grunt tasks in your project's `tasks` 6 | * folder, and allow you to add and remove tasks as you see fit. 7 | * For more information on how this works, check out the `README.md` 8 | * file that was generated in your `tasks` folder. 9 | * 10 | * WARNING: 11 | * Unless you know what you're doing, you shouldn't change this file. 12 | * Check out the `tasks` directory instead. 13 | */ 14 | 15 | module.exports = function(grunt) { 16 | 17 | 18 | // Load the include-all library in order to require all of our grunt 19 | // configurations and task registrations dynamically. 20 | var includeAll; 21 | try { 22 | includeAll = require('include-all'); 23 | } catch (e0) { 24 | try { 25 | includeAll = require('sails/node_modules/include-all'); 26 | } 27 | catch(e1) { 28 | console.error('Could not find `include-all` module.'); 29 | console.error('Skipping grunt tasks...'); 30 | console.error('To fix this, please run:'); 31 | console.error('npm install include-all --save`'); 32 | console.error(); 33 | 34 | grunt.registerTask('default', []); 35 | return; 36 | } 37 | } 38 | 39 | 40 | /** 41 | * Loads Grunt configuration modules from the specified 42 | * relative path. These modules should export a function 43 | * that, when run, should either load/configure or register 44 | * a Grunt task. 45 | */ 46 | function loadTasks(relPath) { 47 | return includeAll({ 48 | dirname: require('path').resolve(__dirname, relPath), 49 | filter: /(.+)\.js$/ 50 | }) || {}; 51 | } 52 | 53 | /** 54 | * Invokes the function from a Grunt configuration module with 55 | * a single argument - the `grunt` object. 56 | */ 57 | function invokeConfigFn(tasks) { 58 | for (var taskName in tasks) { 59 | if (tasks.hasOwnProperty(taskName)) { 60 | tasks[taskName](grunt); 61 | } 62 | } 63 | } 64 | 65 | 66 | 67 | 68 | // Load task functions 69 | var taskConfigurations = loadTasks('./tasks/config'), 70 | registerDefinitions = loadTasks('./tasks/register'); 71 | 72 | // (ensure that a default task exists) 73 | if (!registerDefinitions.default) { 74 | registerDefinitions.default = function (grunt) { grunt.registerTask('default', []); }; 75 | } 76 | 77 | // Run task functions to configure Grunt. 78 | invokeConfigFn(taskConfigurations); 79 | invokeConfigFn(registerDefinitions); 80 | 81 | }; 82 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Sails 101 contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Authentication and Authorization 2 | ================================ 3 | 4 | This is a Sails example application to demonstrate authentication and authorization using `Passport.js`. This explains how a user can authenticate with a username and password. And also explains API authentication using bearer tokens. 5 | 6 | --- 7 | Let us start by seeing things working! Clone this repo, install all the required modules and lift the app: 8 | 9 | ``` shell 10 | git clone https://github.com/sails101/even-more-passport.git 11 | cd sails-auth-example 12 | npm install 13 | sails lift 14 | ``` 15 | 16 | Or see it live here: [even-more-passport](https://even-more-passport.herokuapp.com/) 17 | 18 | I've added two controller actions: `flash/home` and `flash/remotehome`. 19 | 20 | [http://localhost:1337/flash/home](http://localhost:1337/flash/home) cannot be accessed without logging in. You will be redirected to login page ([http://localhost:1337/login](http://localhost:1337/login)). You can register for a new account at [http://localhost:1337/register](http://localhost:1337/register). 21 | 22 | [http://localhost:1337/flash/remotehome](http://localhost:1337/flash/remotehome) cannot be accessed without an API token. Hope you have already obtained your API token from `flash/home`. This should be present in the request in any of the following ways: 23 | 24 | 1. As a query param: Example: [http://localhost:1337/flash/remotehome?access_token=](http://localhost:1337/flash/remotehome?access_token=) 25 | 2. In the body: Example: `access_token: ` 26 | 3. In the header: Example: `Authorization: Bearer ` 27 | 28 | --- 29 | 30 | I am going to explain the step by step procedure for integrating Passport to your Sails app using a passport.js-based authentication generator called [sails-generate-auth](https://www.npmjs.com/package/sails-generate-auth) 31 | 32 | #### Steps 33 | 34 | Step 1. Install the following npm modules: 35 | 36 | ``` shell 37 | npm install sails-generate-auth --save 38 | npm install passport-local --save 39 | npm install passport --save 40 | npm install bcryptjs --save 41 | npm install validator --save 42 | npm install passport-http-bearer --save 43 | ``` 44 | 45 | `passport-local` module is for local authentication strategy. `passport`, `bcryptjs` and `validator` and dependencies for `passport` and `sails-generate-auth`. `passport-http-bearer` is for authenticating APIs using token. 46 | 47 | Step 2. Now, all you have to do to integrate passport is to run the following command in your application: 48 | 49 | ``` shell 50 | sails generate auth 51 | ``` 52 | 53 | It automatically generates all the files needed by passport. 54 | 55 | Step 3. Add the following routes to `config/routes.js` 56 | 57 | ``` js 58 | 'get /login': 'AuthController.login', 59 | 'get /logout': 'AuthController.logout', 60 | 'get /register': 'AuthController.register', 61 | 62 | 'post /auth/local': 'AuthController.callback', 63 | 'post /auth/local/:action': 'AuthController.callback', 64 | 65 | 'get /auth/:provider': 'AuthController.provider', 66 | 'get /auth/:provider/callback': 'AuthController.callback', 67 | 'get /auth/:provider/:action': 'AuthController.callback', 68 | ``` 69 | 70 | Step 4. Next, change your `config/bootstrap.js` to load your Passport providers on startup by adding the following line: 71 | 72 | ``` js 73 | sails.services.passport.loadStrategies(); 74 | ``` 75 | 76 | You can see a detailed explanation of the steps until this in [sails-generate-auth](https://github.com/kasperisager/sails-generate-auth/) page. 77 | 78 | Step 5. Modify `api/policies/sessionAuth.js` to set the policy for controller actions: 79 | 80 | ``` js 81 | module.exports = function(req, res, next) { 82 | if(req.user) return next(); 83 | res.redirect('/login'); 84 | }; 85 | ``` 86 | 87 | Step 6. Now, make the following changes in `config/policies.js` to apply the `sessionAuth` policy to controllers: 88 | 89 | ``` js 90 | module.exports.policies = { 91 | '*': ['passport', 'sessionAuth'], 92 | 'auth': { 93 | '*': ['passport'] 94 | } 95 | } 96 | ``` 97 | 98 | Here you are applying `sessionAuth` policy for all the controller actions except those in `AuthController`. Because auth actions like login, logout and register need to be accessed without logging in. 99 | 100 | Now, lift your Sails application and see things in action! 101 | 102 | --- 103 | 104 | For API authorization, add `bearerAuth` the policy for controller actions that are accessed via API in [config/policies.js](https://github.com/sails101/even-more-passport/blob/master/config/policies.js#L23): 105 | 106 | ``` 107 | 'flash': { 108 | 'remoteHome': ['passport', 'bearerAuth'] 109 | }, 110 | ``` 111 | 112 | If you have any questions or encounter any problems, feel free to raise an issue in this repo. I'll help you to resolve it. 113 | -------------------------------------------------------------------------------- /api/controllers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sails101/even-more-passport/e986742cbbd699a8abbacb08038d5d11506c6c09/api/controllers/.gitkeep -------------------------------------------------------------------------------- /api/controllers/AuthController.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Authentication Controller 3 | * 4 | * This is merely meant as an example of how your Authentication controller 5 | * should look. It currently includes the minimum amount of functionality for 6 | * the basics of Passport.js to work. 7 | */ 8 | var AuthController = { 9 | /** 10 | * Render the login page 11 | * 12 | * The login form itself is just a simple HTML form: 13 | * 14 |
15 | 16 | 17 | 18 |
19 | * 20 | * You could optionally add CSRF-protection as outlined in the documentation: 21 | * http://sailsjs.org/#!documentation/config.csrf 22 | * 23 | * A simple example of automatically listing all available providers in a 24 | * Handlebars template would look like this: 25 | * 26 | {{#each providers}} 27 | {{name}} 28 | {{/each}} 29 | * 30 | * @param {Object} req 31 | * @param {Object} res 32 | */ 33 | login: function (req, res) { 34 | var strategies = sails.config.passport 35 | , providers = {}; 36 | 37 | // Get a list of available providers for use in your templates. 38 | Object.keys(strategies).forEach(function (key) { 39 | if (key === 'local') { 40 | return; 41 | } 42 | 43 | providers[key] = { 44 | name: strategies[key].name 45 | , slug: key 46 | }; 47 | }); 48 | 49 | // Render the `auth/login.ext` view 50 | res.view({ 51 | providers : providers 52 | , errors : req.flash('error') 53 | }); 54 | }, 55 | 56 | /** 57 | * Log out a user and return them to the homepage 58 | * 59 | * Passport exposes a logout() function on req (also aliased as logOut()) that 60 | * can be called from any route handler which needs to terminate a login 61 | * session. Invoking logout() will remove the req.user property and clear the 62 | * login session (if any). 63 | * 64 | * For more information on logging out users in Passport.js, check out: 65 | * http://passportjs.org/guide/logout/ 66 | * 67 | * @param {Object} req 68 | * @param {Object} res 69 | */ 70 | logout: function (req, res) { 71 | req.logout(); 72 | res.redirect('/'); 73 | }, 74 | 75 | /** 76 | * Render the registration page 77 | * 78 | * Just like the login form, the registration form is just simple HTML: 79 | * 80 |
81 | 82 | 83 | 84 | 85 |
86 | * 87 | * @param {Object} req 88 | * @param {Object} res 89 | */ 90 | register: function (req, res) { 91 | res.view({ 92 | errors: req.flash('error') 93 | }); 94 | }, 95 | 96 | /** 97 | * Create a third-party authentication endpoint 98 | * 99 | * @param {Object} req 100 | * @param {Object} res 101 | */ 102 | provider: function (req, res) { 103 | passport.endpoint(req, res); 104 | }, 105 | 106 | /** 107 | * Create a authentication callback endpoint 108 | * 109 | * This endpoint handles everything related to creating and verifying Pass- 110 | * ports and users, both locally and from third-aprty providers. 111 | * 112 | * Passport exposes a login() function on req (also aliased as logIn()) that 113 | * can be used to establish a login session. When the login operation 114 | * completes, user will be assigned to req.user. 115 | * 116 | * For more information on logging in users in Passport.js, check out: 117 | * http://passportjs.org/guide/login/ 118 | * 119 | * @param {Object} req 120 | * @param {Object} res 121 | */ 122 | callback: function (req, res) { 123 | function tryAgain (err) { 124 | 125 | // Only certain error messages are returned via req.flash('error', someError) 126 | // because we shouldn't expose internal authorization errors to the user. 127 | // We do return a generic error and the original request body. 128 | var flashError = req.flash('error')[0]; 129 | 130 | if (err && !flashError ) { 131 | req.flash('error', 'Error.Passport.Generic'); 132 | } else if (flashError) { 133 | req.flash('error', flashError); 134 | } 135 | req.flash('form', req.body); 136 | 137 | // If an error was thrown, redirect the user to the 138 | // login, register or disconnect action initiator view. 139 | // These views should take care of rendering the error messages. 140 | var action = req.param('action'); 141 | 142 | switch (action) { 143 | case 'register': 144 | res.redirect('/register'); 145 | break; 146 | case 'disconnect': 147 | res.redirect('back'); 148 | break; 149 | default: 150 | res.redirect('/login'); 151 | } 152 | } 153 | 154 | passport.callback(req, res, function (err, user) { 155 | if (err) { 156 | return tryAgain(); 157 | } 158 | 159 | req.login(user, function (err) { 160 | if (err) { 161 | return tryAgain(); 162 | } 163 | 164 | // Upon successful login, send the user to the homepage were req.user 165 | // will available. 166 | res.redirect('/'); 167 | }); 168 | }); 169 | }, 170 | 171 | /** 172 | * Disconnect a passport from a user 173 | * 174 | * @param {Object} req 175 | * @param {Object} res 176 | */ 177 | disconnect: function (req, res) { 178 | passport.disconnect(req, res); 179 | } 180 | }; 181 | 182 | module.exports = AuthController; 183 | -------------------------------------------------------------------------------- /api/controllers/FlashController.js: -------------------------------------------------------------------------------- 1 | /** 2 | * FlashController 3 | * 4 | * @description :: Server-side logic for managing flashes 5 | * @help :: See http://links.sailsjs.org/docs/controllers 6 | */ 7 | 8 | module.exports = { 9 | 10 | /** 11 | * `FlashController.home()` 12 | */ 13 | home: function (req, res) { 14 | Passport 15 | .findOne({ protocol: 'local', user: req.user.id }) 16 | .exec(function(err, passport) { 17 | return res.json({ 18 | todo: 'home() is not implemented yet! But you are logged in :)', 19 | token: passport.accessToken 20 | }); 21 | }); 22 | }, 23 | 24 | /** 25 | * `FlashController.remotehome()` 26 | */ 27 | remotehome: function (req, res) { 28 | return res.json({ 29 | todo: 'remotehome() is not implemented yet! But you are authorized :)' 30 | }); 31 | } 32 | 33 | }; 34 | 35 | -------------------------------------------------------------------------------- /api/models/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sails101/even-more-passport/e986742cbbd699a8abbacb08038d5d11506c6c09/api/models/.gitkeep -------------------------------------------------------------------------------- /api/models/Passport.js: -------------------------------------------------------------------------------- 1 | var bcrypt = require('bcryptjs'); 2 | 3 | /** 4 | * Hash a passport password. 5 | * 6 | * @param {Object} password 7 | * @param {Function} next 8 | */ 9 | function hashPassword (passport, next) { 10 | if (passport.password) { 11 | bcrypt.hash(passport.password, 10, function (err, hash) { 12 | passport.password = hash; 13 | next(err, passport); 14 | }); 15 | } else { 16 | next(null, passport); 17 | } 18 | } 19 | 20 | /** 21 | * Passport Model 22 | * 23 | * The Passport model handles associating authenticators with users. An authen- 24 | * ticator can be either local (password) or third-party (provider). A single 25 | * user can have multiple passports, allowing them to connect and use several 26 | * third-party strategies in optional conjunction with a password. 27 | * 28 | * Since an application will only need to authenticate a user once per session, 29 | * it makes sense to encapsulate the data specific to the authentication process 30 | * in a model of its own. This allows us to keep the session itself as light- 31 | * weight as possible as the application only needs to serialize and deserialize 32 | * the user, but not the authentication data, to and from the session. 33 | */ 34 | var Passport = { 35 | attributes: { 36 | // Required field: Protocol 37 | // 38 | // Defines the protocol to use for the passport. When employing the local 39 | // strategy, the protocol will be set to 'local'. When using a third-party 40 | // strategy, the protocol will be set to the standard used by the third- 41 | // party service (e.g. 'oauth', 'oauth2', 'openid'). 42 | protocol: { type: 'alphanumeric', required: true }, 43 | 44 | // Local field: Password 45 | // 46 | // When the local strategy is employed, a password will be used as the 47 | // means of authentication along with either a username or an email. 48 | password: { type: 'string', minLength: 8 }, 49 | 50 | // Provider fields: Provider, identifer and tokens 51 | // 52 | // "provider" is the name of the third-party auth service in all lowercase 53 | // (e.g. 'github', 'facebook') whereas "identifier" is a provider-specific 54 | // key, typically an ID. These two fields are used as the main means of 55 | // identifying a passport and tying it to a local user. 56 | // 57 | // The "tokens" field is a JSON object used in the case of the OAuth stan- 58 | // dards. When using OAuth 1.0, a `token` as well as a `tokenSecret` will 59 | // be issued by the provider. In the case of OAuth 2.0, an `accessToken` 60 | // and a `refreshToken` will be issued. 61 | provider : { type: 'alphanumericdashed' }, 62 | identifier : { type: 'string' }, 63 | tokens : { type: 'json' }, 64 | accessToken : { type: 'string' }, 65 | 66 | // Associations 67 | // 68 | // Associate every passport with one, and only one, user. This requires an 69 | // adapter compatible with associations. 70 | // 71 | // For more information on associations in Waterline, check out: 72 | // https://github.com/balderdashy/waterline 73 | user: { model: 'User', required: true }, 74 | 75 | /** 76 | * Validate password used by the local strategy. 77 | * 78 | * @param {string} password The password to validate 79 | * @param {Function} next 80 | */ 81 | validatePassword: function (password, next) { 82 | bcrypt.compare(password, this.password, next); 83 | } 84 | 85 | }, 86 | 87 | /** 88 | * Callback to be run before creating a Passport. 89 | * 90 | * @param {Object} passport The soon-to-be-created Passport 91 | * @param {Function} next 92 | */ 93 | beforeCreate: function (passport, next) { 94 | hashPassword(passport, next); 95 | }, 96 | 97 | /** 98 | * Callback to be run before updating a Passport. 99 | * 100 | * @param {Object} passport Values to be updated 101 | * @param {Function} next 102 | */ 103 | beforeUpdate: function (passport, next) { 104 | hashPassword(passport, next); 105 | } 106 | }; 107 | 108 | module.exports = Passport; 109 | -------------------------------------------------------------------------------- /api/models/User.js: -------------------------------------------------------------------------------- 1 | var User = { 2 | // Enforce model schema in the case of schemaless databases 3 | schema: true, 4 | 5 | attributes: { 6 | username : { type: 'string', unique: true }, 7 | email : { type: 'email', unique: true }, 8 | passports : { collection: 'Passport', via: 'user' } 9 | } 10 | }; 11 | 12 | module.exports = User; 13 | -------------------------------------------------------------------------------- /api/policies/bearerAuth.js: -------------------------------------------------------------------------------- 1 | module.exports = function(req, res, next) { 2 | 3 | return passport.authenticate('bearer', { session: false })(req, res, next); 4 | 5 | }; 6 | -------------------------------------------------------------------------------- /api/policies/passport.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Passport Middleware 3 | * 4 | * Policy for Sails that initializes Passport.js and as well as its built-in 5 | * session support. 6 | * 7 | * In a typical web application, the credentials used to authenticate a user 8 | * will only be transmitted during the login request. If authentication 9 | * succeeds, a session will be established and maintained via a cookie set in 10 | * the user's browser. 11 | * 12 | * Each subsequent request will not contain credentials, but rather the unique 13 | * cookie that identifies the session. In order to support login sessions, 14 | * Passport will serialize and deserialize user instances to and from the 15 | * session. 16 | * 17 | * For more information on the Passport.js middleware, check out: 18 | * http://passportjs.org/guide/configure/ 19 | * 20 | * @param {Object} req 21 | * @param {Object} res 22 | * @param {Function} next 23 | */ 24 | module.exports = function (req, res, next) { 25 | // Initialize Passport 26 | passport.initialize()(req, res, function () { 27 | // Use the built-in sessions 28 | passport.session()(req, res, function () { 29 | // Make the user available throughout the frontend 30 | res.locals.user = req.user; 31 | 32 | next(); 33 | }); 34 | }); 35 | }; 36 | -------------------------------------------------------------------------------- /api/policies/sessionAuth.js: -------------------------------------------------------------------------------- 1 | /** 2 | * sessionAuth 3 | * 4 | * @module :: Policy 5 | * @description :: Simple policy to allow any authenticated user 6 | * Assumes that your login action in one of your controllers sets `req.session.authenticated = true;` 7 | * @docs :: http://sailsjs.org/#!documentation/policies 8 | * 9 | */ 10 | module.exports = function(req, res, next) { 11 | 12 | if(req.user) return next(); 13 | res.redirect('/login'); 14 | 15 | }; 16 | -------------------------------------------------------------------------------- /api/responses/badRequest.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 400 (Bad Request) Handler 3 | * 4 | * Usage: 5 | * return res.badRequest(); 6 | * return res.badRequest(err); 7 | * return res.badRequest(err, view); 8 | * return res.badRequest(err, redirectTo); 9 | * 10 | * e.g.: 11 | * ``` 12 | * return res.badRequest( 13 | * 'Please choose a valid `password` (6-12 characters)', 14 | * '/trial/signup' 15 | * ); 16 | * ``` 17 | */ 18 | 19 | module.exports = function badRequest(err, viewOrRedirect) { 20 | 21 | // Get access to `req` & `res` 22 | var req = this.req; 23 | var res = this.res; 24 | 25 | // Serve JSON (with optional JSONP support) 26 | function sendJSON (data) { 27 | if (!data) { 28 | return res.send(); 29 | } 30 | else { 31 | if (typeof data !== 'object' || data instanceof Error) { 32 | data = {error: data}; 33 | } 34 | if ( req.options.jsonp && !req.isSocket ) { 35 | return res.jsonp(data); 36 | } 37 | else return res.json(data); 38 | } 39 | } 40 | 41 | // Set status code 42 | res.status(400); 43 | 44 | // Log error to console 45 | this.req._sails.log.verbose('Sent 400 ("Bad Request") response'); 46 | if (err) { 47 | this.req._sails.log.verbose(err); 48 | } 49 | 50 | // If the user-agent wants JSON, always respond with JSON 51 | if (req.wantsJSON) { 52 | return sendJSON(err); 53 | } 54 | 55 | // Make data more readable for view locals 56 | var locals; 57 | if (!err) { locals = {}; } 58 | else if (typeof err !== 'object'){ 59 | locals = {error: err}; 60 | } 61 | else { 62 | var readabilify = function (value) { 63 | if (sails.util.isArray(value)) { 64 | return sails.util.map(value, readabilify); 65 | } 66 | else if (sails.util.isPlainObject(value)) { 67 | return sails.util.inspect(value); 68 | } 69 | else return value; 70 | }; 71 | locals = { error: readabilify(err) }; 72 | } 73 | 74 | // Serve HTML view or redirect to specified URL 75 | if (typeof viewOrRedirect === 'string') { 76 | if (viewOrRedirect.match(/^(\/|http:\/\/|https:\/\/)/)) { 77 | if (err && typeof req.flash === 'function') { 78 | req.flash('error', err); 79 | } 80 | return res.redirect(viewOrRedirect); 81 | } 82 | else return res.view(viewOrRedirect, locals, function viewReady(viewErr, html) { 83 | if (viewErr) return sendJSON(err); 84 | else return res.send(html); 85 | }); 86 | } 87 | else return res.view('400', locals, function viewReady(viewErr, html) { 88 | if (viewErr) return sendJSON(err); 89 | else return res.send(html); 90 | }); 91 | }; 92 | 93 | -------------------------------------------------------------------------------- /api/responses/forbidden.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 403 (Forbidden) Handler 3 | * 4 | * Usage: 5 | * return res.forbidden(); 6 | * return res.forbidden(err); 7 | * return res.forbidden(err, view); 8 | * return res.forbidden(err, redirectTo); 9 | * 10 | * e.g.: 11 | * ``` 12 | * return res.forbidden('Access denied.'); 13 | * ``` 14 | */ 15 | 16 | module.exports = function forbidden (err, viewOrRedirect) { 17 | 18 | // Get access to `req` & `res` 19 | var req = this.req; 20 | var res = this.res; 21 | 22 | // Serve JSON (with optional JSONP support) 23 | function sendJSON (data) { 24 | if (!data) { 25 | return res.send(); 26 | } 27 | else { 28 | if (typeof data !== 'object' || data instanceof Error) { 29 | data = {error: data}; 30 | } 31 | if ( req.options.jsonp && !req.isSocket ) { 32 | return res.jsonp(data); 33 | } 34 | else return res.json(data); 35 | } 36 | } 37 | 38 | // Set status code 39 | res.status(403); 40 | 41 | // Log error to console 42 | this.req._sails.log.verbose('Sent 403 ("Forbidden") response'); 43 | if (err) { 44 | this.req._sails.log.verbose(err); 45 | } 46 | 47 | // If the user-agent wants JSON, always respond with JSON 48 | if (req.wantsJSON) { 49 | return sendJSON(err); 50 | } 51 | 52 | // Make data more readable for view locals 53 | var locals; 54 | if (!err) { locals = {}; } 55 | else if (typeof err !== 'object'){ 56 | locals = {error: err}; 57 | } 58 | else { 59 | var readabilify = function (value) { 60 | if (sails.util.isArray(value)) { 61 | return sails.util.map(value, readabilify); 62 | } 63 | else if (sails.util.isPlainObject(value)) { 64 | return sails.util.inspect(value); 65 | } 66 | else return value; 67 | }; 68 | locals = { error: readabilify(err) }; 69 | } 70 | 71 | // Serve HTML view or redirect to specified URL 72 | if (typeof viewOrRedirect === 'string') { 73 | if (viewOrRedirect.match(/^(\/|http:\/\/|https:\/\/)/)) { 74 | return res.redirect(viewOrRedirect); 75 | } 76 | else return res.view(viewOrRedirect, locals, function viewReady(viewErr, html) { 77 | if (viewErr) return sendJSON(err); 78 | else return res.send(html); 79 | }); 80 | } 81 | else return res.view('403', locals, function viewReady(viewErr, html) { 82 | if (viewErr) return sendJSON(err); 83 | else return res.send(html); 84 | }); 85 | }; 86 | -------------------------------------------------------------------------------- /api/responses/notFound.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 404 (Not Found) Handler 3 | * 4 | * Usage: 5 | * return res.notFound(); 6 | * return res.notFound(err); 7 | * return res.notFound(err, view); 8 | * return res.notFound(err, redirectTo); 9 | * 10 | * e.g.: 11 | * ``` 12 | * return res.notFound(); 13 | * ``` 14 | * 15 | * NOTE: 16 | * If a request doesn't match any explicit routes (i.e. `config/routes.js`) 17 | * or route blueprints (i.e. "shadow routes", Sails will call `res.notFound()` 18 | * automatically. 19 | */ 20 | 21 | module.exports = function notFound (err, viewOrRedirect) { 22 | 23 | // Get access to `req` & `res` 24 | var req = this.req; 25 | var res = this.res; 26 | 27 | // Serve JSON (with optional JSONP support) 28 | function sendJSON (data) { 29 | if (!data) { 30 | return res.send(); 31 | } 32 | else { 33 | if (typeof data !== 'object' || data instanceof Error) { 34 | data = {error: data}; 35 | } 36 | if ( req.options.jsonp && !req.isSocket ) { 37 | return res.jsonp(data); 38 | } 39 | else return res.json(data); 40 | } 41 | } 42 | 43 | // Set status code 44 | res.status(404); 45 | 46 | // Log error to console 47 | this.req._sails.log.verbose('Sent 404 ("Not Found") response'); 48 | if (err) { 49 | this.req._sails.log.verbose(err); 50 | } 51 | 52 | // If the user-agent wants JSON, always respond with JSON 53 | if (req.wantsJSON) { 54 | return sendJSON(err); 55 | } 56 | 57 | // Make data more readable for view locals 58 | var locals; 59 | if (!err) { locals = {}; } 60 | else if (typeof err !== 'object'){ 61 | locals = {error: err}; 62 | } 63 | else { 64 | var readabilify = function (value) { 65 | if (sails.util.isArray(value)) { 66 | return sails.util.map(value, readabilify); 67 | } 68 | else if (sails.util.isPlainObject(value)) { 69 | return sails.util.inspect(value); 70 | } 71 | else return value; 72 | }; 73 | locals = { error: readabilify(err) }; 74 | } 75 | 76 | // Serve HTML view or redirect to specified URL 77 | if (typeof viewOrRedirect === 'string') { 78 | if (viewOrRedirect.match(/^(\/|http:\/\/|https:\/\/)/)) { 79 | return res.redirect(viewOrRedirect); 80 | } 81 | else return res.view(viewOrRedirect, locals, function viewReady(viewErr, html) { 82 | if (viewErr) return sendJSON(err); 83 | else return res.send(html); 84 | }); 85 | } 86 | else return res.view('404', locals, function viewReady(viewErr, html) { 87 | if (viewErr) return sendJSON(err); 88 | else return res.send(html); 89 | }); 90 | }; 91 | -------------------------------------------------------------------------------- /api/responses/ok.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 200 (OK) Response 3 | * 4 | * Usage: 5 | * return res.ok(); 6 | * return res.ok(data); 7 | * return res.ok(data, view); 8 | * return res.ok(data, redirectTo); 9 | * return res.ok(data, true); 10 | * 11 | * @param {Object} data 12 | * @param {Boolean|String} viewOrRedirect 13 | * [optional] 14 | * - pass string to render specified view 15 | * - pass string with leading slash or http:// or https:// to do redirect 16 | */ 17 | 18 | module.exports = function sendOK (data, viewOrRedirect) { 19 | 20 | // Get access to `req` & `res` 21 | var req = this.req; 22 | var res = this.res; 23 | 24 | // Serve JSON (with optional JSONP support) 25 | function sendJSON (data) { 26 | if (!data) { 27 | return res.send(); 28 | } 29 | else { 30 | if (typeof data !== 'object') { return res.send(data); } 31 | if ( req.options.jsonp && !req.isSocket ) { 32 | return res.jsonp(data); 33 | } 34 | else return res.json(data); 35 | } 36 | } 37 | 38 | // Set status code 39 | res.status(200); 40 | 41 | // Log error to console 42 | this.req._sails.log.verbose('Sent 200 ("OK") response'); 43 | if (data) { 44 | this.req._sails.log.verbose(data); 45 | } 46 | 47 | // Serve JSON (with optional JSONP support) 48 | if (req.wantsJSON) { 49 | return sendJSON(data); 50 | } 51 | 52 | // Make data more readable for view locals 53 | var locals; 54 | if (!data || typeof data !== 'object'){ 55 | locals = {}; 56 | } 57 | else { 58 | locals = data; 59 | } 60 | 61 | // Serve HTML view or redirect to specified URL 62 | if (typeof viewOrRedirect === 'string') { 63 | if (viewOrRedirect.match(/^(\/|http:\/\/|https:\/\/)/)) { 64 | return res.redirect(viewOrRedirect); 65 | } 66 | else return res.view(viewOrRedirect, locals, function viewReady(viewErr, html) { 67 | if (viewErr) return sendJSON(data); 68 | else return res.send(html); 69 | }); 70 | } 71 | else return res.view(locals, function viewReady(viewErr, html) { 72 | if (viewErr) return sendJSON(data); 73 | else return res.send(html); 74 | }); 75 | 76 | }; 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /api/responses/serverError.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 500 (Server Error) Response 3 | * 4 | * Usage: 5 | * return res.serverError(); 6 | * return res.serverError(err); 7 | * return res.serverError(err, view); 8 | * return res.serverError(err, redirectTo); 9 | * 10 | * NOTE: 11 | * If something throws in a policy or controller, or an internal 12 | * error is encountered, Sails will call `res.serverError()` 13 | * automatically. 14 | */ 15 | 16 | module.exports = function serverError (err, viewOrRedirect) { 17 | 18 | // Get access to `req` & `res` 19 | var req = this.req; 20 | var res = this.res; 21 | 22 | // Serve JSON (with optional JSONP support) 23 | function sendJSON (data) { 24 | if (!data) { 25 | return res.send(); 26 | } 27 | else { 28 | if (typeof data !== 'object' || data instanceof Error) { 29 | data = {error: data}; 30 | } 31 | 32 | if ( req.options.jsonp && !req.isSocket ) { 33 | return res.jsonp(data); 34 | } 35 | else return res.json(data); 36 | } 37 | } 38 | 39 | // Set status code 40 | res.status(500); 41 | 42 | // Log error to console 43 | this.req._sails.log.error('Sent 500 ("Server Error") response'); 44 | if (err) { 45 | this.req._sails.log.error(err); 46 | } 47 | 48 | // Only include errors in response if application environment 49 | // is not set to 'production'. In production, we shouldn't 50 | // send back any identifying information about errors. 51 | if (this.req._sails.config.environment === 'production') { 52 | err = undefined; 53 | } 54 | 55 | // If the user-agent wants JSON, always respond with JSON 56 | if (req.wantsJSON) { 57 | return sendJSON(err); 58 | } 59 | 60 | // Make data more readable for view locals 61 | var locals; 62 | if (!err) { locals = {}; } 63 | else if (typeof err !== 'object'){ 64 | locals = {error: err}; 65 | } 66 | else { 67 | var readabilify = function (value) { 68 | if (sails.util.isArray(value)) { 69 | return sails.util.map(value, readabilify); 70 | } 71 | else if (sails.util.isPlainObject(value)) { 72 | return sails.util.inspect(value); 73 | } 74 | else return value; 75 | }; 76 | locals = { error: readabilify(err) }; 77 | } 78 | 79 | // Serve HTML view or redirect to specified URL 80 | if (typeof viewOrRedirect === 'string') { 81 | if (viewOrRedirect.match(/^(\/|http:\/\/|https:\/\/)/)) { 82 | return res.redirect(viewOrRedirect); 83 | } 84 | else return res.view(viewOrRedirect, locals, function viewReady(viewErr, html) { 85 | if (viewErr) return sendJSON(err); 86 | else return res.send(html); 87 | }); 88 | } 89 | else return res.view('500', locals, function viewReady(viewErr, html) { 90 | if (viewErr) return sendJSON(err); 91 | else return res.send(html); 92 | }); 93 | 94 | }; 95 | -------------------------------------------------------------------------------- /api/services/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sails101/even-more-passport/e986742cbbd699a8abbacb08038d5d11506c6c09/api/services/.gitkeep -------------------------------------------------------------------------------- /api/services/passport.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | , url = require('url') 3 | , passport = require('passport'); 4 | 5 | /** 6 | * Passport Service 7 | * 8 | * A painless Passport.js service for your Sails app that is guaranteed to 9 | * Rock Your Socks™. It takes all the hassle out of setting up Passport.js by 10 | * encapsulating all the boring stuff in two functions: 11 | * 12 | * passport.endpoint() 13 | * passport.callback() 14 | * 15 | * The former sets up an endpoint (/auth/:provider) for redirecting a user to a 16 | * third-party provider for authentication, while the latter sets up a callback 17 | * endpoint (/auth/:provider/callback) for receiving the response from the 18 | * third-party provider. All you have to do is define in the configuration which 19 | * third-party providers you'd like to support. It's that easy! 20 | * 21 | * Behind the scenes, the service stores all the data it needs within "Pass- 22 | * ports". These contain all the information required to associate a local user 23 | * with a profile from a third-party provider. This even holds true for the good 24 | * ol' password authentication scheme – the Authentication Service takes care of 25 | * encrypting passwords and storing them in Passports, allowing you to keep your 26 | * User model free of bloat. 27 | */ 28 | 29 | // Load authentication protocols 30 | passport.protocols = require('./protocols'); 31 | 32 | /** 33 | * Connect a third-party profile to a local user 34 | * 35 | * This is where most of the magic happens when a user is authenticating with a 36 | * third-party provider. What it does, is the following: 37 | * 38 | * 1. Given a provider and an identifier, find a mathcing Passport. 39 | * 2. From here, the logic branches into two paths. 40 | * 41 | * - A user is not currently logged in: 42 | * 1. If a Passport wassn't found, create a new user as well as a new 43 | * Passport that will be assigned to the user. 44 | * 2. If a Passport was found, get the user associated with the passport. 45 | * 46 | * - A user is currently logged in: 47 | * 1. If a Passport wasn't found, create a new Passport and associate it 48 | * with the already logged in user (ie. "Connect") 49 | * 2. If a Passport was found, nothing needs to happen. 50 | * 51 | * As you can see, this function handles both "authentication" and "authori- 52 | * zation" at the same time. This is due to the fact that we pass in 53 | * `passReqToCallback: true` when loading the strategies, allowing us to look 54 | * for an existing session in the request and taking action based on that. 55 | * 56 | * For more information on auth(entication|rization) in Passport.js, check out: 57 | * http://passportjs.org/guide/authenticate/ 58 | * http://passportjs.org/guide/authorize/ 59 | * 60 | * @param {Object} req 61 | * @param {Object} query 62 | * @param {Object} profile 63 | * @param {Function} next 64 | */ 65 | passport.connect = function (req, query, profile, next) { 66 | var user = {} 67 | , provider; 68 | 69 | // Get the authentication provider from the query. 70 | query.provider = req.param('provider'); 71 | 72 | // Use profile.provider or fallback to the query.provider if it is undefined 73 | // as is the case for OpenID, for example 74 | provider = profile.provider || query.provider; 75 | 76 | // If the provider cannot be identified we cannot match it to a passport so 77 | // throw an error and let whoever's next in line take care of it. 78 | if (!provider){ 79 | return next(new Error('No authentication provider was identified.')); 80 | } 81 | 82 | // If the profile object contains a list of emails, grab the first one and 83 | // add it to the user. 84 | if (profile.hasOwnProperty('emails')) { 85 | user.email = profile.emails[0].value; 86 | } 87 | // If the profile object contains a username, add it to the user. 88 | if (profile.hasOwnProperty('username')) { 89 | user.username = profile.username; 90 | } 91 | 92 | // If neither an email or a username was available in the profile, we don't 93 | // have a way of identifying the user in the future. Throw an error and let 94 | // whoever's next in the line take care of it. 95 | if (!user.username && !user.email) { 96 | return next(new Error('Neither a username nor email was available')); 97 | } 98 | 99 | Passport.findOne({ 100 | provider : provider 101 | , identifier : query.identifier.toString() 102 | }, function (err, passport) { 103 | if (err) { 104 | return next(err); 105 | } 106 | 107 | if (!req.user) { 108 | // Scenario: A new user is attempting to sign up using a third-party 109 | // authentication provider. 110 | // Action: Create a new user and assign them a passport. 111 | if (!passport) { 112 | User.create(user, function (err, user) { 113 | if (err) { 114 | if (err.code === 'E_VALIDATION') { 115 | if (err.invalidAttributes.email) { 116 | req.flash('error', 'Error.Passport.Email.Exists'); 117 | } 118 | else { 119 | req.flash('error', 'Error.Passport.User.Exists'); 120 | } 121 | } 122 | 123 | return next(err); 124 | } 125 | 126 | query.user = user.id; 127 | 128 | Passport.create(query, function (err, passport) { 129 | // If a passport wasn't created, bail out 130 | if (err) { 131 | return next(err); 132 | } 133 | 134 | next(err, user); 135 | }); 136 | }); 137 | } 138 | // Scenario: An existing user is trying to log in using an already 139 | // connected passport. 140 | // Action: Get the user associated with the passport. 141 | else { 142 | // If the tokens have changed since the last session, update them 143 | if (query.hasOwnProperty('tokens') && query.tokens !== passport.tokens) { 144 | passport.tokens = query.tokens; 145 | } 146 | 147 | // Save any updates to the Passport before moving on 148 | passport.save(function (err, passport) { 149 | if (err) { 150 | return next(err); 151 | } 152 | 153 | // Fetch the user associated with the Passport 154 | User.findOne(passport.user.id, next); 155 | }); 156 | } 157 | } else { 158 | // Scenario: A user is currently logged in and trying to connect a new 159 | // passport. 160 | // Action: Create and assign a new passport to the user. 161 | if (!passport) { 162 | query.user = req.user.id; 163 | 164 | Passport.create(query, function (err, passport) { 165 | // If a passport wasn't created, bail out 166 | if (err) { 167 | return next(err); 168 | } 169 | 170 | next(err, req.user); 171 | }); 172 | } 173 | // Scenario: The user is a nutjob or spammed the back-button. 174 | // Action: Simply pass along the already established session. 175 | else { 176 | next(null, req.user); 177 | } 178 | } 179 | }); 180 | }; 181 | 182 | /** 183 | * Create an authentication endpoint 184 | * 185 | * For more information on authentication in Passport.js, check out: 186 | * http://passportjs.org/guide/authenticate/ 187 | * 188 | * @param {Object} req 189 | * @param {Object} res 190 | */ 191 | passport.endpoint = function (req, res) { 192 | var strategies = sails.config.passport 193 | , provider = req.param('provider') 194 | , options = {}; 195 | 196 | // If a provider doesn't exist for this endpoint, send the user back to the 197 | // login page 198 | if (!strategies.hasOwnProperty(provider)) { 199 | return res.redirect('/login'); 200 | } 201 | 202 | // Attach scope if it has been set in the config 203 | if (strategies[provider].hasOwnProperty('scope')) { 204 | options.scope = strategies[provider].scope; 205 | } 206 | 207 | // Redirect the user to the provider for authentication. When complete, 208 | // the provider will redirect the user back to the application at 209 | // /auth/:provider/callback 210 | this.authenticate(provider, options)(req, res, req.next); 211 | }; 212 | 213 | /** 214 | * Create an authentication callback endpoint 215 | * 216 | * For more information on authentication in Passport.js, check out: 217 | * http://passportjs.org/guide/authenticate/ 218 | * 219 | * @param {Object} req 220 | * @param {Object} res 221 | * @param {Function} next 222 | */ 223 | passport.callback = function (req, res, next) { 224 | var provider = req.param('provider', 'local') 225 | , action = req.param('action'); 226 | 227 | // Passport.js wasn't really built for local user registration, but it's nice 228 | // having it tied into everything else. 229 | if (provider === 'local' && action !== undefined) { 230 | if (action === 'register' && !req.user) { 231 | this.protocols.local.register(req, res, next); 232 | } 233 | else if (action === 'connect' && req.user) { 234 | this.protocols.local.connect(req, res, next); 235 | } 236 | else if (action === 'disconnect' && req.user) { 237 | this.protocols.local.disconnect(req, res, next); 238 | } 239 | else { 240 | next(new Error('Invalid action')); 241 | } 242 | } else { 243 | if (action === 'disconnect' && req.user) { 244 | this.disconnect(req, res, next) ; 245 | } else { 246 | // The provider will redirect the user to this URL after approval. Finish 247 | // the authentication process by attempting to obtain an access token. If 248 | // access was granted, the user will be logged in. Otherwise, authentication 249 | // has failed. 250 | this.authenticate(provider, next)(req, res, req.next); 251 | } 252 | } 253 | }; 254 | 255 | /** 256 | * Load all strategies defined in the Passport configuration 257 | * 258 | * For example, we could add this to our config to use the GitHub strategy 259 | * with permission to access a users email address (even if it's marked as 260 | * private) as well as permission to add and update a user's Gists: 261 | * 262 | github: { 263 | name: 'GitHub', 264 | protocol: 'oauth2', 265 | strategy: require('passport-github').Strategy 266 | scope: [ 'user', 'gist' ] 267 | options: { 268 | clientID: 'CLIENT_ID', 269 | clientSecret: 'CLIENT_SECRET' 270 | } 271 | } 272 | * 273 | * For more information on the providers supported by Passport.js, check out: 274 | * http://passportjs.org/guide/providers/ 275 | * 276 | */ 277 | passport.loadStrategies = function () { 278 | var self = this 279 | , strategies = sails.config.passport; 280 | 281 | Object.keys(strategies).forEach(function (key) { 282 | var options = { passReqToCallback: true }, Strategy; 283 | 284 | if (key === 'local') { 285 | // Since we need to allow users to login using both usernames as well as 286 | // emails, we'll set the username field to something more generic. 287 | _.extend(options, { usernameField: 'identifier' }); 288 | 289 | // Only load the local strategy if it's enabled in the config 290 | if (strategies.local) { 291 | Strategy = strategies[key].strategy; 292 | 293 | self.use(new Strategy(options, self.protocols.local.login)); 294 | } 295 | } else if (key === 'bearer') { 296 | 297 | if (strategies.bearer) { 298 | Strategy = strategies[key].strategy; 299 | self.use(new Strategy(function(token, done) { 300 | 301 | Passport.findOne({ accessToken: token }, function(err, passport) { 302 | if (err) { return done(err); } 303 | if (!passport) { return done(null, false); } 304 | User.findById(passport.user, function(err, user) { 305 | if (err) { return done(err); } 306 | if (!user) { return done(null, false); } 307 | return done(null, user, { scope: 'all' }); 308 | }); 309 | }); 310 | 311 | })); 312 | } 313 | 314 | } else { 315 | var protocol = strategies[key].protocol 316 | , callback = strategies[key].callback; 317 | 318 | if (!callback) { 319 | callback = path.join('auth', key, 'callback'); 320 | } 321 | 322 | Strategy = strategies[key].strategy; 323 | 324 | var baseUrl = sails.getBaseurl(); 325 | 326 | switch (protocol) { 327 | case 'oauth': 328 | case 'oauth2': 329 | options.callbackURL = url.resolve(baseUrl, callback); 330 | break; 331 | 332 | case 'openid': 333 | options.returnURL = url.resolve(baseUrl, callback); 334 | options.realm = baseUrl; 335 | options.profile = true; 336 | break; 337 | } 338 | 339 | // Merge the default options with any options defined in the config. All 340 | // defaults can be overriden, but I don't see a reason why you'd want to 341 | // do that. 342 | _.extend(options, strategies[key].options); 343 | 344 | self.use(new Strategy(options, self.protocols[protocol])); 345 | } 346 | }); 347 | }; 348 | 349 | /** 350 | * Disconnect a passport from a user 351 | * 352 | * @param {Object} req 353 | * @param {Object} res 354 | */ 355 | passport.disconnect = function (req, res, next) { 356 | var user = req.user 357 | , provider = req.param('provider'); 358 | 359 | Passport.findOne({ 360 | provider : provider, 361 | user : user.id 362 | }, function (err, passport) { 363 | if (err) { 364 | return next(err); 365 | } 366 | 367 | Passport.destroy(passport.id, function (error) { 368 | if (err) { 369 | return next(err); 370 | } 371 | 372 | next(null, user); 373 | }); 374 | }); 375 | }; 376 | 377 | passport.serializeUser(function (user, next) { 378 | next(null, user.id); 379 | }); 380 | 381 | passport.deserializeUser(function (id, next) { 382 | User.findOne(id, next); 383 | }); 384 | 385 | module.exports = passport; 386 | -------------------------------------------------------------------------------- /api/services/protocols/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Authentication Protocols 3 | * 4 | * Protocols where introduced to patch all the little inconsistencies between 5 | * the different authentication APIs. While the local authentication strategy 6 | * is as straigt-forward as it gets, there are some differences between the 7 | * services that expose an API for authentication. 8 | * 9 | * For example, OAuth 1.0 and OAuth 2.0 both handle delegated authentication 10 | * using tokens, but the tokens have changed between the two versions. This 11 | * is accomodated by having a single `token` object in the Passport model that 12 | * can contain any combination of tokens issued by the authentication API. 13 | */ 14 | module.exports = { 15 | local : require('./local') 16 | , oauth : require('./oauth') 17 | , oauth2 : require('./oauth2') 18 | , openid : require('./openid') 19 | }; 20 | -------------------------------------------------------------------------------- /api/services/protocols/local.js: -------------------------------------------------------------------------------- 1 | var validator = require('validator'); 2 | 3 | /** 4 | * Local Authentication Protocol 5 | * 6 | * The most widely used way for websites to authenticate users is via a username 7 | * and/or email as well as a password. This module provides functions both for 8 | * registering entirely new users, assigning passwords to already registered 9 | * users and validating login requesting. 10 | * 11 | * For more information on local authentication in Passport.js, check out: 12 | * http://passportjs.org/guide/username-password/ 13 | */ 14 | 15 | /** 16 | * Register a new user 17 | * 18 | * This method creates a new user from a specified email, username and password 19 | * and assign the newly created user a local Passport. 20 | * 21 | * @param {Object} req 22 | * @param {Object} res 23 | * @param {Function} next 24 | */ 25 | exports.register = function (req, res, next) { 26 | var email = req.param('email') 27 | , username = req.param('username') 28 | , password = req.param('password'); 29 | 30 | if (!email) { 31 | req.flash('error', 'Error.Passport.Email.Missing'); 32 | return next(new Error('No email was entered.')); 33 | } 34 | 35 | if (!username) { 36 | req.flash('error', 'Error.Passport.Username.Missing'); 37 | return next(new Error('No username was entered.')); 38 | } 39 | 40 | if (!password) { 41 | req.flash('error', 'Error.Passport.Password.Missing'); 42 | return next(new Error('No password was entered.')); 43 | } 44 | 45 | User.create({ 46 | username : username 47 | , email : email 48 | }, function (err, user) { 49 | if (err) { 50 | if (err.code === 'E_VALIDATION') { 51 | if (err.invalidAttributes.email) { 52 | req.flash('error', 'Error.Passport.Email.Exists'); 53 | } else { 54 | req.flash('error', 'Error.Passport.User.Exists'); 55 | } 56 | } 57 | 58 | return next(err); 59 | } 60 | 61 | var token = new Buffer(user.username + user.createdAt).toString('base64'); 62 | Passport.create({ 63 | protocol : 'local' 64 | , password : password 65 | , user : user.id 66 | , accessToken: token 67 | }, function (err, passport) { 68 | if (err) { 69 | if (err.code === 'E_VALIDATION') { 70 | req.flash('error', 'Error.Passport.Password.Invalid'); 71 | } 72 | 73 | return user.destroy(function (destroyErr) { 74 | next(destroyErr || err); 75 | }); 76 | } 77 | 78 | next(null, user); 79 | }); 80 | }); 81 | }; 82 | 83 | /** 84 | * Assign local Passport to user 85 | * 86 | * This function can be used to assign a local Passport to a user who doens't 87 | * have one already. This would be the case if the user registered using a 88 | * third-party service and therefore never set a password. 89 | * 90 | * @param {Object} req 91 | * @param {Object} res 92 | * @param {Function} next 93 | */ 94 | exports.connect = function (req, res, next) { 95 | var user = req.user 96 | , password = req.param('password'); 97 | 98 | Passport.findOne({ 99 | protocol : 'local' 100 | , user : user.id 101 | }, function (err, passport) { 102 | if (err) { 103 | return next(err); 104 | } 105 | 106 | if (!passport) { 107 | Passport.create({ 108 | protocol : 'local' 109 | , password : password 110 | , user : user.id 111 | }, function (err, passport) { 112 | next(err, user); 113 | }); 114 | } 115 | else { 116 | next(null, user); 117 | } 118 | }); 119 | }; 120 | 121 | /** 122 | * Validate a login request 123 | * 124 | * Looks up a user using the supplied identifier (email or username) and then 125 | * attempts to find a local Passport associated with the user. If a Passport is 126 | * found, its password is checked against the password supplied in the form. 127 | * 128 | * @param {Object} req 129 | * @param {string} identifier 130 | * @param {string} password 131 | * @param {Function} next 132 | */ 133 | exports.login = function (req, identifier, password, next) { 134 | var isEmail = validator.isEmail(identifier) 135 | , query = {}; 136 | 137 | if (isEmail) { 138 | query.email = identifier; 139 | } 140 | else { 141 | query.username = identifier; 142 | } 143 | 144 | User.findOne(query, function (err, user) { 145 | if (err) { 146 | return next(err); 147 | } 148 | 149 | if (!user) { 150 | if (isEmail) { 151 | req.flash('error', 'Error.Passport.Email.NotFound'); 152 | } else { 153 | req.flash('error', 'Error.Passport.Username.NotFound'); 154 | } 155 | 156 | return next(null, false); 157 | } 158 | 159 | Passport.findOne({ 160 | protocol : 'local' 161 | , user : user.id 162 | }, function (err, passport) { 163 | if (passport) { 164 | passport.validatePassword(password, function (err, res) { 165 | if (err) { 166 | return next(err); 167 | } 168 | 169 | if (!res) { 170 | req.flash('error', 'Error.Passport.Password.Wrong'); 171 | return next(null, false); 172 | } else { 173 | return next(null, user); 174 | } 175 | }); 176 | } 177 | else { 178 | req.flash('error', 'Error.Passport.Password.NotSet'); 179 | return next(null, false); 180 | } 181 | }); 182 | }); 183 | }; 184 | -------------------------------------------------------------------------------- /api/services/protocols/oauth.js: -------------------------------------------------------------------------------- 1 | /** 2 | * OAuth Authentication Protocol 3 | * 4 | * OAuth 1.0 is a delegated authentication strategy that involves multiple 5 | * steps. First, a request token must be obtained. Next, the user is redirected 6 | * to the service provider to authorize access. Finally, after authorization has 7 | * been granted, the user is redirected back to the application and the request 8 | * token can be exchanged for an access token. The application requesting access, 9 | * known as a consumer, is identified by a consumer key and consumer secret. 10 | * 11 | * For more information on OAuth in Passport.js, check out: 12 | * http://passportjs.org/guide/oauth/ 13 | * 14 | * @param {Object} req 15 | * @param {string} token 16 | * @param {string} tokenSecret 17 | * @param {Object} profile 18 | * @param {Function} next 19 | */ 20 | module.exports = function (req, token, tokenSecret, profile, next) { 21 | var query = { 22 | identifier : profile.id 23 | , protocol : 'oauth' 24 | , tokens : { token: token } 25 | }; 26 | 27 | if (tokenSecret !== undefined) { 28 | query.tokens.tokenSecret = tokenSecret; 29 | } 30 | 31 | passport.connect(req, query, profile, next); 32 | }; 33 | -------------------------------------------------------------------------------- /api/services/protocols/oauth2.js: -------------------------------------------------------------------------------- 1 | /** 2 | * OAuth 2.0 Authentication Protocol 3 | * 4 | * OAuth 2.0 is the successor to OAuth 1.0, and is designed to overcome 5 | * perceived shortcomings in the earlier version. The authentication flow is 6 | * essentially the same. The user is first redirected to the service provider 7 | * to authorize access. After authorization has been granted, the user is 8 | * redirected back to the application with a code that can be exchanged for an 9 | * access token. The application requesting access, known as a client, is iden- 10 | * tified by an ID and secret. 11 | * 12 | * For more information on OAuth in Passport.js, check out: 13 | * http://passportjs.org/guide/oauth/ 14 | * 15 | * @param {Object} req 16 | * @param {string} accessToken 17 | * @param {string} refreshToken 18 | * @param {Object} profile 19 | * @param {Function} next 20 | */ 21 | module.exports = function (req, accessToken, refreshToken, profile, next) { 22 | var query = { 23 | identifier : profile.id 24 | , protocol : 'oauth2' 25 | , tokens : { accessToken: accessToken } 26 | }; 27 | 28 | if (refreshToken !== undefined) { 29 | query.tokens.refreshToken = refreshToken; 30 | } 31 | 32 | passport.connect(req, query, profile, next); 33 | }; 34 | -------------------------------------------------------------------------------- /api/services/protocols/openid.js: -------------------------------------------------------------------------------- 1 | /** 2 | * OpenID Authentication Protocol 3 | * 4 | * OpenID is an open standard for federated authentication. When visiting a 5 | * website, users present their OpenID to sign in. The user then authenticates 6 | * with their chosen OpenID provider, which issues an assertion to confirm the 7 | * user's identity. The website verifies this assertion in order to sign the 8 | * user in. 9 | * 10 | * For more information on OpenID in Passport.js, check out: 11 | * http://passportjs.org/guide/openid/ 12 | * 13 | * @param {Object} req 14 | * @param {string} identifier 15 | * @param {Object} profile 16 | * @param {Function} next 17 | */ 18 | module.exports = function (req, identifier, profile, next) { 19 | var query = { 20 | identifier : identifier 21 | , protocol : 'openid' 22 | }; 23 | 24 | passport.connect(req, query, profile, next); 25 | }; 26 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * app.js 3 | * 4 | * Use `app.js` to run your app without `sails lift`. 5 | * To start the server, run: `node app.js`. 6 | * 7 | * This is handy in situations where the sails CLI is not relevant or useful. 8 | * 9 | * For example: 10 | * => `node app.js` 11 | * => `forever start app.js` 12 | * => `node debug app.js` 13 | * => `modulus deploy` 14 | * => `heroku scale` 15 | * 16 | * 17 | * The same command-line arguments are supported, e.g.: 18 | * `node app.js --silent --port=80 --prod` 19 | */ 20 | 21 | // Ensure a "sails" can be located: 22 | var sails; 23 | try { 24 | sails = require('sails'); 25 | } catch (e) { 26 | console.error('To run an app using `node app.js`, you usually need to have a version of `sails` installed in the same directory as your app.'); 27 | console.error('To do that, run `npm install sails`'); 28 | console.error(''); 29 | console.error('Alternatively, if you have sails installed globally (i.e. you did `npm install -g sails`), you can use `sails lift`.'); 30 | console.error('When you run `sails lift`, your app will still use a local `./node_modules/sails` dependency if it exists,'); 31 | console.error('but if it doesn\'t, the app will run with the global sails instead!'); 32 | return; 33 | } 34 | 35 | // Try to get `rc` dependency 36 | var rc; 37 | try { 38 | rc = require('rc'); 39 | } catch (e0) { 40 | try { 41 | rc = require('sails/node_modules/rc'); 42 | } catch (e1) { 43 | console.error('Could not find dependency: `rc`.'); 44 | console.error('Your `.sailsrc` file(s) will be ignored.'); 45 | console.error('To resolve this, run:'); 46 | console.error('npm install rc --save'); 47 | rc = function () { return {}; }; 48 | } 49 | } 50 | 51 | 52 | // Start server 53 | sails.lift(rc('sails')); 54 | -------------------------------------------------------------------------------- /assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sails101/even-more-passport/e986742cbbd699a8abbacb08038d5d11506c6c09/assets/favicon.ico -------------------------------------------------------------------------------- /assets/images/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sails101/even-more-passport/e986742cbbd699a8abbacb08038d5d11506c6c09/assets/images/.gitkeep -------------------------------------------------------------------------------- /assets/robots.txt: -------------------------------------------------------------------------------- 1 | # The robots.txt file is used to control how search engines index your live URLs. 2 | # See http://www.robotstxt.org/wc/norobots.html for more information. 3 | 4 | 5 | 6 | # To prevent search engines from seeing the site altogether, uncomment the next two lines: 7 | # User-Agent: * 8 | # Disallow: / 9 | -------------------------------------------------------------------------------- /assets/styles/importer.less: -------------------------------------------------------------------------------- 1 | /** 2 | * importer.less 3 | * 4 | * By default, new Sails projects are configured to compile this file 5 | * from LESS to CSS. Unlike CSS files, LESS files are not compiled and 6 | * included automatically unless they are imported below. 7 | * 8 | * The LESS files imported below are compiled and included in the order 9 | * they are listed. Mixins, variables, etc. should be imported first 10 | * so that they can be accessed by subsequent LESS stylesheets. 11 | * 12 | * (Just like the rest of the asset pipeline bundled in Sails, you can 13 | * always omit, customize, or replace this behavior with SASS, SCSS, 14 | * or any other Grunt tasks you like.) 15 | */ 16 | 17 | 18 | 19 | // For example: 20 | // 21 | // @import 'variables/colors.less'; 22 | // @import 'mixins/foo.less'; 23 | // @import 'mixins/bar.less'; 24 | // @import 'mixins/baz.less'; 25 | // 26 | // @import 'styleguide.less'; 27 | // @import 'pages/login.less'; 28 | // @import 'pages/signup.less'; 29 | // 30 | // etc. 31 | -------------------------------------------------------------------------------- /assets/templates/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sails101/even-more-passport/e986742cbbd699a8abbacb08038d5d11506c6c09/assets/templates/.gitkeep -------------------------------------------------------------------------------- /config/blueprints.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Blueprint API Configuration 3 | * (sails.config.blueprints) 4 | * 5 | * These settings are for the global configuration of blueprint routes and 6 | * request options (which impact the behavior of blueprint actions). 7 | * 8 | * You may also override any of these settings on a per-controller basis 9 | * by defining a '_config' key in your controller defintion, and assigning it 10 | * a configuration object with overrides for the settings in this file. 11 | * 12 | * For more information on configuring the blueprint API, check out: 13 | * http://links.sailsjs.org/docs/config/blueprints 14 | */ 15 | 16 | module.exports.blueprints = { 17 | 18 | 19 | /** 20 | * NOTE: 21 | * A lot of the configuration options below affect so-called "CRUD methods", 22 | * or your controllers' `find`, `create`, `update`, and `destroy` actions. 23 | * 24 | * It's important to realize that, even if you haven't defined these yourself, as long as 25 | * a model exists with the same name as the controller, Sails will respond with built-in CRUD 26 | * logic in the form of a JSON API, including support for sort, pagination, and filtering. 27 | */ 28 | 29 | 30 | 31 | // Action blueprints speed up the backend development workflow by eliminating the need 32 | // to manually bind routes. When enabled, GET, POST, PUT, and DELETE routes will be 33 | // generated for every one of a controller's actions. 34 | // 35 | // If an `index` action exists, additional naked routes will be created for it. 36 | // Finally, all `actions` blueprints support an optional path parameter, `id`, for convenience. 37 | // 38 | // For example, assume we have an EmailController with actions `send` and `index`. 39 | // With `actions` enabled, the following blueprint routes would be bound at runtime: 40 | // 41 | // `EmailController.index` 42 | // ::::::::::::::::::::::::::::::::::::::::::::::::::::::: 43 | // `GET /email/:id?` `GET /email/index/:id?` 44 | // `POST /email/:id?` `POST /email/index/:id?` 45 | // `PUT /email/:id?` `PUT /email/index/:id?` 46 | // `DELETE /email/:id?` `DELETE /email/index/:id?` 47 | // 48 | // `EmailController.send` 49 | // ::::::::::::::::::::::::::::::::::::::::::::::::::::::: 50 | // `GET /email/send/:id?` 51 | // `POST /email/send/:id?` 52 | // `PUT /email/send/:id?` 53 | // `DELETE /email/send/:id?` 54 | // 55 | // 56 | // `actions` are enabled by default, and can be OK for production-- however, 57 | // if you'd like to continue to use controller/action autorouting in a production deployment, 58 | // you must take great care not to inadvertently expose unsafe/unintentional controller logic 59 | // to GET requests. 60 | actions: true, 61 | 62 | 63 | 64 | // RESTful Blueprints 65 | // (`sails.config.blueprints.rest`) 66 | // 67 | // REST blueprints are the automatically generated routes Sails uses to expose 68 | // a conventional REST API on top of a controller's `find`, `create`, `update`, and `destroy` 69 | // actions. 70 | // 71 | // For example, a BoatController with `rest` enabled generates the following routes: 72 | // ::::::::::::::::::::::::::::::::::::::::::::::::::::::: 73 | // GET /boat/:id? -> BoatController.find 74 | // POST /boat -> BoatController.create 75 | // PUT /boat/:id -> BoatController.update 76 | // DELETE /boat/:id -> BoatController.destroy 77 | // 78 | // `rest` blueprint routes are enabled by default, and are suitable for use 79 | // in a production scenario, as long you take standard security precautions 80 | // (combine w/ policies, etc.) 81 | rest: true, 82 | 83 | 84 | // Shortcut blueprints are simple helpers to provide access to a controller's CRUD methods 85 | // from your browser's URL bar. When enabled, GET, POST, PUT, and DELETE routes will be generated 86 | // for the controller's`find`, `create`, `update`, and `destroy` actions. 87 | // 88 | // `shortcuts` are enabled by default, but should be disabled in production. 89 | shortcuts: true, 90 | 91 | 92 | 93 | // An optional mount path for all blueprint routes on a controller, including `rest`, 94 | // `actions`, and `shortcuts`. This allows you to take advantage of blueprint routing, 95 | // even if you need to namespace your API methods. 96 | // 97 | // * (NOTE: This only applies to blueprint autoroutes, not manual routes from `sails.config.routes`) 98 | // 99 | // For example, `prefix: '/api/v2'` would make the following REST blueprint routes 100 | // for a FooController: 101 | // 102 | // `GET /api/v2/foo/:id?` 103 | // `POST /api/v2/foo` 104 | // `PUT /api/v2/foo/:id` 105 | // `DELETE /api/v2/foo/:id` 106 | // 107 | // By default, no prefix is used. 108 | prefix: '', 109 | 110 | 111 | 112 | // Whether to pluralize controller names in blueprint routes. 113 | // 114 | // (NOTE: This only applies to blueprint autoroutes, not manual routes from `sails.config.routes`) 115 | // 116 | // For example, REST blueprints for `FooController` with `pluralize` enabled: 117 | // GET /foos/:id? 118 | // POST /foos 119 | // PUT /foos/:id? 120 | // DELETE /foos/:id? 121 | pluralize: false 122 | 123 | }; 124 | -------------------------------------------------------------------------------- /config/bootstrap.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Bootstrap 3 | * (sails.config.bootstrap) 4 | * 5 | * An asynchronous bootstrap function that runs before your Sails app gets lifted. 6 | * This gives you an opportunity to set up your data model, run jobs, or perform some special logic. 7 | * 8 | * For more information on bootstrapping your app, check out: 9 | * http://links.sailsjs.org/docs/config/bootstrap 10 | */ 11 | 12 | module.exports.bootstrap = function(cb) { 13 | 14 | // It's very important to trigger this callack method when you are finished 15 | // with the bootstrap! (otherwise your server will never lift, since it's waiting on the bootstrap) 16 | sails.services.passport.loadStrategies(); 17 | cb(); 18 | }; 19 | -------------------------------------------------------------------------------- /config/connections.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Connections 3 | * (sails.config.connections) 4 | * 5 | * `Connections` are like "saved settings" for your adapters. What's the difference between 6 | * a connection and an adapter, you might ask? An adapter (e.g. `sails-mysql`) is generic-- 7 | * it needs some additional information to work (e.g. your database host, password, user, etc.) 8 | * A `connection` is that additional information. 9 | * 10 | * Each model must have a `connection` property (a string) which is references the name of one 11 | * of these connections. If it doesn't, the default `connection` configured in `config/models.js` 12 | * will be applied. Of course, a connection can (and usually is) shared by multiple models. 13 | * . 14 | * Note: If you're using version control, you should put your passwords/api keys 15 | * in `config/local.js`, environment variables, or use another strategy. 16 | * (this is to prevent you inadvertently sensitive credentials up to your repository.) 17 | * 18 | * For more information on configuration, check out: 19 | * http://links.sailsjs.org/docs/config/connections 20 | */ 21 | 22 | module.exports.connections = { 23 | 24 | flashDBServer: { 25 | adapter: 'sails-postgresql', 26 | host: 'localhost', 27 | user: 'postgres', 28 | password: 'postgres', 29 | database: 'sailsauth' 30 | } 31 | 32 | }; 33 | -------------------------------------------------------------------------------- /config/cors.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Cross-Origin Resource Sharing (CORS) Settings 3 | * (sails.config.cors) 4 | * 5 | * CORS is like a more modern version of JSONP-- it allows your server/API 6 | * to successfully respond to requests from client-side JavaScript code 7 | * running on some other domain (e.g. google.com) 8 | * Unlike JSONP, it works with POST, PUT, and DELETE requests 9 | * 10 | * For more information on CORS, check out: 11 | * http://en.wikipedia.org/wiki/Cross-origin_resource_sharing 12 | * 13 | * Note that any of these settings (besides 'allRoutes') can be changed on a per-route basis 14 | * by adding a "cors" object to the route configuration: 15 | * 16 | * '/get foo': { 17 | * controller: 'foo', 18 | * action: 'bar', 19 | * cors: { 20 | * origin: 'http://foobar.com,https://owlhoot.com' 21 | * } 22 | * } 23 | * 24 | */ 25 | 26 | module.exports.cors = { 27 | 28 | // Allow CORS on all routes by default? If not, you must enable CORS on a 29 | // per-route basis by either adding a "cors" configuration object 30 | // to the route config, or setting "cors:true" in the route config to 31 | // use the default settings below. 32 | allRoutes: false, 33 | 34 | // Which domains which are allowed CORS access? 35 | // This can be a comma-delimited list of hosts (beginning with http:// or https://) 36 | // or "*" to allow all domains CORS access. 37 | origin: '*', 38 | 39 | // Allow cookies to be shared for CORS requests? 40 | credentials: true, 41 | 42 | // Which methods should be allowed for CORS requests? This is only used 43 | // in response to preflight requests (see article linked above for more info) 44 | methods: 'GET, POST, PUT, DELETE, OPTIONS, HEAD', 45 | 46 | // Which headers should be allowed for CORS requests? This is only used 47 | // in response to preflight requests. 48 | headers: 'content-type' 49 | 50 | }; 51 | -------------------------------------------------------------------------------- /config/csrf.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Cross-Site Request Forgery Protection Settings 3 | * (sails.config.csrf) 4 | * 5 | * CSRF tokens are like a tracking chip. While a session tells the server that a user 6 | * "is who they say they are", a csrf token tells the server "you are where you say you are". 7 | * 8 | * When enabled, all non-GET requests to the Sails server must be accompanied by 9 | * a special token, identified as the '_csrf' parameter. 10 | * 11 | * This option protects your Sails app against cross-site request forgery (or CSRF) attacks. 12 | * A would-be attacker needs not only a user's session cookie, but also this timestamped, 13 | * secret CSRF token, which is refreshed/granted when the user visits a URL on your app's domain. 14 | * 15 | * This allows us to have certainty that our users' requests haven't been hijacked, 16 | * and that the requests they're making are intentional and legitimate. 17 | * 18 | * This token has a short-lived expiration timeline, and must be acquired by either: 19 | * 20 | * (a) For traditional view-driven web apps: 21 | * Fetching it from one of your views, where it may be accessed as 22 | * a local variable, e.g.: 23 | *
24 | * 25 | *
26 | * 27 | * or (b) For AJAX/Socket-heavy and/or single-page apps: 28 | * Sending a GET request to the `/csrfToken` route, where it will be returned 29 | * as JSON, e.g.: 30 | * { _csrf: 'ajg4JD(JGdajhLJALHDa' } 31 | * 32 | * 33 | * Enabling this option requires managing the token in your front-end app. 34 | * For traditional web apps, it's as easy as passing the data from a view into a form action. 35 | * In AJAX/Socket-heavy apps, just send a GET request to the /csrfToken route to get a valid token. 36 | * 37 | * For more information on CSRF, check out: 38 | * http://en.wikipedia.org/wiki/Cross-site_request_forgery 39 | */ 40 | 41 | module.exports.csrf = false; 42 | -------------------------------------------------------------------------------- /config/globals.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Global Variable Configuration 3 | * (sails.config.globals) 4 | * 5 | * Configure which global variables which will be exposed 6 | * automatically by Sails. 7 | * 8 | * For more information on configuration, check out: 9 | * http://links.sailsjs.org/docs/config/globals 10 | */ 11 | module.exports.globals = { 12 | _: true, 13 | async: true, 14 | sails: true, 15 | services: true, 16 | models: true 17 | }; 18 | -------------------------------------------------------------------------------- /config/http.js: -------------------------------------------------------------------------------- 1 | /** 2 | * HTTP Server Settings 3 | * (sails.config.http) 4 | * 5 | * Configuration for the underlying HTTP server in Sails. 6 | * Only applies to HTTP requests (not WebSockets) 7 | * 8 | * For more information on configuration, check out: 9 | * http://links.sailsjs.org/docs/config/http 10 | */ 11 | 12 | module.exports.http = { 13 | 14 | middleware: { 15 | 16 | // The order in which middleware should be run for HTTP request. 17 | // (the Sails router is invoked by the "router" middleware below.) 18 | order: [ 19 | 'startRequestTimer', 20 | 'cookieParser', 21 | 'session', 22 | 'bodyParser', 23 | 'handleBodyParserError', 24 | 'compress', 25 | 'methodOverride', 26 | 'poweredBy', 27 | '$custom', 28 | 'router', 29 | 'www', 30 | 'favicon', 31 | '404', 32 | '500' 33 | ], 34 | 35 | // The body parser that will handle incoming multipart HTTP requests. 36 | // By default as of v0.10, Sails uses [skipper](http://github.com/balderdashy/skipper). 37 | // See http://www.senchalabs.org/connect/multipart.html for other options. 38 | // bodyParser: require('skipper') 39 | 40 | }, 41 | 42 | // The number of seconds to cache flat files on disk being served by 43 | // Express static middleware (by default, these files are in `.tmp/public`) 44 | // 45 | // The HTTP static cache is only active in a 'production' environment, 46 | // since that's the only time Express will cache flat-files. 47 | cache: 31557600000 48 | }; 49 | -------------------------------------------------------------------------------- /config/i18n.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Internationalization / Localization Settings 3 | * (sails.config.i18n) 4 | * 5 | * If your app will touch people from all over the world, i18n (or internationalization) 6 | * may be an important part of your international strategy. 7 | * 8 | * 9 | * For more information, check out: 10 | * http://links.sailsjs.org/docs/config/i18n 11 | */ 12 | 13 | module.exports.i18n = { 14 | 15 | // Which locales are supported? 16 | locales: ['en', 'es', 'fr', 'de'] 17 | 18 | }; 19 | -------------------------------------------------------------------------------- /config/locales/_README.md: -------------------------------------------------------------------------------- 1 | # Internationalization / Localization Settings 2 | 3 | > Also see the official docs on internationalization/localization: 4 | > http://links.sailsjs.org/docs/config/locales 5 | 6 | ## Locales 7 | All locale files live under `config/locales`. Here is where you can add translations 8 | as JSON key-value pairs. The name of the file should match the language that you are supporting, which allows for automatic language detection based on request headers. 9 | 10 | Here is an example locale stringfile for the Spanish language (`config/locales/es.json`): 11 | ```json 12 | { 13 | "Hello!": "Hola!", 14 | "Hello %s, how are you today?": "¿Hola %s, como estas?", 15 | } 16 | ``` 17 | ## Usage 18 | Locales can be accessed in controllers/policies through `res.i18n()`, or in views through the `__(key)` or `i18n(key)` functions. 19 | Remember that the keys are case sensitive and require exact key matches, e.g. 20 | 21 | ```ejs 22 |

<%= __('Welcome to PencilPals!') %>

23 |

<%= i18n('Hello %s, how are you today?', 'Pencil Maven') %>

24 |

<%= i18n('That\'s right-- you can use either i18n() or __()') %>

25 | ``` 26 | 27 | ## Configuration 28 | Localization/internationalization config can be found in `config/i18n.js`, from where you can set your supported locales. 29 | -------------------------------------------------------------------------------- /config/locales/de.json: -------------------------------------------------------------------------------- 1 | { 2 | "Welcome": "Wilkommen" 3 | } -------------------------------------------------------------------------------- /config/locales/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "Welcome": "Welcome" 3 | } 4 | -------------------------------------------------------------------------------- /config/locales/es.json: -------------------------------------------------------------------------------- 1 | { 2 | "Welcome": "Bienvenido" 3 | } 4 | -------------------------------------------------------------------------------- /config/locales/fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "Welcome": "Bienvenue" 3 | } 4 | -------------------------------------------------------------------------------- /config/log.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Built-in Log Configuration 3 | * (sails.config.log) 4 | * 5 | * Configure the log level for your app, as well as the transport 6 | * (Underneath the covers, Sails uses Winston for logging, which 7 | * allows for some pretty neat custom transports/adapters for log messages) 8 | * 9 | * For more information on the Sails logger, check out: 10 | * http://links.sailsjs.org/docs/config/log 11 | */ 12 | 13 | module.exports = { 14 | 15 | // Valid `level` configs: 16 | // i.e. the minimum log level to capture with sails.log.*() 17 | // 18 | // 'error' : Display calls to `.error()` 19 | // 'warn' : Display calls from `.error()` to `.warn()` 20 | // 'debug' : Display calls from `.error()`, `.warn()` to `.debug()` 21 | // 'info' : Display calls from `.error()`, `.warn()`, `.debug()` to `.info()` 22 | // 'verbose': Display calls from `.error()`, `.warn()`, `.debug()`, `.info()` to `.verbose()` 23 | // 24 | log: { 25 | level: 'info' 26 | } 27 | 28 | }; 29 | -------------------------------------------------------------------------------- /config/models.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Default model configuration 3 | * (sails.config.models) 4 | * 5 | * Unless you override them, the following properties will be included 6 | * in each of your models. 7 | */ 8 | 9 | module.exports.models = { 10 | 11 | // Your app's default connection. 12 | // i.e. the name of one of your app's connections (see `config/connections.js`) 13 | // 14 | // (defaults to localDiskDb) 15 | connection: 'flashDBServer', 16 | migrate: 'alter' 17 | 18 | }; 19 | -------------------------------------------------------------------------------- /config/passport.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Passport configuration 3 | * 4 | * This if the configuration for your Passport.js setup and it where you'd 5 | * define the authentication strategies you want your application to employ. 6 | * 7 | * I have tested the service with all of the providers listed below - if you 8 | * come across a provider that for some reason doesn't work, feel free to open 9 | * an issue on GitHub. 10 | * 11 | * Also, authentication scopes can be set through the `scope` property. 12 | * 13 | * For more information on the available providers, check out: 14 | * http://passportjs.org/guide/providers/ 15 | */ 16 | 17 | module.exports.passport = { 18 | 19 | local: { 20 | strategy: require('passport-local').Strategy 21 | }, 22 | bearer: { 23 | strategy: require('passport-http-bearer').Strategy 24 | } 25 | 26 | }; 27 | -------------------------------------------------------------------------------- /config/policies.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Policy Mappings 3 | * (sails.config.policies) 4 | * 5 | * Policies are simple functions which run **before** your controllers. 6 | * You can apply one or more policies to a given controller, or protect 7 | * its actions individually. 8 | * 9 | * Any policy file (e.g. `api/policies/authenticated.js`) can be accessed 10 | * below by its filename, minus the extension, (e.g. "authenticated") 11 | * 12 | * For more information on configuring policies, check out: 13 | * http://sailsjs.org/#!documentation/ 14 | */ 15 | 16 | 17 | module.exports.policies = { 18 | 19 | // Default policy for all controllers and actions 20 | // (`true` allows public access) 21 | '*': ['passport', 'sessionAuth'], 22 | 23 | 'flash': { 24 | 'remoteHome': ['passport', 'bearerAuth'] 25 | }, 26 | 27 | 'auth': { 28 | '*': ['passport'] 29 | } 30 | 31 | // Here's an example of mapping some policies to run before 32 | // a controller and its actions 33 | // RabbitController: { 34 | 35 | // Apply the `false` policy as the default for all of RabbitController's actions 36 | // (`false` prevents all access, which ensures that nothing bad happens to our rabbits) 37 | // '*': false, 38 | 39 | // For the action `nurture`, apply the 'isRabbitMother' policy 40 | // (this overrides `false` above) 41 | // nurture : 'isRabbitMother', 42 | 43 | // Apply the `isNiceToAnimals` AND `hasRabbitFood` policies 44 | // before letting any users feed our rabbits 45 | // feed : ['isNiceToAnimals', 'hasRabbitFood'] 46 | // } 47 | }; 48 | -------------------------------------------------------------------------------- /config/routes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Route Mappings 3 | * (sails.config.routes) 4 | * 5 | * Your routes map URLs to views and controllers. 6 | * 7 | * If Sails receives a URL that doesn't match any of the routes below, 8 | * it will check for matching files (images, scripts, stylesheets, etc.) 9 | * in your assets directory. e.g. `http://localhost:1337/images/foo.jpg` 10 | * might match an image file: `/assets/images/foo.jpg` 11 | * 12 | * Finally, if those don't match either, the default 404 handler is triggered. 13 | * See `config/404.js` to adjust your app's 404 logic. 14 | * 15 | * Note: Sails doesn't ACTUALLY serve stuff from `assets`-- the default Gruntfile in Sails copies 16 | * flat files from `assets` to `.tmp/public`. This allows you to do things like compile LESS or 17 | * CoffeeScript for the front-end. 18 | * 19 | * For more information on routes, check out: 20 | * http://links.sailsjs.org/docs/config/routes 21 | */ 22 | 23 | module.exports.routes = { 24 | 25 | 26 | // Make the view located at `views/homepage.ejs` (or `views/homepage.jade`, etc. depending on your 27 | // default view engine) your home page. 28 | // 29 | // (Alternatively, remove this and add an `index.html` file in your `assets` directory) 30 | '/': 'FlashController.home', 31 | 32 | 'get /login': 'AuthController.login', 33 | 'get /logout': 'AuthController.logout', 34 | 'get /register': 'AuthController.register', 35 | 36 | 'post /auth/local': 'AuthController.callback', 37 | 'post /auth/local/:action': 'AuthController.callback', 38 | 39 | 'get /auth/:provider': 'AuthController.provider', 40 | 'get /auth/:provider/callback': 'AuthController.callback', 41 | 'get /auth/:provider/:action': 'AuthController.callback' 42 | 43 | // Custom routes here... 44 | 45 | 46 | // If a request to a URL doesn't match any of the custom routes above, 47 | // it is matched against Sails route blueprints. See `config/blueprints.js` 48 | // for configuration options and examples. 49 | 50 | }; 51 | -------------------------------------------------------------------------------- /config/session.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Session Configuration 3 | * (sails.config.session) 4 | * 5 | * Sails session integration leans heavily on the great work already done by 6 | * Express, but also unifies Socket.io with the Connect session store. It uses 7 | * Connect's cookie parser to normalize configuration differences between Express 8 | * and Socket.io and hooks into Sails' middleware interpreter to allow you to access 9 | * and auto-save to `req.session` with Socket.io the same way you would with Express. 10 | * 11 | * For more information on configuring the session, check out: 12 | * http://links.sailsjs.org/docs/config/session 13 | */ 14 | 15 | module.exports.session = { 16 | 17 | // Session secret is automatically generated when your new app is created 18 | // Replace at your own risk in production-- you will invalidate the cookies of your users, 19 | // forcing them to log in again. 20 | secret: 'c08c7ad7fdd07e95c8b86b9292b6f0d9', 21 | 22 | 23 | // Set the session cookie expire time 24 | // The maxAge is set by milliseconds, the example below is for 24 hours 25 | // 26 | // cookie: { 27 | // maxAge: 24 * 60 * 60 * 1000 28 | // } 29 | 30 | 31 | // In production, uncomment the following lines to set up a shared redis session store 32 | // that can be shared across multiple Sails.js servers 33 | // adapter: 'redis', 34 | // 35 | // The following values are optional, if no options are set a redis instance running 36 | // on localhost is expected. 37 | // Read more about options at: https://github.com/visionmedia/connect-redis 38 | // 39 | // host: 'localhost', 40 | // port: 6379, 41 | // ttl: , 42 | // db: 0, 43 | // pass: 44 | // prefix: 'sess:' 45 | 46 | 47 | // Uncomment the following lines to use your Mongo adapter as a session store 48 | // adapter: 'mongo', 49 | // 50 | // host: 'localhost', 51 | // port: 27017, 52 | // db: 'sails', 53 | // collection: 'sessions', 54 | // 55 | // Optional Values: 56 | // 57 | // # Note: url will override other connection settings 58 | // url: 'mongodb://user:pass@host:port/database/collection', 59 | // 60 | // username: '', 61 | // password: '', 62 | // auto_reconnect: false, 63 | // ssl: false, 64 | // stringify: true 65 | 66 | }; 67 | -------------------------------------------------------------------------------- /config/sockets.js: -------------------------------------------------------------------------------- 1 | /** 2 | * WebSocket Server Settings 3 | * (sails.config.sockets) 4 | * 5 | * These settings provide transparent access to the options for Sails' 6 | * encapsulated WebSocket server, as well as some additional Sails-specific 7 | * configuration layered on top. 8 | * 9 | * For more information on sockets configuration, including advanced config options, see: 10 | * http://sailsjs.org/#!/documentation/reference/sails.config/sails.config.sockets.html 11 | */ 12 | 13 | module.exports.sockets = { 14 | 15 | 16 | /*************************************************************************** 17 | * * 18 | * Node.js (and consequently Sails.js) apps scale horizontally. It's a * 19 | * powerful, efficient approach, but it involves a tiny bit of planning. At * 20 | * scale, you'll want to be able to copy your app onto multiple Sails.js * 21 | * servers and throw them behind a load balancer. * 22 | * * 23 | * One of the big challenges of scaling an application is that these sorts * 24 | * of clustered deployments cannot share memory, since they are on * 25 | * physically different machines. On top of that, there is no guarantee * 26 | * that a user will "stick" with the same server between requests (whether * 27 | * HTTP or sockets), since the load balancer will route each request to the * 28 | * Sails server with the most available resources. However that means that * 29 | * all room/pubsub/socket processing and shared memory has to be offloaded * 30 | * to a shared, remote messaging queue (usually Redis) * 31 | * * 32 | * Luckily, Socket.io (and consequently Sails.js) apps support Redis for * 33 | * sockets by default. To enable a remote redis pubsub server, uncomment * 34 | * the config below. * 35 | * * 36 | * Worth mentioning is that, if `adapter` config is `redis`, but host/port * 37 | * is left unset, Sails will try to connect to redis running on localhost * 38 | * via port 6379 * 39 | * * 40 | ***************************************************************************/ 41 | // adapter: 'memory', 42 | 43 | // 44 | // -OR- 45 | // 46 | 47 | // adapter: 'redis', 48 | // host: '127.0.0.1', 49 | // port: 6379, 50 | // db: 'sails', 51 | // pass: '', 52 | 53 | 54 | 55 | /*************************************************************************** 56 | * * 57 | * Whether to expose a 'get /__getcookie' route with CORS support that sets * 58 | * a cookie (this is used by the sails.io.js socket client to get access to * 59 | * a 3rd party cookie and to enable sessions). * 60 | * * 61 | * Warning: Currently in this scenario, CORS settings apply to interpreted * 62 | * requests sent via a socket.io connection that used this cookie to * 63 | * connect, even for non-browser clients! (e.g. iOS apps, toasters, node.js * 64 | * unit tests) * 65 | * * 66 | ***************************************************************************/ 67 | 68 | // grant3rdPartyCookie: true, 69 | 70 | 71 | 72 | /*************************************************************************** 73 | * * 74 | * `beforeConnect` * 75 | * * 76 | * This custom beforeConnect function will be run each time BEFORE a new * 77 | * socket is allowed to connect, when the initial socket.io handshake is * 78 | * performed with the server. * 79 | * * 80 | * By default, when a socket tries to connect, Sails allows it, every time. * 81 | * (much in the same way any HTTP request is allowed to reach your routes. * 82 | * If no valid cookie was sent, a temporary session will be created for the * 83 | * connecting socket. * 84 | * * 85 | * If the cookie sent as part of the connection request doesn't match any * 86 | * known user session, a new user session is created for it. * 87 | * * 88 | * In most cases, the user would already have a cookie since they loaded * 89 | * the socket.io client and the initial HTML page you're building. * 90 | * * 91 | * However, in the case of cross-domain requests, it is possible to receive * 92 | * a connection upgrade request WITHOUT A COOKIE (for certain transports) * 93 | * In this case, there is no way to keep track of the requesting user * 94 | * between requests, since there is no identifying information to link * 95 | * him/her with a session. The sails.io.js client solves this by connecting * 96 | * to a CORS/jsonp endpoint first to get a 3rd party cookie(fortunately this* 97 | * works, even in Safari), then opening the connection. * 98 | * * 99 | * You can also pass along a ?cookie query parameter to the upgrade url, * 100 | * which Sails will use in the absence of a proper cookie e.g. (when * 101 | * connecting from the client): * 102 | * io.sails.connect('http://localhost:1337?cookie=smokeybear') * 103 | * * 104 | * Finally note that the user's cookie is NOT (and will never be) accessible* 105 | * from client-side javascript. Using HTTP-only cookies is crucial for your * 106 | * app's security. * 107 | * * 108 | ***************************************************************************/ 109 | // beforeConnect: function(handshake, cb) { 110 | // // `true` allows the connection 111 | // return cb(null, true); 112 | // 113 | // // (`false` would reject the connection) 114 | // }, 115 | 116 | 117 | /*************************************************************************** 118 | * * 119 | * `afterDisconnect` * 120 | * * 121 | * This custom afterDisconnect function will be run each time a socket * 122 | * disconnects * 123 | * * 124 | ***************************************************************************/ 125 | // afterDisconnect: function(session, socket, cb) { 126 | // // By default: do nothing. 127 | // return cb(); 128 | // }, 129 | 130 | /*************************************************************************** 131 | * * 132 | * `transports` * 133 | * * 134 | * A array of allowed transport methods which the clients will try to use. * 135 | * On server environments that don't support sticky sessions, the "polling" * 136 | * transport should be disabled. * 137 | * * 138 | ***************************************************************************/ 139 | // transports: ["polling", "websocket"] 140 | 141 | }; 142 | -------------------------------------------------------------------------------- /config/views.js: -------------------------------------------------------------------------------- 1 | /** 2 | * View Engine Configuration 3 | * (sails.config.views) 4 | * 5 | * Server-sent views are a classic and effective way to get your app up 6 | * and running. Views are normally served from controllers. Below, you can 7 | * configure your templating language/framework of choice and configure 8 | * Sails' layout support. 9 | * 10 | * For more information on views and layouts, check out: 11 | * http://links.sailsjs.org/docs/config/views 12 | */ 13 | 14 | module.exports.views = { 15 | 16 | // View engine (aka template language) 17 | // to use for your app's *server-side* views 18 | // 19 | // Sails+Express supports all view engines which implement 20 | // TJ Holowaychuk's `consolidate.js`, including, but not limited to: 21 | // 22 | // ejs, jade, handlebars, mustache 23 | // underscore, hogan, haml, haml-coffee, dust 24 | // atpl, eco, ect, jazz, jqtpl, JUST, liquor, QEJS, 25 | // swig, templayed, toffee, walrus, & whiskers 26 | 27 | // For more options, check out the docs: 28 | // https://github.com/balderdashy/sails-wiki/blob/0.9/config.views.md#engine 29 | 30 | engine: 'ejs', 31 | 32 | 33 | 34 | // Layouts are simply top-level HTML templates you can use as wrappers 35 | // for your server-side views. If you're using ejs or jade, you can take advantage of 36 | // Sails' built-in `layout` support. 37 | // 38 | // When using a layout, when one of your views is served, it is injected into 39 | // the `body` partial defined in the layout. This lets you reuse header 40 | // and footer logic between views. 41 | // 42 | // NOTE: Layout support is only implemented for the `ejs` view engine! 43 | // For most other engines, it is not necessary, since they implement 44 | // partials/layouts themselves. In those cases, this config will be silently 45 | // ignored. 46 | // 47 | // The `layout` setting may be set to one of the following: 48 | // 49 | // If `false`, layouts will be disabled. 50 | // Otherwise, if a string is specified, it will be interpreted as the relative path 51 | // to your layout file from `views/` folder. (the file extension, ".ejs", should be omitted) 52 | // 53 | 54 | layout: 'layout' 55 | 56 | 57 | 58 | // Using Multiple Layouts with EJS 59 | // 60 | // If you're using the default engine, `ejs`, Sails supports the use of multiple 61 | // `layout` files. To take advantage of this, before rendering a view, override 62 | // the `layout` local in your controller by setting `res.locals.layout`. 63 | // (this is handy if you parts of your app's UI look completely different from each other) 64 | // 65 | // e.g. your default might be 66 | // layout: 'layouts/public' 67 | // 68 | // But you might override that in some of your controllers with: 69 | // layout: 'layouts/internal' 70 | 71 | 72 | }; 73 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sails-authentication-and-authorization", 3 | "version": "0.0.0", 4 | "description": "An example with Passport.js to add a user authentication and API authorization using bearer tokens", 5 | "keywords": [], 6 | "dependencies": { 7 | "bcryptjs": "^2.1.0", 8 | "ejs": "~0.8.4", 9 | "grunt": "0.4.2", 10 | "grunt-cli": "^0.1.13", 11 | "grunt-contrib-clean": "~0.5.0", 12 | "grunt-contrib-coffee": "~0.10.1", 13 | "grunt-contrib-concat": "~0.3.0", 14 | "grunt-contrib-copy": "~0.5.0", 15 | "grunt-contrib-cssmin": "~0.9.0", 16 | "grunt-contrib-jst": "~0.6.0", 17 | "grunt-contrib-less": "~0.11.1", 18 | "grunt-contrib-uglify": "~0.4.0", 19 | "grunt-contrib-watch": "~0.5.3", 20 | "grunt-sails-linker": "~0.9.5", 21 | "grunt-sync": "~0.0.4", 22 | "include-all": "~0.1.3", 23 | "passport": "^0.2.1", 24 | "passport-http-bearer": "^1.0.1", 25 | "passport-local": "^1.0.0", 26 | "rc": "~0.5.0", 27 | "sails": "~0.11.0", 28 | "sails-disk": "~0.10.0", 29 | "sails-generate-auth": "^0.2.0", 30 | "sails-postgresql": "^0.10.9", 31 | "validator": "^3.24.0" 32 | }, 33 | "scripts": { 34 | "start": "node app.js", 35 | "debug": "node debug app.js" 36 | }, 37 | "main": "app.js", 38 | "repository": { 39 | "type": "git", 40 | "url": "git://github.com/sails101/contribute-to-sails101.git" 41 | }, 42 | "author": "yedhukrishnan", 43 | "license": "MIT" 44 | } 45 | -------------------------------------------------------------------------------- /tasks/README.md: -------------------------------------------------------------------------------- 1 | # About the `tasks` folder 2 | 3 | The `tasks` directory is a suite of Grunt tasks and their configurations, bundled for your convenience. The Grunt integration is mainly useful for bundling front-end assets, (like stylesheets, scripts, & markup templates) but it can also be used to run all kinds of development tasks, from browserify compilation to database migrations. 4 | 5 | If you haven't used [Grunt](http://gruntjs.com/) before, be sure to check out the [Getting Started](http://gruntjs.com/getting-started) guide, as it explains how to create a [Gruntfile](http://gruntjs.com/sample-gruntfile) as well as install and use Grunt plugins. Once you're familiar with that process, read on! 6 | 7 | 8 | ### How does this work? 9 | 10 | The asset pipeline bundled in Sails is a set of Grunt tasks configured with conventional defaults designed to make your project more consistent and productive. 11 | 12 | The entire front-end asset workflow in Sails is completely customizable-- while it provides some suggestions out of the box, Sails makes no pretense that it can anticipate all of the needs you'll encounter building the browser-based/front-end portion of your application. Who's to say you're even building an app for a browser? 13 | 14 | 15 | 16 | ### What tasks does Sails run automatically? 17 | 18 | Sails runs some of these tasks (the ones in the `tasks/register` folder) automatically when you run certain commands. 19 | 20 | ###### `sails lift` 21 | 22 | Runs the `default` task (`tasks/register/default.js`). 23 | 24 | ###### `sails lift --prod` 25 | 26 | Runs the `prod` task (`tasks/register/prod.js`). 27 | 28 | ###### `sails www` 29 | 30 | Runs the `build` task (`tasks/register/build.js`). 31 | 32 | ###### `sails www --prod` (production) 33 | 34 | Runs the `buildProd` task (`tasks/register/buildProd.js`). 35 | 36 | 37 | ### Can I customize this for SASS, Angular, client-side Jade templates, etc? 38 | 39 | You can modify, omit, or replace any of these Grunt tasks to fit your requirements. You can also add your own Grunt tasks- just add a `someTask.js` file in the `grunt/config` directory to configure the new task, then register it with the appropriate parent task(s) (see files in `grunt/register/*.js`). 40 | 41 | 42 | ### Do I have to use Grunt? 43 | 44 | Nope! To disable Grunt integration in Sails, just delete your Gruntfile or disable the Grunt hook. 45 | 46 | 47 | ### What if I'm not building a web frontend? 48 | 49 | That's ok! A core tenant of Sails is client-agnosticism-- it's especially designed for building APIs used by all sorts of clients; native Android/iOS/Cordova, serverside SDKs, etc. 50 | 51 | You can completely disable Grunt by following the instructions above. 52 | 53 | If you still want to use Grunt for other purposes, but don't want any of the default web front-end stuff, just delete your project's `assets` folder and remove the front-end oriented tasks from the `grunt/register` and `grunt/config` folders. You can also run `sails new myCoolApi --no-frontend` to omit the `assets` folder and front-end-oriented Grunt tasks for future projects. You can also replace your `sails-generate-frontend` module with alternative community generators, or create your own. This allows `sails new` to create the boilerplate for native iOS apps, Android apps, Cordova apps, SteroidsJS apps, etc. 54 | 55 | -------------------------------------------------------------------------------- /tasks/config/clean.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Clean files and folders. 3 | * 4 | * --------------------------------------------------------------- 5 | * 6 | * This grunt task is configured to clean out the contents in the .tmp/public of your 7 | * sails project. 8 | * 9 | * For usage docs see: 10 | * https://github.com/gruntjs/grunt-contrib-clean 11 | */ 12 | module.exports = function(grunt) { 13 | 14 | grunt.config.set('clean', { 15 | dev: ['.tmp/public/**'], 16 | build: ['www'] 17 | }); 18 | 19 | grunt.loadNpmTasks('grunt-contrib-clean'); 20 | }; 21 | -------------------------------------------------------------------------------- /tasks/config/coffee.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Compile CoffeeScript files to JavaScript. 3 | * 4 | * --------------------------------------------------------------- 5 | * 6 | * Compiles coffeeScript files from `assest/js` into Javascript and places them into 7 | * `.tmp/public/js` directory. 8 | * 9 | * For usage docs see: 10 | * https://github.com/gruntjs/grunt-contrib-coffee 11 | */ 12 | module.exports = function(grunt) { 13 | 14 | grunt.config.set('coffee', { 15 | dev: { 16 | options: { 17 | bare: true, 18 | sourceMap: true, 19 | sourceRoot: './' 20 | }, 21 | files: [{ 22 | expand: true, 23 | cwd: 'assets/js/', 24 | src: ['**/*.coffee'], 25 | dest: '.tmp/public/js/', 26 | ext: '.js' 27 | }, { 28 | expand: true, 29 | cwd: 'assets/js/', 30 | src: ['**/*.coffee'], 31 | dest: '.tmp/public/js/', 32 | ext: '.js' 33 | }] 34 | } 35 | }); 36 | 37 | grunt.loadNpmTasks('grunt-contrib-coffee'); 38 | }; 39 | -------------------------------------------------------------------------------- /tasks/config/concat.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Concatenate files. 3 | * 4 | * --------------------------------------------------------------- 5 | * 6 | * Concatenates files javascript and css from a defined array. Creates concatenated files in 7 | * .tmp/public/contact directory 8 | * [concat](https://github.com/gruntjs/grunt-contrib-concat) 9 | * 10 | * For usage docs see: 11 | * https://github.com/gruntjs/grunt-contrib-concat 12 | */ 13 | module.exports = function(grunt) { 14 | 15 | grunt.config.set('concat', { 16 | js: { 17 | src: require('../pipeline').jsFilesToInject, 18 | dest: '.tmp/public/concat/production.js' 19 | }, 20 | css: { 21 | src: require('../pipeline').cssFilesToInject, 22 | dest: '.tmp/public/concat/production.css' 23 | } 24 | }); 25 | 26 | grunt.loadNpmTasks('grunt-contrib-concat'); 27 | }; 28 | -------------------------------------------------------------------------------- /tasks/config/copy.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copy files and folders. 3 | * 4 | * --------------------------------------------------------------- 5 | * 6 | * # dev task config 7 | * Copies all directories and files, exept coffescript and less fiels, from the sails 8 | * assets folder into the .tmp/public directory. 9 | * 10 | * # build task config 11 | * Copies all directories nd files from the .tmp/public directory into a www directory. 12 | * 13 | * For usage docs see: 14 | * https://github.com/gruntjs/grunt-contrib-copy 15 | */ 16 | module.exports = function(grunt) { 17 | 18 | grunt.config.set('copy', { 19 | dev: { 20 | files: [{ 21 | expand: true, 22 | cwd: './assets', 23 | src: ['**/*.!(coffee|less)'], 24 | dest: '.tmp/public' 25 | }] 26 | }, 27 | build: { 28 | files: [{ 29 | expand: true, 30 | cwd: '.tmp/public', 31 | src: ['**/*'], 32 | dest: 'www' 33 | }] 34 | } 35 | }); 36 | 37 | grunt.loadNpmTasks('grunt-contrib-copy'); 38 | }; 39 | -------------------------------------------------------------------------------- /tasks/config/cssmin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Compress CSS files. 3 | * 4 | * --------------------------------------------------------------- 5 | * 6 | * Minifies css files and places them into .tmp/public/min directory. 7 | * 8 | * For usage docs see: 9 | * https://github.com/gruntjs/grunt-contrib-cssmin 10 | */ 11 | module.exports = function(grunt) { 12 | 13 | grunt.config.set('cssmin', { 14 | dist: { 15 | src: ['.tmp/public/concat/production.css'], 16 | dest: '.tmp/public/min/production.min.css' 17 | } 18 | }); 19 | 20 | grunt.loadNpmTasks('grunt-contrib-cssmin'); 21 | }; 22 | -------------------------------------------------------------------------------- /tasks/config/jst.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Precompiles Underscore templates to a `.jst` file. 3 | * 4 | * --------------------------------------------------------------- 5 | * 6 | * (i.e. basically it takes HTML files and turns them into tiny little 7 | * javascript functions that you pass data to and return HTML. This can 8 | * speed up template rendering on the client, and reduce bandwidth usage.) 9 | * 10 | * For usage docs see: 11 | * https://github.com/gruntjs/grunt-contrib-jst 12 | * 13 | */ 14 | 15 | module.exports = function(grunt) { 16 | 17 | var templateFilesToInject = [ 18 | 'templates/**/*.html' 19 | ]; 20 | 21 | grunt.config.set('jst', { 22 | dev: { 23 | 24 | // To use other sorts of templates, specify a regexp like the example below: 25 | // options: { 26 | // templateSettings: { 27 | // interpolate: /\{\{(.+?)\}\}/g 28 | // } 29 | // }, 30 | 31 | // Note that the interpolate setting above is simply an example of overwriting lodash's 32 | // default interpolation. If you want to parse templates with the default _.template behavior 33 | // (i.e. using
), there's no need to overwrite `templateSettings.interpolate`. 34 | 35 | 36 | files: { 37 | // e.g. 38 | // 'relative/path/from/gruntfile/to/compiled/template/destination' : ['relative/path/to/sourcefiles/**/*.html'] 39 | '.tmp/public/jst.js': require('../pipeline').templateFilesToInject 40 | } 41 | } 42 | }); 43 | 44 | grunt.loadNpmTasks('grunt-contrib-jst'); 45 | }; 46 | -------------------------------------------------------------------------------- /tasks/config/less.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Compiles LESS files into CSS. 3 | * 4 | * --------------------------------------------------------------- 5 | * 6 | * Only the `assets/styles/importer.less` is compiled. 7 | * This allows you to control the ordering yourself, i.e. import your 8 | * dependencies, mixins, variables, resets, etc. before other stylesheets) 9 | * 10 | * For usage docs see: 11 | * https://github.com/gruntjs/grunt-contrib-less 12 | */ 13 | module.exports = function(grunt) { 14 | 15 | grunt.config.set('less', { 16 | dev: { 17 | files: [{ 18 | expand: true, 19 | cwd: 'assets/styles/', 20 | src: ['importer.less'], 21 | dest: '.tmp/public/styles/', 22 | ext: '.css' 23 | }] 24 | } 25 | }); 26 | 27 | grunt.loadNpmTasks('grunt-contrib-less'); 28 | }; 29 | -------------------------------------------------------------------------------- /tasks/config/sails-linker.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Autoinsert script tags (or other filebased tags) in an html file. 3 | * 4 | * --------------------------------------------------------------- 5 | * 6 | * Automatically inject ', 22 | appRoot: '.tmp/public' 23 | }, 24 | files: { 25 | '.tmp/public/**/*.html': require('../pipeline').jsFilesToInject, 26 | 'views/**/*.html': require('../pipeline').jsFilesToInject, 27 | 'views/**/*.ejs': require('../pipeline').jsFilesToInject 28 | } 29 | }, 30 | 31 | devJsRelative: { 32 | options: { 33 | startTag: '', 34 | endTag: '', 35 | fileTmpl: '', 36 | appRoot: '.tmp/public', 37 | relative: true 38 | }, 39 | files: { 40 | '.tmp/public/**/*.html': require('../pipeline').jsFilesToInject, 41 | 'views/**/*.html': require('../pipeline').jsFilesToInject, 42 | 'views/**/*.ejs': require('../pipeline').jsFilesToInject 43 | } 44 | }, 45 | 46 | prodJs: { 47 | options: { 48 | startTag: '', 49 | endTag: '', 50 | fileTmpl: '', 51 | appRoot: '.tmp/public' 52 | }, 53 | files: { 54 | '.tmp/public/**/*.html': ['.tmp/public/min/production.min.js'], 55 | 'views/**/*.html': ['.tmp/public/min/production.min.js'], 56 | 'views/**/*.ejs': ['.tmp/public/min/production.min.js'] 57 | } 58 | }, 59 | 60 | prodJsRelative: { 61 | options: { 62 | startTag: '', 63 | endTag: '', 64 | fileTmpl: '', 65 | appRoot: '.tmp/public', 66 | relative: true 67 | }, 68 | files: { 69 | '.tmp/public/**/*.html': ['.tmp/public/min/production.min.js'], 70 | 'views/**/*.html': ['.tmp/public/min/production.min.js'], 71 | 'views/**/*.ejs': ['.tmp/public/min/production.min.js'] 72 | } 73 | }, 74 | 75 | devStyles: { 76 | options: { 77 | startTag: '', 78 | endTag: '', 79 | fileTmpl: '', 80 | appRoot: '.tmp/public' 81 | }, 82 | 83 | files: { 84 | '.tmp/public/**/*.html': require('../pipeline').cssFilesToInject, 85 | 'views/**/*.html': require('../pipeline').cssFilesToInject, 86 | 'views/**/*.ejs': require('../pipeline').cssFilesToInject 87 | } 88 | }, 89 | 90 | devStylesRelative: { 91 | options: { 92 | startTag: '', 93 | endTag: '', 94 | fileTmpl: '', 95 | appRoot: '.tmp/public', 96 | relative: true 97 | }, 98 | 99 | files: { 100 | '.tmp/public/**/*.html': require('../pipeline').cssFilesToInject, 101 | 'views/**/*.html': require('../pipeline').cssFilesToInject, 102 | 'views/**/*.ejs': require('../pipeline').cssFilesToInject 103 | } 104 | }, 105 | 106 | prodStyles: { 107 | options: { 108 | startTag: '', 109 | endTag: '', 110 | fileTmpl: '', 111 | appRoot: '.tmp/public' 112 | }, 113 | files: { 114 | '.tmp/public/index.html': ['.tmp/public/min/production.min.css'], 115 | 'views/**/*.html': ['.tmp/public/min/production.min.css'], 116 | 'views/**/*.ejs': ['.tmp/public/min/production.min.css'] 117 | } 118 | }, 119 | 120 | prodStylesRelative: { 121 | options: { 122 | startTag: '', 123 | endTag: '', 124 | fileTmpl: '', 125 | appRoot: '.tmp/public', 126 | relative: true 127 | }, 128 | files: { 129 | '.tmp/public/index.html': ['.tmp/public/min/production.min.css'], 130 | 'views/**/*.html': ['.tmp/public/min/production.min.css'], 131 | 'views/**/*.ejs': ['.tmp/public/min/production.min.css'] 132 | } 133 | }, 134 | 135 | // Bring in JST template object 136 | devTpl: { 137 | options: { 138 | startTag: '', 139 | endTag: '', 140 | fileTmpl: '', 141 | appRoot: '.tmp/public' 142 | }, 143 | files: { 144 | '.tmp/public/index.html': ['.tmp/public/jst.js'], 145 | 'views/**/*.html': ['.tmp/public/jst.js'], 146 | 'views/**/*.ejs': ['.tmp/public/jst.js'] 147 | } 148 | }, 149 | 150 | devJsJade: { 151 | options: { 152 | startTag: '// SCRIPTS', 153 | endTag: '// SCRIPTS END', 154 | fileTmpl: 'script(src="%s")', 155 | appRoot: '.tmp/public' 156 | }, 157 | files: { 158 | 'views/**/*.jade': require('../pipeline').jsFilesToInject 159 | } 160 | }, 161 | 162 | devJsRelativeJade: { 163 | options: { 164 | startTag: '// SCRIPTS', 165 | endTag: '// SCRIPTS END', 166 | fileTmpl: 'script(src="%s")', 167 | appRoot: '.tmp/public', 168 | relative: true 169 | }, 170 | files: { 171 | 'views/**/*.jade': require('../pipeline').jsFilesToInject 172 | } 173 | }, 174 | 175 | prodJsJade: { 176 | options: { 177 | startTag: '// SCRIPTS', 178 | endTag: '// SCRIPTS END', 179 | fileTmpl: 'script(src="%s")', 180 | appRoot: '.tmp/public' 181 | }, 182 | files: { 183 | 'views/**/*.jade': ['.tmp/public/min/production.min.js'] 184 | } 185 | }, 186 | 187 | prodJsRelativeJade: { 188 | options: { 189 | startTag: '// SCRIPTS', 190 | endTag: '// SCRIPTS END', 191 | fileTmpl: 'script(src="%s")', 192 | appRoot: '.tmp/public', 193 | relative: true 194 | }, 195 | files: { 196 | 'views/**/*.jade': ['.tmp/public/min/production.min.js'] 197 | } 198 | }, 199 | 200 | devStylesJade: { 201 | options: { 202 | startTag: '// STYLES', 203 | endTag: '// STYLES END', 204 | fileTmpl: 'link(rel="stylesheet", href="%s")', 205 | appRoot: '.tmp/public' 206 | }, 207 | 208 | files: { 209 | 'views/**/*.jade': require('../pipeline').cssFilesToInject 210 | } 211 | }, 212 | 213 | devStylesRelativeJade: { 214 | options: { 215 | startTag: '// STYLES', 216 | endTag: '// STYLES END', 217 | fileTmpl: 'link(rel="stylesheet", href="%s")', 218 | appRoot: '.tmp/public', 219 | relative: true 220 | }, 221 | 222 | files: { 223 | 'views/**/*.jade': require('../pipeline').cssFilesToInject 224 | } 225 | }, 226 | 227 | prodStylesJade: { 228 | options: { 229 | startTag: '// STYLES', 230 | endTag: '// STYLES END', 231 | fileTmpl: 'link(rel="stylesheet", href="%s")', 232 | appRoot: '.tmp/public' 233 | }, 234 | files: { 235 | 'views/**/*.jade': ['.tmp/public/min/production.min.css'] 236 | } 237 | }, 238 | 239 | prodStylesRelativeJade: { 240 | options: { 241 | startTag: '// STYLES', 242 | endTag: '// STYLES END', 243 | fileTmpl: 'link(rel="stylesheet", href="%s")', 244 | appRoot: '.tmp/public', 245 | relative: true 246 | }, 247 | files: { 248 | 'views/**/*.jade': ['.tmp/public/min/production.min.css'] 249 | } 250 | }, 251 | 252 | // Bring in JST template object 253 | devTplJade: { 254 | options: { 255 | startTag: '// TEMPLATES', 256 | endTag: '// TEMPLATES END', 257 | fileTmpl: 'script(type="text/javascript", src="%s")', 258 | appRoot: '.tmp/public' 259 | }, 260 | files: { 261 | 'views/**/*.jade': ['.tmp/public/jst.js'] 262 | } 263 | } 264 | }); 265 | 266 | grunt.loadNpmTasks('grunt-sails-linker'); 267 | }; 268 | -------------------------------------------------------------------------------- /tasks/config/sync.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A grunt task to keep directories in sync. It is very similar to grunt-contrib-copy 3 | * but tries to copy only those files that has actually changed. 4 | * 5 | * --------------------------------------------------------------- 6 | * 7 | * Synchronize files from the `assets` folder to `.tmp/public`, 8 | * smashing anything that's already there. 9 | * 10 | * For usage docs see: 11 | * https://github.com/tomusdrw/grunt-sync 12 | * 13 | */ 14 | module.exports = function(grunt) { 15 | 16 | grunt.config.set('sync', { 17 | dev: { 18 | files: [{ 19 | cwd: './assets', 20 | src: ['**/*.!(coffee)'], 21 | dest: '.tmp/public' 22 | }] 23 | } 24 | }); 25 | 26 | grunt.loadNpmTasks('grunt-sync'); 27 | }; 28 | -------------------------------------------------------------------------------- /tasks/config/uglify.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Minify files with UglifyJS. 3 | * 4 | * --------------------------------------------------------------- 5 | * 6 | * Minifies client-side javascript `assets`. 7 | * 8 | * For usage docs see: 9 | * https://github.com/gruntjs/grunt-contrib-uglify 10 | * 11 | */ 12 | module.exports = function(grunt) { 13 | 14 | grunt.config.set('uglify', { 15 | dist: { 16 | src: ['.tmp/public/concat/production.js'], 17 | dest: '.tmp/public/min/production.min.js' 18 | } 19 | }); 20 | 21 | grunt.loadNpmTasks('grunt-contrib-uglify'); 22 | }; 23 | -------------------------------------------------------------------------------- /tasks/config/watch.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Run predefined tasks whenever watched file patterns are added, changed or deleted. 3 | * 4 | * --------------------------------------------------------------- 5 | * 6 | * Watch for changes on 7 | * - files in the `assets` folder 8 | * - the `tasks/pipeline.js` file 9 | * and re-run the appropriate tasks. 10 | * 11 | * For usage docs see: 12 | * https://github.com/gruntjs/grunt-contrib-watch 13 | * 14 | */ 15 | module.exports = function(grunt) { 16 | 17 | grunt.config.set('watch', { 18 | api: { 19 | 20 | // API files to watch: 21 | files: ['api/**/*'] 22 | }, 23 | assets: { 24 | 25 | // Assets to watch: 26 | files: ['assets/**/*', 'tasks/pipeline.js'], 27 | 28 | // When assets are changed: 29 | tasks: ['syncAssets' , 'linkAssets'] 30 | } 31 | }); 32 | 33 | grunt.loadNpmTasks('grunt-contrib-watch'); 34 | }; 35 | -------------------------------------------------------------------------------- /tasks/pipeline.js: -------------------------------------------------------------------------------- 1 | /** 2 | * grunt/pipeline.js 3 | * 4 | * The order in which your css, javascript, and template files should be 5 | * compiled and linked from your views and static HTML files. 6 | * 7 | * (Note that you can take advantage of Grunt-style wildcard/glob/splat expressions 8 | * for matching multiple files.) 9 | */ 10 | 11 | 12 | 13 | // CSS files to inject in order 14 | // 15 | // (if you're using LESS with the built-in default config, you'll want 16 | // to change `assets/styles/importer.less` instead.) 17 | var cssFilesToInject = [ 18 | 'styles/**/*.css' 19 | ]; 20 | 21 | 22 | // Client-side javascript files to inject in order 23 | // (uses Grunt-style wildcard/glob/splat expressions) 24 | var jsFilesToInject = [ 25 | 26 | // Dependencies like sails.io.js, jQuery, or Angular 27 | // are brought in here 28 | 'js/dependencies/**/*.js', 29 | 30 | // All of the rest of your client-side js files 31 | // will be injected here in no particular order. 32 | 'js/**/*.js' 33 | ]; 34 | 35 | 36 | // Client-side HTML templates are injected using the sources below 37 | // The ordering of these templates shouldn't matter. 38 | // (uses Grunt-style wildcard/glob/splat expressions) 39 | // 40 | // By default, Sails uses JST templates and precompiles them into 41 | // functions for you. If you want to use jade, handlebars, dust, etc., 42 | // with the linker, no problem-- you'll just want to make sure the precompiled 43 | // templates get spit out to the same file. Be sure and check out `tasks/README.md` 44 | // for information on customizing and installing new tasks. 45 | var templateFilesToInject = [ 46 | 'templates/**/*.html' 47 | ]; 48 | 49 | 50 | 51 | // Prefix relative paths to source files so they point to the proper locations 52 | // (i.e. where the other Grunt tasks spit them out, or in some cases, where 53 | // they reside in the first place) 54 | module.exports.cssFilesToInject = cssFilesToInject.map(function(path) { 55 | return '.tmp/public/' + path; 56 | }); 57 | module.exports.jsFilesToInject = jsFilesToInject.map(function(path) { 58 | return '.tmp/public/' + path; 59 | }); 60 | module.exports.templateFilesToInject = templateFilesToInject.map(function(path) { 61 | return 'assets/' + path; 62 | }); 63 | -------------------------------------------------------------------------------- /tasks/register/build.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | grunt.registerTask('build', [ 3 | 'compileAssets', 4 | 'linkAssetsBuild', 5 | 'clean:build', 6 | 'copy:build' 7 | ]); 8 | }; 9 | -------------------------------------------------------------------------------- /tasks/register/buildProd.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | grunt.registerTask('buildProd', [ 3 | 'compileAssets', 4 | 'concat', 5 | 'uglify', 6 | 'cssmin', 7 | 'linkAssetsBuildProd', 8 | 'clean:build', 9 | 'copy:build' 10 | ]); 11 | }; 12 | -------------------------------------------------------------------------------- /tasks/register/compileAssets.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | grunt.registerTask('compileAssets', [ 3 | 'clean:dev', 4 | 'jst:dev', 5 | 'less:dev', 6 | 'copy:dev', 7 | 'coffee:dev' 8 | ]); 9 | }; 10 | -------------------------------------------------------------------------------- /tasks/register/default.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | grunt.registerTask('default', ['compileAssets', 'linkAssets', 'watch']); 3 | }; 4 | -------------------------------------------------------------------------------- /tasks/register/linkAssets.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | grunt.registerTask('linkAssets', [ 3 | 'sails-linker:devJs', 4 | 'sails-linker:devStyles', 5 | 'sails-linker:devTpl', 6 | 'sails-linker:devJsJade', 7 | 'sails-linker:devStylesJade', 8 | 'sails-linker:devTplJade' 9 | ]); 10 | }; 11 | -------------------------------------------------------------------------------- /tasks/register/linkAssetsBuild.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | grunt.registerTask('linkAssetsBuild', [ 3 | 'sails-linker:devJsRelative', 4 | 'sails-linker:devStylesRelative', 5 | 'sails-linker:devTpl', 6 | 'sails-linker:devJsRelativeJade', 7 | 'sails-linker:devStylesRelativeJade', 8 | 'sails-linker:devTplJade' 9 | ]); 10 | }; 11 | -------------------------------------------------------------------------------- /tasks/register/linkAssetsBuildProd.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | grunt.registerTask('linkAssetsBuildProd', [ 3 | 'sails-linker:prodJsRelative', 4 | 'sails-linker:prodStylesRelative', 5 | 'sails-linker:devTpl', 6 | 'sails-linker:prodJsRelativeJade', 7 | 'sails-linker:prodStylesRelativeJade', 8 | 'sails-linker:devTplJade' 9 | ]); 10 | }; 11 | -------------------------------------------------------------------------------- /tasks/register/prod.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | grunt.registerTask('prod', [ 3 | 'compileAssets', 4 | 'concat', 5 | 'uglify', 6 | 'cssmin', 7 | 'sails-linker:prodJs', 8 | 'sails-linker:prodStyles', 9 | 'sails-linker:devTpl', 10 | 'sails-linker:prodJsJade', 11 | 'sails-linker:prodStylesJade', 12 | 'sails-linker:devTplJade' 13 | ]); 14 | }; 15 | -------------------------------------------------------------------------------- /tasks/register/syncAssets.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | grunt.registerTask('syncAssets', [ 3 | 'jst:dev', 4 | 'less:dev', 5 | 'sync:dev', 6 | 'coffee:dev' 7 | ]); 8 | }; 9 | -------------------------------------------------------------------------------- /views/403.ejs: -------------------------------------------------------------------------------- 1 | 2 | 36 | 37 | 38 | Forbidden 39 | 40 | 44 | 45 | 46 | 47 |
48 |
49 | 50 |
51 | 52 |
53 |

54 | Forbidden 55 |

56 |

57 | <% if (typeof error !== 'undefined') { %> 58 | <%= error %> 59 | <% } else { %> 60 | You don't have permission to see the page you're trying to reach. 61 | <% } %> 62 |

63 |

64 | Why might this be happening? 65 |

66 |
67 | 68 | 73 |
74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /views/404.ejs: -------------------------------------------------------------------------------- 1 | 2 | 36 | 37 | 38 | Page Not Found 39 | 40 | 44 | 45 | 46 | 47 |
48 |
49 | 50 |
51 | 52 |
53 |

54 | Something's fishy here. 55 |

56 |

57 | <% if (typeof error!== 'undefined') { %> 58 | <%= error %> 59 | <% } else { %> 60 | The page you were trying to reach doesn't exist. 61 | <% } %> 62 |

63 |

64 | Why might this be happening? 65 |

66 |
67 | 68 | 73 |
74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /views/500.ejs: -------------------------------------------------------------------------------- 1 | 2 | 36 | 37 | 38 | Server Error 39 | 40 | 47 | 48 | 49 |
50 |
51 |
52 | 53 | 54 |
55 |
56 |
57 |

58 | Internal Server Error 59 |

60 |

61 | Something isn't right here. 62 |

63 | <% if (typeof error !== 'undefined') { %> 64 |

65 |         	<%- error %>
66 |         
67 | <% } else { %> 68 |

69 | A team of highly trained sea bass is working on this as we speak.
70 | If the problem persists, please contact the system administrator and inform them of the time that the error occured, and anything you might have done that may have caused the error. 71 |

72 | <% } %> 73 | 74 |
75 | 76 | 79 |
80 | 81 | 82 | -------------------------------------------------------------------------------- /views/auth/login.ejs: -------------------------------------------------------------------------------- 1 | <% if (errors) {%> 2 | <% errors.forEach(function (error) { %> 3 | <%= __(error) %> 4 | <% }) %> 5 | <% } %> 6 | 7 |
8 | 9 | 10 | 11 |
12 | 13 | <% if (providers) {%> 14 |

You can also use one of these...

15 | 16 | <% Object.keys(providers).forEach(function (key) { %> 17 |
<%= providers[key].name %> 18 | <% }) %> 19 | <% } %> 20 | -------------------------------------------------------------------------------- /views/auth/register.ejs: -------------------------------------------------------------------------------- 1 | <% if (errors) {%> 2 | <% errors.forEach(function (error) { %> 3 | <%= __(error) %> 4 | <% }) %> 5 | <% } %> 6 | 7 |
8 | 9 | 10 | 11 | 12 |
13 | -------------------------------------------------------------------------------- /views/homepage.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 12 | 13 |
14 |
15 |

<%= __('A brand new app.') %>

16 |

You're looking at: <%= view.pathFromApp + '.' +view.ext %>

17 |
18 |
19 | 21 |
    22 |
  • 23 |
    24 |
    25 |

    Generate a REST API.

    26 |

    27 | Run sails generate api user. This will create two files: a model api/models/User.js and a controllerapi/controllers/UserController.js. 28 |

    29 |
    30 |
  • 31 |
  • 32 |
    33 |
    34 |

    35 | Lift your app. 36 |

    37 |

    38 | Run sails lift to start up your app server. If you visit http://localhost:1337/user in your browser, you'll see a WebSocket-compatible user API. 39 |

    40 |
    41 |
  • 42 |
  • 43 |
    44 |
    45 |

    46 | Dive in. 47 |

    48 |

    Blueprints are just the beginning. You'll probably also want to learn how to customize your app's routes, set up security policies, configure your data sources, and build custom controller actions. For more help getting started, check out the links on this page.

    49 | 50 |
    51 |
  • 52 |
53 | 74 |
75 |
76 | -------------------------------------------------------------------------------- /views/layout.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | New Sails App 5 | 6 | 7 | 8 | 9 | 10 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | <%- body %> 38 | 39 | 40 | 41 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | --------------------------------------------------------------------------------