Skip to content

refactor: oauth flow#726

Merged
steveiliop56 merged 6 commits into
mainfrom
refactor/oauth
Mar 22, 2026
Merged

refactor: oauth flow#726
steveiliop56 merged 6 commits into
mainfrom
refactor/oauth

Conversation

@steveiliop56
Copy link
Copy Markdown
Member

@steveiliop56 steveiliop56 commented Mar 21, 2026

Summary by CodeRabbit

  • Refactor

    • Unified OAuth provider handling under a single, consistent service model and presets for common providers.
  • New Features

    • Short-lived OAuth session cookie to track in-progress logins.
    • Automatic background cleanup of expired OAuth sessions.
  • Bug Fixes

    • Improved error handling and more reliable session cleanup during OAuth exchanges.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 21, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f9ec7855-aa76-4a78-830c-9866b5210bf5

📥 Commits

Reviewing files that changed from the base of the PR and between db73c56 and 7ae16d6.

📒 Files selected for processing (2)
  • internal/controller/oauth_controller.go
  • internal/service/auth_service.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • internal/controller/oauth_controller.go
  • internal/service/auth_service.go

📝 Walkthrough

Walkthrough

Centralizes OAuth into a session-based flow: adds an OAuth session cookie name, consolidates broker initialization, replaces per-provider implementations with a unified OAuthService + presets/extractors, adds pending-session state and cleanup to AuthService, renames two background routines, and updates controller and bootstrap wiring.

Changes

Cohort / File(s) Summary
Configuration
internal/config/config.go
Added OAuthSessionCookieName constant ("tinyauth-oauth").
App & Router Bootstrap
internal/bootstrap/app_bootstrap.go, internal/bootstrap/router_bootstrap.go
Added oauthSessionCookieName to app context and compute it in Setup; renamed background goroutines (heartbeatheartbeatRoutine, dbCleanupdbCleanupRoutine); pass session cookie name into OAuth controller construction.
Service Bootstrap
internal/bootstrap/service_bootstrap.go
Initialized OAuthBrokerService earlier and pass it into AuthService constructor (wiring change).
Auth Service
internal/service/auth_service.go
Added in-memory pending OAuth session type/store, mutex, limits, cleanup routine, and public OAuth methods (NewOAuthSession, GetOAuthURL, GetOAuthToken, GetOAuthUserinfo, GetOAuthService, EndOAuthSession); updated constructor to accept oauthBroker.
OAuth Service Layer
internal/service/oauth_service.go, internal/service/oauth_presets.go, internal/service/oauth_extractors.go
Introduced unified OAuthService with userinfo extractor hook, provider presets for Google/GitHub, and extractor/helpers including GitHub-specific extractor and generic HTTP helper.
Broker Changes
internal/service/oauth_broker_service.go
Reworked interface to OAuthServiceImpl (Name, NewRandom, GetAuthURL, GetToken, GetUserinfo), switched to preset constructors map, removed previous GetUser path.
Removed provider implementations
internal/service/google_oauth_service.go, internal/service/github_oauth_service.go, internal/service/generic_oauth_service.go
Deleted previous provider-specific implementations and helpers (Google, GitHub, generic).
Controller
internal/controller/oauth_controller.go
OAuthController config now includes OAuthSessionCookieName; removed direct broker dependency; handlers use AuthService session APIs and set/clear the OAuth session cookie; updated callback flow to require session cookie and defer EndOAuthSession.
Tests / Call Sites
internal/controller/proxy_controller_test.go, internal/controller/user_controller_test.go
Updated test setup calls to pass *service.OAuthBrokerService{} into NewAuthService where required.

Sequence Diagram

sequenceDiagram
    actor Client
    participant Controller as OAuth Controller
    participant Auth as AuthService
    participant Broker as OAuthBrokerService
    participant Provider as OAuth Provider

    rect rgba(100,150,200,0.5)
        Note over Client,Provider: Authorization request flow
        Client->>Controller: GET /oauth/authorize?provider=github
        Controller->>Auth: NewOAuthSession(provider)
        Auth->>Broker: GetService(provider) / NewRandom()
        Broker-->>Auth: service, state+verifier
        Auth-->>Controller: sessionId
        Controller->>Client: Set OAuth session cookie, Redirect to Provider (via Auth->Broker->GetAuthURL)
    end

    rect rgba(200,150,100,0.5)
        Note over Client,Auth: Callback & token exchange
        Provider-->>Client: Redirect /oauth/callback?code=...
        Client->>Controller: GET /oauth/callback?code=...
        Controller->>Controller: Read session cookie
        Controller->>Auth: GetOAuthToken(sessionId, code)
        Auth->>Broker: GetService(...).GetToken(code, verifier)
        Broker->>Provider: Exchange code -> token
        Broker-->>Auth: token
        Controller->>Auth: GetOAuthUserinfo(sessionId)
        Auth->>Broker: GetService(...).GetUserinfo(token)
        Broker-->>Auth: claims
        Controller->>Auth: EndOAuthSession(sessionId)
        Controller-->>Client: Create app session + Redirect
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested Labels

merge

Poem

🐰 Hoppity hop, a session tucked tight,

Cookies keep secrets through day and night.
Brokers trimmed, services sewn in one,
Cleaner hops now—OAuth’s more fun! 🥕

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'refactor: oauth flow' is vague and generic; it describes the general action (refactor) and broad area (oauth flow) but lacks specificity about what aspects of the OAuth flow were refactored or the key architectural changes involved. Consider a more specific title such as 'refactor: consolidate oauth session management into AuthService' or 'refactor: move OAuth state management from broker to auth service' to clarify the primary architectural change.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/oauth

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

You can customize the high-level summary generated by CodeRabbit.

Configure the reviews.high_level_summary_instructions setting to provide custom instructions for generating the high-level summary.

Comment thread internal/service/auth_service.go
Comment thread internal/service/oauth_service.go
@steveiliop56 steveiliop56 marked this pull request as ready for review March 21, 2026 18:38
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (1)
internal/service/oauth_extractors.go (1)

29-67: Unused url parameter in githubExtractor.

The url parameter is ignored since GitHub API endpoints are hardcoded. This is correct behavior for GitHub (which doesn't use a standard userinfo endpoint), but consider documenting this or using _ to make the intent explicit.

🔧 Optional: Make the unused parameter explicit
-func githubExtractor(client *http.Client, url string) (config.Claims, error) {
+func githubExtractor(client *http.Client, _ string) (config.Claims, error) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/service/oauth_extractors.go` around lines 29 - 67, The function
githubExtractor currently has an unused parameter url; to make intent explicit
and silence linters, rename the parameter to an anonymous identifier or a
clearly ignored name (e.g., replace the named parameter `url string` with `_
string` or `_url string`) in the githubExtractor function signature, or add a
one-line comment above the function noting that GitHub uses hardcoded endpoints
and the url parameter is intentionally unused; update only the githubExtractor
signature/comment so callers and linters understand the parameter is
intentionally ignored.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/controller/oauth_controller.go`:
- Line 76: The debug logging currently emits sensitive OAuth secrets
(session.State, session.Verifier and the callback authorization code) which can
be used to replay exchanges; update the logging in the OAuth session creation
and callback handlers (refer to the tlog.App.Debug() call that logs "Created new
OAuth session" and the callback log around line 149) to sanitize or omit secrets
before logging — e.g., build a sanitized copy of the session object that removes
or masks State and Verifier, and ensure the authorization code is never included
in any log message, logging only non-secret fields (or a redacted placeholder)
instead.
- Around line 158-160: Check the error returned by
controller.auth.GetOAuthUserinfo before accessing claims like user.Email: if err
!= nil handle/return the error (or log and abort the flow) instead of
proceeding, so functions like GetOAuthUserinfo do not lead to treating an error
as "missing email"; update the oauth handling code around GetOAuthUserinfo and
the subsequent user.Email check to first verify err and only read claims when
err is nil.

In `@internal/service/auth_service.go`:
- Around line 672-675: The cleanup goroutine is deadlocking because it uses
defer auth.oauthMutex.Unlock() inside the for range ticker.C loop so the mutex
stays locked after the first tick; change the loop to call
auth.oauthMutex.Lock() at the start of each iteration and then call
auth.oauthMutex.Unlock() explicitly at the end of that iteration (after the
cleanup work) instead of deferring, ensuring the lock is released before the
next tick and allowing OAuth session creation/callbacks/EndOAuthSession to
proceed; locate the auth.oauthMutex usage in the cleanup loop in auth_service.go
and remove the defer, placing a direct Unlock() after the cleanup steps.

In `@internal/service/oauth_service.go`:
- Around line 72-74: In OAuthService.GetToken remove the sensitive logging of
the authorization code and PKCE verifier: locate the tlog.App.Debug() call in
the GetToken method and either delete it or replace it with a non-secret message
(e.g., "Exchanging authorization code for token" or include only non-sensitive
context like request ID), ensuring you do NOT log or format the raw code or
verifier anywhere; keep the s.config.Exchange(s.ctx, code,
oauth2.VerifierOption(verifier)) call as-is.

---

Nitpick comments:
In `@internal/service/oauth_extractors.go`:
- Around line 29-67: The function githubExtractor currently has an unused
parameter url; to make intent explicit and silence linters, rename the parameter
to an anonymous identifier or a clearly ignored name (e.g., replace the named
parameter `url string` with `_ string` or `_url string`) in the githubExtractor
function signature, or add a one-line comment above the function noting that
GitHub uses hardcoded endpoints and the url parameter is intentionally unused;
update only the githubExtractor signature/comment so callers and linters
understand the parameter is intentionally ignored.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 11896ac4-9bf8-471d-a5f1-378a46df4f27

📥 Commits

Reviewing files that changed from the base of the PR and between d71a8e0 and 4a85a9d.

📒 Files selected for processing (15)
  • internal/bootstrap/app_bootstrap.go
  • internal/bootstrap/router_bootstrap.go
  • internal/bootstrap/service_bootstrap.go
  • internal/config/config.go
  • internal/controller/oauth_controller.go
  • internal/controller/proxy_controller_test.go
  • internal/controller/user_controller_test.go
  • internal/service/auth_service.go
  • internal/service/generic_oauth_service.go
  • internal/service/github_oauth_service.go
  • internal/service/google_oauth_service.go
  • internal/service/oauth_broker_service.go
  • internal/service/oauth_extractors.go
  • internal/service/oauth_presets.go
  • internal/service/oauth_service.go
💤 Files with no reviewable changes (3)
  • internal/service/google_oauth_service.go
  • internal/service/generic_oauth_service.go
  • internal/service/github_oauth_service.go

Comment thread internal/controller/oauth_controller.go Outdated
Comment thread internal/controller/oauth_controller.go
Comment thread internal/service/auth_service.go
Comment thread internal/service/oauth_service.go
@codecov
Copy link
Copy Markdown

codecov Bot commented Mar 21, 2026

Codecov Report

❌ Patch coverage is 0% with 266 lines in your changes missing coverage. Please review.
✅ Project coverage is 16.92%. Comparing base (dc3fa58) to head (7ae16d6).
⚠️ Report is 3 commits behind head on main.

Files with missing lines Patch % Lines
internal/service/auth_service.go 0.00% 108 Missing ⚠️
internal/service/oauth_extractors.go 0.00% 46 Missing ⚠️
internal/service/oauth_service.go 0.00% 39 Missing ⚠️
internal/controller/oauth_controller.go 0.00% 34 Missing ⚠️
internal/service/oauth_presets.go 0.00% 13 Missing ⚠️
internal/service/oauth_broker_service.go 0.00% 8 Missing ⚠️
internal/bootstrap/router_bootstrap.go 0.00% 7 Missing ⚠️
internal/bootstrap/service_bootstrap.go 0.00% 6 Missing ⚠️
internal/bootstrap/app_bootstrap.go 0.00% 5 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #726      +/-   ##
==========================================
+ Coverage   16.85%   16.92%   +0.06%     
==========================================
  Files          50       50              
  Lines        3820     3806      -14     
==========================================
  Hits          644      644              
+ Misses       3112     3098      -14     
  Partials       64       64              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (1)
internal/controller/oauth_controller.go (1)

154-160: ⚠️ Potential issue | 🟠 Major

Check GetOAuthUserinfo before reading claims.

Right now provider/network failures get flattened into “did not return an email”, and any extractor that returns partial claims on error could still let the flow continue with a bad state.

🩹 Minimal fix
 	user, err := controller.auth.GetOAuthUserinfo(sessionIdCookie)
+
+	if err != nil {
+		tlog.App.Error().Err(err).Msg("Failed to get OAuth userinfo")
+		c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", controller.config.AppURL))
+		return
+	}
 
 	if user.Email == "" {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/controller/oauth_controller.go` around lines 154 - 160,
GetOAuthUserinfo errors are not checked before reading claims, which flattens
provider/network failures into "did not return an email"; update the handler to
first check err returned by controller.auth.GetOAuthUserinfo(sessionIdCookie)
and if err != nil log the error via tlog.App.Error().Err(err).Msg(...) and
redirect to fmt.Sprintf("%s/error", controller.config.AppURL) (same flow as
missing email), returning early; only after err is nil should you inspect
user.Email and handle the empty-email case as currently done (references:
controller.auth.GetOAuthUserinfo, user.Email, tlog.App.Error(), c.Redirect,
controller.config.AppURL).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/controller/oauth_controller.go`:
- Around line 208-215: The code is persisting the provider from the incoming
route input (req.Provider) instead of the provider resolved earlier from the
pending OAuth session (the sessionIdCookie used during callback resolution),
which allows clients to spoof the provider; update the code that builds the
repository.Session (sessionCookie) to read the provider ID from the stored
pending OAuth session (the value retrieved when resolving
sessionIdCookie/pending OAuth session) and use that value for OAuthName/Provider
fields instead of req.Provider so the persisted session/audit reflects the
actual exchanged token's provider.
- Around line 123-131: After successfully reading sessionIdCookie (the result of
c.Cookie(controller.config.OAuthSessionCookieName)), immediately call defer
controller.auth.EndOAuthSession(sessionIdCookie) so the server-side OAuth
session (State/Verifier/token) is removed on every exit path; keep the existing
c.SetCookie(...) removal of the browser cookie but remove the duplicate
success-only cleanup currently done in the success path (the code that calls
controller.auth.EndOAuthSession(sessionIdCookie) at the end of the happy path)
to avoid double-handling.

In `@internal/service/auth_service.go`:
- Around line 61-62: The oauthPendingSessions map currently grows unbounded; add
a bounded eviction strategy by introducing a maxPendingSessions constant and
managing entries with an LRU or FIFO eviction structure (e.g., a linked list or
github.com/hashicorp/golang-lru) tied to oauthPendingSessions and protected by
oauthMutex; on Insert (the handler that creates /oauth/url/:provider entries)
check len(oauthPendingSessions) and evict oldest entries before inserting, or
refuse new inserts when the cap per client/IP is exceeded (track client key in
the session struct), and ensure all access points that read/write
oauthPendingSessions use oauthMutex consistently (including the code paths
referenced around the oauthPendingSessions declaration and the handlers at the
other noted locations).

---

Duplicate comments:
In `@internal/controller/oauth_controller.go`:
- Around line 154-160: GetOAuthUserinfo errors are not checked before reading
claims, which flattens provider/network failures into "did not return an email";
update the handler to first check err returned by
controller.auth.GetOAuthUserinfo(sessionIdCookie) and if err != nil log the
error via tlog.App.Error().Err(err).Msg(...) and redirect to
fmt.Sprintf("%s/error", controller.config.AppURL) (same flow as missing email),
returning early; only after err is nil should you inspect user.Email and handle
the empty-email case as currently done (references:
controller.auth.GetOAuthUserinfo, user.Email, tlog.App.Error(), c.Redirect,
controller.config.AppURL).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 734fbd39-03f2-4a73-b87b-0429616f7064

📥 Commits

Reviewing files that changed from the base of the PR and between 4a85a9d and db73c56.

📒 Files selected for processing (3)
  • internal/controller/oauth_controller.go
  • internal/service/auth_service.go
  • internal/service/oauth_service.go

Comment thread internal/controller/oauth_controller.go
Comment thread internal/controller/oauth_controller.go
Comment thread internal/service/auth_service.go
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.

2 participants