Skip to content

feat/twitter-auth#149

Draft
Frederick-88 wants to merge 2 commits into
masterfrom
feat/twitter-auth
Draft

feat/twitter-auth#149
Frederick-88 wants to merge 2 commits into
masterfrom
feat/twitter-auth

Conversation

@Frederick-88
Copy link
Copy Markdown
Contributor

@Frederick-88 Frederick-88 commented May 18, 2026

Summary by CodeRabbit

  • New Features

    • Added Twitter OAuth2 authentication support for Turnkey wallet provider
    • Enhanced authentication tracking to identify provider-specific methods (Email, Google, Twitter)
  • Configuration

    • Added Twitter OAuth configuration constants and credentials with 10-minute session expiry window

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 18, 2026

📝 Walkthrough

Walkthrough

This PR adds Twitter OAuth2 authentication support to the Turnkey wallet integration. It introduces configuration constants for Twitter OAuth, extends wallet store state to track which provider (Email, Google, or Twitter) was used for authentication, implements new OAuth flow actions with PKCE and localStorage state management, and configures the Turnkey strategy metadata to include Twitter OAuth fields.

Changes

Twitter OAuth2 Integration for Turnkey

Layer / File(s) Summary
Twitter OAuth configuration constants
app/utils/constant/index.ts, app/utils/constant/setup.ts
Export DEFAULT_TWITTER_OAUTH_EXPIRY (10 minutes), TWITTER_CLIENT_ID, and TWITTER_REDIRECT_URI from Vite environment variables.
Wallet store state and provider-aware getters
app/store/wallet/index.ts
Import TurnkeyProvider, extend WalletStoreState with optional turnkeyProvider field, update isGoogleAuth and isTwitterAuth getters to differentiate Turnkey wallets by provider type, and register lazy-loaded connectTurnkeyTwitter and initTurnkeyTwitter actions.
Turnkey authentication actions with provider tracking
app/store/wallet/turnkey.ts
Update submitTurnkeyOTP, connectTurnkeyGoogle, and initTurnkeyGoogle to patch turnkeyProvider into store. Implement connectTurnkeyTwitter to initiate OAuth2 with PKCE and persist state/expiry to localStorage. Implement initTurnkeyTwitter to validate persisted OAuth state, confirm OAuth2, fetch addresses, update wallet store with provider type, and trigger connection callback.
Wallet strategy metadata for Twitter OAuth
app/wallet/strategy.ts
Import Twitter OAuth configuration constants and extend Turnkey metadata with twitterClientId, oauth2ExchangePath, and twitterRedirectUri fields. Update apiServerEndpoint to route to localhost when IS_TRUE_CURRENT is set.

Sequence Diagram

sequenceDiagram
  participant Client
  participant Turnkey
  participant Twitter
  participant App as App Store
  Client->>Turnkey: connectTurnkeyTwitter()
  Turnkey->>Turnkey: initOAuth2 (PKCE)
  Turnkey->>Turnkey: Save oauth/PKCE/expiry to localStorage
  Turnkey->>Twitter: Redirect to authorization URL
  Twitter->>Client: Redirect with authCode + state
  Client->>Turnkey: initTurnkeyTwitter(authCode, state)
  Turnkey->>Turnkey: Validate state, PKCE, expiry
  Turnkey->>Turnkey: confirmOAuth2 with authCode
  Turnkey->>Turnkey: Fetch wallet addresses
  Turnkey->>App: Patch walletStore (session, address, provider=Twitter)
  Turnkey->>App: onConnect callback
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • InjectiveLabs/injective-ui#147: Modifies apiServerEndpoint selection in Turnkey metadata within getWalletStrategy; this PR extends that same configuration with Twitter OAuth fields.

Poem

🐰 A Twitter bird joins the Turnkey nest,
PKCE and state, put to the test,
OAuth flows with localStorage care,
Provider tracking, everywhere!
Five files dance, one flow takes flight,
Tweet-auth blooms in the digital night! 🌙

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat/twitter-auth' clearly summarizes the main change: adding Twitter authentication support to the Turnkey wallet integration.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ 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 feat/twitter-auth

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

app/store/wallet/index.ts

Oops! Something went wrong! :(

ESLint: 9.39.2

Error: Cannot find module './.nuxt/eslint.config.mjs'
Require stack:

  • /eslint.config.ts
    at Module._resolveFilename (node:internal/modules/cjs/loader:1476:15)
    at wrapResolveFilename (node:internal/modules/cjs/loader:1049:27)
    at resolveForCJSWithHooks (node:internal/modules/cjs/loader:1094:12)
    at require.resolve (node:internal/modules/helpers:171:31)
    at jitiResolve (/node_modules/.pnpm/jiti@2.7.0/node_modules/jiti/dist/jiti.cjs:1:176713)
    at jitiRequire (/node_modules/.pnpm/jiti@2.7.0/node_modules/jiti/dist/jiti.cjs:1:178658)
    at import (/node_modules/.pnpm/jiti@2.7.0/node_modules/jiti/dist/jiti.cjs:1:187724)
    at /eslint.config.ts:1:246
    at eval_evalModule (/node_modules/.pnpm/jiti@2.7.0/node_modules/jiti/dist/jiti.cjs:1:184467)
    at jitiRequire (/node_modules/.pnpm/jiti@2.7.0/node_modules/jiti/dist/jiti.cjs:1:179335)
    at Function.import (/node_modules/.pnpm/jiti@2.7.0/node_modules/jiti/dist/jiti.cjs:1

... [truncated 273 characters] ...

266:11)
at async ConfigLoader.calculateConfigArray (/node_modules/.pnpm/eslint@9.39.2_jiti@2.7.0/node_modules/eslint/lib/config/config-loader.js:589:23)
at async #calculateConfigArray (/node_modules/.pnpm/eslint@9.39.2_jiti@2.7.0/node_modules/eslint/lib/config/config-loader.js:743:23)
at async Promise.all (index 0)
at async findFiles (/node_modules/.pnpm/eslint@9.39.2_jiti@2.7.0/node_modules/eslint/lib/eslint/eslint-helpers.js:635:25)
at async ESLint.lintFiles (/node_modules/.pnpm/eslint@9.39.2_jiti@2.7.0/node_modules/eslint/lib/eslint/eslint.js:1014:21)
at async Object.execute (/node_modules/.pnpm/eslint@9.39.2_jiti@2.7.0/node_modules/eslint/lib/cli.js:428:14)
at async main (/node_modules/.pnpm/eslint@9.39.2_jiti@2.7.0/node_modules/eslint/bin/eslint.js:175:19)

app/utils/constant/index.ts

Oops! Something went wrong! :(

ESLint: 9.39.2

Error: Cannot find module './.nuxt/eslint.config.mjs'
Require stack:

  • /eslint.config.ts
    at Module._resolveFilename (node:internal/modules/cjs/loader:1476:15)
    at wrapResolveFilename (node:internal/modules/cjs/loader:1049:27)
    at resolveForCJSWithHooks (node:internal/modules/cjs/loader:1094:12)
    at require.resolve (node:internal/modules/helpers:171:31)
    at jitiResolve (/node_modules/.pnpm/jiti@2.7.0/node_modules/jiti/dist/jiti.cjs:1:176713)
    at jitiRequire (/node_modules/.pnpm/jiti@2.7.0/node_modules/jiti/dist/jiti.cjs:1:178658)
    at import (/node_modules/.pnpm/jiti@2.7.0/node_modules/jiti/dist/jiti.cjs:1:187724)
    at /eslint.config.ts:1:246
    at eval_evalModule (/node_modules/.pnpm/jiti@2.7.0/node_modules/jiti/dist/jiti.cjs:1:184467)
    at jitiRequire (/node_modules/.pnpm/jiti@2.7.0/node_modules/jiti/dist/jiti.cjs:1:179335)
    at Function.import (/node_modules/.pnpm/jiti@2.7.0/node_modules/jiti/dist/jiti.cjs:1

... [truncated 273 characters] ...

266:11)
at async ConfigLoader.calculateConfigArray (/node_modules/.pnpm/eslint@9.39.2_jiti@2.7.0/node_modules/eslint/lib/config/config-loader.js:589:23)
at async #calculateConfigArray (/node_modules/.pnpm/eslint@9.39.2_jiti@2.7.0/node_modules/eslint/lib/config/config-loader.js:743:23)
at async Promise.all (index 0)
at async findFiles (/node_modules/.pnpm/eslint@9.39.2_jiti@2.7.0/node_modules/eslint/lib/eslint/eslint-helpers.js:635:25)
at async ESLint.lintFiles (/node_modules/.pnpm/eslint@9.39.2_jiti@2.7.0/node_modules/eslint/lib/eslint/eslint.js:1014:21)
at async Object.execute (/node_modules/.pnpm/eslint@9.39.2_jiti@2.7.0/node_modules/eslint/lib/cli.js:428:14)
at async main (/node_modules/.pnpm/eslint@9.39.2_jiti@2.7.0/node_modules/eslint/bin/eslint.js:175:19)

app/store/wallet/turnkey.ts

Oops! Something went wrong! :(

ESLint: 9.39.2

Error: Cannot find module './.nuxt/eslint.config.mjs'
Require stack:

  • /eslint.config.ts
    at Module._resolveFilename (node:internal/modules/cjs/loader:1476:15)
    at wrapResolveFilename (node:internal/modules/cjs/loader:1049:27)
    at resolveForCJSWithHooks (node:internal/modules/cjs/loader:1094:12)
    at require.resolve (node:internal/modules/helpers:171:31)
    at jitiResolve (/node_modules/.pnpm/jiti@2.7.0/node_modules/jiti/dist/jiti.cjs:1:176713)
    at jitiRequire (/node_modules/.pnpm/jiti@2.7.0/node_modules/jiti/dist/jiti.cjs:1:178658)
    at import (/node_modules/.pnpm/jiti@2.7.0/node_modules/jiti/dist/jiti.cjs:1:187724)
    at /eslint.config.ts:1:246
    at eval_evalModule (/node_modules/.pnpm/jiti@2.7.0/node_modules/jiti/dist/jiti.cjs:1:184467)
    at jitiRequire (/node_modules/.pnpm/jiti@2.7.0/node_modules/jiti/dist/jiti.cjs:1:179335)
    at Function.import (/node_modules/.pnpm/jiti@2.7.0/node_modules/jiti/dist/jiti.cjs:1

... [truncated 273 characters] ...

266:11)
at async ConfigLoader.calculateConfigArray (/node_modules/.pnpm/eslint@9.39.2_jiti@2.7.0/node_modules/eslint/lib/config/config-loader.js:589:23)
at async #calculateConfigArray (/node_modules/.pnpm/eslint@9.39.2_jiti@2.7.0/node_modules/eslint/lib/config/config-loader.js:743:23)
at async Promise.all (index 0)
at async findFiles (/node_modules/.pnpm/eslint@9.39.2_jiti@2.7.0/node_modules/eslint/lib/eslint/eslint-helpers.js:635:25)
at async ESLint.lintFiles (/node_modules/.pnpm/eslint@9.39.2_jiti@2.7.0/node_modules/eslint/lib/eslint/eslint.js:1014:21)
at async Object.execute (/node_modules/.pnpm/eslint@9.39.2_jiti@2.7.0/node_modules/eslint/lib/cli.js:428:14)
at async main (/node_modules/.pnpm/eslint@9.39.2_jiti@2.7.0/node_modules/eslint/bin/eslint.js:175:19)

  • 2 others

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

@Frederick-88 Frederick-88 marked this pull request as ready for review May 18, 2026 08:36
Copy link
Copy Markdown

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

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/store/wallet/turnkey.ts`:
- Around line 187-215: The expiresAt validation currently only triggers when
expiresAt > 0, allowing missing or unparsable values to bypass expiry; update
the check around the expiresAt variable so that missing/invalid values also fail
closed (e.g., treat 0/NaN as expired) and throw the same WalletException (with
UnspecifiedErrorCode and ErrorType.WalletError). Locate the expiresAt
declaration and the subsequent if block that throws on expiry and replace the
condition with one that checks for invalid/missing values (isNaN or <= 0) OR
Date.now() > expiresAt so the OAuth session is rejected when expiresAt is absent
or invalid.

In `@app/wallet/strategy.ts`:
- Around line 65-68: The apiServerEndpoint selector currently uses
IS_TRUE_CURRENT to point to 'http://localhost:3001/api/v1', which breaks
OAuth/session exchange outside local dev; update the conditional in
app/wallet/strategy.ts (the apiServerEndpoint assignment that references
IS_TRUE_CURRENT) to use the correct TrueCurrent remote API URL or derive the
host from an environment/config value instead of hardcoding localhost so
deployed environments never route TrueCurrent traffic to localhost.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 40b77517-f37c-468a-8b1a-40e8ff6f84e7

📥 Commits

Reviewing files that changed from the base of the PR and between 10bc4fd and 13bd0ee.

📒 Files selected for processing (5)
  • app/store/wallet/index.ts
  • app/store/wallet/turnkey.ts
  • app/utils/constant/index.ts
  • app/utils/constant/setup.ts
  • app/wallet/strategy.ts

Comment on lines +187 to +215
const expiresAt = Number(
localStorage.getItem('twitter_oauth_expires_at') || '0'
)

localStorage.removeItem('twitter_oauth_state')
localStorage.removeItem('twitter_oauth_expires_at')
localStorage.removeItem('twitter_oauth_code_verifier')
localStorage.removeItem('twitter_oauth_target_public_key')

if (state !== savedState) {
throw new WalletException(
new Error('Twitter sign-in failed — invalid state. Please try again.'),
{ code: UnspecifiedErrorCode, type: ErrorType.WalletError }
)
}

if (!codeVerifier || !targetPublicKey) {
throw new WalletException(
new Error('OAuth session not found — please try signing in again'),
{ code: UnspecifiedErrorCode, type: ErrorType.WalletError }
)
}

if (expiresAt > 0 && Date.now() > expiresAt) {
throw new WalletException(
new Error('OAuth session expired — please try signing in again'),
{ code: UnspecifiedErrorCode, type: ErrorType.WalletError }
)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fail closed when OAuth expiry is missing or invalid.

Line 210 only checks expiry when expiresAt > 0, so missing or unparsable values (0/NaN) skip validation and continue the auth flow.

Proposed fix
-  const expiresAt = Number(
-    localStorage.getItem('twitter_oauth_expires_at') || '0'
-  )
+  const expiresAtRaw = localStorage.getItem('twitter_oauth_expires_at')
+  const expiresAt = Number(expiresAtRaw)

@@
-  if (expiresAt > 0 && Date.now() > expiresAt) {
+  if (!Number.isFinite(expiresAt) || expiresAt <= 0 || Date.now() > expiresAt) {
     throw new WalletException(
       new Error('OAuth session expired — please try signing in again'),
       { code: UnspecifiedErrorCode, type: ErrorType.WalletError }
     )
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/store/wallet/turnkey.ts` around lines 187 - 215, The expiresAt validation
currently only triggers when expiresAt > 0, allowing missing or unparsable
values to bypass expiry; update the check around the expiresAt variable so that
missing/invalid values also fail closed (e.g., treat 0/NaN as expired) and throw
the same WalletException (with UnspecifiedErrorCode and ErrorType.WalletError).
Locate the expiresAt declaration and the subsequent if block that throws on
expiry and replace the condition with one that checks for invalid/missing values
(isNaN or <= 0) OR Date.now() > expiresAt so the OAuth session is rejected when
expiresAt is absent or invalid.

Comment thread app/wallet/strategy.ts
Comment on lines 65 to +68
apiServerEndpoint: IS_TRUE_CURRENT
? 'https://api.ui.staging.tc.xyz/api/v1'
: 'https://api.ui.injective.network/api/v1',
expirationSeconds: '86400'
? 'http://localhost:3001/api/v1'
: // ? 'https://api.ui.staging.tc.xyz/api/v1'
'https://api.ui.injective.network/api/v1',
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Do not route TrueCurrent Turnkey API traffic to localhost.

Using http://localhost:3001/api/v1 for IS_TRUE_CURRENT will fail outside local development and break OAuth/session exchange in deployed environments.

Proposed fix
-                apiServerEndpoint: IS_TRUE_CURRENT
-                  ? 'http://localhost:3001/api/v1'
-                  : // ? 'https://api.ui.staging.tc.xyz/api/v1'
-                    'https://api.ui.injective.network/api/v1',
+                apiServerEndpoint: IS_TRUE_CURRENT
+                  ? 'https://api.ui.staging.tc.xyz/api/v1'
+                  : 'https://api.ui.injective.network/api/v1',
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/wallet/strategy.ts` around lines 65 - 68, The apiServerEndpoint selector
currently uses IS_TRUE_CURRENT to point to 'http://localhost:3001/api/v1', which
breaks OAuth/session exchange outside local dev; update the conditional in
app/wallet/strategy.ts (the apiServerEndpoint assignment that references
IS_TRUE_CURRENT) to use the correct TrueCurrent remote API URL or derive the
host from an environment/config value instead of hardcoding localhost so
deployed environments never route TrueCurrent traffic to localhost.

@Frederick-88 Frederick-88 marked this pull request as draft May 18, 2026 08:53
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