From ec2b12fa895da11149e8c2ca556ffdba262fb89f Mon Sep 17 00:00:00 2001 From: Daniel Dietzler <36593685+danieldietzler@users.noreply.github.com> Date: Wed, 1 Jul 2026 17:32:14 +0200 Subject: [PATCH] feat: authenticated reddit requests (#365) --- backend/src/env.d.ts | 2 ++ backend/src/index.ts | 5 +++- backend/src/repositories/reddit.repository.ts | 26 ++++++++++++++++--- backend/src/workers/reddit-ingest.worker.ts | 3 ++- backend/tsconfig.json | 2 +- deployment/.env | 2 ++ .../modules/cloudflare/backend/variables.tf | 2 ++ .../modules/cloudflare/backend/workers.tf | 10 +++++++ 8 files changed, 46 insertions(+), 6 deletions(-) diff --git a/backend/src/env.d.ts b/backend/src/env.d.ts index 8146a47..44aaeef 100644 --- a/backend/src/env.d.ts +++ b/backend/src/env.d.ts @@ -9,4 +9,6 @@ interface WorkerEnv extends Omit { const influxProvider = new InfluxMetricsPushProvider(env.VMETRICS_DATA_API_URL, env.VMETRICS_DATA_WRITE_TOKEN); const metricsRepository = new MetricsPushRepository('immich_data_repository', {}, [influxProvider]); - const redditWorker = new RedditIngestWorker(metricsRepository, asEnvTag(env)); + const redditWorker = new RedditIngestWorker(metricsRepository, asEnvTag(env), { + clientId: env.REDDIT_OAUTH_CLIENT_ID, + clientSecret: env.REDDIT_OAUTH_CLIENT_SECRET, + }); const discordWorker = new DiscordIngestWorker(metricsRepository, asEnvTag(env)); try { diff --git a/backend/src/repositories/reddit.repository.ts b/backend/src/repositories/reddit.repository.ts index 08d1e39..90792eb 100644 --- a/backend/src/repositories/reddit.repository.ts +++ b/backend/src/repositories/reddit.repository.ts @@ -1,4 +1,5 @@ import fetchRetry from 'fetch-retry'; +import { Buffer } from 'node:buffer'; const fetch = fetchRetry(globalThis.fetch, { retries: 3, @@ -14,14 +15,33 @@ export interface RedditAboutResponse { } export class RedditRepository { - private readonly userAgent = 'Mozilla/5.0 (compatible; ImmichDataBot/1.0)'; + private readonly userAgent = 'web:app.immich.data:v1.0.0 (by u/immichapp)'; - async getSubredditData(subreddit: string): Promise { - const url = `https://www.reddit.com/r/${subreddit}/about.json`; + async getSubredditData( + subreddit: string, + oauthCredentials: { clientId: string; clientSecret: string }, + ): Promise { + const tokenRequest = await fetch('https://www.reddit.com/api/v1/access_token', { + method: 'POST', + headers: { + 'User-Agent': this.userAgent, + Authorization: `Basic ${Buffer.from(`${oauthCredentials.clientId}:${oauthCredentials.clientSecret}`).toString('base64')}`, + }, + body: new URLSearchParams({ grant_type: 'client_credentials' }), + }); + + if (!tokenRequest.ok) { + throw new Error(`Failed to authenticate against Reddit: ${tokenRequest.status} ${tokenRequest.statusText}`); + } + + const { access_token } = (await tokenRequest.json()) as { access_token: string }; + + const url = `https://oauth.reddit.com/r/${subreddit}/about.json`; const response = await fetch(url, { headers: { 'User-Agent': this.userAgent, + Authorization: `Bearer ${access_token}`, }, }); diff --git a/backend/src/workers/reddit-ingest.worker.ts b/backend/src/workers/reddit-ingest.worker.ts index a7e3cb2..5516f15 100644 --- a/backend/src/workers/reddit-ingest.worker.ts +++ b/backend/src/workers/reddit-ingest.worker.ts @@ -7,11 +7,12 @@ export class RedditIngestWorker { constructor( private metricsRepository: IMetricsPushRepository, private envTag: string, + private oauthCredentials: { clientId: string; clientSecret: string }, ) {} async fetchAndStoreCurrentMetrics(subreddit: string = 'immich') { try { - const data = await this.redditRepository.getSubredditData(subreddit); + const data = await this.redditRepository.getSubredditData(subreddit, this.oauthCredentials); const metric = new Metric('reddit_subscriber') .intField('total', data.subscribers) diff --git a/backend/tsconfig.json b/backend/tsconfig.json index bd17171..a70fa22 100644 --- a/backend/tsconfig.json +++ b/backend/tsconfig.json @@ -8,7 +8,7 @@ "module": "es2022" /* Specify what module code is generated. */, "baseUrl": "./", "moduleResolution": "Bundler" /* Specify how TypeScript looks up a file from a given module specifier. */, - "types": ["@cloudflare/workers-types"], + "types": ["@cloudflare/workers-types", "node"], "resolveJsonModule": true /* Enable importing .json files */, "allowJs": true /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */, "checkJs": false /* Enable error reporting in type-checked JavaScript files. */, diff --git a/deployment/.env b/deployment/.env index cd19a75..f35e71a 100644 --- a/deployment/.env +++ b/deployment/.env @@ -12,4 +12,6 @@ export TF_VAR_github_app_tofu_pem_file="op://tf/GITHUB_APP_IMMICH_TOFU/pkcs1" export TF_VAR_github_app_read_only_installation_id="op://tf/GITHUB_APP_IMMICH_READ_ONLY/installation_id" export TF_VAR_github_app_read_only_id="op://tf/GITHUB_APP_IMMICH_READ_ONLY/app_id" export TF_VAR_github_app_read_only_pem_file_pkcs8="op://tf/GITHUB_APP_IMMICH_READ_ONLY/pkcs8" +export TF_VAR_reddit_oauth_client_id="op://tf/REDDIT_OAUTH_CLIENT/client_id" +export TF_VAR_reddit_oauth_client_secret="op://tf/REDDIT_OAUTH_CLIENT/client_secret" export TF_VAR_env=$ENVIRONMENT diff --git a/deployment/modules/cloudflare/backend/variables.tf b/deployment/modules/cloudflare/backend/variables.tf index 10acdfb..08fffa3 100644 --- a/deployment/modules/cloudflare/backend/variables.tf +++ b/deployment/modules/cloudflare/backend/variables.tf @@ -10,3 +10,5 @@ variable "stage" { variable "github_app_read_only_id" {} variable "github_app_read_only_installation_id" {} variable "github_app_read_only_pem_file_pkcs8" {} +variable "reddit_oauth_client_id" {} +variable "reddit_oauth_client_secret" {} diff --git a/deployment/modules/cloudflare/backend/workers.tf b/deployment/modules/cloudflare/backend/workers.tf index 49ca1ac..5890e5d 100644 --- a/deployment/modules/cloudflare/backend/workers.tf +++ b/deployment/modules/cloudflare/backend/workers.tf @@ -165,6 +165,16 @@ resource "cloudflare_workers_script" "data_ingest_cron" { text = var.vmetrics_data_write_token } + secret_text_binding { + name = "REDDIT_OAUTH_CLIENT_ID" + text = var.reddit_oauth_client_id + } + + secret_text_binding { + name = "REDDIT_OAUTH_CLIENT_SECRET" + text = var.reddit_oauth_client_secret + } + compatibility_date = "2025-09-17" compatibility_flags = ["nodejs_compat"] }