3.1. Design Documents

    But before we start to write our first document, let’s take a look at the list of common objects that will be used during our code journey - we’ll be using them extensively within each function:

    Design documents contain functions such as view and update functions. These functions are executed when requested.

    Design documents are denoted by an id field with the format . Their structure follows the example below.

    Example:

    As you can see, a design document can include multiple functions of the same type. The example defines two views, both of which have a map function and one of which has a reduce function. It also defines two update functions and one filter function. The Validate Document Update function is a special case, as each design document cannot contain more than one of those.

    3.1.2. View Functions

    Views are the primary tool used for querying and reporting on CouchDB databases.

    mapfun(doc)

    • Arguments

      • doc – The document that is being processed

    Map functions accept a single document as the argument and (optionally) key/value pairs that are stored in a view.

    1. function (doc) {
    2. if (doc.type === 'post' && doc.tags && Array.isArray(doc.tags)) {
    3. doc.tags.forEach(function (tag) {
    4. emit(tag.toLowerCase(), 1);
    5. });
    6. }
    7. }

    In this example a key/value pair is emitted for each value in the tags array of a document with a type of “post”. Note that emit() may be called many times for a single document, so the same document may be available by several different keys.

    Also keep in mind that each document is sealed to prevent the situation where one map function changes document state and another receives a modified version.

    For efficiency reasons, documents are passed to a group of map functions - each document is processed by a group of map functions from all views of the related design document. This means that if you trigger an index update for one view in the design document, all others will get updated too.

    Since version 1.1.0, map supports modules and the require() function.

    redfun(keys, values[, rereduce])

    • Arguments

      • keys – Array of pairs of key-docid for related map function results. Always null if rereduce is running (has true value).

      • values – Array of map function result values.

      • rereduce – Boolean flag to indicate a rereduce run.

      Returns

      Reduces values

    Reduce functions take two required arguments of keys and values lists - the result of the related map function - and an optional third value which indicates if rereduce mode is active or not. Rereduce is used for additional reduce values list, so when it is true there is no information about related keys (first argument is null).

    Note that if the result of a reduce function is longer than the initial values list then a Query Server error will be raised. However, this behavior can be disabled by setting reduce_limit config option to false:

    1. [query_server_config]
    2. reduce_limit = false

    While disabling reduce_limit might be useful for debug proposes, remember that the main task of reduce functions is to reduce the mapped result, not to make it bigger. Generally, your reduce function should converge rapidly to a single value - which could be an array or similar object.

    3.1.2.2.1. Built-in Reduce Functions

    Additionally, CouchDB has a set of built-in reduce functions. These are implemented in Erlang and run inside CouchDB, so they are much faster than the equivalent JavaScript functions.

    _approx_count_distinct

    New in version 2.2.

    Approximates the number of distinct keys in a view index using a variant of the HyperLogLog algorithm. This algorithm enables an efficient, parallelizable computation of cardinality using fixed memory resources. CouchDB has configured the underlying data structure to have a relative error of ~2%.

    As this reducer ignores the emitted values entirely, an invocation with group=true will simply return a value of 1 for every distinct key in the view. In the case of array keys, querying the view with a group_level specified will return the number of distinct keys that share the common group prefix in each row. The algorithm is also cognizant of the startkey and endkey boundaries and will return the number of distinct keys within the specified key range.

    A final note regarding Unicode collation: this reduce function uses the binary representation of each key in the index directly as input to the HyperLogLog filter. As such, it will (incorrectly) consider keys that are not byte identical but that compare equal according to the Unicode collation rules to be distinct keys, and thus has the potential to overestimate the cardinality of the key space if a large number of such keys exist.

    _count

    Counts the number of values in the index with a given key. This could be implemented in JavaScript as:

    1. // could be replaced by _count
    2. function(keys, values, rereduce) {
    3. if (rereduce) {
    4. return sum(values);
    5. } else {
    6. return values.length;
    7. }
    8. }

    _stats

    Computes the following quantities for numeric values associated with each key: sum, min, max, count, and sumsqr. The behavior of the _stats function varies depending on the output of the map function. The simplest case is when the map phase emits a single numeric value for each key. In this case the _stats function is equivalent to the following JavaScript:

    1. // could be replaced by _stats
    2. function(keys, values, rereduce) {
    3. if (rereduce) {
    4. return {
    5. 'sum': values.reduce(function(a, b) { return a + b.sum }, 0),
    6. 'min': values.reduce(function(a, b) { return Math.min(a, b.min) }, Infinity),
    7. 'max': values.reduce(function(a, b) { return Math.max(a, b.max) }, -Infinity),
    8. 'count': values.reduce(function(a, b) { return a + b.count }, 0),
    9. 'sumsqr': values.reduce(function(a, b) { return a + b.sumsqr }, 0)
    10. }
    11. } else {
    12. return {
    13. 'sum': sum(values),
    14. 'min': Math.min.apply(null, values),
    15. 'max': Math.max.apply(null, values),
    16. 'count': values.length,
    17. 'sumsqr': (function() {
    18. var sumsqr = 0;
    19. values.forEach(function (value) {
    20. sumsqr += value * value;
    21. });
    22. return sumsqr;
    23. })(),
    24. }
    25. }
    26. }

    The _stats function will also work with “pre-aggregated” values from a map phase. A map function that emits an object containing sum, min, max, count, and sumsqr keys and numeric values for each can use the _stats function to combine these results with the data from other documents. The emitted object may contain other keys (these are ignored by the reducer), and it is also possible to mix raw numeric values and pre-aggregated objects in a single view and obtain the correct aggregated statistics.

    Finally, _stats can operate on key-value pairs where each value is an array comprised of numbers or pre-aggregated objects. In this case every value emitted from the map function must be an array, and the arrays must all be the same length, as _stats will compute the statistical quantities above independently for each element in the array. Users who want to compute statistics on multiple values from a single document should either emit each value into the index separately, or compute the statistics for the set of values using the JavaScript example above and emit a pre-aggregated object.

    _sum

    In its simplest variation, _sum sums the numeric values associated with each key, as in the following JavaScript:

    1. // could be replaced by _sum
    2. function(keys, values) {
    3. return sum(values);
    4. }

    As with _stats, the _sum function offers a number of extended capabilities. The _sum function requires that map values be numbers, arrays of numbers, or objects. When presented with array output from a map function, _sum will compute the sum for every element of the array. A bare numeric value will be treated as an array with a single element, and arrays with fewer elements will be treated as if they contained zeroes for every additional element in the longest emitted array. As an example, consider the following map output:

    1. {"total_rows":5, "offset":0, "rows": [
    2. {"id":"id1", "key":"abc", "value": 2},
    3. {"id":"id2", "key":"abc", "value": [3,5,7]},
    4. {"id":"id2", "key":"def", "value": [0,0,0,42]},
    5. {"id":"id2", "key":"ghi", "value": 1},
    6. {"id":"id1", "key":"ghi", "value": 3}
    7. ]}

    The _sum for this output without any grouping would be:

    1. {"rows": [
    2. {"key":null, "value": [9,5,7,42]}
    3. ]}

    while the grouped output would be

    This is in contrast to the behavior of the _stats function which requires that all emitted values be arrays of identical length if any array is emitted.

    Note

    Why don’t reduce functions support CommonJS modules?

    While map functions have limited access to stored modules through , there is no such feature for reduce functions. The reason lies deep inside the way map and reduce functions are processed by the Query Server. Let’s take a look at map functions first:

    1. CouchDB sends all map functions in a processed design document to the Query Server.

    2. the Query Server handles them one by one, compiles and puts them onto an internal stack.

    3. after all map functions have been processed, CouchDB will send the remaining documents for indexing, one by one.

    4. the Query Server receives the document object and applies it to every function from the stack. The emitted results are then joined into a single array and sent back to CouchDB.

    Now let’s see how reduce functions are handled:

    1. CouchDB sends as a single command the list of available reduce functions with the result list of key-value pairs that were previously returned from the map functions.

    2. the Query Server compiles the reduce functions and applies them to the key-value lists. The reduced result is sent back to CouchDB.

    As you may note, reduce functions are applied in a single shot to the map results while map functions are applied to documents one by one. This means that it’s possible for map functions to precompile CommonJS libraries and use them during the entire view processing, but for reduce functions they would be compiled again and again for each view result reduction, which would lead to performance degradation.

    Warning

    Show functions are deprecated in CouchDB 3.0, and will be removed in CouchDB 4.0.

    showfun(doc, req)

    • Arguments

      • doc – The document that is being processed; may be omitted.

      • reqRequest object.

      Returns

      Return type

      object or string

    Show functions are used to represent documents in various formats, commonly as HTML pages with nice formatting. They can also be used to run server-side functions without requiring a pre-existing document.

    Basic example of show function could be:

    1. function(doc, req){
    2. if (doc) {
    3. return "Hello from " + doc._id + "!";
    4. } else {
    5. return "Hello, world!";
    6. }
    7. }

    Also, there is more simple way to return json encoded data:

    1. function(doc, req){
    2. return {
    3. 'json': {
    4. 'id': doc['_id'],
    5. 'rev': doc['_rev']
    6. }
    7. }
    8. }

    and even files (this one is CouchDB logo):

    1. function(doc, req){
    2. return {
    3. 'headers': {
    4. 'Content-Type' : 'image/png',
    5. },
    6. 'base64': ''.concat(
    7. 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAsV',
    8. 'BMVEUAAAD////////////////////////5ur3rEBn////////////////wDBL/',
    9. 'AADuBAe9EB3IEBz/7+//X1/qBQn2AgP/f3/ilpzsDxfpChDtDhXeCA76AQH/v7',
    10. '/84eLyWV/uc3bJPEf/Dw/uw8bRWmP1h4zxSlD6YGHuQ0f6g4XyQkXvCA36MDH6',
    11. 'wMH/z8/yAwX64ODeh47BHiv/Ly/20dLQLTj98PDXWmP/Pz//39/wGyJ7Iy9JAA',
    12. 'AADHRSTlMAbw8vf08/bz+Pv19jK/W3AAAAg0lEQVR4Xp3LRQ4DQRBD0QqTm4Y5',
    13. 'vaND1c8OG4vrdOqD8YwgpDYDxRgkSm5rwu0nQVBJuMg++pLXZyr5jnc1BaH4GT',
    14. 'LvEliY253nA3pVhQqdPt0f/erJkMGMB8xucAAAAASUVORK5CYII=')
    15. }
    16. }

    But what if you need to represent data in different formats via a single function? Functions registerType() and are your the best friends in that question:

    1. function(doc, req){
    2. return {'json': doc}
    3. });
    4. provides('html', function(){
    5. return '<pre>' + toJSON(doc) + '</pre>'
    6. })
    7. provides('xml', function(){
    8. return {
    9. 'headers': {'Content-Type': 'application/xml'},
    10. 'body' : ''.concat(
    11. '<?xml version="1.0" encoding="utf-8"?>\n',
    12. '<doc>',
    13. (function(){
    14. escape = function(s){
    15. return s.replace(/&quot;/g, '"')
    16. .replace(/&gt;/g, '>')
    17. .replace(/&lt;/g, '<')
    18. .replace(/&amp;/g, '&');
    19. };
    20. var content = '';
    21. for(var key in doc){
    22. if(!doc.hasOwnProperty(key)) continue;
    23. var value = escape(toJSON(doc[key]));
    24. var key = escape(key);
    25. content += ''.concat(
    26. '<' + key + '>',
    27. value
    28. '</' + key + '>'
    29. )
    30. }
    31. return content;
    32. })(),
    33. '</doc>'
    34. )
    35. }
    36. })
    37. registerType('text-json', 'text/json')
    38. provides('text-json', function(){
    39. return toJSON(doc);
    40. })
    41. }

    This function may return html, json , xml or our custom text json format representation of same document object with same processing rules. Probably, the xml provider in our function needs more care to handle nested objects correctly, and keys with invalid characters, but you’ve got the idea!

    See also

    CouchDB Guide:

    3.1.4. List Functions

    Warning

    List functions are deprecated in CouchDB 3.0, and will be removed in CouchDB 4.0.

    listfun(head, req)

    While Show Functions are used to customize document presentation, are used for the same purpose, but on View Functions results.

    The following list function formats the view and represents it as a very simple HTML page:

    1. function(head, req){
    2. start({
    3. 'headers': {
    4. 'Content-Type': 'text/html'
    5. }
    6. });
    7. send('<html><body><table>');
    8. send('<tr><th>ID</th><th>Key</th><th>Value</th></tr>');
    9. while(row = getRow()){
    10. send(''.concat(
    11. '<tr>',
    12. '<td>' + toJSON(row.id) + '</td>',
    13. '<td>' + toJSON(row.key) + '</td>',
    14. '<td>' + toJSON(row.value) + '</td>',
    15. '</tr>'
    16. ));
    17. }
    18. send('</table></body></html>');
    19. }

    Templates and styles could obviously be used to present data in a nicer fashion, but this is an excellent starting point. Note that you may also use and provides() functions in a similar way as for ! However, note that provides() expects the return value to be a string when used inside a list function, so you’ll need to use to set any custom headers and stringify your JSON before returning it.

    See also

    CouchDB Guide:

    updatefun(doc, req)

    • Arguments

      • doc – The document that is being processed.

      Returns

      Two-element array: the first element is the (updated or new) document, which is committed to the database. If the first element is null no document will be committed to the database. If you are updating an existing document, it should already have an _id set, and if you are creating a new document, make sure to set its _id to something, either generated based on the input or the req.uuid provided. The second element is the response that will be sent back to the caller.

    Update handlers are functions that clients can request to invoke server-side logic that will create or update a document. This feature allows a range of use cases such as providing a server-side last modified timestamp, updating individual fields in a document without first getting the latest revision, etc.

    When the request to an update handler includes a document ID in the URL, the server will provide the function with the most recent version of that document. You can provide any other values needed by the update handler function via the POST/PUT entity body or query string parameters of the request.

    A basic example that demonstrates all use-cases of update handlers:

    1. function(doc, req){
    2. if (!doc){
    3. if ('id' in req && req['id']){
    4. // create new document
    5. return [{'_id': req['id']}, 'New World']
    6. }
    7. // change nothing in database
    8. return [null, 'Empty World']
    9. }
    10. doc['world'] = 'hello';
    11. doc['edited_by'] = req['userCtx']['name']
    12. return [doc, 'Edited World!']
    13. }

    3.1.6. Filter Functions

    filterfun(doc, req)

    • Arguments

      Returns

      Boolean value: true means that doc passes the filter rules, false means that it does not.

    Filter functions mostly act like and List Functions: they format, or filter the .

    By default the changes feed emits all database documents changes. But if you’re waiting for some special changes, processing all documents is inefficient.

    Filters are special design document functions that allow the changes feed to emit only specific documents that pass filter rules.

    Let’s assume that our database is a mailbox and we need to handle only new mail events (documents with the status new). Our filter function would look like this:

    1. function(doc, req){
    2. // we need only `mail` documents
    3. if (doc.type != 'mail'){
    4. return false;
    5. }
    6. // we're interested only in `new` ones
    7. if (doc.status != 'new'){
    8. return false;
    9. }
    10. return true; // passed!
    11. }

    Filter functions must return true if a document passed all the rules. Now, if you apply this function to the changes feed it will emit only changes about “new mails”:

    1. {"results":[
    2. {"seq":"1-g1AAAAF9eJzLYWBg4MhgTmHgz8tPSTV0MDQy1zMAQsMcoARTIkOS_P___7MymBMZc4EC7MmJKSmJqWaYynEakaQAJJPsoaYwgE1JM0o1TjQ3T2HgLM1LSU3LzEtNwa3fAaQ_HqQ_kQG3qgSQqnoCqvJYgCRDA5ACKpxPWOUCiMr9hFUegKi8T1jlA4hKkDuzAC2yZRo","id":"df8eca9da37dade42ee4d7aa3401f1dd","changes":[{"rev":"1-c2e0085a21d34fa1cecb6dc26a4ae657"}]},
    3. {"seq":"9-g1AAAAIreJyVkEsKwjAURUMrqCOXoCuQ5MU0OrI70XyppcaRY92J7kR3ojupaSPUUgqWwAu85By4t0AITbJYo5k7aUNSAnyJ_SGFf4gEkvOyLPMsFtHRL8ZKaC1M0v3eq5ALP-X2a0G1xYKhgnONpmenjT04o_v5tOJ3LV5itTES_uP3FX9ppcAACaVsQAo38hNd_eVFt8ZklVljPqSPYLoH06PJhG0Cxq7-yhQcz-B4_fQCjFuqBjjewVF3E9cORoExSrpU_gHBTo5m","id":"df8eca9da37dade42ee4d7aa34024714","changes":[{"rev":"1-29d748a6e87b43db967fe338bcb08d74"}]},
    4. ],
    5. "last_seq":"10-g1AAAAIreJyVkEsKwjAURR9tQR25BF2B5GMaHdmdaNIk1FLjyLHuRHeiO9Gd1LQRaimFlsALvOQcuLcAgGkWKpjbs9I4wYSvkDu4cA-BALkoyzLPQhGc3GKSCqWEjrvfexVy6abc_SxQWwzRVHCuYHaxSpuj1aqfTyp-3-IlSrdakmH8oeKvrRSIkJhSNiKFjdyEm7uc6N6YTKo3iI_pw5se3vRsMiETE23WgzJ5x8s73n-9EMYNTUc4Pt5RdxPVDkYJYxR3qfwLwW6OZw"}

    Note that the value of last_seq is 10-.., but we received only two records. Seems like any other changes were for documents that haven’t passed our filter.

    We probably need to filter the changes feed of our mailbox by more than a single status value. We’re also interested in statuses like “spam” to update spam-filter heuristic rules, “outgoing” to let a mail daemon actually send mails, and so on. Creating a lot of similar functions that actually do similar work isn’t good idea - so we need a dynamic filter.

    You may have noticed that filter functions take a second argument named request. This allows the creation of dynamic filters based on query parameters, and more.

    The dynamic version of our filter looks like this:

    1. function(doc, req){
    2. // we need only `mail` documents
    3. if (doc.type != 'mail'){
    4. return false;
    5. }
    6. // we're interested only in requested status
    7. if (doc.status != req.query.status){
    8. return false;
    9. }
    10. return true; // passed!
    11. }

    and now we have passed the status query parameter in the request to let our filter match only the required documents:

    1. GET /somedatabase/_changes?filter=mailbox/by_status&status=new HTTP/1.1
    1. {"results":[
    2. {"seq":"1-g1AAAAF9eJzLYWBg4MhgTmHgz8tPSTV0MDQy1zMAQsMcoARTIkOS_P___7MymBMZc4EC7MmJKSmJqWaYynEakaQAJJPsoaYwgE1JM0o1TjQ3T2HgLM1LSU3LzEtNwa3fAaQ_HqQ_kQG3qgSQqnoCqvJYgCRDA5ACKpxPWOUCiMr9hFUegKi8T1jlA4hKkDuzAC2yZRo","id":"df8eca9da37dade42ee4d7aa3401f1dd","changes":[{"rev":"1-c2e0085a21d34fa1cecb6dc26a4ae657"}]},
    3. {"seq":"9-g1AAAAIreJyVkEsKwjAURUMrqCOXoCuQ5MU0OrI70XyppcaRY92J7kR3ojupaSPUUgqWwAu85By4t0AITbJYo5k7aUNSAnyJ_SGFf4gEkvOyLPMsFtHRL8ZKaC1M0v3eq5ALP-X2a0G1xYKhgnONpmenjT04o_v5tOJ3LV5itTES_uP3FX9ppcAACaVsQAo38hNd_eVFt8ZklVljPqSPYLoH06PJhG0Cxq7-yhQcz-B4_fQCjFuqBjjewVF3E9cORoExSrpU_gHBTo5m","id":"df8eca9da37dade42ee4d7aa34024714","changes":[{"rev":"1-29d748a6e87b43db967fe338bcb08d74"}]},
    4. ],
    5. "last_seq":"10-g1AAAAIreJyVkEsKwjAURR9tQR25BF2B5GMaHdmdaNIk1FLjyLHuRHeiO9Gd1LQRaimFlsALvOQcuLcAgGkWKpjbs9I4wYSvkDu4cA-BALkoyzLPQhGc3GKSCqWEjrvfexVy6abc_SxQWwzRVHCuYHaxSpuj1aqfTyp-3-IlSrdakmH8oeKvrRSIkJhSNiKFjdyEm7uc6N6YTKo3iI_pw5se3vRsMiETE23WgzJ5x8s73n-9EMYNTUc4Pt5RdxPVDkYJYxR3qfwLwW6OZw"}

    and we can easily change filter behavior with:

    1. GET /somedatabase/_changes?filter=mailbox/by_status&status=spam HTTP/1.1
    1. {"results":[
    2. {"seq":"6-g1AAAAIreJyVkM0JwjAYQD9bQT05gk4gaWIaPdlNNL_UUuPJs26im-gmuklMjVClFFoCXyDJe_BSAsA4jxVM7VHpJEswWyC_ktJfRBzEzDlX5DGPDv5gJLlSXKfN560KMfdTbL4W-FgM1oQzpmByskqbvdWqnc8qfvvHCyTXWuBu_K7iz38VCOOUENqjwg79hIvfvOhamQahROoVYn3-I5huwXSvm5BJsTbLTk3B8QiO58-_YMoMkT0cr-BwdRElmFKSNKniDcAcjmM","id":"8960e91220798fc9f9d29d24ed612e0d","changes":[{"rev":"3-cc6ff71af716ddc2ba114967025c0ee0"}]},
    3. ],
    4. "last_seq":"10-g1AAAAIreJyVkEsKwjAURR9tQR25BF2B5GMaHdmdaNIk1FLjyLHuRHeiO9Gd1LQRaimFlsALvOQcuLcAgGkWKpjbs9I4wYSvkDu4cA-BALkoyzLPQhGc3GKSCqWEjrvfexVy6abc_SxQWwzRVHCuYHaxSpuj1aqfTyp-3-IlSrdakmH8oeKvrRSIkJhSNiKFjdyEm7uc6N6YTKo3iI_pw5se3vRsMiETE23WgzJ5x8s73n-9EMYNTUc4Pt5RdxPVDkYJYxR3qfwLwW6OZw"}

    Combining filters with a continuous feed allows creating powerful event-driven systems.

    View filters are the same as classic filters above, with one small difference: they use the map instead of the filter function of a view, to filter the changes feed. Each time a key-value pair is emitted from the map function, a change is returned. This allows avoiding filter functions that mostly do the same work as views.

    To use them just pass filter=_view and view=designdoc/viewname as request parameters to the changes feed:

    Note

    Since view filters use map functions as filters, they can’t show any dynamic behavior since is not available.

    See also

    CouchDB Guide:

    validatefun(newDoc, oldDoc, userCtx, secObj)

    • Arguments

      • newDoc – New version of document that will be stored.

      • userCtx

      • secObjSecurity Object

      Throws

      forbidden error to gracefully prevent document storing.

      Throws

      unauthorized error to prevent storage and allow the user to re-auth.

    A design document may contain a function named validate_doc_update which can be used to prevent invalid or unauthorized document update requests from being stored. The function is passed the new document from the update request, the current document stored in the database, a containing information about the user writing the document (if present), and a Security Object with lists of database security roles.

    Validation functions typically examine the structure of the new document to ensure that required fields are present and to verify that the requesting user should be allowed to make changes to the document properties. For example, an application may require that a user must be authenticated in order to create a new document or that specific document fields be present when a document is updated. The validation function can abort the pending document write by throwing one of two error objects:

    Document validation is optional, and each design document in the database may have at most one validation function. When a write request is received for a given database, the validation function in each design document in that database is called in an unspecified order. If any of the validation functions throw an error, the write will not succeed.

    Example: The _design/_auth ddoc from _users database uses a validation function to ensure that documents contain some required fields and are only modified by a user with the _admin role:

    1. function(newDoc, oldDoc, userCtx, secObj) {
    2. if (newDoc._deleted === true) {
    3. // allow deletes by admins and matching users
    4. // without checking the other fields
    5. if ((userCtx.roles.indexOf('_admin') !== -1) ||
    6. (userCtx.name == oldDoc.name)) {
    7. return;
    8. } else {
    9. throw({forbidden: 'Only admins may delete other user docs.'});
    10. }
    11. }
    12. if ((oldDoc && oldDoc.type !== 'user') || newDoc.type !== 'user') {
    13. throw({forbidden : 'doc.type must be user'});
    14. } // we only allow user docs for now
    15. if (!newDoc.name) {
    16. throw({forbidden: 'doc.name is required'});
    17. }
    18. if (!newDoc.roles) {
    19. throw({forbidden: 'doc.roles must exist'});
    20. }
    21. if (!isArray(newDoc.roles)) {
    22. throw({forbidden: 'doc.roles must be an array'});
    23. }
    24. if (newDoc._id !== ('org.couchdb.user:' + newDoc.name)) {
    25. throw({
    26. forbidden: 'Doc ID must be of the form org.couchdb.user:name'
    27. });
    28. }
    29. if (oldDoc) { // validate all updates
    30. if (oldDoc.name !== newDoc.name) {
    31. throw({forbidden: 'Usernames can not be changed.'});
    32. }
    33. }
    34. if (newDoc.password_sha && !newDoc.salt) {
    35. throw({
    36. forbidden: 'Users with password_sha must have a salt.' +
    37. 'See /_utils/script/couch.js for example code.'
    38. });
    39. }
    40. var is_server_or_database_admin = function(userCtx, secObj) {
    41. // see if the user is a server admin
    42. if(userCtx.roles.indexOf('_admin') !== -1) {
    43. return true; // a server admin
    44. }
    45. // see if the user a database admin specified by name
    46. if(secObj && secObj.admins && secObj.admins.names) {
    47. if(secObj.admins.names.indexOf(userCtx.name) !== -1) {
    48. return true; // database admin
    49. }
    50. }
    51. // see if the user a database admin specified by role
    52. if(secObj && secObj.admins && secObj.admins.roles) {
    53. var db_roles = secObj.admins.roles;
    54. for(var idx = 0; idx < userCtx.roles.length; idx++) {
    55. var user_role = userCtx.roles[idx];
    56. if(db_roles.indexOf(user_role) !== -1) {
    57. return true; // role matches!
    58. }
    59. }
    60. }
    61. return false; // default to no admin
    62. }
    63. if (!is_server_or_database_admin(userCtx, secObj)) {
    64. if (oldDoc) { // validate non-admin updates
    65. if (userCtx.name !== newDoc.name) {
    66. throw({
    67. forbidden: 'You may only update your own user document.'
    68. });
    69. }
    70. // validate role updates
    71. var oldRoles = oldDoc.roles.sort();
    72. var newRoles = newDoc.roles.sort();
    73. if (oldRoles.length !== newRoles.length) {
    74. throw({forbidden: 'Only _admin may edit roles'});
    75. }
    76. for (var i = 0; i < oldRoles.length; i++) {
    77. if (oldRoles[i] !== newRoles[i]) {
    78. throw({forbidden: 'Only _admin may edit roles'});
    79. }
    80. }
    81. } else if (newDoc.roles.length > 0) {
    82. throw({forbidden: 'Only _admin may set roles'});
    83. }
    84. }
    85. // no system roles in users db
    86. for (var i = 0; i < newDoc.roles.length; i++) {
    87. if (newDoc.roles[i][0] === '_') {
    88. throw({
    89. forbidden:
    90. 'No system roles (starting with underscore) in users db.'
    91. });
    92. }
    93. }
    94. // no system names as names
    95. if (newDoc.name[0] === '_') {
    96. throw({forbidden: 'Username may not start with underscore.'});
    97. }
    98. var badUserNameChars = [':'];
    99. for (var i = 0; i < badUserNameChars.length; i++) {
    100. if (newDoc.name.indexOf(badUserNameChars[i]) >= 0) {
    101. throw({forbidden: 'Character `' + badUserNameChars[i] +
    102. '` is not allowed in usernames.'});
    103. }
    104. }
    105. }

    Note

    The return statement is used only for function, it has no impact on the validation process.

    See also

    CouchDB Guide: