Skip to content

Commit 9b92a7f

Browse files
authored
Merge pull request #94
promisify user authentication functions
2 parents 3b33b7e + d43a845 commit 9b92a7f

141 files changed

Lines changed: 4723 additions & 2583 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.eslintrc.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,12 @@ module.exports = {
1111
ecmaVersion: 12,
1212
},
1313
rules: {
14+
'max-len': ['error', 120, 2, {
15+
ignoreUrls: true,
16+
ignoreComments: false,
17+
ignoreRegExpLiterals: true,
18+
ignoreStrings: true,
19+
ignoreTemplateLiterals: true,
20+
}],
1421
},
1522
};

.github/workflows/node.js.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,14 @@ jobs:
2727
node-version: ${{ matrix.node-version }}
2828
cache: 'npm'
2929
- run: npm ci
30-
- run: npm run lint || echo "Lint failed"
30+
- run: npm run lint:ci || echo "Lint failed"
3131
- run: DEBUG=replay* npm run coverage
3232

3333
- name: Coveralls
3434
uses: coverallsapp/github-action@master
3535
with:
3636
github-token: ${{ secrets.GITHUB_TOKEN }}
3737

38-
- run: npm run docker:test
38+
- run: DEBUG='none' npm run docker:test
3939
env:
4040
SOURCE_URL: ${{ secrets.SOURCE_URL }}

lib/corpus.js

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const { CorpusMask } = require('fielddb/api/corpus/CorpusMask');
77
const { DataList } = require('fielddb/api/data_list/DataList');
88
const { Team } = require('fielddb/api/user/Team');
99
const uuid = require('uuid');
10+
const nano = require('nano');
1011
/* variable for permissions */
1112
const commenter = 'commenter';
1213
const collaborator = 'reader';
@@ -65,7 +66,7 @@ exports.createPlaceholderDocsForCorpus = createPlaceholderDocsForCorpus;
6566
// Only create users on the same server.
6667
const parsed = url.parse('http://localhost:5984');
6768
const couchConnectUrl = `${parsed.protocol}//${couchKeys.username}:${couchKeys.password}@${parsed.host}`;
68-
debug('Using corpus url: ', parsed);
69+
debug('Using corpus url: ', couchConnectUrl);
6970
const createNewCorpus = function createNewCorpus(corpusObject, done) {
7071
const { username } = corpusObject;
7172
delete corpusObject.username;
@@ -78,7 +79,7 @@ const createNewCorpus = function createNewCorpus(corpusObject, done) {
7879
corpusObject.id = uuid.v4();
7980
}
8081
debug(`${new Date()} Creating new database ${corpusObject.dbname}`);
81-
const server = require('nano')({
82+
const server = nano({
8283
requestDefaults: {
8384
headers: {
8485
'x-request-id': requestId,
@@ -128,7 +129,7 @@ const createNewCorpus = function createNewCorpus(corpusObject, done) {
128129
],
129130
},
130131
};
131-
const newDatabase = require('nano')({
132+
const newDatabase = nano({
132133
requestDefaults: {
133134
headers: {
134135
'x-request-id': requestId,
@@ -184,7 +185,7 @@ const createNewCorpus = function createNewCorpus(corpusObject, done) {
184185
if (!couchDBError) {
185186
debug(`${new Date()} Corpus activity feed successfully replicated.`, couchActivityFeedResponse);
186187
// Set up security on new corpus activity feed
187-
const activityDb = require('nano')({
188+
const activityDb = nano({
188189
requestDefaults: {
189190
headers: {
190191
'x-request-id': requestId,
@@ -226,7 +227,7 @@ module.exports.updateRoles = updateRoles;
226227
var addRoleToUserInfo = function addRoleToUserInfo(connection, username, roles, successdone, errordone) {
227228
debug(`${new Date()} In addRoleToUser ${util.inspect(roles)} to ${username} on ${connection.dbname}`);
228229
const connect = `${couchConnectUrl}/_users`;
229-
const db = require('nano')(connect);
230+
const db = nano(connect);
230231
const userid = `org.couchdb.user:${username}`;
231232
const _ = require('lodash');
232233
db.get(userid, (couchDBError, body) => {
@@ -266,7 +267,7 @@ const isRequestingUserAnAdminOnCorpus = function isRequestingUserAnAdminOnCorpus
266267
/*
267268
* Check to see if the user is an admin on the corpus
268269
*/
269-
const nanoforpermissions = require('nano')({
270+
const nanoforpermissions = nano({
270271
requestDefaults: {
271272
headers: {
272273
'x-request-id': requestId,
@@ -280,8 +281,8 @@ const isRequestingUserAnAdminOnCorpus = function isRequestingUserAnAdminOnCorpus
280281
// Clean the couchDBErroror of couchdb leaks
281282
delete couchDBError.request;
282283
delete couchDBError.headers;
283-
couchDBError.status = couchDBError.statusCode || 401;
284-
couchDBError.error = couchDBError.error || `User ${requestingUser} couldn't be found on this server`;
284+
couchDBError.status = couchDBError.statusCode || 500;
285+
couchDBError.error = couchDBError.error;
285286
return done(couchDBError, null, {
286287
message: 'There was a problem deciding if you have permission to do this.',
287288
});

lib/corpusmanagement.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ const addRoleToUser = function addRoleToUser(dbConnection, userPermission, succe
230230
}
231231
err.status = err.status || 500;
232232
userPermission.status = err.status;
233-
userPermission.message = `There was a problem giving ${resultingRolesForThisCorpus.join(', ')} access to user ${userPermission.username}. Please notify us of this error.`;
233+
userPermission.message = `There was a problem giving ${resultingRolesForThisCorpus.join(', ')} access to user ${userPermission.username}.${err.status === 409 ? '' : ' Please notify us of this error.'}`;
234234
return errorcallback(err, userPermission, {
235235
message: userPermission.message,
236236
});

lib/email.js

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
const bcrypt = require('bcryptjs');
2+
const config = require('config');
3+
const debug = require('debug')('lib:email');
4+
const nodemailer = require('nodemailer');
5+
6+
const corpusmanagement = require('./corpusmanagement');
7+
8+
function emailWhenServerStarts() {
9+
return new Promise((resolve, reject) => {
10+
setTimeout(() => {
11+
reject(new Error('not implemented'));
12+
});
13+
});
14+
}
15+
16+
function emailWelcomeToTheUser({
17+
// user,
18+
} = {}) {
19+
return new Promise((resolve, reject) => {
20+
setTimeout(() => {
21+
reject(new Error('not implemented'));
22+
});
23+
});
24+
}
25+
26+
function emailWelcomeToCorpus({
27+
user,
28+
newConnection,
29+
} = {}) {
30+
if (!user.email || !user.email.length > 5 || config.mailConnection.auth.user === '') {
31+
debug(`${new Date()} Didn't email welcome to corpus to new user ${
32+
user.username} why: emailpresent: ${user.email
33+
}, mailconfig: ${config.mailConnection.auth.user !== ''}`);
34+
return Promise.resolve({
35+
info: {
36+
message: 'Email not sent',
37+
},
38+
});
39+
}
40+
const smtpTransport = nodemailer.createTransport(config.mailConnection);
41+
let mailOptions = config.welcomeToCorpusTeamMailOptions();
42+
if (user.appbrand === 'phophlo') {
43+
mailOptions = config.welcomeToCorpusTeamMailOptionsPhophlo();
44+
}
45+
mailOptions.to = `${user.email},${mailOptions.to}`;
46+
mailOptions.text = mailOptions.text.replace(/insert_corpus_identifier/g, newConnection.dbname);
47+
mailOptions.html = mailOptions.html.replace(/insert_corpus_identifier/g, newConnection.dbname);
48+
return smtpTransport.sendMail(mailOptions)
49+
.then((response) => {
50+
debug(`${new Date()} Message sent: \n${response.message}`);
51+
debug(`${new Date()} Sent User ${user.username} a welcome to corpus email at ${user.email}`);
52+
smtpTransport.close();
53+
return {
54+
info: {
55+
message: 'Email sent',
56+
},
57+
};
58+
})
59+
.catch((error) => {
60+
debug(`${new Date()} Mail error${JSON.stringify(error)}`);
61+
});
62+
}
63+
64+
/**
65+
function generates a temporary password which is alpha-numeric and 10
66+
* chars long
67+
*
68+
* @returns {String}
69+
*/
70+
function makeRandomPassword() {
71+
let text = '';
72+
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
73+
for (let i = 0; i < 10; i++) {
74+
text += possible.charAt(Math.floor(Math.random() * possible.length));
75+
}
76+
return text;
77+
}
78+
79+
/**
80+
* This emails the user, if the user has an email, if the
81+
* email is 'valid' TODO do better email validation. and if
82+
* the config has a valid user. For the dev and local
83+
* versions of the app, this wil never be fired because the
84+
* config doesnt have a valid user. But the production
85+
* config does, and it is working.
86+
*
87+
* @param {[type]} user [description]
88+
* @param {[type]} temporaryPassword message [description]
89+
* @param {Function} done [description]
90+
* @return {[type]} [description]
91+
*/
92+
function emailTemporaryPasswordToTheUserIfTheyHavAnEmail({
93+
user = { email: '' },
94+
temporaryPassword,
95+
successMessage,
96+
} = {}) {
97+
if (!user.email || user.email.length < 5) {
98+
const err = new Error('The user didnt provide a valid email.');
99+
err.status = 412;
100+
err.userFriendlyErrors = ["You didnt provide an email when you registered, so we can't send you a temporary password. If you can't remember your password, you might need to contact us to ask us to reset your password."];
101+
return Promise.reject(err);
102+
}
103+
if (config.mailConnection.auth.user === '') {
104+
const err = new Error('The mail configuration is missing a user, this server cant send email.');
105+
err.userFriendlyErrors = ['The server was unable to send you an email, your password has not been reset. Please report this 2823'];
106+
return Promise.reject(err);
107+
}
108+
const newpassword = temporaryPassword || makeRandomPassword();
109+
const smtpTransport = nodemailer.createTransport(config.mailConnection);
110+
let mailOptions = config.suspendedUserMailOptions();
111+
if (user.appbrand === 'phophlo') {
112+
mailOptions = config.suspendedUserMailOptionsPhophlo();
113+
}
114+
mailOptions.to = `${user.email},${mailOptions.to}`;
115+
mailOptions.text = mailOptions.text.replace(/insert_temporary_password/g, newpassword);
116+
mailOptions.html = mailOptions.html.replace(/insert_temporary_password/g, newpassword);
117+
return smtpTransport.sendMail(mailOptions)
118+
.then((response) => {
119+
debug(`${new Date()} Temporary pasword sent: \n${response.message}`);
120+
const connection = user.corpora[user.corpora.length - 1];
121+
// save new password to couch _users too
122+
// TODO convert to return promises
123+
return corpusmanagement.changeUsersPassword(
124+
connection,
125+
user,
126+
newpassword,
127+
(res) => {
128+
debug(`${new Date()} There was success in creating changing the couchdb password: ${JSON.stringify(res)}\n`);
129+
debug(`${new Date()} this is the user after changing their couch password ${JSON.stringify(user)}`);
130+
const salt = bcrypt.genSaltSync(10);
131+
user.hash = bcrypt.hashSync(newpassword, salt);
132+
user.serverlogs.oldIncorrectPasswordAttempts = user.serverlogs.oldIncorrectPasswordAttempts || [];
133+
user.serverlogs.incorrectPasswordAttempts = user.serverlogs.incorrectPasswordAttempts || [];
134+
user.serverlogs.oldIncorrectPasswordAttempts = user.serverlogs.oldIncorrectPasswordAttempts
135+
.concat(user.serverlogs.incorrectPasswordAttempts);
136+
user.serverlogs.incorrectPasswordAttempts = [];
137+
138+
return {
139+
user,
140+
info: {
141+
message: successMessage,
142+
},
143+
};
144+
},
145+
(err) => {
146+
debug(`${new Date()} There was an error in creating changing the couchdb password ${JSON.stringify(err)}\n`);
147+
err.error = err.error || 'Couchdb errored when trying to save the user.';
148+
err.status = err.status || err.statusCode || 500;
149+
err.userFriendlyErrors = ['The server was unable to change your password, your password has not been reset. Please report this 2893'];
150+
throw err;
151+
},
152+
);
153+
// smtpTransport.close();
154+
})
155+
.catch((error) => {
156+
debug(`${new Date()} Mail error${JSON.stringify(error)}`);
157+
error.error = error.error || error.code || 'Mail server failed to send an email';
158+
error.userFriendlyErrors = error.userFriendlyErrors || ['The server was unable to send you an email, your password has not been reset. Please report this 2898'];
159+
throw error;
160+
});
161+
}
162+
163+
function emailCorusCreationFailure({
164+
connection = {},
165+
user = { email: '' },
166+
} = {}) {
167+
let { email } = user;
168+
if (!email) {
169+
email = 'bounce@lingsync.org';
170+
}
171+
172+
if (config.mailConnection.auth.user === '') {
173+
debug(`${new Date()} Didnt email welcome to new user${user.username} why: emailpresent: ${email}, valid user email: ${email.length > 5}, mailconfig: ${config.mailConnection.auth.user !== ''}`);
174+
return Promise.resolve();
175+
}
176+
177+
const smtpTransport = nodemailer.createTransport(config.mailConnection);
178+
let mailOptions = config.newUserMailOptions();
179+
if (user.appbrand === 'phophlo') {
180+
mailOptions = config.newUserMailOptionsPhophlo();
181+
}
182+
mailOptions.to = `${email},${mailOptions.to}`;
183+
mailOptions.text = `There was a problem while cerating your corpus ${connection.dbname}. The server admins have been notified.${user.username}`;
184+
mailOptions.html = `There was a problem while cerating your corpus ${connection.dbname}. The server admins have been notified.${user.username}`;
185+
return smtpTransport.sendMail(mailOptions)
186+
.then((response) => {
187+
debug(`${new Date()} Message sent: \n${response.message}`);
188+
debug(`${new Date()} Sent User ${user.username} a welcome email at ${email}`);
189+
smtpTransport.close();
190+
})
191+
.catch((error) => {
192+
debug(`${new Date()} Mail error${JSON.stringify(error)}`);
193+
});
194+
}
195+
196+
module.exports = {
197+
emailCorusCreationFailure,
198+
emailWhenServerStarts,
199+
emailWelcomeToTheUser,
200+
emailWelcomeToCorpus,
201+
emailTemporaryPasswordToTheUserIfTheyHavAnEmail,
202+
makeRandomPassword,
203+
};

0 commit comments

Comments
 (0)