authenticate static method

Future<Map<String, dynamic>> authenticate({
  1. required String code,
  2. String? callbackPath,
  3. required String? clientId,
  4. required String? clientSecret,
  5. required String redirectBase,
})

Implementation

static Future<Map<String, dynamic>> authenticate({
  required String code,
  String? callbackPath,
  required String? clientId,
  required String? clientSecret,
  required String redirectBase,
}) async {
  if (clientId == null || clientSecret == null) {
    throw AuthException(message: 'GitHub OAuth is not configured');
  }

  final redirectUri = callbackPath != null
      ? '$redirectBase$callbackPath'
      : '$redirectBase/api/auth/github/callback';

  // 1. Exchange code for access token
  final accessToken = await _exchangeCodeForToken(
    code,
    clientId,
    clientSecret,
    redirectUri,
  );

  // 2. Fetch user profile
  final profile = await _fetchUserProfile(accessToken);

  // 3. Get user email
  String? email = profile['email'];
  if (email == null || email.isEmpty) {
    final emails = await _fetchUserEmails(accessToken);
    Map<String, dynamic>? selected;

    for (final entry in emails) {
      if (entry is Map<String, dynamic> && entry['primary'] == true) {
        selected = entry;
        break;
      }
    }
    selected ??= emails
        .whereType<Map<String, dynamic>>()
        .cast<Map<String, dynamic>>()
        .firstWhere(
          (e) => e['verified'] == true,
          orElse: () => <String, dynamic>{},
        );
    selected = selected.isNotEmpty
        ? selected
        : emails.whereType<Map<String, dynamic>>().isNotEmpty
            ? emails.whereType<Map<String, dynamic>>().first
            : null;

    email = selected?['email']?.toString();
    if (email == null || email.isEmpty) {
      throw AuthException(message: 'No usable email found for GitHub user');
    }
  }

  return AuthProvider.formatUserData(
    provider: 'github',
    providerId: profile['id'].toString(),
    email: email,
    name: profile['name'] ?? profile['login'],
    picture: profile['avatar_url'],
    rawProfile: profile,
  );
}