Skip to content

Commit 97f2e10

Browse files
committed
Finish implementation of email verification on create
1 parent 51224ed commit 97f2e10

File tree

6 files changed

+206
-41
lines changed

6 files changed

+206
-41
lines changed

spec/ParseUser.spec.js

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,49 @@ describe('Parse.User testing', () => {
7272
user.signUp(null, {
7373
success: function(user) {
7474
expect(emailAdapter.sendVerificationEmail).toHaveBeenCalled();
75+
user.fetch()
76+
.then(() => {
77+
expect(user.get('emailVerified')).toEqual(false);
78+
done();
79+
});
80+
},
81+
error: function(userAgain, error) {
82+
fail('Failed to save user');
7583
done();
84+
}
85+
});
86+
});
87+
88+
it('does not send verification email if email verification is disabled', done => {
89+
var emailAdapter = {
90+
sendVerificationEmail: () => Promise.resolve()
91+
}
92+
setServerConfiguration({
93+
serverURL: 'http://localhost:8378/1',
94+
appId: 'test',
95+
appName: 'unused',
96+
javascriptKey: 'test',
97+
dotNetKey: 'windows',
98+
clientKey: 'client',
99+
restAPIKey: 'rest',
100+
masterKey: 'test',
101+
collectionPrefix: 'test_',
102+
fileKey: 'test',
103+
verifyUserEmails: false,
104+
emailAdapter: emailAdapter,
105+
});
106+
spyOn(emailAdapter, 'sendVerificationEmail');
107+
var user = new Parse.User();
108+
user.setPassword("asdf");
109+
user.setUsername("zxcv");
110+
user.signUp(null, {
111+
success: function(user) {
112+
user.fetch()
113+
.then(() => {
114+
expect(emailAdapter.sendVerificationEmail.calls.count()).toEqual(0);
115+
expect(user.get('emailVerified')).toEqual(undefined);
116+
done();
117+
});
76118
},
77119
error: function(userAgain, error) {
78120
fail('Failed to save user');
@@ -81,6 +123,141 @@ describe('Parse.User testing', () => {
81123
});
82124
});
83125

126+
it('receives the app name and user in the adapter', done => {
127+
var emailAdapter = {
128+
sendVerificationEmail: options => {
129+
expect(options.appName).toEqual('emailing app');
130+
expect(options.user.get('email')).toEqual('[email protected]');
131+
done();
132+
}
133+
}
134+
setServerConfiguration({
135+
serverURL: 'http://localhost:8378/1',
136+
appId: 'test',
137+
appName: 'emailing app',
138+
javascriptKey: 'test',
139+
dotNetKey: 'windows',
140+
clientKey: 'client',
141+
restAPIKey: 'rest',
142+
masterKey: 'test',
143+
collectionPrefix: 'test_',
144+
fileKey: 'test',
145+
verifyUserEmails: true,
146+
emailAdapter: emailAdapter,
147+
});
148+
var user = new Parse.User();
149+
user.setPassword("asdf");
150+
user.setUsername("zxcv");
151+
user.set('email', '[email protected]');
152+
user.signUp(null, {
153+
success: () => {},
154+
error: function(userAgain, error) {
155+
fail('Failed to save user');
156+
done();
157+
}
158+
});
159+
})
160+
161+
it('when you click the link in the email it sets emailVerified to true and redirects you', done => {
162+
var user = new Parse.User();
163+
var emailAdapter = {
164+
sendVerificationEmail: options => {
165+
request.get(options.link, {
166+
followRedirect: false,
167+
}, (error, response, body) => {
168+
expect(response.statusCode).toEqual(302);
169+
expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/verify_email_success.html?username=zxcv');
170+
user.fetch()
171+
.then(() => {
172+
expect(user.get('emailVerified')).toEqual(true);
173+
done();
174+
});
175+
});
176+
}
177+
}
178+
setServerConfiguration({
179+
serverURL: 'http://localhost:8378/1',
180+
appId: 'test',
181+
appName: 'emailing app',
182+
javascriptKey: 'test',
183+
dotNetKey: 'windows',
184+
clientKey: 'client',
185+
restAPIKey: 'rest',
186+
masterKey: 'test',
187+
collectionPrefix: 'test_',
188+
fileKey: 'test',
189+
verifyUserEmails: true,
190+
emailAdapter: emailAdapter,
191+
});
192+
user.setPassword("asdf");
193+
user.setUsername("zxcv");
194+
user.set('email', '[email protected]');
195+
user.signUp();
196+
});
197+
198+
it('redirects you to invalid link if you try to verify email incorrecly', done => {
199+
request.get('http://localhost:8378/1/verify_email', {
200+
followRedirect: false,
201+
}, (error, response, body) => {
202+
expect(response.statusCode).toEqual(302);
203+
expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/invalid_link.html');
204+
done()
205+
});
206+
});
207+
208+
it('redirects you to invalid link if you try to validate a nonexistant users email', done => {
209+
request.get('http://localhost:8378/1/verify_email?token=asdfasdf&username=sadfasga', {
210+
followRedirect: false,
211+
}, (error, response, body) => {
212+
expect(response.statusCode).toEqual(302);
213+
expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/invalid_link.html');
214+
done();
215+
});
216+
});
217+
218+
it('does not update email verified if you use an invalid token', done => {
219+
var user = new Parse.User();
220+
var emailAdapter = {
221+
sendVerificationEmail: options => {
222+
request.get('http://localhost:8378/1/verify_email?token=invalid&username=zxcv', {
223+
followRedirect: false,
224+
}, (error, response, body) => {
225+
expect(response.statusCode).toEqual(302);
226+
expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/invalid_link.html');
227+
user.fetch()
228+
.then(() => {
229+
expect(user.get('emailVerified')).toEqual(false);
230+
done();
231+
});
232+
});
233+
}
234+
}
235+
setServerConfiguration({
236+
serverURL: 'http://localhost:8378/1',
237+
appId: 'test',
238+
appName: 'emailing app',
239+
javascriptKey: 'test',
240+
dotNetKey: 'windows',
241+
clientKey: 'client',
242+
restAPIKey: 'rest',
243+
masterKey: 'test',
244+
collectionPrefix: 'test_',
245+
fileKey: 'test',
246+
verifyUserEmails: true,
247+
emailAdapter: emailAdapter,
248+
});
249+
user.setPassword("asdf");
250+
user.setUsername("zxcv");
251+
user.set('email', '[email protected]');
252+
user.signUp(null, {
253+
success: () => {},
254+
error: function(userAgain, error) {
255+
fail('Failed to save user');
256+
done();
257+
}
258+
});
259+
});
260+
84261
it("user login wrong username", (done) => {
85262
Parse.User.signUp("asdf", "zxcv", null, {
86263
success: function(user) {

src/Config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export class Config {
2626

2727
this.verifyUserEmails = cacheInfo.verifyUserEmails;
2828
this.emailAdapter = cacheInfo.emailAdapter;
29+
this.appName = cacheInfo.appName;
2930

3031
this.database = DatabaseAdapter.getDatabaseConnection(applicationId);
3132
this.filesController = cacheInfo.filesController;

src/RestWrite.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -405,7 +405,6 @@ RestWrite.prototype.transformUser = function() {
405405
'Account already exists for this username');
406406
}
407407
if (this.config.verifyUserEmails && this.data.email) {
408-
this.data.emailVerified = false;
409408
this.data._perishable_token = cryptoUtils.randomString(25);
410409
}
411410
return Promise.resolve();

src/Routers/UsersRouter.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import Auth from '../Auth';
99
import passwordCrypto from '../password';
1010
import RestWrite from '../RestWrite';
1111
let cryptoUtils = require('../cryptoUtils');
12+
let triggers = require('../triggers');
1213

1314
export class UsersRouter extends ClassesRouter {
1415
handleFind(req) {
@@ -28,6 +29,7 @@ export class UsersRouter extends ClassesRouter {
2829

2930
if (req.config.verifyUserEmails) {
3031
req.body._email_verify_token = cryptoUtils.randomString(25);
32+
req.body.emailVerified = false;
3133
}
3234

3335
let p = super.handleCreate(req);
@@ -39,7 +41,7 @@ export class UsersRouter extends ClassesRouter {
3941
req.config.emailAdapter.sendVerificationEmail({
4042
appName: req.config.appName,
4143
link: link,
42-
user: req.auth.user,
44+
user: triggers.inflate('_User', req.body),
4345
});
4446
});
4547
}

src/index.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -147,9 +147,10 @@ function ParseServer({
147147
oauth: oauth,
148148
verifyUserEmails: verifyUserEmails,
149149
emailAdapter: emailAdapter,
150+
appName: appName,
150151
};
151152

152-
// To maintain compatibility. TODO: Remove in v2.1
153+
// To maintain compatibility. TODO: Remove in some version that breaks backwards compatability
153154
if (process.env.FACEBOOK_APP_ID) {
154155
cache.apps[appId]['facebookAppIds'].push(process.env.FACEBOOK_APP_ID);
155156
}
@@ -164,9 +165,11 @@ function ParseServer({
164165

165166
// File handling needs to be before default middlewares are applied
166167
api.use('/', new FilesRouter().getExpressRouter());
167-
api.use('/request_password_reset', passwordReset.reset(appName, appId));
168-
api.get('/password_reset_success', passwordReset.success);
169-
api.get('/verify_email', verifyEmail);
168+
if (process.env.PARSE_EXPERIMENTAL_EMAIL_VERIFICATION_ENABLED || process.env.TESTING == 1) {
169+
api.use('/request_password_reset', passwordReset.reset(appName, appId));
170+
api.get('/password_reset_success', passwordReset.success);
171+
api.get('/verify_email', verifyEmail(appId, serverURL));
172+
}
170173

171174
// TODO: separate this from the regular ParseServer object
172175
if (process.env.TESTING == 1) {

src/verifyEmail.js

Lines changed: 18 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,26 @@
1-
function verifyEmail (appId) {
1+
function verifyEmail(appId, serverURL) {
22
var DatabaseAdapter = require('./DatabaseAdapter');
33
var database = DatabaseAdapter.getDatabaseConnection(appId);
4-
return function (req, res) {
4+
return (req, res) => {
55
var token = req.query.token;
66
var username = req.query.username;
7-
8-
Promise.resolve()
9-
.then(()=>{
10-
var error = null;
11-
if (!token || !username) {
12-
error = "Unable to verify email, check the URL and try again";
13-
}
14-
return Promise.resolve(error)
15-
})
16-
.then((error)=>{
17-
if (error) {
18-
return Promise.resolve(error);
19-
}
20-
return database.find('_User', {email: username})
21-
.then((results)=>{
22-
if (!results.length) {
23-
return Promise.resolve("Could not find email " + username + " check the URL and try again");
7+
if (!token || !username) {
8+
res.redirect(302, serverURL + '/invalid_link.html');
9+
return;
10+
}
11+
database.collection('_User').then(coll => {
12+
// Need direct database access because verification token is not a parse field
13+
coll.findAndModify({
14+
username: username,
15+
_email_verify_token: token,
16+
}, null, {$set: {emailVerified: true}}, (err, doc) => {
17+
if (err || !doc.value) {
18+
res.redirect(302, serverURL + '/invalid_link.html');
19+
} else {
20+
res.redirect(302, serverURL + '/verify_email_success.html?username=' + username);
2421
}
25-
26-
var user = results[0];
27-
return database.update("_User", {email: username}, {emailVerified: true}, {acl:[user.objectId]})
28-
.then(()=>Promise.resolve())
29-
})
30-
31-
})
32-
.then((error)=>{
33-
res.render('email-verified', {
34-
email: username,
35-
error: error
36-
})
37-
})
38-
.catch(()=>{
39-
res.status(404).render('not-found')
40-
})
22+
});
23+
});
4124
}
4225
}
4326

0 commit comments

Comments
 (0)