GithubHelp home page GithubHelp logo

colinskow / superlogin Goto Github PK

View Code? Open in Web Editor NEW
369.0 23.0 117.0 143 KB

Powerful authentication for APIs and single page apps using the CouchDB ecosystem which supports a variety of providers.

License: MIT License

JavaScript 98.93% HTML 1.07%

superlogin's Introduction

SuperLogin

Build Status

SuperLogin is a full-featured NodeJS/Express user authentication solution for APIs and Single Page Apps (SPA) using CouchDB or Cloudant.

User authentication is often the hardest part of building any web app, especially if you want to integrate multiple providers. Now all the tough work has been done for you so you can relax and create with less boilerplate!

(Live Demo)

For issues and feature requests visit the issue tracker.

Contents

Features

  • Ideal authentication and security solution for modern APIs and Single Page Apps
  • Supports local login with username and password using best security practices
  • Sends system emails for account confirmation, password reset, or anything else you want to configure
  • Add any Passport OAuth2 strategy with literally just a couple lines of code
  • Facebook, WindowsLive, Google, Github, and LinkedIn integration fully tested
  • Link multiple authentication strategies to the same account for user convenience
  • 100% cookie free, which means that CSRF attacks are impossible against your app
  • Fast and massively scalable with a Redis session store
  • Provides seamless token access to both your CouchDB server (or Cloudant) and your private API
  • Manages permissions on an unlimited number of private or shared user databases and seeds them with the correct design documents

Client Tools and Demo

  • NG-SuperLogin Helps you easily integrate a SuperLogin backend into your single page AngularJS applications.

  • SuperLogin Demo A full-stack demo of how to integrate SuperLogin and Express with AngularJS and CouchDB.

  • SuperLogin-client Helps you easily integrate a SuperLogin backend into your Javascript applications.

How It Works

Simply authenticate yourself with SuperLogin using any supported strategy and you will be issued a temporary access token and password. Then include the access token and password in an Authorization Bearer header on every request to access protected endpoints. The same credentials will authenticate you on any CouchDB or Cloudant database you have been authorized to use.

Session storage is handled by Redis for production environments, but SuperLogin includes a memory adapter for testing purposes. When you logout or the token expires, your session is invalidated and those credentials are also removed from any database you had access to.

Quick Start

Here's a simple minimalist configuration that will get you up and running right away:

First:

npm install superlogin

Then...

var express = require('express');
var http = require('http');
var bodyParser = require('body-parser');
var logger = require('morgan');
var SuperLogin = require('superlogin');

var app = express();
app.set('port', process.env.PORT || 3000);
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));

var config = {
  dbServer: {
    protocol: 'http://',
    host: 'localhost:5984',
    user: '',
    password: '',
    userDB: 'sl-users',
    couchAuthDB: '_users'
  },
  mailer: {
    fromEmail: '[email protected]',
    options: {
      service: 'Gmail',
        auth: {
          user: '[email protected]',
          pass: 'userpass'
        }
    }
  },
  userDBs: {
    defaultDBs: {
      private: ['supertest']
    }
  }
}

// Initialize SuperLogin
var superlogin = new SuperLogin(config);

// Mount SuperLogin's routes to our app
app.use('/auth', superlogin.router);

http.createServer(app).listen(app.get('port'));

Now get a request tool like Postman and let's create our first user.

{
  "name": "Joe Smith",
  "username": "joesmith",
  "email": "[email protected]",
  "password": "bigsecret",
  "confirmPassword": "bigsecret"
}

POST the form to http://localhost:3000/auth/register (using x-www-form-urlencoded) and you should get the response {"success": "User created."}.

Now to login, simply post your username and password to http://localhost:3000/auth/login. You should get a response similar to this:

{
  "issued": 1440232999594,
  "expires": 1440319399594,
  "provider": "local",
  "ip": "127.0.0.1",
  "token": "aViSVnaDRFKFfdepdXtiEg",
  "password": "p7l9VCNbTbOVeuvEBhYW_A",
  "user_id": "joesmith",
  "roles": [
    "user"
  ],
  "userDBs": {
      "supertest": "http://aViSVnaDRFKFfdepdXtiEg:p7l9VCNbTbOVeuvEBhYW_A@localhost:5984/supertest$joesmith"
    }
}

You have now been issued an access token. Let's use it to access a protected endpoint. Make a request to http://localhost:3000/auth/session and you'll see it was unauthorized. Now add a header to your request: "Authorization": "Bearer {token}:{password}" and you should see information about your session. That was easy!

If your user document contains a field called profile, this will automatically be included with the session information.

You can also use the same token and password combination to access your personal database. But as soon as you log out your session that access will be revoked.

Note: Session tokens for your API will be unusable as soon as they expire. However, there is no mechanism to automatically revoke expired credentials with CouchDB. Whenever a user logs in, logs out, or refreshes the session, SuperLogin will automatically clean up any expired credentials for that user. For additional security, periodically run superlogin.removeExpiredKeys() either with setInterval or a cron job. This will deauthorize every single expired credential that exists in the system.

Securing Your Routes

Securing your routes is very simple:

app.get('/admin', superlogin.requireAuth, superlogin.requireRole('admin'),
  function(req, res) {
    res.send('Welcome Admin');
  });

Note that you must use requireAuth prior to checking any roles or an error will be thrown.

superlogin.requireAuth

Middleware that authenticates a user with a token and password in the request header. ("Authorization": "Bearer {token}:{password}")

superlogin.requireRole(role)

Middleware that makes sure the authenticated user possesses the specified role (string).

superlogin.requireAnyRole(possibleRoles)

Middleware that makes sure the user possesses at least one of the specified possibleRoles (array).

superlogin.requireAllRoles(requiredRoles)

Middleware that makes sure the user possesses ALL of the specified requiredRoles (array).

Database Security

If you are using Cloudant, then your databases are secure by default and all you have to do is ensure the correct permissions are specified under userDBs.model in your config.

If, however, you are using regular CouchDB, then Admin Party is default and all your databases are readable and writable by the public until you implement the correct security measures. It is your responsibility to study up on best security practices and apply them. To block anonymous reads across all databases you can set require_valid_user to true under [couch_httpd_auth] in your CouchDB config.

SuperLogin also allows you to specify default _security roles for members and admins in the userDBs section of your config file. See config.example.js for details.

CouchDB Document Update Validation

CouchDB can save your API a lot of traffic by handling both reads and writes. CouchDB provides the validate_doc_update function to approve or disapprove what gets written. However, since your CouchDB users are temporary random API keys, you have no idea which user is requesting to write. SuperLogin has inserted the original user_id into userCtx.roles[0], prefixed by user: (e.g. user:superman).

If you are using Cloudant authentication, the prefixed user_id is inserted as the first item on the permissions array, which will also appear inside roles in your userCtx object. You will also find all the roles from your user doc here.

If you wish to give a user special Cloudant permissions other than the ones specified in your config, you can edit the user doc from the sl-users database and under personalDBs add an array called permissions under the corresponding DB for that user.

Adding Providers

You can add support for any Passport OAuth2 strategy to SuperLogin with just a few lines of code. (OAuth1 strategies generally require a cookie-based session to work, so are not currently supported by SuperLogin which is sessionless.)

Configuration

The first step is to add credentials to your config file. You can skip the callback URL as it will be generated automatically. Here is how to add support for Dropbox:

providers: {
  dropbox: {
    // Credentials here will be passed in on the call to passport.use
    credentials: {
      consumerKey: DROPBOX_APP_KEY,
      consumerSecret: DROPBOX_APP_SECRET
    },
    options: {
      // Options here will be passed in on the call to passport.authenticate
    }
  }
}

SuperLogin supports two types of workflows for OAuth2 providers: popup window and client access token.

Popup Window Workflow for web browsers (desktop and mobile)

Your client must create a popup window and point it to /{provider}, where the user will be directed to authenticate with that provider. After authentication succeeds or fails, it will call a Javascript callback on the parent window called superlogin.oauthSession.

After completing the configuration step above, all you have to do is register your new provider with SuperLogin. Simply follow this pattern:

var DropboxStrategy = require('passport-dropbox-oauth2').Strategy;
superlogin.registerOAuth2('dropbox', DroboxStrategy);

Now, assuming your credentials are valid, you should be able to authenticate with Dropbox by opening a popup window to /dropbox. See below in the Routes documentation for more detail.

Client Access Token for Cordova / Phonegap and Native Apps

Cordova and most native app frameworks (including iOS and Android) have plugins which authenticate a user with a provider and provide an access_token to the client app. All you have to do is post a request to /{provider}/token and include your access_token in the request body. SuperLogin will respond with a new session or an error message.

You must use Passport strategies that accept access_token posted in the body of the request, such as passport-facebook-token, passport-google-token, etc.

Here is how to setup the Client Access Token strategy:

var FacebookTokenStrategy = require('passport-facebook-token');
superlogin.registerTokenProvider('facebook', FacebookTokenStrategy);

Note that this uses the exact settings in your config as the popup window workflow.

Adding additional fields

It's easy to add custom fields to user documents. When added to a profile field it will automatically be included with the session information (in a profile object).

  1. First whitelist the fields in the config, for example:

    userModel: {
       whitelist: ['profile.fullname']
    }
  2. Include the fields with registrations.

  3. To also fill in custom fields after social authentications use the superlogin.onCreate handler. Example:

    superlogin.onCreate(function(userDoc, provider) {
      if(userDoc.profile === undefined) {
        userDoc.profile = {};
      }
      if(provider !== 'local') {
        const displayName = userDoc[provider].profile.displayName;
        if (displayName) {
          userDoc.profile.fullname = displayName;
        }
      }
      return Promise.resolve(userDoc);
    })

Advanced Configuration

Take a look at config.example.js for a complete tour of all available configuration options. You'll find a lot of cool hidden features there that aren't documented here.

/config/default.config.js contains a list of default settings that will be assumed if you don't specify anything.

Routes

POST /register

Creates a new account with a username and password. Required fields are: username, email, password and confirmPassword. name is optional. Any additional fields you want to include need to be white listed under userModel in your config. See config.example.js for details.

If local.sendConfirmEmail is true, a confirmation email will be sent with a verify link. If local.requireEmailConfirm is true, the user will not be able to login until the confirmation is complete. If security.loginOnRegistration is true a session will be automatically created and sent as the response.

POST /login

Include username and password fields to authenticate and initiate a session. The field names can be customized in your config under local.usernameField and local.passwordField.

GET /confirm-email/{token}

This link is included in the confirmation email, and will mark the user as confirmed. If local.confirmEmailRedirectURL is specified in your config, it will redirect to that location with ?success=true if successful or error={error}&message={msg} if it failed. Otherwise it will generate a standard JSON response.

POST /refresh

Authentication token required. Extends the life of your current token and returns updated token information. The only field that will change is expires. Token life is configurable under security.sessionLife and is measured in seconds.

POST /logout

Authentication required. Logs out the current session and deauthorizes the token on all user databases.

POST /logout-others

Authentication required. Logs out and deauthorizes all user sessions except the current one.

POST /logout-all

Authentication required. Logs out every session the user has open and deauthorizes the user completely on all databases.

POST /forgot-password

Include email field to send the forgot password email containing a password reset token. The life of the token can be set under security.tokenLife (in seconds).

Have the email template redirect back to you're app where you're app presents U.I. to gather a new password and then POST to /password-reset with the forgot-password token and new password

POST /password-reset

Resets the password. Required fields: token, password, and confirmPassword.

POST /password-change

Authentication required. Changes the user's password or creates one if it doesn't exist. Required fields: newPassword, and confirmPassword. If the user already has a password set then currentPassword is required.

GET /validate-username/{username}

Checks a username to make sure it is correctly formed and not already in use. Responds with status 200 if successful, or status 409 if unsuccessful.

GET /validate-email/{email}

Checks an email to make sure it is valid and not already in use. Responds with status 200 if successful, or status 409 if unsuccessful.

POST /change-email

Authentication required. Changes the user's email. Required field: newEmail.

GET /session

Returns information on the current session if it is valid. Otherwise you will get a 401 unauthorized response.

GET /{provider}

Open this in a popup window to initiate authentication with Facebook, Google, etc. After authentication, the callback will call a javascript function on the parent window called superlogin.oauthSession which takes 3 arguments: error, session, and link. error explains anything that went wrong. session includes the same session object that is generated by /login. link simply contains the name of the provider that was successfully linked.

GET /link/{provider}?bearer_token={token:password}

This popup window is opened by a user that is already authenticated in order to link additional providers to the account.

There is a security concern here that the session token is exposed as a query parameter in the URL. While this is secure from interception under HTTPS, it can be stored in the user's browser history and your server logs. If you are concerned about this you can either force your user to log out the session after linking an account, or disable link functionality completely by setting security.disableLinkAccounts to true.

POST /unlink/{provider}

Authentication required. Removes the specified provider from the user's account. Local cannot be removed. If there is only one provider left it will fail.

POST /{provider}/token

This will invoke the client access_token strategy for the specified provider if you have registered it. You should include the access_token for the provider in the body of your request.

POST /link/{provider}/token

This will link additional providers to an already authenticated user using the client access_token strategy.

Event Emitter

SuperLogin also acts as an event emitter, which allows you to receive notifications when important things happen.

Example:

superlogin.on('login', function(userDoc, provider){
  console.log('User: ' + userDoc._id + ' logged in with ' + provider);
});

Here is a full list of the events that SuperLogin emits, and parameters provided:

  • signup: (userDoc, provider)
  • login: (newSession, provider)
  • refresh: (newSession)
  • signup: (userDoc, provider)
  • password-reset: (userDoc)
  • password-change: (userDoc)
  • forgot-password: (userDoc)
  • email-verified: (userDoc)
  • email-changed: (userDoc)
  • user-db-added: (dbName)
  • user-db-removed: (dbName)
  • logout: (user_id)
  • logout-all: (user_id)

Main API

new SuperLogin(config, passport, userDB, couchAuthDB)

Constructs a new instance of SuperLogin. All arguments are optional. If you don't supply any config object, default settings will be used for a local CouchDB instance in admin party mode. Emails will be logged to the console but not sent.

  • config: Your full configuration object.
  • passport: You can pass in your own instance of Passport or SuperLogin will generate one if you do not.
  • userDB: This is the database that SuperLogin uses to keep track of users, distinct from CouchDB's _users database. You can pass in a PouchDB instance here or otherwise specify your database name in the config under dbServer.userDB.
  • couchAuthDB: This should point to your CouchDB _users database or something else if you just want to test. Specify in config or pass in a PouchDB object here.

Returns: the complete SuperLogin API.

superlogin.config

A reference to the configuration object. Use this to lookup and change configuration settings at runtime. key is a dot path string to the item you want to look up. For example 'emails.confirmEmail.subject'

  • superlogin.config.getItem(key)
  • superlogin.config.setItem(key, value)
  • superlogin.config.removeItem(key)
superlogin.router

A reference to the Express Router that contains all of SuperLogin's routes.

superlogin.passport

A reference to Passport

superlogin.userDB

A PouchDB instance that gives direct access to the SuperLogin users database

superlogin.couchAuthDB

A PouchDB instance that gives direct access to the CouchDB authentication (_users) database. (Not used with Cloudant.)

superlogin.registerProvider(provider, configFunction)

Adds support for additional Passport strategies. See below under Adding Providers for more information.

superlogin.validateUsername(username)

Checks that a username is valid and not in use. Resolves with nothing if successful. Resolves with an error object in failed.

superlogin.validateEmail(email)

Checks that an email is valid and not in use. Resolves with nothing if successful. Resolves with an error object in failed.

superlogin.validateEmailUsername(email)

The same as above, but for use when you are using email as the username. (local.emailUsername set to true.)

superlogin.getUser(login)

Fetches a user document by either username or email.

superlogin.createUser(form, req)

Creates a new local user with a username and password.

form requires the following: username, email, password, and confirmPassword. name is optional. Any additional fields must be whitelisted in your config under userModel or they will be removed.

req should contain protocol and headers.host to properly generate the confirmation email link. ip will be logged if given.

superlogin.onCreate(fn)

Use this to add as many functions as you want to transform the new user document before it is saved. Your function should accept two arguments (userDoc, provider) and return a Promise that resolves to the modified user document. onCreate functions will be chained in the order they were added.

superlogin.onLink(fn)

Does the same thing as onCreate, but is called every time a user links a new provider, or their profile information is refreshed. This allows you to process profile information and, for example, create a master profile. If an object called profile exists inside the user doc it will be passed to the client along with session information at each login.

superlogin.socialAuth(provider, auth, profile, req)

Creates a new user following authentication from an OAuth provider. If the user already exists it will update the profile.

  • provider: the name of the provider in lowercase, (e.g. 'facebook')
  • auth: credentials supplied by the provider
  • profile: the profile supplied by the provider
  • req: used just to log the user's ip if supplied
superlogin.hashPassword(password)

Hashes a password using PBKDF2 and returns an object containing salt and derived_key.

superlogin.verifyPassword(hashObj, password)

Verifies a password using a hash object. If you have a user doc, pass in local as the hash object.

superlogin.createSession(user_id, provider, req)

Creates a new session for a user. provider is the name of the provider. (eg. 'local', 'facebook', twitter.) req is used to log the IP if provided.

superlogin.changePassword(user_id, password)

Changes the user's password.

superlogin.forgotPassword(email, req)

Sends out the forgot password email and issues a reset token.

superlogin.resetPassword(form, req)

Resets the user's password. Required fields are token (from the forgot password email), password, and confirmPassword.

superlogin.changeEmail(user_id, newEmail)

Changes the user's email. If email verification is enabled (local.sendConfirmEmail) then a new confirmation email will be sent out.

superlogin.verifyEmail(token, req)

Marks the user's email as verified. token comes from the confirmation email.

superlogin.addUserDB(user_id, dbName, type, designDoc, permissions)

Associates a new database with the user's account. Will also authenticate all existing sessions with the new database.

  • dbName: the name of the database. For a shared db, this is the actual path. For a private db userDBs.privatePrefix will be prepended, and ${user_id} appended. (required)
  • type: 'private' (default) or 'shared' (optional)
  • designDoc: the name of the designDoc (if any) that will be seeded. (optional)
  • permissions: an array of permissions for use with Cloudant. (optional)

If the optional fields are not specified they will be taken from userDBs.model.{dbName} or userDBs.model._default in your config.

superlogin.removeUserDB(user_id, dbName, deletePrivate, deleteShared)

Deauthorizes the specified database from the user's account, and optionally destroys it.

  • dbName: the full path for a shared db, or the base name for a private db
  • deletePrivate: when true, will destroy a db if it is marked as private
  • deleteShared: when true, will destroy a db if it is marked as shared. Caution: may destroy other users' data!
superlogin.logoutUser(user_id, session_id)

Logs out all of a user's sessions at once. If user_id is not specified SuperLogin will look it up from the session_id.

superlogin.logoutSession(session_id)

Logs out the specified session.

superlogin.logoutOthers(session_id)

Logs out all of a user's sessions, except for the one specified.

superlogin.removeUser(user_id, destroyDBs)

Deletes a user, deauthorizes all the sessions, and optionally destroys all private databases if destroyDBs is true.

superlogin.confirmSession(token, password)

Logs out all of a user's sessions, except for the one specified.

superlogin.removeExpiredKeys()

Deauthorizes every single expired session found in the user database.

superlogin.sendEmail(templateName, email, locals)

Renders an email and sends it out. Server settings are specified under mailer in your config.

  • templateName: the name of a template object specified under emails in your config. See config.example.js for details.
  • email: the email address that the email
  • locals: local variables that will be passed into the ejs template to be rendered
superlogin.quitRedis()

Quits Redis if that is the session adapter you are using. This is useful for cleanup when your server shuts down.

Releases

Moved to CHANGELOG.md

superlogin's People

Contributors

andyhasit avatar cgestes avatar colinsheppard10 avatar colinskow avatar euandreh avatar gregvis avatar jlcarvalho avatar marcbachmann avatar marcmcintosh avatar micky2be avatar peteruithoven avatar pluscubed avatar ralphtheninja avatar skiqh avatar staxmanade avatar sukantgujar avatar tohagan avatar ybian 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  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  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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

superlogin's Issues

Seeding/updating design docs into DBs post creation

So, during app life-cycle, design docs may be added/edited against private and shared DBs. Is there a mechanism in SL right now which can automatically update them into the existing DBs during app startup?

Why this is important?
The DBs are created and maintained by SL. It needs the design docs in a certain format (seed) and already has support to add them in the created DBs. However as the app develops further, there will be changes to these docs and it'd be tedious to have an update mechanism outside of SL.

As of now, my test app is in early stages of development, so it is trivial for me to reset the SL DBs and begin again, in case I need new design docs seeded, but this is not scale-able and I'd soon need to maintain new/changed design docs in both the CouchDB format as well as seed format and would need to come up with a process of updating these changes.

Adding an endpoint like 'validSession'

When reloading my SPA I need to know for sure that my current session (in local storage) is still valid.

I'm going through refresh for now, but ideally I would like to avoid writing in the database just for that.
But the idea would be really similar and could even trigger a login event on the frontend.

failing with `"status\":404,\"name\":\"not_found\",\"message\":\"missing\"`

Hi there. I'm following the Quick Start example in the README but getting the following message. Do you know what the issue could be? I'm running node v4.2.2.

Correction: I just tried the vanilla Quick Start example server code and it worked 👍 . My bad for trying to integrate this first into an existing express server. Now it's time to figure out why I get this message with when using the integrated example code.

screen shot 2016-03-29 at 10 06 20 am

Add more session adapter

Would be nice to be able to configure a different session adapter other than Redis or memory.

We are not sure we gonna use Redis for our project, so now we are stuck with using the memory adapter but like you can imagine this is far from being ideal.

[Question] I added another shared DB after some users registration.

Hello,

I added another shared DB after some users registration.
The access to the new Shared DB are generated with new users created after its creation.
But for previous Users, the access to this new Shared DB is not generated then they can't have access to it.
Is there a way to connect the previous created users to this new Shared DB ?

t0 : I config a A-sharedDB.
t1 : Some users registered with an access to the A-sharedDB.
t2 : I config a B-sharedDB.
t3 : New users registrered with an acccess to the A-sharedDB and B-sharedDB.

but when t1 users deconnect and reconnect to superLogin they only get an access to the A-sharedDB and not to the B-sharedDB.

Ron

Testing report

I've just run some very basic testing of your demo app with the following:

  • Win7 Latest Chrome 46.0.2490.80 m - Works well.
  • Android 4.4 - Works very well and live syncs ok
  • iPhone Safari - Works well and live syncs ok
  • Same with Firefox 41.02 and Firefox 42.0
    • Facebook Auth appears to work
    • Then nothing more displayed.
    • Does not sync with existing data from same FB account.
    • Does not save data between session (so probably not syncing at all)
  • Internet Explorer 11.0.9600.18059
    • shows login page but only displays title bar with header text - no login buttons or links.

Check if CouchDB is connected

I tried to connect my project with Cloudant with this code

  dbServer: {
    protocol: "https://",
    host: "cloudant_account.cloudant.com/_all_dbs",
    user: "cloudant_account",
    password:  CLOUDANT_ACCOUNT_PASSWORD,
    cloudant: true,
    userDB: "sl-users",
    couchAuthDB: "_users"
  }

But I think it doesn't work as the APIs don't work properly. Is there anyway for me to check if CouchDB is connected? Do you think it will be better if we can add a log for it? Also, if I made something wrong with my configurations, please help me!

Logout trigger a conflict error in CouchDB

Not entirely sure if this is a FE or BE issue but (considering the FE library) when logging out it might trigger the checkRefresh call which in fact will update the data in sl-users after this one has been read for the logout code.
Which will trigger a conflict error when logout tries to write the data back in sl-users.

Assuming that username == doc._id constrains how you can use the sl_users couch database.

Normally a single couch database contains a mixture of document types but sadly this appears to be rather difficult to do with the current superlogin structure. I'm basing this on a quick look at auth views like this one ...

  username: function(doc) {
     emit(doc._id, null);
  },

Please correct me Colin if I missed something here but if I'm right can we can perhaps find some workarounds for now and maybe include some refactoring to support mixed doc types in the user database in the future roadmap? Of course you may have had some other reasons I'm not aware of so please share them. I've seen one fork of this project that I think got the complete wrong message about CouchDb and went off to create a database per record type. I think they just blindly followed your pattern.

Apologies for my next Couch 101 rant ... I know it's all rather basic stuff but I'm unsure if these reasons were missed.

Why & How to mix doc types in one database ...

Maintaining a mixture of records in the one db instance also has many benefits including: Simpler/safer db syncing, using single updates via _bulk_docs and single gets via _all_docs, being able to watch all change with a single change feed and leverage many map/reduce view patterns that need to emit a mixture of record types (and often efficiently reduce and cache/index the result). In my current app I want to do this with the user docs but can't since they are in a different sluser database where _id values might collide unless prefixed (but this would break the current views that assume username == doc._id).

A common convention to distinguish document types is to place a prefix on _id values and/or to add a type attribute. The _id prefix means you can then use startkey and endkey in either a view or _all_docs queries to perform efficient indexed lookups for the subset of record of the given type.

UserDB's Urls Host

Hi. superlogin is very nice!! I love it. Best solution i found so far! :-)
I found one issue when you use a local couchdb-instance on the server.
In lib/user.js at createSession you build the url for the UserDBs with the dbServer.host, which is for example localhost:5984. But on the client, for example with IonicFramework, you can't connect to http://tokenxyz:passwordxyz@localhost:5984/dbname. Instead of localhost there must be the IP of my server. This should be configurable in the config on the server.

[how-to?] installation of Redis

Hello,

I have 2 questions concerning Redis

Why using reddit instead of a levelDown PouchDB ?

Is it possible to update the DOC in order to explain how to install Reddit and how to configure it for Superlogin ?

regards,
Ron

Update docs on private DB security

Hi,

From the current documentation in the readme, and looking into the code, I think the privateDBs are not restricted to just the user_ids they are based on. E.g. If I am logged in as A so long as I know B's user_id (and private DB URL), I can read/write to it.

Considering the above assumption is right, how can I add a design doc with validate_doc_update function to disallow users whose id won't match with the DB name? If I can do that, then even if the userID is leaked, a different user won't be able to read/write into this user's DB even after guessing the DBURL. Although CouchDB is new to me, I don't think I can get DB Name within the validate_doc_update function. As an alternative, is there a way in which I can match the logged in user's ID with the DBUrl on the server (although I take it that once the client has the DB URL, all requests would directly hit the remote CouchDB, not go through the express app)?

My intention is to prevent any user other than the privateDB user from having any access to it.

Thanks in advance!

Can't make the README example work

Following the quick start in the README, I can't register a new user:

# The node app is already up and running
$ curl -vX POST http://localhost:3000/auth/register \
    -H 'Content-Type:application/x-www-form-urlencoded' \
    -d '{"name": "Joe Smith", "username": "joesmith", "email": "[email protected]", "password": "bigsecret", "confirmPassword": "bigsecret"}'

*   Trying ::1...
* Connected to localhost (::1) port 3000 (#0)
> POST /auth/register HTTP/1.1
> Host: localhost:3000
> User-Agent: curl/7.43.0
> Accept: */*
> Content-Type:application/x-www-form-urlencoded
> Content-Length: 135
> 
* upload completely sent off: 135 out of 135 bytes
< HTTP/1.1 400 Bad Request
< X-Powered-By: Express
< Content-Type: application/json; charset=utf-8
< Content-Length: 228
< ETag: W/"e4-KPd++izl8BaMF99qid8Zxw"
< Date: Tue, 29 Sep 2015 11:42:06 GMT
< Connection: keep-alive
< 
* Connection #0 to host localhost left intact
{
  "error": "Validation failed",
  "validationErrors": {
    "email": [
      "Email can't be blank"
    ],
    "username": [
      "Username can't be blank"
    ],
    "password": [
      "Password can't be blank"
    ],
    "confirmPassword": [
      "Confirm password can't be blank"
    ]
  },
  "status": 400
}

Is there an extra setup step that I'm missing?

Does SuperLogin work with a PouchDB server?

Although the docs hint towards this, I wasn't sure. The reason why I ask is because I am leaning towards the PouchDB-find project for easier in-browser queries compared to the PouchDB views. It sadly only works (for remote instances) with PouchDB server or CouchDB 2.0 preview. I have decided to use PouchDB server and hence this question.

Thanks in advance!
Sukant

Event Emitter not working.

Hello,

Here is the example on superlogin / lib / index.js

  //Working
  emitter.on('testemit1', function (){
    console.log('1 - ring ring ring');
  });

  var mailer = new Mailer(config);
  var user = new User(config, userDB, couchAuthDB, mailer, emitter);
  emitter.emit('testemit1');
  emitter.emit('testemit2');
  var oauth = new Oauth(router, passport, user, config);

  //Not Working
  emitter.on('testemit2', function (){
    console.log('2 - ring ring ring');
  });

Using existing users from _user

I'm testing superlogin module and I'm able to create new users into the dbServer.userDB database and login with them.

Now I would like to login with an user from dbServer.couchAuthDB, without registering that user with superlogin with no success.

Is it supported to login with users from _users ?

Problems with using superlogin on apps

What's the problem ?

I've some problems using superlogin while creating an app with nodewebkit

I've used superlogin-demo as a base.

But had to overwrite some parts like the logout in routers and the authorization on the profile refresh.

The main problem is that in superlogin, passeport's "bearer" and header.authorization on http request are often used to find the user's session. But it looks like if the server and the client aren't hosted on the same computer, it doesn't work.

Here are some "patch" that I used in my fork :

Commit 1
Commit 2

How to repeat the problem, without Nodewebkit ?

  1. Clone superlogin-demo
  2. Configure superlogin-demo server, and start it with npm start (like said in the Readme)
  3. Copy the client part on an apache Server
  4. Start the apache server, and access to the superlogin-demo Client by Apache.
  5. Remove "<base href="/">" in index.html, and remove the use of $location in src/app.js and src/token/token.js (By passing the problem parts as comment)
  6. In src/app.js, configure superloginConfig.baseUrl to set your nodejs server url (Like http://localhost:3000/auth/)
  7. Sign-up and/or log-in
  8. Try to** log out** and you should have also a 401 error on superlogin-demo Server
{ error: 'unauthorized', status: 401 }
POST /auth/logout 401
  1. You'll have the same problem when you try to access to the user'sprofile. But to see the 401, you have to modify the url of the http.get in src/profile/profile.js and you should see 401 error on superlogin-demo Server. Url sample : http://localhost:3000/user/profile

If you try it in localhost on you computer, you should need to add cors to the nodejs server

I'll try to easily upload a .zip with the nodejs server and apache client ready, so it'll be more easy to setup.

I've also tried it with a distant server, to see if it's not the cors and localhost the problem but the same problem occurs.

So, is there a way to solve more easily my problems than my patches ?

Option to set the password constraints

I would like to be able to choose myself what is mandatory for the password for my users.
Being stuck to "minimum 6 characters" is not ideal for the application I'm trying to build.

If it already exist please point me to the right direction.

Implement rate limiting middleware to prevent attacks

Routes like /login are already protected, but request spamming could compromise security in other ways such as with password reset or run up a high bill with the CouchDB provider. I would like to implement rate limiting middleware such as express-brute in order to mitigate such risks.

Setting loginOnRegistration to true does not return a session

setting local.loginOnRegistration: true, does not return a session.

Tested it on your sample app where the issue also exist.

Looks like you are looking for security.loginOnRegistration, however per the docs it should local.loginOnRegistration

trying to reset password with a token generates an error

havent had a chance to look into it but here is the error

Unhandled rejection TypeError: user.createSession is not a function
node-2 | 2016-01-08T05:08:20.783460263Z     at /usr/src/app/node_modules/superlogin/lib/routes.js:114:25
node-2 | 2016-01-08T05:08:20.783483814Z     at processImmediate [as _immediateCallback] (timers.js:383:17)
node-2 | 2016-01-08T05:08:20.783502865Z From previous event:
node-2 | 2016-01-08T05:08:20.783520506Z     at /usr/src/app/node_modules/superlogin/lib/routes.js:112:10
node-2 | 2016-01-08T05:08:20.783539709Z     at Layer.handle [as handle_request] (/usr/src/app/node_modules/express/lib/router/layer.js:95:5)
node-2 | 2016-01-08T05:08:20.783556118Z     at next (/usr/src/app/node_modules/express/lib/router/route.js:131:13)
node-2 | 2016-01-08T05:08:20.783571577Z     at Route.dispatch (/usr/src/app/node_modules/express/lib/router/route.js:112:3)
node-2 | 2016-01-08T05:08:20.783586956Z     at Layer.handle [as handle_request] (/usr/src/app/node_modules/express/lib/router/layer.js:95:5)
node-2 | 2016-01-08T05:08:20.783602485Z     at /usr/src/app/node_modules/express/lib/router/index.js:277:22
node-2 | 2016-01-08T05:08:20.783617911Z     at Function.process_params (/usr/src/app/node_modules/express/lib/router/index.js:330:12)
node-2 | 2016-01-08T05:08:20.783633408Z     at next (/usr/src/app/node_modules/express/lib/router/index.js:271:10)
node-2 | 2016-01-08T05:08:20.783648799Z     at Function.handle (/usr/src/app/node_modules/express/lib/router/index.js:176:3)
node-2 | 2016-01-08T05:08:20.783664379Z     at router (/usr/src/app/node_modules/express/lib/router/index.js:46:12)
node-2 | 2016-01-08T05:08:20.783679645Z     at Layer.handle [as handle_request] (/usr/src/app/node_modules/express/lib/router/layer.js:95:5)
node-2 | 2016-01-08T05:08:20.783695292Z     at trim_prefix (/usr/src/app/node_modules/express/lib/router/index.js:312:13)
node-2 | 2016-01-08T05:08:20.783710675Z     at /usr/src/app/node_modules/express/lib/router/index.js:280:7
node-2 | 2016-01-08T05:08:20.783726098Z     at Function.process_params (/usr/src/app/node_modules/express/lib/router/index.js:330:12)
node-2 | 2016-01-08T05:08:20.783741798Z     at next (/usr/src/app/node_modules/express/lib/router/index.js:271:10)
node-2 | 2016-01-08T05:08:20.783757351Z     at logger (/usr/src/app/node_modules/morgan/index.js:136:5)
node-2 | 2016-01-08T05:08:20.783773602Z     at Layer.handle [as handle_request] (/usr/src/app/node_modules/express/lib/router/layer.js:95:5)
node-2 | 2016-01-08T05:08:20.783794548Z     at trim_prefix (/usr/src/app/node_modules/express/lib/router/index.js:312:13)
node-2 | 2016-01-08T05:08:20.783832303Z     at /usr/src/app/node_modules/express/lib/router/index.js:280:7

user registration error

Hi,

If I try to register a user on local secured couchdb instance with "userDBs" option. It fails with

[SyntaxError: Unexpected token u]
[SyntaxError: Unexpected token u]
SyntaxError: Unexpected token u
at Object.parse (native)

at /home/dixitne/work/nodetest/node_modules/superlogin/lib/dbauth/index.js:245:39

Here is my sample config

var config = {
dbServer: {
cloudant: false,
protocol: 'http://',
host: 'localhost:5984',
user: 'test1',
password: 'test1234',
userDB: 'sl-users',
couchAuthDB: '_users'
},
userDBs: {
defaultDBs: {
private: ['test']
}
}
}

I tried to debug and the problem is with the way it tries to send request to create the user on localDB which in turn is because of the way superagent.js client parses the url.

I was able to get this working for local env by changing the "getDBURL()" in util.js to not add username and password to the url instead use

.auth('couchdb_admin_username', 'couchdb_admin_password')

in "createDB()"

I hope this helps in fixing the problem

Thanks,
Neeraj

Downgrade [email protected]

Hello all,

I've been working a little with this awesome plugin and I found an issue with nodemailer, when I try to register a new user with sendConfirmEmail: true I receive:

{ cause: [Error: Unsupported configuration, downgrade Nodemailer to v0.7.1 to use it],
  isOperational: true }

I managed to solve it by doing so... But I still have the feeling that I'm missing something since by default Superlogin installs [email protected]. Could you guys give me some clues about it?

Thanks a lot!

Pouchdb client usage

First off, this really looks like a great solution and the first library I have seen to support all thèse features. Nice job!

Do you have an idea how this could work with pouchdb in the browser? Maybe as a better replacement to pouchdb-authentication?

Unit tests have interdependencies

Some unit tests depend on state from prior tests or on test case order. This is an anti-pattern for unit testing.

This means that you can't selectively run unit test cases (using it.only("...") { ... }).

Example: user.spec.js ... it("should prepare the database", ...) and it("should destroy all the test databases" ...) create and destroy databases. I think they should be converted to before() and after() or perhaps beforeEach() or afterEach().

dbServer.userDB is Public

At launch, the dbServer.userDB doesn't have any security.

dbServer.user should be the only account to access it.

Help with example code

Hi! This project sounds really interesting, and I wanted to give it a try.
But when I copied the code from the example, I got this error:

Memory Adapter loaded
/var/www/pouch-superlogin/node_modules/superlogin/lib/util.js:99
  Object.keys(providers).forEach(function(provider) {
         ^
TypeError: Object.keys called on non-object
    at Function.keys (native)
    at Object.exports.addProvidersToDesignDoc (/var/www/pouch-superlogin/node_modules/superlogin/lib/util.js:99:10)
    at new module.exports (/var/www/pouch-superlogin/node_modules/superlogin/lib/index.js:51:21)
    at Object.<anonymous> (/var/www/pouch-superlogin/index.js:39:18)
    at Module._compile (module.js:460:26)
    at Object.Module._extensions..js (module.js:478:10)
    at Module.load (module.js:355:32)
    at Function.Module._load (module.js:310:12)
    at Function.Module.runMain (module.js:501:10)
    at startup (node.js:129:16)
    at node.js:814:3

I tried adding providers: { local: true } to the config object, and that seemed to work, but the next step (posting to /auth/register) is failing:

{ error: 'Validation failed',
  validationErrors: [TypeError: Cannot read property 'match' of undefined],
  status: 400 }

So maybe adding local: true was not a good idea =)
Could you provide directions on how to get the example code running?

Warning in crypto.pbkdf2 with NodeJS 6.

I recently upgraded to NodeJS 6.1.0, npm 3.8.6. Now Superlogin APIs result in this deprecation warning -

(node:11324) DeprecationWarning: crypto.pbkdf2 without specifying a digest is deprecated. Please specify a digest

Cookie/Header based authentication of userDBs

Hi,

Currently the util.getDBURL API injects username and password into the private DB URLs. Is there a way to use session tokens for authorization instead of this? I understand that in Production, the Superlogin server will be behind SSL, but if there's an alternative, it'd be great.

Thanks!

Conditionally add multiple DBs to new users

I want to add several user DBs when a particular type of user signs up. I can't do this with defaultDBs as I don't want create these DBs for every user account, only ones with a particular type. Here's my code:

var dbs = [
  'example',
  'dbs',
  'to',
  'add'
];

superlogin.on('signup', function(userDoc, provider) {
  if (userDoc.user_type === 'special') {
    dbs.forEach(function(db) {
      superlogin.addUserDB(userDoc._id, db).then(function(res) {
        console.log('Success: ' + res);
      }, function(err) {
        console.log('Error: ' + err);
      });
    });
  }
});

dbs is an array of the DB names that I want to create for a user with the user_type "special". The first DB gets created but the others all throw "Document update conflict" errors. I'm assuming that I'm using addUserDB incorrectly, but am stumped as to how. Any help would be appreciated! I've tried other approaches as well but nothing seems to work.

userModel is not extended correctly

When extending the user model, nested arrays are not concatenated their positions are overwritten.

ie:

var userModelConfig = {whitelist: ['1','2','3','4','5','6']}
var userModel = {whitelist: ['a','b','c']}

$.extend(true, {}, userModel, userModelConfig)
//returns
{"whitelist":["1","2","3","4","5","6"]}

and
$.extend(true, {}, userModelConfig, userModel)
"{"whitelist":["a","b","c","4","5","6"]}"

As you can see the index position of the array is overwritten rather then extend or merged.

A potential solution would be to transform the arrays into objects, extend them and transform them back.

I was going to try to fix it and submit a pull request, but i dont have the time tonight so i wanted to get this to you, maybe you have a quicker solution.

Enhancement: `getUserDB` method

Please add a getUserDB(user_id, dbName, type) method which returns a PouchDB instance with admin access. This will allow the server API to easily manipulate the user DB with admin privileges. Without this API, the only way is to share the CouchDB admin credentials separately with the rest of the server code.

This will also go in parallel with other PouchDB instances provided by superlogin, like usersDB and couchAuthDB.

expose a different url for dbServer

The config of the database url on the server (local ip) might differ from the url the client need to access from (public ip).
protocol, host and port could be completely different.

Change defaultRoles at runtime

Hi all!

I'm trying to change 'defaultRoles' at runtime usign superlogin.config.setItem as indicated on Main API documentation, I printed out the config after change it so I'm sure setItem works ok ,but when I'm registering new users it gets the value previously defined on my config file.

exports.postUser = function(req, res, next){
  superlogin.config.setItem('security.defaultRoles', [ 'user', 'whatever' ])
  superlogin.createUser(req.body, req)
    .then(function(){
      res.status(201).json({ok: true, success: 'User created'})
    }, function(err){
      res.json(err)
   })
}

Any help will be appreciated

Easy management of user access to databases

Introduction :

Imagine that you create an App, and you want to only allow some users (Premium account) to access to the server to sync (like evernote).

Actually, you have to do it manualy, by calling superlogin.addUserDB and superlogin.removeUserDB by http requests (on another protocol).

It would be nice to only manage it like this :

The userDoc has an variable (array) of roles / access.
And in the config file, or when you make an addUserDB, you can specify the required access.

userDoc.access = ["basic", "premium"]; //The user's accesses
databases = {
        "basicDatabase" : {
               "requireUserAccess" : [] //Everybody can access it !         
        },
        "premiumDatabase":{
            "requireUserAccess" : ["premium"] //Only users with "premium" access can access it.
        }
}

Then, you only have to manage the user's accesses, and when the user log-in, the session.userDBs is refreshed and allow the front part to easily identify the allowed ones.

What this feature must not forget :

  • It's mainly handled in the configuration, but it should not forget the manuals superlogin.addUserDBs. When you call superlogin.addUserDBs, you may be able to specify an requiredUserAccess for that database, so it can also benefit of the automatic user access management

Technical Debates

1) Should the new "user access management" use userDoc.access or userDoc.role ?

For me, it should use a special access variable, because userDoc.role and userDoc.permissions are used for technical access to Cloudant and CouchDB ['_reader', '_writer', '_replicator'].

But I'm still questionning about the userDoc.role, for me it would be nice to use it, but what I saw in lib/dbauth/cloudant.js , it's transformed to permissions.

2) The process of management

Solution A)

Use a filter on the userDBs sent into the session. I've a sample of that solution in this commit.

Solution B)

At the start of the user's session creation, parse userDoc.personalDBs and compare it to the access difined in the config (and don't forget the ones addes by addUserDB).

While parsing and comparing :

Do the user has access to that database ?
 YES = Do you already have that database in userDoc.personalDBs ?
         YES = No action
         NO = superlogin.addUserDB(user, databaseName, ...)
 NO  = Do you have that database in userDoc.personalDBs ?
         YES = superlogin.removeUserDB(user, databaseName, ...)              
         NO  = No action

And even, to optimize and not always do a parsing, you can use a system of databaseSignature.

A) A superlogin config has a signature (like "abcdefg45")
B) A userDoc has a personalDBs signature
B) Before doing the parsing & comparing, the management system check the user's signature and config signature, and if it's the sames : the system know it doesn't need to re-do the checking.

I've tried something similar in this commit

3) When execute the proccess management ?

For me the best, it to do it on the user's session creation, so it's not a big bottlenek, it won't surcharge the server, parsing all the users, and the front can get the good session.userDBs.

I think this feature might be really useful, and make superlogin more easy to use.

SuperLogin ForgotPassword UseCase - Reset Pasword Link for an hybrid mobile App.

Hello,

I try to implement the ForgotPAssword UseCase on a Cordova / PhoneGap / Ionic Mobile Hybrid App.

I need to manage the password reset from that and not from a website...

I launch the command :

superlogin.forgotPassword("[email protected]");

And I receive a proper email :
image

When I click on the link :
I got this error : Cannot GET /password-reset/mK9671GeQKWOo74U4Y6MIw

and from the Heroku Logs :

2016-04-24T09:18:20.356789+00:00 heroku[router]: at=info method=GET path="/password-reset/mK9671GeQKWOo74U4Y6MIw" host=*******-superlogin.herokuapp.com request_id=8a4be95f
-699a-4abc-af19-41da2f0022a1 fwd="78.***.***.64" dyno=web.1 connect=0ms service=2ms status=404 bytes=427

How Can I make this working properly ?

I can see on the Superlogin-demo the same e-mail with this link :

On the superlogin Demo there is a Sensegrid redirection like :

image

https://u19**480.ct.sendgrid.net/wf/click?upn=4RVtDsLk14tpYYyLSbT246p1i9JuyL69W3GiHHUdnVKuej6uxOSagz5XJ-2Ff7-2FHnwif3PaOVAz6M1xcMFA9-2Fnhl5hPrHL616COgWtIw_BQvb1-2B8lEcvg1yJWswS37EQzdeiO5rjciTkF5tMolWwvOW0rYeC9leKCRGNN7toZFCHQvphEzfXwnqq6QZFIRLku0kYTN-2F7pfIZqFk1HoRhzLnGgcoeZPPMEGIidjl-2FA5B2rJUZQzXcjHUfgcILtMYrtiGU6ROx6rYrjDjc-2Bg-2B-2FPHSNN6ssq7xDB1C4tejBm-2F12cUPN6f-2F-2FpnnsHk5P13Q-3D-3D

This link redirect to the superlogin-demo

What Can I do in my context ?
[Mobile hybrid App] => [Email with Reset Link] => [form hybrid App] is not possible.

Is this workflow a good one ?

Like something with a code pin :

  1. "Click Forgot Pwd Button"
  2. "An email is sent with a CODE PIN"
  3. "The user can type a Form on the app with
  • a code pin
  • his email
  • new password
  • confirm new password.

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.