diff --git a/CHANGELOG.md b/CHANGELOG.md index 438d19de..53ea1dc1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 3.35.0 - 2026-07-18 + +- Add `completeSignInWithRedirect` to the auth service. On web, OAuth sign-in with `redirect: true` reloads the page on return from the provider, discarding the in-flight `signInWith...` call before it can provision a `YustUser` — leaving an authenticated Firebase user with no matching `YustUser`. Call `completeSignInWithRedirect` once at app startup to fetch the pending redirect result and create/link the `YustUser`. No-op when there is no pending redirect (safe to call on every startup) and on non-web platforms. + ## 3.33.2 - 2026-05-18 - Fix handling of empty maps in `YustDatabaseService` (dart-only) diff --git a/lib/src/services/yust_auth_service_dart.dart b/lib/src/services/yust_auth_service_dart.dart index f5872549..b8ddd4be 100644 --- a/lib/src/services/yust_auth_service_dart.dart +++ b/lib/src/services/yust_auth_service_dart.dart @@ -94,6 +94,10 @@ class YustAuthService { throw UnsupportedError('Not supported. No UI available.'); } + /// Completes an OAuth redirect sign-in. Web-only concept; on the server this + /// is a no-op so shared code can call it unconditionally. + Future completeSignInWithRedirect() async => null; + /// Sign out the current user. Future signOut() async { throw UnsupportedError('Not supported. No UI available.'); diff --git a/lib/src/services/yust_auth_service_flutter.dart b/lib/src/services/yust_auth_service_flutter.dart index a05d7615..44c5c37f 100644 --- a/lib/src/services/yust_auth_service_flutter.dart +++ b/lib/src/services/yust_auth_service_flutter.dart @@ -119,6 +119,42 @@ class YustAuthService { provider, redirect: redirect, ); + return _provisionYustUser(userCredential, method); + } + + /// Completes an OAuth sign-in that was started with `redirect: true` on web. + /// + /// The redirect flow navigates the whole page away to the identity provider + /// and reloads the app on return, which discards the in-flight + /// `signInWith...` call before it can provision a [YustUser]. Firebase + /// restores the auth user from persistence on reload, but the [YustUser] + /// document would otherwise never be created — leaving an authenticated + /// Firebase user with no matching domain user. + /// + /// Call this once at app startup (web only). It fetches the pending redirect + /// result and, if a sign-in just completed, creates or links the matching + /// [YustUser]. It is a no-op when there is no pending redirect result, so it + /// is safe to call on every startup. + Future completeSignInWithRedirect() async { + if (!kIsWeb) return null; + final userCredential = await _fireAuth.getRedirectResult(); + if (userCredential.user == null) return null; + final method = _methodFromProviderId( + userCredential.additionalUserInfo?.providerId ?? + userCredential.credential?.providerId, + ); + return _provisionYustUser(userCredential, method); + } + + /// Ensures a [YustUser] exists for the signed-in [userCredential], linking + /// an existing user (matched by email) or creating a new one. Returns the + /// created user, or `null` when the sign-in failed or a matching user + /// already existed / was linked. Idempotent — safe to call for a user that + /// is already provisioned. + Future _provisionYustUser( + UserCredential userCredential, + YustAuthenticationMethod? method, + ) async { if (_signInFailed(userCredential)) return null; final connectedYustUser = await _maybeGetConnectedYustUser(userCredential); if (_yustUserWasLinked(connectedYustUser)) return null; @@ -146,6 +182,23 @@ class YustAuthService { ); } + YustAuthenticationMethod? _methodFromProviderId(String? providerId) { + switch (providerId) { + case 'microsoft.com': + return YustAuthenticationMethod.microsoft; + case 'google.com': + return YustAuthenticationMethod.google; + case 'apple.com': + return YustAuthenticationMethod.apple; + case null: + case 'password': + case 'firebase': + return null; + default: + return YustAuthenticationMethod.openId; + } + } + String _getId(UserCredential userCredential) => userCredential.user!.uid; String _getEmail(UserCredential userCredential) => diff --git a/lib/src/services/yust_auth_service_mocked.dart b/lib/src/services/yust_auth_service_mocked.dart index b568b3d8..2340a32e 100644 --- a/lib/src/services/yust_auth_service_mocked.dart +++ b/lib/src/services/yust_auth_service_mocked.dart @@ -88,6 +88,9 @@ class YustAuthServiceMocked implements YustAuthService { bool redirect = false, }) => throw UnimplementedError(); + @override + Future completeSignInWithRedirect() async => null; + @override Future signOut() => throw UnimplementedError(); diff --git a/pubspec.yaml b/pubspec.yaml index d1581341..3916e76b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: yust description: Build awesome apps with Flutter and Firebase - this package allows communication with Firebase to be a breeze. -version: 3.33.2 +version: 3.35.0 homepage: https://github.com/univelop/yust environment: