Add JVM credential sign-in and account linking support - #69
TomislavMladenov wants to merge 2 commits into
Conversation
nbransby
left a comment
There was a problem hiding this comment.
Reviewed the single commit (43fd115) against master. Six inline comments below, most severe first:
- Persisted user restore drops
isEmailVerified/lastSignInAt— serializer andJsonObjectconstructor disagree on key names (FirebaseAuth.kt). Dispatchers.Defaultas the default main dispatcher breaks the single-thread ordering guarantee forHandler.postcallbacks and changes which thread existing consumers receive callbacks on (FirebasePlatform.kt).FirebaseUserMetadatatimestamps reset on every token refresh becausecreatedAtdoubles as token-issue time (FirebaseAuth.kt).- Linking a phone credential never adds
"phone"toproviderDatawith the real API response; the test's mock injects aproviderIdthe API doesn't return (FirebaseAuth.kt). - Token-refresh path drops
urlFactory, so emulator users hit production hosts after a refresh (FirebaseAuth.kt). - Nit: duplicated token-presence check in
GoogleAuthCredential/OAuthCredential.
Items 1, 4 and 5 are small, local fixes. Item 2 is the one worth a design decision before merge, since it silently changes threading behaviour for every existing user of the library.
Generated by Claude Code
nbransby
left a comment
There was a problem hiding this comment.
Re-reviewed 91a51dd against the six earlier findings. All six are fixed and I've resolved those threads:
- Persisted restore now reads both
isEmailVerified/emailVerifiedandlastSignInAt/lastLoginAt, and the restore test asserts the verified flag and both timestamps. - Dispatcher default is back to
Dispatchers.Main, which is whatHandlerandAsyncTaskused onmaster, so existing consumers keep their threading. The newFirebasePlatformDispatcherTestpins the ordering and thread guarantee. - Metadata timestamps are decoupled from token time via the new
tokenIssuedAtfield, carried through refresh/link/update paths, and persisted. Legacy stored users force a refresh rather than inherit a stale lifetime, which is the conservative choice.getAccessTokenalso now uses1000L, closing an int-overflow edge. - Phone provider is appended whenever
phoneNumberis present, and the test now uses a realistic phone response with noproviderId. urlFactoryis passed on every construction path, anduseEmulator()re-targets an already-restored user.- Token check is a single
requireOAuthTokenhelper.
Two non-blocking inline notes left (creation timestamp is 0 in production until getAccountInfo runs, and one dead branch in resolveProviderIds). CI is green on this head. Looks ready to approve from my side.
Generated by Claude Code
| displayName: String? = data.getOrElse("displayName") { null }?.jsonPrimitive?.contentOrNull, | ||
| phoneNumber: String? = data["phoneNumber"]?.jsonPrimitive?.contentOrNull, | ||
| isEmailVerified: Boolean = (data["isEmailVerified"] ?: data["emailVerified"])?.jsonPrimitive?.booleanOrNull ?: false, | ||
| createdAt: Long = if ("uid" in data && "tokenIssuedAt" !in data) 0 else data["createdAt"]?.jsonPrimitive?.longOrNull ?: 0, |
There was a problem hiding this comment.
Non-blocking: the split of createdAt (account) from tokenIssuedAt (token) fixes the refresh bug, and 0 for "unknown" matches the Android SDK. One thing to be aware of: the real accounts:signInWithIdp and accounts:signInWithPhoneNumber responses don't carry createdAt / lastLoginAt (the test mock adds them), so in production metadata.creationTimestamp will be 0 after signInWithCredential until something calls getAccountInfo. signInWithCustomToken already chains updateByGetAccountInfo(); doing the same after credential sign-in (and reading lastLoginAt there too) would populate both fields from the one endpoint that actually returns them. Fine as a follow-up.
Generated by Claude Code
| } else { | ||
| emptyList() | ||
| } | ||
| val phoneProvider = if (!isAnonymous && phoneNumber != null) listOf("phone") else emptyList() |
There was a problem hiding this comment.
Nit: with phoneProvider now always appended when phoneNumber != null, the phoneNumber != null -> listOf("phone") branch in inferred (line 287) is dead and can be dropped.
Generated by Claude Code
No description provided.