Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
37 changes: 21 additions & 16 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 = "[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();
MockCookieUser::new(&app, u)
});
let user_model = user.as_model();
Expand Down Expand Up @@ -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