mirror of
https://github.com/donl/slouch.git
synced 2026-05-26 06:12:11 -06:00
* Pass slouch in when instantiating EnhancedRequest This is to allow EnhanceRequest to access slouch._requestWrapper.setCookie * Update locally stored cookie when response has set-cookie header * Delete unused this._cookie * Fix cookie-authentication in browser * Fix test Request header now always include withCredentials: true * Fix test EnhancedRequest constructor now requires instance of Slouch * Typo * Fix test * Remove TODO comment and clean up redundant code * Revert change to user.js * Fix user test spec * Fix user test spec * Fix browser coverage test fail * FIx beautify test fail * Fix failed jshint test
262 lines
7.2 KiB
JavaScript
262 lines
7.2 KiB
JavaScript
'use strict';
|
|
|
|
var NotAuthenticatedError = require('./not-authenticated-error'),
|
|
url = require('url'),
|
|
sporks = require('sporks'),
|
|
Promise = require('sporks/scripts/promise');
|
|
|
|
var User = function (slouch) {
|
|
this._slouch = slouch;
|
|
this._dbName = '_users';
|
|
};
|
|
|
|
User.prototype.toUserId = function (username) {
|
|
return 'org.couchdb.user:' + username;
|
|
};
|
|
|
|
User.prototype.toUsername = function (userId) {
|
|
return userId.replace(/org.couchdb.user:/, '');
|
|
};
|
|
|
|
User.prototype._insert = function (username, user) {
|
|
user._id = this.toUserId(username);
|
|
return this._slouch.doc.update(this._dbName, user);
|
|
};
|
|
|
|
User.prototype.create = function (username, password, roles, metadata) {
|
|
var user = {
|
|
name: username,
|
|
password: password,
|
|
roles: typeof roles === 'undefined' ? [] : roles,
|
|
type: 'user',
|
|
metadata: metadata,
|
|
createdAt: (new Date().toISOString())
|
|
};
|
|
|
|
return this._insert(username, user);
|
|
};
|
|
|
|
User.prototype.get = function (username, params) {
|
|
return this._slouch.doc.get(this._dbName, this.toUserId(username), params);
|
|
};
|
|
|
|
User.prototype._update = function (username, user) {
|
|
return this._insert(username, user);
|
|
};
|
|
|
|
User.prototype.addRole = function (username, role) {
|
|
var self = this;
|
|
return self.get(username).then(function (user) {
|
|
user.roles.push(role);
|
|
return self._update(username, user);
|
|
});
|
|
};
|
|
|
|
User.prototype.upsertRole = function (username, role) {
|
|
var self = this;
|
|
return self._slouch.doc._persistThroughConflicts(function () {
|
|
return self.addRole(username, role);
|
|
});
|
|
};
|
|
|
|
User.prototype.removeRole = function (username, role) {
|
|
var self = this;
|
|
return self.get(username).then(function (user) {
|
|
user.roles.splice(user.roles.indexOf(role), 1);
|
|
return self._update(username, user);
|
|
});
|
|
};
|
|
|
|
User.prototype.downsertRole = function (username, role) {
|
|
var self = this;
|
|
return self._slouch.doc._persistThroughConflicts(function () {
|
|
return self.removeRole(username, role);
|
|
});
|
|
};
|
|
|
|
User.prototype.setPassword = function (username, password) {
|
|
var self = this;
|
|
return self.get(username).then(function (user) {
|
|
user.password = password;
|
|
return self._update(username, user);
|
|
});
|
|
};
|
|
|
|
User.prototype.setMetadata = function (username, metadata) {
|
|
var self = this;
|
|
return self.get(username).then(function (user) {
|
|
user.metadata = sporks.merge(user.metadata, metadata);
|
|
return self._update(username, user);
|
|
});
|
|
};
|
|
|
|
User.prototype._destroy = function (username, rev) {
|
|
return this._slouch.doc.destroy(this._dbName, this.toUserId(username), rev);
|
|
};
|
|
|
|
User.prototype.destroy = function (username) {
|
|
var self = this;
|
|
return self.get(username).then(function (user) {
|
|
return self._destroy(username, user._rev);
|
|
});
|
|
};
|
|
|
|
// Abstracted as cookies are not returned in the browser
|
|
User.prototype._getCookieFromResponse = function (response) {
|
|
return response.headers['set-cookie'] ? response.headers['set-cookie'][0] : null;
|
|
};
|
|
|
|
User.prototype.authenticate = function (username, password) {
|
|
var self = this;
|
|
return self.createSession({
|
|
name: username,
|
|
password: password
|
|
}).then(function (response) {
|
|
return {
|
|
cookie: self._getCookieFromResponse(response)
|
|
};
|
|
}).catch(function (err) {
|
|
throw new NotAuthenticatedError(err.message);
|
|
});
|
|
};
|
|
|
|
User.prototype.createSession = function (doc) {
|
|
return this._slouch._req({
|
|
uri: this._slouch._url + '/_session',
|
|
method: 'POST',
|
|
json: doc,
|
|
parseBody: true,
|
|
fullResponse: true
|
|
});
|
|
};
|
|
|
|
User.prototype._getHeaderWithCookie = function (cookie) {
|
|
var headers = null;
|
|
|
|
if (cookie) {
|
|
headers = {
|
|
cookie: cookie
|
|
};
|
|
} else {
|
|
// Need to set to undefined here for jshint
|
|
headers = undefined;
|
|
}
|
|
|
|
return headers;
|
|
};
|
|
|
|
User.prototype.getSession = function (cookie, url) {
|
|
return this._slouch._req({
|
|
uri: (url ? url : this._slouch._url) + '/_session',
|
|
method: 'GET',
|
|
headers: this._getHeaderWithCookie(cookie),
|
|
parseBody: true,
|
|
fullResponse: true
|
|
});
|
|
};
|
|
|
|
User.prototype.destroySession = function (cookie) {
|
|
return this._slouch._req({
|
|
uri: this._slouch._url + '/_session',
|
|
method: 'DELETE',
|
|
headers: this._getHeaderWithCookie(cookie),
|
|
parseBody: true,
|
|
fullResponse: true
|
|
});
|
|
};
|
|
|
|
User.prototype.authenticated = function (cookie) {
|
|
// Specify a URL w/o a username and password as we want to check to make sure that the cookie is
|
|
// for a current session
|
|
var parts = url.parse(this._slouch._url);
|
|
var _url = parts.protocol + '//' + parts.host + parts.pathname;
|
|
|
|
return this.getSession(cookie, _url).then(function (response) {
|
|
var body = response.body;
|
|
if (!body.userCtx.name) { // not authenticated?
|
|
throw new NotAuthenticatedError('not authenticated via cookie');
|
|
}
|
|
return body;
|
|
});
|
|
};
|
|
|
|
User.prototype.authenticateAndGetSession = function (username, password) {
|
|
var self = this,
|
|
response = null;
|
|
return self.authenticate(username, password).then(function (_response) {
|
|
response = _response;
|
|
return self.authenticated(response.cookie);
|
|
}).then(function (session) {
|
|
session.cookie = response.cookie;
|
|
return session;
|
|
});
|
|
};
|
|
|
|
User.prototype.setCookie = function (cookie) {
|
|
this._slouch._requestWrapper.setCookie(cookie);
|
|
};
|
|
|
|
User.prototype.logIn = function (username, password) {
|
|
var self = this;
|
|
return self.authenticate(username, password).then(function (response) {
|
|
// Set cookie for all subsequent calls
|
|
self.setCookie(response.cookie);
|
|
return response;
|
|
});
|
|
};
|
|
|
|
User.prototype.logOut = function () {
|
|
// Destroy session
|
|
var self = this;
|
|
return self.destroySession().then(function (response) {
|
|
// Remove cookie from subsequent requests
|
|
self.setCookie(null);
|
|
return response;
|
|
});
|
|
};
|
|
|
|
// Provides a simple way of resolving conflicts at the user layer whereby a merge of the roles is
|
|
// assumed to be the only data needed in the conflicting docs. Until you resolve these conflicts,
|
|
// users cannot log in. You can pretty easily encounter conflicts on user docs with CouchDB 2. For
|
|
// example, if a user is being added to two roles simultaneously via different CouchDB nodes then
|
|
// when the CouchDB nodes replicate the user, the user will be in conflict.
|
|
User.prototype.resolveConflicts = function (username) {
|
|
var self = this;
|
|
return self.get(username, {
|
|
conflicts: true
|
|
}).then(function (user) {
|
|
// Verify that there is a conflict
|
|
if (user._conflicts) {
|
|
|
|
var roles = sporks.flip(user.roles),
|
|
gets = [],
|
|
destroys = [];
|
|
|
|
user._conflicts.forEach(function (rev) {
|
|
gets.push(self.get(username, {
|
|
rev: rev
|
|
}).then(function (userRev) {
|
|
userRev.roles.forEach(function (role) {
|
|
// Build a list of all roles, using an associative array so that duplicates are ignored.
|
|
roles[role] = true;
|
|
});
|
|
}));
|
|
});
|
|
|
|
return Promise.all(gets).then(function () {
|
|
// Update the user and set all the roles
|
|
user.roles = sporks.keys(roles);
|
|
return self._update(username, user);
|
|
}).then(function () {
|
|
// Delete all the conflicts
|
|
user._conflicts.forEach(function (rev) {
|
|
destroys.push(self._destroy(username, rev));
|
|
});
|
|
return Promise.all(destroys);
|
|
});
|
|
|
|
}
|
|
});
|
|
};
|
|
|
|
module.exports = User;
|