Skip to content

Conversation

@shuhuiluo
Copy link
Collaborator

@shuhuiluo shuhuiluo commented Nov 22, 2025

This commit fixes critical bugs and improves the OAuth token validation flow:

Critical Fixes:

  • Fix app crash when OAuth token is revoked (401 errors now handled gracefully)
  • Fix parameter order bug in oauth-helpers.ts (channelId/spaceId were swapped)
  • Add proactive token validation before subscription attempts

OAuth Token Validation:

  • Add TokenStatus enum (Valid, Invalid, NotLinked, Unknown) for state tracking
  • Add validateToken() method to GitHubOAuthService with automatic cleanup
  • Auto-delete revoked/expired tokens from database on 401 detection
  • Handle 403 separately (insufficient scope/rate limits) without deleting tokens
  • Widen try/catch scope to catch DB/decryption errors as Unknown (never throws)
  • Update subscription handler to validate tokens before use with switch statement
  • Show tailored user messages for each TokenStatus state

Error Handling Improvements:

  • Wrap errors with { cause } to preserve debugging info
  • Add try/catch around getUserProfile() in subscription service
  • Return SubscribeFailure instead of throwing unhandled errors
  • Consistent error handling across all OAuth client methods

Code Quality:

  • Refactor UserOAuthClient from stateless class to pure functions
  • Move src/services/user-oauth-client.ts → src/api/user-oauth-client.ts
  • Refactor validateToken() to reuse getUserOctokit() (eliminates duplication)
  • Generate authUrl once in closure to avoid repetition across error cases
  • Replace if/else with switch statement for exhaustiveness checking
  • Convert let+if/else to const+ternary for deliveryInfo
  • Document side effect: validateToken() auto-deletes tokens on 401

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added OAuth helpers to validate repositories and fetch authenticated user profiles.
    • Introduced token status (NotLinked / Invalid / Valid / Unknown) for clearer connection state.
  • Bug Fixes

    • Improved subscription messaging for unlinked, expired/revoked, and unknown token states.
    • Safer token validation that handles transient errors without breaking flow.
  • Refactor

    • Removed legacy user OAuth client and simplified subscription initialization.
  • Chores

    • Minor argument ordering tweak in authorization URL helper.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Nov 22, 2025

Walkthrough

Adds a new module src/api/user-oauth-client.ts exporting typed helpers validateRepository and getUserProfile and their types; removes the old src/services/user-oauth-client.ts. Introduces TokenStatus and validateToken in src/services/github-oauth-service.ts (replacing isLinked) with new handling for 401/403 cases. Updates SubscriptionService to remove the injected user client and call the standalone OAuth helpers, with explicit failure-return paths. Modifies github-subscription-handler to branch messaging based on TokenStatus. Updates wiring in src/index.ts and swaps argument order in src/utils/oauth-helpers.ts.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Heterogeneous changes across API surface, service behavior, handler messaging, and startup wiring require separate reasoning.
  • Areas to focus on:
    • src/api/user-oauth-client.ts — Octokit instantiation, field mappings, error handling with causes.
    • src/services/github-oauth-service.ts — semantics of TokenStatus, token deletion on 401, and handling of 403/other errors.
    • src/services/subscription-service.ts — removal of injected dependency, changed constructor signature, and new failure-return shapes.
    • src/handlers/github-subscription-handler.ts — TokenStatus branching and authorization URL generation/messages.
    • src/index.ts — updated SubscriptionService construction and dependency wiring.
    • src/utils/oauth-helpers.ts — swapped parameter order for getAuthorizationUrl calls.

Possibly related PRs

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main objectives: handling OAuth token revocation (401 errors) and improving error handling with graceful degradation, token validation, and better error messages.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/oauth-token-validation-and-refactoring

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

Copy link

@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: 0

🧹 Nitpick comments (4)
src/api/user-oauth-client.ts (1)

32-63: Tighten error typing instead of relying on any + eslint-disable

Both helpers inspect error.status using (error as any)?.status and disable no-unsafe-member-access. Consider keeping error as unknown and using a small type guard (e.g., check typeof error === "object" and "status" in error) so you can drop the eslint-disable and avoid any while preserving the same behavior.

Also applies to: 72-88

src/services/github-oauth-service.ts (1)

333-369: Token validation and cleanup behavior looks correct; consider minor reuse improvements

The flow of:

  • returning false if no token,
  • deleting the record and returning false on 401,
  • and treating other errors as transient (log + true)

matches the intended behavior and is safe against races. You might optionally reuse existing helpers (e.g., centralize user-scoped client creation) or have validateToken return a richer status (e.g., "not-linked" | "invalid" | "valid") to avoid the separate isLinked check in callers, but that’s a nice-to-have.

src/services/subscription-service.ts (1)

119-133: Graceful handling of profile lookup errors; consider user-facing messaging

Wrapping getUserProfile in try/catch and returning a SubscribeFailure instead of throwing aligns with the PR’s error-handling goals. One optional refinement: for unexpected/internal errors (e.g., network issues), you might want a more generic user-facing message while logging the detailed error elsewhere, to avoid surfacing low-level details directly to end users.

src/handlers/github-subscription-handler.ts (1)

124-136: Two‑step OAuth check (linked + valid) matches intended UX; minor redundancy is acceptable

The sequence isLinkedvalidateToken lets you distinguish “never connected” from “was connected but token is now invalid,” which is important for tailored messages. Note that both methods hit the tokens table, so there’s some redundant DB work; if this becomes hot, you could have validateToken return a more detailed status and skip the separate isLinked call. For now the clarity/UX trade-off seems reasonable.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e8baca7 and d31a795.

📒 Files selected for processing (7)
  • src/api/user-oauth-client.ts (1 hunks)
  • src/handlers/github-subscription-handler.ts (1 hunks)
  • src/index.ts (0 hunks)
  • src/services/github-oauth-service.ts (1 hunks)
  • src/services/subscription-service.ts (2 hunks)
  • src/services/user-oauth-client.ts (0 hunks)
  • src/utils/oauth-helpers.ts (1 hunks)
💤 Files with no reviewable changes (2)
  • src/services/user-oauth-client.ts
  • src/index.ts
🧰 Additional context used
🧬 Code graph analysis (3)
src/services/github-oauth-service.ts (2)
src/db/index.ts (1)
  • db (57-57)
src/db/schema.ts (1)
  • githubUserTokens (20-43)
src/services/subscription-service.ts (1)
src/api/user-oauth-client.ts (4)
  • UserProfile (19-22)
  • getUserProfile (72-89)
  • RepositoryInfo (6-14)
  • validateRepository (32-64)
src/handlers/github-subscription-handler.ts (1)
src/services/github-oauth-service.ts (1)
  • isLinked (328-331)
🪛 ESLint
src/api/user-oauth-client.ts

[error] 1-1: Unable to resolve path to module '@octokit/rest'.

(import-x/no-unresolved)

🔇 Additional comments (5)
src/utils/oauth-helpers.ts (1)

16-20: Authorization URL call now matches GitHubOAuthService.getAuthorizationUrl signature

Passing (userId, channelId, spaceId, "subscribe", { repo, eventTypes }) aligns with the service’s parameter order and fixes the previous channel/space swap. Looks good.

src/services/subscription-service.ts (2)

3-8: Good move to shared OAuth client module

Importing getUserProfile / validateRepository and the shared UserProfile / RepositoryInfo types centralizes GitHub OAuth behavior and keeps this service focused on subscription orchestration. The direction looks solid.


136-149: Repository validation failure mapping aligns with new flow

Catching errors from validateRepository and returning a structured SubscribeFailure (re-using the helper’s descriptive messages for 403/404) keeps the subscription handler simple and prevents unhandled exceptions from bubbling out of createSubscription. This matches the intended OAuth-first, failure-as-data design.

src/handlers/github-subscription-handler.ts (1)

100-122: Nice reuse via sendOAuthRedirect closure for OAuth messaging

The helper cleanly centralizes URL generation and the two message variants (“not linked” vs “expired”), avoiding duplication and keeping the main flow readable while still passing through the repo + eventTypes context in redirectData.

src/api/user-oauth-client.ts (1)

1-1: @octokit/rest dependency is already installed—no action required

The dependency is properly declared in package.json with version "^22.0.1" as a regular dependency. The import will resolve correctly. If ESLint continues reporting it as unresolved, this indicates a linting or TypeScript configuration issue, not a missing dependency—verify your .eslintignore, ESLint plugin settings, or tsconfig.json path resolution if the error persists in CI.

Likely an incorrect or invalid review comment.

Copy link

@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: 0

🧹 Nitpick comments (4)
src/handlers/github-subscription-handler.ts (2)

103-124: TokenStatus-based preflight check is solid; consider explicit handling per status

The preflight validateToken call and tailored messaging for NotLinked vs “expired/revoked” tokens look correct and align with the PR goals. The only small future-proofing tweak would be to switch to an explicit switch (tokenStatus) so that any new TokenStatus values (e.g., Unknown) don’t automatically get mapped into the “expired token” branch without a conscious choice.


156-160: Unified deliveryInfo reads well; ensure installUrl is always set for polling mode

The ternary-based deliveryInfo construction is clearer than inlined string building and keeps the success message tidy. Just make sure SubscriptionService always provides a non-empty installUrl whenever deliveryMode !== "webhook" so you never end up interpolating undefined into the hint text.

src/services/github-oauth-service.ts (2)

35-42: TokenStatus enum API is clear and easy to consume

The three-way split into NotLinked, Invalid, and Valid with readable string values makes downstream handling straightforward and keeps logs/telemetry easy to interpret. Only minor naming/UX nit: your UI copy talks about “expired/revoked” tokens while the enum uses Invalid; if you ever expose these strings externally, consider aligning terminology, but it’s not required.


331-367: Consider refactoring validateToken to reuse getUserOctokit and handle 403 responses separately

The code duplication concern is valid: validateToken (line 344) duplicates the Octokit instantiation pattern from getUserOctokit (lines 296–298). Refactoring to reuse getUserOctokit would centralize token-to-client construction.

Additionally, the web search confirms that GitHub's GET /user endpoint returns both 401 and 403 as authentication error codes, with 403 indicating insufficient scope, temporary lockouts, or rate-limit blocks. The current logic treats all non-401 errors as Valid and logs a warning, which could mask 403 responses that indicate an unusable token. Introducing TokenStatus.Unknown (or similar) to distinguish transient errors from confirmed validity would provide crisper UX and prevent callers from assuming a token is valid when validation was inconclusive.

However, since getUserOctokit returns null (not TokenStatus.NotLinked), reusing it requires handling the null case differently.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d31a795 and dbd741d.

📒 Files selected for processing (2)
  • src/handlers/github-subscription-handler.ts (3 hunks)
  • src/services/github-oauth-service.ts (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/services/github-oauth-service.ts (2)
src/db/index.ts (1)
  • db (57-57)
src/db/schema.ts (1)
  • githubUserTokens (20-43)
🔇 Additional comments (1)
src/handlers/github-subscription-handler.ts (1)

4-7: Typed import of GitHubOAuthService is idiomatic and keeps runtime bundle lean

Using TokenStatus as a value import and GitHubOAuthService as a type-only import is a clean pattern; no issues here.

Copy link

@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: 0

🧹 Nitpick comments (3)
src/handlers/github-subscription-handler.ts (2)

103-142: Token pre‑validation and user messaging look solid; consider a defensive default branch

The flow correctly blocks subscription attempts when the token is not valid and gives tailored copy for not‑linked, invalid, and unknown states, which matches the new TokenStatus contract.

To future‑proof this against additional TokenStatus values, consider adding a defensive default branch that also returns early (e.g., log and treat as Unknown) so new statuses can’t accidentally fall through and proceed to subscription.


172-178: Guard against missing installUrl in polling mode

deliveryInfo assumes that result.installUrl is always set when deliveryMode !== "webhook". If installUrl can ever be undefined for polling responses, the success message will render a broken markdown link.

You could defensively handle this with a small guard:

-  const deliveryInfo =
-    result.deliveryMode === "webhook"
-      ? "⚡ Real-time webhook delivery enabled!"
-      : "⏱️ Events are checked every 5 minutes (polling mode)\n\n" +
-        `💡 **Want real-time notifications?** [Install the GitHub App](${result.installUrl})`;
+  const deliveryInfo =
+    result.deliveryMode === "webhook"
+      ? "⚡ Real-time webhook delivery enabled!"
+      : result.installUrl
+          ? "⏱️ Events are checked every 5 minutes (polling mode)\n\n" +
+            `💡 **Want real-time notifications?** [Install the GitHub App](${result.installUrl})`
+          : "⏱️ Events are checked every 5 minutes (polling mode)";

If the contract guarantees installUrl in this case, just confirm that with tests and leave as is.

src/services/github-oauth-service.ts (1)

35-43: TokenStatus model and validation logic are on point; consider widening the try/catch scope

The TokenStatus enum and validateToken behavior (NotLinked → no token, Valid → authenticated call ok, Invalid → 401 with local deletion, Unknown → 403 or other errors) are a good fit for the handler logic and the PR’s revoked‑token handling goal.

One improvement: getUserOctokit(townsUserId) is called outside the try block, so any DB or decryption failures will bubble up as uncaught errors instead of being surfaced as TokenStatus.Unknown. If you want validateToken to be the single entrypoint that never throws for consumer code, you can wrap that call as well:

-  async validateToken(townsUserId: string): Promise<TokenStatus> {
-    const octokit = await this.getUserOctokit(townsUserId);
-    if (!octokit) {
-      return TokenStatus.NotLinked;
-    }
-
-    try {
+  async validateToken(townsUserId: string): Promise<TokenStatus> {
+    try {
+      const octokit = await this.getUserOctokit(townsUserId);
+      if (!octokit) {
+        return TokenStatus.NotLinked;
+      }
+
       await octokit.users.getAuthenticated();
       return TokenStatus.Valid;
     } catch (error) {
       // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
       const status = (error as any)?.status;
@@
       console.warn(`Error validating token for ${townsUserId}:`, error);
       return TokenStatus.Unknown;
     }
   }

This keeps the external contract (“returns a TokenStatus instead of throwing”) consistent even when storage or crypto issues occur, while still logging underlying failures.

Also applies to: 334-370

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dbd741d and 4f8a71e.

📒 Files selected for processing (2)
  • src/handlers/github-subscription-handler.ts (3 hunks)
  • src/services/github-oauth-service.ts (4 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/services/github-oauth-service.ts (2)
src/db/index.ts (1)
  • db (57-57)
src/db/schema.ts (1)
  • githubUserTokens (20-43)
🔇 Additional comments (2)
src/handlers/github-subscription-handler.ts (1)

4-7: OAuth service import wiring looks good

Importing TokenStatus and using a type-only import for the OAuth service keeps this handler decoupled from construction details and aligns with the new service API. No issues here.

src/services/github-oauth-service.ts (1)

290-297: Use of decrypted token for user client and revocation is consistent

getUserToken already returns decrypted accessToken/refreshToken, so using token.accessToken both when constructing the user-scoped client and when revoking the token ensures callers don’t need to know about encryption details. This keeps crypto concerns nicely encapsulated inside the service.

Also applies to: 305-316

Copy link

@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: 1

🧹 Nitpick comments (1)
src/handlers/github-subscription-handler.ts (1)

103-149: Well-structured token validation with clear user messaging.

The switch statement handles all TokenStatus cases appropriately:

  • NotLinked: Clear prompt to connect GitHub
  • Invalid: Explains token expiration/revocation with reconnection link
  • Unknown: Helpful guidance about temporary issues (rate limiting)
  • Default: Exhaustiveness check ensures compile-time safety

Minor optimization opportunity: The authorization URL is generated at line 107 before the switch statement, meaning it's created even for the Valid case where it's not needed. Consider moving the URL generation inside each case to avoid unnecessary OAuth state creation.

Apply this diff to defer URL generation:

  // Check if user has linked their GitHub account and token is valid
  const tokenStatus = await oauthService.validateToken(userId);

  if (tokenStatus !== TokenStatus.Valid) {
-   const authUrl = await oauthService.getAuthorizationUrl(
-     userId,
-     channelId,
-     spaceId,
-     "subscribe",
-     { repo, eventTypes }
-   );
-
    switch (tokenStatus) {
      case TokenStatus.NotLinked:
+       const authUrlNotLinked = await oauthService.getAuthorizationUrl(
+         userId,
+         channelId,
+         spaceId,
+         "subscribe",
+         { repo, eventTypes }
+       );
        await handler.sendMessage(
          channelId,
          `🔐 **GitHub Account Required**\n\n` +
            `To subscribe to repositories, you need to connect your GitHub account.\n\n` +
-           `[Connect GitHub Account](${authUrl})`
+           `[Connect GitHub Account](${authUrlNotLinked})`
        );
        return;

      case TokenStatus.Invalid:
+       const authUrlInvalid = await oauthService.getAuthorizationUrl(
+         userId,
+         channelId,
+         spaceId,
+         "subscribe",
+         { repo, eventTypes }
+       );
        await handler.sendMessage(
          channelId,
          `⚠️ **GitHub Token Expired**\n\n` +
            `Your GitHub token has expired or been revoked. Please reconnect your account.\n\n` +
-           `[Reconnect GitHub Account](${authUrl})`
+           `[Reconnect GitHub Account](${authUrlInvalid})`
        );
        return;

      case TokenStatus.Unknown:
+       const authUrlUnknown = await oauthService.getAuthorizationUrl(
+         userId,
+         channelId,
+         spaceId,
+         "subscribe",
+         { repo, eventTypes }
+       );
        await handler.sendMessage(
          channelId,
          `⚠️ **Unable to Verify GitHub Connection**\n\n` +
            `We couldn't verify your GitHub token. This could be temporary (rate limiting) or indicate a connection issue.\n\n` +
-           `Please try again in a few moments, or [reconnect your account](${authUrl}) if the problem persists.`
+           `Please try again in a few moments, or [reconnect your account](${authUrlUnknown}) if the problem persists.`
        );
        return;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4f8a71e and 77291e3.

📒 Files selected for processing (2)
  • src/handlers/github-subscription-handler.ts (3 hunks)
  • src/services/github-oauth-service.ts (4 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/services/github-oauth-service.ts (2)
src/db/index.ts (1)
  • db (57-57)
src/db/schema.ts (1)
  • githubUserTokens (20-43)
🔇 Additional comments (2)
src/services/github-oauth-service.ts (1)

35-43: LGTM! Well-defined token status enum.

The TokenStatus enum provides clear, semantic values for different token validation states. The four states (NotLinked, Invalid, Valid, Unknown) appropriately cover all scenarios in the validation flow.

src/handlers/github-subscription-handler.ts (1)

180-184: LGTM! Cleaner delivery info formatting.

The refactor consolidates the conditional logic into a single ternary expression, making the code more concise while maintaining the same functionality.

This commit fixes critical bugs and improves the OAuth token validation flow:

**Critical Fixes:**
- Fix app crash when OAuth token is revoked (401 errors now handled gracefully)
- Fix parameter order bug in oauth-helpers.ts (channelId/spaceId were swapped)
- Add proactive token validation before subscription attempts

**OAuth Token Validation:**
- Add TokenStatus enum (Valid, Invalid, NotLinked, Unknown) for state tracking
- Add validateToken() method to GitHubOAuthService with automatic cleanup
- Auto-delete revoked/expired tokens from database on 401 detection
- Handle 403 separately (insufficient scope/rate limits) without deleting tokens
- Widen try/catch scope to catch DB/decryption errors as Unknown (never throws)
- Update subscription handler to validate tokens before use with switch statement
- Show tailored user messages for each TokenStatus state

**Error Handling Improvements:**
- Wrap errors with { cause } to preserve debugging info
- Add try/catch around getUserProfile() in subscription service
- Return SubscribeFailure instead of throwing unhandled errors
- Consistent error handling across all OAuth client methods

**Code Quality:**
- Refactor UserOAuthClient from stateless class to pure functions
- Move src/services/user-oauth-client.ts → src/api/user-oauth-client.ts
- Refactor validateToken() to reuse getUserOctokit() (eliminates duplication)
- Generate authUrl once in closure to avoid repetition across error cases
- Replace if/else with switch statement for exhaustiveness checking
- Convert let+if/else to const+ternary for deliveryInfo
- Document side effect: validateToken() auto-deletes tokens on 401

**Files Changed:**
- src/api/user-oauth-client.ts (new) - Pure functions for user OAuth API calls
- src/services/github-oauth-service.ts - Add TokenStatus enum and validateToken()
- src/handlers/github-subscription-handler.ts - Add token validation with switch
- src/services/subscription-service.ts - Add error handling, use functions
- src/utils/oauth-helpers.ts - Fix parameter order bug
- src/index.ts - Remove UserOAuthClient instantiation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@shuhuiluo shuhuiluo force-pushed the fix/oauth-token-validation-and-refactoring branch from 77291e3 to b613203 Compare November 23, 2025 06:30
Copy link

@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: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 77291e3 and b613203.

📒 Files selected for processing (7)
  • src/api/user-oauth-client.ts (1 hunks)
  • src/handlers/github-subscription-handler.ts (3 hunks)
  • src/index.ts (0 hunks)
  • src/services/github-oauth-service.ts (4 hunks)
  • src/services/subscription-service.ts (2 hunks)
  • src/services/user-oauth-client.ts (0 hunks)
  • src/utils/oauth-helpers.ts (1 hunks)
💤 Files with no reviewable changes (2)
  • src/index.ts
  • src/services/user-oauth-client.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/utils/oauth-helpers.ts
  • src/services/subscription-service.ts
🧰 Additional context used
🧬 Code graph analysis (1)
src/services/github-oauth-service.ts (2)
src/db/index.ts (1)
  • db (57-57)
src/db/schema.ts (1)
  • githubUserTokens (20-43)
🪛 ESLint
src/api/user-oauth-client.ts

[error] 1-1: Unable to resolve path to module '@octokit/rest'.

(import-x/no-unresolved)

🔇 Additional comments (4)
src/handlers/github-subscription-handler.ts (2)

103-149: LGTM! Excellent token status handling with exhaustiveness check.

The token validation flow correctly branches on all TokenStatus values with user-friendly messaging for each scenario. The TypeScript exhaustiveness check at lines 143-147 ensures compile-time safety if new status values are added in the future.


180-191: LGTM! Improved readability by extracting delivery info logic.

The refactor from inline conditional to a named constant improves code readability without changing functionality.

src/services/github-oauth-service.ts (2)

328-336: LGTM! JSDoc now clearly documents the token deletion side effect.

The updated documentation addresses the previous review concern by explicitly noting that invalid tokens are automatically deleted from the database on 401 responses. This makes the side effect clear to callers.


337-374: LGTM! Correct implementation of token validation with appropriate error handling.

The implementation properly distinguishes between:

  • 401 (revoked/invalid): Deletes token and returns Invalid
  • 403 (rate limit/scope): Preserves token and returns Unknown for retry
  • Other errors: Returns Unknown with logging for debugging

The deletion operation on line 355-357 is correctly awaited, ensuring that DB failures will propagate rather than silently fail.

Comment on lines +48 to +52
owner: {
login: data.owner.login,
type: data.owner.type as "User" | "Organization",
id: data.owner.id,
},
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Unsafe type assertion for owner.type may cause runtime issues.

Line 50 casts data.owner.type to "User" | "Organization" without validation. GitHub's API can return other owner types such as "Bot", which would violate the type constraint and potentially cause issues in downstream code that expects only User or Organization.

Apply this diff to add runtime validation:

-    owner: {
-      login: data.owner.login,
-      type: data.owner.type as "User" | "Organization",
-      id: data.owner.id,
-    },
+    owner: {
+      login: data.owner.login,
+      type: (data.owner.type === "User" || data.owner.type === "Organization")
+        ? data.owner.type
+        : "User", // Fallback for Bot and other types
+      id: data.owner.id,
+    },

Alternatively, expand the type union to include "Bot" if bot-owned repositories are expected.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
owner: {
login: data.owner.login,
type: data.owner.type as "User" | "Organization",
id: data.owner.id,
},
owner: {
login: data.owner.login,
type: (data.owner.type === "User" || data.owner.type === "Organization")
? data.owner.type
: "User", // Fallback for Bot and other types
id: data.owner.id,
},
🤖 Prompt for AI Agents
In src/api/user-oauth-client.ts around lines 48 to 52, the owner.type is being
asserted to "User" | "Organization" without validation; replace the unsafe cast
with a runtime check that reads data.owner.type and only assigns it when it
equals "User" or "Organization", otherwise either include "Bot" (or other
expected types) in the allowed union or handle unexpected values by mapping them
to a safe fallback or throwing/logging an error. Ensure the code branches: if
allowedTypes.includes(data.owner.type) assign that value, else handle explicitly
(e.g., add "Bot" to the type union, set a default like "Organization", or raise
a clear error) so downstream code never receives an invalid type.

@shuhuiluo shuhuiluo merged commit 888c10b into main Nov 23, 2025
2 checks passed
@shuhuiluo shuhuiluo deleted the fix/oauth-token-validation-and-refactoring branch November 23, 2025 06:48
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