Skip to content

A simple Node.js module to manage users sessions on a web application or any kind of JS apps It uses a Singleton pattern to ensure that only one instance of the module is running at a time. SessionManager is a singleton class that can be used to manage users sessions. For every user that logs in, a new session is created and stored in the databa…

License

Notifications You must be signed in to change notification settings

Elius94/users-session-manager

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

91 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

GitHub Workflow Status Coverage Status npm version npm npm bundle size GitHub

users-session-manager

A simple Node.js module to manage users sessions on a web application or any kind of JS apps It uses a Singleton pattern to ensure that only one instance of the module is running at a time. SessionManager is a singleton class that can be used to manage users sessions. For every user that logs in, a new session is created and stored in the database. Every session has a unique ID that is generated by the system. Every session has a setTimeout that expires after a certain time (setSessionTimeout). When a user logs out, the session is deleted from the class. Every action fires an event that can be used to listen to the session manager.

Readme Card https://nodei.co/npm/users-session-manager.png?downloads=true&downloadRank=true&stars=true

Installation

Install with:

npm i users-session-manager

Example of usage:

// Import module with ES6 syntax
import { SessionManager } from 'users-session-manager'
// or
// const SessionManager = require('users-session-manager')

// Create a new instance of the SessionManager class
const SM = new SessionManager()

// Change session Expiration time:
SM.setSessionTimeOut(6)

// Call this to initialize a new user session
SM.loadNewSession("Luca")
SM.loadNewSession("Fabio")

// You can listen to events emitted from this library through eventEmitter object exported
SM.on("activeUserDeleted", (key) => {
    console.log(`User ${key} has been deleted`)
})

setInterval(() => {
    console.log(SM.getLoggedUsers())
}, 5000)

Example of Frontend and Backend session exchange

// Frontend
let session_key = ""

/**
 * Function to call try_login API
 *
 * @param {*} user username text
 * @param {*} pwd password text
 * @return {*} false if wrong login or the user table ROW of the selected user JSON format
 */
async function TryLogin(user, pwd) {
    //console.log(ENDPOINT)
    let credentials = {
        "username": user,
        "password": md5(pwd)
    }
    const rawResponse = await fetch(ENDPOINT + API_ROUTE, {
        method: 'POST',
        headers: {
            'Accept': 'application/json',
            'Content-Type': 'application/json',
            'api_name': 'try_login'
        },
        body: JSON.stringify(credentials)
    })
    const user_data = await rawResponse.json()
    if (user_data.length > 0)
        session_key = user_data[0].session_key // save session key to the global variable.

        //console.log("user_data: ", user_data)
    return user_data
}

// And on the next calls, you can use the session_key to call the API

/**
 * Function to call get_table_data API
 *
 * @param {*} siteid number
 * @return {*} JSON object
 */
async function GetTableData(page) {
    let body = {
        page
    }
    const rawResponse = await fetch(ENDPOINT + API_ROUTE, {
        method: 'POST',
        headers: {
            'Accept': 'application/json',
            'Content-Type': 'application/json',
            'session_key': session_key,
            'api_name': 'get_table_data'
        },
        body: JSON.stringify(body)
    })
    const sectors = await rawResponse.json()
    if (sectors.logout) Logout()
        //console.log("sectors: ", sectors)
    return sectors
}

// Backend

// API.js route (cutted from the original file)
...
case 'try_login':
    response = {
        accepted: false,
        message: '',
        user_data: {}
    }
    if (typeof(req.body) === 'object') {
        try {
            const body = req.body
            const db_response = await db.tryLogin(body.username, body.password, true) // true to get the session key
            if (db_response !== false) {
                response.accepted = true
                response.message = 'Welcome! 😘'
                response.user_data = db_response
                response.user_data.session_key = loadNewSession(body.username) // generate a new session key
            } else {
                response.accepted = false
                response.message = 'Wrong username or password... Are you a f**ing HACKER? 💩💩💩'
            }
        } catch (error) {
            response.accepted = false
            response.message = 'Error in API call!'
            response.user_data = null
        } finally {
            res.send(JSON.stringify(response))
        }
    }
    break
case 'get_table_data':
    response = {
        accepted: false,
        message: '',
        table_data: {}
    }
    if (typeof(req.body) === 'object') {
        try {
            const body = req.body
            if (await db.validateApiRequest(req.headers.session_key, "get_data")) {
                const dbResponse = await db.getTableData(body.table)
                if (dbResponse !== false) {
                    response.accepted = true
                    response.message = 'OK'
                    response.table_data = dbResponse
                } else {
                    response.accepted = false
                    response.message = 'Error in API call!'
                    response.table_data = null
                }
            } else {
                response.accepted = false
                response.message = 'Action not allowed!'
                console.warn('Action not allowed! api_name:', api_name)
            }
        } catch (error) {
            response.accepted = false
            response.message = 'Error in API call!'
            response.analytics = null
        } finally {
            res.send(JSON.stringify(response))
        }
    }
    break
...

// In file db.js (cutted from the original file)
...
/** 
 * @async
 * @description Validate the session key
 * @param {string} sessionKey Session key
 * @param {string} action Action to validate
 * @throws Will throw if query to DB will fail
 * @returns {Promise<boolean>} Return true if session key is valid, false otherwise
 */
async function validateApiRequest(sessionKey, action = undefined) {
    const username = getUsernameFromSessionKey(sessionKey)
    if (username) {
        let user_data = undefined
        const query_user_id = {
            text: 'SELECT users_management, dataset_management ' +
                'FROM users WHERE username = $1;',
            values: [username]
        }
        try {
            const userIdRes = await pool.query(query_user_id)
                // console.log('[getUserProfilePic]', userProfilePicPathRes.rows)
            if (!userIdRes.rows.length) {
                user_data = undefined
                return false
            } else {
                /* This may be a string or null */
                user_data = userIdRes.rows[0]
            }
        } catch (err) {
            console.error(err)
            throw err.message
        }
        switch (action) {
            case "get_data":
                {
                    // check data validity here
                }
                break
            default:
                return true
        }
    }
    return false
}
...

Integrate with Socket.io server to notify clients

// Import module with ES6 syntax
import { SessionManager } from '../index.js';

const http = require('http')

// Create a new instance of the SessionManager class
const SM = new SessionManager();

/**
 * Create and start an ioSocket server
 * @param {*} app
 * "Express" handle
 * @param {*} port
 * Port the server should listen on
 * @returns {SocketIO.Server}
 * The newly created server
 */
 function startServer(app, port) {
    // Create an http server
    const server = http.createServer(app)
    server.listen(port)
    server.on('error', function(error) { onError(error, port) })
    server.on('listening', function() { onListening(server) })

    // Create the socketIO server
    const ENDPOINT = `localhost:3000`;
    const { Server } = require("socket.io");
    const io = new Server(server, {
        cors: {
            origin: ENDPOINT,
            methods: ["GET", "POST"]
        }
    });

    io.on('connection', (sk) => {
        console.log('Browser Connected!')
        sk.on('session_key', async function(data) {
            const key = data.session_key
            console.log(`User ${data.user} joined key ${key}`)
            sk.join(key)
        })
    })

    return io
}

SM.initSocketReferences(startServer(app, port)) // Initialize the socket references

SM.on("notifyClientToLogout", (io, key) => { // When a user logs out, notify the client
    console.debug(`Session is expired for key ${key}... Logging out now!`)
    io.in(key).emit('logout') // Emit the logout event to the client
})

Exported APIs

  • eventEmitter: Node.js Event Emitter object, is extended by the class. It fires the following events:
    • 'error': Called when some error happens (eg: Session is rejected)
    • 'sessionDeleted': Called when a session is deleted or if expired
    • 'sessionCreated': Called when a user session is created
    • 'notifyClientToLogout': Called when a session timer is expired, bind this to a Socket.io server to force clients to logout

Integrate with metrics tools like PM2

const io = require('@pm2/io') // Initialize the pm2 io module

// The PM2 IO metrics to monitor the number of connected users
const realtimeUser = io.counter({
    name: 'Realtime Users',
    id: 'app/realtime/users',
})

SM.on("sessionCreated", (key) => { // When a user logs out, notify the client
    realtimeUser.inc() // Increment the number of active users
})

SM.on("sessionDeleted", (key) => { // When a user logs out, notify the client
    realtimeUser.dec() // Decrement the number of active users
})

Classes

SessionManagerEventEmitter

SessionManager is a class that manages the sessions of the users.

Constants

sessions : Object

The sessions of the users.

MIN_SESSION_TIMEOUT : number

The minimum session timeout.

settings : Object

The settings of the session manager.

Functions

log(msg)void

Logs a message to the console if the debug flag is set to true in the config.

SessionManager ⇐ EventEmitter

SessionManager is a class that manages the sessions of the users.

Kind: global class Extends: EventEmitter

sessionManager.setSessionTimeOut(sessionTimeout) ⇒ boolean

This function is used to set the session timeout

Kind: instance method of SessionManager Returns: boolean - true or false: true if ok

Param Type Description
sessionTimeout number The session timeout in seconds

Example

setSessionTimeOut(3000) // Returns true or false

sessionManager.getSessionTimeout() ⇒ number

This function is used to get the session timeout

Kind: instance method of SessionManager Returns: number - The session timeout in seconds Example

getSessionTimeOut() // Returns 3000

sessionManager.getLoggedUsers() ⇒ array

This function is used to get the list of logged users

Kind: instance method of SessionManager Returns: array - The list of logged users Example

getLoggedUsers() // Returns ['Gino', 'Gino2']

sessionManager.initSocketReference(ioRef) ⇒ boolean

Function to copy the Socket IO http server reference

Kind: instance method of SessionManager Returns: boolean - true or false, true if ok

Param Type
ioRef *

sessionManager.getSocketReference() ⇒ SocketIO.Server

Function to get the socket reference

Kind: instance method of SessionManager Returns: SocketIO.Server - The socket reference

sessionManager.loadNewSession(username) ⇒ string

Function to add users sessions in this module. Use it at login

Kind: instance method of SessionManager Returns: string - user unique key

Param Type Description
username string The username provided on successful login

Example

addSession('Gino') // Returns 'session_key'

sessionManager.setSessionData(key, data) ⇒ boolean

Function to set the property 'data' of a session. Use it for example to store something in the session, like the user actions history, etc.

Kind: instance method of SessionManager Returns: boolean - true or false, true if ok Throws:

  • Error If the session_key is not found
Param Type Description
key string The session_key provided on successful login
data object The data to be stored in the session

Example

setSessionData('session_key', {'actions': ["logged in", ...]}) // Returns true or false

sessionManager.getSessionData(key) ⇒ object

Function to get the property 'data' of a session. Use it for example to get the user actions history, etc.

Kind: instance method of SessionManager Returns: object - The data stored in the session Throws:

  • Error If the session_key is not found
Param Type Description
key string The session_key provided on successful login

Example

getSessionData('session_key') // Returns {'actions': ["logged in", ...]}

sessionManager.restartSessionTimer(key) ⇒ boolean

Function that restart the session timer. Use it after an API call to keep the session alive.

Kind: instance method of SessionManager Returns: boolean - true or false, true if ok Throws:

  • Error If the session key is not found
Param Type Description
key string The session_key

Example

restartSessionTimer('session_key') // Returns true or false

sessionManager.getSessionDetails(key) ⇒ object | boolean

Function to get details of a session. Use it to get the username, the creation date and the data.

Kind: instance method of SessionManager Returns: object | boolean - The session details or false if not found Throws:

  • Error If the session key is not found
Param Type Description
key string The session_key

Example

getSessionDetails('session_key') // Returns {'username': 'Gino', 'createdAt': 1523456789, 'data': {'actions': ["logged in", ...]}}

sessionManager.deleteSession(key) ⇒ boolean

Function to delete users sessions in this module. Use it at client logout

Kind: instance method of SessionManager
Returns: boolean - true or false, true if ok Throws:

  • Error If the session_key is not found
Param Type Description
key string The session_key provided on successful login

Example

deleteSession('session_key') // Returns true or false

sessionManager.deleteAllSessions() ⇒ boolean

Function to delete all sessions

Kind: instance method of SessionManager Returns: boolean - true or false, true if ok

sessionManager.sendLogoutMessage(key) ⇒ boolean

Use this to notify the client to logout with WebSocket

Kind: instance method of SessionManager Returns: boolean - true or false, true if ok

Param Type Description
key string The session_key

Example

sendLogoutMessage('session_key') // Returns true or false

sessionManager.createNewSessionTimer(key, username) ⇒ NodeJS.Timeout

Function to return a new setTimeout object and start it.

Kind: instance method of SessionManager

Param Type Description
key string The session_key
username string The username, only for logging features

Example

createNewSessionTimer('session_key', 'username') // Returns a new setTimeout object

sessionManager.checkSessionStatus(key) ⇒ boolean

Use this before every API.js function execution.n the stored collection

Kind: instance method of SessionManager Returns: boolean - true or false: true if session is active Throws:

  • Error if the session is not valid
  • Error if the session is expired
Param Type Description
key string the user key generated at login

Example

checkSessionStatus('my_session_key') // true or false

sessionManager.getUsernameFromSessionKey(key) ⇒ string

Function to get the username from a session key

Kind: instance method of SessionManager Returns: string - The username or false if not found

Param Type Description
key string The session key

Example

getUsernameFromSessionKey('123456789_123456789') // 'username'

sessions : Object

The sessions of the users.

Kind: global constant

MIN_SESSION_TIMEOUT : number

The minimum session timeout.

Kind: global constant

settings : Object

The settings of the session manager.

Kind: global constant

log(msg) ⇒ void

Logs a message to the console if the debug flag is set to true in the config.

Kind: global function

Param Type
msg string

About

A simple Node.js module to manage users sessions on a web application or any kind of JS apps It uses a Singleton pattern to ensure that only one instance of the module is running at a time. SessionManager is a singleton class that can be used to manage users sessions. For every user that logs in, a new session is created and stored in the databa…

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published