Skip to content

Take Hardcover's new API keys, with or without "Bearer" - #1655

Merged
GianniCarlo merged 1 commit into
developfrom
fix/hardcover-api-key
Oct 18, 2026
Merged

GianniCarlo merged 1 commit into
developfrom
fix/hardcover-api-key

Conversation

@GianniCarlo

Copy link
Copy Markdown
Collaborator

Hardcover's API page now hands out bare API keys (hc_pat_…) instead of the old Bearer eyJ… token, and the Hardcover settings placeholder still showed the old format. The API itself still requires the Bearer scheme, and the app sent whatever was saved as the whole Authorization header, so a key pasted the way Hardcover shows it now was rejected.

What changes

  • Saved bare, sent with the scheme. HardcoverService.authorization keeps the bare key; HardcoverService.apiKey(from:) trims the input and drops a leading Bearer (any case, any whitespace), and every GraphQL request sends Authorization: Bearer <key>.
  • Existing links keep working. Tokens saved with their Bearer prefix read without it, so their header is byte-for-byte what it was. Nothing is migrated: the Keychain value is rewritten bare the next time Settings is saved.
  • Settings. The placeholder shows hc_pat_…. Save stays disabled for input that's only Bearer or whitespace (before, Bearer alone could be saved and sent).
  • The footer ("You can find your Hardcover API access at https://hardcover.app/account/api.") is unchanged: the page there is still titled "API Access".
  • CLAUDE.md's Hardcover section notes the stored format and the header.

How Hardcover answers (probed with fake keys)

Authorization header Response
hc_pat_… (what the app sent for a key pasted as shown) Invalid Authorization format. Use 'Bearer <token>'
Bearer hc_pat_… / bearer hc_pat_… Invalid or expired token (format accepted)
eyJ… without the scheme Invalid Authorization format
Bearer eyJ… (the old tokens) Token is not associated with a user (format accepted)

Testing

  • HardcoverServiceTests (new, 8 tests) pass on Xcode 27.1, iOS 27.0 simulator: a bare key is kept; Bearer/bearer/BEARER with spaces, several spaces or a tab is dropped; surrounding whitespace and newlines are trimmed; Bearer alone and whitespace alone come out empty; a key that only starts with "bearer" is kept; saving Bearer hc_pat_… stores the bare key; a token saved as Bearer eyJ… reads bare; saving Bearer removes the token.
  • Not tried: linking a real Hardcover account with an hc_pat_ key on a device.

Hardcover's API page now hands out bare keys (hc_pat_…), but the API still
requires `Authorization: Bearer <key>`, and the app sent the saved text as
the whole header. A key pasted as shown was rejected ("Invalid Authorization
format"). The key is now saved bare (a pasted "Bearer " is dropped) and every
request adds the scheme; tokens saved with it before read without it, so
existing links keep working. The placeholder shows the new key format.
ThemedSection {
TextField(
"Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXV…",
"hc_pat_…",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 INFO — This placeholder is the TextField's only label, so VoiceOver announces the empty field as "hc_pat_…". That tells a VoiceOver user very little (the old Bearer eyJ… placeholder had the same problem). Since this line is changing anyway, consider adding .accessibilityLabel("hardcover_api_access_title".localized) to the TextField, so the field is announced by its section title while the placeholder stays as a format hint.

@github-actions

Copy link
Copy Markdown

✅ Claude PR Review — PASS

Hardcover's API key is now stored without its Bearer prefix, and Authorization: Bearer <key> is added to every GraphQL request. HardcoverService.apiKey(from:) trims input and drops a leading Bearer in any case. Tokens already saved as Bearer eyJ… read back bare and send the same header as before. Settings Save is disabled for input that is only Bearer or whitespace, and new HardcoverServiceTests cover the cases.

I checked every caller of authorization: the Settings view model, the item-details section, the stub in ImportOperationTests, and the Keychain observer, which still starts and stops library tracking on set and remove. The new tests can't hit the library-service mock's unset publishers, because the mock Keychain never publishes a change.

Threading: the Settings save still runs on the main actor. The new header logic is a pure helper and adds no shared state or subscriptions. The PR doesn't touch player or audio-session lifecycle, CoreData/SwiftData, or entitlements. Only a minor accessibility note.

Findings: 1 info

Model claude-opus-5-5 · run log · 1 new · 0 carried over · 0 resolved · advisory (a human should still review). Findings are de-duplicated across pushes; an earlier finding closes only when the verification pass judges it against the current code — fixed, no longer applicable, accepted by a maintainer, or a duplicate of a finding reported on this push.

@GianniCarlo
GianniCarlo merged commit 7af6d5e into develop Oct 18, 2026
3 checks passed
@GianniCarlo
GianniCarlo deleted the fix/hardcover-api-key branch October 18, 2026 13:28

This branch was successfully deployed

1 active deployment
reviewer — 2f8c124d Deployed Oct 18, 2026 by GianniCarlo via review #292
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant