Conversation
Add Google OAuth as a second authentication provider alongside Apple Sign-In, using ASWebAuthenticationSession with the shared flyfun-common auth server. - SecretsManager: add authBaseURL for shared auth server - AuthenticationService: Google OAuth flow, JWT token handling, /auth/me user info fetch, Keychain persistence - FlyFunBriefApp: handle flyfunbrief://auth/callback deep link - SettingsView: add Google sign-in button with loading state Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Code ReviewThree issues worth fixing before merging: 1. Token saved to Keychain before
|
- Move token save after fetchUserInfo to avoid orphaned Keychain entry - Replace deprecated UIWindowScene.windows with keyWindow - Remove shared isLoading spinner from Google button (browser is obvious) - Remove unused AuthError.cancelled case Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Code Review Bug: handleAuthCallback is unreachable dead code ASWebAuthenticationSession with .customScheme intercepts the redirect URL and delivers it directly to the session completion handler — it never reaches the app onOpenURL. The url.host == auth block in FlyFunBriefApp.swift is never triggered for this flow, and handleAuthCallback is never called. The full auth response is already handled inside signInWithGoogle() via withCheckedThrowingContinuation. Both the URL routing in FlyFunBriefApp.swift and handleAuthCallback should be removed. Bug: handleAuthCallback orphans a Keychain token on error handleAuthCallback saves the JWT to Keychain BEFORE calling fetchUserInfo. If fetchUserInfo throws, the token stays in Keychain while isAuthenticated is false and currentUser is nil — inconsistent state. signInWithGoogle() correctly does fetch-then-persist with an explicit comment explaining why. handleAuthCallback should follow the same pattern. Bug: fetchUserInfo hardcodes .google provider fetchUserInfo returns AuthUser with provider .google unconditionally, despite hitting a shared /auth/me endpoint. Provider should be a parameter or derived from the response. Design issue: authError is now publicly mutable authError changed from private(set) var to var. SettingsView now sets appState?.auth.authError = error.localizedDescription directly, bypassing the service. Error state should flow through the service — keep authError as private(set) and let signInWithGoogle() throws propagate to callers. Minor: authSession not cleared after completion After ASWebAuthenticationSession completes, self.authSession is never set back to nil, retaining a reference to the completed session until the next sign-in. Set self.authSession = nil after the continuation resumes. |
- Remove dead handleAuthCallback and URL routing (ASWebAuthenticationSession handles callback internally via .customScheme) - Make fetchUserInfo take provider parameter instead of hardcoding .google - Make authError private(set), errors set internally by signInWithGoogle - Change signInWithGoogle from throws to async (handles errors internally) - Clear authSession after completion to avoid retaining completed session Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Code ReviewThree issues found, all in 1.
|
- Use MainActor.assumeIsolated in presentationAnchor for Swift 6 safety - Only catch .canceledLogin, not all ASWebAuthenticationSession errors - Throw on empty userId from /auth/me instead of storing empty string Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Code ReviewThree issues worth addressing before merge: 1. Silent Keychain Migration Break (Bug)The Keychain account key changed from Either keep loading from // checkExistingSession — existing Apple sessions won't be found
if let userData = loadFromKeychain(service: keychainServiceUser, account: "authUser"), ...2.
|
- Add Keychain migration from legacy "appleUser" key to "authUser" - Validate Google JWT on session restore (match Apple's checkCredentialState) - Replace JSONSerialization with Codable AuthMeResponse struct Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Code ReviewBug: Keychain migration will silently fail for existing Apple Sign-In usersIn userData = legacyData
// ...
let user = try? JSONDecoder().decode(AuthUser.self, from: userData)The old {"userId":"...", "email":"...", "firstName":"...", "lastName":"..."}But Fix: decode the legacy data with a transitional struct (or give private struct LegacyAppleUser: Decodable {
let userId: String; let email: String?
let firstName: String?; let lastName: String?
}
// In migration block:
if let legacy = try? JSONDecoder().decode(LegacyAppleUser.self, from: legacyData) {
let converted = AuthUser(userId: legacy.userId, email: legacy.email,
firstName: legacy.firstName, lastName: legacy.lastName, provider: .apple)
if let reencoded = try? JSONEncoder().encode(converted) {
userData = reencoded
saveToKeychain(service: keychainServiceUser, account: "authUser", data: reencoded)
}
deleteFromKeychain(service: keychainServiceUser, account: "appleUser")
}Everything else looks solid: the 🤖 Generated with Claude Code |
Old AppleUser JSON has no provider field, so decoding as AuthUser silently fails. Use LegacyAppleUser struct to decode, then re-encode as AuthUser with provider: .apple. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Code ReviewOverall this is a clean, well-structured addition. A few issues to address: Bug: force unwrap crash in
|
- Remove force unwrap on URLComponents for malformed authURL - Replace force unwrap in name splitting with safe flatMap - Silence misleading error log for non-import deep links - Skip approved flag check (server returns 403 for unapproved users) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Code ReviewBug: Google session authenticated before token validationIn self.currentUser = user
self.isAuthenticated = true
onAuthChanged?(sessionToken) // BriefingService starts using the token
// ...only then validated:
_ = try await fetchUserInfo(token: token, provider: .google)
// catch: signOut()If the stored token is expired, Bug: No concurrent-call guard in
|
Summary
ASWebAuthenticationSessionwith shared flyfun-common auth server (forms.flyfun.aero)/auth/mePrerequisites
APPLE_APP_IDSon flyfun-common server needsnet.ro-z.FlyFunBriefadded for Apple token exchangeplatform=ios&scheme=flyfunbrief) already supported by server-side regexTest plan
flyfunbrief://auth/callbackdeep link works🤖 Generated with Claude Code