Skip to content
This repository has been archived by the owner on Jan 9, 2023. It is now read-only.

Offline fixes #1080

Merged
merged 29 commits into from
May 18, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
8640e1f
Remove app manifest support
jkleinsc Apr 27, 2017
ad76252
Make sure login tests wait until db is loaded to finish.
jkleinsc May 1, 2017
c85937a
Setup testing to use chrome and service worker
jkleinsc May 1, 2017
314c83b
Fixed error when sys admin and not in stand alone mode
jkleinsc May 1, 2017
4b0fe25
Use worker-pouch to access local db via sw
jkleinsc May 1, 2017
7adc52c
Move pouch-find-indexes to mixin
jkleinsc May 1, 2017
9df4191
Sync local changes to remote
jkleinsc May 1, 2017
51b6fde
Added logic to use background sync to retry remote syncing.
jkleinsc May 4, 2017
27d9f5c
Change all users to use oauth
jkleinsc May 4, 2017
6ab060e
Remove unneeded logging
jkleinsc May 5, 2017
646459a
Setup to rely on background sync retrying on failure.
jkleinsc May 5, 2017
aa734ee
Merge branch 'master' into offline-fixes
jkleinsc May 5, 2017
a457159
Make sure logout and login handle offline gracefully.
jkleinsc May 8, 2017
c11d2ee
Merge branch 'master' into offline-fixes
jkleinsc May 10, 2017
7540643
Make sure service workers are disabled for Electron.
jkleinsc May 11, 2017
92bc64f
Make sure service worker is disabled on Electron tests.
jkleinsc May 11, 2017
d9b6d05
Merge branch 'master' into offline-fixes
jkleinsc May 11, 2017
351468f
Try chrome headless
jkleinsc May 11, 2017
211f88f
Try using yarn and chrome beta
jkleinsc May 11, 2017
4fbc868
Fix issues logging in with Electron
jkleinsc May 12, 2017
4495f07
Try using chrome beta headless
jkleinsc May 12, 2017
6005a9e
pbkdf2 shouldn't need to be specified directly
jkleinsc May 12, 2017
4e182b5
Removed unneeded code
jkleinsc May 12, 2017
2373f17
Try to get chrome headless working
jkleinsc May 12, 2017
f4d877e
Try upgrading ember-concurrency if that is the issue.
jkleinsc May 12, 2017
edbb090
Fix issues with users DB in standalone (Electron) mode
May 17, 2017
cd3339e
Try using regular Google Chrome
May 17, 2017
f14f81c
Move back to PhantomJS
May 17, 2017
d7ed858
Go back to npm
May 17, 2017
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ addons:
- ubuntu-toolchain-r-test
packages:
- g++-4.8

cache:
directories:
- $HOME/.npm
Expand Down
111 changes: 55 additions & 56 deletions app/adapters/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ const {
} = Ember;

export default Adapter.extend(CheckForErrors, {
config: Ember.inject.service(),
ajax: Ember.inject.service(),
database: Ember.inject.service(),
db: reads('database.mainDB'),
standAlone: reads('config.standAlone'),
usePouchFind: reads('database.usePouchFind'),

_specialQueries: [
'containsValue',
Expand All @@ -29,66 +29,65 @@ export default Adapter.extend(CheckForErrors, {
_esDefaultSize: 25,

_executeContainsSearch(store, type, query) {
let standAlone = get(this, 'standAlone');
if (standAlone) {
let usePouchFind = get(this, 'usePouchFind');
if (usePouchFind) {
return this._executePouchDBFind(store, type, query);
}
return new Ember.RSVP.Promise((resolve, reject) => {
let typeName = this.getRecordTypeName(type);
let searchUrl = `/search/hrdb/${typeName}/_search`;
if (query.containsValue && query.containsValue.value) {
let queryString = '';
query.containsValue.keys.forEach((key) => {
if (!Ember.isEmpty(queryString)) {
queryString = `${queryString} OR `;
}
let queryValue = query.containsValue.value;
switch (key.type) {
case 'contains': {
queryValue = `*${queryValue}*`;
break;
}
case 'fuzzy': {
queryValue = `${queryValue}~`;
break;
}
let typeName = this.getRecordTypeName(type);
let searchUrl = `/search/hrdb/${typeName}/_search`;
if (query.containsValue && query.containsValue.value) {
let queryString = '';
query.containsValue.keys.forEach((key) => {
if (!Ember.isEmpty(queryString)) {
queryString = `${queryString} OR `;
}
let queryValue = query.containsValue.value;
switch (key.type) {
case 'contains': {
queryValue = `*${queryValue}*`;
break;
}
queryString = `${queryString}data.${key.name}:${queryValue}`;
});
let successFn = (results) => {
if (results && results.hits && results.hits.hits) {
let resultDocs = Ember.A(results.hits.hits).map((hit) => {
let mappedResult = hit._source;
mappedResult.id = hit._id;
return mappedResult;
});
let response = {
rows: resultDocs
};
this._handleQueryResponse(response, store, type).then(resolve, reject);
} else if (results.rows) {
this._handleQueryResponse(results, store, type).then(resolve, reject);
} else {
reject('Search results are not valid');
case 'fuzzy': {
queryValue = `${queryValue}~`;
break;
}
};

if (Ember.isEmpty(query.size)) {
query.size = this.get('_esDefaultSize');
}

Ember.$.ajax(searchUrl, {
dataType: 'json',
data: {
q: queryString,
size: this.get('_esDefaultSize')
},
success: successFn
});
} else {
reject('invalid query');
queryString = `${queryString}data.${key.name}:${queryValue}`;
});
let ajax = get(this, 'ajax');
if (Ember.isEmpty(query.size)) {
query.size = this.get('_esDefaultSize');
}
});

return ajax.request(searchUrl, {
dataType: 'json',
data: {
q: queryString,
size: this.get('_esDefaultSize')
}
}).then((results) => {
if (results && results.hits && results.hits.hits) {
let resultDocs = Ember.A(results.hits.hits).map((hit) => {
let mappedResult = hit._source;
mappedResult.id = hit._id;
return mappedResult;
});
let response = {
rows: resultDocs
};
return this._handleQueryResponse(response, store, type);
} else if (results.rows) {
return this._handleQueryResponse(results, store, type);
} else {
throw new Error('Search results are not valid');
}
}).catch(() => {
// Try pouch db find if ajax fails
return this._executePouchDBFind(store, type, query);
});
} else {
throw new Error('invalid query');
}
},

_executePouchDBFind(store, type, query) {
Expand Down
1 change: 0 additions & 1 deletion app/admin/textreplace/route.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ export default AbstractIndexRoute.extend({
return store.findAll('text-expansion').then((result) => {
return result.filter((model) => {
let isNew = model.get('isNew');
console.log(`${model.get('from')} ${isNew}`);
return !isNew;
});
});
Expand Down
150 changes: 72 additions & 78 deletions app/authenticators/custom.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import Ember from 'ember';
import BaseAuthenticator from 'ember-simple-auth/authenticators/base';
import crypto from 'npm:crypto';
import MapOauthParams from 'hospitalrun/mixins/map-oauth-params';
import OAuthHeaders from 'hospitalrun/mixins/oauth-headers';

const {
computed: {
Expand All @@ -10,69 +12,72 @@ const {
RSVP
} = Ember;

export default BaseAuthenticator.extend({
export default BaseAuthenticator.extend(MapOauthParams, OAuthHeaders, {
ajax: Ember.inject.service(),
config: Ember.inject.service(),
database: Ember.inject.service(),
serverEndpoint: '/db/_session',
useGoogleAuth: false,
serverEndpoint: '/auth/#',

standAlone: alias('config.standAlone'),
usersDB: alias('database.usersDB'),

/**
@method absolutizeExpirationTime
@private
*/
_absolutizeExpirationTime(expiresIn) {
if (!Ember.isEmpty(expiresIn)) {
return new Date((new Date().getTime()) + (expiresIn - 5) * 1000).getTime();
}
},

_checkUser(user) {
_checkUser(user, oauthConfigs) {
return new RSVP.Promise((resolve, reject) => {
this._makeRequest('POST', { name: user.name }, '/chkuser').then((response) => {
let headers = this.getOAuthHeaders(oauthConfigs);
this._makeRequest({ name: user.name }, '/chkuser', headers).then((response) => {
if (response.error) {
reject(response);
}
user.displayName = response.displayName;
user.role = response.role;
user.prefix = response.prefix;
resolve(user);
}, () => {
}).catch(() => {
// If chkuser fails, user is probably offline; resolve with currently stored credentials
resolve(user);
});
});
},

_getPromise(type, data) {
return new RSVP.Promise(function(resolve, reject) {
this._makeRequest(type, data).then(function(response) {
Ember.run(function() {
resolve(response);
});
}, function(xhr) {
Ember.run(function() {
reject(xhr.responseJSON || xhr.responseText);
});
});
}.bind(this));
_finishAuth(user, oauthConfigs) {
let config = this.get('config');
let database = this.get('database');
config.setCurrentUser(user);
return database.setup().then(() => {
user.oauthConfigs = oauthConfigs;
return user;
});
},

_makeRequest(type, data, url) {
_makeRequest(data, url, headers, method) {
if (!url) {
url = this.serverEndpoint;
}
return Ember.$.ajax({
url,
type,
let ajax = get(this, 'ajax');
let params = {
type: 'POST',
data,
dataType: 'json',
contentType: 'application/x-www-form-urlencoded',
xhrFields: {
withCredentials: true
}
};
if (method) {
params.type = method;
}
if (headers) {
params.headers = headers;
}

return ajax.request(url, params);
},

_saveOAuthConfigs(params) {
let config = get(this, 'config');
let oauthConfigs = this.mapOauthParams(params);
return config.saveOauthConfigs(oauthConfigs).then(() => {
return oauthConfigs;
});
},

Expand All @@ -88,75 +93,56 @@ export default BaseAuthenticator.extend({
return this._authenticateStandAlone(credentials);
}
if (credentials.google_auth) {
this.useGoogleAuth = true;
let sessionCredentials = {
google_auth: true,
consumer_key: credentials.params.k,
consumer_secret: credentials.params.s1,
token: credentials.params.t,
token_secret: credentials.params.s2,
name: credentials.params.i
};
return new RSVP.Promise((resolve, reject) => {
this._checkUser(sessionCredentials).then((user) => {
resolve(user);
this.get('config').setCurrentUser(user.name);
}, reject);
return this._saveOAuthConfigs(credentials.params).then((oauthConfigs) => {
return this._checkUser({ name: credentials.params.i }, oauthConfigs).then((user) => {
return this._finishAuth(user, oauthConfigs);
});
});
}

return new Ember.RSVP.Promise((resolve, reject) => {
let username = credentials.identification;
if (typeof username === 'string' && username) {
username = username.trim();
let username = this._getUserName(credentials);
let data = { name: username, password: credentials.password };
return this._makeRequest(data).then((user) => {
if (user.error) {
throw new Error(user.errorResult || 'Unauthorized user');
}
let data = { name: username, password: credentials.password };
this._makeRequest('POST', data).then((response) => {
response.name = data.name;
response.expires_at = this._absolutizeExpirationTime(600);
this._checkUser(response).then((user) => {
this.get('config').setCurrentUser(user.name);
let database = this.get('database');
database.setup({}).then(() => {
resolve(user);
}, reject);
}, reject);
}, function(xhr) {
reject(xhr.responseJSON || xhr.responseText);
let userInfo = {
displayName: user.displayName,
prefix: user.prefix,
role: user.role
};
userInfo.name = username;

return this._saveOAuthConfigs(user).then((oauthConfigs) => {
return this._finishAuth(userInfo, oauthConfigs);
});
});
},

invalidate() {
invalidate(data) {
let standAlone = get(this, 'standAlone');
if (this.useGoogleAuth || standAlone) {
return RSVP.resolve();
} else {
return this._getPromise('DELETE');
// Ping the remote db to make sure we still have connectivity before logging off.
let headers = this.getOAuthHeaders(data.oauthConfigs);
let remoteDBUrl = get(this, 'database').getRemoteDBUrl();
return this._makeRequest({}, remoteDBUrl, headers, 'GET');
}
},

restore(data) {
if (window.ELECTRON) { // config service has not been setup yet, so config.standAlone not available yet
return RSVP.resolve(data);
}
return new RSVP.Promise((resolve, reject) => {
let now = (new Date()).getTime();
if (!Ember.isEmpty(data.expires_at) && data.expires_at < now) {
reject();
} else {
if (data.google_auth) {
this.useGoogleAuth = true;
}
this._checkUser(data).then(resolve, reject);
}
});
return this._checkUser(data, data.oauthConfigs);
},

_authenticateStandAlone(credentials) {
let usersDB = get(this, 'usersDB');
return new RSVP.Promise((resolve, reject) => {
usersDB.get(`org.couchdb.user:${credentials.identification}`).then((user) => {
let username = this._getUserName(credentials);
usersDB.get(`org.couchdb.user:${username}`).then((user) => {
let { salt, iterations, derived_key } = user;
let { password } = credentials;
this._checkPassword(password, salt, iterations, derived_key, (error, isCorrectPassword) => {
Expand All @@ -167,7 +153,7 @@ export default BaseAuthenticator.extend({
reject(new Error('UNAUTHORIZED'));
}
user.role = this._getPrimaryRole(user);
resolve(user);
this._finishAuth(user, {}).then(resolve, reject);
});
}, reject);
});
Expand All @@ -193,6 +179,14 @@ export default BaseAuthenticator.extend({
});
}
return primaryRole;
},

_getUserName(credentials) {
let username = credentials.identification;
if (typeof username === 'string' && username) {
username = username.trim();
}
return username;
}

});
Loading