Skip to content

Comments

Google Reader API: Add feed icon URL endpoint#3195

Merged
fguillot merged 1 commit intominiflux:mainfrom
jocmp:jc/greader-icon-url
Mar 26, 2025
Merged

Google Reader API: Add feed icon URL endpoint#3195
fguillot merged 1 commit intominiflux:mainfrom
jocmp:jc/greader-icon-url

Conversation

@jocmp
Copy link
Contributor

@jocmp jocmp commented Mar 5, 2025

Adds an endpoint to the Google Reader integration to serve feed icon URLs.

The /icons/{externalIconID} route is an unauthorized endpoint. This is consistent with other feed readers like FreshRSS (icon source code) which uses crc32 to generate icon endpoints.

The icon endpoint is made public for consistency with the Google Reader API, and the icon is obfuscated using a randomly generated hex string value

The subscription output looks something like this:

{
  "subscriptions": [
    {
      "id": "feed/2",
      "title": "Miniflux",
      "categories": [
        {
          "id": "user/1/label/All",
          "label": "All",
          "type": "folder"
        }
      ],
      "url": "https://miniflux.app/feed.xml",
      "htmlUrl": "https://miniflux.app/",
      // New!
      "iconUrl": "http://localhost/reader/api/0/icons/e3481406d20b3982b48f3aa857c518bac7dc58da"
    }
]

Have you followed these guidelines?

@jocmp jocmp changed the title Google Reader API: Add feed icon URLs endpoint Google Reader API: Add feed icon URL endpoint Mar 5, 2025
@jvoisin
Copy link
Collaborator

jvoisin commented Mar 5, 2025

To obfuscate the Icon ID, the icon hash is used instead. The SHA-256 hash should generate a hexadecimal value which is url safe.

What's the point of this?

@jocmp
Copy link
Contributor Author

jocmp commented Mar 5, 2025

@jvoisin I went with this direction given a comment from the initial "Share article" PR #475 (comment)

Not sure if you need to expose the entryID to the public URL. The share token should be enough. Just to avoid exposing internal IDs publicly. The share code/token could have a unique constraints as well in the SQL migration.

So in the same way, this avoids exposing the internal Icon or Feed IDs for these endpoints.

@jocmp jocmp force-pushed the jc/greader-icon-url branch 5 times, most recently from d3b1264 to b0304de Compare March 5, 2025 03:52
builder := response.New(w, r)
builder.WithHeader("Content-Type", icon.MimeType)
builder.WithBody(icon.Content)
builder.Write()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to use HTTP caching similar to the web ui in feed_icon.go.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call. I copied over that code but happy to work out an abstraction if desired.

Testing in my browser, the second call correctly returns 304 Not Modified.

@jocmp jocmp force-pushed the jc/greader-icon-url branch 4 times, most recently from e52949d to d8a1df1 Compare March 5, 2025 05:32
@jvoisin
Copy link
Collaborator

jvoisin commented Mar 5, 2025

So in the same way, this avoids exposing the internal Icon or Feed IDs for these endpoints.

Then you might want to use something like an HMAC instead, as an attacker could simply download a bunch of favicon from popular feeds, and check if they exist on the navidrome instance.

@jocmp
Copy link
Contributor Author

jocmp commented Mar 5, 2025

HMAC could work. I'll take another pass at this.

@fguillot
Copy link
Member

fguillot commented Mar 6, 2025

HMAC requires a secret key. How would that be implemented?

@jocmp
Copy link
Contributor Author

jocmp commented Mar 6, 2025

There's a few options, really open to any:

Option 1: Integration level

Add a secret to the integrations table, e.g. googlereader_salt. From there, generate the HMAC on the subscription list request with the secret and the Icon ID:

ALTER TABLE integrations ADD COLUMN googlereader_salt text ...;

On result, check googlereader_salt + iconID pairs until there's a match. Read that match from the icons table and serve the binary result.

The upside is that this would only add a single row to the database, and only populate it if the Google Reader integration is enabled. The downside is that it's relatively resource intensive: several SQL queries, and re-computation of HMACs on each request.

Option 2: Icon level

With this option, the secret isn't added as an HMAC, it's generated alongside each icon either as a random string or something like a UUIDv4. The generated URL will use the unique, random alphanumeric value in the same way that the "share article":

ALTER TABLE icons ADD COLUMN share_code text ...;

The subscription list will use /icon/{shareCode} and on result the share code will make a simple lookup on the icon table based on the shareCode value.

The upside with this approach is that it's much lighter. No extra hashing involved. The downside is that it shifts the complexity from computation to database storage.

All things equal, option 2 would be my approach to avoid hashing and heavy SQL queries.

@jocmp jocmp force-pushed the jc/greader-icon-url branch 2 times, most recently from a8f0460 to 4f6b43b Compare March 21, 2025 21:16
@jocmp
Copy link
Contributor Author

jocmp commented Mar 21, 2025

Updated to use a generated integrations.googlereader_salt database value to generate an HMAC for each icon.

On fetch, the requested HMAC is compared with all generated icon HMACs.

@jocmp jocmp force-pushed the jc/greader-icon-url branch from 4f6b43b to 7518185 Compare March 22, 2025 01:19
@fguillot
Copy link
Member

The integrations table has a user_id column, meaning everything stored in this table should be associated with a specific user. Fetching all icons from the database, computing, and comparing the HMAC for each one of them to return a single icon does not seem very efficient in terms of CPU and memory usage.

I think we should keep it simple.

The media proxy feature also uses HMAC, and users can specify the secret value using the MEDIA_PROXY_PRIVATE_KEY config option. If no value is specified, a random secret is generated at startup. The same mechanism could be used for feed icons.

This approach could be generic enough to support both the Miniflux REST API and the Google Reader API in the future.

  1. Add a new config option FEED_ICON_PRIVATE_KEY similar to MEDIA_PROXY_PRIVATE_KEY
  2. The icon URL could be something like this: {some_base_path}/icon/{icon_hash}/{icon_hmac_signature}
  3. The icon HMAC signature could be generated like this: GenerateSHA256Hmac(iconSecretKey, iconHash)
  4. The icon endpoint verifies the HMAC signature with hmac.Equal(receivedHMAC, expectedHMAC) and returns a 403 if it doesn't match

Option 2 that you mentioned in the previous comment also looks fine to me. Using {base_path}/icon/{externalIconID} is probably easier to implement compared to the HMAC solution.

@jocmp
Copy link
Contributor Author

jocmp commented Mar 24, 2025

Hey @fguillot thanks for reviewing this again. If you're ok with option 2 - {base_path}/icon/{externalIconID} - I'll try that out. It will require a database backfill for existing icons but will take way less configuration and computation.

@jocmp jocmp force-pushed the jc/greader-icon-url branch 2 times, most recently from 7518185 to bae08be Compare March 25, 2025 03:16
_, err = tx.Exec(sql)
return err
},
func(tx *sql.Tx, _ string) (err error) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

similar to a previous users migration (link) this migration

  1. Finds empty external_id's
  2. Updates each external_id to a random hex value
  3. Commits

This could be done purely in SQL, but the alternatives are way more involved:

  1. Pull in pgcrypto which would require pulling in OpenSSL as a dep and enabling the extension
  2. Hand write a random function for a one-off (example from brandur.org)

@jocmp jocmp force-pushed the jc/greader-icon-url branch from 515f966 to 16d27ea Compare March 25, 2025 03:22
@jocmp
Copy link
Contributor Author

jocmp commented Mar 25, 2025

Code and description are updated with to reflect {base_path}/icons/{externalIconID} direction instead of using an HMAC.

@jocmp jocmp force-pushed the jc/greader-icon-url branch from 16d27ea to a67fadb Compare March 25, 2025 04:01
Adds an endpoint to the Google Reader integration to serve
feed icon URLs.
@jocmp jocmp force-pushed the jc/greader-icon-url branch from a67fadb to a3148db Compare March 25, 2025 04:02
@fguillot fguillot merged commit df8bc74 into miniflux:main Mar 26, 2025
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants