Skip to content

feat: v3.0.0 - 手機端 Google 登入改用後端代理 ticket 策略 - #56

Merged
Retsomm merged 2 commits into
mainfrom
dev
Jun 22, 2026
Merged

Retsomm merged 2 commits into
mainfrom
dev

Conversation

@Retsomm

@Retsomm Retsomm commented Jun 22, 2026 •

Copy link
Copy Markdown
Owner
  • 新增 /api/auth/mobile/google 後端路由,驗證 Google ID Token 並換發 Clerk sign-in token
  • 手機端改用 @react-native-google-signin/google-signin + ticket 策略,繞過 Clerk redirect URL 限制
  • 更新 version 至 3.0.0

Summary by CodeRabbit

Release Notes

  • New Features

    • Redesigned mobile Google sign-in authentication flow.
    • Added login prompt on profile screen for unauthenticated users.
    • Introduced new learning routes for topic exploration.
  • Enhancements

    • Updated app and mobile client versions to 3.0.0.
    • Added Android audio recording permission support.

- 新增 /api/auth/mobile/google 後端路由,驗證 Google ID Token 並換發 Clerk sign-in token
- 手機端改用 @react-native-google-signin/google-signin + ticket 策略,繞過 Clerk redirect URL 限制
- 更新 version 至 3.0.0

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 22, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
self-map Ready Ready Preview, Comment Jun 22, 2026 1:04pm

@coderabbitai

coderabbitai Bot commented Jun 22, 2026 •

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@Retsomm, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 46 minutes and 19 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 54a3959a-4747-4691-bc2d-c209de908475

📥 Commits

Reviewing files that changed from the base of the PR and between 32dc8e1 and e5e4286.

📒 Files selected for processing (4)
  • app/api/auth/mobile/google/route.ts
  • mobile/app/(auth)/sign-in.tsx
  • mobile/app/(tabs)/profile.tsx
  • mobile/hooks/useGoogleSignIn.ts
📝 Walkthrough

Walkthrough

A new Next.js API route exchanges a Google ID token for a Clerk sign-in ticket. The mobile sign-in screen replaces the OAuth redirect flow with the @react-native-google-signin/google-signin SDK token flow. Auth gating is added to the profile screen (sign-in prompt) and chart preview actions. SafeAreaView is migrated to react-native-safe-area-context across multiple screens, and EAS build configuration is introduced.

Changes

Mobile Google Sign-In & Auth Gating

Layer / File(s) Summary
Backend API route: Google ID token exchange
app/api/auth/mobile/google/route.ts
New POST handler verifies Google idToken via the tokeninfo endpoint, finds or creates a Clerk user by email, and returns a 300-second Clerk sign-in ticket. Returns HTTP 400 on missing token and HTTP 401 on all other failures.
Mobile config, EAS build, and dependency updates
mobile/app.json, mobile/eas.json, mobile/package.json, package.json
Bumps app version to 3.0.0, adds RECORD_AUDIO Android permission, registers expo-sharing and @react-native-google-signin/google-signin plugins, adds eas.projectId, introduces EAS build profiles, and adds the @react-native-google-signin/google-signin dependency.
Sign-in screen: OAuth replaced with Google Sign-In SDK token flow
mobile/app/(auth)/sign-in.tsx
Removes useOAuth/expo-linking imports; configures Google Sign-In on mount via useEffect; rewrites handleGoogleSignIn to obtain a Google idToken, POST it to the backend for exchange, and create a Clerk session using the ticket strategy.
Root layout: SafeAreaProvider and updated auth guard
mobile/app/_layout.tsx
Wraps root layout in SafeAreaProvider, replaces the public-routes negation check with an isProtectedRoute helper in AuthGuard, and adds a learn/[topic] screen with headerShown: false.
Profile screen: sign-in prompt when unauthenticated
mobile/app/(tabs)/profile.tsx
Adds a SignInPrompt component with a Google OAuth sign-in handler; ProfileScreen early-returns the prompt in a SafeAreaView when isSignedIn is false. New signIn* styles support the prompt layout.
Chart preview: auth-gated download and copy actions
mobile/app/chart/preview.tsx
handleDownload and handleCopyPrompt both call requireLogin() and exit early when unauthenticated. SafeAreaView import moved to react-native-safe-area-context with explicit edges.
SafeAreaView import migration across screens
mobile/app/(tabs)/create.tsx, mobile/app/(tabs)/index.tsx, mobile/app/(tabs)/learn.tsx, mobile/app/chart/[id].tsx, mobile/app/learn/[topic].tsx
SafeAreaView import source changed from react-native to react-native-safe-area-context in all affected tab and route screens.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant SignInScreen
  participant GoogleSignIn SDK
  participant POST /api/auth/mobile/google
  participant Clerk

  User->>SignInScreen: Tap "Sign in with Google"
  SignInScreen->>GoogleSignIn SDK: GoogleSignin.signIn()
  GoogleSignIn SDK-->>SignInScreen: idToken
  SignInScreen->>POST /api/auth/mobile/google: POST { idToken }
  POST /api/auth/mobile/google->>Clerk: getUserList / createUser
  Clerk-->>POST /api/auth/mobile/google: userId
  POST /api/auth/mobile/google->>Clerk: createSignInToken(userId, 300s)
  Clerk-->>POST /api/auth/mobile/google: ticket token
  POST /api/auth/mobile/google-->>SignInScreen: { token }
  SignInScreen->>Clerk: signIn.create({ strategy: "ticket", ticket: token })
  Clerk-->>SignInScreen: session
  SignInScreen->>SignInScreen: setActive(session) → route to /(tabs)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • Retsomm/SelfMap#47: Both PRs modify mobile/app/(tabs)/profile.tsx to add OAuth-related UI — the retrieved PR adds linked account display/settings and this PR adds a sign-in prompt for unauthenticated users.

Poem

🐇 Hop hop, the OAuth redirect's gone away,
A shiny new token flies through the day!
Google says "yes," Clerk hands out the key,
SafeAreaView moved where it ought to be.
The rabbit signs in — no redirects, hooray! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 31.25% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: implementing backend-proxied Google sign-in for mobile using a ticket strategy, which aligns with the PR's core objective.
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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dev

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
mobile/app/(auth)/sign-in.tsx (1)

21-54: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Extract shared sign-in logic to avoid duplication.

The handleGoogleSignIn function is duplicated nearly identically in mobile/app/(tabs)/profile.tsx (lines 21-54). Extract this logic into a shared utility or custom hook to maintain consistency and reduce maintenance burden.

🤖 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 `@mobile/app/`(auth)/sign-in.tsx around lines 21 - 54, The handleGoogleSignIn
function is duplicated across multiple files, which creates maintenance burden
and risk of inconsistency when updates are needed. Extract the shared sign-in
logic (including the Google token retrieval, backend verification, and Clerk
session creation steps) into a custom hook or utility function that can be
imported and reused in both locations. This hook should accept necessary
dependencies like router, signIn, and setActive, and return the configured async
handler function that both files can use consistently.
🧹 Nitpick comments (2)
mobile/app/(auth)/sign-in.tsx (2)

9-10: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Move WEB_CLIENT_ID to environment variable.

Hardcoding the Google client ID in source code makes it harder to manage different environments (dev/staging/production) and exposes internal configuration. Move it to an environment variable like EXPO_PUBLIC_GOOGLE_WEB_CLIENT_ID.

♻️ Proposed fix
-const WEB_CLIENT_ID = '679544930449-vo4mgolhcfm8qp1v374phl611p6nv597.apps.googleusercontent.com'
-const API_BASE = process.env.EXPO_PUBLIC_API_BASE_URL
+const WEB_CLIENT_ID = process.env.EXPO_PUBLIC_GOOGLE_WEB_CLIENT_ID
+const API_BASE = process.env.EXPO_PUBLIC_API_BASE_URL
🤖 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 `@mobile/app/`(auth)/sign-in.tsx around lines 9 - 10, The WEB_CLIENT_ID
constant is hardcoded with the Google client ID value which should be moved to
an environment variable for better environment management and security. Replace
the hardcoded string in the WEB_CLIENT_ID assignment with
process.env.EXPO_PUBLIC_GOOGLE_WEB_CLIENT_ID to retrieve it from an environment
variable instead, similar to how API_BASE is already configured.

47-47: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Replace any with unknown for proper type safety.

The ESLint error flags the any type. Use unknown with a type guard for safer error handling.

♻️ Proposed fix
-    } catch (err: any) {
-      if (err.code === statusCodes.SIGN_IN_CANCELLED) return
+    } catch (err: unknown) {
+      if (err && typeof err === 'object' && 'code' in err && err.code === statusCodes.SIGN_IN_CANCELLED) return
🤖 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 `@mobile/app/`(auth)/sign-in.tsx at line 47, Replace the `any` type annotation
in the catch clause with `unknown` to ensure proper type safety. After changing
the parameter type from `any` to `unknown`, add a type guard before accessing
any properties on the error object. This will require you to check the actual
type of the error (typically using instanceof Error or typeof checks) before
using it, ensuring the code is type-safe and compliant with ESLint rules.

Source: Linters/SAST tools

🤖 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/api/auth/mobile/google/route.ts`:
- Around line 4-17: The verifyGoogleIdToken function lacks validation of the
`aud` claim in the Google ID token, creating a token substitution vulnerability
where tokens from other applications could be accepted. After fetching and
parsing the token payload, add a check to verify that the `aud` claim matches
your application's Google Client ID (which should be stored as an environment
variable). If the audience claim does not match the expected client ID, throw an
error similar to the existing validation checks for email_verified. This ensures
that only tokens issued specifically for your application are accepted.

In `@mobile/app/`(tabs)/profile.tsx:
- Around line 349-366: The SignInPrompt component must be updated to use the
token-based Google sign-in strategy instead of the redirect-based OAuth flow.
Replace the useOAuth hook call with the Google Sign-in hook from
`@react-native-google-signin/google-signin`. In the handleGoogleSignIn function,
remove the startOAuthFlow call and instead call GoogleSignin.signIn() to get the
ID token, then POST that idToken to the /api/auth/mobile/google endpoint, and
finally use signIn.create with strategy 'ticket' and the returned token value to
create the session, matching the implementation pattern already used in the main
sign-in screen.

---

Outside diff comments:
In `@mobile/app/`(auth)/sign-in.tsx:
- Around line 21-54: The handleGoogleSignIn function is duplicated across
multiple files, which creates maintenance burden and risk of inconsistency when
updates are needed. Extract the shared sign-in logic (including the Google token
retrieval, backend verification, and Clerk session creation steps) into a custom
hook or utility function that can be imported and reused in both locations. This
hook should accept necessary dependencies like router, signIn, and setActive,
and return the configured async handler function that both files can use
consistently.

---

Nitpick comments:
In `@mobile/app/`(auth)/sign-in.tsx:
- Around line 9-10: The WEB_CLIENT_ID constant is hardcoded with the Google
client ID value which should be moved to an environment variable for better
environment management and security. Replace the hardcoded string in the
WEB_CLIENT_ID assignment with process.env.EXPO_PUBLIC_GOOGLE_WEB_CLIENT_ID to
retrieve it from an environment variable instead, similar to how API_BASE is
already configured.
- Line 47: Replace the `any` type annotation in the catch clause with `unknown`
to ensure proper type safety. After changing the parameter type from `any` to
`unknown`, add a type guard before accessing any properties on the error object.
This will require you to check the actual type of the error (typically using
instanceof Error or typeof checks) before using it, ensuring the code is
type-safe and compliant with ESLint rules.
🪄 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 Plus

Run ID: 93925d36-5407-4822-a8a8-3bed2cd2bcd3

📥 Commits

Reviewing files that changed from the base of the PR and between bf616b0 and 32dc8e1.

⛔ Files ignored due to path filters (3)
  • mobile/assets/android-icon-foreground.png is excluded by !**/*.png
  • mobile/assets/icon.png is excluded by !**/*.png
  • mobile/yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (14)
  • app/api/auth/mobile/google/route.ts
  • mobile/app.json
  • mobile/app/(auth)/sign-in.tsx
  • mobile/app/(tabs)/create.tsx
  • mobile/app/(tabs)/index.tsx
  • mobile/app/(tabs)/learn.tsx
  • mobile/app/(tabs)/profile.tsx
  • mobile/app/_layout.tsx
  • mobile/app/chart/[id].tsx
  • mobile/app/chart/preview.tsx
  • mobile/app/learn/[topic].tsx
  • mobile/eas.json
  • mobile/package.json
  • package.json

Comment thread app/api/auth/mobile/google/route.ts
Comment thread mobile/app/(tabs)/profile.tsx Outdated
- route.ts 加入 aud claim 驗證防止 token 替換攻擊
- SignInPrompt 改用 ticket 策略取代舊的 OAuth redirect
- 提取 useGoogleSignIn hook 消除重複邏輯
- WEB_CLIENT_ID 改用環境變數 EXPO_PUBLIC_GOOGLE_WEB_CLIENT_ID
- 修正 err: any → unknown 型別安全

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@Retsomm
Retsomm merged commit b608b58 into main Jun 22, 2026
3 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request Aug 3, 2026
Merged

This branch was successfully deployed

1 active deployment
Preview — e5e42863 Deployed Jun 22, 2026 by vercel[bot]
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