Skip to content

Drop Email column (merge and deploy the fix for #1888 first!) #1891

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Dec 2, 2019
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion migrations/20140924113530_dumped_migration_1/up.sql
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ CREATE TABLE users (
email VARCHAR NOT NULL UNIQUE,
gh_access_token VARCHAR NOT NULL,
api_token VARCHAR NOT NULL
);
);

2 changes: 1 addition & 1 deletion migrations/20161115111957_dumped_migration_119/up.sql
Original file line number Diff line number Diff line change
@@ -1 +1 @@
CREATE FUNCTION update_categories_crates_cnt() RETURNS trigger AS $$ BEGIN IF (TG_OP = 'INSERT') THEN UPDATE categories SET crates_cnt = crates_cnt + 1 WHERE id = NEW.category_id; return NEW; ELSIF (TG_OP = 'DELETE') THEN UPDATE categories SET crates_cnt = crates_cnt - 1 WHERE id = OLD.category_id; return OLD; END IF; END $$ LANGUAGE plpgsql; CREATE TRIGGER trigger_update_categories_crates_cnt BEFORE INSERT OR DELETE ON crates_categories FOR EACH ROW EXECUTE PROCEDURE update_categories_crates_cnt(); CREATE TRIGGER touch_crate_on_modify_categories AFTER INSERT OR DELETE ON crates_categories FOR EACH ROW EXECUTE PROCEDURE touch_crate();
CREATE FUNCTION update_categories_crates_cnt() RETURNS trigger AS $$ BEGIN IF (TG_OP = 'INSERT') THEN UPDATE categories SET crates_cnt = crates_cnt + 1 WHERE id = NEW.category_id; return NEW; ELSIF (TG_OP = 'DELETE') THEN UPDATE categories SET crates_cnt = crates_cnt - 1 WHERE id = OLD.category_id; return OLD; END IF; END $$ LANGUAGE plpgsql; CREATE TRIGGER trigger_update_categories_crates_cnt BEFORE INSERT OR DELETE ON crates_categories FOR EACH ROW EXECUTE PROCEDURE update_categories_crates_cnt(); CREATE TRIGGER touch_crate_on_modify_categories AFTER INSERT OR DELETE ON crates_categories FOR EACH ROW EXECUTE PROCEDURE touch_crate();
1 change: 1 addition & 0 deletions migrations/2019-11-11-162609_drop_email_from_user/down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
-- Not reversible
1 change: 1 addition & 0 deletions migrations/2019-11-11-162609_drop_email_from_user/up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE users DROP COLUMN email CASCADE;
10 changes: 2 additions & 8 deletions src/controllers/user/me.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,9 @@ pub fn me(req: &mut dyn Request) -> CargoResult<Response> {

let verified = verified.unwrap_or(false);
let verification_sent = verified || verification_sent;
let user = User { email, ..user };

Ok(req.json(&EncodableMe {
user: user.encodable_private(verified, verification_sent),
user: user.encodable_private(email, verified, verification_sent),
}))
}

Expand Down Expand Up @@ -91,8 +90,7 @@ pub fn updates(req: &mut dyn Request) -> CargoResult<Response> {
/// Handles the `PUT /user/:user_id` route.
pub fn update_user(req: &mut dyn Request) -> CargoResult<Response> {
use self::emails::user_id;
use self::users::dsl::{email, gh_login, users};
use diesel::{insert_into, update};
use diesel::insert_into;

let mut body = String::new();
req.body().read_to_string(&mut body)?;
Expand Down Expand Up @@ -130,10 +128,6 @@ pub fn update_user(req: &mut dyn Request) -> CargoResult<Response> {
}

conn.transaction::<_, Box<dyn CargoError>, _>(|| {
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: user_email,
Expand Down
3 changes: 1 addition & 2 deletions src/controllers/user/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,12 +118,11 @@ impl GithubUser {
NewUser::new(
self.id,
&self.login,
self.email.as_ref().map(|s| &s[..]),
self.name.as_ref().map(|s| &s[..]),
self.avatar_url.as_ref().map(|s| &s[..]),
access_token,
)
.create_or_update(conn)
.create_or_update(self.email.as_ref().map(|s| &s[..]), conn)
.map_err(Into::into)
.or_else(|e: Box<dyn CargoError>| {
// If we're in read only mode, we can't update their details
Expand Down
26 changes: 19 additions & 7 deletions src/models/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,19 @@ use crate::views::{EncodablePrivateUser, EncodablePublicUser};
#[derive(Clone, Debug, PartialEq, Eq, Queryable, Identifiable, AsChangeset, Associations)]
pub struct User {
pub id: i32,
pub email: Option<String>,
pub gh_access_token: String,
pub gh_login: String,
pub name: Option<String>,
pub gh_avatar: Option<String>,
pub gh_id: i32,
}

/// Represents a new user record insertable to the `users` table
#[derive(Insertable, Debug, Default)]
#[table_name = "users"]
pub struct NewUser<'a> {
pub gh_id: i32,
pub gh_login: &'a str,
pub email: Option<&'a str>,
pub name: Option<&'a str>,
pub gh_avatar: Option<&'a str>,
pub gh_access_token: Cow<'a, str>,
Expand All @@ -36,23 +35,25 @@ impl<'a> NewUser<'a> {
pub fn new(
gh_id: i32,
gh_login: &'a str,
email: Option<&'a str>,
name: Option<&'a str>,
gh_avatar: Option<&'a str>,
gh_access_token: &'a str,
) -> Self {
NewUser {
gh_id,
gh_login,
email,
name,
gh_avatar,
gh_access_token: Cow::Borrowed(gh_access_token),
}
}

/// Inserts the user into the database, or updates an existing one.
pub fn create_or_update(&self, conn: &PgConnection) -> QueryResult<User> {
pub fn create_or_update(
&self,
email: Option<&'a str>,
conn: &PgConnection,
) -> QueryResult<User> {
use crate::schema::users::dsl::*;
use diesel::dsl::sql;
use diesel::insert_into;
Expand Down Expand Up @@ -81,7 +82,7 @@ impl<'a> NewUser<'a> {
.get_result::<User>(conn)?;

// To send the user an account verification email...
if let Some(user_email) = user.email.as_ref() {
if let Some(user_email) = email {
let new_email = NewEmail {
user_id: user.id,
email: user_email,
Expand Down Expand Up @@ -168,6 +169,8 @@ impl User {
Ok(best)
}

/// Queries the database for the verified emails
/// belonging to a given user
pub fn verified_email(&self, conn: &PgConnection) -> CargoResult<Option<String>> {
Ok(Email::belonging_to(self)
.select(emails::email)
Expand All @@ -179,18 +182,19 @@ impl User {
/// Converts this `User` model into an `EncodablePrivateUser` for JSON serialization.
pub fn encodable_private(
self,
email: Option<String>,
email_verified: bool,
email_verification_sent: bool,
) -> EncodablePrivateUser {
let User {
id,
email,
name,
gh_login,
gh_avatar,
..
} = self;
let url = format!("https://github.com/{}", gh_login);

EncodablePrivateUser {
id,
email,
Expand All @@ -203,6 +207,14 @@ impl User {
}
}

/// Queries for the email belonging to a particular user
pub fn email(&self, conn: &PgConnection) -> CargoResult<Option<String>> {
Ok(Email::belonging_to(self)
.select(emails::email)
.first::<String>(&*conn)
.optional()?)
}

/// Converts this`User` model into an `EncodablePublicUser` for JSON serialization.
pub fn encodable_public(self) -> EncodablePublicUser {
let User {
Expand Down
2 changes: 1 addition & 1 deletion src/publish_rate_limit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ mod tests {
gh_login,
..NewUser::default()
}
.create_or_update(conn)?;
.create_or_update(None, conn)?;
Ok(user.id)
}

Expand Down
6 changes: 0 additions & 6 deletions src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -745,12 +745,6 @@ table! {
///
/// (Automatically generated by Diesel.)
id -> Int4,
/// The `email` column of the `users` table.
///
/// Its SQL type is `Nullable<Varchar>`.
///
/// (Automatically generated by Diesel.)
email -> Nullable<Varchar>,
/// The `gh_access_token` column of the `users` table.
///
/// Its SQL type is `Varchar`.
Expand Down
1 change: 0 additions & 1 deletion src/tasks/dump_db/dump-db.toml
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,6 @@ id in (
)"""
[users.columns]
id = "public"
email = "private"
gh_access_token = "private"
gh_login = "public"
name = "public"
Expand Down
4 changes: 2 additions & 2 deletions src/tasks/update_downloads.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,8 @@ mod test {
}

fn user(conn: &PgConnection) -> User {
NewUser::new(2, "login", None, None, None, "access_token")
.create_or_update(conn)
NewUser::new(2, "login", None, None, "access_token")
.create_or_update(None, conn)
.unwrap()
}

Expand Down
1 change: 0 additions & 1 deletion src/tests/all.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,6 @@ fn new_user(login: &str) -> NewUser<'_> {
NewUser {
gh_id: NEXT_GH_ID.fetch_add(1, Ordering::SeqCst) as i32,
gh_login: login,
email: None,
name: None,
gh_avatar: None,
gh_access_token: Cow::Borrowed("some random token"),
Expand Down
2 changes: 1 addition & 1 deletion src/tests/krate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ fn index() {
assert_eq!(json.meta.total, 0);

let krate = app.db(|conn| {
let u = new_user("foo").create_or_update(conn).unwrap();
let u = new_user("foo").create_or_update(None, conn).unwrap();
CrateBuilder::new("fooindex", u.id).expect_build(conn)
});

Expand Down
2 changes: 1 addition & 1 deletion src/tests/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ fn token_gives_access_to_me() {
anon.get(url).assert_forbidden();

let json: UserShowPrivateResponse = token.get(url).good();
assert_eq!(json.user.email, user.as_model().email);
assert_eq!(json.user.name, user.as_model().name);
}

#[test]
Expand Down
39 changes: 22 additions & 17 deletions src/tests/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ fn me() {
let user = app.db_new_user("foo");
let json = user.show_me();

assert_eq!(json.user.email, user.as_model().email);
assert_eq!(json.user.name, user.as_model().name);
}

#[test]
Expand Down Expand Up @@ -141,21 +141,19 @@ fn show_latest_user_case_insensitively() {
t!(NewUser::new(
1,
"foobar",
Some("[email protected]"),
Some("I was first then deleted my github account"),
None,
"bar"
)
.create_or_update(conn));
.create_or_update(None, conn));
t!(NewUser::new(
2,
"FOOBAR",
Some("[email protected]"),
Some("I was second, I took the foobar username on github"),
None,
"bar"
)
.create_or_update(conn));
.create_or_update(None, conn));
});

let json: UserShowPublicResponse = anon.get("api/v1/users/fOObAr").good();
Expand Down Expand Up @@ -324,7 +322,7 @@ fn updating_existing_user_doesnt_change_api_token() {

let user = app.db(|conn| {
// Reuse gh_id but use new gh_login and gh_access_token
t!(NewUser::new(gh_id, "bar", None, None, None, "bar_token").create_or_update(conn));
t!(NewUser::new(gh_id, "bar", None, None, "bar_token").create_or_update(None, conn));

// Use the original API token to find the now updated user
t!(User::find_by_api_token(conn, token))
Expand Down Expand Up @@ -353,7 +351,7 @@ fn github_without_email_does_not_overwrite_email() {
// Don't use app.db_new_user because it adds a verified email.
let user_without_github_email = app.db(|conn| {
let u = new_user("arbitrary_username");
let u = u.create_or_update(conn).unwrap();
let u = u.create_or_update(None, conn).unwrap();
MockCookieUser::new(&app, u)
});
let user_without_github_email_model = user_without_github_email.as_model();
Expand All @@ -373,7 +371,7 @@ fn github_without_email_does_not_overwrite_email() {
// new_user uses a None email; the rest of the fields are arbitrary
..new_user("arbitrary_username")
};
let u = u.create_or_update(conn).unwrap();
let u = u.create_or_update(None, conn).unwrap();
MockCookieUser::new(&app, u)
});

Expand All @@ -386,9 +384,16 @@ fn github_without_email_does_not_overwrite_email() {
*/
#[test]
fn github_with_email_does_not_overwrite_email() {
use cargo_registry::schema::emails;

let (app, _, user) = TestApp::init().with_user();
let model = user.as_model();
let original_email = &model.email;
let original_email = app.db(|conn| {
Email::belonging_to(model)
.select(emails::email)
.first::<String>(&*conn)
.unwrap()
});

let new_github_email = "[email protected]";

Expand All @@ -397,16 +402,15 @@ fn github_with_email_does_not_overwrite_email() {
let u = NewUser {
// Use the same github ID to link to the existing account
gh_id: model.gh_id,
email: Some(new_github_email),
// the rest of the fields are arbitrary
..new_user("arbitrary_username")
};
let u = u.create_or_update(conn).unwrap();
let u = u.create_or_update(Some(new_github_email), conn).unwrap();
MockCookieUser::new(&app, u)
});

let json = user_with_different_email_in_github.show_me();
assert_eq!(json.user.email, *original_email);
assert_eq!(json.user.email, Some(original_email));
}

/* Given a crates.io user, check that the user's email can be
Expand Down Expand Up @@ -510,12 +514,13 @@ fn test_confirm_user_email() {

// Simulate logging in via GitHub. Don't use app.db_new_user because it inserts a verified
// email directly into the database and we want to test the verification flow here.
let email = "cow@mammals@milk";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm curious, why is the value different here? Any particular reason, and any reason why it's not a valid email address (two @s)?


let user = app.db(|conn| {
let u = NewUser {
email: Some("[email protected]"),
..new_user("arbitrary_username")
};
let u = u.create_or_update(conn).unwrap();
let u = u.create_or_update(Some(email), conn).unwrap();
MockCookieUser::new(&app, u)
});
let user_model = user.as_model();
Expand All @@ -530,7 +535,7 @@ fn test_confirm_user_email() {
user.confirm_email(&email_token);

let json = user.show_me();
assert_eq!(json.user.email.unwrap(), "[email protected]");
assert_eq!(json.user.email.unwrap(), "cow@mammals@milk");
assert!(json.user.email_verified);
assert!(json.user.email_verification_sent);
}
Expand All @@ -549,12 +554,12 @@ fn test_existing_user_email() {

// Simulate logging in via GitHub. Don't use app.db_new_user because it inserts a verified
// email directly into the database and we want to test the verification flow here.
let email = "[email protected]";
let user = app.db(|conn| {
let u = NewUser {
email: Some("[email protected]"),
..new_user("arbitrary_username")
};
let u = u.create_or_update(conn).unwrap();
let u = u.create_or_update(Some(email), conn).unwrap();
update(Email::belonging_to(&u))
// Users created before we added verification will have
// `NULL` in the `token_generated_at` column.
Expand Down
6 changes: 4 additions & 2 deletions src/tests/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,9 +135,11 @@ impl TestApp {
use diesel::prelude::*;

let user = self.db(|conn| {
let mut user = crate::new_user(username).create_or_update(conn).unwrap();
let email = "[email protected]";
user.email = Some(email.to_string());

let user = crate::new_user(username)
.create_or_update(None, conn)
.unwrap();
diesel::insert_into(emails::table)
.values((
emails::user_id.eq(user.id),
Expand Down
2 changes: 1 addition & 1 deletion src/views.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,10 +161,10 @@ pub struct EncodableMe {
pub struct EncodablePrivateUser {
pub id: i32,
pub login: String,
pub email: Option<String>,
pub email_verified: bool,
pub email_verification_sent: bool,
pub name: Option<String>,
pub email: Option<String>,
pub avatar: Option<String>,
pub url: Option<String>,
}
Expand Down