Research
Security News
Malicious npm Package Targets Solana Developers and Hijacks Funds
A malicious npm package targets Solana developers, rerouting funds in 2% of transactions to a hardcoded address.
Asynchronous Data Modelling and Validation.
Obey is a library for creating asynchronous data models and rules. The core goal of the project is to provide methods for managing data models both through synchronous and asynchronous validation and alignment using Promises.
Obey can be installed via NPM: npm install obey --save
Detailed API Documentation is available for assistance in using, modifying, or contributing to the Obey library.
Rules are core definitions of how a value should be validated:
const obey = require('obey')
const firstName = obey.rule({ type: 'string', min: 2, max: 45, required: true })
Models allow for creating validation rules for entire object schemas. The following demonstrates a model being created with Obey:
const obey = require('obey')
const userModel = obey.model({
id: { type: 'uuid', creator: 'uuid', required: true },
email: { type: 'email', required: true },
password: { type: 'string', modifier: 'encryptPassword', required: true },
passwordConfirm: { type: 'string', equalTo: 'password' },
fname: { type: 'string', description: 'First Name' },
lname: { type: 'string', description: 'Last Name', empty: true },
suffix: { type: 'string', allowNull: true },
phone: { type: 'phone:numeric', min: 7, max: 10 },
phoneType: { type: 'string', requiredIf: 'phone' },
carrier: { type: 'string', requiredIf: { phoneType: 'mobile' }},
// Array
labels: { type: 'array', values: {
type: 'object', keys: {
label: { type: 'string' }
}
}},
// Nested object
address: { type: 'object', keys: {
street: { type: 'string', max: 45 },
city: { type: 'string', max: 45 },
state: { type: 'string', max: 2, modifier: 'upperCase' },
zip: { type: 'number', min: 10000, max: 99999 }
}},
// Key-independent object validation
permissions: { type: 'object', values: {
type: 'string'
}},
account: { type: 'string', allow: [ 'user', 'admin' ], default: 'user' }
})
Using the example above, validation is done by calling the validate
method and supplying data. This applies to both individual rules and data models:
userModel.validate({ /* some data */ })
.then(data => {
// Passes back `data` object, includes any defaults set,
// generated, or modified data
})
.catch(error => {
// Returns instance of ValidationError
// `error.message` => String format error messages
// `error.collection` => Raw array of error objects
})
The validate method returns a promise (for more information see [Asynchronous Validation](#Asynchronous Validation)). A passing run will resolve with the data, any failures will reject and the ValidationError
instance will be returned.
The validate
method has the ability to validate partial data objects:
// Allow partial validation by supplying second argument with `partial: true`
userModel.validate({ /* some (partial) data */ }, { partial: true })
The default for the partial option is false
, but passing true
will allow for validation of an object containing a subset (i.e. will not throw errors for required
properties).
The common use-case for validating partials is PATCH
updates.
Note: Running a partial validation will prevent running creator
's on any properties
Validation errors are collected and thrown after all validation has run. This is as opposed to blocking, or stopping, on the first failure.
As shown in the example above, the catch
will contain an instance of ValidationError
with two properties; message
and collection
. The message
simply contains the description of all errors.
The collection
is an array of objects containing details on each of the validation errors. For example, if a type
evaluation for phone:numeric
was performed and the value failed the following would be contained as an object in the array:
{
type: 'phone', // The type evaluation performed
sub: 'numeric', // The sub-type (if applicable)
key: 'primaryPhone', // Name of the property in the model
value: '(555) 123-4567', // The value evaluated
message: 'Value must be a numeric phone number' // Message
}
When setting definitions for rules or model properties, the following are supported:
type
: The type of value with (optional) sub-type see Typeskeys
: Property of object
type, indicates nested object propertiesvalues
: Defines value specification for arrays or key-independent objectsmodifier
: uses a method and accepts a passed value to modify or transform data, see Modifierscreator
: uses a method to create a default value if no value is supplied, see Creatorsempty
: Set to true
allows empty string or array, (default false
)default
: The default value if no value specifiedmin
: The minimum character length for a string, lowest number, or minimum items in arraymax
: The maximum character length for a string, highest number, or maximum items in arrayrequired
: Enforces the value cannot be undefined
during validation (default false
)requiredIf
: Enforces the value cannot be undefined
if a value exists or matches a given value ({ propertyName: 'requiredValue' }
), or any of a list of values ({ propertyName: [ 'val1', 'val2' ] }
)requiredIfNot
: Enforces the value cannot be undefined
if a value does not exist or match a given value ({ propertyName: 'requiredValue' }
), or any of a list of values ({ propertyName: [ 'val1', 'val2' ] }
)equalTo
: Enforces the value to be the same as the corresponding fieldallow
: Object, array or single value representing allowed value(s), see AllowallowNull
: Accepts a null value or processes specified typestrict
: Enable or disable strict checking of an object, see Strict Modedescription
: A description of the propertyjexl
: One or more objects containing an expr
string for validating data against Jexl expressions. See Jexl Validation.Reference: Type Documentation
Types are basic checks against native types, built-ins or customs. The library includes native types (
boolean
,number
,string
,array
, andobject
) as well other common types. A list of built-in types is contained in the source.
The type
definition can also specify a sub-type, for example:
phone: { type: 'phone:numeric' }
The above would specify the general type phone
with sub-type numeric
(only allowing numbers).
New types can be added to the Obey lib with the obey.type
method. Types can be added as single methods or objects supporting sub-types:
obey.type('lowerCaseOnly', context => {
if (!/[a-z]/.test(context.value)) {
context.fail(`${context.key} must be lowercase`)
}
})
obey.type('password', {
default: context => {
if (context.value.length < 6) {
context.fail(`${context.key} must contain at least 6 characters`)
}
},
strong: context => {
if (!context.value.test((/^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])[0-9a-zA-Z]{8,}$/))) {
context.fail(`${context.key} must contain a number, letter, and be at least 8 characters`)
}
}
})
The definition object contains keys that indicate the subtype (or default
if no sub-type specified).
Each method will be passed a context
object at runtime. This object has the following properties:
def
: The entire rule for the property in the modelsub
: The sub-type (if provided)key
: The name of the property being tested (if an element in a model/object)value
: The value to testfail
: A function accepting a failure message as an argumentThe above would add a new type which would then be available for setting in the model configuration for any properties.
password: { type: 'password', /* ...additional config... */ }
/* ...or... */
password: { type: 'password:strong', /* ...additional config... */ }
Types can be synchronous or asynchronous. For example, if a unique email is required the following could be used to define a uniqueEmail
type:
obey.type('uniqueEmail', context => {
return someDataSource.find({ email: context.value })
.then(record => {
if (record.length >= 1) {
context.fail(`${context.key} already exists`)
}
})
}
Types can return/resolve a value, though it is not required and is recommended any coercion be handled with a modifier.
Regardless of if a value is returned/resolved, asynchronous types must resolve. Errors should be handled with the context.fail()
method.
The allow
property in definition objects accepts three formats; string
, array
or object
The string
and array
methods are straight-forward:
// Only allow 'bar'
foo: { type: 'string', allow: 'bar' }
// Allow 'buzz', 'bazz', 'bizz'
fizz: { type: 'string', allow: [ 'buzz', 'bazz', 'bizz' ] }
The object
representation of the allow
property gives the ability to store enums alongside the model structure making sharing/reuse of the objects simplified:
const allowedStatuses = {
'prog': 'in progress',
'comp': 'completed',
'arch': 'archived'
}
// Allow statuses
{ status: { type: 'string', allow: allowedStatuses } }
In the above example, the model would only accept the keys (prog
, comp
, arch
) during validation.
Modifiers allow custom methods to return values which are modified/transformed versions of the received value.
Modifiers can be added to the Obey lib with the obey.modifier
method:
obey.modifier('upperCase', val => val.toUpperCase())
When the model is validated, the value in any fields with the upperCase
modifier will be transformed to uppercase.
Similar to types, modifiers may be synchronous (returning a value) or asynchronous (returning a promise).
Creators allow custom methods to return values which set the value similar to the
default
property. When validating, if a value is not provided the creator assigned will be used to set the value.
Creators can be added to the Obey lib with the obey.creator
method:
obey.creator('timestamp', () => new Date().getTime())
The above example would add a creator named timestamp
which could be assigned as shown below:
created: { type: 'number', creator: 'timestamp' }
When the model is validated, if no created
property is provided the timestamp
creator will assign the property a UTC timestamp.
Similar to modifiers, creators may be synchronous (returning a value) or asynchronous (returning a promise).
By default, Obey enforces strict matching on objects; meaning an object must define any keys that will be present in the data object being validated.
To disable strict mode on a rule or object set the strict
property to false:
foo: { type: 'object', strict: false, keys: { /* ... */ } }
To disable strict mode on a model pass the (optional) strict argument as false
:
const model = obey.model({ /* definition */ }, false)
Obey allows for validating data against Jexl expressions via the jexl
rule:
obey.model({
exprVal: {
type: 'string',
jexl: [{
expr: "value == root.testVal.nestedObjArray[.name == 'some specific name'].payload",
message: "Do not seek the treasure" // Optional expression-specific error message
}]
},
testVal: {
type: 'object',
keys: {
nestedObjArray: {
type: 'array',
values: {
type: 'object',
keys: {
name: { type: 'string' },
payload: { type: 'string' }
}
}
}
}
}
})
Note: the jexl
validator uses value
and root
as its context keys for the specific value being validated and the corresponding data, respectively, so expression strings should be constructed accordingly.
If necessary, a preconfigured Jexl instance can be passed in before the model is constructed, in order to utitlize user-defined transforms in validation:
const obey = require('obey')
const jexl = require('jexl')
jexl.addTransform('upper', val => val.toUpperCase(val))
obey.use('jexl', jexl)
obey.model({/* ...definition... */})
The goal with Obey is to provide more than just standard type/regex checks against data to validate values and models. The ability to write both synchronous and asynchronous checks, creators, and modifiers, and include data coercion in the validation simplifies the process of validation and checking before moving onto data source interactions.
Additionally, with the widespread use of promises, this structure fits well in the scheme of data processing in general:
// Define a model somewhere in your code...
const user = obey.model(/* ...Model Definition... */)
// Use it to validate before creating a record...
user.validate(/* ...some data object... */)
.then(createUser)
.then(/* ...response or other action... */)
.catch(/* ...handle errors... */)
Contibutions to Obey are welcomed and encouraged. If you would like to make a contribution please fork the repository and submit a PR with changes. Acceptance of PR's is based on a review by core contributors. To increase the likelihood of acceptance please ensure the following:
npm run doc
README
is suppliedObey was originally created at TechnologyAdvice in Nashville, TN and released under the MIT license.
v5.0.1 (2022-10-10):
min
and max
validation and messagingFAQs
Data modelling and validation library
The npm package obey receives a total of 530 weekly downloads. As such, obey popularity was classified as not popular.
We found that obey demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 3 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
A malicious npm package targets Solana developers, rerouting funds in 2% of transactions to a hardcoded address.
Security News
Research
Socket researchers have discovered malicious npm packages targeting crypto developers, stealing credentials and wallet data using spyware delivered through typosquats of popular cryptographic libraries.
Security News
Socket's package search now displays weekly downloads for npm packages, helping developers quickly assess popularity and make more informed decisions.