GithubHelp home page GithubHelp logo

twilson63 / express-couchuser Goto Github PK

View Code? Open in Web Editor NEW
35.0 9.0 11.0 170 KB

A express module for CouchDb based User Authentication Module

Home Page: http://twilson63.github.io/express-couchUser

JavaScript 99.32% CSS 0.05% HTML 0.63%

express-couchuser's Introduction

express-user-couchdb - ExpressCouchDb Module

Build Status

This module is a authentication lib built on top of couch's user model.

requirements

  • couchdb
  • nodejs

init example

var couchUser = require('express-user-couchdb');
var express = require('express');
var cookieParser = require('cookie-parser');
var session = require('express-session');
var app = express();

// Required for session storage
app.use(cookieParser());
app.use(session({ secret: 'Use something else here.'}));

// The request_defaults node is required if you have turned the Admin Party off

app.use(couchUser({
  users: 'http://localhost:5984/_users',
  request_defaults: {
    auth: {
      user: 'couchAdminUserName',
      pass: 'couchAdminPassword'
    }
  },
  email: {
  ...
  },
  adminRoles: [ 'admin' ],
  validateUser: function(data, cb) {...}
}));

The optional adminRoles attribute allows you to specify one or more administrative roles that are allowed to add, update, and delete users.

The optional validateUser allows you to specify extra validation other than username/password. For example, checking for greater than x number of login attempts. The first argument data is an object that has request, user, and headers fields. The reason the callback pattern is used to pass out the result is that you can do async calls within this function (like check another database, etc).

Example validateUser function.

validateUser: function(data, cb) {
  var MAX_FAILED_LOGIN = 5;
  var req = data.req;     //all the fields in data is captured from /api/user/signin callback function
  var user = data.user;
  var headers = data.headers;
  var outData = {         // This object will be attached to the session 
    userInfo: "userAge"   // req.session.userInfo will be "userAge"
  };

  if(data.user.failedLogin > MAX_FAILED_LOGIN) {
    //fails check
    var errorPayload = {
      statusCode: 403,                           //if not included will default to 401 
      message: 'Exceeded fail login attempts',   //if not included will default to 'Invalid User Login'
      error: 'Forbidden'                         //if not included will default 'unauthorized'
    }
    cb(errorPayload);
  } else {
    //passess check
    cb(null, outData);
  }
}

Initialize CouchDb

Before you can use this module, you need to run a setup process to add the required views to the couchDb Users table, there is an init.js file that does this for you. You can include it in your Gruntfile as a task.

grunt.registerTask('setup', 'setup database', function() {
  var userSetup = require('./node_modules/express-user-couchdb/init');
  userSetup('http://localhost:5984/_users', function(err) {
    console.log("configured express-user-couchdb module");
  });
});

or you can invoke via command line

node ./node_modules/express-user-couchdb/init http://localhost:5984/_users

API Commands

POST /api/user/signup

Create a new user account. If config.verify is set, the user will be sent an email with a link to verify their account. If the user trying to login has enabled set to false, they will be notified to contact an admin to reactivate their account.

{
  "name": "user",
  "password": "password",
  "email": "[email protected]",
  "data": {}
}

POST /api/user/signin

Allow a user to log in. If config.verify is set, then the user is required to validate their email address before logging in.

{
  "name": "user",
  "password": "password"
}

POST /api/user/signout

{
  "name": "user"
}

GET /api/user/current

The currently logged in user. Returns a 401 error if the user is not currently logged in.

POST /api/user/forgot

If the user trying to retrieve their password has enabled set to false, they will be notified to contact an admin to reactivate their account.

{
  "email": "[email protected]"
}

POST /api/user/verify:email

Send an email to a user that includes a verification code and link to verify their account.

{
  "name": "email"
}

POST /api/user/verify/:code

Confirm that a user's email address is valid using a previously generated verification code.

POST /api/user/reset

Reset a user's password (requires the code generated using /api/user/forgot).

{
  "name": "user",
  "code": "code"
}

GET /api/user?roles=foo,bar

Return a list of users matching the specified roles.

[{ user... }, { user2... }]

POST /api/user

Create a new user. If config.adminRoles is set, the user making this call must have one of the specified roles.

{
  "name": "user1",
  "type": "user",
  "password": "foo",
  "roles": ['admin']
}

GET /api/user/:name

Returns the user whose name matches :name.

{
  "_id": "org.couchdb.user:user1",
  "_rev": "1-123456",
  "name": "user1",
  "type": "user",
  "password": "foo",
  "roles": ['admin']
}

PUT /api/user/:name

Updates the user specified by :name. If config.adminRoles is set, then a user must have an admin role to be able to update anyone else's record. Non-admins cannot update their own role information.

{
  "_id": "org.couchdb.user:user1",
  "_rev": "1-123456",
  "name": "user1",
  "type": "user",
  "password": "foo",
  "roles": ['admin']
}

DELETE /api/user/:name

Removes the specified user. If config.adminRoles is set, then a user must have an admin role to be able to delete a user.

{
  "_id": "org.couchdb.user:user1",
  "_rev": "1-123456",
  "name": "user1",
  "type": "user",
  "password": "foo",
  "roles": ['admin']
}

Usage

var user = require('express-user-couchdb');
var config = { 
  users: 'http://localhost:5984/_users', 
  email ....
};
app.config(function() {
  // handle other requests first
  app.use(express.router);
  // handle core requests
  app.use(user(config));
});
node init.js [couchdb users db]

setting up email templates

express-user-couchdb has the capability to attach to an smtp server and send emails for certain user events. And you have the ability to manipulate the e-mail templates.

express-user-couchdb uses the nodemailer and email-templates modules to perform the email process, but you need to pass in the config settings in the config argument of the module:

Here is an example of the config settings:

{
  couch: 'http://localhost:5984/_users',
  email: {
    service: 'SMTP',
    SMTP: {
      ....
    },
    templateDir: ....
  }
}

to setup the forgot password email template you need to create a folder called forgot in the email template directory, then in that folder you need to create a style.css, html.ejs, and text.ejs. These files will be used to build your email template. Here is an example of the text.ejs

Forgot Password

Foo App
#######

We are sorry you forgot your password, in order to reset your password,  we need you to click the link below.

Copy Link:

http://fooapp.com/account/reset?code=<%= user.code %>

and paste into your browsers url.

Thank you for supporting our application, please let us know if you have any questions or concerns.

Thanks

The Team

confirm e-mail

If you plan to enable the users to register, you may want to send a confirmation email to them when they sign up.

You would follow the same steps above, but instead of creating a forgot folder, you would need to create a confirm folder and place your css, html.ejs, and text.ejs files.

Contribution

Pull requests are welcome.

License

MIT

Support

Please add issues to the github repo.

Thanks

  • CouchDb Team
  • NodeJS Team
  • NodeMailier
  • Nano Team
  • Email-Templates

express-couchuser's People

Contributors

brentcobb avatar duhrer avatar joelv avatar the-t-in-rtf avatar twilson63 avatar warrensplayer avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

express-couchuser's Issues

Special characters in templates incorrectly escaped...

I converted the included .ejs "forgot password" mail template in the test directory to handlebars. The only variable used in the template is the code in the URL:

https://terms.raisingthefloor.org/api/users/reset?code={{user.code}}

Unfortunately, in the email sent, the question mark, ampersand, equals and other key characters are escaped. These are characters in the text of the template itself, and not values passed in a variable.

I went back and confirmed this also happens with ejs files. I'm hoping that the templating library offers a configuration option to control this behavior.

Verify that express-couchUser works with express 4.0.0

I needed to update to express 4 in an application and wanted to test express-couchUser in the new environment. In my testing it appears to work just fine without updating the local express dependency version or updating to use "express 4"isms.

Expose less user data to end users...

I realized in working with POSTman and reviewing the raw JSON data returned to users that /api/user/:name is too free with what it shares.

Critically, it shares the "code" variable, meaning that anyone who knows a username can reset the password on someone else's account.

It also shares the new "verification_code" variable, meaning that spammers can bypass the verification mechanism.

Email is also a bit of a concern.

I would propose the following:

  • Create a convenience function to strip sensitive variables (code, verification_code, salt, derived_key) from the raw user object returned by couch.
  • Wrap any calls that return user data in that convenience function.

In that way we can decouple the required format coming from couch (which we need to see to verify logins, etc.) from the data users see. It's either that or make another view and make two calls for each request.

I would also propose requiring users to be logged in to poll /api/user/:name and other endpoints that expose user data. I'm thinking specifically of trawling for email addresses and valid usernames. Ideally we would add more protection for email addresses, but at least we will keep the data within the community of trust on a single site.

If you are happy with both I can put together the pull request.

Update to run Node v8.x, Express v4.x

It may be a good idea to update this module to support the latest and greatest from Node and Express. I'd be happy to assist with this process, too.

Add ability to look up current user based on AuthSession cookie...

On the server side, req.session.user stores the current user. On the client side, the user object is returned on login, but users shouldn't have to log in repeatedly.

We need a way to tell which user is logged in on the client side based on their session cookie.

I propose adding /api/user/current as a GET interface that either returns the current user record from req.session.user, or throws a 401 status and asks the user to log in.

That will make it much easier to build client side calls that check the current user once and only require a login if needed.

@twilson63, if you're happy with this approach, I will create the pull request shortly.

Token Auth

Sometimes cookies are unacceptable. Is there any plans to support token auth easily? Or is it already available?

Thanks.

Require configurable role to access CRUD features...

In addition to requiring logins for sensitive functions, we should also require that the user have a role to manipulate other user records. The check would be as follows.

  • If the user is you, you can update your own record
  • Otherwise, you have to belong to one of the allowed roles

The list of roles would be passed as config.adminRoles and default to [ "admin" ]. Setting config.adminRoles to false or undefined would disable the check and allow everyone access.

These checks would be added to the raw CRUD functions, but not to signup or other end-user self-service end points.

If you are OK with the proposed changes I will prepare another pull request.

Preserve upstream error codes...

When an upstream call to couch (for example) returns an error, the error is rethrown with error code 500. It would be better to pass along the upstream error code so that (for example) we can tell the difference between a server error when authenticating and an incorrect password.

As far as I can see, fixing this is simply a matter of replacing code like:

      if (err) { return res.send(500, err); }

With code like:

      if (err) { return res.send(err.status_code, err); }

I am putting together a pull request along those lines.

Readme is unclear

Hi there. Firstly - cool module, crazy that this hadn't already been done well.

Im trying to use some of the basic methods by following the readme, but so far im having no luck.

Every request I make is denied (500, 403), essentially saying I don't have access to the user's table. I have tried a signin method with my DB admin credentials and then a sign up request - didn't work.

The readme and its examples don't seem to make any any reference to where admin credentials go, so couchUser can execute the views from the init file on my _users database. There is two different usage examples at the top and bottom of the readme, and I have no idea what the bottom one refers to:

Top
var couchUser = require('express-couchUser');
var express = require('express');
var app = express();

// Required for session storage
app.use(express.cookieParser());
app.use(express.session({ secret: 'Use something else here.'}));

app.configure(function() {
app.use(couchUser({
users: 'http://localhost:5984/_users',
email: {
...
},
adminRoles: [ 'admin' ],
validateUser: function(data, cb) {...}
}));
});

Bottom
var user = require('express-user-couchdb');
var config = {
users: 'http://localhost:5984/_users',
email ....
};
app.config(function() {
// handle other requests first
app.use(express.router);
// handle core requests
app.use(user(config));
});
node init.js [couchdb users db]

So in simple terms, what is the minimum requirement to get this module up and running. Have I missed anything obvious?

Cheers

signin and signout fail...

Attempting to sign in results in a server error that crashes express:

TypeError: Cannot call method 'regenerate' of undefined
    at /Users/duhrer/Source/rtf/rtf-terms-registry/express/node_modules/express-user-couchdb/index.js:63:21

This is because there is no check to see if the session already exists (and it doesn't for people who haven't yet logged in).

There is a similar problem with the signout call as well:

TypeError: Cannot call method 'destroy' of undefined
    at /Users/duhrer/Source/rtf/rtf-terms-registry/express/node_modules/express-user-couchdb/index.js:78:17

user delete

With angular, you cannot attach data to the body of the $http['delete] request call, so we cannot get the rev from req.body. Joel and I created a custom api call to delete users. Below is the code that worked for us.

From Angular

$http['delete']('/api/user/del/' + user.name, {params: { rev: user._rev} })
    .success(function(data) {
     //handle response
    });

Node API Call

app.del('/api/user/del/:name', function(req, res) {
  var db = nano(config.users);
 db.destroy('org.couchdb.user:' + req.params.name, req.query.rev).pipe(res);
});

SMTP Config - RecipientError - 550 Administrative Prohibition Error

Hey there,

I'm unable to get your node-email-templates integration working. In other projects I'm using node-email-templates, nodemailer and the node-mailer-smtp-transport plugin directly, so I know my credentials are fine. Is there something wrong with my setup? It is:

email: {
    service: 'SMTP',
    SMTP: {
        host: Config.email.host, // smtp.hostingprovider.com
        port: Config.email.port, // 587
        auth: {
            user: Config.email.user, // my email address
            pass: Config.email.pass // my password
        },
        ignoreTLS: true // got this from the node-simplesmtp npm page
    },
    templateDir: `${Config.root}/server/templates/` // verified correct
}

One the client side, I get this error:

{
     "message": "Can't send mail - all recipients were rejected",
     "stack": "RecipientError: Can't send mail - all recipients were rejected\n, // etc
     "name": "RecipientError",
     "data": "550 Administrative prohibition",
     "stage": "rcpt"
}

The process also crashes the node server, with this error:

POST /api/user/forgot 500 2626.020 ms - 695
events.js:142
      throw er; // Unhandled 'error' event
      ^

Error: 140735214301184:error:1408F10B:SSL routines:SSL3_GET_RECORD:wrong version number:../deps/openssl/openssl/ssl/s3_pkt.c:362:

    at Error (native)

Unable to install module - node-gyp/node-sass build errors

I'm on node 5.2.0 and npm 3.5.2 on OSX El Capitan. I've got the latest XCode and Command Line Tools. Python 2.7.11. I've tried using node-sass 3.4.2 and their latest beta release. I've updated node-gyp globally. I've removed the node_modules folder, cleaned the cache and ran npm install. I can install node-email-templates and node-sass without issue. I get the following errors when trying to install express-user-couchdb:

gyp ERR! build error 
gyp ERR! stack Error: `make` failed with exit code: 2
gyp ERR! stack     at ChildProcess.onExit (/usr/local/lib/node_modules/npm/node_modules/node-   gyp/lib/build.js:276:23)
gyp ERR! stack     at emitTwo (events.js:88:13)
gyp ERR! stack     at ChildProcess.emit (events.js:173:7)
gyp ERR! stack     at Process.ChildProcess._handle.onexit (internal/child_process.js:201:12)
gyp ERR! System Darwin 15.3.0
gyp ERR! command "/usr/local/bin/node" "/usr/local/lib/node_modules/npm/node_modules/node-   gyp/bin/node-gyp.js" "rebuild"
gyp ERR! cwd /Users/Justin/Code/nec/kRaceSandbox/src/node_modules/express-user-couchdb/node_modules/node-sass
gyp ERR! node -v v5.2.0
gyp ERR! node-gyp -v v3.2.1
gyp ERR! not ok 
Build failed

and

npm ERR! Darwin 15.3.0
npm ERR! argv "/usr/local/bin/node" "/usr/local/bin/npm" "install" "--save" "express-user-couchdb"
npm ERR! node v5.2.0
npm ERR! npm  v3.5.2
npm ERR! code ELIFECYCLE

npm ERR! [email protected] install: `node build.js`
npm ERR! Exit status 1
npm ERR! 
npm ERR! Failed at the [email protected] install script 'node build.js'.

I realize there could be a problem anywhere in the chain, so I'm just starting with the module I'm unable to install. Any help would be awesome. Let me know if you need additional info. Thanks!

how to run this?

Hi,

I installed this package from node. Package installed successful. But how can i run it?

when i run:

node ./node_modules/express-user-couchdb/init http://localhost:5984/_users

im getting the following error:

{ Error: Document update conflict.
    at Request._callback (/home/kabus/node_modules/nano/nano.js:319:39)
    at Request.self.callback (/home/kabus/node_modules/nano/node_modules/request/request.js:122:22)
    at emitTwo (events.js:106:13)
    at Request.emit (events.js:191:7)
    at Request.<anonymous> (/home/kabus/node_modules/nano/node_modules/request/request.js:888:14)
    at emitOne (events.js:101:20)
    at Request.emit (events.js:188:7)
    at IncomingMessage.<anonymous> (/home/kabus/node_modules/nano/node_modules/request/request.js:839:12)
    at emitNone (events.js:91:20)
  name: 'Error',
  scope: 'couch',
  status_code: 409,
  'status-code': 409,

How can i set the user credentials?

Implement /api/user/verify...

/api/user/verify appears to be the mechanism by which a user is allowed to reset their password, but it's not yet implemented.

Can't assume error object always has a status code...

I am testing the module with express 4 (more on that later). I was having trouble tracking down the root cause because of this pattern, which is used often:

if (err) { return res.send(err.status_code, err); }

There are cases in which err does not have a status code, which itself throws an error. I will prepare a pull request which defaults to 500 if status_code is undefined.

Prevent tests from ever connecting to a couch instance...

In working on recent tests, I have noticed that nock is not entirely preventing connections to the local couch instance, which is still required to run many tests.

I would propose one of the following:

  1. migrating to using pouchdb for testing. I have had good results using that to replace couch in other unit tests, as long as the views used are not too complex (ours aren't in this case).
  2. firewalling nock by disabling connections using disableNetConnect() and fixing any tests that break (see below).

For the firewalling option, I tried using code like the following:

 nock.disableNetConnect();
 nock.enableNetConnect("(localhost|127.0.0.1):(?!5984).*");

In doing so, I discovered that nock appears to be shared across tests, which is hugely dangerous. I will file a separate bug on that.

Return fields from _users other than name, email and roles

Hi there,

So, the GET /api/user/?roles=roles and POST /api/user/signin return only the fields name, email and roles from the db. Obviously we don't want salts or hashes, but I have other fields in the db I'd like to get back in the response. Is there some configuration option I'm missing? I think my setup is pretty much standard boilerplate:

app.use(couchUser({
    users: `${Config.couchDb.uri}/_users`,
    request_defaults: {
        auth: {
            user: Config.couchDb.adminUser,
            pass: Config.couchDb.adminPass
        }
    },
    email: {
        service: 'SMTP',
        SMTP: {
            host: Config.email.host,
            port: Config.email.port,
            auth: {
                user: Config.email.user,
                pass: Config.email.pass
            },
            tls: {
                rejectUnauthorized: false
            }
        },
        templateDir: `${Config.root}/server/templates/`
    },
    adminRoles: [ 'admin' ]
}));

Create database on a user signup

One database per user seems a popular approach for CouchDB. This project adds many features missing in CouchDB authentication. Perhaps it could also add creation of a database on a signup? Any plans along those lines?

Fix session handling....

When logging in, we seem to have a hybrid approach, and set our own cookies rather than letting express-session take care of it:

    req.session.regenerate(function() {
      req.session.user = user;
      req.session.save();

      res.writeHead(200, { 'set-cookie': headers['set-cookie']});
      res.end(JSON.stringify({ok:true, user: strip(user)}));
    });

In my testing, this fails outright with newer versions of express. Instead we need to stop setting the cookies and calling regenerate ourselves, which does work.

Nock configuration in one test breaks other tests....

In working with the mocha tests recently, I discovered a disturbing behavior.

Configuring "nock" in one test file affects the instances of nock in other objects. I tried disabling connections from the signup tests using code like:

describe('User sign up', function() {
  var nock = require('nock');
  nock.disableNetConnect();
  nock.enableNetConnect("(localhost|127.0.0.1):(?!5984).*");

This somehow managed to break tests running in completely separate files, which should never happen. Either one of two things is happening:

  1. The "nock" object is somehow shared between tests.
  2. Disabling network connections in one test means that data created by the test no longer exists in couch itself.

Either of these is a problem. I was seeing cases in which three tests would fail and then would work again if you waited a few seconds. I suspect either the nock problem or the direct use of couch is causing this.

Add user validation...

In addition to allowing password resets and "forgot" password links, it would be useful to require users to confirm that their email address is valid before allowing them to log in.

Handle (or prevent) multiple accounts with the same email address...

In testing the library with multiple users, I've discovered that the "forgot password" feature does not handle multiple accounts with the same email address sanely. Only the first account returned for a given email address is associated with the reset token. This makes it impossible to reset the password for any other accounts associated with the same email address.

I can see a few ways to address this:

  1. Make the signup method check to see if a user with the suggested email address already exists and throw an error.
  2. Make the "forgot password" function accept a username or an email address.
  3. Do both.

Happy to make the changes and submit a pull request, just let me know what approach you'd prefer.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.