ugle-auth

2.2.5 • Public • Published

ugle-auth

An authentication system for NodeJS web apps using sqlite

ugle-auth icon

Quickstart

Install using this command:

npm install ugle-auth

Create two files using the command below:

touch .env
touch database.db

Use the following format to substitue your information in the .env file:

EMAIL_SENDER = "myawesomecompany@gmail.com"
EMAIL_DOMAIN = "gmail"
EMAIL_TOKEN = "abcdefghijklmnop"

AUTH_SALT = "A cool and hard to guess salt"

WEBAPP_DOMAIN = "https://myawesomecompany.com"

ADMIN_EMAIL = "myawesomecompany@gmail.com"
ADMIN_PASSWORD = "MyAwesomeP@ssw0rd"

Then use the following code in your main.js or index.js file. It will connect to a .db file at the path provided and setup all the routes needed to handle server authentication.

const dotenv = require('dotenv');
dotenv.config();

const ugle_auth = require('ugle-auth');
ugle_auth.connectToDatabase(`${__dirname}/database.db`, (err, dtb) => {
    if (err) {
        console.error(err.message);
    } else {
        ugle_auth.routes(app, dtb);
    }
})

Overview

There are two ways to use ugle-auth:

  • You can call our predefined route function and let the package do all the work, or...
  • You can call individual functions inside custom routing.

Security

I've taken steps to make this authentication system secure by including account lockout protocols for failed login attempts, sensitive data hashing, manual lockout procedurres, account verification, and more.

Password and Tempkey Hashing

The hashing algorithm used for passwords is below, and relies on the argon2 package for NodeJS:

hash = await argon2.hash(password, {
    timeCost: 16,
    memoryCost: 128 * 1024,
    parallelism: 2
})

These settings are intended to balance security and performance. Tempkeys are generated using the following algorithm:

crypto.pbkdf2(crypto.randomBytes(256).toString('hex'), crypto.randomBytes(256).toString('hex'), 999999, 255, 'sha512', (err, derivedKey) => {
    if (err) reject(err);
    resolve(derivedKey.toString('hex'));
});

Which is meant to be long, complex, and utterly random. While a single randomBytes function may have sufficed, even random functions have some predictability that can be exploited. Hashing two large random strings a large number of times is meant to be the solution to that vulnerability.

Login Attempts

If a login attempt makes it through all input validation AND finds a valid email but the password hashes do not match, then a failed_login_attempts integer is incremented. If the failed_login_attempts for a given email is equal to (or greater than) the lockout_policy variable (which is set to 4 by default), login attempts are denied until the password is reset.

Setup

Installation

npm install ugle-auth

Dependencies

You will probably be able to use this package with older or newer versions than these, but I know these work for sure.

"dependencies": {
    "argon2": "^0.30.3",
    "bcrypt": "^5.1.0",
    "dotenv": "^16.0.3",
    "express": "^4.18.2",
    "nodemailer": "^6.8.0",
    "sqlite3": "^5.1.2"
}

Usage

const ugle_auth = require('ugle-auth');

Preset Routing

// env variables
const dotenv = require('dotenv');
dotenv.config();


// app initialization
const express = require('express');
const app = express();


// session configuration
const session = require('express-session');
app.use(
    session({
        cookie: {
            // httpOnly: true,
            // secure: true,
            // sameSite: true,
            maxAge: 500 * 60 * 1000,
            // expires: 5 * 60 * 1000,
        },
        resave: true,
        saveUninitialized: true,
        secret: 'secret',
        secure: true,
    })
);

// body parsing
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// ejs view engine
const path = require('path');
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, '/../views'));




// ugle-auth functions
const ugle_auth = require('ugle-auth');
ugle_auth.connectToDatabase(`${__dirname}/database.db`, (err, dtb) => {
    if (err) {
        console.error(err.message);
    } else {


        // setting up database
        ugle_auth.setupDatabase(dtb, (err) => {
            if (err) {
                console.error(err.message)
            } else {
                console.info('database setup complete')
            }

        })


        // creating default admin account
        args = {
            'email': process.env.ADMIN_EMAIL,
            'password': process.env.ADMIN_PASSWORD,
            'created_by': 0
        }
        ugle_auth.createAdmin(dtb, args, (err) => {
            if (err) {
                console.error(err.message);
            } else {
                console.info('default admin created')
            }
        })


        // activating preset routing
        ugle_auth.routes(app, dtb);


        // listening on development port
        app.listen(3000);
        console.log('listening on port 3000');

    }
});

If you use the preset routing function, these are the endpoints that will be available on your server:

/auth                           GET 
/auth/signup                    GET & POST
/auth/login                     GET & POST
/auth/logout                    GET & POST
/auth/forgot-password           GET & POST
/auth/reset-password            GET & POST
/auth/refresh-session           GET
/auth/change-password           GET & POST
/auth/request-verification      GET & POST
/auth/confirm-verification      GET
/auth/delete-account            GET & POST
/auth/lock-account              GET & POST
/auth/unlock-account            GET & POST
/auth/add-permission            GET & POST
/auth/remove-permission         GET & POST

You'll also need a folder titled "auth" for the ejs files that correspond to each route. An "auth" folder is present within this package, and can be downloaded from GitLab here. If you'd rather make them yourself, these are the files you'll need within your ejs views directory:

auth/
    admin/
        add-permission.ejs
        lock-account.ejs
        remove-permission.ejs
        unlock-account.ejs
    layout/
        auth_begin.ejs
        auth_end.ejs
        messages.ejs
    change-password.ejs
    delete-account.ejs
    forgot-password.ejs
    login.ejs
    logout.ejs
    request-verification.ejs
    reset-password.ejs
    signup.ejs
    verify-request.ejs

Function Examples

path = `${__dirname}/database.db`;
await ugle_auth.connectToDatabase(path, async (err, dtb) => {
    if (err) {
        console.error(err.message);
    } else {
        console.log('connectToDatabase successful');
        global.dtb = dtb;
    }
});




await ugle_auth.setupDatabase(dtb, async (err) => {
    if (err) {
        console.error(err.message);
    } else {
        console.log('setupDatabase successful');
    }
});




await ugle_auth.formatDatabase(dtb, async (err) => {
    if (err) {
        console.error(err.message);
    } else {
        console.log('formatDatabase successful');
    }
});




perms = {
    'admin': false,
    'user': true
};
await ugle_auth.defaultPerms(perms, async (err) => {
    if (err) {
        console.error(err.message);
    } else {
        console.log('defaultPerms successful');
    }
});




attempts = 8;
await ugle_auth.lockoutPolicy(attempts, async (err) => {
    if (err) {
        console.error(err.message);
    } else {
        console.log('lockoutPolicy successful');
    }
});




url = '/auth/login';
await ugle_auth.loginRedirect(url, async (err) => {
    if (err) {
        console.error(err.message);
    } else {
        console.log('loginRedirect successful');
    }
});




args = {
    'email': 'admin.uglesoft@gmail.com',
    'password': 'P@ssw0rd',
    'created_by': 0
};
await ugle_auth.createAdmin(dtb, args, async (err) => {
    if (err) {
        console.error(err.message);
    } else {
        console.log('createAdmin successful');
    }
});




args = {
    'email': 'user.uglesoft@gmail.com',
    'password': 'P@ssw0rd',
    'created_by': 0
};
await ugle_auth.createUser(dtb, args, async (err) => {
    if (err) {
        console.error(err.message);
    } else {
        console.log('createUser successful');
    }
});




email = 'admin.uglesoft@gmail.com';
await ugle_auth.readUser(dtb, email, async (err, data) => {
    if (err) {
        console.error(err.message);
    } else {
        console.log('readUser successful');
        console.log(data);
    }
});




email = 'admin.uglesoft@gmail.com';
await ugle_auth.readUsers(dtb, email, async (err, data) => {
    if (err) {
        console.error(err.message);
    } else {
        console.log('readUsers successful');
        console.log(data);
    }
});




email = 'admin.uglesoft@gmail.com';
await ugle_auth.deleteUser(dtb, email, async (err) => {
    if (err) {
        console.error(err.message);
    } else {
        console.log('deleteUser successful');
    }
});




await ugle_auth.allUsers(dtb, async (err, data) => {
    if (err) {
        console.error(err.message);
    } else {
        console.log('allUsers successful');
        console.log(data);
    }
});




args = {
    'email': 'user.uglesoft@gmail.com',
    'password': 'NewP@ssw0rd',
};
await ugle_auth.changePassword(dtb, args, async (err) => {
    if (err) {
        console.error(err.message);
    } else {
        console.log('changePassword successful');
    }
});




args = {
    'email': 'user.uglesoft@gmail.com',
    'password': 'NewP@ssw0rd',
};
await ugle_auth.login(dtb, args, async (err, session) => {
    if (err) {
        console.error(err.message);
    } else {
        console.log('login successful');
        console.log(session);
        req.session = session;
    }
});




await ugle_auth.refreshSession(dtb, req.session, async (err, session) => {
    if (err) {
        console.error(err.message);
    } else {
        console.log('refreshSession successful');
        console.log(session);
        req.session = session;
    }
});




await ugle_auth.logout(req.session, async (err, session) => {
    if (err) {
        console.error(err.message);
    } else {
        console.log('logout successful');
        console.log(session);
        req.session = session;
    }
});




if (ugle_auth.navSession(req.session, res)) {
    console.log('navSession successful');
}




if (ugle_auth.navSessionUnverified(req.session, res)) {
    console.log('navSessionUnverified successful');
}




if (ugle_auth.apiSession(req.session, res)) {
    console.log('apiSession successful');
}




if (ugle_auth.apiSessionUnverified(req.session, res)) {
    console.log('apiSessionUnverified successful');
}




if (ugle_auth.navPermission(req.session, res, 'user')) {
    console.log('navPermission successful');
}




if (ugle_auth.apiPermission(req.session, res, 'user')) {
    console.log('apiPermission successful');
}




email = 'user.uglesoft@gmail.com';
await ugle_auth.lockAccount(dtb, email, async (err) => {
    if (err) {
        console.error(err.message);
    } else {
        console.log('lockAccount successful');
    }
});




email = 'user.uglesoft@gmail.com';
await ugle_auth.unlockAccount(dtb, email, async (err) => {
    if (err) {
        console.error(err.message);
    } else {
        console.log('unlockAccount successful');
    }
});




args = {
    'email': 'user.uglesoft@gmail.com',
    'permission': 'developer',
};
await ugle_auth.addPermission(dtb, args, async (err) => {
    if (err) {
        console.error(err.message);
    } else {
        console.log('addPermission successful');
    }
});




args = {
    'email': 'user.uglesoft@gmail.com',
    'permission': 'developer',
};
await ugle_auth.removePermission(dtb, args, async (err) => {
    if (err) {
        console.error(err.message);
    } else {
        console.log('removePermission successful');
    }
});




args = {
    'recipient': 'example@gmail.com',
    'subject': 'Requested Account Verification Link',
    'text': `=== Account Verification Link === Please copy and paste this link into your browser to verify your account: ${process.env.WEBAPP_DOMAIN}/auth/confirm-verification?tempkey=`,
    'html': `<h4>Account Verification Link</h4><p>Please click the link below to verify your account.</p><a href="${process.env.WEBAPP_DOMAIN}/auth/confirm-verification?tempkey=">Verify My Account</a>`
};
await ugle_auth.sendTempkeyEmail(dtb, args, async (err, data) => {
    if (err) {
        console.error(err.message);
    } else {
        console.log('sendTempkeyEmail successful');
        console.log(data);
    }
});




var args = {
    'email': 'user.uglesoft@gmail.com',
    'tempkey': req.query.tempkey,
};
await ugle_auth.verifyUser(dtb, args, async (err) => {
    if (err) {
        console.error(err.message);
    } else {
        console.log('verifyUser successful');
    }
});




args = {
    'email': req.query.email,
    'tempkey': req.query.tempkey,
    'password': req.body.password
};
await ugle_auth.resetPassword(dtb, args, async (err) => {
    if (err) {
        console.error(err.message);
    } else {
        console.log('resetPassword successful');
    }
});

Support

Reach out to uglesoft at uglesoft@gmail.com

Author(s)

Christian J Kesler, Uglesoft Openware ©

Acknowledgements

As always, I have ChatGPT to thank for helping me walk through my thought process and teach me new things as I go.

Package Sidebar

Install

npm i ugle-auth

Weekly Downloads

1

Version

2.2.5

License

MIT

Unpacked Size

365 kB

Total Files

33

Last publish

Collaborators

  • christiankesler