Current designdoc for live CouchDB:
function t (newDoc, oldDoc, userCtx, secObj) {
if (newDoc._deleted === true) {
// allow deletes by admins and matching users
// without checking the other fields
if (
userCtx.roles.indexOf ('_admin') !== -1 ||
userCtx.name == oldDoc.name
) {
return;
} else {
throw {forbidden: 'Only admins may delete other user docs.'};
}
}
if (newDoc.type !== 'user') {
throw {forbidden: 'doc.type must be user'};
} // we only allow user docs for now
if (!newDoc.name) {
throw {forbidden: 'doc.name is required'};
}
if (!newDoc.roles) {
throw {forbidden: 'doc.roles must exist'};
}
if (!isArray (newDoc.roles)) {
throw {forbidden: 'doc.roles must be an array'};
}
for (var idx = 0; idx < newDoc.roles.length; idx++) {
if (typeof newDoc.roles[idx] !== 'string') {
throw {forbidden: 'doc.roles can only contain strings'};
}
}
if (newDoc._id !== 'org.couchdb.user:' + newDoc.name) {
throw {
forbidden: 'Doc ID must be of the form org.couchdb.user:name',
};
}
if (oldDoc) {
// validate all updates
if (oldDoc.name !== newDoc.name) {
throw {forbidden: 'Usernames can not be changed.'};
}
}
if (newDoc.password_sha && !newDoc.salt) {
throw {
forbidden: 'Users with password_sha must have a salt.' +
'See /_utils/script/couch.js for example code.',
};
}
var available_schemes = ['simple', 'pbkdf2', 'simple+pbkdf2'];
if (
newDoc.password_scheme &&
available_schemes.indexOf (newDoc.password_scheme) == -1
) {
throw {
forbidden: 'Password scheme `' +
newDoc.password_scheme +
'` not supported.',
};
}
if (newDoc.password_scheme === 'pbkdf2') {
if (typeof newDoc.iterations !== 'number') {
throw {forbidden: 'iterations must be a number.'};
}
if (typeof newDoc.derived_key !== 'string') {
throw {forbidden: 'derived_key must be a string.'};
}
}
var is_server_or_database_admin = function (userCtx, secObj) {
// see if the user is a server admin
if (userCtx.roles.indexOf ('_admin') !== -1) {
return true; // a server admin
}
// see if the user a database admin specified by name
if (secObj && secObj.admins && secObj.admins.names) {
if (secObj.admins.names.indexOf (userCtx.name) !== -1) {
return true; // database admin
}
}
// see if the user a database admin specified by role
if (secObj && secObj.admins && secObj.admins.roles) {
var db_roles = secObj.admins.roles;
for (var idx = 0; idx < userCtx.roles.length; idx++) {
var user_role = userCtx.roles[idx];
if (db_roles.indexOf (user_role) !== -1) {
return true; // role matches!
}
}
}
return false; // default to no admin
};
if (!is_server_or_database_admin (userCtx, secObj)) {
if (oldDoc) {
// validate non-admin updates
if (userCtx.name !== newDoc.name) {
throw {
forbidden: 'You may only update your own user document.',
};
}
// validate role updates
var oldRoles = (oldDoc.roles || []).sort ();
var newRoles = newDoc.roles.sort ();
if (oldRoles.length !== newRoles.length) {
throw {forbidden: 'Only _admin may edit roles'};
}
for (var i = 0; i < oldRoles.length; i++) {
if (oldRoles[i] !== newRoles[i]) {
throw {forbidden: 'Only _admin may edit roles'};
}
}
} else if (newDoc.roles.length > 0) {
throw {forbidden: 'Only _admin may set roles'};
}
}
// no system roles in users db
for (var i = 0; i < newDoc.roles.length; i++) {
if (newDoc.roles[i] !== '_metrics') {
if (newDoc.roles[i][0] === '_') {
throw {
forbidden: 'No system roles (starting with underscore) in users db.',
};
}
}
}
// no system names as names
if (newDoc.name[0] === '_') {
throw {forbidden: 'Username may not start with underscore.'};
}
var badUserNameChars = [':'];
for (var i = 0; i < badUserNameChars.length; i++) {
if (newDoc.name.indexOf (badUserNameChars[i]) >= 0) {
throw {
forbidden: 'Character `' +
badUserNameChars[i] +
'` is not allowed in usernames.',
};
}
}
}