resolveCloudflareFirebaseServiceAccount function

Future<String> resolveCloudflareFirebaseServiceAccount(
  1. ExecContext context, {
  2. required String projectId,
})

Resolves one Firebase Admin SDK credential for a Cloudflare Worker flavor.

Explicit Authentication and Messaging values must identify the same key. File discovery only accepts a unique key for the selected Firebase project.

Implementation

Future<String> resolveCloudflareFirebaseServiceAccount(
  ExecContext context, {
  required String projectId,
}) async {
  final authYaml = context.yaml
      .getAsMap("cloudflare")
      .getAsMap("authentication")
      .getAsMap("delete_user")
      .get<Object?>("service_account", null);
  final authSecrets = context.secrets
      .getAsMap("cloudflare")
      .getAsMap("authentication")
      .getAsMap("delete_user")
      .get<Object?>("service_account", null);
  final messagingYaml = context.yaml
      .getAsMap("firebase")
      .getAsMap("messaging")
      .get<Object?>("service_account", null);
  final messagingSecrets = context.secrets
      .getAsMap("firebase")
      .getAsMap("messaging")
      .get<Object?>("service_account", null);
  if ([authSecrets, authYaml, messagingSecrets, messagingYaml]
      .any((value) => value != null && value is! String)) {
    throw StateError(
      "Firebase service account must be a JSON string resolved for the selected flavor.",
    );
  }
  final auth = authSecrets is String && authSecrets.trim().isNotEmpty
      ? authSecrets
      : authYaml;
  final messaging =
      messagingSecrets is String && messagingSecrets.trim().isNotEmpty
          ? messagingSecrets
          : messagingYaml;
  final configured = <String>[
    if (auth is String && auth.trim().isNotEmpty) auth,
    if (messaging is String && messaging.trim().isNotEmpty) messaging,
  ];
  if (configured.isNotEmpty) {
    final selected = _parseFirebaseServiceAccount(configured.first);
    if (selected == null || selected["project_id"] != projectId) {
      throw StateError(
        "The configured Firebase service account does not match firebase.project_id for this flavor.",
      );
    }
    for (final value in configured.skip(1)) {
      final candidate = _parseFirebaseServiceAccount(value);
      if (candidate == null ||
          candidate["project_id"] != projectId ||
          candidate["client_email"] != selected["client_email"] ||
          candidate["private_key"] != selected["private_key"]) {
        throw StateError(
          "Authentication and Messaging configure different Firebase service accounts for one Worker secret.",
        );
      }
    }
    return configured.first;
  }

  final matches = <String, String>{};
  final jsonNamePattern = RegExp(r"^([a-zA-Z0-9_-]+)\.json$");
  for (final directoryName in ["cloudflare", "android"]) {
    final directory = Directory(directoryName);
    if (!directory.existsSync()) {
      continue;
    }
    final files = await directory
        .list(recursive: false, followLinks: false)
        .where((entity) => entity is File)
        .cast<File>()
        .toList();
    files.sort((a, b) => a.path.compareTo(b.path));
    for (final file in files) {
      final name = file.path.split(Platform.pathSeparator).last;
      if (!jsonNamePattern.hasMatch(name)) {
        continue;
      }
      try {
        final content = await file.readAsString();
        final parsed = _parseFirebaseServiceAccount(content);
        if (parsed?["project_id"] == projectId) {
          final identity = jsonEncode([
            parsed!["client_email"],
            parsed["private_key"],
          ]);
          matches.putIfAbsent(identity, () => content);
        }
      } on FileSystemException {
        continue;
      }
    }
  }
  if (matches.length != 1) {
    throw StateError(
      matches.isEmpty
          ? "No Firebase service account matches firebase.project_id for this flavor."
          : "Multiple Firebase service accounts match firebase.project_id for this flavor. Configure one explicitly.",
    );
  }
  return matches.values.single;
}