Typedef
Static Public Summary | ||
public |
Options to pass to the JSON schema validator. |
|
public |
An object with the key |
|
public |
A plain object that is shared for the given request. |
|
public |
An object mapping store method names to policy hooks. |
|
public |
An object with the key |
|
public |
An operand is a boolean (to explicitly allow or deny) or a policy function, or an AndExpression, OrExpression, or NotExpression, which can be used to assemble more complex policies out of a series of policy functions. |
|
public |
An object with the key |
|
public |
A function that is evaluated before a store method. |
|
public |
An object that represents a record that may be operated on by a model. |
|
public |
A JSON schema object. |
|
public |
A Record that has been transformed by the serialize function as a matter of convenience. |
|
public |
Store(serialize: function(data: Record): Promise<SerializedRecord, Error> | SerializedRecord, unserialize: function(data: SerializedRecord): Promise<Record, Error> | Record): object An object that has methods for CRUD operations for a typical record. |
Static Public
public AndExpression: object source
An object with the key and
set to an array of Operands. The expression will only evaluate to true if none of
the operands are false or throw errors. The error of the first policy to throw will be used, and short-circuiting
will prevent further policies from executing when one throws.
Properties:
Name | Type | Attribute | Description |
and | Operand[] |
public Meta: object source
A plain object that is shared for the given request. It is passed to all policies that execute for the given store method, which can read and mutate it freely, and passed into the given store method. The meta object can be used to add non-serializable data that is useful in the store method, data that is tangentially related to the request but does not belong in the body (such as information about the current user session), cached results of policies that may be repeated in an expression, etc.
Example:
async function getCurrentUserPolicy(req, res, meta) {
if (meta.user) {
// We already have fetched the user (i.e. this policy has probably already been run in this request).
return
}
const authHeader = req.getHeader('Authorization')
if (!authHeader) {
throw new AutonymError(AutonymError.UNAUTHORIZED, 'You must be logged in to perform this action.')
}
const [, token] = authHeader.split('Bearer ')
const data = await verify(token)
// Save data on meta object, which will be accessible on subsequent policies and store methods.
meta.user = await User.findOne(data.userId)
}
function canCreatePostPolicy(req, res, meta) {
// Access the user object saved in the previous policy. Note: this means this policy is tightly coupled to the
// getCurrentUserPolicy and will throw an error if it is used in isolation, which would by default return a 500
// error. We *could* `await getCurrentUserPolicy(req, res, meta)` here if we wanted to use this policy alone.
if (!meta.user.privileges.includes('createPost')) {
throw new AutonymError(AutonymError.FORBIDDEN, 'You must have the createPost privilege to perform this action.')
}
}
const Post = new Model({
name: 'post',
policies: {
create: { and: [getCurrentUserPolicy, canCreatePostPolicy] },
},
store: {
// Since the `getCurrentUserPolicy` was called before inserting, the `user` object is available in the store
// methods. If calling the API programmatically, e.g. `Post.create()`, this data will need to be supplied
// manually in the `create` method, since policies are not called when using the model instance directly.
create: (data, meta) => Db.insert({ ...data, authorId: meta.user.id }),
}
})
public ModelPolicies: object source
An object mapping store method names to policy hooks. The hooks will be run for the given method. If the value is
just true
, it is assumed that all requests will be honored and no policies are necessary for any lifecycle event;
if just false
, it is assumed no requests will be honored.
Properties:
Name | Type | Attribute | Description |
create | object | boolean |
|
Policies to run for the |
create.preSchema | Operand |
|
An expression to evaluate before the data is validated against the schema. |
create.postSchema | Operand |
|
An expression to evaluate after the data is validated against the schema. |
create.preStore | Operand |
|
An expression to evaluate before the data is passed to the store method. |
create.postStore | Operand |
|
An expression to evaluate after the store method has completed. |
find | object | boolean |
|
Policies to run for the |
find.preStore | Operand |
|
An expression to evaluate before the data is passed to the store method. |
find.postStore | Operand |
|
An expression to evaluate after the store method has completed. |
findOne | object | boolean |
|
Policies to run for the |
findOne.preStore | Operand |
|
An expression to evaluate before the data is passed to the store method. |
findOne.postStore | Operand |
|
An expression to evaluate after the store method has completed. |
findOneAndUpdate | object | boolean |
|
Policies to run for the |
findOneAndUpdate.preSchema | Operand |
|
An expression to evaluate before the data is validated against the schema. |
findOneAndUpdate.postSchema | Operand |
|
An expression to evaluate after the data is validated against the schema. |
findOneAndUpdate.preStore | Operand |
|
An expression to evaluate before the data is passed to the store method. |
findOneAndUpdate.postStore | Operand |
|
An expression to evaluate after the store method has completed. |
findOneAndDelete | object | boolean |
|
Policies to run for the |
findOneAndDelete.preStore | Operand |
|
An expression to evaluate before the data is passed to the store method. |
findOneAndDelete.postStore | Operand |
|
An expression to evaluate after the store method has completed. |
Example:
const Post = new Model({
name: 'post',
policies: {
create: {
// Users must be logged in and have the proper permission to create a post
preSchema: { and: [getCurrentUserPolicy, canCreatePostPolicy] },
// Once the post is validated, it is safe to read and manipulate the request data
postSchema: trimPostBodyPolicy,
},
find: {
// All requests to get all posts should include a header with the total count
postStore: addTotalCountHeaderToResponsePolicy,
},
findOneAndUpdate: {
// Users must be logged in and own the post that they are trying to update
preSchema: { and: [getCurrentUserPolicy, userIsOwnerOfPostPolicy] },
postSchema: trimPostBodyPolicy,
},
findOneAndDelete: {
preStore: { and: [getCurrentUserPolicy, userIsOwnerOfPostPolicy] },
},
},
store: {},
})
public NotExpression: object source
An object with the key not
set to an Operand. The expression will only evaluate to true if the result of the
operand is false or throws an error (which will be swallowed). If the result is true or does not throw an error,
a generic error will be thrown, so implement a custom policy if you require a more specific error message.
Properties:
Name | Type | Attribute | Description |
not | Operand |
public Operand: boolean | Policy | AndExpression | OrExpression | NotExpression source
An operand is a boolean (to explicitly allow or deny) or a policy function, or an AndExpression, OrExpression, or NotExpression, which can be used to assemble more complex policies out of a series of policy functions. AndExpressions, OrExpressions, and NotExpressions may be nested recursively. For details, see the documentation of async-boolean-expression-evaluator.
public OrExpression: object source
An object with the key or
set to an array of Operands. The expression will only evaluate to true if at least
one of the operands is true or does not throw an error. The error of the last policy that throws will be used,
and short-circuiting will prevent further policies from executing when one does not throw.
Properties:
Name | Type | Attribute | Description |
or | Operand[] |
public Policy: function(req: AutonymReq, res: AutonymRes, meta: Meta): Promise<*, Error> | * source
A function that is evaluated before a store method. It may modify the request data, validate the data and throw an error, or determine that the client may not perform this request and throw an error. Policies may be combined in an expression and applied to various store methods in a model's configuration.
Example:
async function getCurrentUserPolicy(req, res, meta) {
if (meta.user) {
// We already have fetched the user (i.e. this policy has probably already been run in this request).
return
}
const authHeader = req.getHeader('Authorization')
if (!authHeader) {
throw new AutonymError(AutonymError.UNAUTHORIZED, 'You must be logged in to perform this action.')
}
const [, token] = authHeader.split('Bearer ')
const data = await verify(token)
// Save data on meta object, which will be accessible on subsequent policies and store methods.
meta.user = await User.findOne(data.userId)
}
function canCreatePostPolicy(req, res, meta) {
// Access the user object saved in the previous policy. Note: this means this policy is tightly coupled to the
// getCurrentUserPolicy and will throw an error if it is used in isolation, which would by default return a 500
// error. We *could* `await getCurrentUserPolicy(req, res, meta)` here if we wanted to use this policy alone.
if (!meta.user.privileges.includes('createPost')) {
throw new AutonymError(AutonymError.FORBIDDEN, 'You must have the createPost privilege to perform this action.')
}
}
function userIsOwnerOfPostPolicy(req, res, meta) {
if (req.getId() !== req.meta.user.id) {
throw new AutonymError(AutonymError.FORBIDDEN, 'You are not the owner of this post.')
}
}
function trimPostBodyPolicy(req, res, meta) {
// If this policy is in the postSchema hook, it is safe to get and set data.
req.setData({ body: req.getData().body.trim() })
}
Post.count = () => Db.selectCount('posts')
async function addTotalCountHeaderToResponsePolicy(req, res, meta) {
// If this policy is in the postStore hook, it is safe to get and modify the response data.
const model = req.getModel()
if (model.count) {
const totalCount = await req.getModel().count()
res.setHeader('X-Total-Count', totalCount)
}
}
public Record: object source
An object that represents a record that may be operated on by a model. A record may be partial (e.g. if it is
the properties to update in a findOneAndUpdate request). If it is the result of a store method, it should at
least have an id
property.
public SerializedRecord: object source
A Record that has been transformed by the serialize function as a matter of convenience. It is only used in the store methods and is always unserialized back into a Record before being passed onto other code outside the store methods.
public Store(serialize: function(data: Record): Promise<SerializedRecord, Error> | SerializedRecord, unserialize: function(data: SerializedRecord): Promise<Record, Error> | Record): object source
An object that has methods for CRUD operations for a typical record. It does not matter if this is a plain object or an instance of a Store class, as long as it has these members on it.
Properties:
Name | Type | Attribute | Description |
create | function(data: SerializedRecord, meta: Meta, unserializedData: Record) | SerializedRecord |
|
A function called to create a new record. |
find | function(query: object, meta: Meta): Promise<SerializedRecord[], Error> | SerializedRecord[] |
|
A function called to find records. |
findOne | function(id: string, meta: Meta): Promise<SerializedRecord, Error> | SerializedRecord |
|
A function called to find a single record. |
findOneAndUpdate | function(id: string, data: SerializedRecord, completeData: SerializedRecord, meta: Meta, unserializedData: Record, unserializedCompleteData: Record) | SerializedRecord |
|
A function called to update a single record. |
findOneAndDelete | function(id: string, meta: Meta): Promise<*, Error> | * |
|
A function called to delete a single record. |
Example:
const Post = new Model({
name: 'post',
store: {
create: data => Db.insert('posts', data),
find: () => Db.selectAll('posts'),
findOne: id => Db.selectOne('posts', { id }),
findOneAndUpdate: (id, data) => Db.updateWhere('posts', { id }, data),
findOneAndDelete: id => Db.deleteWhere('posts', { id }),
// Column names are the same as properties on the data, but in snake-case
serialize: data => mapKeys(data, property => snakeCase(property)),
// Properties are the same as column names in the table, but in camel-case
unserialize: data => mapKeys(data, columnName => camelCase(columnName)),
},
})