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.
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
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
Import the model
const security = require('./security')
... // other imports and dependencies
const User = security.auth.model
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)
_spartan serves the full suite of firebase authentication methods both client-side (through firebase services) and server-side (via the admin SDK)
To utilize _spartan’s fire_auth()
services you need to meet the following requirements:
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')
}
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. |
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
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
The following errors may appear during user authentication/authorization: