Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sending an email to a user to confirm their email address #1045

Merged
merged 67 commits into from
Sep 16, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
aa407fc
add database migrations and print schema for email and token tables
natboehm Aug 7, 2017
69cec97
create/update seems to add to email table
natboehm Aug 7, 2017
e45871e
on create/update user, email field is added to new table and token is…
natboehm Aug 8, 2017
225b365
add conflict target to migrations
natboehm Aug 8, 2017
0982c3b
try to add email and table functionality to edit/add email address, t…
natboehm Aug 8, 2017
40c6b0f
for GET /me route, get email from emails table rather than user table…
natboehm Aug 9, 2017
44d56eb
UserShowResponse should be separated into public and private accordin…
natboehm Aug 9, 2017
2cf7ad0
add email_verified field to private user response
natboehm Aug 9, 2017
ed0f060
delete debug print statements
natboehm Aug 9, 2017
7f21fd1
add function for emailing users, no idea if it works
natboehm Aug 9, 2017
5ca9ef8
preliminary send/confirm user email
natboehm Aug 9, 2017
fa305cf
Add routing info and lettre crate for confirming emails
natboehm Aug 10, 2017
232134e
add error handling for confirming user email
natboehm Aug 10, 2017
9c1511f
Add lettre crate
natboehm Aug 10, 2017
545ace2
delete unused imports
natboehm Aug 10, 2017
490bcb4
test for when user clicks on link to confirm email
natboehm Aug 10, 2017
5112f87
beginnings of route to display success/error of user confirming email…
natboehm Aug 14, 2017
ea2a0b4
route to confirm user email token displays success when token found, …
natboehm Aug 15, 2017
d5a25ad
remove console logs and update confirm success page
natboehm Aug 15, 2017
d0b7e5b
displays notification on account settings indicating whether or not e…
natboehm Aug 15, 2017
6056a5c
add ability to resend verification emails
natboehm Aug 15, 2017
bd46867
fixes bug where user model would not reload between confirming email …
natboehm Aug 16, 2017
b712905
separates into 'development' and 'production' parts
natboehm Aug 16, 2017
78b4f44
For testing purposes when env variables not found `send_user_confirm_…
natboehm Aug 17, 2017
e295069
added return result for fn send_user_confirm_email
natboehm Aug 23, 2017
329f021
this change is just to get my branch to run on my version of heroku
natboehm Aug 23, 2017
ab0826c
add debug statements to email sending
natboehm Aug 28, 2017
0d0a2f8
figure out why mail isn't sending, should fail
natboehm Aug 28, 2017
042a3e9
debugging why emails aren't sending - add print statement for email_r…
natboehm Aug 28, 2017
e88251b
Add proper port back
natboehm Aug 29, 2017
42246ff
Edit url to match heroku mirror name
natboehm Aug 29, 2017
34ac0e2
update /me route to /api/v1/me
natboehm Aug 29, 2017
37aecab
add back warnings as errors
natboehm Aug 29, 2017
0d64487
change route /me to /api/v1/me
natboehm Aug 29, 2017
34ab92f
delete unused code, add more detailed comment for blank Err block whe…
natboehm Aug 29, 2017
4375f21
attempt to show error message if email cannot be sent
natboehm Aug 29, 2017
499eff2
error messages for when email can't be sent
natboehm Aug 29, 2017
74f7f02
reminder to change URL from my mirror to crates.io
natboehm Aug 29, 2017
221aa4a
error messages for when email can't be sent
natboehm Aug 30, 2017
3120424
seems to display message to user when email cannot be sent
natboehm Aug 30, 2017
8350e63
css styling for resend email/errors
natboehm Aug 30, 2017
eb253d3
moved email info out into separate block and added styling to said se…
natboehm Sep 1, 2017
704a934
moved please add email message to outside email blocks, if statement …
natboehm Sep 1, 2017
1f6af9f
edited tests for confirming and inserting user email, added test for …
natboehm Sep 5, 2017
59fb74b
consistent commenting style
natboehm Sep 6, 2017
c37290e
fixing bug where please add email message is always displayed, even w…
natboehm Sep 6, 2017
b842c9b
ember linting errors, deletion of debug variable
natboehm Sep 6, 2017
a9bc633
Adds lettre crate + updates other things, perhaps i should have just …
natboehm Sep 6, 2017
af0b205
cargo fmt
natboehm Sep 6, 2017
5e10635
change link from my mirror back to crates.io
natboehm Sep 6, 2017
458de27
npm lint errors
natboehm Sep 6, 2017
c883f7f
nightly/beta errors
natboehm Sep 6, 2017
b716033
cargo fmt
natboehm Sep 8, 2017
ba0a123
clippy reference/dereference error
natboehm Sep 11, 2017
1c27fa6
Add back deleted deny statement, add debug
natboehm Sep 12, 2017
d8bf2e6
shorten if/else in emailNotVerified
natboehm Sep 12, 2017
99410d6
separate email logic from user logic, created new src/email.rs file
natboehm Sep 12, 2017
17a433c
add back app/templates/error.hbs
natboehm Sep 12, 2017
2073d26
simplify User struct construction as email is the only field being mo…
natboehm Sep 12, 2017
66bfc44
cargo fmt
natboehm Sep 12, 2017
8256def
Remove 'not found' when getting mailgun env vars, change env var stru…
natboehm Sep 13, 2017
25c3cf6
add category link back to doc comment
natboehm Sep 13, 2017
ce858f8
fix clippy errors
natboehm Sep 13, 2017
af3a66d
mention how to set up email in contributing docs
natboehm Sep 14, 2017
5a3a2b1
Add Mailgun configuration variables to .env + explaination
natboehm Sep 14, 2017
3530771
prevent null email from being added to emails table
natboehm Sep 14, 2017
26254f4
add http data files for email tests
natboehm Sep 15, 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
11 changes: 11 additions & 0 deletions .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,14 @@ export GIT_REPO_CHECKOUT=./tmp/index-co
# to the address `http://localhost:4200/authorize/github`.
export GH_CLIENT_ID=
export GH_CLIENT_SECRET=

# Credentials for configuring Mailgun. You can leave these commented out
# if you are not interested in actually sending emails. If left empty,
# a mock email will be sent to a file in your local '/tmp/' directory.
# If interested in setting up Mailgun to send emails, you will have
# to create an account with Mailgun and modify these manually.
# If running a crates mirror on heroku, you can instead add the Mailgun
# app to your instance and shouldn't have to mess with these.
# export MAILGUN_SMTP_LOGIN=
# export MAILGUN_SMTP_PASSWORD=
# export MAILGUN_SMTP_SERVER=
153 changes: 153 additions & 0 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ comrak = { version = "0.1.9", default-features = false }
ammonia = "0.7.0"
docopt = "0.8.1"
itertools = "0.6.0"
lettre = "0.6"

conduit = "0.8"
conduit-conditional-get = "0.8"
Expand Down
44 changes: 43 additions & 1 deletion app/components/email-input.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,31 @@
import Component from '@ember/component';
import { empty } from '@ember/object/computed';
import { computed } from '@ember/object';
import { inject as service } from '@ember/service';

export default Component.extend({
ajax: service(),
flashMessages: service(),

type: '',
value: '',
isEditing: false,
user: null,
disableSave: empty('user.email'),
notValidEmail: false,
prevEmail: '',
emailIsNull: true,
emailIsNull: computed('user.email', function() {
let email = this.get('user.email');
return (email == null);
}),
emailNotVerified: computed('user.email', 'user.email_verified', function() {
let email = this.get('user.email');
let verified = this.get('user.email_verified');

return (email != null && !verified);
}),
isError: false,
emailError: '',

actions: {
editEmail() {
Expand Down Expand Up @@ -48,6 +64,8 @@ export default Component.extend({
msg = 'An unknown error occurred while saving this email.';
}
this.set('serverError', msg);
this.set('isError', true);
this.set('emailError', `Error in saving email: ${msg}`);
});

this.set('isEditing', false);
Expand All @@ -57,6 +75,30 @@ export default Component.extend({
cancelEdit() {
this.set('isEditing', false);
this.set('value', this.get('prevEmail'));
},

resendEmail() {
let user = this.get('user');

this.get('ajax').raw(`/api/v1/users/${user.id}/resend`, { method: 'PUT',
user: {
avatar: user.avatar,
email: user.email,
email_verified: user.email_verified,
kind: user.kind,
login: user.login,
name: user.name,
url: user.url
}
}).catch((error) => {
if (error.payload) {
this.set('isError', true);
this.set('emailError', `Error in resending message: ${error.payload.errors[0].detail}`);
} else {
this.set('isError', true);
this.set('emailError', 'Unknown error in resending message');
}
});
}
}
});
1 change: 1 addition & 0 deletions app/models/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import DS from 'ember-data';

export default DS.Model.extend({
email: DS.attr('string'),
email_verified: DS.attr('boolean'),
name: DS.attr('string'),
login: DS.attr('string'),
avatar: DS.attr('string'),
Expand Down
1 change: 1 addition & 0 deletions app/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ Router.map(function() {
this.route('catchAll', { path: '*path' });
this.route('team', { path: '/teams/:team_id' });
this.route('policies');
this.route('confirm', { path: '/confirm/:email_token' });
});

export default Router;
37 changes: 37 additions & 0 deletions app/routes/confirm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import Ember from 'ember';
import { inject as service } from '@ember/service';

export default Ember.Route.extend({
flashMessages: service(),
ajax: service(),

model(params) {
return this.get('ajax').raw(`/api/v1/confirm/${params.email_token}`, { method: 'PUT', data: {} })
.then(() => {
/* We need this block to reload the user model from the database,
without which if we haven't submitted another GET /me after
clicking the link and before checking their account info page,
the user will still see that their email has not yet been
validated and could potentially be confused, resend the email,
and set up a situation where their email has been verified but
they have an unverified token sitting in the DB.

Suggestions of a more ideomatic way to fix/test this are welcome!
*/
if (this.session.get('isLoggedIn')) {
this.get('ajax').request('/api/v1/me').then((response) => {
this.session.set('currentUser', this.store.push(this.store.normalize('user', response.user)));
});
}
})
.catch((error) => {
if (error.payload) {
this.get('flashMessages').queue(`Error in email confirmation: ${error.payload.errors[0].detail}`);
return this.replaceWith('index');
} else {
this.get('flashMessages').queue(`Unknown error in email confirmation`);
return this.replaceWith('index');
}
});
}
});
41 changes: 41 additions & 0 deletions app/styles/me.scss
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,47 @@
}
}

#me-email {
border-bottom: 5px solid $gray-border;
padding-bottom: 20px;
margin-bottom: 20px;
@include display-flex;
@include flex-direction(column);
.row {
width: 100%;
border: 1px solid #d5d3cb;
border-bottom-width: 0px;
&:last-child { border-bottom-width: 1px; }
padding: 10px 20px;
@include display-flex;
@include align-items(center);
.label {
@include flex(1);
margin-right: 0.4em;
font-weight: bold;
}
.email {
@include flex(20);
}
.actions {
@include display-flex;
@include align-items(center);
img { margin-left: 10px }
}
.email-form {
display: inline-flex;
}
.space-right {
margin-right: 10px;
}
}
.friendly-message {
width: 95%;
margin-left: auto;
margin-right: auto;
}
}

#me-api {
@media only screen and (max-width: 350px) {
.api { display: none; }
Expand Down
40 changes: 31 additions & 9 deletions app/templates/components/email-input.hbs
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
{{#if emailIsNull }}
<div class='friendly-message'>
<p class='small-text'> Please add your email address. We will only use
it to contact you about your account. We promise we'll never share it!
</p>
</div>
{{/if}}

{{#if isEditing }}
<div class='row'>
<div class='label'>
<dt>Email</dt>
</div>
<form {{action 'saveEmail' on='submit'}}>
{{input type=type value=value placeholder='Email' class='form-control space-bottom'}}
<form class='email-form' {{action 'saveEmail' on='submit'}}>
{{input type=type value=value placeholder='Email' class='form-control space-right'}}
{{#if notValidEmail }}
<p class='small-text error'>Invalid email format. Please try again.</p>
{{/if}}
{{#if emailIsNull }}
<p class='small-text'> Please add your email address. We will only use
it to contact you about your account. We promise we'll never share it!
</p>
<div class='error'>
<p class='small-text error'>Whoops, that email format is invalid</p>
</div>
{{/if}}
<div class='actions'>
<button type='submit' class='small yellow-button space-right' disabled={{disableSave}}>Save</button>
Expand All @@ -20,7 +25,7 @@
</form>
</div>
{{else}}
<div class='row align-center'>
<div class='row'>
<div class='label'>
<dt>Email</dt>
</div>
Expand All @@ -31,4 +36,21 @@
<button class='small yellow-button space-left' {{action 'editEmail'}}>Edit</button>
</div>
</div>
{{#if emailNotVerified }}
<div class='row'>
<div class='label'>
<p class='small-text'>Your email has not yet been verified.</p>
</div>
<div class='actions'>
<button class='small yellow-button space-left' {{action 'resendEmail'}}>Resend</button>
</div>
</div>
{{/if}}
{{#if isError}}
<div class='row'>
<div class='label'>
<p class='small-text'>{{emailError}}</p>
</div>
</div>
{{/if}}
{{/if}}
1 change: 1 addition & 0 deletions app/templates/confirm.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<h1>Thank you for confirming your email! :)</h1>
2 changes: 1 addition & 1 deletion app/templates/error.hbs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<h1>Something Went Wrong!</h1>
<h5>{{model.message}}</h5>
<pre>
{{model.stack}}
{{model.stack}}
</pre>
6 changes: 5 additions & 1 deletion app/templates/me/index.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,15 @@
<dd>{{ model.user.name }}</dd>
<dt>GitHub Account</dt>
<dd>{{ model.user.login }}</dd>
{{email-input type='email' value=model.user.email user=model.user}}
</dl>
</div>
</div>

<div id='me-email'>
<h2>User Email</h2>
{{email-input type='email' value=model.user.email user=model.user}}
</div>

<div id='me-api'>
<div class='me-subheading'>
<h2>API Access</h2>
Expand Down
29 changes: 29 additions & 0 deletions docs/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,35 @@ yarn run start:local

And then you should be able to visit http://localhost:4200!

##### Using Mailgun to Send Emails

We currently have email functionality enabled for confirming a user's email
address. In development, the sending of emails is simulated by a file
representing the email being created in your local `/tmp/` directory. If
you want to test sending real emails, you will have to either set the
Mailgun environment variables in `.env` manually or run your app instance
on Heroku and add the Mailgun app.

To set the environment variables manually, create an account and configure
Mailgun. [These quick start instructions]
(http://mailgun-documentation.readthedocs.io/en/latest/quickstart.html)
might be helpful. Once you get the environment variables for the app, you
will have to add them to the bottom of the `.env` file. You will need to
fill in the `MAILGUN_SMTP_LOGIN`, `MAILGUN_SMTP_PASSWORD`, and
`MAILGUN_SMTP_SERVER` fields.

If using Heroku, you should be able to add the app to your instance on your
dashboard. When your code is pushed and run on Heroku, the environment
variables should be detected and you should not have to set anything
manually.

In either case, you should be able to check in your Mailgun account to see
if emails are being detected and sent. Relevant information should be under
the 'logs' tab on your Mailgun dashboard. To access, if the variables were
set up manually, log in to your account. If the variables were set through
Heroku, you should be able to click on the Mailgun icon in your Heroku
dashboard, which should take you to your Mailgun dashboard.

#### Running the backend tests

In your `.env` file, set `TEST_DATABASE_URL` to a value that's the same as
Expand Down
3 changes: 3 additions & 0 deletions migrations/20170804200817_add_email_table/down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-- This file should undo anything in `up.sql`
DROP table tokens;
DROP table emails;
17 changes: 17 additions & 0 deletions migrations/20170804200817_add_email_table/up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
-- Your SQL goes here
CREATE table emails (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL UNIQUE,
email VARCHAR NOT NULL,
verified BOOLEAN DEFAULT false NOT NULL
);

CREATE table tokens (
id SERIAL PRIMARY KEY,
email_id INTEGER NOT NULL UNIQUE REFERENCES emails,
token VARCHAR NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT now()
);

INSERT INTO emails (user_id, email)
SELECT id, email FROM users WHERE email IS NOT NULL;
Loading