From aa407fcecaac134c127e2c8814ab7fd75ecc8620 Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Mon, 7 Aug 2017 11:25:02 -0400 Subject: [PATCH 01/67] add database migrations and print schema for email and token tables --- .../20170804200817_add_email_table/down.sql | 3 + .../20170804200817_add_email_table/up.sql | 17 +++++ src/schema.rs | 65 +++++++++++++++++++ 3 files changed, 85 insertions(+) create mode 100644 migrations/20170804200817_add_email_table/down.sql create mode 100644 migrations/20170804200817_add_email_table/up.sql diff --git a/migrations/20170804200817_add_email_table/down.sql b/migrations/20170804200817_add_email_table/down.sql new file mode 100644 index 00000000000..68415a79c95 --- /dev/null +++ b/migrations/20170804200817_add_email_table/down.sql @@ -0,0 +1,3 @@ +-- This file should undo anything in `up.sql` +DROP table tokens; +DROP table emails; diff --git a/migrations/20170804200817_add_email_table/up.sql b/migrations/20170804200817_add_email_table/up.sql new file mode 100644 index 00000000000..5bb4728ee51 --- /dev/null +++ b/migrations/20170804200817_add_email_table/up.sql @@ -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 REFERENCES emails, + token VARCHAR NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT now() +); + +INSERT INTO emails (user_id, email) + SELECT id, email FROM users; diff --git a/src/schema.rs b/src/schema.rs index 80ef28cc823..e0555444750 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -408,6 +408,38 @@ table! { } } +table! { + /// Representation of the `emails` table. + /// + /// (Automatically generated by Diesel.) + emails (id) { + /// The `id` column of the `emails` table. + /// + /// Its SQL type is `Int4`. + /// + /// (Automatically generated by Diesel.) + id -> Int4, + /// The `user_id` column of the `emails` table. + /// + /// Its SQL type is `Int4`. + /// + /// (Automatically generated by Diesel.) + user_id -> Int4, + /// The `email` column of the `emails` table. + /// + /// Its SQL type is `Varchar`. + /// + /// (Automatically generated by Diesel.) + email -> Varchar, + /// The `verified` column of the `emails` table. + /// + /// Its SQL type is `Bool`. + /// + /// (Automatically generated by Diesel.) + verified -> Bool, + } +} + table! { /// Representation of the `follows` table. /// @@ -546,6 +578,38 @@ table! { } } +table! { + /// Representation of the `tokens` table. + /// + /// (Automatically generated by Diesel.) + tokens (id) { + /// The `id` column of the `tokens` table. + /// + /// Its SQL type is `Int4`. + /// + /// (Automatically generated by Diesel.) + id -> Int4, + /// The `email_id` column of the `tokens` table. + /// + /// Its SQL type is `Int4`. + /// + /// (Automatically generated by Diesel.) + email_id -> Int4, + /// The `token` column of the `tokens` table. + /// + /// Its SQL type is `Varchar`. + /// + /// (Automatically generated by Diesel.) + token -> Varchar, + /// The `created_at` column of the `tokens` table. + /// + /// Its SQL type is `Timestamp`. + /// + /// (Automatically generated by Diesel.) + created_at -> Timestamp, + } +} + table! { /// Representation of the `users` table. /// @@ -753,3 +817,4 @@ joinable!(crate_owners -> teams (owner_id)); joinable!(crate_owners -> users (owner_id)); joinable!(readme_rendering -> versions (version_id)); joinable!(crate_owner_invitations -> crates (crate_id)); +joinable!(tokens -> emails (email_id)); From 69cec97e7fdd95448347fd3276caebad38614ee6 Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Mon, 7 Aug 2017 17:03:08 -0400 Subject: [PATCH 02/67] create/update seems to add to email table --- src/user/mod.rs | 160 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 158 insertions(+), 2 deletions(-) diff --git a/src/user/mod.rs b/src/user/mod.rs index 5ba429bb1e2..25a86eea9f5 100644 --- a/src/user/mod.rs +++ b/src/user/mod.rs @@ -5,6 +5,7 @@ use diesel::prelude::*; use rand::{thread_rng, Rng}; use std::borrow::Cow; use serde_json; +use time::Timespec; use app::RequestApp; use db::RequestTransaction; @@ -44,6 +45,38 @@ pub struct NewUser<'a> { pub gh_access_token: Cow<'a, str>, } +#[derive(Debug, Queryable, AsChangeset)] +pub struct Email { + pub id: i32, + pub user_id: i32, + pub email: String, + pub verified: bool, +} + +#[derive(Debug, Insertable)] +#[table_name="emails"] +pub struct NewEmail { + pub user_id: i32, + pub email: String, + pub verified: bool, +} + +#[derive(Debug, Queryable, AsChangeset)] +pub struct Token { + pub id: i32, + pub email_id: i32, + pub token: String, + pub created_at: Timespec, +} + +#[derive(Debug, Insertable)] +#[table_name="tokens"] +pub struct NewToken { + pub email_id: i32, + pub token: String, + pub created_at: Timespec, +} + impl<'a> NewUser<'a> { pub fn new( gh_id: i32, @@ -69,6 +102,7 @@ impl<'a> NewUser<'a> { use diesel::expression::dsl::sql; use diesel::types::Integer; use diesel::pg::upsert::*; + use time; let update_user = NewUser { email: None, @@ -88,12 +122,43 @@ impl<'a> NewUser<'a> { // necessary for most fields in the database to be used as a conflict // target :) let conflict_target = sql::("(gh_id) WHERE gh_id > 0"); - insert(&self.on_conflict( + let result = insert(&self.on_conflict( conflict_target, do_update().set(&update_user), )).into(users::table) .get_result(conn) - .map_err(Into::into) + .map_err(Into::into); + + println!("insert into user table result: {:?}", result); + + if let Some(user_email) = self.email { + let user_id = users::table.select(users::id).filter(users::gh_id.eq(&self.gh_id)).first(&*conn).unwrap(); + + let new_email = NewEmail { + user_id: user_id, + email: String::from(user_email), + verified: false, + }; + + let conflict_target = sql::("(user_id) WHERE user_id > 0"); + let email_result : QueryResult = insert(&new_email.on_conflict_do_nothing()) + .into(emails::table) + .get_result(conn) + .map_err(Into::into); + + println!("insert into email table: {:?}", email_result); + + /*let token = generate_token(); + let new_token = NewToken { + email_id: user_id, + token: token, + created_at: time::now_utc().to_timespec(), + }; + + let token_result = insert(new_token).into(tokens::table).get_result(conn).map_err(Into::into);*/ + } + + result } } @@ -432,6 +497,12 @@ pub fn stats(req: &mut Request) -> CargoResult { pub fn update_user(req: &mut Request) -> CargoResult { use diesel::update; use self::users::dsl::{users, gh_login, email}; + use diesel::insert; + use diesel::expression::dsl::sql; + use diesel::types::Integer; + use diesel::pg::upsert::*; + use time; + let mut body = String::new(); req.body().read_to_string(&mut body)?; @@ -473,9 +544,94 @@ pub fn update_user(req: &mut Request) -> CargoResult { .set(email.eq(user_email)) .execute(&*conn)?; + /*let token = generate_token(); + + let update_email = Email { + id: 1, + user_id: user.id, + email: String::from(user_email), + verified: false, + }; + + let new_token = Token { + id: 1, + email_id: user.id, + token: token, + created_at: time::now_utc().to_timespec(), + }; + + let conflict_target = sql::("(user_id) WHERE user_id > 0"); + insert(&update_email.on_conflict( + conflict_target, + do_update().set(&update_email), + )).into(emails::table) + .get_result(conn) + .map_err(Into::into); + + let confict_target = sql::("(email_id) WHERE email_id > 0"); + insert(&new_token.on_conflict( + conflict_target, + do_update().set(&new_token), + )).into(emails::table) + .get_result(conn) + .map_err(Into::into); + + confirm_user_email(user_email, user, token);*/ + #[derive(Serialize)] struct R { ok: bool, } Ok(req.json(&R { ok: true })) } + +/*fn confirm_user_email(email: &str, user: &User, token: String) { + // perhaps use crate lettre and heroku service Mailgun + // Need to add two, perhaps three, columns to the user table + // One column: token string + // Two column: email confirmed? + // Three column: token expiration - perhaps a timestamp of when + // the token was created, or when the token is invalid? + // Generate longish string as a token, store it in the table + // Create a URL with token string as path to send to user + // If user clicks on path, look email/user up in database, + // make sure tokens match + + use dotenv::dotenv; + use std::env; + use lettre::transport::smtp::{SecurityLevel, SmtpTransportBuilder}; + use lettre::email::EmailBuilder; + use lettre::transport::smtp::authentication::Mechanism; + use lettre::transport::smtp::SUBMISSION_PORT; + use lettre::transport::EmailTransport; + + dotenv().ok(); + let mailgun_username = env::var("MAILGUN_SMTP_LOGIN").unwrap(); + let mailgun_password = env::var("MAILGUN_SMTP_PASSWORD").unwrap(); + let mailgun_server = env::var("MAILGUN_SMTP_SERVER").unwrap(); + + let email = EmailBuilder::new() + .to(email) + .from(mailgun_username.as_str()) + .subject("Please confirm your email address") + .body(format!("Hello {}! Welcome to Crates.io. Please click the + link below to verify your email address. Thank you! + \n\n + {}", user.name.unwrap(), token).as_str()) + .build().unwrap(); + + let mut transport = SmtpTransportBuilder::new((mailgun_server.as_str(), SUBMISSION_PORT)) + .unwrap() + .credentials(&mailgun_username, &mailgun_password) + .security_level(SecurityLevel::AlwaysEncrypt) + .smtp_utf8(true) + .authentication_mechanism(Mechanism::Plain) + .build(); + + transport.send(email) +}*/ + +fn generate_token() -> String { + let token: String = thread_rng().gen_ascii_chars().take(26).collect(); + token +} From e45871e4f31b8bd12a67f3daf2acc81cdd0cd4ca Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Tue, 8 Aug 2017 14:25:55 -0400 Subject: [PATCH 03/67] on create/update user, email field is added to new table and token is generated, preliminary test for functionality added --- src/tests/user.rs | 62 +++++++++++++++++++++++++++++++++++++++++++++++ src/user/mod.rs | 33 +++++++++++++++---------- 2 files changed, 82 insertions(+), 13 deletions(-) diff --git a/src/tests/user.rs b/src/tests/user.rs index 456d0c16faa..f5ee05be9b6 100644 --- a/src/tests/user.rs +++ b/src/tests/user.rs @@ -510,3 +510,65 @@ fn test_this_user_cannot_change_that_user_email() { ); } + +#[test] +fn test_insert_into_email_table() { + #[derive(Deserialize)] + struct R { + user: EncodablePrivateUser, + } + + #[derive(Deserialize)] + struct S { + ok: bool, + } + + let (_b, app, middle) = ::app(); + let mut req = ::req(app.clone(), Method::Get, "/me"); + let user = { + let conn = app.diesel_database.get().unwrap(); + let user = NewUser { + gh_id: 1, + email: Some("hi@hello.hey"), + ..::new_user("potato") + }; + + let user = user.create_or_update(&conn).unwrap(); + ::sign_in_as(&mut req, &user); + user + }; + + let mut response = ok_resp!(middle.call(req.with_path("/me").with_method(Method::Get))); + let r = ::json::(&mut response); + assert_eq!(r.user.email.unwrap(), "hi@hello.hey"); + assert_eq!(r.user.login, "potato"); + + /*let body = r#"{"user":{"email":"apricot@apricots.apricot","name":"Apricot Apricoto","login":"apricot","avatar":"https://avatars0.githubusercontent.com","url":"https://github.com/apricot","kind":null}}"#; + let mut response = ok_resp!( + middle.call( + req.with_path(&format!("/api/v1/users/{}", user.id)) + .with_method(Method::Put) + .with_body(body.as_bytes()), + ) + ); + assert!(::json::(&mut response).ok);*/ + + ::logout(&mut req); + + { + let conn = app.diesel_database.get().unwrap(); + let user = NewUser { + gh_id: 1, + email: Some("hi@hello.hey"), + ..::new_user("potato") + }; + + let user = user.create_or_update(&conn).unwrap(); + ::sign_in_as(&mut req, &user); + } + + let mut response = ok_resp!(middle.call(req.with_path("/me").with_method(Method::Get))); + let r = ::json::(&mut response); + assert_eq!(r.user.email.unwrap(), "hi@hello.hey"); + assert_eq!(r.user.login, "potato"); +} diff --git a/src/user/mod.rs b/src/user/mod.rs index 25a86eea9f5..bf45306a628 100644 --- a/src/user/mod.rs +++ b/src/user/mod.rs @@ -45,7 +45,7 @@ pub struct NewUser<'a> { pub gh_access_token: Cow<'a, str>, } -#[derive(Debug, Queryable, AsChangeset)] +#[derive(Debug, Queryable, AsChangeset, Clone)] pub struct Email { pub id: i32, pub user_id: i32, @@ -103,6 +103,7 @@ impl<'a> NewUser<'a> { use diesel::types::Integer; use diesel::pg::upsert::*; use time; + use diesel::result::Error; let update_user = NewUser { email: None, @@ -129,8 +130,6 @@ impl<'a> NewUser<'a> { .get_result(conn) .map_err(Into::into); - println!("insert into user table result: {:?}", result); - if let Some(user_email) = self.email { let user_id = users::table.select(users::id).filter(users::gh_id.eq(&self.gh_id)).first(&*conn).unwrap(); @@ -146,16 +145,24 @@ impl<'a> NewUser<'a> { .get_result(conn) .map_err(Into::into); - println!("insert into email table: {:?}", email_result); - - /*let token = generate_token(); - let new_token = NewToken { - email_id: user_id, - token: token, - created_at: time::now_utc().to_timespec(), - }; - - let token_result = insert(new_token).into(tokens::table).get_result(conn).map_err(Into::into);*/ + match email_result { + Ok(email) => { + let token = generate_token(); + let new_token = NewToken { + email_id: email.id, + token: token, + created_at: time::now_utc().to_timespec(), + }; + + let token_result : QueryResult = insert(&new_token).into(tokens::table).get_result(conn).map_err(Into::into); + }, + Err(Error::NotFound) => { + // Pass, email had conflict, do nothing, this is expected + }, + Err(err) => { + return Err(err); + } + } } result From 225b365606920d99db8423e2303025091c9d59e1 Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Tue, 8 Aug 2017 17:03:34 -0400 Subject: [PATCH 04/67] add conflict target to migrations --- migrations/20170804200817_add_email_table/up.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/migrations/20170804200817_add_email_table/up.sql b/migrations/20170804200817_add_email_table/up.sql index 5bb4728ee51..4a0dc37161b 100644 --- a/migrations/20170804200817_add_email_table/up.sql +++ b/migrations/20170804200817_add_email_table/up.sql @@ -8,7 +8,7 @@ CREATE table emails ( CREATE table tokens ( id SERIAL PRIMARY KEY, - email_id INTEGER NOT NULL REFERENCES emails, + email_id INTEGER NOT NULL UNIQUE REFERENCES emails, token VARCHAR NOT NULL, created_at TIMESTAMP NOT NULL DEFAULT now() ); From 0982c3b790a50269268928cf364cda59515ec85a Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Tue, 8 Aug 2017 17:04:24 -0400 Subject: [PATCH 05/67] try to add email and table functionality to edit/add email address, tests fail --- src/user/mod.rs | 69 ++++++++++++++++++++++++++++--------------------- 1 file changed, 39 insertions(+), 30 deletions(-) diff --git a/src/user/mod.rs b/src/user/mod.rs index bf45306a628..ee14c794933 100644 --- a/src/user/mod.rs +++ b/src/user/mod.rs @@ -53,7 +53,7 @@ pub struct Email { pub verified: bool, } -#[derive(Debug, Insertable)] +#[derive(Debug, Insertable, AsChangeset)] #[table_name="emails"] pub struct NewEmail { pub user_id: i32, @@ -69,7 +69,7 @@ pub struct Token { pub created_at: Timespec, } -#[derive(Debug, Insertable)] +#[derive(Debug, Insertable, AsChangeset)] #[table_name="tokens"] pub struct NewToken { pub email_id: i32, @@ -139,7 +139,7 @@ impl<'a> NewUser<'a> { verified: false, }; - let conflict_target = sql::("(user_id) WHERE user_id > 0"); + let conflict_target = sql::("user_id"); let email_result : QueryResult = insert(&new_email.on_conflict_do_nothing()) .into(emails::table) .get_result(conn) @@ -550,40 +550,49 @@ pub fn update_user(req: &mut Request) -> CargoResult { update(users.filter(gh_login.eq(&user.gh_login))) .set(email.eq(user_email)) .execute(&*conn)?; - - /*let token = generate_token(); - - let update_email = Email { - id: 1, + + let new_email = NewEmail { user_id: user.id, email: String::from(user_email), verified: false, }; - - let new_token = Token { - id: 1, - email_id: user.id, - token: token, - created_at: time::now_utc().to_timespec(), - }; - - let conflict_target = sql::("(user_id) WHERE user_id > 0"); - insert(&update_email.on_conflict( - conflict_target, - do_update().set(&update_email), - )).into(emails::table) - .get_result(conn) - .map_err(Into::into); - let confict_target = sql::("(email_id) WHERE email_id > 0"); - insert(&new_token.on_conflict( - conflict_target, - do_update().set(&new_token), - )).into(emails::table) - .get_result(conn) + let conflict_target = sql::("user_id"); + let email_result : QueryResult = insert(&new_email + .on_conflict( + conflict_target, + do_update().set(&new_email) + )) + .into(emails::table) + .get_result(&*conn) .map_err(Into::into); - confirm_user_email(user_email, user, token);*/ + println!("email_result from insert/update: {:?}", email_result); + + match email_result { + Ok(email_response) => { + let token = generate_token(); + let new_token = NewToken { + email_id: email_response.id, + token: token, + created_at: time::now_utc().to_timespec(), + }; + + let conflict_target = sql::("email_id"); + let token_result : QueryResult = insert(&new_token + .on_conflict( + conflict_target, + do_update().set(&new_token) + )) + .into(tokens::table) + .get_result(&*conn) + .map_err(Into::into); + println!("token_result from insert/update: {:?}", token_result); + }, + Err(err) => { + return Err(human("Error in creating token")); + } + } #[derive(Serialize)] struct R { From 40c6b0f90bb58c0c2e38a170829553387862f300 Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Wed, 9 Aug 2017 11:52:06 -0400 Subject: [PATCH 06/67] for GET /me route, get email from emails table rather than user table, tests passing --- src/user/mod.rs | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/src/user/mod.rs b/src/user/mod.rs index ee14c794933..5f2e674de77 100644 --- a/src/user/mod.rs +++ b/src/user/mod.rs @@ -139,7 +139,6 @@ impl<'a> NewUser<'a> { verified: false, }; - let conflict_target = sql::("user_id"); let email_result : QueryResult = insert(&new_email.on_conflict_do_nothing()) .into(emails::table) .get_result(conn) @@ -154,7 +153,7 @@ impl<'a> NewUser<'a> { created_at: time::now_utc().to_timespec(), }; - let token_result : QueryResult = insert(&new_token).into(tokens::table).get_result(conn).map_err(Into::into); + let _token_result : QueryResult = insert(&new_token).into(tokens::table).get_result(conn).map_err(Into::into); }, Err(Error::NotFound) => { // Pass, email had conflict, do nothing, this is expected @@ -392,10 +391,30 @@ pub fn me(req: &mut Request) -> CargoResult { // This change is not preferable, we'd rather fix the request, // perhaps adding `req.mut_extensions().insert(user)` to the // update_user route, however this somehow does not seem to work + use self::users::dsl::{users, id}; - let user_id = req.user()?.id; + use self::emails::dsl::{emails, user_id}; + + let u_id = req.user()?.id; let conn = req.db_conn()?; - let user = users.filter(id.eq(user_id)).first::(&*conn)?; + + let user_info = users.filter(id.eq(u_id)).first::(&*conn)?; + let email_result = emails.filter(user_id.eq(u_id)).first::(&*conn); + + let email : Option = match email_result { + Ok(response) => Some(response.email), + Err(err) => None + }; + + let user = User { + id: user_info.id, + email: email, + gh_access_token: user_info.gh_access_token, + gh_login: user_info.gh_login, + name: user_info.name, + gh_avatar: user_info.gh_avatar, + gh_id: user_info.gh_id, + }; #[derive(Serialize)] struct R { @@ -504,6 +523,8 @@ pub fn stats(req: &mut Request) -> CargoResult { pub fn update_user(req: &mut Request) -> CargoResult { use diesel::update; use self::users::dsl::{users, gh_login, email}; + use self::emails::dsl::user_id; + use self::tokens::dsl::email_id; use diesel::insert; use diesel::expression::dsl::sql; use diesel::types::Integer; @@ -557,10 +578,9 @@ pub fn update_user(req: &mut Request) -> CargoResult { verified: false, }; - let conflict_target = sql::("user_id"); let email_result : QueryResult = insert(&new_email .on_conflict( - conflict_target, + user_id, do_update().set(&new_email) )) .into(emails::table) @@ -578,10 +598,9 @@ pub fn update_user(req: &mut Request) -> CargoResult { created_at: time::now_utc().to_timespec(), }; - let conflict_target = sql::("email_id"); let token_result : QueryResult = insert(&new_token .on_conflict( - conflict_target, + email_id, do_update().set(&new_token) )) .into(tokens::table) From 44d56eb3ff3ac0323c3151091b43e0ebfc4181cf Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Wed, 9 Aug 2017 12:30:27 -0400 Subject: [PATCH 07/67] UserShowResponse should be separated into public and private according to what is route we get from --- src/tests/token.rs | 2 +- src/tests/user.rs | 19 ++++++++++--------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/tests/token.rs b/src/tests/token.rs index db253afe8c6..d164d243892 100644 --- a/src/tests/token.rs +++ b/src/tests/token.rs @@ -385,7 +385,7 @@ fn token_gives_access_to_me() { req.header("Authorization", &token.token); let mut response = ok_resp!(middle.call(&mut req)); - let json: ::user::UserShowResponse = ::json(&mut response); + let json: ::user::UserShowPrivateResponse = ::json(&mut response); assert_eq!(json.user.email, user.email); } diff --git a/src/tests/user.rs b/src/tests/user.rs index f5ee05be9b6..190f1caca4e 100644 --- a/src/tests/user.rs +++ b/src/tests/user.rs @@ -4,7 +4,7 @@ use conduit::{Handler, Method}; use cargo_registry::token::ApiToken; use cargo_registry::krate::EncodableCrate; -use cargo_registry::user::{User, NewUser, EncodablePrivateUser}; +use cargo_registry::user::{User, NewUser, EncodablePrivateUser, EncodablePublicUser}; use cargo_registry::version::EncodableVersion; use diesel::prelude::*; @@ -16,7 +16,12 @@ struct AuthResponse { } #[derive(Deserialize)] -pub struct UserShowResponse { +pub struct UserShowPublicResponse { + pub user: EncodablePublicUser, +} + +#[derive(Deserialize)] +pub struct UserShowPrivateResponse { pub user: EncodablePrivateUser, } @@ -48,7 +53,7 @@ fn me() { let user = ::sign_in(&mut req, &app); let mut response = ok_resp!(middle.call(&mut req)); - let json: UserShowResponse = ::json(&mut response); + let json: UserShowPrivateResponse = ::json(&mut response); assert_eq!(json.user.email, user.email); } @@ -65,15 +70,11 @@ fn show() { let mut req = ::req(app.clone(), Method::Get, "/api/v1/users/foo"); let mut response = ok_resp!(middle.call(&mut req)); - let json: UserShowResponse = ::json(&mut response); - // Emails should be None as when on the user/:user_id page, a user's email should - // not be accessible in order to keep private. - assert_eq!(None, json.user.email); + let json: UserShowPublicResponse = ::json(&mut response); assert_eq!("foo", json.user.login); let mut response = ok_resp!(middle.call(req.with_path("/api/v1/users/bar"))); - let json: UserShowResponse = ::json(&mut response); - assert_eq!(None, json.user.email); + let json: UserShowPublicResponse = ::json(&mut response); assert_eq!("bar", json.user.login); assert_eq!(Some("https://github.com/bar".into()), json.user.url); } From 2cf7ad051ab224df65ec2c9db69d082ef6df3df5 Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Wed, 9 Aug 2017 12:31:08 -0400 Subject: [PATCH 08/67] add email_verified field to private user response --- src/user/mod.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/user/mod.rs b/src/user/mod.rs index 5f2e674de77..b4ed17f94b8 100644 --- a/src/user/mod.rs +++ b/src/user/mod.rs @@ -187,6 +187,7 @@ pub struct EncodablePrivateUser { pub id: i32, pub login: String, pub email: Option, + pub email_verified: bool, pub name: Option, pub avatar: Option, pub url: Option, @@ -220,7 +221,7 @@ impl User { } /// Converts this `User` model into an `EncodablePrivateUser` for JSON serialization. - pub fn encodable_private(self) -> EncodablePrivateUser { + pub fn encodable_private(self, email_verified : bool) -> EncodablePrivateUser { let User { id, email, @@ -233,6 +234,7 @@ impl User { EncodablePrivateUser { id: id, email: email, + email_verified, avatar: gh_avatar, login: gh_login, name: name, @@ -401,9 +403,9 @@ pub fn me(req: &mut Request) -> CargoResult { let user_info = users.filter(id.eq(u_id)).first::(&*conn)?; let email_result = emails.filter(user_id.eq(u_id)).first::(&*conn); - let email : Option = match email_result { - Ok(response) => Some(response.email), - Err(err) => None + let (email, verified) : (Option, bool) = match email_result { + Ok(response) => (Some(response.email), response.verified), + Err(err) => (None, false) }; let user = User { @@ -420,7 +422,7 @@ pub fn me(req: &mut Request) -> CargoResult { struct R { user: EncodablePrivateUser, } - Ok(req.json(&R { user: user.encodable_private() })) + Ok(req.json(&R { user: user.encodable_private(verified) })) } /// Handles the `GET /users/:user_id` route. From ed0f0607fa8f0e1dcb4ba5b9ec9545a4f92378e0 Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Wed, 9 Aug 2017 12:32:42 -0400 Subject: [PATCH 09/67] delete debug print statements --- src/user/mod.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/user/mod.rs b/src/user/mod.rs index b4ed17f94b8..89be140a90f 100644 --- a/src/user/mod.rs +++ b/src/user/mod.rs @@ -589,8 +589,6 @@ pub fn update_user(req: &mut Request) -> CargoResult { .get_result(&*conn) .map_err(Into::into); - println!("email_result from insert/update: {:?}", email_result); - match email_result { Ok(email_response) => { let token = generate_token(); @@ -608,7 +606,6 @@ pub fn update_user(req: &mut Request) -> CargoResult { .into(tokens::table) .get_result(&*conn) .map_err(Into::into); - println!("token_result from insert/update: {:?}", token_result); }, Err(err) => { return Err(human("Error in creating token")); From 7f21fd1eff421420853eafc21539f82bc32a17e0 Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Wed, 9 Aug 2017 13:05:06 -0400 Subject: [PATCH 10/67] add function for emailing users, no idea if it works --- src/user/mod.rs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/user/mod.rs b/src/user/mod.rs index 89be140a90f..babf7246af7 100644 --- a/src/user/mod.rs +++ b/src/user/mod.rs @@ -612,6 +612,8 @@ pub fn update_user(req: &mut Request) -> CargoResult { } } + send_user_confirm_email(user_email, user, token); + #[derive(Serialize)] struct R { ok: bool, @@ -619,14 +621,8 @@ pub fn update_user(req: &mut Request) -> CargoResult { Ok(req.json(&R { ok: true })) } -/*fn confirm_user_email(email: &str, user: &User, token: String) { +fn send_user_confirm_email(email: &str, user: &User, token: String) { // perhaps use crate lettre and heroku service Mailgun - // Need to add two, perhaps three, columns to the user table - // One column: token string - // Two column: email confirmed? - // Three column: token expiration - perhaps a timestamp of when - // the token was created, or when the token is invalid? - // Generate longish string as a token, store it in the table // Create a URL with token string as path to send to user // If user clicks on path, look email/user up in database, // make sure tokens match @@ -651,7 +647,7 @@ pub fn update_user(req: &mut Request) -> CargoResult { .body(format!("Hello {}! Welcome to Crates.io. Please click the link below to verify your email address. Thank you! \n\n - {}", user.name.unwrap(), token).as_str()) + crates.io/me/confirm/{}", user.name.unwrap(), token).as_str()) .build().unwrap(); let mut transport = SmtpTransportBuilder::new((mailgun_server.as_str(), SUBMISSION_PORT)) @@ -663,7 +659,7 @@ pub fn update_user(req: &mut Request) -> CargoResult { .build(); transport.send(email) -}*/ +} fn generate_token() -> String { let token: String = thread_rng().gen_ascii_chars().take(26).collect(); From 5ca9ef84d0742e6e490e25fd563c0a3ab055771d Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Wed, 9 Aug 2017 17:05:14 -0400 Subject: [PATCH 11/67] preliminary send/confirm user email --- src/user/mod.rs | 60 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 48 insertions(+), 12 deletions(-) diff --git a/src/user/mod.rs b/src/user/mod.rs index babf7246af7..b3cd68993f1 100644 --- a/src/user/mod.rs +++ b/src/user/mod.rs @@ -23,7 +23,7 @@ pub use self::middleware::{Middleware, RequestUser, AuthenticationSource}; pub mod middleware; /// The model representing a row in the `users` database table. -#[derive(Clone, Debug, PartialEq, Eq, Queryable, Identifiable, AsChangeset)] +#[derive(Clone, Debug, PartialEq, Eq, Queryable, Identifiable, AsChangeset, Associations)] pub struct User { pub id: i32, pub email: Option, @@ -45,7 +45,8 @@ pub struct NewUser<'a> { pub gh_access_token: Cow<'a, str>, } -#[derive(Debug, Queryable, AsChangeset, Clone)] +#[derive(Debug, Queryable, AsChangeset, Identifiable, Associations)] +#[belongs_to(User)] pub struct Email { pub id: i32, pub user_id: i32, @@ -61,7 +62,8 @@ pub struct NewEmail { pub verified: bool, } -#[derive(Debug, Queryable, AsChangeset)] +#[derive(Debug, Queryable, AsChangeset, Identifiable, Associations)] +#[belongs_to(Email)] pub struct Token { pub id: i32, pub email_id: i32, @@ -129,6 +131,7 @@ impl<'a> NewUser<'a> { )).into(users::table) .get_result(conn) .map_err(Into::into); + println!("create_or_update upsert user: {:?}", result); if let Some(user_email) = self.email { let user_id = users::table.select(users::id).filter(users::gh_id.eq(&self.gh_id)).first(&*conn).unwrap(); @@ -143,6 +146,7 @@ impl<'a> NewUser<'a> { .into(emails::table) .get_result(conn) .map_err(Into::into); + println!("create_or_update upsert email: {:?}", email_result); match email_result { Ok(email) => { @@ -153,7 +157,8 @@ impl<'a> NewUser<'a> { created_at: time::now_utc().to_timespec(), }; - let _token_result : QueryResult = insert(&new_token).into(tokens::table).get_result(conn).map_err(Into::into); + let token_result : QueryResult = insert(&new_token).into(tokens::table).get_result(conn).map_err(Into::into); + println!("create_or_update insert token: {:?}", token_result); }, Err(Error::NotFound) => { // Pass, email had conflict, do nothing, this is expected @@ -403,6 +408,8 @@ pub fn me(req: &mut Request) -> CargoResult { let user_info = users.filter(id.eq(u_id)).first::(&*conn)?; let email_result = emails.filter(user_id.eq(u_id)).first::(&*conn); + println!("GET /me user_info: {:?}", user_info); + println!("GET /me email_result: {:?}", email_result); let (email, verified) : (Option, bool) = match email_result { Ok(response) => (Some(response.email), response.verified), Err(err) => (None, false) @@ -588,13 +595,15 @@ pub fn update_user(req: &mut Request) -> CargoResult { .into(emails::table) .get_result(&*conn) .map_err(Into::into); + println!("update_user upsert email: {:?}", email_result); + + let token = generate_token(); match email_result { Ok(email_response) => { - let token = generate_token(); let new_token = NewToken { email_id: email_response.id, - token: token, + token: token.clone(), created_at: time::now_utc().to_timespec(), }; @@ -606,13 +615,14 @@ pub fn update_user(req: &mut Request) -> CargoResult { .into(tokens::table) .get_result(&*conn) .map_err(Into::into); + println!("update_user upsert token: {:?}", token_result); }, Err(err) => { return Err(human("Error in creating token")); } } - send_user_confirm_email(user_email, user, token); + //send_user_confirm_email(user_email, user, &token); #[derive(Serialize)] struct R { @@ -621,7 +631,7 @@ pub fn update_user(req: &mut Request) -> CargoResult { Ok(req.json(&R { ok: true })) } -fn send_user_confirm_email(email: &str, user: &User, token: String) { +fn send_user_confirm_email(email: &str, user: &User, token: &str) { // perhaps use crate lettre and heroku service Mailgun // Create a URL with token string as path to send to user // If user clicks on path, look email/user up in database, @@ -647,18 +657,44 @@ fn send_user_confirm_email(email: &str, user: &User, token: String) { .body(format!("Hello {}! Welcome to Crates.io. Please click the link below to verify your email address. Thank you! \n\n - crates.io/me/confirm/{}", user.name.unwrap(), token).as_str()) - .build().unwrap(); + crates.io/me/confirm/{}", user.name.as_ref().unwrap(), token).as_str()) + .build() + .expect("Failed to build confirm email message"); let mut transport = SmtpTransportBuilder::new((mailgun_server.as_str(), SUBMISSION_PORT)) - .unwrap() + .expect("Failed to create message transport") .credentials(&mailgun_username, &mailgun_password) .security_level(SecurityLevel::AlwaysEncrypt) .smtp_utf8(true) .authentication_mechanism(Mechanism::Plain) .build(); - transport.send(email) + transport.send(email); +} + +/// Handles the `PUT /confirm/:email_token` route +fn confirm_user_email(req: &mut Request) -> CargoResult { + // to confirm, we must grab the token on the request as part of the URL + // look up the token in the tokens table + // find what user the token belongs to + // on the email table, change 'verified' to true + // delete the token from the tokens table + use diesel::{update, delete}; + + let conn = req.db_conn()?; + let req_token = &req.params()["email_token"]; + + let token_info = tokens::table.filter(tokens::token.eq(req_token)).first::(&*conn)?; + let email_info = emails::table.filter(emails::id.eq(token_info.email_id)).first::(&*conn)?; + + update(emails::table.filter(emails::id.eq(email_info.id))).set(emails::verified.eq(true)).execute(&*conn)?; + delete(tokens::table.filter(tokens::id.eq(token_info.id))).execute(&*conn)?; + + #[derive(Serialize)] + struct R { + ok: bool, + } + Ok(req.json(&R { ok: true })) } fn generate_token() -> String { From fa305cf584aa30c744621470bca33657e17298c9 Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Thu, 10 Aug 2017 12:47:11 -0400 Subject: [PATCH 12/67] Add routing info and lettre crate for confirming emails --- src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 6b705f97e10..e81455f25d9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -42,6 +42,7 @@ extern crate tar; extern crate time; extern crate toml; extern crate url; +extern crate lettre; extern crate conduit; extern crate conduit_conditional_get; @@ -191,6 +192,7 @@ pub fn middleware(app: Arc) -> MiddlewareBuilder { C(crate_owner_invitation::list), ); api_router.get("/summary", C(krate::summary)); + api_router.put("/confirm/:email_token", C(user::confirm_user_email)); let api_router = Arc::new(R404(api_router)); let mut router = RouteBuilder::new(); From 232134e73c3829ccfa9cf049d9b8b1f3520ea6d7 Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Thu, 10 Aug 2017 12:50:03 -0400 Subject: [PATCH 13/67] add error handling for confirming user email --- src/user/mod.rs | 34 +++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/src/user/mod.rs b/src/user/mod.rs index b3cd68993f1..c3ba6438c0d 100644 --- a/src/user/mod.rs +++ b/src/user/mod.rs @@ -12,7 +12,8 @@ use db::RequestTransaction; use krate::Follow; use pagination::Paginate; use schema::*; -use util::{RequestUtils, CargoResult, human}; +use util::errors::NotFound; +use util::{RequestUtils, CargoResult, internal, ChainError, human, bad_request}; use version::EncodableVersion; use {http, Version}; use owner::{Owner, OwnerKind, CrateOwner}; @@ -673,7 +674,7 @@ fn send_user_confirm_email(email: &str, user: &User, token: &str) { } /// Handles the `PUT /confirm/:email_token` route -fn confirm_user_email(req: &mut Request) -> CargoResult { +pub fn confirm_user_email(req: &mut Request) -> CargoResult { // to confirm, we must grab the token on the request as part of the URL // look up the token in the tokens table // find what user the token belongs to @@ -684,11 +685,30 @@ fn confirm_user_email(req: &mut Request) -> CargoResult { let conn = req.db_conn()?; let req_token = &req.params()["email_token"]; - let token_info = tokens::table.filter(tokens::token.eq(req_token)).first::(&*conn)?; - let email_info = emails::table.filter(emails::id.eq(token_info.email_id)).first::(&*conn)?; - - update(emails::table.filter(emails::id.eq(email_info.id))).set(emails::verified.eq(true)).execute(&*conn)?; - delete(tokens::table.filter(tokens::id.eq(token_info.id))).execute(&*conn)?; + let token_info = tokens::table.filter(tokens::token.eq(req_token)) + .first::(&*conn) + .map_err(|_| { + bad_request("Email token not found.") + })?; + + let email_info = emails::table.filter(emails::id.eq(token_info.email_id)) + .first::(&*conn) + .map_err(|_| { + bad_request("Email belonging to token not found.") + })?; + + update(emails::table.filter(emails::id.eq(email_info.id))) + .set(emails::verified.eq(true)) + .execute(&*conn) + .map_err(|_| { + bad_request("Email verification could not be updated") + })?; + + delete(tokens::table.filter(tokens::id.eq(token_info.id))) + .execute(&*conn) + .map_err(|_| { + bad_request("Email token could not be deleted") + })?; #[derive(Serialize)] struct R { From 9c1511f74e5bde2066784fb4c2988aa3f9c12a39 Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Thu, 10 Aug 2017 12:51:37 -0400 Subject: [PATCH 14/67] Add lettre crate --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 7172a7a2e9e..d4270b6fee7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" From 545ace21d985d3ce731321d213808f9e62e7b606 Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Thu, 10 Aug 2017 17:02:01 -0400 Subject: [PATCH 15/67] delete unused imports --- src/user/mod.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/user/mod.rs b/src/user/mod.rs index c3ba6438c0d..5e918dbfd21 100644 --- a/src/user/mod.rs +++ b/src/user/mod.rs @@ -536,8 +536,6 @@ pub fn update_user(req: &mut Request) -> CargoResult { use self::emails::dsl::user_id; use self::tokens::dsl::email_id; use diesel::insert; - use diesel::expression::dsl::sql; - use diesel::types::Integer; use diesel::pg::upsert::*; use time; From 490bcb4add0f5ee78db1701c4958fb65750eaeba Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Thu, 10 Aug 2017 17:03:56 -0400 Subject: [PATCH 16/67] test for when user clicks on link to confirm email --- src/tests/user.rs | 61 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/src/tests/user.rs b/src/tests/user.rs index 190f1caca4e..594d70d2d18 100644 --- a/src/tests/user.rs +++ b/src/tests/user.rs @@ -4,7 +4,7 @@ use conduit::{Handler, Method}; use cargo_registry::token::ApiToken; use cargo_registry::krate::EncodableCrate; -use cargo_registry::user::{User, NewUser, EncodablePrivateUser, EncodablePublicUser}; +use cargo_registry::user::{User, NewUser, EncodablePrivateUser, EncodablePublicUser, Email, Token}; use cargo_registry::version::EncodableVersion; use diesel::prelude::*; @@ -512,6 +512,9 @@ fn test_this_user_cannot_change_that_user_email() { } +// Test inserting into the email & token tables +// TODO make test better/actually test what is being put in +// and taken out of the tables #[test] fn test_insert_into_email_table() { #[derive(Deserialize)] @@ -573,3 +576,59 @@ fn test_insert_into_email_table() { assert_eq!(r.user.email.unwrap(), "hi@hello.hey"); assert_eq!(r.user.login, "potato"); } + +/* Given a new user +*/ +#[test] +fn test_confirm_user_email() { + use cargo_registry::schema::{emails, tokens}; + + #[derive(Deserialize)] + struct R { + user: EncodablePrivateUser, + } + + #[derive(Deserialize)] + struct S { + ok: bool, + } + + let (_b, app, middle) = ::app(); + let mut req = ::req(app.clone(), Method::Get, "/me"); + let user = { + let conn = app.diesel_database.get().unwrap(); + let user = NewUser { + email: Some("hi@hello.hey"), + ..::new_user("potato") + }; + + let user = user.create_or_update(&conn).unwrap(); + ::sign_in_as(&mut req, &user); + user + }; + + let email_token = { + let conn = app.diesel_database.get().unwrap(); + let email_info = emails::table.filter(emails::user_id.eq(user.id)) + .first::(&*conn) + .unwrap(); + let token_info = tokens::table.filter(tokens::email_id.eq(email_info.id)) + .first::(&*conn) + .unwrap(); + token_info.token + }; + + let mut response = ok_resp!( + middle.call( + req.with_path(&format!("/api/v1/confirm/{}", email_token)) + .with_method(Method::Put) + ) + ); + assert!(::json::(&mut response).ok); + + let mut response = ok_resp!(middle.call(req.with_path("/me").with_method(Method::Get))); + let r = ::json::(&mut response); + assert_eq!(r.user.email.unwrap(), "hi@hello.hey"); + assert_eq!(r.user.login, "potato"); + assert!(r.user.email_verified); +} From 5112f87826d95159b05a586d1576d90b5989cc03 Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Mon, 14 Aug 2017 17:09:59 -0400 Subject: [PATCH 17/67] beginnings of route to display success/error of user confirming email - added confirm route that submits PUT request to backend with error handling --- app/router.js | 1 + app/routes/confirm.js | 27 +++++++++++++++++++++++++++ app/templates/confirm/error.hbs | 1 + app/templates/confirm/index.hbs | 1 + 4 files changed, 30 insertions(+) create mode 100644 app/routes/confirm.js create mode 100644 app/templates/confirm/error.hbs create mode 100644 app/templates/confirm/index.hbs diff --git a/app/router.js b/app/router.js index 13a021ea994..a5c0f57cf9c 100644 --- a/app/router.js +++ b/app/router.js @@ -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; diff --git a/app/routes/confirm.js b/app/routes/confirm.js new file mode 100644 index 00000000000..4b0a60312b1 --- /dev/null +++ b/app/routes/confirm.js @@ -0,0 +1,27 @@ +import Ember from 'ember'; +import { inject as service } from '@ember/service'; + +export default Ember.Route.extend({ + flashMessages: service(), + ajax: service(), + + model(params) { + console.log(params); + + return this.get('ajax').raw(`/api/v1/confirm/${params.email_token}`, { method: 'PUT', data: {}}) + .then(({response}) => { + console.log("response: " + response) + }) + .catch((error) => { + console.log(error.payload); + if (error.payload) { + console.log("finding error in payload: " + error.payload.errors[0].detail); + this.get('flashMessages').queue(`Error in email confirmation: ${error.payload.errors[0].detail}`); + return; + } else { + this.get('flashMessages').queue(`Unknown error in email confirmation`); + return; + } + }); + } +}); diff --git a/app/templates/confirm/error.hbs b/app/templates/confirm/error.hbs new file mode 100644 index 00000000000..416baef4b33 --- /dev/null +++ b/app/templates/confirm/error.hbs @@ -0,0 +1 @@ +

Something went wrong in confirm

diff --git a/app/templates/confirm/index.hbs b/app/templates/confirm/index.hbs new file mode 100644 index 00000000000..4d5e2bc95be --- /dev/null +++ b/app/templates/confirm/index.hbs @@ -0,0 +1 @@ +

Thanks for confimring

From ea2a0b49f57aed7e3abe02fc856b69a826fb1fc0 Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Tue, 15 Aug 2017 10:55:56 -0400 Subject: [PATCH 18/67] route to confirm user email token displays success when token found, confirmed, deleted & displays error message when token not found --- app/routes/confirm.js | 4 ++-- app/templates/confirm.hbs | 1 + app/templates/confirm/error.hbs | 1 - app/templates/confirm/index.hbs | 1 - app/templates/error.hbs | 5 ----- 5 files changed, 3 insertions(+), 9 deletions(-) create mode 100644 app/templates/confirm.hbs delete mode 100644 app/templates/confirm/error.hbs delete mode 100644 app/templates/confirm/index.hbs delete mode 100644 app/templates/error.hbs diff --git a/app/routes/confirm.js b/app/routes/confirm.js index 4b0a60312b1..5fae3a3d9c0 100644 --- a/app/routes/confirm.js +++ b/app/routes/confirm.js @@ -17,10 +17,10 @@ export default Ember.Route.extend({ if (error.payload) { console.log("finding error in payload: " + error.payload.errors[0].detail); this.get('flashMessages').queue(`Error in email confirmation: ${error.payload.errors[0].detail}`); - return; + return this.replaceWith('index'); } else { this.get('flashMessages').queue(`Unknown error in email confirmation`); - return; + return this.replaceWith('index'); } }); } diff --git a/app/templates/confirm.hbs b/app/templates/confirm.hbs new file mode 100644 index 00000000000..160002ae11f --- /dev/null +++ b/app/templates/confirm.hbs @@ -0,0 +1 @@ +

Thanks for confirming

diff --git a/app/templates/confirm/error.hbs b/app/templates/confirm/error.hbs deleted file mode 100644 index 416baef4b33..00000000000 --- a/app/templates/confirm/error.hbs +++ /dev/null @@ -1 +0,0 @@ -

Something went wrong in confirm

diff --git a/app/templates/confirm/index.hbs b/app/templates/confirm/index.hbs deleted file mode 100644 index 4d5e2bc95be..00000000000 --- a/app/templates/confirm/index.hbs +++ /dev/null @@ -1 +0,0 @@ -

Thanks for confimring

diff --git a/app/templates/error.hbs b/app/templates/error.hbs deleted file mode 100644 index 95e6ededdb7..00000000000 --- a/app/templates/error.hbs +++ /dev/null @@ -1,5 +0,0 @@ -

Something Went Wrong!

-
{{model.message}}
-
-  {{model.stack}}
-
From d5a25ad54750e2dcf77703977bfc8d3b668b3f9e Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Tue, 15 Aug 2017 11:23:10 -0400 Subject: [PATCH 19/67] remove console logs and update confirm success page --- app/routes/confirm.js | 8 +------- app/templates/confirm.hbs | 2 +- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/app/routes/confirm.js b/app/routes/confirm.js index 5fae3a3d9c0..7c57b3bf5be 100644 --- a/app/routes/confirm.js +++ b/app/routes/confirm.js @@ -6,16 +6,10 @@ export default Ember.Route.extend({ ajax: service(), model(params) { - console.log(params); - return this.get('ajax').raw(`/api/v1/confirm/${params.email_token}`, { method: 'PUT', data: {}}) - .then(({response}) => { - console.log("response: " + response) - }) + .then(({response}) => {}) .catch((error) => { - console.log(error.payload); if (error.payload) { - console.log("finding error in payload: " + error.payload.errors[0].detail); this.get('flashMessages').queue(`Error in email confirmation: ${error.payload.errors[0].detail}`); return this.replaceWith('index'); } else { diff --git a/app/templates/confirm.hbs b/app/templates/confirm.hbs index 160002ae11f..2b38239f2df 100644 --- a/app/templates/confirm.hbs +++ b/app/templates/confirm.hbs @@ -1 +1 @@ -

Thanks for confirming

+

Thank you for confirming your email! :)

From d0b7e5b7fcb7dd2631d83e4930629b140c98e8e2 Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Tue, 15 Aug 2017 12:48:23 -0400 Subject: [PATCH 20/67] displays notification on account settings indicating whether or not email has been verified --- app/components/email-input.js | 11 +++++++++++ app/models/user.js | 1 + app/templates/components/email-input.hbs | 4 ++++ 3 files changed, 16 insertions(+) diff --git a/app/components/email-input.js b/app/components/email-input.js index 88b81063f37..4451e82a90d 100644 --- a/app/components/email-input.js +++ b/app/components/email-input.js @@ -1,5 +1,6 @@ import Component from '@ember/component'; import { empty } from '@ember/object/computed'; +import { computed } from '@ember/object'; export default Component.extend({ type: '', @@ -10,6 +11,16 @@ export default Component.extend({ notValidEmail: false, prevEmail: '', emailIsNull: true, + emailNotVerified: computed('user.email', 'user.email_verified', function() { + let email = this.get('user.email'); + let verified = this.get('user.email_verified'); + + if (email != null && !verified) { + return true; + } else { + return false; + } + }), actions: { editEmail() { diff --git a/app/models/user.js b/app/models/user.js index a5348032596..5f03635b20f 100644 --- a/app/models/user.js +++ b/app/models/user.js @@ -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'), diff --git a/app/templates/components/email-input.hbs b/app/templates/components/email-input.hbs index e3c4be93fef..b1f7f91b82f 100644 --- a/app/templates/components/email-input.hbs +++ b/app/templates/components/email-input.hbs @@ -30,5 +30,9 @@
+ {{#if emailNotVerified }} +

Your email has not yet been verified.

+ + {{/if}} {{/if}} From 6056a5c1e0b8f8197295ba78bb5f5844e636f797 Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Tue, 15 Aug 2017 15:14:55 -0400 Subject: [PATCH 21/67] add ability to resend verification emails --- app/components/email-input.js | 28 +++++++++++++ app/templates/components/email-input.hbs | 2 +- src/lib.rs | 1 + src/user/mod.rs | 51 ++++++++++++++++++++++++ 4 files changed, 81 insertions(+), 1 deletion(-) diff --git a/app/components/email-input.js b/app/components/email-input.js index 4451e82a90d..7f83cf7ce25 100644 --- a/app/components/email-input.js +++ b/app/components/email-input.js @@ -1,8 +1,11 @@ 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(), + type: '', value: '', isEditing: false, @@ -68,6 +71,31 @@ export default Component.extend({ cancelEdit() { this.set('isEditing', false); this.set('value', this.get('prevEmail')); + }, + + resendEmail() { + let userEmail = this.get('value'); + 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 + } + }) + .then(({response}) => {}) + .catch((error) => { + if (error.payload) { + console.log("error payload: " + error.payload.errors[0].detail); + } else { + console.log("unknown error"); + } + }); } } }); diff --git a/app/templates/components/email-input.hbs b/app/templates/components/email-input.hbs index b1f7f91b82f..8d66029dfe3 100644 --- a/app/templates/components/email-input.hbs +++ b/app/templates/components/email-input.hbs @@ -32,7 +32,7 @@ {{#if emailNotVerified }}

Your email has not yet been verified.

- + {{/if}} {{/if}} diff --git a/src/lib.rs b/src/lib.rs index e81455f25d9..06ea220970a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -193,6 +193,7 @@ pub fn middleware(app: Arc) -> MiddlewareBuilder { ); api_router.get("/summary", C(krate::summary)); api_router.put("/confirm/:email_token", C(user::confirm_user_email)); + api_router.put("/users/:user_id/resend", C(user::regenerate_token_and_send)); let api_router = Arc::new(R404(api_router)); let mut router = RouteBuilder::new(); diff --git a/src/user/mod.rs b/src/user/mod.rs index 5e918dbfd21..7d8ba27027f 100644 --- a/src/user/mod.rs +++ b/src/user/mod.rs @@ -715,6 +715,57 @@ pub fn confirm_user_email(req: &mut Request) -> CargoResult { Ok(req.json(&R { ok: true })) } +/// Handles `PUT /user/:user_id/resend` route +pub fn regenerate_token_and_send(req: &mut Request) -> CargoResult { + use diesel::update; + use diesel::pg::upsert::*; + use diesel::insert; + use time; + use self::tokens::dsl::email_id; + + let mut body = String::new(); + req.body().read_to_string(&mut body)?; + let user = req.user()?; + let name = &req.params()["user_id"].parse::().ok().unwrap(); + let conn = req.db_conn()?; + + // need to check if current user matches user to be updated + if &user.id != name { + return Err(human("current user does not match requested user")); + } + + let email_info = emails::table.filter(emails::user_id.eq(user.id)) + .first::(&*conn) + .map_err(|_| { + bad_request("Email could not be found") + })?; + + let token = generate_token(); + + let new_token = NewToken { + email_id: email_info.id, + token: token.clone(), + created_at: time::now_utc().to_timespec(), + }; + + let token_result : QueryResult = insert(&new_token + .on_conflict( + email_id, + do_update().set(&new_token) + )) + .into(tokens::table) + .get_result(&*conn) + .map_err(Into::into); + + //send_user_confirm_email(email_info.email, user, token); + + #[derive(Serialize)] + struct R { + ok: bool, + } + Ok(req.json(&R { ok: true })) +} + fn generate_token() -> String { let token: String = thread_rng().gen_ascii_chars().take(26).collect(); token From bd4686789c1ac55002c99a53e6589a9f388619f2 Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Wed, 16 Aug 2017 12:05:40 -0400 Subject: [PATCH 22/67] fixes bug where user model would not reload between confirming email and navigating to account settings page. Uses GET request to `/me` to get the latest version of the model upon returning successfully from the PUT request to `/confirm`. --- app/routes/confirm.js | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/app/routes/confirm.js b/app/routes/confirm.js index 7c57b3bf5be..6a9d81a9b2c 100644 --- a/app/routes/confirm.js +++ b/app/routes/confirm.js @@ -7,7 +7,23 @@ export default Ember.Route.extend({ model(params) { return this.get('ajax').raw(`/api/v1/confirm/${params.email_token}`, { method: 'PUT', data: {}}) - .then(({response}) => {}) + .then(({response}) => { + /* 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('/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}`); From b712905e6902f0530228ae9a373a0d66b49c9862 Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Wed, 16 Aug 2017 15:33:32 -0400 Subject: [PATCH 23/67] separates into 'development' and 'production' parts One part uses the env variables if found, the other uses an email stub that prints to the env_logger. --- src/lib.rs | 1 + src/user/mod.rs | 88 ++++++++++++++++++++++++++++++++----------------- 2 files changed, 59 insertions(+), 30 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 06ea220970a..8e05ba608f0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -43,6 +43,7 @@ extern crate time; extern crate toml; extern crate url; extern crate lettre; +extern crate env_logger; extern crate conduit; extern crate conduit_conditional_get; diff --git a/src/user/mod.rs b/src/user/mod.rs index 7d8ba27027f..352016383fd 100644 --- a/src/user/mod.rs +++ b/src/user/mod.rs @@ -132,8 +132,8 @@ impl<'a> NewUser<'a> { )).into(users::table) .get_result(conn) .map_err(Into::into); - println!("create_or_update upsert user: {:?}", result); + // To send the user an account verification email... if let Some(user_email) = self.email { let user_id = users::table.select(users::id).filter(users::gh_id.eq(&self.gh_id)).first(&*conn).unwrap(); @@ -147,22 +147,32 @@ impl<'a> NewUser<'a> { .into(emails::table) .get_result(conn) .map_err(Into::into); - println!("create_or_update upsert email: {:?}", email_result); match email_result { Ok(email) => { let token = generate_token(); let new_token = NewToken { email_id: email.id, - token: token, + token: token.clone(), created_at: time::now_utc().to_timespec(), }; let token_result : QueryResult = insert(&new_token).into(tokens::table).get_result(conn).map_err(Into::into); - println!("create_or_update insert token: {:?}", token_result); + + let user = User { + id: 0, + email: None, + gh_id: 0, + gh_login: String::from(""), + name: self.name.map(str::to_string), + gh_avatar: None, + gh_access_token: String::from(""), + }; + send_user_confirm_email(user_email, &user, &token) }, Err(Error::NotFound) => { - // Pass, email had conflict, do nothing, this is expected + // Pass, email had conflict, so it already exists in the database + // We don't have to do anything }, Err(err) => { return Err(err); @@ -621,7 +631,7 @@ pub fn update_user(req: &mut Request) -> CargoResult { } } - //send_user_confirm_email(user_email, user, &token); + send_user_confirm_email(user_email, user, &token); #[derive(Serialize)] struct R { @@ -630,6 +640,12 @@ pub fn update_user(req: &mut Request) -> CargoResult { Ok(req.json(&R { ok: true })) } +pub struct MailgunConfigVars { + pub smtp_login: String, + pub smtp_password: String, + pub smtp_server: String, +} + fn send_user_confirm_email(email: &str, user: &User, token: &str) { // perhaps use crate lettre and heroku service Mailgun // Create a URL with token string as path to send to user @@ -643,32 +659,45 @@ fn send_user_confirm_email(email: &str, user: &User, token: &str) { use lettre::transport::smtp::authentication::Mechanism; use lettre::transport::smtp::SUBMISSION_PORT; use lettre::transport::EmailTransport; + use lettre::transport::stub::StubEmailTransport; + use env_logger; dotenv().ok(); - let mailgun_username = env::var("MAILGUN_SMTP_LOGIN").unwrap(); - let mailgun_password = env::var("MAILGUN_SMTP_PASSWORD").unwrap(); - let mailgun_server = env::var("MAILGUN_SMTP_SERVER").unwrap(); + let mailgun_config = MailgunConfigVars { + smtp_login: env::var("MAILGUN_SMTP_LOGIN").unwrap_or(String::from("Not found")), + smtp_password: env::var("MAILGUN_SMTP_PASSWORD").unwrap_or(String::from("Not found")), + smtp_server: env::var("MAILGUN_SMTP_SERVER").unwrap_or(String::from("Not found")), + }; let email = EmailBuilder::new() - .to(email) - .from(mailgun_username.as_str()) - .subject("Please confirm your email address") - .body(format!("Hello {}! Welcome to Crates.io. Please click the - link below to verify your email address. Thank you! - \n\n - crates.io/me/confirm/{}", user.name.as_ref().unwrap(), token).as_str()) - .build() - .expect("Failed to build confirm email message"); - - let mut transport = SmtpTransportBuilder::new((mailgun_server.as_str(), SUBMISSION_PORT)) - .expect("Failed to create message transport") - .credentials(&mailgun_username, &mailgun_password) - .security_level(SecurityLevel::AlwaysEncrypt) - .smtp_utf8(true) - .authentication_mechanism(Mechanism::Plain) - .build(); - - transport.send(email); + .to(email) + .from(mailgun_config.smtp_login.as_str()) + .subject("Please confirm your email address") + .body(format!("Hello {}! Welcome to Crates.io. Please click the + link below to verify your email address. Thank you! + \n\n + https://crates.io/confirm/{}", + user.name.as_ref().unwrap(), token).as_str()) + .build() + .expect("Failed to build confirm email message"); + + if mailgun_config.smtp_login == "Not found" { + // To engage run `RUST_LOG=lettre=info cargo run --bin server` + env_logger::init(); + let mut sender = StubEmailTransport; + let result = sender.send(email); + println!("Sending your email now"); + } else { + let mut transport = SmtpTransportBuilder::new((mailgun_config.smtp_server.as_str(), SUBMISSION_PORT)) + .expect("Failed to create message transport") + .credentials(&mailgun_config.smtp_login, &mailgun_config.smtp_password) + .security_level(SecurityLevel::AlwaysEncrypt) + .smtp_utf8(true) + .authentication_mechanism(Mechanism::Plain) + .build(); + + transport.send(email); + } } /// Handles the `PUT /confirm/:email_token` route @@ -717,7 +746,6 @@ pub fn confirm_user_email(req: &mut Request) -> CargoResult { /// Handles `PUT /user/:user_id/resend` route pub fn regenerate_token_and_send(req: &mut Request) -> CargoResult { - use diesel::update; use diesel::pg::upsert::*; use diesel::insert; use time; @@ -757,7 +785,7 @@ pub fn regenerate_token_and_send(req: &mut Request) -> CargoResult { .get_result(&*conn) .map_err(Into::into); - //send_user_confirm_email(email_info.email, user, token); + send_user_confirm_email(&email_info.email, user, &token); #[derive(Serialize)] struct R { From 78b4f447a6b9264a717bfbea9185a8417f91d804 Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Thu, 17 Aug 2017 12:52:23 -0400 Subject: [PATCH 24/67] For testing purposes when env variables not found `send_user_confirm_email` will send output to a file named {message_id}.txt in user's `\tmp` directory --- src/user/mod.rs | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/user/mod.rs b/src/user/mod.rs index 352016383fd..fde9d0cbaef 100644 --- a/src/user/mod.rs +++ b/src/user/mod.rs @@ -655,12 +655,13 @@ fn send_user_confirm_email(email: &str, user: &User, token: &str) { use dotenv::dotenv; use std::env; use lettre::transport::smtp::{SecurityLevel, SmtpTransportBuilder}; - use lettre::email::EmailBuilder; + use lettre::email::{EmailBuilder, SendableEmail}; use lettre::transport::smtp::authentication::Mechanism; use lettre::transport::smtp::SUBMISSION_PORT; use lettre::transport::EmailTransport; - use lettre::transport::stub::StubEmailTransport; + use lettre::transport::file::FileEmailTransport; use env_logger; + use std::path::Path; dotenv().ok(); let mailgun_config = MailgunConfigVars { @@ -674,19 +675,19 @@ fn send_user_confirm_email(email: &str, user: &User, token: &str) { .from(mailgun_config.smtp_login.as_str()) .subject("Please confirm your email address") .body(format!("Hello {}! Welcome to Crates.io. Please click the - link below to verify your email address. Thank you! - \n\n - https://crates.io/confirm/{}", - user.name.as_ref().unwrap(), token).as_str()) +link below to verify your email address. Thank you!\n +https://crates.io/confirm/{}", + user.name.as_ref().unwrap(), token).as_str()) .build() .expect("Failed to build confirm email message"); if mailgun_config.smtp_login == "Not found" { - // To engage run `RUST_LOG=lettre=info cargo run --bin server` - env_logger::init(); - let mut sender = StubEmailTransport; - let result = sender.send(email); println!("Sending your email now"); + + let mut sender = FileEmailTransport::new(Path::new("/tmp")); + let result = sender.send(email.clone()); + println!("result: {:?}", result); + println!("message id: {:?}", email.message_id()); } else { let mut transport = SmtpTransportBuilder::new((mailgun_config.smtp_server.as_str(), SUBMISSION_PORT)) .expect("Failed to create message transport") From e295069913b9aad9fa6e9c5a474ccac705043171 Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Wed, 23 Aug 2017 14:38:05 -0400 Subject: [PATCH 25/67] added return result for fn send_user_confirm_email --- src/user/mod.rs | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/user/mod.rs b/src/user/mod.rs index fde9d0cbaef..fdd6fd0df8e 100644 --- a/src/user/mod.rs +++ b/src/user/mod.rs @@ -6,6 +6,7 @@ use rand::{thread_rng, Rng}; use std::borrow::Cow; use serde_json; use time::Timespec; +use lettre; use app::RequestApp; use db::RequestTransaction; @@ -168,7 +169,7 @@ impl<'a> NewUser<'a> { gh_avatar: None, gh_access_token: String::from(""), }; - send_user_confirm_email(user_email, &user, &token) + send_user_confirm_email(user_email, &user, &token); }, Err(Error::NotFound) => { // Pass, email had conflict, so it already exists in the database @@ -549,7 +550,6 @@ pub fn update_user(req: &mut Request) -> CargoResult { use diesel::pg::upsert::*; use time; - let mut body = String::new(); req.body().read_to_string(&mut body)?; let user = req.user()?; @@ -646,7 +646,7 @@ pub struct MailgunConfigVars { pub smtp_server: String, } -fn send_user_confirm_email(email: &str, user: &User, token: &str) { +fn send_user_confirm_email(email: &str, user: &User, token: &str) -> CargoResult<()> { // perhaps use crate lettre and heroku service Mailgun // Create a URL with token string as path to send to user // If user clicks on path, look email/user up in database, @@ -655,7 +655,7 @@ fn send_user_confirm_email(email: &str, user: &User, token: &str) { use dotenv::dotenv; use std::env; use lettre::transport::smtp::{SecurityLevel, SmtpTransportBuilder}; - use lettre::email::{EmailBuilder, SendableEmail}; + use lettre::email::{SendableEmail, EmailBuilder}; use lettre::transport::smtp::authentication::Mechanism; use lettre::transport::smtp::SUBMISSION_PORT; use lettre::transport::EmailTransport; @@ -677,17 +677,17 @@ fn send_user_confirm_email(email: &str, user: &User, token: &str) { .body(format!("Hello {}! Welcome to Crates.io. Please click the link below to verify your email address. Thank you!\n https://crates.io/confirm/{}", - user.name.as_ref().unwrap(), token).as_str()) + user.gh_login, token).as_str()) .build() .expect("Failed to build confirm email message"); if mailgun_config.smtp_login == "Not found" { - println!("Sending your email now"); let mut sender = FileEmailTransport::new(Path::new("/tmp")); let result = sender.send(email.clone()); - println!("result: {:?}", result); - println!("message id: {:?}", email.message_id()); + result.map_err(|_| { + bad_request("Email file could not be generated") + }); } else { let mut transport = SmtpTransportBuilder::new((mailgun_config.smtp_server.as_str(), SUBMISSION_PORT)) .expect("Failed to create message transport") @@ -697,8 +697,13 @@ https://crates.io/confirm/{}", .authentication_mechanism(Mechanism::Plain) .build(); - transport.send(email); + let result = transport.send(email.clone()); + result.map_err(|_| { + bad_request("Error in sending email") + }); } + + Ok(()) } /// Handles the `PUT /confirm/:email_token` route From 329f0216ab4527f55b6f4190f2de200e63f29c90 Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Wed, 23 Aug 2017 15:09:01 -0400 Subject: [PATCH 26/67] this change is just to get my branch to run on my version of heroku --- src/lib.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 8e05ba608f0..c0870e64226 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,11 +1,9 @@ //! This crate implements the backend server for https://crates.io/ //! //! All implemented routes are defined in the [middleware](fn.middleware.html) function and -//! implemented in the [category](category/index.html), [keyword](keyword/index.html), -//! [krate](krate/index.html), [user](user/index.html) and [version](version/index.html) modules. - -#![deny(warnings)] -#![deny(missing_debug_implementations, missing_copy_implementations)] +//! implemented in the [keyword](keyword/index.html), [krate](krate/index.html), +//! [user](user/index.html) and [version](version/index.html) modules. +//#![deny(warnings)] #![cfg_attr(feature = "clippy", feature(plugin))] #![cfg_attr(feature = "clippy", plugin(clippy))] #![recursion_limit="128"] From ab0826c25841a26e8882c27241c90ba5c5dffb9a Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Mon, 28 Aug 2017 16:21:59 -0400 Subject: [PATCH 27/67] add debug statements to email sending --- src/user/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/user/mod.rs b/src/user/mod.rs index fdd6fd0df8e..5cc453f9a62 100644 --- a/src/user/mod.rs +++ b/src/user/mod.rs @@ -682,13 +682,14 @@ https://crates.io/confirm/{}", .expect("Failed to build confirm email message"); if mailgun_config.smtp_login == "Not found" { - + println!("Email file generated"); let mut sender = FileEmailTransport::new(Path::new("/tmp")); let result = sender.send(email.clone()); result.map_err(|_| { bad_request("Email file could not be generated") }); } else { + println!("Actual email sent, maybe"); let mut transport = SmtpTransportBuilder::new((mailgun_config.smtp_server.as_str(), SUBMISSION_PORT)) .expect("Failed to create message transport") .credentials(&mailgun_config.smtp_login, &mailgun_config.smtp_password) From 0d0a2f8389905f862df5e14a7903bb3e5cd3760d Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Mon, 28 Aug 2017 16:37:09 -0400 Subject: [PATCH 28/67] figure out why mail isn't sending, should fail --- src/user/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/user/mod.rs b/src/user/mod.rs index 5cc453f9a62..2368277553e 100644 --- a/src/user/mod.rs +++ b/src/user/mod.rs @@ -631,7 +631,7 @@ pub fn update_user(req: &mut Request) -> CargoResult { } } - send_user_confirm_email(user_email, user, &token); + let email_result = send_user_confirm_email(user_email, user, &token); #[derive(Serialize)] struct R { @@ -692,7 +692,7 @@ https://crates.io/confirm/{}", println!("Actual email sent, maybe"); let mut transport = SmtpTransportBuilder::new((mailgun_config.smtp_server.as_str(), SUBMISSION_PORT)) .expect("Failed to create message transport") - .credentials(&mailgun_config.smtp_login, &mailgun_config.smtp_password) + .credentials(&mailgun_config.smtp_password, &mailgun_config.smtp_login) .security_level(SecurityLevel::AlwaysEncrypt) .smtp_utf8(true) .authentication_mechanism(Mechanism::Plain) From 042a3e9957692bda13cd4cdb74b07d2ef037ba65 Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Mon, 28 Aug 2017 16:55:10 -0400 Subject: [PATCH 29/67] debugging why emails aren't sending - add print statement for email_result --- src/user/mod.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/user/mod.rs b/src/user/mod.rs index 2368277553e..384959a470e 100644 --- a/src/user/mod.rs +++ b/src/user/mod.rs @@ -632,6 +632,7 @@ pub fn update_user(req: &mut Request) -> CargoResult { } let email_result = send_user_confirm_email(user_email, user, &token); + println!("Email result: {:?}", email_result); #[derive(Serialize)] struct R { @@ -690,9 +691,9 @@ https://crates.io/confirm/{}", }); } else { println!("Actual email sent, maybe"); - let mut transport = SmtpTransportBuilder::new((mailgun_config.smtp_server.as_str(), SUBMISSION_PORT)) + let mut transport = SmtpTransportBuilder::new((mailgun_config.smtp_server.as_str(), 200)) .expect("Failed to create message transport") - .credentials(&mailgun_config.smtp_password, &mailgun_config.smtp_login) + .credentials(&mailgun_config.smtp_login, &mailgun_config.smtp_password) .security_level(SecurityLevel::AlwaysEncrypt) .smtp_utf8(true) .authentication_mechanism(Mechanism::Plain) From e88251bfbc9fec297be46f49813d5c137550cd78 Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Tue, 29 Aug 2017 10:28:45 -0400 Subject: [PATCH 30/67] Add proper port back --- src/user/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/user/mod.rs b/src/user/mod.rs index 384959a470e..b9ac03e6796 100644 --- a/src/user/mod.rs +++ b/src/user/mod.rs @@ -691,7 +691,7 @@ https://crates.io/confirm/{}", }); } else { println!("Actual email sent, maybe"); - let mut transport = SmtpTransportBuilder::new((mailgun_config.smtp_server.as_str(), 200)) + let mut transport = SmtpTransportBuilder::new((mailgun_config.smtp_server.as_str(), SUBMISSION_PORT)) .expect("Failed to create message transport") .credentials(&mailgun_config.smtp_login, &mailgun_config.smtp_password) .security_level(SecurityLevel::AlwaysEncrypt) From 42246ff46426275698cdd92835a9412d34282794 Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Tue, 29 Aug 2017 10:38:28 -0400 Subject: [PATCH 31/67] Edit url to match heroku mirror name --- src/user/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/user/mod.rs b/src/user/mod.rs index b9ac03e6796..61ba35a66e9 100644 --- a/src/user/mod.rs +++ b/src/user/mod.rs @@ -677,7 +677,7 @@ fn send_user_confirm_email(email: &str, user: &User, token: &str) -> CargoResult .subject("Please confirm your email address") .body(format!("Hello {}! Welcome to Crates.io. Please click the link below to verify your email address. Thank you!\n -https://crates.io/confirm/{}", +https://crates-mirror.herokuapp.com/confirm/{}", user.gh_login, token).as_str()) .build() .expect("Failed to build confirm email message"); From 34ac0e2df27e030bdfb9fe7a459a0088f4254f57 Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Tue, 29 Aug 2017 11:30:52 -0400 Subject: [PATCH 32/67] update /me route to /api/v1/me --- app/routes/confirm.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/routes/confirm.js b/app/routes/confirm.js index 6a9d81a9b2c..94d57d6f354 100644 --- a/app/routes/confirm.js +++ b/app/routes/confirm.js @@ -19,7 +19,7 @@ export default Ember.Route.extend({ Suggestions of a more ideomatic way to fix/test this are welcome! */ if (this.session.get('isLoggedIn')) { - this.get('ajax').request('/me').then((response) => { + this.get('ajax').request('/api/v1/me').then((response) => { this.session.set('currentUser', this.store.push(this.store.normalize('user', response.user))); }) } From 37aecab26dc111fd2f1786d6960ed72bf4892368 Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Tue, 29 Aug 2017 14:56:25 -0400 Subject: [PATCH 33/67] add back warnings as errors --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index c0870e64226..7fbd2ad65a4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,7 +3,7 @@ //! All implemented routes are defined in the [middleware](fn.middleware.html) function and //! implemented in the [keyword](keyword/index.html), [krate](krate/index.html), //! [user](user/index.html) and [version](version/index.html) modules. -//#![deny(warnings)] +#![deny(warnings)] #![cfg_attr(feature = "clippy", feature(plugin))] #![cfg_attr(feature = "clippy", plugin(clippy))] #![recursion_limit="128"] From 0d64487c339babb4a6b1b944dc8d3580c7265ab3 Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Tue, 29 Aug 2017 14:57:06 -0400 Subject: [PATCH 34/67] change route /me to /api/v1/me --- src/tests/user.rs | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/src/tests/user.rs b/src/tests/user.rs index 594d70d2d18..6217fd8026d 100644 --- a/src/tests/user.rs +++ b/src/tests/user.rs @@ -522,14 +522,9 @@ fn test_insert_into_email_table() { user: EncodablePrivateUser, } - #[derive(Deserialize)] - struct S { - ok: bool, - } - let (_b, app, middle) = ::app(); let mut req = ::req(app.clone(), Method::Get, "/me"); - let user = { + { let conn = app.diesel_database.get().unwrap(); let user = NewUser { gh_id: 1, @@ -539,10 +534,9 @@ fn test_insert_into_email_table() { let user = user.create_or_update(&conn).unwrap(); ::sign_in_as(&mut req, &user); - user - }; + } - let mut response = ok_resp!(middle.call(req.with_path("/me").with_method(Method::Get))); + let mut response = ok_resp!(middle.call(req.with_path("/api/v1/me").with_method(Method::Get))); let r = ::json::(&mut response); assert_eq!(r.user.email.unwrap(), "hi@hello.hey"); assert_eq!(r.user.login, "potato"); @@ -571,7 +565,7 @@ fn test_insert_into_email_table() { ::sign_in_as(&mut req, &user); } - let mut response = ok_resp!(middle.call(req.with_path("/me").with_method(Method::Get))); + let mut response = ok_resp!(middle.call(req.with_path("/api/v1/me").with_method(Method::Get))); let r = ::json::(&mut response); assert_eq!(r.user.email.unwrap(), "hi@hello.hey"); assert_eq!(r.user.login, "potato"); @@ -626,7 +620,7 @@ fn test_confirm_user_email() { ); assert!(::json::(&mut response).ok); - let mut response = ok_resp!(middle.call(req.with_path("/me").with_method(Method::Get))); + let mut response = ok_resp!(middle.call(req.with_path("/api/v1/me").with_method(Method::Get))); let r = ::json::(&mut response); assert_eq!(r.user.email.unwrap(), "hi@hello.hey"); assert_eq!(r.user.login, "potato"); From 34ab92f02b3206b33c4b40b589bd5ad69ff54795 Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Tue, 29 Aug 2017 14:58:51 -0400 Subject: [PATCH 35/67] delete unused code, add more detailed comment for blank Err block when updating email --- src/user/mod.rs | 65 ++++++++++++++++++++----------------------------- 1 file changed, 27 insertions(+), 38 deletions(-) diff --git a/src/user/mod.rs b/src/user/mod.rs index 61ba35a66e9..2e539b5a848 100644 --- a/src/user/mod.rs +++ b/src/user/mod.rs @@ -6,15 +6,13 @@ use rand::{thread_rng, Rng}; use std::borrow::Cow; use serde_json; use time::Timespec; -use lettre; use app::RequestApp; use db::RequestTransaction; use krate::Follow; use pagination::Paginate; use schema::*; -use util::errors::NotFound; -use util::{RequestUtils, CargoResult, internal, ChainError, human, bad_request}; +use util::{RequestUtils, CargoResult, human, bad_request}; use version::EncodableVersion; use {http, Version}; use owner::{Owner, OwnerKind, CrateOwner}; @@ -158,22 +156,21 @@ impl<'a> NewUser<'a> { created_at: time::now_utc().to_timespec(), }; - let token_result : QueryResult = insert(&new_token).into(tokens::table).get_result(conn).map_err(Into::into); + insert(&new_token).into(tokens::table).execute(conn)?; - let user = User { - id: 0, - email: None, - gh_id: 0, - gh_login: String::from(""), - name: self.name.map(str::to_string), - gh_avatar: None, - gh_access_token: String::from(""), + if let Err(_) = send_user_confirm_email(user_email, &self.gh_login, &token) { + return Err(Error::NotFound); }; - send_user_confirm_email(user_email, &user, &token); }, Err(Error::NotFound) => { - // Pass, email had conflict, so it already exists in the database - // We don't have to do anything + // This block is reached if a user already has an email stored + // in the database. If an email already exists then we will + // not overwrite it with the one received from GitHub. Doing + // so would force us to resend a verification email each time + // the user logs in, which doesn't make any sense. + // Thus, we don't consider this case to actually be an error, + // so we don't do anything with it, whereas the block below + // will catch all other relevant errors. }, Err(err) => { return Err(err); @@ -420,11 +417,9 @@ pub fn me(req: &mut Request) -> CargoResult { let user_info = users.filter(id.eq(u_id)).first::(&*conn)?; let email_result = emails.filter(user_id.eq(u_id)).first::(&*conn); - println!("GET /me user_info: {:?}", user_info); - println!("GET /me email_result: {:?}", email_result); let (email, verified) : (Option, bool) = match email_result { Ok(response) => (Some(response.email), response.verified), - Err(err) => (None, false) + Err(_) => (None, false) }; let user = User { @@ -604,7 +599,6 @@ pub fn update_user(req: &mut Request) -> CargoResult { .into(emails::table) .get_result(&*conn) .map_err(Into::into); - println!("update_user upsert email: {:?}", email_result); let token = generate_token(); @@ -616,23 +610,21 @@ pub fn update_user(req: &mut Request) -> CargoResult { created_at: time::now_utc().to_timespec(), }; - let token_result : QueryResult = insert(&new_token + insert(&new_token .on_conflict( email_id, do_update().set(&new_token) )) .into(tokens::table) - .get_result(&*conn) - .map_err(Into::into); - println!("update_user upsert token: {:?}", token_result); + .execute(&*conn)?; }, - Err(err) => { + Err(_) => { return Err(human("Error in creating token")); } } - let email_result = send_user_confirm_email(user_email, user, &token); - println!("Email result: {:?}", email_result); + let email_result = send_user_confirm_email(user_email, &user.gh_login, &token); + email_result.map_err(|_| { bad_request("Email could not be sent") })?; #[derive(Serialize)] struct R { @@ -647,7 +639,7 @@ pub struct MailgunConfigVars { pub smtp_server: String, } -fn send_user_confirm_email(email: &str, user: &User, token: &str) -> CargoResult<()> { +fn send_user_confirm_email(email: &str, user_name: &str, token: &str) -> CargoResult<()> { // perhaps use crate lettre and heroku service Mailgun // Create a URL with token string as path to send to user // If user clicks on path, look email/user up in database, @@ -656,12 +648,11 @@ fn send_user_confirm_email(email: &str, user: &User, token: &str) -> CargoResult use dotenv::dotenv; use std::env; use lettre::transport::smtp::{SecurityLevel, SmtpTransportBuilder}; - use lettre::email::{SendableEmail, EmailBuilder}; + use lettre::email::EmailBuilder; use lettre::transport::smtp::authentication::Mechanism; use lettre::transport::smtp::SUBMISSION_PORT; use lettre::transport::EmailTransport; use lettre::transport::file::FileEmailTransport; - use env_logger; use std::path::Path; dotenv().ok(); @@ -678,19 +669,17 @@ fn send_user_confirm_email(email: &str, user: &User, token: &str) -> CargoResult .body(format!("Hello {}! Welcome to Crates.io. Please click the link below to verify your email address. Thank you!\n https://crates-mirror.herokuapp.com/confirm/{}", - user.gh_login, token).as_str()) + user_name, token).as_str()) .build() .expect("Failed to build confirm email message"); if mailgun_config.smtp_login == "Not found" { - println!("Email file generated"); let mut sender = FileEmailTransport::new(Path::new("/tmp")); let result = sender.send(email.clone()); result.map_err(|_| { bad_request("Email file could not be generated") - }); + })?; } else { - println!("Actual email sent, maybe"); let mut transport = SmtpTransportBuilder::new((mailgun_config.smtp_server.as_str(), SUBMISSION_PORT)) .expect("Failed to create message transport") .credentials(&mailgun_config.smtp_login, &mailgun_config.smtp_password) @@ -702,7 +691,7 @@ https://crates-mirror.herokuapp.com/confirm/{}", let result = transport.send(email.clone()); result.map_err(|_| { bad_request("Error in sending email") - }); + })?; } Ok(()) @@ -784,16 +773,16 @@ pub fn regenerate_token_and_send(req: &mut Request) -> CargoResult { created_at: time::now_utc().to_timespec(), }; - let token_result : QueryResult = insert(&new_token + insert(&new_token .on_conflict( email_id, do_update().set(&new_token) )) .into(tokens::table) - .get_result(&*conn) - .map_err(Into::into); + .execute(&*conn)?; - send_user_confirm_email(&email_info.email, user, &token); + let email_result = send_user_confirm_email(&email_info.email, &user.gh_login, &token); + email_result.map_err(|_| { bad_request("Error in sending email") })?; #[derive(Serialize)] struct R { From 4375f2181f322e32ad973c4073d72bcb0cf849f2 Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Tue, 29 Aug 2017 15:31:50 -0400 Subject: [PATCH 36/67] attempt to show error message if email cannot be sent --- app/components/email-input.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/components/email-input.js b/app/components/email-input.js index 7f83cf7ce25..1b590aedac4 100644 --- a/app/components/email-input.js +++ b/app/components/email-input.js @@ -5,6 +5,7 @@ import { inject as service } from '@ember/service'; export default Component.extend({ ajax: service(), + flashMessages: service(), type: '', value: '', @@ -62,6 +63,8 @@ export default Component.extend({ msg = 'An unknown error occurred while saving this email.'; } this.set('serverError', msg); + this.get('flashMessages').queue(`Email error: ${err.errors[0].detail}`); + return this.replaceWith('me'); }); this.set('isEditing', false); @@ -91,9 +94,13 @@ export default Component.extend({ .then(({response}) => {}) .catch((error) => { if (error.payload) { + this.get('flashMessages').queue(`Error in email confirmation: ${error.payload.errors[0].detail}`) console.log("error payload: " + error.payload.errors[0].detail); + return this.replaceWith('me'); } else { + this.get('flashmessages').queue(`Unknown error in email confirmation`); console.log("unknown error"); + return this.replaceWith('me'); } }); } From 499eff29d8827bdc3c293998abe93b49ba825bb1 Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Tue, 29 Aug 2017 16:15:26 -0400 Subject: [PATCH 37/67] error messages for when email can't be sent --- app/components/email-input.js | 10 ++++++---- app/templates/components/email-input.hbs | 3 +++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/app/components/email-input.js b/app/components/email-input.js index 1b590aedac4..1cbe6aa0eb5 100644 --- a/app/components/email-input.js +++ b/app/components/email-input.js @@ -25,6 +25,8 @@ export default Component.extend({ return false; } }), + isError: false, + emailError: '', actions: { editEmail() { @@ -94,13 +96,13 @@ export default Component.extend({ .then(({response}) => {}) .catch((error) => { if (error.payload) { - this.get('flashMessages').queue(`Error in email confirmation: ${error.payload.errors[0].detail}`) + this.set('isError', true); + this.set('emailError', `Error in sending message: ${error.payload.errors[0].detail}`) console.log("error payload: " + error.payload.errors[0].detail); - return this.replaceWith('me'); } else { - this.get('flashmessages').queue(`Unknown error in email confirmation`); + this.set('isError', true); + this.set('emailError', 'Unknown error in resending message'); console.log("unknown error"); - return this.replaceWith('me'); } }); } diff --git a/app/templates/components/email-input.hbs b/app/templates/components/email-input.hbs index 8d66029dfe3..0400d1d787d 100644 --- a/app/templates/components/email-input.hbs +++ b/app/templates/components/email-input.hbs @@ -34,5 +34,8 @@

Your email has not yet been verified.

{{/if}} + {{#if isError}} +

{{emailError}}

+ {{/if}} {{/if}} From 74f7f02fd242d15893962ceebb95ae25b4c33d3b Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Tue, 29 Aug 2017 17:01:40 -0400 Subject: [PATCH 38/67] reminder to change URL from my mirror to crates.io --- src/user/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/user/mod.rs b/src/user/mod.rs index 2e539b5a848..c5a96ac4513 100644 --- a/src/user/mod.rs +++ b/src/user/mod.rs @@ -662,6 +662,8 @@ fn send_user_confirm_email(email: &str, user_name: &str, token: &str) -> CargoRe smtp_server: env::var("MAILGUN_SMTP_SERVER").unwrap_or(String::from("Not found")), }; + // TODO change URL back to crates.io, currently using my + // mirror's URL for testing purposes let email = EmailBuilder::new() .to(email) .from(mailgun_config.smtp_login.as_str()) From 221aa4a0985cce26d36745565f7b95547a5ef1df Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Wed, 30 Aug 2017 11:45:33 -0400 Subject: [PATCH 39/67] error messages for when email can't be sent --- app/components/email-input.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/components/email-input.js b/app/components/email-input.js index 1cbe6aa0eb5..fb6bf78e9a1 100644 --- a/app/components/email-input.js +++ b/app/components/email-input.js @@ -65,8 +65,8 @@ export default Component.extend({ msg = 'An unknown error occurred while saving this email.'; } this.set('serverError', msg); - this.get('flashMessages').queue(`Email error: ${err.errors[0].detail}`); - return this.replaceWith('me'); + this.set('isError', true); + this.set('emailError', `Error in saving email: ${msg}`); }); this.set('isEditing', false); From 3120424bb2faf44ca57fe648649d8cff15afa052 Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Wed, 30 Aug 2017 11:54:18 -0400 Subject: [PATCH 40/67] seems to display message to user when email cannot be sent --- app/components/email-input.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/email-input.js b/app/components/email-input.js index fb6bf78e9a1..3a2c20593d9 100644 --- a/app/components/email-input.js +++ b/app/components/email-input.js @@ -97,7 +97,7 @@ export default Component.extend({ .catch((error) => { if (error.payload) { this.set('isError', true); - this.set('emailError', `Error in sending message: ${error.payload.errors[0].detail}`) + this.set('emailError', `Error in resending message: ${error.payload.errors[0].detail}`) console.log("error payload: " + error.payload.errors[0].detail); } else { this.set('isError', true); From 8350e63f7ccc4972fe8624127231ac8697303949 Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Wed, 30 Aug 2017 12:13:57 -0400 Subject: [PATCH 41/67] css styling for resend email/errors --- app/templates/components/email-input.hbs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/app/templates/components/email-input.hbs b/app/templates/components/email-input.hbs index 0400d1d787d..d8ebf95c6b9 100644 --- a/app/templates/components/email-input.hbs +++ b/app/templates/components/email-input.hbs @@ -31,11 +31,15 @@ {{#if emailNotVerified }} -

Your email has not yet been verified.

- +
+

Your email has not yet been verified.

+ +
{{/if}} {{#if isError}} -

{{emailError}}

+
+

{{emailError}}

+
{{/if}} {{/if}} From eb253d346160474dd9fcab5f627de1a0c13a6913 Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Fri, 1 Sep 2017 16:03:23 -0400 Subject: [PATCH 42/67] moved email info out into separate block and added styling to said separate block css is really fun --- app/styles/me.scss | 37 ++++++++++++++++++++++++ app/templates/components/email-input.hbs | 32 +++++++++++--------- app/templates/me/index.hbs | 6 +++- 3 files changed, 61 insertions(+), 14 deletions(-) diff --git a/app/styles/me.scss b/app/styles/me.scss index 57e21e2a75c..7057400ecb9 100644 --- a/app/styles/me.scss +++ b/app/styles/me.scss @@ -65,6 +65,43 @@ } } +#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; + } + } +} + #me-api { @media only screen and (max-width: 350px) { .api { display: none; } diff --git a/app/templates/components/email-input.hbs b/app/templates/components/email-input.hbs index d8ebf95c6b9..0f2b0010632 100644 --- a/app/templates/components/email-input.hbs +++ b/app/templates/components/email-input.hbs @@ -3,10 +3,12 @@
Email
-
- {{input type=type value=value placeholder='Email' class='form-control space-bottom'}} + + {{input type=type value=value placeholder='Email' class='form-control space-right'}} {{#if notValidEmail }} -

Invalid email format. Please try again.

+
+

Whoops, that email format is invalid

+
{{/if}} {{#if emailIsNull }}

Please add your email address. We will only use @@ -20,7 +22,7 @@

{{else}} -
+
Email
@@ -30,16 +32,20 @@
- {{#if emailNotVerified }} -
+
+ {{#if emailNotVerified }} +
+

Your email has not yet been verified.

-
- {{/if}} - {{#if isError}} -
-

{{emailError}}

+
+
- {{/if}} -
+
+ {{/if}} + {{#if isError}} +
+

{{emailError}}

+
+ {{/if}} {{/if}} diff --git a/app/templates/me/index.hbs b/app/templates/me/index.hbs index 7da0ab915d5..ffe434749d4 100644 --- a/app/templates/me/index.hbs +++ b/app/templates/me/index.hbs @@ -16,11 +16,15 @@
{{ model.user.name }}
GitHub Account
{{ model.user.login }}
- {{email-input type='email' value=model.user.email user=model.user}}
+
+

User Email

+ {{email-input type='email' value=model.user.email user=model.user}} +
+

API Access

From 704a934675f49c1c737f567cbe99b3ca1cd73aa5 Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Fri, 1 Sep 2017 17:03:18 -0400 Subject: [PATCH 43/67] moved please add email message to outside email blocks, if statement broken --- app/styles/me.scss | 6 +++++- app/templates/components/email-input.hbs | 17 +++++++++++------ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/app/styles/me.scss b/app/styles/me.scss index 7057400ecb9..b11ce4e81f9 100644 --- a/app/styles/me.scss +++ b/app/styles/me.scss @@ -94,12 +94,16 @@ } .email-form { display: inline-flex; - } .space-right { margin-right: 10px; } } + .friendly-message { + width: 95%; + margin-left: auto; + margin-right: auto; + } } #me-api { diff --git a/app/templates/components/email-input.hbs b/app/templates/components/email-input.hbs index 0f2b0010632..e064dbbaf2d 100644 --- a/app/templates/components/email-input.hbs +++ b/app/templates/components/email-input.hbs @@ -1,3 +1,11 @@ +{{#if emailIsNull }} +
+

Please add your email address. We will only use + it to contact you about your account. We promise we'll never share it! +

+
+{{/if}} + {{#if isEditing }}
@@ -10,11 +18,6 @@

Whoops, that email format is invalid

{{/if}} - {{#if emailIsNull }} -

Please add your email address. We will only use - it to contact you about your account. We promise we'll never share it! -

- {{/if}}
@@ -45,7 +48,9 @@ {{/if}} {{#if isError}}
-

{{emailError}}

+
+

{{emailError}}

+
{{/if}} {{/if}} From 1f6af9f9656abce4500ae30d14ce468aac92a26a Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Tue, 5 Sep 2017 12:42:01 -0400 Subject: [PATCH 44/67] edited tests for confirming and inserting user email, added test for ensuring email remains consistant on crrates regardless of GitHub changes --- src/tests/user.rs | 88 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 76 insertions(+), 12 deletions(-) diff --git a/src/tests/user.rs b/src/tests/user.rs index 6217fd8026d..94e492dcc49 100644 --- a/src/tests/user.rs +++ b/src/tests/user.rs @@ -512,9 +512,11 @@ fn test_this_user_cannot_change_that_user_email() { } -// Test inserting into the email & token tables -// TODO make test better/actually test what is being put in -// and taken out of the tables +// Given a new user, test that if they sign in with +// one email, change their email on GitHub, then +// sign in again, that the email will remain +// consistant with the original email used on +// GitHub. #[test] fn test_insert_into_email_table() { #[derive(Deserialize)] @@ -528,7 +530,7 @@ fn test_insert_into_email_table() { let conn = app.diesel_database.get().unwrap(); let user = NewUser { gh_id: 1, - email: Some("hi@hello.hey"), + email: Some("potato@example.com"), ..::new_user("potato") }; @@ -538,10 +540,67 @@ fn test_insert_into_email_table() { let mut response = ok_resp!(middle.call(req.with_path("/api/v1/me").with_method(Method::Get))); let r = ::json::(&mut response); - assert_eq!(r.user.email.unwrap(), "hi@hello.hey"); + assert_eq!(r.user.email.unwrap(), "potato@example.com"); assert_eq!(r.user.login, "potato"); - /*let body = r#"{"user":{"email":"apricot@apricots.apricot","name":"Apricot Apricoto","login":"apricot","avatar":"https://avatars0.githubusercontent.com","url":"https://github.com/apricot","kind":null}}"#; + ::logout(&mut req); + + // What if user changes their github user email + { + let conn = app.diesel_database.get().unwrap(); + let user = NewUser { + gh_id: 1, + email: Some("banana@example.com"), + ..::new_user("potato") + }; + + let user = user.create_or_update(&conn).unwrap(); + ::sign_in_as(&mut req, &user); + } + + let mut response = ok_resp!(middle.call(req.with_path("/api/v1/me").with_method(Method::Get))); + let r = ::json::(&mut response); + assert_eq!(r.user.email.unwrap(), "potato@example.com"); + assert_eq!(r.user.login, "potato"); +} + +// Given a new user, check that when an email is added, +// changed by user on GitHub, changed on crates.io, +// that the email remains consistant with that which +// the user has changed +#[test] +fn test_insert_into_email_table_with_email_change() { + #[derive(Deserialize)] + struct R { + user: EncodablePrivateUser, + } + + #[derive(Deserialize)] + struct S { + ok: bool, + } + + let (_b, app, middle) = ::app(); + let mut req = ::req(app.clone(), Method::Get, "/me"); + let user = { + let conn = app.diesel_database.get().unwrap(); + let user = NewUser { + gh_id: 1, + email: Some("potato@example.com"), + ..::new_user("potato") + }; + + let user = user.create_or_update(&conn).unwrap(); + ::sign_in_as(&mut req, &user); + user + }; + + let mut response = ok_resp!(middle.call(req.with_path("/api/v1/me").with_method(Method::Get))); + let r = ::json::(&mut response); + assert_eq!(r.user.email.unwrap(), "potato@example.com"); + assert_eq!(r.user.login, "potato"); + + let body = r#"{"user":{"email":"apricot@apricots.apricot","name":"potato","login":"potato","avatar":"https://avatars0.githubusercontent.com","url":"https://github.com/potato","kind":null}}"#; let mut response = ok_resp!( middle.call( req.with_path(&format!("/api/v1/users/{}", user.id)) @@ -549,15 +608,16 @@ fn test_insert_into_email_table() { .with_body(body.as_bytes()), ) ); - assert!(::json::(&mut response).ok);*/ + assert!(::json::(&mut response).ok); ::logout(&mut req); + // What if user changes their github user email { let conn = app.diesel_database.get().unwrap(); let user = NewUser { gh_id: 1, - email: Some("hi@hello.hey"), + email: Some("banana@example.com"), ..::new_user("potato") }; @@ -567,11 +627,15 @@ fn test_insert_into_email_table() { let mut response = ok_resp!(middle.call(req.with_path("/api/v1/me").with_method(Method::Get))); let r = ::json::(&mut response); - assert_eq!(r.user.email.unwrap(), "hi@hello.hey"); + assert_eq!(r.user.email.unwrap(), "apricot@apricots.apricot"); assert_eq!(r.user.login, "potato"); } -/* Given a new user +/* Given a new user, test that their email can be added + to the email table and a token for the email is generated + and added to the token table. When /confirm/:email_token is + requested, check that the response back is ok, and that + the email_verified field on user is now set to true. */ #[test] fn test_confirm_user_email() { @@ -592,7 +656,7 @@ fn test_confirm_user_email() { let user = { let conn = app.diesel_database.get().unwrap(); let user = NewUser { - email: Some("hi@hello.hey"), + email: Some("potato@example.com"), ..::new_user("potato") }; @@ -622,7 +686,7 @@ fn test_confirm_user_email() { let mut response = ok_resp!(middle.call(req.with_path("/api/v1/me").with_method(Method::Get))); let r = ::json::(&mut response); - assert_eq!(r.user.email.unwrap(), "hi@hello.hey"); + assert_eq!(r.user.email.unwrap(), "potato@example.com"); assert_eq!(r.user.login, "potato"); assert!(r.user.email_verified); } From 59fb74b435e9466bf315d33a62130cddfbd9a0fe Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Tue, 5 Sep 2017 21:18:19 -0400 Subject: [PATCH 45/67] consistent commenting style --- src/tests/user.rs | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/tests/user.rs b/src/tests/user.rs index 94e492dcc49..f7824ba8750 100644 --- a/src/tests/user.rs +++ b/src/tests/user.rs @@ -512,11 +512,12 @@ fn test_this_user_cannot_change_that_user_email() { } -// Given a new user, test that if they sign in with -// one email, change their email on GitHub, then -// sign in again, that the email will remain -// consistant with the original email used on -// GitHub. +/* Given a new user, test that if they sign in with + one email, change their email on GitHub, then + sign in again, that the email will remain + consistent with the original email used on + GitHub. +*/ #[test] fn test_insert_into_email_table() { #[derive(Deserialize)] @@ -564,10 +565,11 @@ fn test_insert_into_email_table() { assert_eq!(r.user.login, "potato"); } -// Given a new user, check that when an email is added, -// changed by user on GitHub, changed on crates.io, -// that the email remains consistant with that which -// the user has changed +/* Given a new user, check that when an email is added, + changed by user on GitHub, changed on crates.io, + that the email remains consistent with that which + the user has changed +*/ #[test] fn test_insert_into_email_table_with_email_change() { #[derive(Deserialize)] From c37290e6fb41989e669decf16027f51c857f8841 Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Tue, 5 Sep 2017 22:00:18 -0400 Subject: [PATCH 46/67] fixing bug where please add email message is always displayed, even when email has been added --- app/components/email-input.js | 5 ++++- app/templates/components/email-input.hbs | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/app/components/email-input.js b/app/components/email-input.js index 3a2c20593d9..7510f30aeb4 100644 --- a/app/components/email-input.js +++ b/app/components/email-input.js @@ -14,7 +14,10 @@ export default Component.extend({ 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'); diff --git a/app/templates/components/email-input.hbs b/app/templates/components/email-input.hbs index e064dbbaf2d..ec8f57da450 100644 --- a/app/templates/components/email-input.hbs +++ b/app/templates/components/email-input.hbs @@ -1,4 +1,5 @@ {{#if emailIsNull }} + {{emailIsNull}}

Please add your email address. We will only use it to contact you about your account. We promise we'll never share it! From b842c9b98a6bf0d81329648a049a01575e09d2d3 Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Tue, 5 Sep 2017 22:09:25 -0400 Subject: [PATCH 47/67] ember linting errors, deletion of debug variable --- app/routes/confirm.js | 6 +++--- app/templates/components/email-input.hbs | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/app/routes/confirm.js b/app/routes/confirm.js index 94d57d6f354..391e1ba484a 100644 --- a/app/routes/confirm.js +++ b/app/routes/confirm.js @@ -6,8 +6,8 @@ export default Ember.Route.extend({ ajax: service(), model(params) { - return this.get('ajax').raw(`/api/v1/confirm/${params.email_token}`, { method: 'PUT', data: {}}) - .then(({response}) => { + 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, @@ -21,7 +21,7 @@ export default Ember.Route.extend({ 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) => { diff --git a/app/templates/components/email-input.hbs b/app/templates/components/email-input.hbs index ec8f57da450..e064dbbaf2d 100644 --- a/app/templates/components/email-input.hbs +++ b/app/templates/components/email-input.hbs @@ -1,5 +1,4 @@ {{#if emailIsNull }} - {{emailIsNull}}

Please add your email address. We will only use it to contact you about your account. We promise we'll never share it! From a9bc633042eb0243c3aa10a6323a5d4a93ab0c55 Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Tue, 5 Sep 2017 22:18:13 -0400 Subject: [PATCH 48/67] Adds lettre crate + updates other things, perhaps i should have just cherrypicked the lettre crate? + there was a merge conflict that i resolved and hope it didn't break anything, something with the libc crate --- Cargo.lock | 153 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 153 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index e7ff161aecb..ad92fe315ae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -73,6 +73,14 @@ dependencies = [ "libc 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "base64" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "base64" version = "0.6.0" @@ -92,6 +100,11 @@ name = "bitflags" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "bufstream" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "byteorder" version = "1.1.0" @@ -141,6 +154,7 @@ dependencies = [ "hyper 0.11.2 (registry+https://github.com/rust-lang/crates.io-index)", "hyper-tls 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "itertools 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lettre 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", "license-exprs 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", "oauth2 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -175,6 +189,15 @@ name = "cfg-if" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "chrono" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "chrono" version = "0.4.0" @@ -538,6 +561,77 @@ name = "either" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "email" +version = "0.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "base64 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "chrono 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "encoding 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "encoding" +version = "0.2.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "encoding-index-japanese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)", + "encoding-index-korean 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)", + "encoding-index-simpchinese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)", + "encoding-index-singlebyte 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)", + "encoding-index-tradchinese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "encoding-index-japanese" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "encoding-index-korean" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "encoding-index-simpchinese" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "encoding-index-singlebyte" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "encoding-index-tradchinese" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "encoding_index_tests" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "entities" version = "1.0.0" @@ -737,6 +831,22 @@ name = "lazycell" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "lettre" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bufstream 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "email 0.0.17 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "mime 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl 0.9.14 (registry+https://github.com/rust-lang/crates.io-index)", + "rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)", + "uuid 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "libc" version = "0.2.29" @@ -851,6 +961,14 @@ dependencies = [ "libc 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "mime" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "mime" version = "0.3.3" @@ -1170,6 +1288,18 @@ name = "route-recognizer" version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "rust-crypto" +version = "0.2.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "gcc 0.3.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rustc-demangle" version = "0.1.4" @@ -1628,6 +1758,14 @@ name = "utf8-ranges" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "uuid" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "vcpkg" version = "0.2.2" @@ -1673,13 +1811,16 @@ dependencies = [ "checksum antidote 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "34fde25430d87a9388dadbe6e34d7f72a462c8b43ac8d309b42b0a8505d7e2a5" "checksum backtrace 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "72f9b4182546f4b04ebc4ab7f84948953a118bd6021a1b6a6c909e3e94f6be76" "checksum backtrace-sys 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "3a0d842ea781ce92be2bf78a9b38883948542749640b8378b3b2f03d1fd9f1ff" +"checksum base64 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "30e93c03064e7590d0466209155251b90c22e37fab1daf2771582598b5827557" "checksum base64 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "96434f987501f0ed4eb336a411e0631ecd1afa11574fe148587adc4ff96143c9" "checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" "checksum bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5" +"checksum bufstream 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "f2f382711e76b9de6c744cc00d0497baba02fb00a787f088c879f01d09468e32" "checksum byteorder 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ff81738b726f5d099632ceaffe7fb65b90212e8dce59d518729e7e8634032d3d" "checksum bytes 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d828f97b58cc5de3e40c421d0cf2132d6b2da4ee0e11b8632fa838f0f9333ad6" "checksum cargo_metadata 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "be1057b8462184f634c3a208ee35b0f935cfd94b694b26deadccd98732088d7b" "checksum cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de" +"checksum chrono 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "158b0bd7d75cbb6bf9c25967a48a2e9f77da95876b858eadfabaa99cd069de6e" "checksum chrono 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7c20ebe0b2b08b0aeddba49c609fe7957ba2e33449882cb186a180bc60682fa9" "checksum civet 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6263e7af767a5bf9e4d3d0a6c3ceb5f3940ec85cf2fbfee59024b8a264be180f" "checksum civet-sys 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "958d15372bf28b7983cb35e1d4bf36dd843b0d42e507c1c73aad7150372c5936" @@ -1718,6 +1859,14 @@ dependencies = [ "checksum dotenv 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d6f0e2bb24d163428d8031d3ebd2d2bd903ad933205a97d0f18c7c1aade380f3" "checksum dtoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "80c8b71fd71146990a9742fc06dcbbde19161a267e0ad4e572c35162f4578c90" "checksum either 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "18785c1ba806c258137c937e44ada9ee7e69a37e3c72077542cd2f069d78562a" +"checksum email 0.0.17 (registry+https://github.com/rust-lang/crates.io-index)" = "88560dfb4e8eb3403e6402dfed790d0ef1c16f6d9f80028243a4f09826772f4f" +"checksum encoding 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "6b0d943856b990d12d3b55b359144ff341533e516d94098b1d3fc1ac666d36ec" +"checksum encoding-index-japanese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)" = "04e8b2ff42e9a05335dbf8b5c6f7567e5591d0d916ccef4e0b1710d32a0d0c91" +"checksum encoding-index-korean 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)" = "4dc33fb8e6bcba213fe2f14275f0963fd16f0a02c878e3095ecfdf5bee529d81" +"checksum encoding-index-simpchinese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d87a7194909b9118fc707194baa434a4e3b0fb6a5a757c73c3adb07aa25031f7" +"checksum encoding-index-singlebyte 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3351d5acffb224af9ca265f435b859c7c01537c0849754d3db3fdf2bfe2ae84a" +"checksum encoding-index-tradchinese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)" = "fd0e20d5688ce3cab59eb3ef3a2083a5c77bf496cb798dc6fcdb75f323890c18" +"checksum encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569" "checksum entities 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "34302e97cd148c302c39cf11f322bcf3412c06caddd2edf9222c22eac7fd63ef" "checksum env_logger 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3ddf21e73e016298f5cb37d6ef8e8da8e39f91f9ec8b0df44b7deb16a9f8cd5b" "checksum error-chain 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d9435d864e017c3c6afeac1654189b06cdb491cf2ff73dbf0d73b0f292f42ff8" @@ -1742,6 +1891,7 @@ dependencies = [ "checksum language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" "checksum lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3b37545ab726dd833ec6420aaba8231c5b320814b9029ad585555d2a03e94fbf" "checksum lazycell 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3b585b7a6811fb03aa10e74b278a0f00f8dd9b45dc681f148bb29fa5cb61859b" +"checksum lettre 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "062777c2e39d4ccf5a1f30bb308d6464341e7587a5e140f79887d522ca906844" "checksum libc 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)" = "8a014d9226c2cc402676fbe9ea2e15dd5222cd1dd57f576b5b283178c944a264" "checksum libgit2-sys 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "df18a822100352d9863b302faf6f8f25c0e77f0e60feb40e5dbe1238b7f13b1d" "checksum libssh2-sys 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0db4ec23611747ef772db1c4d650f8bd762f07b461727ec998f953c614024b75" @@ -1756,6 +1906,7 @@ dependencies = [ "checksum matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "100aabe6b8ff4e4a7e32c1c13523379802df0772b82466207ac25b013f193376" "checksum memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d8b629fb514376c675b98c1421e80b151d3817ac42d7c667717d282761418d20" "checksum memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1dbccc0e46f1ea47b9f17e6d67c5a96bd27030519c519c9c91327e31275a47b4" +"checksum mime 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ba626b8a6de5da682e1caa06bdb42a335aee5a84db8e5046a3e8ab17ba0a3ae0" "checksum mime 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "153f98dde2b135dece079e5478ee400ae1bab13afa52d66590eacfc40e912435" "checksum miniz-sys 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "28eaee17666671fa872e567547e8428e83308ebe5808cdf6a0e28397dbe2c726" "checksum mio 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "dbd91d3bfbceb13897065e97b2ef177a09a438cb33612b2d371bf568819a9313" @@ -1793,6 +1944,7 @@ dependencies = [ "checksum regex-syntax 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ad890a5eef7953f55427c50575c680c42841653abd2b028b68cd223d157f62db" "checksum ring 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1f2a6dc7fc06a05e6de183c5b97058582e9da2de0c136eafe49609769c507724" "checksum route-recognizer 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3255338088df8146ba63d60a9b8e3556f1146ce2973bc05a75181a42ce2256" +"checksum rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)" = "f76d05d3993fd5f4af9434e8e436db163a12a9d40e1a58a726f27a01dfd12a2a" "checksum rustc-demangle 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3058a43ada2c2d0b92b3ae38007a2d0fa5e9db971be260e0171408a4ff471c95" "checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" "checksum rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "c5f5376ea5e30ce23c03eb77cbe4962b988deead10910c372b226388b594c084" @@ -1850,6 +2002,7 @@ dependencies = [ "checksum utf-8 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b6f923c601c7ac48ef1d66f7d5b5b2d9a7ba9c51333ab75a3ddf8d0309185a56" "checksum utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1ca13c08c41c9c3e04224ed9ff80461d97e121589ff27c753a16cb10830ae0f" "checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122" +"checksum uuid 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7cfec50b0842181ba6e713151b72f4ec84a6a7e2c9c8a8a3ffc37bb1cd16b231" "checksum vcpkg 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9e0a7d8bed3178a8fb112199d466eeca9ed09a14ba8ad67718179b4fd5487d0b" "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" From af0b205ead337983da412327ac78dca5f0eceb4c Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Wed, 6 Sep 2017 10:33:04 -0400 Subject: [PATCH 49/67] cargo fmt --- src/tests/user.rs | 28 +++++++--- src/user/mod.rs | 130 +++++++++++++++++++++++----------------------- 2 files changed, 84 insertions(+), 74 deletions(-) diff --git a/src/tests/user.rs b/src/tests/user.rs index f7824ba8750..0ccaa9c44ae 100644 --- a/src/tests/user.rs +++ b/src/tests/user.rs @@ -539,7 +539,9 @@ fn test_insert_into_email_table() { ::sign_in_as(&mut req, &user); } - let mut response = ok_resp!(middle.call(req.with_path("/api/v1/me").with_method(Method::Get))); + let mut response = ok_resp!(middle.call( + req.with_path("/api/v1/me").with_method(Method::Get), + )); let r = ::json::(&mut response); assert_eq!(r.user.email.unwrap(), "potato@example.com"); assert_eq!(r.user.login, "potato"); @@ -559,7 +561,9 @@ fn test_insert_into_email_table() { ::sign_in_as(&mut req, &user); } - let mut response = ok_resp!(middle.call(req.with_path("/api/v1/me").with_method(Method::Get))); + let mut response = ok_resp!(middle.call( + req.with_path("/api/v1/me").with_method(Method::Get), + )); let r = ::json::(&mut response); assert_eq!(r.user.email.unwrap(), "potato@example.com"); assert_eq!(r.user.login, "potato"); @@ -597,7 +601,9 @@ fn test_insert_into_email_table_with_email_change() { user }; - let mut response = ok_resp!(middle.call(req.with_path("/api/v1/me").with_method(Method::Get))); + let mut response = ok_resp!(middle.call( + req.with_path("/api/v1/me").with_method(Method::Get), + )); let r = ::json::(&mut response); assert_eq!(r.user.email.unwrap(), "potato@example.com"); assert_eq!(r.user.login, "potato"); @@ -627,7 +633,9 @@ fn test_insert_into_email_table_with_email_change() { ::sign_in_as(&mut req, &user); } - let mut response = ok_resp!(middle.call(req.with_path("/api/v1/me").with_method(Method::Get))); + let mut response = ok_resp!(middle.call( + req.with_path("/api/v1/me").with_method(Method::Get), + )); let r = ::json::(&mut response); assert_eq!(r.user.email.unwrap(), "apricot@apricots.apricot"); assert_eq!(r.user.login, "potato"); @@ -669,10 +677,12 @@ fn test_confirm_user_email() { let email_token = { let conn = app.diesel_database.get().unwrap(); - let email_info = emails::table.filter(emails::user_id.eq(user.id)) + let email_info = emails::table + .filter(emails::user_id.eq(user.id)) .first::(&*conn) .unwrap(); - let token_info = tokens::table.filter(tokens::email_id.eq(email_info.id)) + let token_info = tokens::table + .filter(tokens::email_id.eq(email_info.id)) .first::(&*conn) .unwrap(); token_info.token @@ -681,12 +691,14 @@ fn test_confirm_user_email() { let mut response = ok_resp!( middle.call( req.with_path(&format!("/api/v1/confirm/{}", email_token)) - .with_method(Method::Put) + .with_method(Method::Put), ) ); assert!(::json::(&mut response).ok); - let mut response = ok_resp!(middle.call(req.with_path("/api/v1/me").with_method(Method::Get))); + let mut response = ok_resp!(middle.call( + req.with_path("/api/v1/me").with_method(Method::Get), + )); let r = ::json::(&mut response); assert_eq!(r.user.email.unwrap(), "potato@example.com"); assert_eq!(r.user.login, "potato"); diff --git a/src/user/mod.rs b/src/user/mod.rs index c5a96ac4513..48aeecc968c 100644 --- a/src/user/mod.rs +++ b/src/user/mod.rs @@ -55,7 +55,7 @@ pub struct Email { } #[derive(Debug, Insertable, AsChangeset)] -#[table_name="emails"] +#[table_name = "emails"] pub struct NewEmail { pub user_id: i32, pub email: String, @@ -72,7 +72,7 @@ pub struct Token { } #[derive(Debug, Insertable, AsChangeset)] -#[table_name="tokens"] +#[table_name = "tokens"] pub struct NewToken { pub email_id: i32, pub token: String, @@ -134,7 +134,11 @@ impl<'a> NewUser<'a> { // To send the user an account verification email... if let Some(user_email) = self.email { - let user_id = users::table.select(users::id).filter(users::gh_id.eq(&self.gh_id)).first(&*conn).unwrap(); + let user_id = users::table + .select(users::id) + .filter(users::gh_id.eq(&self.gh_id)) + .first(&*conn) + .unwrap(); let new_email = NewEmail { user_id: user_id, @@ -142,7 +146,7 @@ impl<'a> NewUser<'a> { verified: false, }; - let email_result : QueryResult = insert(&new_email.on_conflict_do_nothing()) + let email_result: QueryResult = insert(&new_email.on_conflict_do_nothing()) .into(emails::table) .get_result(conn) .map_err(Into::into); @@ -161,7 +165,7 @@ impl<'a> NewUser<'a> { if let Err(_) = send_user_confirm_email(user_email, &self.gh_login, &token) { return Err(Error::NotFound); }; - }, + } Err(Error::NotFound) => { // This block is reached if a user already has an email stored // in the database. If an email already exists then we will @@ -171,7 +175,7 @@ impl<'a> NewUser<'a> { // Thus, we don't consider this case to actually be an error, // so we don't do anything with it, whereas the block below // will catch all other relevant errors. - }, + } Err(err) => { return Err(err); } @@ -235,7 +239,7 @@ impl User { } /// Converts this `User` model into an `EncodablePrivateUser` for JSON serialization. - pub fn encodable_private(self, email_verified : bool) -> EncodablePrivateUser { + pub fn encodable_private(self, email_verified: bool) -> EncodablePrivateUser { let User { id, email, @@ -417,9 +421,9 @@ pub fn me(req: &mut Request) -> CargoResult { let user_info = users.filter(id.eq(u_id)).first::(&*conn)?; let email_result = emails.filter(user_id.eq(u_id)).first::(&*conn); - let (email, verified) : (Option, bool) = match email_result { + let (email, verified): (Option, bool) = match email_result { Ok(response) => (Some(response.email), response.verified), - Err(_) => (None, false) + Err(_) => (None, false), }; let user = User { @@ -584,21 +588,18 @@ pub fn update_user(req: &mut Request) -> CargoResult { update(users.filter(gh_login.eq(&user.gh_login))) .set(email.eq(user_email)) .execute(&*conn)?; - + let new_email = NewEmail { user_id: user.id, email: String::from(user_email), verified: false, }; - let email_result : QueryResult = insert(&new_email - .on_conflict( - user_id, - do_update().set(&new_email) - )) - .into(emails::table) - .get_result(&*conn) - .map_err(Into::into); + let email_result: QueryResult = + insert(&new_email.on_conflict(user_id, do_update().set(&new_email))) + .into(emails::table) + .get_result(&*conn) + .map_err(Into::into); let token = generate_token(); @@ -610,21 +611,21 @@ pub fn update_user(req: &mut Request) -> CargoResult { created_at: time::now_utc().to_timespec(), }; - insert(&new_token - .on_conflict( - email_id, - do_update().set(&new_token) - )) - .into(tokens::table) + insert(&new_token.on_conflict( + email_id, + do_update().set(&new_token), + )).into(tokens::table) .execute(&*conn)?; - }, + } Err(_) => { return Err(human("Error in creating token")); } } let email_result = send_user_confirm_email(user_email, &user.gh_login, &token); - email_result.map_err(|_| { bad_request("Email could not be sent") })?; + email_result.map_err( + |_| bad_request("Email could not be sent"), + )?; #[derive(Serialize)] struct R { @@ -665,25 +666,31 @@ fn send_user_confirm_email(email: &str, user_name: &str, token: &str) -> CargoRe // TODO change URL back to crates.io, currently using my // mirror's URL for testing purposes let email = EmailBuilder::new() - .to(email) - .from(mailgun_config.smtp_login.as_str()) - .subject("Please confirm your email address") - .body(format!("Hello {}! Welcome to Crates.io. Please click the + .to(email) + .from(mailgun_config.smtp_login.as_str()) + .subject("Please confirm your email address") + .body( + format!( + "Hello {}! Welcome to Crates.io. Please click the link below to verify your email address. Thank you!\n https://crates-mirror.herokuapp.com/confirm/{}", - user_name, token).as_str()) - .build() - .expect("Failed to build confirm email message"); + user_name, + token + ).as_str(), + ) + .build() + .expect("Failed to build confirm email message"); if mailgun_config.smtp_login == "Not found" { let mut sender = FileEmailTransport::new(Path::new("/tmp")); let result = sender.send(email.clone()); - result.map_err(|_| { - bad_request("Email file could not be generated") - })?; + result.map_err( + |_| bad_request("Email file could not be generated"), + )?; } else { - let mut transport = SmtpTransportBuilder::new((mailgun_config.smtp_server.as_str(), SUBMISSION_PORT)) - .expect("Failed to create message transport") + let mut transport = SmtpTransportBuilder::new( + (mailgun_config.smtp_server.as_str(), SUBMISSION_PORT), + ).expect("Failed to create message transport") .credentials(&mailgun_config.smtp_login, &mailgun_config.smtp_password) .security_level(SecurityLevel::AlwaysEncrypt) .smtp_utf8(true) @@ -691,9 +698,7 @@ https://crates-mirror.herokuapp.com/confirm/{}", .build(); let result = transport.send(email.clone()); - result.map_err(|_| { - bad_request("Error in sending email") - })?; + result.map_err(|_| bad_request("Error in sending email"))?; } Ok(()) @@ -711,30 +716,24 @@ pub fn confirm_user_email(req: &mut Request) -> CargoResult { let conn = req.db_conn()?; let req_token = &req.params()["email_token"]; - let token_info = tokens::table.filter(tokens::token.eq(req_token)) + let token_info = tokens::table + .filter(tokens::token.eq(req_token)) .first::(&*conn) - .map_err(|_| { - bad_request("Email token not found.") - })?; + .map_err(|_| bad_request("Email token not found."))?; - let email_info = emails::table.filter(emails::id.eq(token_info.email_id)) + let email_info = emails::table + .filter(emails::id.eq(token_info.email_id)) .first::(&*conn) - .map_err(|_| { - bad_request("Email belonging to token not found.") - })?; + .map_err(|_| bad_request("Email belonging to token not found."))?; update(emails::table.filter(emails::id.eq(email_info.id))) .set(emails::verified.eq(true)) .execute(&*conn) - .map_err(|_| { - bad_request("Email verification could not be updated") - })?; + .map_err(|_| bad_request("Email verification could not be updated"))?; delete(tokens::table.filter(tokens::id.eq(token_info.id))) .execute(&*conn) - .map_err(|_| { - bad_request("Email token could not be deleted") - })?; + .map_err(|_| bad_request("Email token could not be deleted"))?; #[derive(Serialize)] struct R { @@ -761,11 +760,10 @@ pub fn regenerate_token_and_send(req: &mut Request) -> CargoResult { return Err(human("current user does not match requested user")); } - let email_info = emails::table.filter(emails::user_id.eq(user.id)) + let email_info = emails::table + .filter(emails::user_id.eq(user.id)) .first::(&*conn) - .map_err(|_| { - bad_request("Email could not be found") - })?; + .map_err(|_| bad_request("Email could not be found"))?; let token = generate_token(); @@ -775,16 +773,16 @@ pub fn regenerate_token_and_send(req: &mut Request) -> CargoResult { created_at: time::now_utc().to_timespec(), }; - insert(&new_token - .on_conflict( - email_id, - do_update().set(&new_token) - )) - .into(tokens::table) + insert(&new_token.on_conflict( + email_id, + do_update().set(&new_token), + )).into(tokens::table) .execute(&*conn)?; let email_result = send_user_confirm_email(&email_info.email, &user.gh_login, &token); - email_result.map_err(|_| { bad_request("Error in sending email") })?; + email_result.map_err( + |_| bad_request("Error in sending email"), + )?; #[derive(Serialize)] struct R { From 5e106353652ef37a35917c914385c9b80c947d39 Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Wed, 6 Sep 2017 10:35:26 -0400 Subject: [PATCH 50/67] change link from my mirror back to crates.io --- src/user/mod.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/user/mod.rs b/src/user/mod.rs index 48aeecc968c..c5728854aff 100644 --- a/src/user/mod.rs +++ b/src/user/mod.rs @@ -663,8 +663,6 @@ fn send_user_confirm_email(email: &str, user_name: &str, token: &str) -> CargoRe smtp_server: env::var("MAILGUN_SMTP_SERVER").unwrap_or(String::from("Not found")), }; - // TODO change URL back to crates.io, currently using my - // mirror's URL for testing purposes let email = EmailBuilder::new() .to(email) .from(mailgun_config.smtp_login.as_str()) @@ -673,7 +671,7 @@ fn send_user_confirm_email(email: &str, user_name: &str, token: &str) -> CargoRe format!( "Hello {}! Welcome to Crates.io. Please click the link below to verify your email address. Thank you!\n -https://crates-mirror.herokuapp.com/confirm/{}", +https://crates.io/confirm/{}", user_name, token ).as_str(), From 458de2790a2516af2e0f0c6033632ca457de60a0 Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Wed, 6 Sep 2017 14:19:25 -0400 Subject: [PATCH 51/67] npm lint errors --- app/components/email-input.js | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/app/components/email-input.js b/app/components/email-input.js index 7510f30aeb4..4a274fc7551 100644 --- a/app/components/email-input.js +++ b/app/components/email-input.js @@ -82,7 +82,6 @@ export default Component.extend({ }, resendEmail() { - let userEmail = this.get('value'); let user = this.get('user'); this.get('ajax').raw(`/api/v1/users/${user.id}/resend`, { method: 'PUT', @@ -95,17 +94,13 @@ export default Component.extend({ name: user.name, url: user.url } - }) - .then(({response}) => {}) - .catch((error) => { + }).catch((error) => { if (error.payload) { this.set('isError', true); - this.set('emailError', `Error in resending message: ${error.payload.errors[0].detail}`) - console.log("error payload: " + error.payload.errors[0].detail); + 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'); - console.log("unknown error"); } }); } From c883f7fa80447cc8b3c916ea0ffbb164bd05ccf9 Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Wed, 6 Sep 2017 15:52:48 -0400 Subject: [PATCH 52/67] nightly/beta errors --- src/lib.rs | 1 - src/user/mod.rs | 8 ++++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 7fbd2ad65a4..a7a711fbe61 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -41,7 +41,6 @@ extern crate time; extern crate toml; extern crate url; extern crate lettre; -extern crate env_logger; extern crate conduit; extern crate conduit_conditional_get; diff --git a/src/user/mod.rs b/src/user/mod.rs index c5728854aff..a227044c27b 100644 --- a/src/user/mod.rs +++ b/src/user/mod.rs @@ -162,7 +162,7 @@ impl<'a> NewUser<'a> { insert(&new_token).into(tokens::table).execute(conn)?; - if let Err(_) = send_user_confirm_email(user_email, &self.gh_login, &token) { + if send_user_confirm_email(user_email, &self.gh_login, &token).is_err() { return Err(Error::NotFound); }; } @@ -658,9 +658,9 @@ fn send_user_confirm_email(email: &str, user_name: &str, token: &str) -> CargoRe dotenv().ok(); let mailgun_config = MailgunConfigVars { - smtp_login: env::var("MAILGUN_SMTP_LOGIN").unwrap_or(String::from("Not found")), - smtp_password: env::var("MAILGUN_SMTP_PASSWORD").unwrap_or(String::from("Not found")), - smtp_server: env::var("MAILGUN_SMTP_SERVER").unwrap_or(String::from("Not found")), + smtp_login: env::var("MAILGUN_SMTP_LOGIN").unwrap_or_else(|_| String::from("Not found")), + smtp_password: env::var("MAILGUN_SMTP_PASSWORD").unwrap_or_else(|_| String::from("Not found")), + smtp_server: env::var("MAILGUN_SMTP_SERVER").unwrap_or_else(|_| String::from("Not found")), }; let email = EmailBuilder::new() From b716033f135a3b97f80710f393eafd61f9817756 Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Fri, 8 Sep 2017 09:50:33 -0400 Subject: [PATCH 53/67] cargo fmt --- src/user/mod.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/user/mod.rs b/src/user/mod.rs index a227044c27b..cc54cf34018 100644 --- a/src/user/mod.rs +++ b/src/user/mod.rs @@ -659,7 +659,9 @@ fn send_user_confirm_email(email: &str, user_name: &str, token: &str) -> CargoRe dotenv().ok(); let mailgun_config = MailgunConfigVars { smtp_login: env::var("MAILGUN_SMTP_LOGIN").unwrap_or_else(|_| String::from("Not found")), - smtp_password: env::var("MAILGUN_SMTP_PASSWORD").unwrap_or_else(|_| String::from("Not found")), + smtp_password: env::var("MAILGUN_SMTP_PASSWORD").unwrap_or_else(|_| { + String::from("Not found") + }), smtp_server: env::var("MAILGUN_SMTP_SERVER").unwrap_or_else(|_| String::from("Not found")), }; From ba0a1233ddf0535ba1166786ccc3590b7074d3de Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Mon, 11 Sep 2017 13:50:23 -0400 Subject: [PATCH 54/67] clippy reference/dereference error --- src/user/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/user/mod.rs b/src/user/mod.rs index cc54cf34018..a4c06d53e1f 100644 --- a/src/user/mod.rs +++ b/src/user/mod.rs @@ -162,7 +162,7 @@ impl<'a> NewUser<'a> { insert(&new_token).into(tokens::table).execute(conn)?; - if send_user_confirm_email(user_email, &self.gh_login, &token).is_err() { + if send_user_confirm_email(user_email, self.gh_login, &token).is_err() { return Err(Error::NotFound); }; } From 1c27fa62fa1a587696665e472610dccea439390d Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Tue, 12 Sep 2017 10:49:18 -0400 Subject: [PATCH 55/67] Add back deleted deny statement, add debug --- src/lib.rs | 1 + src/user/mod.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index a7a711fbe61..372e8b10d62 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,7 @@ //! implemented in the [keyword](keyword/index.html), [krate](krate/index.html), //! [user](user/index.html) and [version](version/index.html) modules. #![deny(warnings)] +#![deny(missing_debug_implementations, missing_copy_implementations)] #![cfg_attr(feature = "clippy", feature(plugin))] #![cfg_attr(feature = "clippy", plugin(clippy))] #![recursion_limit="128"] diff --git a/src/user/mod.rs b/src/user/mod.rs index a4c06d53e1f..c9d1725f9ba 100644 --- a/src/user/mod.rs +++ b/src/user/mod.rs @@ -634,6 +634,7 @@ pub fn update_user(req: &mut Request) -> CargoResult { Ok(req.json(&R { ok: true })) } +#[derive(Debug)] pub struct MailgunConfigVars { pub smtp_login: String, pub smtp_password: String, From d8bf2e62980e1c46e471b3aeacd58040edacef30 Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Tue, 12 Sep 2017 10:56:24 -0400 Subject: [PATCH 56/67] shorten if/else in emailNotVerified --- app/components/email-input.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/app/components/email-input.js b/app/components/email-input.js index 4a274fc7551..7b22c41b0e2 100644 --- a/app/components/email-input.js +++ b/app/components/email-input.js @@ -22,11 +22,7 @@ export default Component.extend({ let email = this.get('user.email'); let verified = this.get('user.email_verified'); - if (email != null && !verified) { - return true; - } else { - return false; - } + return (email != null && !verified); }), isError: false, emailError: '', From 99410d6cbace0821beaa637b80fc85f12ba2e9b0 Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Tue, 12 Sep 2017 15:17:36 -0400 Subject: [PATCH 57/67] separate email logic from user logic, created new src/email.rs file --- src/email.rs | 68 +++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + src/user/mod.rs | 68 +++++-------------------------------------------- 3 files changed, 75 insertions(+), 62 deletions(-) create mode 100644 src/email.rs diff --git a/src/email.rs b/src/email.rs new file mode 100644 index 00000000000..6f7631b1f17 --- /dev/null +++ b/src/email.rs @@ -0,0 +1,68 @@ +use dotenv::dotenv; +use std::env; +use std::path::Path; +use util::{CargoResult, bad_request}; +use lettre::email::{EmailBuilder, Email}; +use lettre::transport::file::FileEmailTransport; +use lettre::transport::EmailTransport; +use lettre::transport::smtp::{SecurityLevel, SmtpTransportBuilder}; +use lettre::transport::smtp::SUBMISSION_PORT; +use lettre::transport::smtp::authentication::Mechanism; + +#[derive(Debug)] +pub struct MailgunConfigVars { + pub smtp_login: String, + pub smtp_password: String, + pub smtp_server: String, +} + +fn init_config_vars() -> MailgunConfigVars { + dotenv().ok(); + + let mailgun_config = MailgunConfigVars { + smtp_login: env::var("MAILGUN_SMTP_LOGIN").unwrap_or_else(|_| String::from("Not Found")), + smtp_password: env::var("MAILGUN_SMTP_PASSWORD").unwrap_or_else(|_| String::from("Not Found")), + smtp_server: env::var("MAILGUN_SMTP_SERVER").unwrap_or_else(|_| String::from("Not Found")), + }; + + mailgun_config +} + +fn build_email(recipient: &str, subject: &str, body: &str, smtp_login: &str) -> Email { + let email = EmailBuilder::new() + .to(recipient) + .from(smtp_login) + .subject(subject) + .body(body) + .build() + .expect("Failed to build confirm email message"); + + email +} + +pub fn send_email(recipient: &str, subject: &str, body: &str) -> CargoResult<()> { + let mailgun_config = init_config_vars(); + let email = build_email(recipient, subject, body, &mailgun_config.smtp_login); + + if mailgun_config.smtp_login == "Not Found" && mailgun_config.smtp_password == "Not Found" && mailgun_config.smtp_server == "Not Found" { + let mut sender = FileEmailTransport::new(Path::new("/tmp")); + let result = sender.send(email.clone()); + result.map_err( + |_| bad_request("Email file could not be generated"), + )?; + } else { + let mut transport = SmtpTransportBuilder::new( + (mailgun_config.smtp_server.as_str(), SUBMISSION_PORT), + ).expect("Failed to create message transport") + .credentials(&mailgun_config.smtp_login, &mailgun_config.smtp_password) + .security_level(SecurityLevel::AlwaysEncrypt) + .smtp_utf8(true) + .authentication_mechanism(Mechanism::Plain) + .build(); + + let result = transport.send(email.clone()); + result.map_err(|_| bad_request("Error in sending email"))?; + } + + Ok(()) +} diff --git a/src/lib.rs b/src/lib.rs index 372e8b10d62..39093310a7c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -96,6 +96,7 @@ pub mod uploaders; pub mod user; pub mod util; pub mod version; +pub mod email; mod local_upload; mod pagination; diff --git a/src/user/mod.rs b/src/user/mod.rs index c9d1725f9ba..b81ee8845c3 100644 --- a/src/user/mod.rs +++ b/src/user/mod.rs @@ -634,75 +634,19 @@ pub fn update_user(req: &mut Request) -> CargoResult { Ok(req.json(&R { ok: true })) } -#[derive(Debug)] -pub struct MailgunConfigVars { - pub smtp_login: String, - pub smtp_password: String, - pub smtp_server: String, -} - fn send_user_confirm_email(email: &str, user_name: &str, token: &str) -> CargoResult<()> { - // perhaps use crate lettre and heroku service Mailgun // Create a URL with token string as path to send to user // If user clicks on path, look email/user up in database, // make sure tokens match + use email; - use dotenv::dotenv; - use std::env; - use lettre::transport::smtp::{SecurityLevel, SmtpTransportBuilder}; - use lettre::email::EmailBuilder; - use lettre::transport::smtp::authentication::Mechanism; - use lettre::transport::smtp::SUBMISSION_PORT; - use lettre::transport::EmailTransport; - use lettre::transport::file::FileEmailTransport; - use std::path::Path; - - dotenv().ok(); - let mailgun_config = MailgunConfigVars { - smtp_login: env::var("MAILGUN_SMTP_LOGIN").unwrap_or_else(|_| String::from("Not found")), - smtp_password: env::var("MAILGUN_SMTP_PASSWORD").unwrap_or_else(|_| { - String::from("Not found") - }), - smtp_server: env::var("MAILGUN_SMTP_SERVER").unwrap_or_else(|_| String::from("Not found")), - }; - - let email = EmailBuilder::new() - .to(email) - .from(mailgun_config.smtp_login.as_str()) - .subject("Please confirm your email address") - .body( - format!( - "Hello {}! Welcome to Crates.io. Please click the + let subject = "Please confirm your email address"; + let body = format!("Hello {}! Welcome to Crates.io. Please click the link below to verify your email address. Thank you!\n -https://crates.io/confirm/{}", - user_name, - token - ).as_str(), - ) - .build() - .expect("Failed to build confirm email message"); - - if mailgun_config.smtp_login == "Not found" { - let mut sender = FileEmailTransport::new(Path::new("/tmp")); - let result = sender.send(email.clone()); - result.map_err( - |_| bad_request("Email file could not be generated"), - )?; - } else { - let mut transport = SmtpTransportBuilder::new( - (mailgun_config.smtp_server.as_str(), SUBMISSION_PORT), - ).expect("Failed to create message transport") - .credentials(&mailgun_config.smtp_login, &mailgun_config.smtp_password) - .security_level(SecurityLevel::AlwaysEncrypt) - .smtp_utf8(true) - .authentication_mechanism(Mechanism::Plain) - .build(); - - let result = transport.send(email.clone()); - result.map_err(|_| bad_request("Error in sending email"))?; - } +https://crates.io/confirm/{}", user_name, token); - Ok(()) + let result = email::send_email(email, subject, &body); + result } /// Handles the `PUT /confirm/:email_token` route From 17a433cc39783be15235f781597ee0075f227926 Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Tue, 12 Sep 2017 15:28:05 -0400 Subject: [PATCH 58/67] add back app/templates/error.hbs --- app/templates/error.hbs | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 app/templates/error.hbs diff --git a/app/templates/error.hbs b/app/templates/error.hbs new file mode 100644 index 00000000000..32e95e8eeff --- /dev/null +++ b/app/templates/error.hbs @@ -0,0 +1,5 @@ +

Something Went Wrong!

+
{{model.message}}
+
+    {{model.stack}}
+
From 2073d260965376865acf7a1ad8d0ceb38310a3e2 Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Tue, 12 Sep 2017 15:42:27 -0400 Subject: [PATCH 59/67] simplify User struct construction as email is the only field being modified --- src/user/mod.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/user/mod.rs b/src/user/mod.rs index b81ee8845c3..a62dada99f4 100644 --- a/src/user/mod.rs +++ b/src/user/mod.rs @@ -427,13 +427,8 @@ pub fn me(req: &mut Request) -> CargoResult { }; let user = User { - id: user_info.id, email: email, - gh_access_token: user_info.gh_access_token, - gh_login: user_info.gh_login, - name: user_info.name, - gh_avatar: user_info.gh_avatar, - gh_id: user_info.gh_id, + ..user_info }; #[derive(Serialize)] From 66bfc449e678903e36300efe6b6c6969b959ea7a Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Tue, 12 Sep 2017 15:49:26 -0400 Subject: [PATCH 60/67] cargo fmt --- src/email.rs | 10 +++++++--- src/user/mod.rs | 10 +++++++--- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/email.rs b/src/email.rs index 6f7631b1f17..be99a728d7c 100644 --- a/src/email.rs +++ b/src/email.rs @@ -21,7 +21,9 @@ fn init_config_vars() -> MailgunConfigVars { let mailgun_config = MailgunConfigVars { smtp_login: env::var("MAILGUN_SMTP_LOGIN").unwrap_or_else(|_| String::from("Not Found")), - smtp_password: env::var("MAILGUN_SMTP_PASSWORD").unwrap_or_else(|_| String::from("Not Found")), + smtp_password: env::var("MAILGUN_SMTP_PASSWORD").unwrap_or_else(|_| { + String::from("Not Found") + }), smtp_server: env::var("MAILGUN_SMTP_SERVER").unwrap_or_else(|_| String::from("Not Found")), }; @@ -44,7 +46,9 @@ pub fn send_email(recipient: &str, subject: &str, body: &str) -> CargoResult<()> let mailgun_config = init_config_vars(); let email = build_email(recipient, subject, body, &mailgun_config.smtp_login); - if mailgun_config.smtp_login == "Not Found" && mailgun_config.smtp_password == "Not Found" && mailgun_config.smtp_server == "Not Found" { + if mailgun_config.smtp_login == "Not Found" && mailgun_config.smtp_password == "Not Found" && + mailgun_config.smtp_server == "Not Found" + { let mut sender = FileEmailTransport::new(Path::new("/tmp")); let result = sender.send(email.clone()); result.map_err( @@ -59,7 +63,7 @@ pub fn send_email(recipient: &str, subject: &str, body: &str) -> CargoResult<()> .smtp_utf8(true) .authentication_mechanism(Mechanism::Plain) .build(); - + let result = transport.send(email.clone()); result.map_err(|_| bad_request("Error in sending email"))?; } diff --git a/src/user/mod.rs b/src/user/mod.rs index a62dada99f4..d148ff75bb0 100644 --- a/src/user/mod.rs +++ b/src/user/mod.rs @@ -17,6 +17,7 @@ use version::EncodableVersion; use {http, Version}; use owner::{Owner, OwnerKind, CrateOwner}; use krate::Crate; +use email; pub use self::middleware::{Middleware, RequestUser, AuthenticationSource}; @@ -633,12 +634,15 @@ fn send_user_confirm_email(email: &str, user_name: &str, token: &str) -> CargoRe // Create a URL with token string as path to send to user // If user clicks on path, look email/user up in database, // make sure tokens match - use email; let subject = "Please confirm your email address"; - let body = format!("Hello {}! Welcome to Crates.io. Please click the + let body = format!( + "Hello {}! Welcome to Crates.io. Please click the link below to verify your email address. Thank you!\n -https://crates.io/confirm/{}", user_name, token); +https://crates.io/confirm/{}", + user_name, + token + ); let result = email::send_email(email, subject, &body); result From 8256def0d93331b3cfc24feeb50941528153481d Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Wed, 13 Sep 2017 14:46:46 -0400 Subject: [PATCH 61/67] Remove 'not found' when getting mailgun env vars, change env var struct to be an Option for dealing with development versus production modes, change build_email to return a CargoResult for better error handling --- src/email.rs | 80 +++++++++++++++++++++++++++++++--------------------- 1 file changed, 48 insertions(+), 32 deletions(-) diff --git a/src/email.rs b/src/email.rs index be99a728d7c..d1b97a7afae 100644 --- a/src/email.rs +++ b/src/email.rs @@ -16,56 +16,72 @@ pub struct MailgunConfigVars { pub smtp_server: String, } -fn init_config_vars() -> MailgunConfigVars { +pub fn init_config_vars() -> Option { dotenv().ok(); - let mailgun_config = MailgunConfigVars { - smtp_login: env::var("MAILGUN_SMTP_LOGIN").unwrap_or_else(|_| String::from("Not Found")), - smtp_password: env::var("MAILGUN_SMTP_PASSWORD").unwrap_or_else(|_| { - String::from("Not Found") - }), - smtp_server: env::var("MAILGUN_SMTP_SERVER").unwrap_or_else(|_| String::from("Not Found")), + let mailgun_config = match ( + env::var("MAILGUN_SMTP_LOGIN"), + env::var("MAILGUN_SMTP_PASSWORD"), + env::var("MAILGUN_SMTP_SERVER"), + ) { + (Ok(login), Ok(password), Ok(server)) => { + Some(MailgunConfigVars { + smtp_login: login, + smtp_password: password, + smtp_server: server, + }) + } + _ => None, }; mailgun_config } -fn build_email(recipient: &str, subject: &str, body: &str, smtp_login: &str) -> Email { +pub fn build_email( + recipient: &str, + subject: &str, + body: &str, + mailgun_config: &Option, +) -> CargoResult { + let sender = mailgun_config + .as_ref() + .map(|s| s.smtp_login.as_str()) + .unwrap_or("Development Mode"); + let email = EmailBuilder::new() .to(recipient) - .from(smtp_login) + .from(sender) .subject(subject) .body(body) - .build() - .expect("Failed to build confirm email message"); + .build()?; - email + Ok(email) } pub fn send_email(recipient: &str, subject: &str, body: &str) -> CargoResult<()> { let mailgun_config = init_config_vars(); - let email = build_email(recipient, subject, body, &mailgun_config.smtp_login); + let email = build_email(recipient, subject, body, &mailgun_config)?; - if mailgun_config.smtp_login == "Not Found" && mailgun_config.smtp_password == "Not Found" && - mailgun_config.smtp_server == "Not Found" - { - let mut sender = FileEmailTransport::new(Path::new("/tmp")); - let result = sender.send(email.clone()); - result.map_err( - |_| bad_request("Email file could not be generated"), - )?; - } else { - let mut transport = SmtpTransportBuilder::new( - (mailgun_config.smtp_server.as_str(), SUBMISSION_PORT), - ).expect("Failed to create message transport") - .credentials(&mailgun_config.smtp_login, &mailgun_config.smtp_password) - .security_level(SecurityLevel::AlwaysEncrypt) - .smtp_utf8(true) - .authentication_mechanism(Mechanism::Plain) - .build(); + match mailgun_config { + Some(mailgun_config) => { + let mut transport = + SmtpTransportBuilder::new((mailgun_config.smtp_server.as_str(), SUBMISSION_PORT))? + .credentials(&mailgun_config.smtp_login, &mailgun_config.smtp_password) + .security_level(SecurityLevel::AlwaysEncrypt) + .smtp_utf8(true) + .authentication_mechanism(Mechanism::Plain) + .build(); - let result = transport.send(email.clone()); - result.map_err(|_| bad_request("Error in sending email"))?; + let result = transport.send(email.clone()); + result.map_err(|_| bad_request("Error in sending email"))?; + } + None => { + let mut sender = FileEmailTransport::new(Path::new("/tmp")); + let result = sender.send(email.clone()); + result.map_err( + |_| bad_request("Email file could not be generated"), + )?; + } } Ok(()) From 25c3cf6f65da7926f6db74e6c4a1d1ca22328cea Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Wed, 13 Sep 2017 15:17:42 -0400 Subject: [PATCH 62/67] add category link back to doc comment --- src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 39093310a7c..442ed59d7ec 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,8 @@ //! This crate implements the backend server for https://crates.io/ //! //! All implemented routes are defined in the [middleware](fn.middleware.html) function and -//! implemented in the [keyword](keyword/index.html), [krate](krate/index.html), -//! [user](user/index.html) and [version](version/index.html) modules. +//! implemented in the [category](category/index.html), [keyword](keyword/index.html), +//! [krate](krate/index.html), [user](user/index.html) and [version](version/index.html) modules. #![deny(warnings)] #![deny(missing_debug_implementations, missing_copy_implementations)] #![cfg_attr(feature = "clippy", feature(plugin))] From ce858f885886f50f46fd60beff4125703539ee06 Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Wed, 13 Sep 2017 15:18:04 -0400 Subject: [PATCH 63/67] fix clippy errors --- src/email.rs | 6 ++---- src/user/mod.rs | 3 +-- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/email.rs b/src/email.rs index d1b97a7afae..38fbff23141 100644 --- a/src/email.rs +++ b/src/email.rs @@ -19,7 +19,7 @@ pub struct MailgunConfigVars { pub fn init_config_vars() -> Option { dotenv().ok(); - let mailgun_config = match ( + match ( env::var("MAILGUN_SMTP_LOGIN"), env::var("MAILGUN_SMTP_PASSWORD"), env::var("MAILGUN_SMTP_SERVER"), @@ -32,9 +32,7 @@ pub fn init_config_vars() -> Option { }) } _ => None, - }; - - mailgun_config + } } pub fn build_email( diff --git a/src/user/mod.rs b/src/user/mod.rs index d148ff75bb0..307ac007b21 100644 --- a/src/user/mod.rs +++ b/src/user/mod.rs @@ -644,8 +644,7 @@ https://crates.io/confirm/{}", token ); - let result = email::send_email(email, subject, &body); - result + email::send_email(email, subject, &body) } /// Handles the `PUT /confirm/:email_token` route From af3a66db9f9aded1d74761048aa889d3abdb601d Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Thu, 14 Sep 2017 11:51:20 -0400 Subject: [PATCH 64/67] mention how to set up email in contributing docs --- docs/CONTRIBUTING.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 174234b390c..2b91a10e93b 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -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 From 5a3a2b18d2cde4fc679c6f44fa07c7f124538060 Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Thu, 14 Sep 2017 11:51:59 -0400 Subject: [PATCH 65/67] Add Mailgun configuration variables to .env + explaination --- .env.sample | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.env.sample b/.env.sample index bb285f3d066..e638e6ecb8a 100644 --- a/.env.sample +++ b/.env.sample @@ -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= From 3530771e441b47c484582f3321fdb69426c85dcc Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Thu, 14 Sep 2017 14:15:39 -0400 Subject: [PATCH 66/67] prevent null email from being added to emails table --- migrations/20170804200817_add_email_table/up.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/migrations/20170804200817_add_email_table/up.sql b/migrations/20170804200817_add_email_table/up.sql index 4a0dc37161b..d1402e4f95b 100644 --- a/migrations/20170804200817_add_email_table/up.sql +++ b/migrations/20170804200817_add_email_table/up.sql @@ -14,4 +14,4 @@ CREATE table tokens ( ); INSERT INTO emails (user_id, email) - SELECT id, email FROM users; + SELECT id, email FROM users WHERE email IS NOT NULL; From 26254f4f8b61dc955f6f8e1cd5d347f8f4a5a5fe Mon Sep 17 00:00:00 2001 From: Natalie Boehm Date: Fri, 15 Sep 2017 10:36:32 -0400 Subject: [PATCH 67/67] add http data files for email tests --- src/tests/http-data/user_test_confirm_user_email | 1 + src/tests/http-data/user_test_insert_into_email_table | 1 + .../user_test_insert_into_email_table_with_email_change | 1 + 3 files changed, 3 insertions(+) create mode 100644 src/tests/http-data/user_test_confirm_user_email create mode 100644 src/tests/http-data/user_test_insert_into_email_table create mode 100644 src/tests/http-data/user_test_insert_into_email_table_with_email_change diff --git a/src/tests/http-data/user_test_confirm_user_email b/src/tests/http-data/user_test_confirm_user_email new file mode 100644 index 00000000000..fe51488c706 --- /dev/null +++ b/src/tests/http-data/user_test_confirm_user_email @@ -0,0 +1 @@ +[] diff --git a/src/tests/http-data/user_test_insert_into_email_table b/src/tests/http-data/user_test_insert_into_email_table new file mode 100644 index 00000000000..fe51488c706 --- /dev/null +++ b/src/tests/http-data/user_test_insert_into_email_table @@ -0,0 +1 @@ +[] diff --git a/src/tests/http-data/user_test_insert_into_email_table_with_email_change b/src/tests/http-data/user_test_insert_into_email_table_with_email_change new file mode 100644 index 00000000000..fe51488c706 --- /dev/null +++ b/src/tests/http-data/user_test_insert_into_email_table_with_email_change @@ -0,0 +1 @@ +[]