authentication.js

ABOUT

The ability to autneticate and authorize your application users is foundational to any project’s success. _spartan provides capabilities to register new users and authenticate them locally via mongodb.

REQUIREMENTS & ASSUMPTIONS

Local authentication schemes require use of the following dependencies:

let mongoose = require('mongoose')
const bcrypt = require('bcrypt')
const authPolicy = require('../security.json').accessControlsPolicy.authenticationPolicy
const MAX_LOGIN_ATTEMPTS = authPolicy.passwords.lockout.attempts
const LOCK_TIME = authPolicy.passwords.lockout.automaticReset
let schema = require('../schemas/userSchema').UserSchema // <--- expects that you have built a user schema
let name = 'User'

At the time of this writing MongoDB is the only natively supported database for authentication purposes, though support for PostgresDB is currently under development

For local authentication, _spartan assumes you have defined a user schema like this

application root folder
|--schemas/
   |----userSchema.js (exported as UserSchema)
   |----other schema files
|--security/
   |----authentication.js (provided by \_spartan; accesses schemas and models)

Both username and email are expected to be unique in the schema

AVAILABLE METHODS

User Registration

method name description params returns
save saves a record to database of provided model in with the defined schema user model (called as user = new User) && a callback function null or Error
pre provides the ‘pre-save’ method which hashes provided password prior to saving to database will be called on the user model when 'save' method is invoked; next() middleware OR callback null or Error

USAGE

  1. Import the model

    const security = require('./security')
    ... // other imports and dependencies
    const User = security.auth.model
    
  2. In your registration POST route:

    let uData = new User({
      username: request.body.username,
      email: request.body.email,
      password: request.body.password
    })
    uData.save(function (err, user) { // 'pre' method will be called to hash the password prior to saving
      if (err) return next(err)
      else {
        app.locals.username = request.body.username
        request.session.userId = user._id
        response.redirect('/thanks')
      }
    })
    

User Sign-in

method name description params returns
getAuthenticated searches the database for the supplied user and compares password to see if it is valid email, password, callback user OR Error

USAGE

User.getAuthenticated(request.body.email, request.body.password, function (err, user, reason) {
      if (err) {
        next(err)
      }
      if (!user) {
        let error = new Error('No such user')
        error.status = 401
        next(error)
      }
      if (user) {
        request.session.userId = user._id
        return response.redirect('/profile')
      }
    })

User Sign-out

method name description params returns
N/a there are no methods for this purpose. Instead call request.session.destroy on the current running session request session void (destroyes the existing session && id) OR Error

USAGE

if (request.session) {
    request.session.destroy(function (err) {
      if (err) return next(err)
      else {
        response.redirect('/')
      }
    })
  }

Change Password

method name description params returns
changePassword updates the password for the currently authenticated user session_id alphanum String, oldPassword String, newPassword String updated user OR Error

USAGE

let updatedUser = changePassword(request.session._id, request.body.oldPassword, request.body.newPassword)

FIREBASE AUTHENTICATION

_spartan serves the full suite of firebase authentication methods both client-side (through firebase services) and server-side (via the admin SDK)

REQUIREMENTS

To utilize _spartan’s fire_auth() services you need to meet the following requirements:

  • firebase account with an app. If done correctly, you will be provided with a pre-made configuration value file.

    Treat the values contained in the firebase app configuration as SECRET. In your dotenv file, be sure to create and save the values as referenced in the configuration file.

_spartan will reference the values in the config file with the following variable names:

let firebaseConfig = {
      apiKey: secrets.fetchSecret('FIREBASE_API_KEY'),
      authDomain: secrets.fetchSecret('FIREBASE_AUTH_DOMAIN'),
      databaseURL: secrets.fetchSecret('FIREBASE_DB_URL'),
      projectId: secrets.fetchSecret('FIREBASE_PROJECT_ID'),
      storageBucket: secrets.fetchSecret('FIREBASE_STORAGE_BUCKET'),
      senderId: secrets.fetchSecret('FIREBASE_SENDER_ID')
    }
  • _spartan will attempt to initialize both the firebase application as well as any databases or storage services assoicated with the admin SDK.

User Records Several of the available methods return firebase ‘user records’ as the result of a resolved promise. This is the contents of the user record object:

property type description
uid string The uid to assign to the newly created user. Must be a string between 1 and 128 characters long, inclusive. If not provided, a random uid will be automatically generated.
email string The user’s primary email. Must be a valid email address.
emailVerified boolean Whether or not the user’s primary email is verified. If not provided, the default is false.
phoneNumber string The user’s primary phone number. Must be a valid E.164 spec compliant phone number.
password string The user’s raw, unhashed password. Must be at least six characters long.
displayName string The users’ display name.
photoURL string The user’s photo URL.
disabled boolean Whether or not the user is disabled. true for disabled; false for enabled. If not provided, the default is false.

* from firebase docs

AVAILABLE METHODS

User Registration

method name description params returns
register creates a new user associated with your project {register: { username: String, email: email, password: String}} Promise => { msg: success message, ur: user record } or Error

Both username and email are required to be unique

USAGE

where auth = security.fireAuth && first parameter MUST be String ‘firebase’:

{auth('firebase',
    { register: {
      username: request.body.username,
      email: request.body.email,
      password: request.body.password
    } })}

User Sign-in

method name description params returns
login authenticates user associated with your project {login: { email: email, password: String}} Promise => user’s record OR Error

firebase creates a session cookie for a successfully authenticated user. Reference sessions.js for more information

USAGE

auth('firebase', { login: { email: request.body.email, password: request.body.password } }).then(value => {
    if (value instanceof Error || value === undefined) {
      next(value)
    } else {
      app.locals.email = request.body.email
      app.locals.userData = value
      response.redirect('/profile')
    }
  })

User Sign-out

method name description params returns
logout destroys the current user’s session {logout: true } Promise => msg logout success message or Error

logout will only logout the currently signed-in user with the associated session cookie. If a user signs-in but never signs out, their session will remain valid until you explicitly destroy it.

USAGE

auth('firebase', { logout: true }).then(value => {
    if (value instanceof Error) {
      next(value)
    } else {
      console.log(value)
      response.redirect('/')
    }
  })

Reset Password

method name description params returns
resetPassword sends a password reset link to the email specified in params {logout: true } Promise => msg logout success message or Error

Neither _spartan nor firebase do any kind of validation to ensure that the the person requesting the reset actually owns the email in question. It is assumed that if the user has access to the email address, they are the authorized account owner. You may consider adding additional validations in addition to this to mitigate some of the risk of an unauthorized person gaining access to your application

USAGE

auth('firebase', { resetPassword: { email: request.body.email } }).then(value => {
    if (value instanceof Error) {
      next(value)
    } else {
      app.locals.email = request.body.email
      response.redirect('/login')
    }
  })

Change Password

method name description params returns
changePassword takes { changePassword: { old: String, new: String } } Promise => updatedUser modified userRecord or Error

Can only change password of currently logged in user.

USAGE

auth('firebase', { changePassword: { old: request.body.old, new: request.body.new } }).then(value => {
    if (value instanceof Error) {
      next(value)
    }
    if (value === undefined) {
      let error = new Error(`Problem changing password for user ${app.locals.email}`)
      error.status = 401
      next(error)
    } else {
      response.redirect('/profile')
    }
  })

* though the ‘password’ field is a string, it must adhere to the password complexity rules defined in the security.json password policy otherwise it will throw an error and the user will not be created

ERRORS

The following errors may appear during user authentication/authorization:

  • (‘auth/invalid-email-address’) => thrown if the email string is less than 5 characters
  • (‘auth/email-in-use’) => thrown if the email address is already in use
  • (‘auth/username-in-use’) => thrown if the username is already in use
  • (‘auth/password-too-short’) => thrown if the password string doesn’t adhere to the minimum length requirement defined in the password policy
  • (‘auth/weak-password’) => thrown if the password’s complexity does not adhere to the complexity standard defined in the password policy
  • (‘auth/wrong-password’) => thrown if the user’s password is incorrect. If the password policy includes attempt limits, this ‘wrong’ try will count against it. Once the attempt limit is reached, the account will be disabled for the duration of time defined in the lockout policy; also thrown during changePassword prior to update if the old password provided doesn’t match what’s in the database
  • (‘auth/user-disabled’) => thrown if the user’s account is disabled. This may come either through automated means (e.g. too many bad authentication attempts, so account was locked out) or if the project administrator has manually locked out the account. If the account was disabled as a result of too many authentication attempts, the account aill be automatically re-enabled after the timeout period defined in the lockout policy
  • (‘auth/too-many-requests’) => this is a firebase-specific error. If too many bad attempts are made against firebase’s service in a specified period of time, the user will not be able to log in.
  • (‘auth/no-user-session’) => thrown during changePassword if there is no session_id associated with the user (e.g. user is not logged in). If you receive this error, redirect to your sign in page

ADDITIONAL INFORMAITON