implement server-side scrypt to protect passwords better
This commit is contained in:
parent
3d7f080751
commit
b10a50be08
|
@ -51,6 +51,33 @@ version = "1.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "block-buffer"
|
||||||
|
version = "0.7.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b"
|
||||||
|
dependencies = [
|
||||||
|
"block-padding",
|
||||||
|
"byte-tools",
|
||||||
|
"byteorder",
|
||||||
|
"generic-array",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "block-padding"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5"
|
||||||
|
dependencies = [
|
||||||
|
"byte-tools",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "byte-tools"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "byteorder"
|
name = "byteorder"
|
||||||
version = "1.3.4"
|
version = "1.3.4"
|
||||||
|
@ -90,6 +117,16 @@ dependencies = [
|
||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crypto-mac"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
"subtle",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "devise"
|
name = "devise"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
|
@ -155,6 +192,15 @@ dependencies = [
|
||||||
"migrations_macros",
|
"migrations_macros",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "digest"
|
||||||
|
version = "0.8.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dotenv"
|
name = "dotenv"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
|
@ -164,6 +210,12 @@ dependencies = [
|
||||||
"regex",
|
"regex",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fake-simd"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "filetime"
|
name = "filetime"
|
||||||
version = "0.2.8"
|
version = "0.2.8"
|
||||||
|
@ -223,6 +275,15 @@ version = "0.3.55"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2"
|
checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "generic-array"
|
||||||
|
version = "0.12.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec"
|
||||||
|
dependencies = [
|
||||||
|
"typenum",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hermit-abi"
|
name = "hermit-abi"
|
||||||
version = "0.1.7"
|
version = "0.1.7"
|
||||||
|
@ -232,6 +293,16 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hmac"
|
||||||
|
version = "0.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5dcb5e64cda4c23119ab41ba960d1e170a774c8e4b9d9e6a9bc18aabf5e59695"
|
||||||
|
dependencies = [
|
||||||
|
"crypto-mac",
|
||||||
|
"digest",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "httparse"
|
name = "httparse"
|
||||||
version = "1.3.4"
|
version = "1.3.4"
|
||||||
|
@ -523,6 +594,12 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "opaque-debug"
|
||||||
|
version = "0.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parking_lot"
|
name = "parking_lot"
|
||||||
version = "0.10.0"
|
version = "0.10.0"
|
||||||
|
@ -547,6 +624,16 @@ dependencies = [
|
||||||
"winapi 0.3.8",
|
"winapi 0.3.8",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pbkdf2"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "006c038a43a45995a9670da19e67600114740e8511d4333bf97a56e66a7542d9"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
"crypto-mac",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pear"
|
name = "pear"
|
||||||
version = "0.1.2"
|
version = "0.1.2"
|
||||||
|
@ -651,6 +738,19 @@ dependencies = [
|
||||||
"winapi 0.3.8",
|
"winapi 0.3.8",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand"
|
||||||
|
version = "0.5.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c618c47cd3ebd209790115ab837de41425723956ad3ce2e6a7f09890947cacb9"
|
||||||
|
dependencies = [
|
||||||
|
"cloudabi",
|
||||||
|
"fuchsia-cprng",
|
||||||
|
"libc",
|
||||||
|
"rand_core 0.3.1",
|
||||||
|
"winapi 0.3.8",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand_core"
|
name = "rand_core"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
|
@ -850,6 +950,22 @@ version = "1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "scrypt"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "656c79d0e90d0ab28ac86bf3c3d10bfbbac91450d3f190113b4e76d9fec3cfdd"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.9.3",
|
||||||
|
"byte-tools",
|
||||||
|
"byteorder",
|
||||||
|
"hmac",
|
||||||
|
"pbkdf2",
|
||||||
|
"rand 0.5.6",
|
||||||
|
"sha2",
|
||||||
|
"subtle",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.104"
|
version = "1.0.104"
|
||||||
|
@ -892,9 +1008,22 @@ dependencies = [
|
||||||
"rocket",
|
"rocket",
|
||||||
"rocket_contrib",
|
"rocket_contrib",
|
||||||
"rust-crypto",
|
"rust-crypto",
|
||||||
|
"scrypt",
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sha2"
|
||||||
|
version = "0.8.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "27044adfd2e1f077f649f59deb9490d3941d674002f7d062870a60ebe9bd47a0"
|
||||||
|
dependencies = [
|
||||||
|
"block-buffer",
|
||||||
|
"digest",
|
||||||
|
"fake-simd",
|
||||||
|
"opaque-debug",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "slab"
|
name = "slab"
|
||||||
version = "0.4.2"
|
version = "0.4.2"
|
||||||
|
@ -922,6 +1051,12 @@ version = "0.4.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7345c971d1ef21ffdbd103a75990a15eb03604fc8b8852ca8cb418ee1a099028"
|
checksum = "7345c971d1ef21ffdbd103a75990a15eb03604fc8b8852ca8cb418ee1a099028"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "subtle"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "0.15.44"
|
version = "0.15.44"
|
||||||
|
@ -985,6 +1120,12 @@ version = "0.1.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1410f6f91f21d1612654e7cc69193b0334f909dcf2c790c4826254fbb86f8887"
|
checksum = "1410f6f91f21d1612654e7cc69193b0334f909dcf2c790c4826254fbb86f8887"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typenum"
|
||||||
|
version = "1.11.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6d2783fe2d6b8c1101136184eb41be8b1ad379e4657050b8aaff0c79ee7575f9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ucd-util"
|
name = "ucd-util"
|
||||||
version = "0.1.7"
|
version = "0.1.7"
|
||||||
|
|
|
@ -12,4 +12,5 @@ diesel = { version = "1.4.3", features = ["sqlite"] }
|
||||||
diesel_migrations = "1.4.0"
|
diesel_migrations = "1.4.0"
|
||||||
dotenv = "0.9.0"
|
dotenv = "0.9.0"
|
||||||
serde = { version = "1.0.104", features = ["derive"] }
|
serde = { version = "1.0.104", features = ["derive"] }
|
||||||
|
scrypt = "0.2.0"
|
||||||
rust-crypto = "0.2.36"
|
rust-crypto = "0.2.36"
|
|
@ -12,6 +12,7 @@ extern crate dotenv;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate serde;
|
extern crate serde;
|
||||||
extern crate crypto;
|
extern crate crypto;
|
||||||
|
extern crate scrypt;
|
||||||
|
|
||||||
mod schema;
|
mod schema;
|
||||||
mod api;
|
mod api;
|
||||||
|
|
81
src/user.rs
81
src/user.rs
|
@ -15,8 +15,40 @@ impl UserOpError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Queryable, Debug)]
|
// Password should ALWAYS be hashed
|
||||||
pub struct User {
|
#[derive(Debug)]
|
||||||
|
pub struct Password(String);
|
||||||
|
|
||||||
|
impl Password {
|
||||||
|
fn new(passwd: &str) -> Password {
|
||||||
|
let params = scrypt::ScryptParams::new(11, 8, 1).unwrap();
|
||||||
|
Password(scrypt::scrypt_simple(passwd, ¶ms).unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq<&str> for Password {
|
||||||
|
fn eq(&self, other: &&str) -> bool {
|
||||||
|
scrypt::scrypt_check(*other, &self.0).is_ok()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<Password> for String {
|
||||||
|
fn into(self) -> Password {
|
||||||
|
Password::new(&self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert itself to a hash String for db operations
|
||||||
|
impl Into<String> for Password {
|
||||||
|
fn into(self) -> String {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A raw User returned from database
|
||||||
|
// we need to wrap the password in the Password type
|
||||||
|
#[derive(Queryable)]
|
||||||
|
struct UserQuery {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub email: String,
|
pub email: String,
|
||||||
pub password: String,
|
pub password: String,
|
||||||
|
@ -25,6 +57,31 @@ pub struct User {
|
||||||
pub version: String
|
pub version: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Into<User> for UserQuery {
|
||||||
|
fn into(self) -> User {
|
||||||
|
User {
|
||||||
|
id: self.id,
|
||||||
|
email: self.email,
|
||||||
|
// We can directly construct Password here
|
||||||
|
// because it's already the hashed value from db
|
||||||
|
password: Password(self.password),
|
||||||
|
pw_cost: self.pw_cost,
|
||||||
|
pw_nonce: self.pw_nonce,
|
||||||
|
version: self.version
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct User {
|
||||||
|
pub id: i32,
|
||||||
|
pub email: String,
|
||||||
|
pub password: Password,
|
||||||
|
pub pw_cost: String,
|
||||||
|
pub pw_nonce: String,
|
||||||
|
pub version: String
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Insertable, Deserialize)]
|
#[derive(Insertable, Deserialize)]
|
||||||
#[table_name="users"]
|
#[table_name="users"]
|
||||||
pub struct NewUser {
|
pub struct NewUser {
|
||||||
|
@ -37,10 +94,18 @@ pub struct NewUser {
|
||||||
|
|
||||||
impl User {
|
impl User {
|
||||||
pub fn create(db: &SqliteConnection, new_user: &NewUser) -> Result<(), UserOpError> {
|
pub fn create(db: &SqliteConnection, new_user: &NewUser) -> Result<(), UserOpError> {
|
||||||
|
let user_hashed = NewUser {
|
||||||
|
email: new_user.email.clone(),
|
||||||
|
password: Password::new(&new_user.password).into(),
|
||||||
|
pw_cost: new_user.pw_cost.clone(),
|
||||||
|
pw_nonce: new_user.pw_nonce.clone(),
|
||||||
|
version: new_user.version.clone()
|
||||||
|
};
|
||||||
|
|
||||||
match Self::find_user_by_email(db, &new_user.email) {
|
match Self::find_user_by_email(db, &new_user.email) {
|
||||||
Ok(_) => Err(UserOpError::new("User already registered")),
|
Ok(_) => Err(UserOpError::new("User already registered")),
|
||||||
Err(_) => diesel::insert_into(users::table)
|
Err(_) => diesel::insert_into(users::table)
|
||||||
.values(new_user)
|
.values(user_hashed)
|
||||||
.execute(db)
|
.execute(db)
|
||||||
.map(|_| ())
|
.map(|_| ())
|
||||||
.map_err(|_| UserOpError::new("Database error"))
|
.map_err(|_| UserOpError::new("Database error"))
|
||||||
|
@ -50,18 +115,18 @@ impl User {
|
||||||
pub fn find_user_by_email(db: &SqliteConnection, user_email: &str) -> Result<User, UserOpError> {
|
pub fn find_user_by_email(db: &SqliteConnection, user_email: &str) -> Result<User, UserOpError> {
|
||||||
let mut results = users.filter(email.eq(user_email))
|
let mut results = users.filter(email.eq(user_email))
|
||||||
.limit(1)
|
.limit(1)
|
||||||
.load::<User>(db)
|
.load::<UserQuery>(db)
|
||||||
.map_err(|_| UserOpError::new("Database error"))?;
|
.map_err(|_| UserOpError::new("Database error"))?;
|
||||||
if results.is_empty() {
|
if results.is_empty() {
|
||||||
Result::Err(UserOpError::new("No matching user found"))
|
Result::Err(UserOpError::new("No matching user found"))
|
||||||
} else {
|
} else {
|
||||||
Result::Ok(results.remove(0)) // Take ownership, kill the stupid Vec
|
Result::Ok(results.remove(0).into()) // Take ownership, kill the stupid Vec
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a JWT token for the current user if password matches
|
// Create a JWT token for the current user if password matches
|
||||||
pub fn create_token(&self, passwd: &str) -> Result<String, UserOpError> {
|
pub fn create_token(&self, passwd: &str) -> Result<String, UserOpError> {
|
||||||
if passwd != self.password {
|
if self.password != passwd {
|
||||||
Err(UserOpError::new("Password mismatch"))
|
Err(UserOpError::new("Password mismatch"))
|
||||||
} else {
|
} else {
|
||||||
jwt::Token::new(
|
jwt::Token::new(
|
||||||
|
@ -86,14 +151,14 @@ impl User {
|
||||||
// Change the password in database, if old password is provided
|
// Change the password in database, if old password is provided
|
||||||
// The current instance of User model will not be mutated
|
// The current instance of User model will not be mutated
|
||||||
pub fn change_pw(&self, db: &SqliteConnection, passwd: &str, new_passwd: &str) -> Result<(), UserOpError> {
|
pub fn change_pw(&self, db: &SqliteConnection, passwd: &str, new_passwd: &str) -> Result<(), UserOpError> {
|
||||||
if passwd != self.password {
|
if self.password != passwd {
|
||||||
Err(UserOpError::new("Password mismatch"))
|
Err(UserOpError::new("Password mismatch"))
|
||||||
} else {
|
} else {
|
||||||
// Update database
|
// Update database
|
||||||
// TODO: Maybe we should revoke all JWTs somehow?
|
// TODO: Maybe we should revoke all JWTs somehow?
|
||||||
// maybe we can record when the user last changed?
|
// maybe we can record when the user last changed?
|
||||||
diesel::update(users.find(self.id))
|
diesel::update(users.find(self.id))
|
||||||
.set(password.eq(new_passwd))
|
.set(password.eq::<String>(Password::new(new_passwd).into()))
|
||||||
.execute(db)
|
.execute(db)
|
||||||
.map(|_| ())
|
.map(|_| ())
|
||||||
.map_err(|_| UserOpError::new("Database error"))
|
.map_err(|_| UserOpError::new("Database error"))
|
||||||
|
|
Loading…
Reference in New Issue