mirror of https://gitee.com/bigwinds/arangodb
899 lines
27 KiB
JavaScript
899 lines
27 KiB
JavaScript
'use strict';
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief Foxx Request Context
|
|
///
|
|
/// @file
|
|
///
|
|
/// DISCLAIMER
|
|
///
|
|
/// Copyright 2013 triagens GmbH, Cologne, Germany
|
|
///
|
|
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|
/// you may not use this file except in compliance with the License.
|
|
/// You may obtain a copy of the License at
|
|
///
|
|
/// http://www.apache.org/licenses/LICENSE-2.0
|
|
///
|
|
/// Unless required by applicable law or agreed to in writing, software
|
|
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
/// See the License for the specific language governing permissions and
|
|
/// limitations under the License.
|
|
///
|
|
/// Copyright holder is triAGENS GmbH, Cologne, Germany
|
|
///
|
|
/// @author Lucas Dohmen
|
|
/// @author Michael Hackstein
|
|
/// @author Copyright 2013, triAGENS GmbH, Cologne, Germany
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
const SwaggerDocs = require('@arangodb/foxx/swaggerDocs').Docs;
|
|
const joi = require('joi');
|
|
const _ = require('underscore');
|
|
const internal = require('@arangodb/foxx/internals');
|
|
const toJSONSchema = require('@arangodb/foxx/schema').toJSONSchema;
|
|
const is = require('@arangodb/is');
|
|
const UnprocessableEntity = require('http-errors').UnprocessableEntity;
|
|
const UnauthorizedError = require('@arangodb/foxx/authentication').UnauthorizedError;
|
|
|
|
function createBodyParamExtractor(rootElement, paramName, allowInvalid) {
|
|
var extractElement;
|
|
|
|
if (rootElement) {
|
|
extractElement = function (req) {
|
|
return req.body()[paramName];
|
|
};
|
|
} else {
|
|
extractElement = function (req) {
|
|
return req.body();
|
|
};
|
|
}
|
|
|
|
if (!allowInvalid) {
|
|
return extractElement;
|
|
}
|
|
|
|
return function (req) {
|
|
try {
|
|
return extractElement(req);
|
|
} catch (e) {
|
|
return {};
|
|
}
|
|
};
|
|
}
|
|
|
|
function createModelInstantiator(Model, allowInvalid) {
|
|
var multiple = is.array(Model);
|
|
Model = multiple ? Model[0] : Model;
|
|
var instantiate = function (raw) {
|
|
if (!allowInvalid) {
|
|
raw = validateOrThrow(raw, Model.prototype.schema, allowInvalid);
|
|
}
|
|
return new Model(raw);
|
|
};
|
|
if (!multiple) {
|
|
return instantiate;
|
|
}
|
|
return function (raw) {
|
|
return _.map(raw, instantiate);
|
|
};
|
|
}
|
|
|
|
function isJoi(schema) {
|
|
if (!schema || typeof schema !== 'object' || is.array(schema)) {
|
|
return false;
|
|
}
|
|
|
|
if (schema.isJoi) { // shortcut for pure joi schemas
|
|
return true;
|
|
}
|
|
|
|
return Object.keys(schema).some(function (key) {
|
|
return schema[key].isJoi;
|
|
});
|
|
}
|
|
|
|
function validateOrThrow(raw, schema, allowInvalid, validateOptions) {
|
|
if (!isJoi(schema)) {
|
|
return raw;
|
|
}
|
|
var result = joi.validate(raw, schema, validateOptions);
|
|
if (result.error && !allowInvalid) {
|
|
throw new UnprocessableEntity(result.error.message.replace(/^"value"/, 'Request body'));
|
|
}
|
|
return result.value;
|
|
}
|
|
|
|
class RequestContext {
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// JSF_foxx_RequestContext_initializer
|
|
/// @brief Context of a Request Definition
|
|
///
|
|
/// Used for documenting and constraining the routes.
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
constructor(executionBuffer, models, route, path, rootElement, constraints, extensions) {
|
|
this.path = path;
|
|
this.route = route;
|
|
this.typeToRegex = {
|
|
int: '/[0-9]+/',
|
|
integer: '/[0-9]+/',
|
|
string: '/[^/]+/'
|
|
};
|
|
this.rootElement = rootElement;
|
|
this.constraints = constraints;
|
|
|
|
this.docs = new SwaggerDocs(this.route.docs, models);
|
|
|
|
var attr;
|
|
var extensionWrapper = function(scope, func) {
|
|
return function() {
|
|
func.apply(this, arguments);
|
|
return this;
|
|
}.bind(scope);
|
|
};
|
|
for (attr in extensions) {
|
|
if (extensions.hasOwnProperty(attr)) {
|
|
this[attr] = extensionWrapper(this, extensions[attr]);
|
|
}
|
|
}
|
|
executionBuffer.applyEachFunction(this);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @startDocuBlock JSF_foxx_RequestContext_pathParam
|
|
///
|
|
/// `Route.pathParam(id, options)`
|
|
///
|
|
/// If you defined a route "/foxx/:name", containing a parameter called `name` you can
|
|
/// constrain which format this parameter is allowed to have.
|
|
/// This format is defined using *joi* in the `options` parameter.
|
|
/// Using this function will at first allow you to access this parameter in your
|
|
/// route handler using `req.params(id)`, will reject any request having a paramter
|
|
/// that does not match the *joi* definition and creates a documentation for this
|
|
/// parameter in ArangoDBs WebInterface.
|
|
///
|
|
/// For more information on *joi* see [the official Joi documentation](https://github.com/spumko/joi).
|
|
///
|
|
/// *Parameter*
|
|
///
|
|
/// * *id*: name of the param.
|
|
/// * *options*: a joi schema or an object with the following properties:
|
|
/// * *type*: a joi schema.
|
|
/// * *description*: documentation description for the parameter.
|
|
/// * *required* (optional): whether the parameter is required. Default: determined by *type*.
|
|
///
|
|
/// *Examples*
|
|
///
|
|
/// ```js
|
|
/// app.get("/foxx/:name", function {
|
|
/// // Do something
|
|
/// }).pathParam("name", joi.string().required().description("Name of the Foxx"));
|
|
/// ```
|
|
///
|
|
/// You can also pass in a configuration object instead:
|
|
///
|
|
/// ```js
|
|
/// app.get("/foxx/:name", function {
|
|
/// // Do something
|
|
/// }).pathParam("name", {
|
|
/// type: joi.string(),
|
|
/// required: true,
|
|
/// description: "Name of the Foxx"
|
|
/// });
|
|
/// ```
|
|
/// @endDocuBlock
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
pathParam(paramName, attributes) {
|
|
var url = this.route.url,
|
|
urlConstraint = url.constraint || {},
|
|
type = attributes.type,
|
|
required = attributes.required,
|
|
description = attributes.description,
|
|
constraint, regexType, cfg;
|
|
|
|
if (attributes.isJoi) {
|
|
type = attributes;
|
|
required = undefined;
|
|
description = undefined;
|
|
}
|
|
|
|
constraint = type;
|
|
regexType = type;
|
|
|
|
if (type) {
|
|
if (typeof required === 'boolean') {
|
|
constraint = required ? constraint.required() : constraint.optional();
|
|
}
|
|
if (typeof description === 'string') {
|
|
constraint = constraint.description(description);
|
|
}
|
|
this.constraints.urlParams[paramName] = constraint;
|
|
cfg = constraint.describe();
|
|
if (is.array(cfg)) {
|
|
cfg = cfg[0];
|
|
type = 'string';
|
|
} else {
|
|
type = cfg.type;
|
|
}
|
|
required = Boolean(cfg.flags && cfg.flags.presence === 'required');
|
|
description = cfg.description;
|
|
if (
|
|
type === 'number' &&
|
|
_.isArray(cfg.rules) &&
|
|
_.some(cfg.rules, function (rule) {
|
|
return rule.name === 'integer';
|
|
})
|
|
) {
|
|
type = 'integer';
|
|
}
|
|
if (_.has(this.typeToRegex, type)) {
|
|
regexType = type;
|
|
} else {
|
|
regexType = 'string';
|
|
}
|
|
}
|
|
|
|
urlConstraint[paramName] = this.typeToRegex[regexType];
|
|
if (!urlConstraint[paramName]) {
|
|
throw new Error('Illegal attribute type: ' + regexType);
|
|
}
|
|
this.route.url = internal.constructUrlObject(url.match, urlConstraint, url.methods[0]);
|
|
this.docs.addPathParam(paramName, description, type, required);
|
|
return this;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @startDocuBlock JSF_foxx_RequestContext_queryParam
|
|
///
|
|
/// `Route.queryParam(id, options)`
|
|
///
|
|
/// Describe a query parameter:
|
|
///
|
|
/// If you defined a route "/foxx", you can allow a query paramter with the
|
|
/// name `id` on it and constrain the format of this parameter by giving it a *joi* type in the `options` parameter.
|
|
/// Using this function will at first allow you to access this parameter in your
|
|
/// route handler using `req.params(id)`, will reject any request having a paramter
|
|
/// that does not match the *joi* definition and creates a documentation for this
|
|
/// parameter in ArangoDBs WebInterface.
|
|
///
|
|
/// For more information on *joi* see [the official Joi documentation](https://github.com/spumko/joi).
|
|
///
|
|
/// You can also provide a description of this parameter and
|
|
/// whether you can provide the parameter multiple times.
|
|
///
|
|
/// *Parameter*
|
|
///
|
|
/// * *id*: name of the parameter
|
|
/// * *options*: a joi schema or an object with the following properties:
|
|
/// * *type*: a joi schema
|
|
/// * *description*: documentation description for this param.
|
|
/// * *required* (optional): whether the param is required. Default: determined by *type*.
|
|
/// * *allowMultiple* (optional): whether the param can be specified more than once. Default: `false`.
|
|
///
|
|
/// *Examples*
|
|
///
|
|
/// ```js
|
|
/// app.get("/foxx", function {
|
|
/// // Do something
|
|
/// }).queryParam("id",
|
|
/// joi.string()
|
|
/// .required()
|
|
/// .description("Id of the Foxx")
|
|
/// .meta({allowMultiple: false})
|
|
/// });
|
|
/// ```
|
|
///
|
|
/// You can also pass in a configuration object instead:
|
|
///
|
|
/// ```js
|
|
/// app.get("/foxx", function {
|
|
/// // Do something
|
|
/// }).queryParam("id", {
|
|
/// type: joi.string().required().description("Id of the Foxx"),
|
|
/// allowMultiple: false
|
|
/// });
|
|
/// ```
|
|
/// @endDocuBlock
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
queryParam(paramName, attributes) {
|
|
var type = attributes.type,
|
|
required = attributes.required,
|
|
description = attributes.description,
|
|
allowMultiple = attributes.allowMultiple,
|
|
constraint, cfg;
|
|
|
|
if (attributes.isJoi) {
|
|
type = attributes;
|
|
required = undefined;
|
|
description = undefined;
|
|
allowMultiple = undefined;
|
|
}
|
|
|
|
constraint = type;
|
|
|
|
if (type) {
|
|
if (typeof required === 'boolean') {
|
|
constraint = required ? constraint.required() : constraint.optional();
|
|
}
|
|
if (typeof description === 'string') {
|
|
constraint = constraint.description(description);
|
|
}
|
|
if (typeof allowMultiple === 'boolean') {
|
|
constraint = constraint.meta({allowMultiple: allowMultiple});
|
|
}
|
|
this.constraints.queryParams[paramName] = constraint;
|
|
cfg = constraint.describe();
|
|
if (is.array(cfg)) {
|
|
cfg = cfg[0];
|
|
type = 'string';
|
|
} else {
|
|
type = cfg.type;
|
|
}
|
|
required = Boolean(cfg.flags && cfg.flags.presence === 'required');
|
|
description = cfg.description;
|
|
if (cfg.meta) {
|
|
if (!is.array(cfg.meta)) {
|
|
cfg.meta = [cfg.meta];
|
|
}
|
|
_.each(cfg.meta, function (meta) {
|
|
if (meta && typeof meta.allowMultiple === 'boolean') {
|
|
allowMultiple = meta.allowMultiple;
|
|
}
|
|
});
|
|
}
|
|
if (
|
|
type === 'number' &&
|
|
_.isArray(cfg.rules) &&
|
|
_.some(cfg.rules, function (rule) {
|
|
return rule.name === 'integer';
|
|
})
|
|
) {
|
|
type = 'integer';
|
|
}
|
|
}
|
|
|
|
this.docs.addQueryParam(
|
|
paramName,
|
|
description,
|
|
type,
|
|
required,
|
|
Boolean(allowMultiple)
|
|
);
|
|
return this;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @startDocuBlock JSF_foxx_RequestContext_bodyParam
|
|
///
|
|
/// `Route.bodyParam(paramName, options)`
|
|
///
|
|
/// Defines that this route expects a JSON body when requested and binds it to
|
|
/// a pseudo parameter with the name `paramName`.
|
|
/// The body can than be read in the the handler using `req.params(paramName)` on the request object.
|
|
/// In the `options` parameter you can define how a valid request body should look like.
|
|
/// This definition can be done in two ways, either using *joi* directly.
|
|
/// Accessing the body in this case will give you a JSON object.
|
|
/// The other way is to use a Foxx *Model*.
|
|
/// Accessing the body in this case will give you an instance of this Model.
|
|
/// For both ways an entry for the body will be added in the Documentation in ArangoDBs WebInterface.
|
|
/// For information about how to annotate your models, see the Model section.
|
|
/// All requests sending a body that does not match the validation given this way
|
|
/// will automatically be rejected.
|
|
///
|
|
/// You can also wrap the definition into an array, in this case this route
|
|
/// expects a body of type array containing arbitrary many valid objects.
|
|
/// Accessing the body parameter will then of course return an array of objects.
|
|
///
|
|
/// Note: The behavior of `bodyParam` changes depending on the `rootElement` option
|
|
/// set in the [manifest](../Develop/Manifest.md). If it is set to `true`, it is
|
|
/// expected that the body is an
|
|
/// object with a key of the same name as the `paramName` argument.
|
|
/// The value of this object is either a single object or in the case of a multi
|
|
/// element an array of objects.
|
|
///
|
|
/// *Parameter*
|
|
///
|
|
/// * *paramName*: name of the body parameter in `req.parameters`.
|
|
/// * *options*: a joi schema or an object with the following properties:
|
|
/// * *description*: the documentation description of the request body.
|
|
/// * *type*: the Foxx model or joi schema to use.
|
|
/// * *allowInvalid* (optional): `true` if validation should be skipped. (Default: `false`)
|
|
///
|
|
/// *Examples*
|
|
///
|
|
/// ```js
|
|
/// app.post("/foxx", function (req, res) {
|
|
/// var foxxBody = req.parameters.foxxBody;
|
|
/// // Do something with foxxBody
|
|
/// }).bodyParam("foxxBody", {
|
|
/// description: "Body of the Foxx",
|
|
/// type: FoxxBodyModel
|
|
/// });
|
|
/// ```
|
|
///
|
|
/// Using a joi schema:
|
|
///
|
|
/// ```js
|
|
/// app.post("/foxx", function (req, res) {
|
|
/// var joiBody = req.parameters.joiBody;
|
|
/// // Do something with the number
|
|
/// }).bodyParam("joiBody", {
|
|
/// type: joi.number().integer().min(5),
|
|
/// description: "A number greater than five",
|
|
/// allowInvalid: false // default
|
|
/// });
|
|
/// ```
|
|
///
|
|
/// Shorthand version:
|
|
///
|
|
/// ```js
|
|
/// app.post("/foxx", function (req, res) {
|
|
/// var joiBody = req.parameters.joiBody;
|
|
/// // Do something with the number
|
|
/// }).bodyParam(
|
|
/// "joiBody",
|
|
/// joi.number().integer().min(5)
|
|
/// .description("A number greater than five")
|
|
/// .meta({allowInvalid: false}) // default
|
|
/// );
|
|
/// ```
|
|
/// @endDocuBlock
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bodyParam(paramName, attributes) {
|
|
var type = attributes.type,
|
|
description = attributes.description,
|
|
allowInvalid = attributes.allowInvalid,
|
|
validateOptions = {},
|
|
cfg, construct;
|
|
|
|
if (attributes.isJoi) {
|
|
type = attributes;
|
|
description = undefined;
|
|
allowInvalid = undefined;
|
|
}
|
|
|
|
if (!type) {
|
|
construct = function (raw) {
|
|
return raw;
|
|
};
|
|
} else if (typeof type === 'function' || is.array(type)) {
|
|
// assume ModelOrSchema is a Foxx Model
|
|
construct = createModelInstantiator(type, allowInvalid);
|
|
} else {
|
|
if (!type.isJoi) {
|
|
type = joi.object().keys(type).required();
|
|
}
|
|
if (typeof allowInvalid === 'boolean') {
|
|
type = type.meta({allowInvalid: allowInvalid});
|
|
}
|
|
if (typeof description === 'string') {
|
|
type = type.description(description);
|
|
}
|
|
cfg = type.describe();
|
|
description = cfg.description;
|
|
if (cfg.meta) {
|
|
if (!is.array(cfg.meta)) {
|
|
cfg.meta = [cfg.meta];
|
|
}
|
|
_.each(cfg.meta, function (meta) {
|
|
if (meta && typeof meta.allowInvalid === 'boolean') {
|
|
allowInvalid = meta.allowInvalid;
|
|
}
|
|
});
|
|
}
|
|
if (cfg.options) {
|
|
if (!is.array(cfg.options)) {
|
|
cfg.options = [cfg.options];
|
|
}
|
|
_.each(cfg.options, function (options) {
|
|
_.extend(validateOptions, options);
|
|
});
|
|
}
|
|
construct = function (raw) {
|
|
return validateOrThrow(raw, type, allowInvalid, validateOptions);
|
|
};
|
|
}
|
|
|
|
this.docs.addBodyParam(
|
|
paramName,
|
|
description,
|
|
toJSONSchema(paramName, is.array(type) ? type[0] : type)
|
|
);
|
|
this.route.action.bodyParams.push({
|
|
extract: createBodyParamExtractor(this.rootElement, paramName, allowInvalid),
|
|
paramName: paramName,
|
|
construct: construct
|
|
});
|
|
return this;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @startDocuBlock JSF_foxx_RequestContext_summary
|
|
///
|
|
/// `Route.summary(description)`
|
|
///
|
|
/// Set the summary for this route in the documentation.
|
|
/// Can't be longer than 8192 characters.
|
|
/// This is equal to using JavaDoc style comments right above your function.
|
|
/// If you provide both comment and `summary()` the call to `summary()` wins
|
|
/// and will be used.
|
|
///
|
|
/// *Examples*
|
|
///
|
|
/// Version with comment:
|
|
///
|
|
/// ```js
|
|
/// /** Short description
|
|
/// *
|
|
/// * Longer description
|
|
/// * with multiple lines
|
|
/// */
|
|
/// app.get("/foxx", function() {
|
|
/// });
|
|
/// ```
|
|
///
|
|
/// is identical to:
|
|
///
|
|
/// ```js
|
|
/// app.get("/foxx", function() {
|
|
/// })
|
|
/// .summary("Short description")
|
|
/// .notes(["Longer description", "with multiple lines"]);
|
|
/// ```
|
|
///
|
|
/// @endDocuBlock
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
summary(summary) {
|
|
if (summary.length > 8192) {
|
|
throw new Error('Summary can\'t be longer than 8192 characters');
|
|
}
|
|
this.docs.addSummary(summary);
|
|
return this;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @startDocuBlock JSF_foxx_RequestContext_notes
|
|
///
|
|
/// `Route.notes(...description)`
|
|
///
|
|
/// Set the long description for this route in the documentation
|
|
//
|
|
/// *Examples*
|
|
///
|
|
/// Version with comment:
|
|
///
|
|
/// ```js
|
|
/// /** Short description
|
|
/// *
|
|
/// * Longer description
|
|
/// * with multiple lines
|
|
/// */
|
|
/// app.get("/foxx", function() {
|
|
/// });
|
|
/// ```
|
|
///
|
|
/// is identical to:
|
|
///
|
|
/// ```js
|
|
/// app.get("/foxx", function() {
|
|
/// })
|
|
/// .summary("Short description")
|
|
/// .notes(["Longer description", "with multiple lines"]);
|
|
/// ```
|
|
///
|
|
/// @endDocuBlock
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
notes() {
|
|
var notes = Array.prototype.join.call(arguments, '\n');
|
|
this.docs.addNotes(notes);
|
|
return this;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @startDocuBlock JSF_foxx_RequestContext_errorResponse
|
|
///
|
|
/// `Route.errorResponse(errorClassOrName, code, description, [callback])`
|
|
///
|
|
/// Define a reaction to a thrown error for this route: If your handler throws an error
|
|
/// of the errorClass defined in `errorClassOrName` or the error has an attribute `name` equal to `errorClassOrName`,
|
|
/// it will be caught and the response object will be filled with the given
|
|
/// status code and a JSON with error set to your description as the body.
|
|
///
|
|
/// If you want more control over the returned JSON, you can give an optional fourth
|
|
/// parameter in form of a function. It gets the error as an argument, the return
|
|
/// value will be transformed into JSON and then be used as the body.
|
|
/// The status code will be used as described above. The description will be used for
|
|
/// the documentation.
|
|
///
|
|
/// It also adds documentation for this error response to the generated documentation.
|
|
///
|
|
/// *Examples*
|
|
///
|
|
/// ```js
|
|
/// /* define our own error type, FoxxyError */
|
|
/// var FoxxyError = function (message) {
|
|
/// this.name = "FError";
|
|
/// this.message = "the following FoxxyError occurred: " + message;
|
|
/// };
|
|
/// FoxxyError.prototype = new Error();
|
|
///
|
|
/// app.get("/foxx", function {
|
|
/// /* throws a FoxxyError */
|
|
/// throw new FoxxyError();
|
|
/// }).errorResponse(FoxxyError, 303, "This went completely wrong. Sorry!");
|
|
///
|
|
/// app.get("/foxx", function {
|
|
/// throw new FoxxyError("oops!");
|
|
/// }).errorResponse("FError", 303, "This went completely wrong. Sorry!", function (e) {
|
|
/// return {
|
|
/// code: 123,
|
|
/// desc: e.message
|
|
/// };
|
|
/// });
|
|
/// ```
|
|
/// @endDocuBlock
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
errorResponse(errorClass, code, reason, errorHandler) {
|
|
this.route.action.errorResponses.push({
|
|
errorClass: errorClass,
|
|
code: code,
|
|
reason: reason,
|
|
errorHandler: errorHandler
|
|
});
|
|
this.route.docs.errorResponses.push({code: code, reason: reason});
|
|
return this;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @startDocuBlock JSF_foxx_RequestContext_onlyIf
|
|
///
|
|
/// `Route.onlyIf(check)`
|
|
///
|
|
/// This functionality is used to secure a route by applying a checking function
|
|
/// on the request beforehand, for example the check authorization.
|
|
/// It expects `check` to be a function that takes the request object as first parameter.
|
|
/// This function is executed before the actual handler is invoked.
|
|
/// If `check` throws an error the actual handler will not be invoked.
|
|
/// Remember to provide an `errorResponse` on the route as well to define the behavior in this case.
|
|
///
|
|
/// *Examples*
|
|
///
|
|
/// ```js
|
|
/// app.get("/foxx", function {
|
|
/// // Do something
|
|
/// }).onlyIf(aFunction).errorResponse(ErrorClass, 303, "This went completely wrong. Sorry!");
|
|
/// ```
|
|
/// @endDocuBlock
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
onlyIf(check) {
|
|
this.route.action.checks.push({
|
|
check: check
|
|
});
|
|
return this;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @startDocuBlock JSF_foxx_RequestContext_onlyIfAuthenticated
|
|
///
|
|
/// `FoxxController#onlyIfAuthenticated(code, reason)`
|
|
///
|
|
/// Please activate sessions for this app if you want to use this function.
|
|
/// Or activate authentication (deprecated).
|
|
/// If the user is logged in, it will do nothing. Otherwise it will respond with
|
|
/// the status code and the reason you provided (the route handler won't be called).
|
|
/// This will also add the according documentation for this route.
|
|
///
|
|
/// *Examples*
|
|
///
|
|
/// ```js
|
|
/// app.get("/foxx", function {
|
|
/// // Do something
|
|
/// }).onlyIfAuthenticated(401, "You need to be authenticated");
|
|
/// ```
|
|
/// @endDocuBlock
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
onlyIfAuthenticated(code, reason) {
|
|
var check;
|
|
|
|
check = function (req) {
|
|
if (
|
|
!(req.session && req.session.get('uid')) // new and shiny
|
|
&& !(req.user && req.currentSession) // old and busted
|
|
) {
|
|
throw new UnauthorizedError();
|
|
}
|
|
};
|
|
|
|
if (is.notExisty(code)) {
|
|
code = 401;
|
|
}
|
|
if (is.notExisty(reason)) {
|
|
reason = 'Not authorized';
|
|
}
|
|
|
|
this.onlyIf(check);
|
|
this.errorResponse(UnauthorizedError, code, reason);
|
|
|
|
return this;
|
|
}
|
|
}
|
|
|
|
class RequestContextBuffer {
|
|
constructor() {
|
|
this.applyChain = [];
|
|
}
|
|
}
|
|
|
|
_.extend(RequestContextBuffer.prototype, {
|
|
applyEachFunction(target) {
|
|
_.each(this.applyChain, function (x) {
|
|
target[x.functionName].apply(target, x.argumentList);
|
|
});
|
|
}
|
|
});
|
|
|
|
_.each([
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @startDocuBlock JSF_foxx_RequestContextBuffer_pathParam
|
|
///
|
|
/// `Controller.allRoutes.pathParam(id, options)`
|
|
///
|
|
/// This is equal to invoking `Route.pathParam` on all routes bound to this controller.
|
|
///
|
|
/// *Examples*
|
|
///
|
|
/// ```js
|
|
/// app.allRoutes.pathParam("id", joi.string().required().description("Id of the Foxx"));
|
|
///
|
|
/// app.get("/foxx/:id", function {
|
|
/// // Secured by pathParam
|
|
/// });
|
|
/// ```
|
|
///
|
|
/// You can also pass in a configuration object instead:
|
|
///
|
|
/// ```js
|
|
/// app.allRoutes.pathParam("id", {
|
|
/// type: joi.string(),
|
|
/// required: true,
|
|
/// description: "Id of the Foxx"
|
|
/// });
|
|
///
|
|
/// app.get("/foxx/:id", function {
|
|
/// // Secured by pathParam
|
|
/// });
|
|
/// ```
|
|
/// @endDocuBlock
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
'pathParam',
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @startDocuBlock JSF_foxx_RequestContextBuffer_queryParam
|
|
///
|
|
/// `Controller.allRoutes.queryParam(id, options)`
|
|
///
|
|
/// This is equal to invoking `Route.queryParam` on all routes bound to this controller.
|
|
///
|
|
/// *Examples*
|
|
///
|
|
/// ```js
|
|
/// app.allroutes.queryParam("id",
|
|
/// joi.string()
|
|
/// .required()
|
|
/// .description("Id of the Foxx")
|
|
/// .meta({allowMultiple: false})
|
|
/// });
|
|
///
|
|
/// app.get("/foxx", function {
|
|
/// // Do something
|
|
/// });
|
|
/// ```
|
|
///
|
|
/// You can also pass in a configuration object instead:
|
|
///
|
|
/// ```js
|
|
/// app.allroutes.queryParam("id", {
|
|
/// type: joi.string().required().description("Id of the Foxx"),
|
|
/// allowMultiple: false
|
|
/// });
|
|
///
|
|
/// app.get("/foxx", function {
|
|
/// // Do something
|
|
/// });
|
|
/// ```
|
|
/// @endDocuBlock
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
'queryParam',
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @startDocuBlock JSF_foxx_RequestContextBuffer_errorResponse
|
|
///
|
|
/// `Controller.allRoutes.errorResponse(errorClass, code, description)`
|
|
///
|
|
/// This is equal to invoking `Route.errorResponse` on all routes bound to this controller.
|
|
///
|
|
/// *Examples*
|
|
///
|
|
/// ```js
|
|
/// app.allRoutes.errorResponse(FoxxyError, 303, "This went completely wrong. Sorry!");
|
|
///
|
|
/// app.get("/foxx", function {
|
|
/// // Do something
|
|
/// });
|
|
/// ```
|
|
/// @endDocuBlock
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
'errorResponse',
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @startDocuBlock JSF_foxx_RequestContextBuffer_onlyIf
|
|
///
|
|
/// `Controller.allRoutes.onlyIf(code, reason)`
|
|
///
|
|
/// This is equal to invoking `Route.onlyIf` on all routes bound to this controller.
|
|
///
|
|
/// *Examples*
|
|
///
|
|
/// ```js
|
|
/// app.allRoutes.onlyIf(myPersonalCheck);
|
|
///
|
|
/// app.get("/foxx", function {
|
|
/// // Do something
|
|
/// });
|
|
/// ```
|
|
/// @endDocuBlock
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
'onlyIf',
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @startDocuBlock JSF_foxx_RequestContextBuffer_onlyIfAuthenticated
|
|
///
|
|
/// `Controller.allRoutes.onlyIfAuthenticated(code, description)`
|
|
///
|
|
/// This is equal to invoking `Route.onlyIfAuthenticated` on all routes bound to this controller.
|
|
///
|
|
/// *Examples*
|
|
///
|
|
/// ```js
|
|
/// app.allRoutes.onlyIfAuthenticated(401, "You need to be authenticated");
|
|
///
|
|
/// app.get("/foxx", function {
|
|
/// // Do something
|
|
/// });
|
|
/// ```
|
|
/// @endDocuBlock
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
'onlyIfAuthenticated'
|
|
], function (functionName) {
|
|
_.extend(RequestContextBuffer.prototype[functionName] = function () {
|
|
this.applyChain.push({
|
|
functionName: functionName,
|
|
argumentList: arguments
|
|
});
|
|
return this;
|
|
});
|
|
});
|
|
|
|
RequestContext.Buffer = RequestContextBuffer;
|
|
|
|
module.exports = exports = RequestContext;
|
|
exports.RequestContext = RequestContext;
|
|
exports.RequestContextBuffer = RequestContextBuffer;
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// --SECTION-- END-OF-FILE
|
|
// -----------------------------------------------------------------------------
|
|
|
|
/// Local Variables:
|
|
/// mode: outline-minor
|
|
/// outline-regexp: "/// @brief\\|/// @addtogroup\\|/// @page\\|// --SECTION--\\|/// @\\}\\|/\\*jslint"
|
|
/// End:
|