Compare commits

...

9 commits

Author SHA1 Message Date
f55ef1d7ef Version 0.10.0-rc.7 2021-03-19 11:46:46 -04:00
14bc9f0946
Merge pull request #1500 from LemmyNet/jwt_revocation_dess
Jwt revocation dess
2021-03-19 15:43:47 +00:00
493598c1ba A few suggestion fixes. 2021-03-19 10:02:58 -04:00
05b485b678 Merge branch 'Mart-Bogdan-1462-jwt-revocation-on-pwd-change' into jwt_revocation_dess 2021-03-19 00:31:49 -04:00
360d4ea8d1 Merge branch '1462-jwt-revocation-on-pwd-change' of https://github.com/Mart-Bogdan/lemmy into Mart-Bogdan-1462-jwt-revocation-on-pwd-change 2021-03-18 21:41:00 -04:00
Bogdan Mart
74272ed754 more correct tests 2021-03-13 22:36:40 +02:00
Bogdan Mart
4426c3176d fix timestamp condition #1462 2021-03-13 22:18:26 +02:00
Bogdan Mart
7b0a09e84e Merge remote-tracking branch 'origin/main' into 1462-jwt-revocation-on-pwd-change
* origin/main:
  revert Compose file version from 3.3 to 2.2
  Adding more mem limits
  bump memory limit of iframely
  Remove extra category_id s . Fixes #1429
  Fixing wrong user_ and community icon and banner urls.
  Remove category from activitypub context
  Adding a password length check to other API actions. (#1474)
  Update test script
  Use URL type in most outstanding struct fields (#1468)
  Forbid usage of unwrap
  Upgrade Rust version
  Rewrite settings implementation. Fixes #1270 (#1433)
  Rename `lemmy_structs` to `lemmy_api_structs`

# Conflicts:
#	crates/db_schema/src/source/user.rs
2021-03-13 20:19:55 +02:00
Bogdan Mart
ab947f1f08 User token revocation upon password change
Added DB column validator_time and chedking that is is less then token's "Issuead at time"
Wip on #1462
2021-03-13 20:16:35 +02:00
15 changed files with 136 additions and 41 deletions

View file

@ -1 +1 @@
0.10.0-rc.5
0.10.0-rc.7

View file

@ -41,7 +41,7 @@ use lemmy_utils::{
};
use lemmy_websocket::{serialize_websocket_message, LemmyContext, UserOperation};
use serde::Deserialize;
use std::process::Command;
use std::{env, process::Command};
use url::Url;
pub mod comment;
@ -100,16 +100,32 @@ pub(crate) async fn get_local_user_view_from_jwt(
Ok(claims) => claims.claims,
Err(_e) => return Err(ApiError::err("not_logged_in").into()),
};
let local_user_id = LocalUserId(claims.local_user_id);
let local_user_id = LocalUserId(claims.sub);
let local_user_view =
blocking(pool, move |conn| LocalUserView::read(conn, local_user_id)).await??;
// Check for a site ban
if local_user_view.person.banned {
return Err(ApiError::err("site_ban").into());
}
check_validator_time(&local_user_view.local_user.validator_time, &claims)?;
Ok(local_user_view)
}
/// Checks if user's token was issued before user's password reset.
pub(crate) fn check_validator_time(
validator_time: &chrono::NaiveDateTime,
claims: &Claims,
) -> Result<(), LemmyError> {
let user_validation_time = validator_time.timestamp();
if user_validation_time > claims.iat {
Err(ApiError::err("not_logged_in").into())
} else {
Ok(())
}
}
pub(crate) async fn get_local_user_view_from_jwt_opt(
jwt: &Option<String>,
pool: &DbPool,
@ -128,7 +144,7 @@ pub(crate) async fn get_local_user_settings_view_from_jwt(
Ok(claims) => claims.claims,
Err(_e) => return Err(ApiError::err("not_logged_in").into()),
};
let local_user_id = LocalUserId(claims.local_user_id);
let local_user_id = LocalUserId(claims.sub);
let local_user_view = blocking(pool, move |conn| {
LocalUserSettingsView::read(conn, local_user_id)
})
@ -137,6 +153,9 @@ pub(crate) async fn get_local_user_settings_view_from_jwt(
if local_user_view.person.banned {
return Err(ApiError::err("site_ban").into());
}
check_validator_time(&local_user_view.local_user.validator_time, &claims)?;
Ok(local_user_view)
}
@ -459,7 +478,11 @@ pub(crate) fn captcha_espeak_wav_base64(captcha: &str) -> Result<String, LemmyEr
pub(crate) fn espeak_wav_base64(text: &str) -> Result<String, LemmyError> {
// Make a temp file path
let uuid = uuid::Uuid::new_v4().to_string();
let file_path = format!("/tmp/lemmy_espeak_{}.wav", &uuid);
let file_path = format!(
"{}/lemmy_espeak_{}.wav",
env::temp_dir().to_string_lossy(),
&uuid
);
// Write the wav file
Command::new("espeak")
@ -491,7 +514,70 @@ pub(crate) fn password_length_check(pass: &str) -> Result<(), LemmyError> {
#[cfg(test)]
mod tests {
use crate::captcha_espeak_wav_base64;
use crate::{captcha_espeak_wav_base64, check_validator_time};
use lemmy_db_queries::{establish_unpooled_connection, source::local_user::LocalUser_, Crud};
use lemmy_db_schema::source::{
local_user::{LocalUser, LocalUserForm},
person::{Person, PersonForm},
};
use lemmy_utils::claims::Claims;
#[test]
fn test_should_not_validate_user_token_after_password_change() {
let conn = establish_unpooled_connection();
let new_person = PersonForm {
name: "Gerry9812".into(),
preferred_username: None,
avatar: None,
banner: None,
banned: None,
deleted: None,
published: None,
updated: None,
actor_id: None,
bio: None,
local: None,
private_key: None,
public_key: None,
last_refreshed_at: None,
inbox_url: None,
shared_inbox_url: None,
};
let inserted_person = Person::create(&conn, &new_person).unwrap();
let local_user_form = LocalUserForm {
person_id: inserted_person.id,
email: None,
matrix_user_id: None,
password_encrypted: "123456".to_string(),
admin: None,
show_nsfw: None,
theme: None,
default_sort_type: None,
default_listing_type: None,
lang: None,
show_avatars: None,
send_notifications_to_email: None,
};
let inserted_local_user = LocalUser::create(&conn, &local_user_form).unwrap();
let jwt = Claims::jwt(inserted_local_user.id.0).unwrap();
let claims = Claims::decode(&jwt).unwrap().claims;
let check = check_validator_time(&inserted_local_user.validator_time, &claims);
assert!(check.is_ok());
// The check should fail, since the validator time is now newer than the jwt issue time
let updated_local_user =
LocalUser::update_password(&conn, inserted_local_user.id, &"password111").unwrap();
let check_after = check_validator_time(&updated_local_user.validator_time, &claims);
assert!(check_after.is_err());
let num_deleted = Person::delete(&conn, inserted_person.id).unwrap();
assert_eq!(1, num_deleted);
}
#[test]
fn test_espeak() {

View file

@ -130,7 +130,7 @@ impl Perform for Login {
// Return the jwt
Ok(LoginResponse {
jwt: Claims::jwt(local_user_view.local_user.id.0, Settings::get().hostname())?,
jwt: Claims::jwt(local_user_view.local_user.id.0)?,
})
}
}
@ -336,7 +336,7 @@ impl Perform for Register {
// Return the jwt
Ok(LoginResponse {
jwt: Claims::jwt(inserted_local_user.id.0, Settings::get().hostname())?,
jwt: Claims::jwt(inserted_local_user.id.0)?,
})
}
}
@ -526,7 +526,7 @@ impl Perform for SaveUserSettings {
// Return the jwt
Ok(LoginResponse {
jwt: Claims::jwt(updated_local_user.id.0, Settings::get().hostname())?,
jwt: Claims::jwt(updated_local_user.id.0)?,
})
}
}
@ -1078,7 +1078,7 @@ impl Perform for PasswordChange {
// Return the jwt
Ok(LoginResponse {
jwt: Claims::jwt(updated_local_user.id.0, Settings::get().hostname())?,
jwt: Claims::jwt(updated_local_user.id.0)?,
})
}
}

View file

@ -2,26 +2,13 @@ use crate::Crud;
use bcrypt::{hash, DEFAULT_COST};
use diesel::{dsl::*, result::Error, *};
use lemmy_db_schema::{
naive_now,
schema::local_user::dsl::*,
source::local_user::{LocalUser, LocalUserForm},
LocalUserId,
PersonId,
};
mod safe_type {
use crate::ToSafe;
use lemmy_db_schema::{schema::local_user::columns::*, source::local_user::LocalUser};
type Columns = (id, person_id, admin, matrix_user_id);
impl ToSafe for LocalUser {
type SafeColumns = Columns;
fn safe_columns_tuple() -> Self::SafeColumns {
(id, person_id, admin, matrix_user_id)
}
}
}
mod safe_settings_type {
use crate::ToSafeSettings;
use lemmy_db_schema::{schema::local_user::columns::*, source::local_user::LocalUser};
@ -39,6 +26,7 @@ mod safe_settings_type {
show_avatars,
send_notifications_to_email,
matrix_user_id,
validator_time,
);
impl ToSafeSettings for LocalUser {
@ -59,6 +47,7 @@ mod safe_settings_type {
show_avatars,
send_notifications_to_email,
matrix_user_id,
validator_time,
)
}
}
@ -92,7 +81,10 @@ impl LocalUser_ for LocalUser {
let password_hash = hash(new_password, DEFAULT_COST).expect("Couldn't hash password");
diesel::update(local_user.find(local_user_id))
.set((password_encrypted.eq(password_hash),))
.set((
password_encrypted.eq(password_hash),
validator_time.eq(naive_now()),
))
.get_result::<Self>(conn)
}

View file

@ -155,6 +155,7 @@ table! {
show_avatars -> Bool,
send_notifications_to_email -> Bool,
matrix_user_id -> Nullable<Text>,
validator_time -> Timestamp,
}
}

View file

@ -17,6 +17,7 @@ pub struct LocalUser {
pub show_avatars: bool,
pub send_notifications_to_email: bool,
pub matrix_user_id: Option<String>,
pub validator_time: chrono::NaiveDateTime,
}
// TODO redo these, check table defaults
@ -53,4 +54,5 @@ pub struct LocalUserSettings {
pub show_avatars: bool,
pub send_notifications_to_email: bool,
pub matrix_user_id: Option<String>,
pub validator_time: chrono::NaiveDateTime,
}

View file

@ -227,7 +227,7 @@ fn get_feed_front(
jwt: String,
) -> Result<ChannelBuilder, LemmyError> {
let site_view = SiteView::read(&conn)?;
let local_user_id = LocalUserId(Claims::decode(&jwt)?.claims.local_user_id);
let local_user_id = LocalUserId(Claims::decode(&jwt)?.claims.sub);
let person_id = LocalUser::read(&conn, local_user_id)?.person_id;
let posts = PostQueryBuilder::create(&conn)
@ -254,7 +254,7 @@ fn get_feed_front(
fn get_feed_inbox(conn: &PgConnection, jwt: String) -> Result<ChannelBuilder, LemmyError> {
let site_view = SiteView::read(&conn)?;
let local_user_id = LocalUserId(Claims::decode(&jwt)?.claims.local_user_id);
let local_user_id = LocalUserId(Claims::decode(&jwt)?.claims.sub);
let person_id = LocalUser::read(&conn, local_user_id)?.person_id;
let sort = SortType::New;

View file

@ -1,4 +1,5 @@
use crate::settings::structs::Settings;
use chrono::Utc;
use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, TokenData, Validation};
use serde::{Deserialize, Serialize};
@ -6,8 +7,11 @@ type Jwt = String;
#[derive(Debug, Serialize, Deserialize)]
pub struct Claims {
pub local_user_id: i32,
/// local_user_id, standard claim by RFC 7519.
pub sub: i32,
pub iss: String,
/// Time when this token was issued as UNIX-timestamp in seconds
pub iat: i64,
}
impl Claims {
@ -23,10 +27,11 @@ impl Claims {
)
}
pub fn jwt(local_user_id: i32, hostname: String) -> Result<Jwt, jsonwebtoken::errors::Error> {
pub fn jwt(local_user_id: i32) -> Result<Jwt, jsonwebtoken::errors::Error> {
let my_claims = Claims {
local_user_id,
iss: hostname,
sub: local_user_id,
iss: Settings::get().hostname(),
iat: Utc::now().timestamp(),
};
encode(
&Header::default(),

View file

@ -13,6 +13,7 @@ use crate::{
};
use anyhow::{anyhow, Context};
use deser_hjson::from_str;
use log::warn;
use merge::Merge;
use std::{env, fs, io::Error, net::IpAddr, sync::RwLock};
@ -24,7 +25,13 @@ static CONFIG_FILE: &str = "config/config.hjson";
lazy_static! {
static ref SETTINGS: RwLock<Settings> = RwLock::new(match Settings::init() {
Ok(c) => c,
Err(e) => panic!("{}", e),
Err(e) => {
warn!(
"Couldn't load settings file, using default settings.\n{}",
e
);
Settings::default()
}
});
}

View file

@ -1 +1 @@
pub const VERSION: &str = "0.10.0-rc.5";
pub const VERSION: &str = "0.10.0-rc.7";

View file

@ -17,7 +17,7 @@ services:
- iframely
lemmy-ui:
image: dessalines/lemmy-ui:0.10.0-rc.5
image: dessalines/lemmy-ui:0.10.0-rc.7
ports:
- "1235:1234"
restart: always

View file

@ -29,7 +29,7 @@ services:
- ./volumes/pictrs_alpha:/mnt
lemmy-alpha-ui:
image: dessalines/lemmy-ui:0.10.0-rc.5
image: dessalines/lemmy-ui:0.10.0-rc.7
environment:
- LEMMY_INTERNAL_HOST=lemmy-alpha:8541
- LEMMY_EXTERNAL_HOST=localhost:8541
@ -58,7 +58,7 @@ services:
- ./volumes/postgres_alpha:/var/lib/postgresql/data
lemmy-beta-ui:
image: dessalines/lemmy-ui:0.10.0-rc.5
image: dessalines/lemmy-ui:0.10.0-rc.7
environment:
- LEMMY_INTERNAL_HOST=lemmy-beta:8551
- LEMMY_EXTERNAL_HOST=localhost:8551
@ -87,7 +87,7 @@ services:
- ./volumes/postgres_beta:/var/lib/postgresql/data
lemmy-gamma-ui:
image: dessalines/lemmy-ui:0.10.0-rc.5
image: dessalines/lemmy-ui:0.10.0-rc.7
environment:
- LEMMY_INTERNAL_HOST=lemmy-gamma:8561
- LEMMY_EXTERNAL_HOST=localhost:8561
@ -117,7 +117,7 @@ services:
# An instance with only an allowlist for beta
lemmy-delta-ui:
image: dessalines/lemmy-ui:0.10.0-rc.5
image: dessalines/lemmy-ui:0.10.0-rc.7
environment:
- LEMMY_INTERNAL_HOST=lemmy-delta:8571
- LEMMY_EXTERNAL_HOST=localhost:8571
@ -147,7 +147,7 @@ services:
# An instance who has a blocklist, with lemmy-alpha blocked
lemmy-epsilon-ui:
image: dessalines/lemmy-ui:0.10.0-rc.5
image: dessalines/lemmy-ui:0.10.0-rc.7
environment:
- LEMMY_INTERNAL_HOST=lemmy-epsilon:8581
- LEMMY_EXTERNAL_HOST=localhost:8581

View file

@ -12,7 +12,7 @@ services:
restart: always
lemmy:
image: dessalines/lemmy:0.10.0-rc.5
image: dessalines/lemmy:0.10.0-rc.7
ports:
- "127.0.0.1:8536:8536"
restart: always
@ -26,7 +26,7 @@ services:
- iframely
lemmy-ui:
image: dessalines/lemmy-ui:0.10.0-rc.5
image: dessalines/lemmy-ui:0.10.0-rc.7
ports:
- "127.0.0.1:1235:1234"
restart: always

View file

@ -0,0 +1 @@
alter table local_user drop column validator_time;

View file

@ -0,0 +1 @@
alter table local_user add column validator_time timestamp not null default now();