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
sessionscolumns 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
loginandsecretare 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 = usersUsers 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
|
(static) table :string
- Description:
Table to create and use for users, it is an instance of DbTable and uses the default module:api/users.schema property.
- Source:
Methods
(async, static) aadd(query, optionsopt) → {Object}
- Description:
Async version of the module:api/users.add method
- 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:
Async version of module:api/users.checkSecret, verify user by login and secret
- 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:
Async version of the module:api/users.del method
- 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:
Async version of the module:api/users.get method
- 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:
Async version of the module:api/users.login method
- Source:
Parameters:
| Name | Type | Description | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
context |
RequestContext |
Properties
|
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:
Async version of the module:api/users.logout method
- Source:
Parameters:
| Name | Type | Description |
|---|---|---|
context |
RequestContext |
Returns:
| Type | Description |
|---|---|
| Promise |
(async, static) aupdate(query, optionsopt) → {Object}
- Description:
Async version of the module:api/users.update method
- 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
sessionscolumn, 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
|
||||||||||||||||||||||
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=1in 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_codeproperty
- Source:
Parameters:
| Name | Type | Attributes | Description | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
user |
object | user record |
|||||||||||
options |
object |
<optional> |
Properties
|
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.sessionsproperty, 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 |
|
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
|
Returns:
| Type | Description |
|---|---|
| string |
token - formatted as |
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)orAuthorization: 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 userlogin: generated UUIDv4 by module:lib.uuid, unique primary keysecret: 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 likeapi_........ - check the
user.expire_timecolumn, 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) |