resolveOAuthSignInForProvider<TContext, TProfile extends Object> function

Future<AuthOAuthSignInResolution> resolveOAuthSignInForProvider<TContext, TProfile extends Object>({
  1. required AuthStore store,
  2. required TContext context,
  3. required OAuthProvider<TProfile> provider,
  4. required String code,
  5. required Client httpClient,
  6. String? codeVerifier,
  7. String? oidcNonce,
  8. String fallbackAccountId()?,
})

Resolves OAuth callback payloads into user/account/profile sign-in data.

Implementation

Future<AuthOAuthSignInResolution>
resolveOAuthSignInForProvider<TContext, TProfile extends Object>({
  required AuthStore store,
  required TContext context,
  required OAuthProvider<TProfile> provider,
  required String code,
  required http.Client httpClient,
  String? codeVerifier,
  String? oidcNonce,
  String Function()? fallbackAccountId,
}) async {
  late OAuthTokenResponse tokenResponse;
  try {
    tokenResponse = await exchangeOAuthAuthorizationCode(
      provider,
      code: code,
      codeVerifier: codeVerifier,
      httpClient: httpClient,
    );
  } catch (_) {
    throw AuthFlowException('token_exchange_failed');
  }

  final rawProfile = await loadOAuthProfile(
    provider,
    token: tokenResponse,
    httpClient: httpClient,
    oidcNonce: oidcNonce,
  );
  late final TProfile enrichedProfile;
  late final AuthUser user;
  late final Map<String, dynamic> profileMap;
  try {
    final parsedProfile = provider.parseProfile(rawProfile);
    enrichedProfile = await Future.sync(
      () => provider.enrichProfile(
        context,
        tokenResponse,
        httpClient,
        parsedProfile,
      ),
    );
    final mappedUser = provider.mapProfile(enrichedProfile);
    final overrideUser = await Future.sync(
      () => provider.overrideProfile(context, enrichedProfile),
    );
    user = overrideUser ?? mappedUser;
    profileMap = provider.serializeProfile(enrichedProfile);
  } on AuthFlowException {
    rethrow;
  } catch (_) {
    throw AuthFlowException('profile_invalid');
  }
  final emailVerified =
      profileMap['verified'] == true || profileMap['email_verified'] == true;
  final accountId = resolveAuthAccountId(
    profileMap,
    user,
    fallbackId: fallbackAccountId ?? secureRandomToken,
    emailVerified: emailVerified,
  );
  final accountExpiresAt = oauthTokenExpiryFromSeconds(tokenResponse.expiresIn);

  final userResolution = await resolveOAuthUserForAccount(
    store: store,
    providerId: provider.id,
    accountId: accountId,
    mappedUser: user,
    // Only link to a local account by email when the provider asserted
    // ownership of the address (Discord `verified`, Google `email_verified`,
    // GitHub `verified`, ...). Unverified profile emails must never take over
    // an existing local user.
    emailVerified: emailVerified,
  );

  final account = buildOAuthAuthAccount(
    providerId: provider.id,
    providerAccountId: accountId,
    userId: userResolution.user.id,
    token: tokenResponse,
    expiresAt: accountExpiresAt,
    metadata: profileMap,
  );
  await linkOAuthAccountOrThrow(store: store, account: account);

  return AuthOAuthSignInResolution(
    user: userResolution.user,
    isNewUser: userResolution.isNewUser,
    userUpdated: userResolution.userUpdated,
    account: account,
    profile: profileMap,
  );
}