From f86ca34baff487112126a0f80059a1bb7778ff34 Mon Sep 17 00:00:00 2001 From: Diptesh Choudhuri Date: Tue, 18 Jul 2023 09:57:20 +0530 Subject: [PATCH] feat(backend): use hashed id for user id --- apps/backend/src/config.rs | 4 +++ apps/backend/src/main.rs | 6 ++--- apps/backend/src/miscellaneous/resolver.rs | 29 +++++++++++++++++----- docs/includes/backend-config-schema.ts | 2 ++ 4 files changed, 32 insertions(+), 9 deletions(-) diff --git a/apps/backend/src/config.rs b/apps/backend/src/config.rs index c1f3f0a176..648e5a4376 100644 --- a/apps/backend/src/config.rs +++ b/apps/backend/src/config.rs @@ -324,6 +324,9 @@ pub struct IntegrationConfig { /// every `n` hours. #[setting(default = 2)] pub pull_every: i32, + /// The salt used to hash user IDs. + #[setting(default = format!("{}", PROJECT_NAME))] + pub hasher_salt: String, } impl IsFeatureEnabled for FileStorageConfig { @@ -453,6 +456,7 @@ impl AppConfig { cl.file_storage.s3_access_key_id = gt(); cl.file_storage.s3_secret_access_key = gt(); cl.file_storage.s3_url = gt(); + cl.integration.hasher_salt = gt(); cl.movies.tmdb.access_token = gt(); cl.podcasts.listennotes.api_token = gt(); cl.shows.tmdb.access_token = gt(); diff --git a/apps/backend/src/main.rs b/apps/backend/src/main.rs index 72aa26fe9a..e427a7a394 100644 --- a/apps/backend/src/main.rs +++ b/apps/backend/src/main.rs @@ -214,7 +214,7 @@ async fn main() -> Result<()> { ) .allow_credentials(true); - let webhook_routes = Router::new().route("/jellyfin/:integration_slug", post(jellyfin_webhook)); + let webhook_routes = Router::new().route("/jellyfin/:user_hash_id", post(jellyfin_webhook)); let app_routes = Router::new() .nest("/webhooks", webhook_routes) @@ -486,14 +486,14 @@ async fn export( } async fn jellyfin_webhook( - Path(integration_slug): Path, + Path(user_hash_id): Path, Extension(media_service): Extension>, // DEV: jellyfin does not send the `Content-Type: application/json` header, // so we consume the body as a string payload: String, ) -> std::result::Result { media_service - .process_jellyfin_event(payload, integration_slug) + .process_jellyfin_event(payload, user_hash_id) .await .map_err(|e| { tracing::error!("{:?}", e); diff --git a/apps/backend/src/miscellaneous/resolver.rs b/apps/backend/src/miscellaneous/resolver.rs index d04427fe7e..d44b622342 100644 --- a/apps/backend/src/miscellaneous/resolver.rs +++ b/apps/backend/src/miscellaneous/resolver.rs @@ -3,7 +3,7 @@ use std::{ sync::Arc, }; -use anyhow::Result as AnyhowResult; +use anyhow::{anyhow, Result as AnyhowResult}; use apalis::{prelude::Storage as ApalisStorage, sqlite::SqliteStorage}; use argon2::{Argon2, PasswordHash, PasswordVerifier}; use async_graphql::{Context, Enum, Error, InputObject, Object, Result, SimpleObject, Union}; @@ -11,6 +11,7 @@ use chrono::{NaiveDate, Utc}; use cookie::{time::Duration as CookieDuration, time::OffsetDateTime, Cookie}; use enum_meta::Meta; use futures::TryStreamExt; +use harsh::Harsh; use http::header::SET_COOKIE; use itertools::Itertools; use markdown::{ @@ -450,10 +451,14 @@ fn create_cookie( Ok(()) } -fn get_hasher() -> Argon2<'static> { +fn get_password_hasher() -> Argon2<'static> { Argon2::default() } +fn get_id_hasher(salt: &str) -> Harsh { + Harsh::builder().length(10).salt(salt).build().unwrap() +} + #[derive(Default)] pub struct MiscellaneousQuery; @@ -2842,7 +2847,7 @@ impl MiscellaneousService { }; let user = user.unwrap(); let parsed_hash = PasswordHash::new(&user.password).unwrap(); - if get_hasher() + if get_password_hasher() .verify_password(password.as_bytes(), &parsed_hash) .is_err() { @@ -3197,7 +3202,9 @@ impl MiscellaneousService { timestamp: Utc::now(), settings: match input.lot { UserSinkIntegrationLot::Jellyfin => { - UserSinkIntegrationSetting::Jellyfin { slug: nanoid!(10) } + let slug = get_id_hasher(&self.config.integration.hasher_salt) + .encode(&[user_id.try_into().unwrap()]); + UserSinkIntegrationSetting::Jellyfin { slug } } }, }; @@ -3511,8 +3518,13 @@ impl MiscellaneousService { pub async fn process_jellyfin_event( &self, payload: String, - integration_slug: String, + user_hash_id: String, ) -> AnyhowResult<()> { + let user_id = get_id_hasher(&self.config.integration.hasher_salt).decode(user_hash_id)?; + let user_id = user_id + .get(0) + .ok_or(anyhow!("Incorrect hash id provided"))?; + dbg!(user_id); #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "PascalCase")] struct JellyfinWebhookItemUserDataPayload { @@ -3533,8 +3545,13 @@ impl MiscellaneousService { item: JellyfinWebhookItemPayload, } + // std::fs::write( + // "tmp/output.json", + // serde_json::to_string_pretty(&payload).unwrap(), + // ) + // .unwrap(); + let payload = serde_json::from_str::(&payload)?; - dbg!(&payload, &integration_slug); Ok(()) } } diff --git a/docs/includes/backend-config-schema.ts b/docs/includes/backend-config-schema.ts index e3a5015d9a..9fff83c0e7 100644 --- a/docs/includes/backend-config-schema.ts +++ b/docs/includes/backend-config-schema.ts @@ -92,6 +92,8 @@ export interface FileStorageConfig { } export interface IntegrationConfig { + /** The salt used to hash user IDs. */ + hasher_salt: string; /** * Sync data from [yank](/docs/guides/integrations.md) based integrations * every `n` hours.