api/users

module:api/users

Description:
  • Users table management and DB operations

    This is a wrapper around users table to support multiple databases with the same interface, makes it easy to find users by id or login.

    Supports simple TOTP and email MFA code verification.

    User cookie sessions are stored in the sessions columns as a list of expiration time in ms. Every login or logout removes expired sessions from the list. To login a session must be present in the list.

    API token based authentication is implemented as well, the login and secret are generated by the module:api/users.prepareToken, the id can be provided or generated.

    Real users can have more than one API token by saving api tokens with the same user id.

    To enable it just add a table name of your choice to the bkjs.conf

    api-users-table = users
    

    Users and tokens can be created via shell commands:

    # bksh -user-add login admin name admin secret XXXXX roles admin
    '136ea1b9fb1778bd2d37096d'
    
    # bksh -user-add-token name test flags api -prefix api_
    'api_7e717a99e4b642ce88010629d32921b7caf92f4d165c46f59f983f880aa25c78'
    
Source:

Members

(static) args :Array.<object>

Description:
  • Module specific config parameters

Source:

(static) maxLength :number

Description:
  • Max length for user name, login

Source:
Default Value:
  • 140

(static) mfaAge :number

Description:
  • Age of a MFA code in ms

Source:
Default Value:
  • 600000

(static) schema

Description:
  • Default users table schema, each column is represented by a DbTableColumn type.

Source:
Properties:
Name Type Description
schema object
Properties
Name Type Description
login string

primary key, user email, name or other unique identifier

id string

unique auto-generated UUIDv4

name string

full user name

roles Array.<string>

list of roles for authorization

flags Array.<string>

tags, feature flags...

secret string

hashed user password

topt_secret string

generated secret for TOTP MFA encoded as base32, the present of the sedret will require TOTP code on every login

mfa_code string

used to enforce MFA verification and keep the last code sent, to enable set any non-empty value, when sent it is saved in the format: code,expires,type, a code, expiration in ms, sent method(email,text..)

pushkey string

can be used for push notifications, tokens: [service://]token[@appname]

passkey string

can be used for passkey verifications

sessions Array.<number>

list of sessions, expiration time

create_time number

create time in milliseconds

update_time number

last modified time, auto saved

access_time number

last access time

login_time number

last time user logged in

expire_time number

deny access if this value is before current date, ms

(static) table :string

Description:
Source:

Methods

(async, static) aadd(query, optionsopt) → {Object}

Description:
Source:
Parameters:
Name Type Attributes Description
query object | string
options object <optional>
Returns:
Type Description
{err:object, data:object}
Example
const { err, secret } = await lib.aprepareSecret("secret....");
const { err, data } = await api.users.aadd({ login: "john@mail.com", name: "John", secret });

(async, static) acheckSecret(query) → {Object}

Description:
Source:
Parameters:
Name Type Description
query object

an object with { login|id, secret } properties for login

Returns:
Type Description
{err:object, data:object}
Example
const { err, data } = api.users.acheckSecret({ id: "...", secret : "..." });

(static) add(query, optionsopt, callback)

Description:
  • Registers a new user, returns new record in the callback,

Source:
Parameters:
Name Type Attributes Description
query object

user record

options object <optional>
callback function(err:object, user:object)

returning new user record

Example
lib.prepareSecret("secret....", (err, secret) => {
    api.users.add({ login: "john@mail.com", name: "John", secret }, (err) => {
    });
});

(async, static) adel(query, optionsopt) → {Object}

Description:
Source:
Parameters:
Name Type Attributes Description
query object | string
options object <optional>
Returns:
Type Description
{err:object, data:object}
Example
const { err, data } = await api.users.adel("john@mail.com");

const { err, data } = await api.users.adel({ login: "john@mail.com" });

(async, static) aget(query, optionsopt) → {Object}

Description:
Source:
Parameters:
Name Type Attributes Description
query object | string
options object <optional>
Returns:
Type Description
{err:object, data:object}
Example
const { err, data } = await api.users.aget("john@mail.com");

(async, static) alogin(context) → {Object}

Description:
Source:
Parameters:
Name Type Description
context RequestContext
Properties
Name Type Description
body object

login credentials

Properties
Name Type Attributes Description
login string

username or email

secret string

clear text password

code string <optional>

optional MFA/TOTP code if enabled

Returns:
Type Description
{err:object, user:object}
Example
api.app.post("/login", async (context) => {

    const { err, user } = await api.users.alogin(context);

    context.reply(err);
});

(static) alogout(context) → {Promise}

Description:
Source:
Parameters:
Name Type Description
context RequestContext
Returns:
Type Description
Promise

(async, static) aupdate(query, optionsopt) → {Object}

Description:
Source:
Parameters:
Name Type Attributes Description
query object
options object <optional>
Returns:
Type Description
{err:object, data:object}
Example
const { err, data } = await api.users.aupdate({ login: "john@mail.com", name: "John" });

(static) checkSecret(query, callback)

Description:
  • Verify user by login and secret

Source:
Parameters:
Name Type Description
query object

an object with { login|id, secret } properties for login

callback function(err:object, user:object)
Example
api.users.checkSecret({ login: "john", secret : "..." }, (err, user) => {
    if (err) return
})

(static) clearup(user) → {object}

Description:
  • Cleanup user record to be returned by API endpoint, remove all internal and priv properties

Source:
Parameters:
Name Type Description
user object
Returns:
Type Description
object

(static) del(query, optionsopt, callback)

Description:
  • Deletes an existing user by login or id, no admin checks, returns the old record in the callback

Source:
Parameters:
Name Type Attributes Description
query object | string

user id or login or { id, login }

options object <optional>
callback function(err:object, user:object)
Example
api.users.del("john@mail.com", lib.log);

api.users.del({ login: "john@mail.com" }, lib.log);

(static) get(query, optionsopt, callback)

Description:
  • Returns a user record by login or id

Source:
Parameters:
Name Type Attributes Description
query object | string

user id or login or { id, login }

options object <optional>
callback function(err:object, user:object)

returning found record

(static) isUuid(id) → {boolean}

Description:
  • Returns true of the given id is a valid user uuid, it is module:lib.isUuid with optional user's table id column prefix

Source:
Parameters:
Name Type Description
id string
Returns:
Type Description
boolean

(static) login(context, callback)

Description:
  • Login with just the secret, set the user in the context, creates a cookie session and store .exp in the sessions column, cleanup expired sessions.

    Verification code will be enforced if a user has TOPT secret or mfa code saved.

    In case MFA is wrong or missing the error object will contain a property code: "MFA" to signal the UI to deal with it by asking for code or using a separate endpoint to send it. The user record is returned in the callback because it was verified already.

Source:
Parameters:
Name Type Description
context RequestContext
Properties
Name Type Description
body object

login credentials

Properties
Name Type Attributes Description
login string

username or email

secret string

clear text password

code string <optional>

optional MFA/TOTP code if enabled

callback function(err:object, user:object)
Example
api.app.post("/login", (context) => {

   api.users.login(context, async (err, user) => {

       if (err?.code === "MFA" && user.mfa_code) {
           const code = api.users.prepareMFA(user);
           await api.users.aupdate({ login: user.login, mfa_code: user.mfa_code });
           await sendmail.asend({ to: user.login, subject: "Verification code", text: `Your verification code ${code}`);
           err.message += "\nPlease provide the code that was sent to your email";
       } else {
           err.message += "\nPlease provide code from your auth app";
       }
       context.reply(err, api.users.cleanup(user));
   })
})

(static) logout(context, callback)

Description:
  • Clear session, delete expired sessions Passing &force=1 in the query url will clear all sessions for a user

Source:
Parameters:
Name Type Description
context RequestContext
callback function(err:object)

(static) prepareMFA(user, optionsopt) → {number}

Description:
  • Prepare a MFA code to be sent and saved in mfa_code property

Source:
Parameters:
Name Type Attributes Description
user object

user record

options object <optional>
Properties
Name Type Attributes Default Description
method string <optional>
email
Returns:
Type Description
number

code - generated random code

Example
const { sendmail, api } = required("backendjs");

const user = await api.users.get('user@email');
const code = api.users.prepareMFA(user);
await api.users.aupdate({ login: user.login, mfa_code: user.mfa_code });

sendmail.send({ to: user.login, subject: "Verification code", text: `Your verification code ${code}`, lib.log)

(static) prepareSession(context, user)

Description:
  • Creates a cookie session and store .exp in the user.sessions property, cleanup expired sessions

Source:
Parameters:
Name Type Description
context RequestContext
user object

validated user record

(static) prepareTOTP(user) → {object}

Description:
  • Prepare a new TOTP secret to be saved in the user record

Source:
Parameters:
Name Type Description
user object

user record

Returns:
Type Description
object
  • same user record with updated topt_secret property
Example
const user = await api.users.get('user@email')
const code = api.users.prepareTOTP(user);
await api.users.aupdate({ login: user.login, totp_secret: user.totp_secret });

(static) prepareToken(user, optionsopt) → {string}

Description:
  • Prepare an API token before saving in the database, generates UUIDv4 in place login/secret properties.

Source:
Parameters:
Name Type Attributes Description
user object

user record

options object <optional>
Properties
Name Type Attributes Description
prefix string <optional>

prefix for the login part

Returns:
Type Description
string

token - formatted as login+secret

Example
api.users.prepareToken(query, { prefix:"test_" })
'test_9f73e6cbcbe2440bb3e304ce1316507b1d1cfdedaf7841a6883c9bbf6cf02593'

(static) update(query, optionsopt, callback)

Description:
  • Updates an existing user by login

Source:
Parameters:
Name Type Attributes Description
query object
options object <optional>
callback function(err:object, user:object)

returning updated user record

Example
api.users.update({ login: "john@mail.com", name: "John" }, lib.log);

(static) verifySession(context, callback)

Description:
  • Verify current cookie session and set the user in the context on success, this can be used standalone or as middleware

Source:
Parameters:
Name Type Description
context RequestContext
callback function(err:object, user:object)

(static) verifyToken(context, callback)

Description:
  • Verify API token from Authorization header and sets user in the context on success, this can be used standalone or as middleware.

    It must be in format: Authorization: Basic base64(token) or Authorization: Bearer token,

    An API token is created for corresponding columns in the user table, see module:api/users.prepareToken:

    • id: generated UUIDv4 by module:lib.uuid as ussual or can be an existing user id to link multiple tokens for a user
    • login: generated UUIDv4 by module:lib.uuid, unique primary key
    • secret: generated UUIDv4 by module:lib.uuid
    • send the token as ${login}${secret} back to user via secure method, like TLS....
    • hash the secret with SHA256 and store in the DB

    Verification is done the following way:

    • get a user record from DB by token.substr(0, 32) if there is a prefix skip it, the convention about prefix it must be separated by underscore from the token like api_........
    • check the user.expire_time column, must be empty or in the future
    • compare the stored user secret by comparing SHA256(token.substr(32)) with module:lib.timingSafeEqual
Source:
Parameters:
Name Type Description
context RequestContext
callback function(err:object, user:object)