fix(config): let TMI_OAUTH_CALLBACK_URL outrank the database row - #626
Merged
Conversation
On AWS, GET /oauth2/providers advertised redirect_uri=http://localhost:8080/oauth2/callback for Google, GitHub and Microsoft, so OAuth sign-in could not complete — even though the deployment's TMI_OAUTH_CALLBACK_URL was correctly set to https://api.tmi.dev/oauth2/callback. auth.oauth_callback_url was the one singleton setting that hardcoded Source: "config" instead of routing through settingSource(); the other 130 all do. Source feeds Explicit, and SettingsService.getConfigSetting discards any operational setting that is not Explicit, so GetString("auth.oauth_callback_url") always fell through to the database regardless of what the operator configured. The database held the localhost struct default, and because Handlers.oauthCallbackURL returns the runtime value whenever it is non-empty, that stale row won and the correct env-backed value was never reached. Route Source through settingSource("TMI_OAUTH_CALLBACK_URL") and record EnvVar, matching every other env-backed key. Only TMI_OAUTH_CALLBACK_URL is named, deliberately. auth/config.go also honors a legacy OAUTH_CALLBACK_URL, but this package's OAuthConfig.CallbackURL struct tag does not bind it -- so counting it would report "environment" while Value was still the localhost default, making that default Explicit and letting it outrank a correct database row. That inverts a fallback into an override and is a worse failure than the one being fixed; a test pins the behavior, and settingSource's doc comment now states the constraint. Tests cover all three paths: env set wins, unset defers to the database, and the unbound legacy name does not mark the default Explicit. Verified failing before the fix. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WnrVyySuyJFqcMxB8q3pNc
9 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Symptom
GET https://api.tmi.dev/oauth2/providersadvertised localhost for every provider:{"id":"google","redirect_uri":"http://localhost:8080/oauth2/callback"} {"id":"github","redirect_uri":"http://localhost:8080/oauth2/callback"} {"id":"microsoft","redirect_uri":"http://localhost:8080/oauth2/callback"}OAuth sign-in cannot complete — Google rejects the
redirect_urimismatch.The deployment's env var was correct the whole time: the
tmi-oauth-providersSecret hasTMI_OAUTH_CALLBACK_URL=https://api.tmi.dev/oauth2/callback, and the pod started 4 days after the Secret was last patched, so it definitely had that value. A stale-pod-env explanation is ruled out.Root cause
auth.oauth_callback_urlwas the one singleton setting that hardcodedSource: "config"rather than routing throughsettingSource()— the other 130 all do it correctly. Compare:server.base_urlSource: settingSource("TMI_SERVER_BASE_URL"), EnvVar: …✅auth.oauth_callback_urlSource: "config", noEnvVar❌SourcefeedsExplicit, andSettingsService.getConfigSettingdiscards any operational setting that is not Explicit (#415). SoGetString("auth.oauth_callback_url")always fell through to the database no matter what the operator set. The database held the localhost struct default, and sinceHandlers.oauthCallbackURLreturns the runtime value whenever it's non-empty, that stale row won and the correct env-backedh.config.OAuth.CallbackURLwas never reached.The chain, all confirmed by reading code and live state:
Fix
Route
SourcethroughsettingSource("TMI_OAUTH_CALLBACK_URL")and recordEnvVar, matching every other env-backed key.One deliberate subtlety
Only
TMI_OAUTH_CALLBACK_URLis named.auth/config.goalso honors a legacyOAUTH_CALLBACK_URL, and my first attempt counted both — that was wrong and would have been a worse regression. This package'sOAuthConfig.CallbackURLstruct tag binds onlyTMI_OAUTH_CALLBACK_URL, so a legacy-only operator would getValue= localhost default withSource="environment"→ the default becomes Explicit and outranks a correct DB row. A fallback inverted into an override. A test pins this, andsettingSource's doc comment now states the constraint.Tests
Three cases, all verified failing before the fix:
Source: "environment",Explicit: true,EnvVarrecordedmake lint0 issues ·make test-unit2447 passed / 0 failedDeploying this is what restores AWS sign-in
Note the ordering problem: the broken callback blocks interactive OAuth login, which blocks the admin API, which is the only way to hot-fix the DB row (service-account tokens are denied on
/admin/*per #399). So the DB-row route is not reachable while the bug is live — shipping this fix is the practical path.config-reference.mdis unchanged:auth.oauth_callback_urlsits in the Operational table, which has no Env var column.🤖 Generated with Claude Code
https://claude.ai/code/session_01WnrVyySuyJFqcMxB8q3pNc