mirror of https://github.com/LemmyNet/lemmy.git
Merge 0bfedab2ef
into 1a4aa3eaba
This commit is contained in:
commit
68ff237f28
|
@ -19,7 +19,7 @@ pub async fn change_password_after_reset(
|
|||
) -> LemmyResult<Json<SuccessResponse>> {
|
||||
// Fetch the user_id from the token
|
||||
let token = data.token.clone();
|
||||
let local_user_id = PasswordResetRequest::read_from_token(&mut context.pool(), &token)
|
||||
let local_user_id = PasswordResetRequest::read(&mut context.pool(), &token)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::TokenNotFound)?
|
||||
.local_user_id;
|
||||
|
|
|
@ -22,11 +22,8 @@ pub async fn reset_password(
|
|||
.ok_or(LemmyErrorType::IncorrectLogin)?;
|
||||
|
||||
// Check for too many attempts (to limit potential abuse)
|
||||
let recent_resets_count = PasswordResetRequest::get_recent_password_resets_count(
|
||||
&mut context.pool(),
|
||||
local_user_view.local_user.id,
|
||||
)
|
||||
.await?;
|
||||
let recent_resets_count =
|
||||
PasswordResetRequest::recent_count(&mut context.pool(), local_user_view.local_user.id).await?;
|
||||
if recent_resets_count >= 3 {
|
||||
Err(LemmyErrorType::PasswordResetLimitReached)?
|
||||
}
|
||||
|
|
|
@ -440,7 +440,7 @@ pub async fn send_password_reset_email(
|
|||
// Insert the row after successful send, to avoid using daily reset limit while
|
||||
// email sending is broken.
|
||||
let local_user_id = user.local_user.id;
|
||||
PasswordResetRequest::create_token(pool, local_user_id, token.clone()).await?;
|
||||
PasswordResetRequest::create(pool, local_user_id, token.clone()).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
@ -1,49 +1,29 @@
|
|||
use crate::{
|
||||
diesel::OptionalExtension,
|
||||
newtypes::LocalUserId,
|
||||
schema::password_reset_request::dsl::{local_user_id, password_reset_request, published, token},
|
||||
schema::password_reset_request::dsl::{
|
||||
local_user_id,
|
||||
password_reset_request,
|
||||
published,
|
||||
token,
|
||||
valid,
|
||||
},
|
||||
source::password_reset_request::{PasswordResetRequest, PasswordResetRequestForm},
|
||||
traits::Crud,
|
||||
utils::{get_conn, DbPool},
|
||||
};
|
||||
use diesel::{
|
||||
dsl::{insert_into, now, IntervalDsl},
|
||||
result::Error,
|
||||
sql_types::Timestamptz,
|
||||
update,
|
||||
ExpressionMethods,
|
||||
IntoSql,
|
||||
QueryDsl,
|
||||
};
|
||||
use diesel_async::RunQueryDsl;
|
||||
|
||||
#[async_trait]
|
||||
impl Crud for PasswordResetRequest {
|
||||
type InsertForm = PasswordResetRequestForm;
|
||||
type UpdateForm = PasswordResetRequestForm;
|
||||
type IdType = i32;
|
||||
|
||||
async fn create(pool: &mut DbPool<'_>, form: &PasswordResetRequestForm) -> Result<Self, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
insert_into(password_reset_request)
|
||||
.values(form)
|
||||
.get_result::<Self>(conn)
|
||||
.await
|
||||
}
|
||||
async fn update(
|
||||
pool: &mut DbPool<'_>,
|
||||
password_reset_request_id: i32,
|
||||
form: &PasswordResetRequestForm,
|
||||
) -> Result<Self, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
diesel::update(password_reset_request.find(password_reset_request_id))
|
||||
.set(form)
|
||||
.get_result::<Self>(conn)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
impl PasswordResetRequest {
|
||||
pub async fn create_token(
|
||||
pub async fn create(
|
||||
pool: &mut DbPool<'_>,
|
||||
from_local_user_id: LocalUserId,
|
||||
token_: String,
|
||||
|
@ -52,23 +32,27 @@ impl PasswordResetRequest {
|
|||
local_user_id: from_local_user_id,
|
||||
token: token_,
|
||||
};
|
||||
|
||||
Self::create(pool, &form).await
|
||||
}
|
||||
pub async fn read_from_token(pool: &mut DbPool<'_>, token_: &str) -> Result<Option<Self>, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
password_reset_request
|
||||
insert_into(password_reset_request)
|
||||
.values(form)
|
||||
.get_result::<Self>(conn)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Reads reset token and invalidates it
|
||||
pub async fn read(pool: &mut DbPool<'_>, token_: &str) -> Result<Option<Self>, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
update(password_reset_request)
|
||||
.filter(valid.eq(true))
|
||||
.filter(token.eq(token_))
|
||||
.filter(published.gt(now.into_sql::<Timestamptz>() - 1.days()))
|
||||
.first(conn)
|
||||
.set(valid.eq(false))
|
||||
.get_result(conn)
|
||||
.await
|
||||
.optional()
|
||||
}
|
||||
|
||||
pub async fn get_recent_password_resets_count(
|
||||
pool: &mut DbPool<'_>,
|
||||
user_id: LocalUserId,
|
||||
) -> Result<i64, Error> {
|
||||
pub async fn recent_count(pool: &mut DbPool<'_>, user_id: LocalUserId) -> Result<i64, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
password_reset_request
|
||||
.filter(local_user_id.eq(user_id))
|
||||
|
@ -94,62 +78,66 @@ mod tests {
|
|||
traits::Crud,
|
||||
utils::build_db_pool_for_tests,
|
||||
};
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serial_test::serial;
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_crud() {
|
||||
async fn test_password_reset() -> LemmyResult<()> {
|
||||
let pool = &build_db_pool_for_tests().await;
|
||||
let pool = &mut pool.into();
|
||||
|
||||
let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Setup
|
||||
let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?;
|
||||
let new_person = PersonInsertForm::builder()
|
||||
.name("thommy prw".into())
|
||||
.public_key("pubkey".to_string())
|
||||
.instance_id(inserted_instance.id)
|
||||
.build();
|
||||
|
||||
let inserted_person = Person::create(pool, &new_person).await.unwrap();
|
||||
|
||||
let inserted_person = Person::create(pool, &new_person).await?;
|
||||
let new_local_user = LocalUserInsertForm::builder()
|
||||
.person_id(inserted_person.id)
|
||||
.password_encrypted("pass".to_string())
|
||||
.build();
|
||||
let inserted_local_user = LocalUser::create(pool, &new_local_user, vec![]).await?;
|
||||
|
||||
let inserted_local_user = LocalUser::create(pool, &new_local_user, vec![])
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Create password reset token
|
||||
let token = "nope";
|
||||
|
||||
let inserted_password_reset_request =
|
||||
PasswordResetRequest::create_token(pool, inserted_local_user.id, token.to_string())
|
||||
.await
|
||||
.unwrap();
|
||||
PasswordResetRequest::create(pool, inserted_local_user.id, token.to_string()).await?;
|
||||
|
||||
let expected_password_reset_request = PasswordResetRequest {
|
||||
id: inserted_password_reset_request.id,
|
||||
local_user_id: inserted_local_user.id,
|
||||
token: token.to_string(),
|
||||
published: inserted_password_reset_request.published,
|
||||
};
|
||||
|
||||
let read_password_reset_request = PasswordResetRequest::read_from_token(pool, token)
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
let num_deleted = Person::delete(pool, inserted_person.id).await.unwrap();
|
||||
Instance::delete(pool, inserted_instance.id).await.unwrap();
|
||||
|
||||
assert_eq!(expected_password_reset_request, read_password_reset_request);
|
||||
// Read it and verify
|
||||
let read_password_reset_request = PasswordResetRequest::read(pool, token).await?.unwrap();
|
||||
assert_eq!(
|
||||
expected_password_reset_request,
|
||||
inserted_password_reset_request
|
||||
inserted_password_reset_request.id,
|
||||
read_password_reset_request.id
|
||||
);
|
||||
assert_eq!(
|
||||
inserted_password_reset_request.local_user_id,
|
||||
read_password_reset_request.local_user_id
|
||||
);
|
||||
assert_eq!(
|
||||
inserted_password_reset_request.token,
|
||||
read_password_reset_request.token
|
||||
);
|
||||
assert_eq!(
|
||||
inserted_password_reset_request.published,
|
||||
read_password_reset_request.published
|
||||
);
|
||||
|
||||
// Check number of resets
|
||||
let reset_count = PasswordResetRequest::recent_count(pool, inserted_local_user.id).await?;
|
||||
assert_eq!(1, reset_count);
|
||||
|
||||
// Cannot reuse same token again
|
||||
let read_password_reset_request = PasswordResetRequest::read(pool, token).await?;
|
||||
assert!(read_password_reset_request.is_none());
|
||||
|
||||
// Cleanup
|
||||
let num_deleted = Person::delete(pool, inserted_person.id).await?;
|
||||
Instance::delete(pool, inserted_instance.id).await?;
|
||||
assert_eq!(1, num_deleted);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -610,6 +610,7 @@ diesel::table! {
|
|||
token -> Text,
|
||||
published -> Timestamptz,
|
||||
local_user_id -> Int4,
|
||||
valid -> Bool,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ pub struct PasswordResetRequest {
|
|||
pub token: String,
|
||||
pub published: DateTime<Utc>,
|
||||
pub local_user_id: LocalUserId,
|
||||
pub valid: bool,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
ALTER TABLE password_reset_request
|
||||
DROP COLUMN valid;
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
ALTER TABLE password_reset_request
|
||||
ADD COLUMN valid bool NOT NULL DEFAULT TRUE;
|
||||
|
Loading…
Reference in New Issue