Move jwt secret from config to database (fixes #1728)

This commit is contained in:
Felix Ableitner 2021-09-20 17:46:34 +02:00
parent 527eefbe92
commit cf214ff583
25 changed files with 116 additions and 50 deletions

View file

@ -16,8 +16,6 @@
hostname: "{{ domain }}" hostname: "{{ domain }}"
# the port where lemmy should listen for incoming requests # the port where lemmy should listen for incoming requests
port: 8536 port: 8536
# json web token for authorization between server and client
jwt_secret: "{{ jwt_password }}"
# whether tls is required for activitypub. only disable this for debugging, never for producion. # whether tls is required for activitypub. only disable this for debugging, never for producion.
tls_enabled: true tls_enabled: true
# address where pictrs is available # address where pictrs is available

View file

@ -33,8 +33,6 @@
port: 8536 port: 8536
# whether tls is required for activitypub. only disable this for debugging, never for producion. # whether tls is required for activitypub. only disable this for debugging, never for producion.
tls_enabled: true tls_enabled: true
# json web token for authorization between server and client
jwt_secret: "changeme"
# address where pictrs is available # address where pictrs is available
pictrs_url: "http://pictrs:8080" pictrs_url: "http://pictrs:8080"
# maximum length of local community and user names # maximum length of local community and user names

View file

@ -188,10 +188,15 @@ pub(crate) fn captcha_as_wav_base64(captcha: &Captcha) -> String {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use lemmy_api_common::check_validator_time; use lemmy_api_common::check_validator_time;
use lemmy_db_queries::{establish_unpooled_connection, source::local_user::LocalUser_, Crud}; use lemmy_db_queries::{
establish_unpooled_connection,
source::{local_user::LocalUser_, secret::SecretSingleton},
Crud,
};
use lemmy_db_schema::source::{ use lemmy_db_schema::source::{
local_user::{LocalUser, LocalUserForm}, local_user::{LocalUser, LocalUserForm},
person::{Person, PersonForm}, person::{Person, PersonForm},
secret::Secret,
}; };
use lemmy_utils::claims::Claims; use lemmy_utils::claims::Claims;
@ -214,8 +219,9 @@ mod tests {
let inserted_local_user = LocalUser::create(&conn, &local_user_form).unwrap(); let inserted_local_user = LocalUser::create(&conn, &local_user_form).unwrap();
let jwt = Claims::jwt(inserted_local_user.id.0).unwrap(); let jwt_secret = Secret::get().jwt_secret;
let claims = Claims::decode(&jwt).unwrap().claims; let jwt = Claims::jwt(inserted_local_user.id.0, &jwt_secret).unwrap();
let claims = Claims::decode(&jwt, jwt_secret.as_ref()).unwrap().claims;
let check = check_validator_time(&inserted_local_user.validator_time, &claims); let check = check_validator_time(&inserted_local_user.validator_time, &claims);
assert!(check.is_ok()); assert!(check.is_ok());

View file

@ -25,6 +25,7 @@ use lemmy_db_queries::{
person_mention::PersonMention_, person_mention::PersonMention_,
post::Post_, post::Post_,
private_message::PrivateMessage_, private_message::PrivateMessage_,
secret::SecretSingleton,
}, },
Blockable, Blockable,
Crud, Crud,
@ -43,6 +44,7 @@ use lemmy_db_schema::{
person_mention::*, person_mention::*,
post::Post, post::Post,
private_message::PrivateMessage, private_message::PrivateMessage,
secret::Secret,
site::*, site::*,
}, },
}; };
@ -103,8 +105,9 @@ impl Perform for Login {
} }
// Return the jwt // Return the jwt
let jwt_secret = Secret::get().jwt_secret;
Ok(LoginResponse { Ok(LoginResponse {
jwt: Claims::jwt(local_user_view.local_user.id.0)?, jwt: Claims::jwt(local_user_view.local_user.id.0, &jwt_secret)?,
}) })
} }
} }
@ -268,8 +271,9 @@ impl Perform for SaveUserSettings {
}; };
// Return the jwt // Return the jwt
let jwt_secret = Secret::get().jwt_secret;
Ok(LoginResponse { Ok(LoginResponse {
jwt: Claims::jwt(updated_local_user.id.0)?, jwt: Claims::jwt(updated_local_user.id.0, &jwt_secret)?,
}) })
} }
} }
@ -311,8 +315,9 @@ impl Perform for ChangePassword {
.await??; .await??;
// Return the jwt // Return the jwt
let jwt_secret = Secret::get().jwt_secret;
Ok(LoginResponse { Ok(LoginResponse {
jwt: Claims::jwt(updated_local_user.id.0)?, jwt: Claims::jwt(updated_local_user.id.0, &jwt_secret)?,
}) })
} }
} }
@ -770,8 +775,9 @@ impl Perform for PasswordChange {
.map_err(|_| ApiError::err("couldnt_update_user"))?; .map_err(|_| ApiError::err("couldnt_update_user"))?;
// Return the jwt // Return the jwt
let jwt_secret = Secret::get().jwt_secret;
Ok(LoginResponse { Ok(LoginResponse {
jwt: Claims::jwt(updated_local_user.id.0)?, jwt: Claims::jwt(updated_local_user.id.0, &jwt_secret)?,
}) })
} }
} }

View file

@ -11,6 +11,7 @@ use lemmy_db_queries::{
source::{ source::{
community::{CommunityModerator_, Community_}, community::{CommunityModerator_, Community_},
person_block::PersonBlock_, person_block::PersonBlock_,
secret::SecretSingleton,
site::Site_, site::Site_,
}, },
Crud, Crud,
@ -25,6 +26,7 @@ use lemmy_db_schema::{
person_block::PersonBlock, person_block::PersonBlock,
person_mention::{PersonMention, PersonMentionForm}, person_mention::{PersonMention, PersonMentionForm},
post::{Post, PostRead, PostReadForm}, post::{Post, PostRead, PostReadForm},
secret::Secret,
site::Site, site::Site,
}, },
CommunityId, CommunityId,
@ -245,7 +247,8 @@ pub async fn get_local_user_view_from_jwt(
jwt: &str, jwt: &str,
pool: &DbPool, pool: &DbPool,
) -> Result<LocalUserView, LemmyError> { ) -> Result<LocalUserView, LemmyError> {
let claims = Claims::decode(jwt) let jwt_secret = Secret::get().jwt_secret;
let claims = Claims::decode(jwt, &jwt_secret)
.map_err(|_| ApiError::err("not_logged_in"))? .map_err(|_| ApiError::err("not_logged_in"))?
.claims; .claims;
let local_user_id = LocalUserId(claims.sub); let local_user_id = LocalUserId(claims.sub);
@ -293,7 +296,8 @@ pub async fn get_local_user_settings_view_from_jwt(
jwt: &str, jwt: &str,
pool: &DbPool, pool: &DbPool,
) -> Result<LocalUserSettingsView, LemmyError> { ) -> Result<LocalUserSettingsView, LemmyError> {
let claims = Claims::decode(jwt) let jwt_secret = Secret::get().jwt_secret;
let claims = Claims::decode(jwt, &jwt_secret)
.map_err(|_| ApiError::err("not_logged_in"))? .map_err(|_| ApiError::err("not_logged_in"))?
.claims; .claims;
let local_user_id = LocalUserId(claims.sub); let local_user_id = LocalUserId(claims.sub);

View file

@ -9,7 +9,7 @@ use lemmy_apub::{
EndpointType, EndpointType,
}; };
use lemmy_db_queries::{ use lemmy_db_queries::{
source::{local_user::LocalUser_, site::Site_}, source::{local_user::LocalUser_, secret::SecretSingleton, site::Site_},
Crud, Crud,
Followable, Followable,
Joinable, Joinable,
@ -21,6 +21,7 @@ use lemmy_db_schema::{
community::*, community::*,
local_user::{LocalUser, LocalUserForm}, local_user::{LocalUser, LocalUserForm},
person::*, person::*,
secret::Secret,
site::*, site::*,
}, },
CommunityId, CommunityId,
@ -218,8 +219,9 @@ impl PerformCrud for Register {
} }
// Return the jwt // Return the jwt
let jwt_secret = Secret::get().jwt_secret;
Ok(LoginResponse { Ok(LoginResponse {
jwt: Claims::jwt(inserted_local_user.id.0)?, jwt: Claims::jwt(inserted_local_user.id.0, &jwt_secret)?,
}) })
} }
} }

View file

@ -12,4 +12,5 @@ pub mod person_mention;
pub mod post; pub mod post;
pub mod post_report; pub mod post_report;
pub mod private_message; pub mod private_message;
pub mod secret;
pub mod site; pub mod site;

View file

@ -0,0 +1,37 @@
use diesel::{result::Error, *};
use lemmy_db_schema::source::secret::Secret;
use lemmy_utils::settings::structs::Settings;
use std::sync::RwLock;
use crate::get_database_url_from_env;
lazy_static! {
static ref SECRET: RwLock<Secret> = RwLock::new(init().expect("Failed to load secrets from DB."));
}
pub trait SecretSingleton {
fn get() -> Secret;
}
impl SecretSingleton for Secret {
/// Returns the Secret as a struct
fn get() -> Self {
SECRET.read().expect("read secrets").to_owned()
}
}
/// Reads the secrets from the DB
fn init() -> Result<Secret, Error> {
let db_url = match get_database_url_from_env() {
Ok(url) => url,
Err(_) => Settings::get().get_database_url(),
};
let conn = PgConnection::establish(&db_url).expect("Couldn't get DB connection for Secrets.");
read_secrets(&conn)
}
fn read_secrets(conn: &PgConnection) -> Result<Secret, Error> {
use lemmy_db_schema::schema::secret::dsl::*;
secret.first::<Secret>(conn)
}

View file

@ -551,6 +551,13 @@ table! {
} }
} }
table! {
secret(id) {
id -> Int4,
jwt_secret -> Varchar,
}
}
joinable!(comment_alias_1 -> person_alias_1 (creator_id)); joinable!(comment_alias_1 -> person_alias_1 (creator_id));
joinable!(comment -> comment_alias_1 (parent_id)); joinable!(comment -> comment_alias_1 (parent_id));
joinable!(person_mention -> person_alias_1 (recipient_id)); joinable!(person_mention -> person_alias_1 (recipient_id));

View file

@ -12,4 +12,5 @@ pub mod person_mention;
pub mod post; pub mod post;
pub mod post_report; pub mod post_report;
pub mod private_message; pub mod private_message;
pub mod secret;
pub mod site; pub mod site;

View file

@ -0,0 +1,8 @@
use crate::schema::secret;
#[derive(Queryable, Identifiable, Clone)]
#[table_name = "secret"]
pub struct Secret {
pub id: i32,
pub jwt_secret: String,
}

View file

@ -4,13 +4,13 @@ use chrono::{DateTime, NaiveDateTime, Utc};
use diesel::PgConnection; use diesel::PgConnection;
use lemmy_api_common::blocking; use lemmy_api_common::blocking;
use lemmy_db_queries::{ use lemmy_db_queries::{
source::{community::Community_, person::Person_}, source::{community::Community_, person::Person_, secret::SecretSingleton},
Crud, Crud,
ListingType, ListingType,
SortType, SortType,
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
source::{community::Community, local_user::LocalUser, person::Person}, source::{community::Community, local_user::LocalUser, person::Person, secret::Secret},
LocalUserId, LocalUserId,
}; };
use lemmy_db_views::{ use lemmy_db_views::{
@ -229,17 +229,15 @@ fn get_feed_front(
jwt: String, jwt: String,
) -> Result<ChannelBuilder, LemmyError> { ) -> Result<ChannelBuilder, LemmyError> {
let site_view = SiteView::read(conn)?; let site_view = SiteView::read(conn)?;
let local_user_id = LocalUserId(Claims::decode(&jwt)?.claims.sub); let jwt_secret = Secret::get().jwt_secret;
let local_user_id = LocalUserId(Claims::decode(&jwt, &jwt_secret)?.claims.sub);
let local_user = LocalUser::read(conn, local_user_id)?; let local_user = LocalUser::read(conn, local_user_id)?;
let person_id = local_user.person_id;
let show_bot_accounts = local_user.show_bot_accounts;
let show_read_posts = local_user.show_read_posts;
let posts = PostQueryBuilder::create(conn) let posts = PostQueryBuilder::create(conn)
.listing_type(ListingType::Subscribed) .listing_type(ListingType::Subscribed)
.my_person_id(person_id) .my_person_id(local_user.person_id)
.show_bot_accounts(show_bot_accounts) .show_bot_accounts(local_user.show_bot_accounts)
.show_read_posts(show_read_posts) .show_read_posts(local_user.show_read_posts)
.sort(*sort_type) .sort(*sort_type)
.list()?; .list()?;
@ -261,7 +259,8 @@ fn get_feed_front(
fn get_feed_inbox(conn: &PgConnection, jwt: String) -> Result<ChannelBuilder, LemmyError> { fn get_feed_inbox(conn: &PgConnection, jwt: String) -> Result<ChannelBuilder, LemmyError> {
let site_view = SiteView::read(conn)?; let site_view = SiteView::read(conn)?;
let local_user_id = LocalUserId(Claims::decode(&jwt)?.claims.sub); let jwt_secret = Secret::get().jwt_secret;
let local_user_id = LocalUserId(Claims::decode(&jwt, &jwt_secret)?.claims.sub);
let local_user = LocalUser::read(conn, local_user_id)?; let local_user = LocalUser::read(conn, local_user_id)?;
let person_id = local_user.person_id; let person_id = local_user.person_id;
let show_bot_accounts = local_user.show_bot_accounts; let show_bot_accounts = local_user.show_bot_accounts;

View file

@ -2,6 +2,8 @@ use actix_http::http::header::ACCEPT_ENCODING;
use actix_web::{body::BodyStream, http::StatusCode, web::Data, *}; use actix_web::{body::BodyStream, http::StatusCode, web::Data, *};
use anyhow::anyhow; use anyhow::anyhow;
use awc::Client; use awc::Client;
use lemmy_db_queries::source::secret::SecretSingleton;
use lemmy_db_schema::source::secret::Secret;
use lemmy_utils::{claims::Claims, rate_limit::RateLimit, settings::structs::Settings, LemmyError}; use lemmy_utils::{claims::Claims, rate_limit::RateLimit, settings::structs::Settings, LemmyError};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::time::Duration; use std::time::Duration;
@ -52,7 +54,8 @@ async fn upload(
.cookie("jwt") .cookie("jwt")
.expect("No auth header for picture upload"); .expect("No auth header for picture upload");
if Claims::decode(jwt.value()).is_err() { let jwt_secret = Secret::get().jwt_secret;
if Claims::decode(jwt.value(), &jwt_secret).is_err() {
return Ok(HttpResponse::Unauthorized().finish()); return Ok(HttpResponse::Unauthorized().finish());
}; };

View file

@ -35,7 +35,7 @@ strum_macros = "0.21.1"
futures = "0.3.16" futures = "0.3.16"
diesel = "1.4.7" diesel = "1.4.7"
http = "0.2.4" http = "0.2.4"
jsonwebtoken = "7.2.0"
deser-hjson = "1.0.2" deser-hjson = "1.0.2"
smart-default = "0.6.0" smart-default = "0.6.0"
webpage = { version = "1.3.0", default-features = false, features = ["serde"] } webpage = { version = "1.3.0", default-features = false, features = ["serde"] }
jsonwebtoken = "7.2.0"

View file

@ -1,4 +1,4 @@
use crate::settings::structs::Settings; use crate::{settings::structs::Settings, LemmyError};
use chrono::Utc; use chrono::Utc;
use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, TokenData, Validation}; use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, TokenData, Validation};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -15,28 +15,23 @@ pub struct Claims {
} }
impl Claims { impl Claims {
pub fn decode(jwt: &str) -> Result<TokenData<Claims>, jsonwebtoken::errors::Error> { pub fn decode(jwt: &str, jwt_secret: &str) -> Result<TokenData<Claims>, LemmyError> {
let v = Validation { let v = Validation {
validate_exp: false, validate_exp: false,
..Validation::default() ..Validation::default()
}; };
decode::<Claims>( let key = DecodingKey::from_secret(jwt_secret.as_ref());
jwt, Ok(decode::<Claims>(jwt, &key, &v)?)
&DecodingKey::from_secret(Settings::get().jwt_secret.as_ref()),
&v,
)
} }
pub fn jwt(local_user_id: i32) -> Result<Jwt, jsonwebtoken::errors::Error> { pub fn jwt(local_user_id: i32, jwt_secret: &str) -> Result<Jwt, LemmyError> {
let my_claims = Claims { let my_claims = Claims {
sub: local_user_id, sub: local_user_id,
iss: Settings::get().hostname, iss: Settings::get().hostname,
iat: Utc::now().timestamp(), iat: Utc::now().timestamp(),
}; };
encode(
&Header::default(), let key = EncodingKey::from_secret(jwt_secret.as_ref());
&my_claims, Ok(encode(&Header::default(), &my_claims, &key)?)
&EncodingKey::from_secret(Settings::get().jwt_secret.as_ref()),
)
} }
} }

View file

@ -6,12 +6,12 @@ extern crate strum_macros;
extern crate smart_default; extern crate smart_default;
pub mod apub; pub mod apub;
pub mod claims;
pub mod email; pub mod email;
pub mod rate_limit; pub mod rate_limit;
pub mod request; pub mod request;
pub mod settings; pub mod settings;
pub mod claims;
#[cfg(test)] #[cfg(test)]
mod test; mod test;
pub mod utils; pub mod utils;

View file

@ -24,8 +24,6 @@ pub struct Settings {
pub port: u16, pub port: u16,
#[default(true)] #[default(true)]
pub tls_enabled: bool, pub tls_enabled: bool,
#[default("changeme")]
pub jwt_secret: String,
#[default(None)] #[default(None)]
pub pictrs_url: Option<String>, pub pictrs_url: Option<String>,
#[default(None)] #[default(None)]

View file

@ -2,7 +2,6 @@
hostname: lemmy-alpha:8541 hostname: lemmy-alpha:8541
port: 8541 port: 8541
tls_enabled: false tls_enabled: false
jwt_secret: changeme
setup: { setup: {
admin_username: lemmy_alpha admin_username: lemmy_alpha
admin_password: lemmylemmy admin_password: lemmylemmy

View file

@ -2,7 +2,6 @@
hostname: lemmy-beta:8551 hostname: lemmy-beta:8551
port: 8551 port: 8551
tls_enabled: false tls_enabled: false
jwt_secret: changeme
setup: { setup: {
admin_username: lemmy_beta admin_username: lemmy_beta
admin_password: lemmylemmy admin_password: lemmylemmy

View file

@ -2,7 +2,6 @@
hostname: lemmy-delta:8571 hostname: lemmy-delta:8571
port: 8571 port: 8571
tls_enabled: false tls_enabled: false
jwt_secret: changeme
setup: { setup: {
admin_username: lemmy_delta admin_username: lemmy_delta
admin_password: lemmylemmy admin_password: lemmylemmy

View file

@ -2,7 +2,6 @@
hostname: lemmy-epsilon:8581 hostname: lemmy-epsilon:8581
port: 8581 port: 8581
tls_enabled: false tls_enabled: false
jwt_secret: changeme
setup: { setup: {
admin_username: lemmy_epsilon admin_username: lemmy_epsilon
admin_password: lemmylemmy admin_password: lemmylemmy

View file

@ -2,7 +2,6 @@
hostname: lemmy-gamma:8561 hostname: lemmy-gamma:8561
port: 8561 port: 8561
tls_enabled: false tls_enabled: false
jwt_secret: changeme
setup: { setup: {
admin_username: lemmy_gamma admin_username: lemmy_gamma
admin_password: lemmylemmy admin_password: lemmylemmy

View file

@ -17,8 +17,6 @@
bind: "0.0.0.0" bind: "0.0.0.0"
# port where lemmy should listen for incoming requests # port where lemmy should listen for incoming requests
port: 8536 port: 8536
# json web token for authorization between server and client
jwt_secret: "changeme"
# settings related to the postgresql database # settings related to the postgresql database
# address where pictrs is available # address where pictrs is available
pictrs_url: "http://pictrs:8080" pictrs_url: "http://pictrs:8080"

View file

@ -0,0 +1 @@
drop table secret;

View file

@ -0,0 +1,9 @@
-- generate a jwt secret
create extension if not exists pgcrypto;
create table secret(
id serial primary key,
jwt_secret varchar not null default gen_random_uuid()
);
insert into secret default values;