@dta5/npm-dmx-utils
v5.0.44
Published
DMX microservice utils
Downloads
3
Readme
npm-dmx-utils
DMXSchema
The DMXSchema
introduces an extension to mongoose.Schema
with built-in support for DMX Entitlements.
The basic configuration allows for an entitlements
field on each of the schema's defined fields with the ability to configure visibility and editability for each field in the schema. For processing data before sending it to a user the DMXSchema
adds sanitize
methods on the instance and the model. For protecting data from being modified by those without the correct entitlement
it provides an instance method setForUser
.
Schema configuration
docinfo
DMXSchema updates automatically docinfo.createdAt
and docinfo.updatedAt
fields.
To avoid adding docinfo
field, pass dmx.skipDocinfo
(a nested field inside dmx
field) param to the options (second param of DMXSchema constructor) e.g. for subschemas.
As default docinfo has the following fields available:
createdAt: Date,
createdBy: String,
updatedAt: Date,
updatedBy: String,
deletedAt: Date,
deletedBy: String,
If you want to add any more fields to docinfo
, just pass docinfo.yourFieldName
(a nested field inside docinfo
field) with the field definition to the schema (first param of DMXSchema constructor). Then docinfo
will contain all the default fields and your field as well. This doesn't mean DMXSchema will modify your field, it's only a definition so without defining custom fields, an error will be thrown for an attempt of using it.
Audits
DMXSchema can be configured to store an audit trail on documents stored in the database. This feature is configurable per field within a schema. When a document is updated, all fields which are configured to be auditable, will be stored in a separate collection with information on what the previous and new values are as well as information about the user that made those changes. To enable audits for a schema, you must define the collection to which the audit documents will be stored.
const schema = new DMXSchema({
auditField: { type: Boolean, audit: true },
dontAuditField: { type: Boolean },
}, {
dmx: {
audit: {
collection: 'auditCollection'
}
}
})
Entitlements
The DMXSchema
supports a custom option field called entitlements
which contains up to four sub-options:
view
an array of entitlement names of which the user must have at least one to be able to view this fieldconditionalView
a method for custom logic which adds another layer to theview
above to further reduce visibility. It should returntrue
if the field should be visible to the user andfalse
if it should not be visible.edit
an array of entitlement names of which the user must have at least one to be able to edit the fieldconditionalEdit
a method for custom logic which adds another layer to theedit
above to further reduce editability. It should throw anEntitlementError
if the edit is disallowed.
const mongoose = require('mongoose');
const { getSchema, EntitlementError } = require('@dmx/npm-dmx-utils').DMXSchema;
const DMXSchema = getSchema(mongoose);
const someSchema = new DMXSchema({
// Fields defined without an `entitlements` option will be invisible to and uneditable by users.
hiddenA: String,
hiddenB: { type: Number },
// Fields with a `*` view are visible to all users
visibleToAll: {
type: String,
entitlements: {
view: ['*']
}
},
// Users with either `entitlementA` or `entitlementB` will be able to view this field but only
// those with `entitlementA` will be able to modify it.
basicField: {
type: String,
entitlements: {
view: ['entitlementA', 'entitlementB'],
edit: ['entitlementA']
}
},
// Users with any entitlement beginning with `entitlementC` will be able to view this field
// e.g. `entitlementC.create`, `entitlementC.remove`, `entitlementC.all`
visibleToAnyEntitlementC: {
type: String,
entitlements: {
view: ['entitlementC.*'],
}
},
// `conditionalView` builds on the `view` limitations. In this example the user must
// at least have `entitlementA`. The other restrictions are described below.
customViewField: {
type: Boolean,
entitlements: {
view: ['entitlementA'],
conditionalView: function (options) {
// This function is run so that `this` refers to the current document which provides access
// to the document's fields and virtuals.
if (this.hiddenB < 10) {
return false;
}
// The `options` argument can contain additional information about the user and its
// entitlements, `orgId`, etc. Entitlement restrictions don't have to follow any
// specific pattern but can be created as needed.
const { restriction } = options.entitlements.entitlementA;
if (restriction.hiddenA && restriction.hiddenA !== this.hiddenA) {
return false;
}
}
},
// `conditionalEdit` builds on the `edit` limitations. In this example the user must
// at least have `entitlementB`. The other restrictions are described below.
customEditField: {
type: Number,
entitlements: {
edit: ['entitlementB'],
conditionalEdit: function (value, options) {
if (this.hiddenA !== 'active') {
throw new EntitlementError('cannot edit `customEditField` while inactive');
}
const { restriction } = options.entitlements.entitlementA;
if (value > restriction.maxCustomEditFieldValue) {
throw new EntitlementError('you cannot set `customeEditField` to greater than ...');
}
}
}
}
}
});
setForUser
instance method
The DMXSchema
provides an instance method setForUser
which is an analog to the standard mongoose
set
method but takes an additional argument. This argument is passed in as the options
argument to the conditionalEdit
function. It is expected to at least contain entitlements
, userId
and orgId
but any custom fields can be included.
The document.setForUser
method supports two different argument formats:
document.setForUser(key, value, options)
e.g.foo.setForUser('bar', 'baz', { entitlements...})
document.setForUser(object, options)
e.g.foo.setForUser({ bar: 'baz' }, { entitlements...})
Virtual fields
The schema also supports entitlements
configuration for viewing/editing limitations on virtuals
schema.virtual('virtualField', {
entitlements: {
view: ['entitlementA'],
conditionalView: function(options) {
return options.orgId === this.dealerId;
},
edit: ['entitlementB'],
conditionalEdit: function (value, options) {
if (value > this.someOtherField && !options.isDMXUser) {
throw new EntitlementError('nope');
}
}
}
}).get(...).set(...);
EntitlementError
The EntitlementError
class extends Error
and supports the following fields
requiredEntitlements
an array of entitlements required to successfully execute the rejected actionfield
string containing the name of the field which was unable to be updatedcollection
name of the model collection
throw new EntitlementError('Some error message', {
requiredEntitlements: ['entitlementA', 'entitlementB'],
field: 'some.field.path',
collection: 'someCollection'
});
Model.sanitize
method
This method will return plain object(s) which only include the fields which should be visible to the user (based on the options
object). It supports single documents and arrays of documents
sanitizedArray = Model.sanitize([documents], options)
sanitizedDocument = Model.sanitize(document, options)
document.sanitize
This method will return a plain object which only includes the fields which should be visible to the user (based on the options
object)
document.sanitize(options)
Message queue clients
In addition to publishing/subscribing clients encapsulate logic to create queues, exchanges and bindings to avoid the need to create queues manually in RabbitMQ console in all environments. These operations are idempotent.
PubMessageQueueClient
publish()
Publishes message with routingKey
(that is basically event name) to exchange with exchangeName
name.
In case such exchange is missing it'll be created to avoid getting Exchange does not exist
error.
SubMessageQueueClient
Subscribes handlers to messages from certain queues.
In case corresponding queues and exchanges are missing they'll be created to avoid getting Queue does not exist
and similar errors.
In addition queues are ensured to be bounded to exchanges.