Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion api/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -501,6 +501,7 @@ async def authorize_discogs(
consumer_key=consumer_key,
consumer_secret=consumer_secret,
user_agent=_config.discogs_user_agent,
callback_url=_config.discogs_oauth_callback_url,
)
except DiscogsOAuthError as exc:
logger.error("❌ Failed to get Discogs request token", error=str(exc))
Expand All @@ -518,13 +519,15 @@ async def authorize_discogs(
await _redis.setex(redis_key, REDIS_OAUTH_STATE_TTL, state_data)

authorize_url = f"{DISCOGS_AUTHORIZE_URL}?oauth_token={state}"
logger.info("🔐 Discogs OAuth flow started", user_id=current_user.get("sub"))
callback_mode = "callback" if _config.discogs_oauth_callback_url else "oob"
logger.info("🔐 Discogs OAuth flow started", user_id=current_user.get("sub"), callback_mode=callback_mode)

return JSONResponse(
content={
"authorize_url": authorize_url,
"state": state,
"expires_in": REDIS_OAUTH_STATE_TTL,
"callback_mode": callback_mode,
}
)

Expand Down
11 changes: 10 additions & 1 deletion api/services/discogs.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,18 @@ async def request_oauth_token(
consumer_key: str,
consumer_secret: str,
user_agent: str,
callback_url: str | None = None,
) -> dict[str, str]:
"""Request an OAuth request token from Discogs.

Args:
consumer_key: Discogs app consumer key
consumer_secret: Discogs app consumer secret
user_agent: User-Agent string for Discogs API
callback_url: Public URL Discogs should redirect to after authorization.
When None, falls back to OAuth 1.0a "out-of-band" mode and Discogs
displays a verifier code for the user to copy back into the app.

Returns:
dict with 'oauth_token' and 'oauth_token_secret'

Expand All @@ -53,7 +62,7 @@ async def request_oauth_token(
url = DISCOGS_REQUEST_TOKEN_URL

oauth_params = {
"oauth_callback": "oob",
"oauth_callback": callback_url if callback_url else "oob",
"oauth_consumer_key": consumer_key,
"oauth_nonce": nonce,
"oauth_signature_method": "HMAC-SHA1",
Expand Down
6 changes: 6 additions & 0 deletions common/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,10 @@ class ApiConfig:
jwt_algorithm: str = "HS256"
jwt_expire_minutes: int = 30
discogs_user_agent: str = "discogsography/1.0 +https://github.com/SimplicityGuy/discogsography"
# Public URL Discogs should redirect to after the user authorizes the app.
# When unset, the OAuth 1.0a "out-of-band" flow is used and the user has to
# paste a verifier code shown by Discogs back into the app.
discogs_oauth_callback_url: str | None = None
cors_origins: list[str] | None = None
snapshot_ttl_days: int = 28
snapshot_max_nodes: int = 100
Expand Down Expand Up @@ -615,6 +619,7 @@ def from_env(cls) -> "ApiConfig":
"DISCOGS_USER_AGENT",
"discogsography/1.0 +https://github.com/SimplicityGuy/discogsography",
)
discogs_oauth_callback_url = getenv("DISCOGS_OAUTH_CALLBACK_URL") or None

cors_origins_env = getenv("CORS_ORIGINS")
cors_origins = [o.strip() for o in cors_origins_env.split(",") if o.strip()] if cors_origins_env else None
Expand Down Expand Up @@ -660,6 +665,7 @@ def from_env(cls) -> "ApiConfig":
jwt_algorithm=jwt_algorithm,
jwt_expire_minutes=jwt_expire_minutes,
discogs_user_agent=discogs_user_agent,
discogs_oauth_callback_url=discogs_oauth_callback_url,
neo4j_host=_build_neo4j_uri(),
neo4j_username=cast("str", neo4j_username),
neo4j_password=cast("str", neo4j_password),
Expand Down
17 changes: 12 additions & 5 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -317,11 +317,12 @@ REDIS_PASSWORD_FILE="/run/secrets/redis_password"

## JWT Configuration

| Variable | Description | Default | Required |
| -------------------- | ----------------------------------- | ------- | --------- |
| `JWT_SECRET_KEY` | HMAC-SHA256 signing secret | (none) | Yes (API) |
| `JWT_EXPIRE_MINUTES` | Token lifetime in minutes | `30` | No |
| `DISCOGS_USER_AGENT` | User-Agent for Discogs API requests | (none) | Yes (API) |
| Variable | Description | Default | Required |
| ------------------------------ | ----------------------------------------------------------------- | ------- | --------- |
| `JWT_SECRET_KEY` | HMAC-SHA256 signing secret | (none) | Yes (API) |
| `JWT_EXPIRE_MINUTES` | Token lifetime in minutes | `30` | No |
| `DISCOGS_USER_AGENT` | User-Agent for Discogs API requests | (none) | Yes (API) |
| `DISCOGS_OAUTH_CALLBACK_URL` | Public callback URL Discogs redirects to after the user authorizes the app. When unset, the OAuth flow falls back to the out-of-band (OOB) mode where the user copy/pastes a verifier code into the app. When set, the value must exactly match the **Callback URL** field on your Discogs developer app settings page and point at `oauth-discogs-callback.html` on the Explore frontend (e.g. `https://your-host/oauth-discogs-callback.html`). | (none) | No |

**Used By**: API

Expand Down Expand Up @@ -351,6 +352,12 @@ JWT_EXPIRE_MINUTES=1440 # 24 hours

# Discogs User-Agent (required for Discogs API)
DISCOGS_USER_AGENT="Discogsography/1.0 +https://github.com/SimplicityGuy/discogsography"

# Optional — public Discogs OAuth callback URL. When set, end users no longer
# have to copy/paste a verifier code; Discogs redirects directly back to the
# app. The URL must also be registered as the "Callback URL" on the Discogs
# developer app settings page.
DISCOGS_OAUTH_CALLBACK_URL="https://your-host/oauth-discogs-callback.html"
```

## Logging Configuration
Expand Down
Loading
Loading