diff --git a/.gitignore b/.gitignore index 4fcdbc1..88abcbc 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,5 @@ build/ # Directory created by dartdoc doc/api/ +.idea/ *.iml \ No newline at end of file diff --git a/analysis_options.yaml b/analysis_options.yaml index a686c1b..ea2c9e9 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,14 +1 @@ -# Defines a default set of lint rules enforced for -# projects at Google. For details and rationale, -# see https://github.com/dart-lang/pedantic#enabled-lints. -include: package:pedantic/analysis_options.yaml - -# For lint rules and documentation, see http://dart-lang.github.io/linter/lints. -# Uncomment to specify additional rules. -# linter: -# rules: -# - camel_case_types - -analyzer: -# exclude: -# - path/to/excluded/files/** +include: package:lints/recommended.yaml \ No newline at end of file diff --git a/example/main.dart b/example/main.dart index c38dcda..0ab4b7f 100644 --- a/example/main.dart +++ b/example/main.dart @@ -1,5 +1,4 @@ import 'package:firebase_admin/firebase_admin.dart'; -import 'package:firebase_admin/src/credential.dart'; void main() async { // applicationDefault() will look for credentials in the following locations: diff --git a/lib/src/app.dart b/lib/src/app.dart index 2e77697..cfc77c6 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -5,10 +5,7 @@ import 'package:firebase_admin/src/storage.dart'; import '../firebase_admin.dart'; import 'app/app.dart'; import 'database.dart'; -import 'utils/error.dart'; import 'service.dart'; -import 'auth.dart'; -import 'credential.dart'; /// Represents initialized Firebase application and provides access to the /// app's services. diff --git a/lib/src/app/app.dart b/lib/src/app/app.dart index b725557..ffbbe7f 100644 --- a/lib/src/app/app.dart +++ b/lib/src/app/app.dart @@ -1,9 +1,9 @@ import 'dart:async'; +import 'package:clock/clock.dart'; import 'package:firebase_admin/src/utils/error.dart'; import '../credential.dart'; -import 'package:clock/clock.dart'; class FirebaseAppInternals { final Credential credential; @@ -55,9 +55,9 @@ class FirebaseAppInternals { hasAccessTokenChanged || hasExpirationChanged) { _cachedToken = token; - _tokenListeners.forEach((listener) { + for (var listener in _tokenListeners) { listener(token.accessToken); - }); + } } var expiresIn = token.expirationTime.difference(clock.now()); diff --git a/lib/src/app/app_extension.dart b/lib/src/app/app_extension.dart index 3f8eb82..9445b94 100644 --- a/lib/src/app/app_extension.dart +++ b/lib/src/app/app_extension.dart @@ -3,7 +3,6 @@ import 'dart:io'; import 'package:firebase_admin/src/auth/credential.dart'; import '../../firebase_admin.dart'; -import '../credential.dart'; extension GetProjectIdExtension on App { String get projectId => _getProjectId(this); diff --git a/lib/src/auth.dart b/lib/src/auth.dart index 05a881d..dff9549 100644 --- a/lib/src/auth.dart +++ b/lib/src/auth.dart @@ -3,11 +3,11 @@ import 'package:firebase_admin/src/auth/token_generator.dart'; import 'package:firebase_admin/src/auth/token_verifier.dart'; import 'package:openid_client/openid_client.dart'; -import 'app.dart'; import 'auth/auth_api_request.dart'; -import 'auth/user_record.dart'; import 'service.dart'; +export 'auth/user_record.dart'; + /// The Firebase Auth service interface. class Auth implements FirebaseService { @override @@ -35,6 +35,15 @@ class Auth implements FirebaseService { return UserRecord.fromJson(response['users'][0]); } + /// Gets the user data for the users corresponding to the given [uids]. + Future> getUsers(List uids) async { + var response = await _authRequestHandler.getAccountInfoByUids(uids); + // Returns the user record populated with server response. + return (response['users'] as List) + .map((u) => UserRecord.fromJson(u)) + .toList(); + } + /// Looks up the user identified by the provided email and returns a future /// that is fulfilled with a user record for the given user if that user is /// found. diff --git a/lib/src/auth/auth_api_request.dart b/lib/src/auth/auth_api_request.dart index a975360..e7d69d2 100644 --- a/lib/src/auth/auth_api_request.dart +++ b/lib/src/auth/auth_api_request.dart @@ -1,18 +1,16 @@ import 'dart:convert'; import 'package:clock/clock.dart'; +import 'package:collection/collection.dart'; +import 'package:http/http.dart'; -import '../auth.dart'; +import '../app.dart'; import '../app/app_extension.dart'; +import '../auth.dart'; import '../utils/api_request.dart'; import '../utils/error.dart'; - -import '../app.dart'; -import 'action_code_settings.dart'; import '../utils/validator.dart' as validator; -import 'package:http/http.dart'; - -import 'package:collection/collection.dart'; +import 'action_code_settings.dart'; class ApiClient { final Client httpClient; @@ -67,6 +65,7 @@ class ApiClient { class AuthRequestHandler { final ApiClient apiClient; + // ignore: prefer_function_declarations_over_variables static AuthRequestHandler Function(App app) factory = (app) => AuthRequestHandler._(app); @@ -87,6 +86,16 @@ class AuthRequestHandler { }); } + /// Looks up users by their uids. + Future> getAccountInfoByUids(List uids) async { + if (uids.any((uid) => !validator.isUid(uid))) { + throw FirebaseAuthError.invalidUid(); + } + return _getAccountInfo({ + 'localId': uids, + }); + } + /// Looks up a user by email. Future> getAccountInfoByEmail(String email) async { if (!validator.isEmail(email)) { @@ -474,6 +483,7 @@ class CreateEditAccountRequest { // will be passed. // Currently this applies to phone provider only. if (phoneNumber == '') 'deleteProvider': ['phone'], + if (customAttributes != null) 'customAttributes': customAttributes, if (validSince != null) 'validSince': validSince!.millisecondsSinceEpoch ~/ 1000 }; diff --git a/lib/src/auth/credential.dart b/lib/src/auth/credential.dart index 311e2bf..d532786 100644 --- a/lib/src/auth/credential.dart +++ b/lib/src/auth/credential.dart @@ -1,14 +1,15 @@ import 'dart:async'; -import 'dart:io'; import 'dart:convert'; -import 'package:x509/x509.dart'; -import '../utils/error.dart'; +import 'dart:io'; + +import 'package:clock/clock.dart'; import 'package:http/http.dart' as http; import 'package:jose/jose.dart'; -import 'package:crypto_keys/crypto_keys.dart'; -import '../credential.dart'; -import 'package:clock/clock.dart'; import 'package:openid_client/openid_client.dart' as openid; +import 'package:x509/x509.dart'; + +import '../credential.dart'; +import '../utils/error.dart'; /// Contains the properties necessary to use service-account JSON credentials. class Certificate { diff --git a/lib/src/auth/token_generator.dart b/lib/src/auth/token_generator.dart index 3427e3b..31feef1 100644 --- a/lib/src/auth/token_generator.dart +++ b/lib/src/auth/token_generator.dart @@ -9,6 +9,7 @@ import '../utils/validator.dart' as validator; class FirebaseTokenGenerator { final App app; + // ignore: prefer_function_declarations_over_variables static FirebaseTokenGenerator Function(App app) factory = (app) => FirebaseTokenGenerator(app); diff --git a/lib/src/auth/token_verifier.dart b/lib/src/auth/token_verifier.dart index e979c53..b3cdf6b 100644 --- a/lib/src/auth/token_verifier.dart +++ b/lib/src/auth/token_verifier.dart @@ -1,8 +1,8 @@ +import 'package:meta/meta.dart'; import 'package:openid_client/openid_client.dart'; import '../../firebase_admin.dart'; import '../app/app_extension.dart'; -import 'package:meta/meta.dart'; import '../utils/validator.dart' as validator; /// Class for verifying general purpose Firebase JWTs. @@ -14,6 +14,7 @@ class FirebaseTokenVerifier { final String _jwtName = 'ID token'; + // ignore: prefer_function_declarations_over_variables static FirebaseTokenVerifier Function(App app) factory = (app) => FirebaseTokenVerifier(app); diff --git a/lib/src/auth/user_record.dart b/lib/src/auth/user_record.dart index 7cc68b5..725f46d 100644 --- a/lib/src/auth/user_record.dart +++ b/lib/src/auth/user_record.dart @@ -15,14 +15,14 @@ class UserMetadata { ? null : DateTime.fromMillisecondsSinceEpoch( int.parse(map['createdAt'])), - lastSignInTime: map['lastSignInTime'] == null + lastSignInTime: map['lastLoginAt'] == null ? null : DateTime.fromMillisecondsSinceEpoch( - int.parse(map['lastSignInTime']))); + int.parse(map['lastLoginAt']))); Map toJson() { return { - 'lastSignInTime': lastSignInTime?.toIso8601String(), + 'lastLoginAt': lastSignInTime?.toIso8601String(), 'creationTime': creationTime?.toIso8601String(), }; } diff --git a/lib/src/database.dart b/lib/src/database.dart index 56cbe1c..6b30503 100644 --- a/lib/src/database.dart +++ b/lib/src/database.dart @@ -1,13 +1,11 @@ import 'dart:async'; import 'package:firebase_admin/src/service.dart'; +import 'package:firebase_dart/standalone_database.dart'; -import '../firebase_admin.dart'; +import 'app.dart'; import 'app/app.dart'; import 'app/app_extension.dart'; -import 'app.dart'; - -import 'package:firebase_dart/standalone_database.dart'; class _AuthTokenProvider implements AuthTokenProvider { final FirebaseAppInternals internals; @@ -22,13 +20,11 @@ class _AuthTokenProvider implements AuthTokenProvider { } @override - Stream get onTokenChanged { - var controller = StreamController(); - var listener = (v) => controller.add(v); + Stream?> get onTokenChanged { + var controller = StreamController?>(); + listener(String v) => controller.add(Future.value(v)); - controller.onListen = () { - internals.addAuthTokenListener(listener); - }; + controller.onListen = () => internals.addAuthTokenListener(listener); controller.onCancel = () => internals.removeAuthTokenListener(listener); return controller.stream; diff --git a/lib/src/storage.dart b/lib/src/storage.dart index f5ef08c..3b33b96 100644 --- a/lib/src/storage.dart +++ b/lib/src/storage.dart @@ -1,9 +1,8 @@ import 'package:firebase_admin/firebase_admin.dart'; +import 'package:firebase_admin/src/service.dart'; import 'package:firebase_admin/src/utils/api_request.dart'; import 'package:gcloud/storage.dart' as gcloud; -import 'package:firebase_admin/src/app.dart'; -import 'package:firebase_admin/src/service.dart'; import 'app/app_extension.dart'; /// Storage service bound to the provided app. diff --git a/lib/src/testing.dart b/lib/src/testing.dart index c0e4454..b60a9c6 100644 --- a/lib/src/testing.dart +++ b/lib/src/testing.dart @@ -1,17 +1,17 @@ import 'dart:convert'; import 'package:clock/clock.dart'; -import 'package:firebase_admin/src/auth/credential.dart'; -import 'package:firebase_admin/src/auth/token_verifier.dart'; -import 'package:firebase_admin/src/credential.dart'; import 'package:firebase_admin/firebase_admin.dart'; import 'package:firebase_admin/src/app.dart'; +import 'package:firebase_admin/src/auth/credential.dart'; +import 'package:firebase_admin/src/auth/token_verifier.dart'; import 'package:jose/jose.dart'; import 'package:openid_client/openid_client.dart' hide Credential; class ServiceAccountMockCredential extends ServiceAccountCredential with MockCredentialMixin { @override + // ignore: prefer_function_declarations_over_variables late final AccessToken Function() tokenFactory = () { return MockAccessToken.fromJson({ 'access_token': (JsonWebSignatureBuilder() diff --git a/lib/src/utils/api_request.dart b/lib/src/utils/api_request.dart index b38c164..744757d 100644 --- a/lib/src/utils/api_request.dart +++ b/lib/src/utils/api_request.dart @@ -1,6 +1,3 @@ - - -import 'package:firebase_admin/firebase_admin.dart'; import 'package:http/http.dart'; import '../app.dart'; diff --git a/lib/src/utils/error.dart b/lib/src/utils/error.dart index c516441..f5cf8de 100644 --- a/lib/src/utils/error.dart +++ b/lib/src/utils/error.dart @@ -1,3 +1,5 @@ +// ignore_for_file: non_constant_identifier_names + import 'dart:convert'; /// Base class for all Firebase exceptions. @@ -69,7 +71,7 @@ class FirebaseAuthError extends _PrefixedFirebaseError { // serverErrorCode could contain additional details: // ERROR_CODE : Detailed message which can also contain colons final colonSeparator = serverErrorCode.indexOf(':'); - var customMessage; + String? customMessage; if (colonSeparator != -1) { customMessage = serverErrorCode.substring(colonSeparator + 1).trim(); serverErrorCode = serverErrorCode.substring(0, colonSeparator).trim(); diff --git a/pubspec.yaml b/pubspec.yaml index 96df4c9..cedeef5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -12,16 +12,17 @@ dependencies: jose: ^0.3.2 dotenv: ^3.0.0 openid_client: ^0.4.3 - firebase_dart: ^1.0.0-dev.49 + firebase_dart: ^1.0.9 path: ^1.8.0 meta: ^1.4.0 http: ^0.13.0 crypto_keys: ^0.3.0 collection: ^1.15.0 gcloud: ^0.8.0 + googleapis: '>=8.0.0 <12.0.0' dev_dependencies: test: ^1.17.8 fake_async: ^1.2.0 - pedantic: ^1.11.1 + lints: ^1.0.1 mockito: ^5.0.10 diff --git a/test/app_test.dart b/test/app_test.dart index 0c05b54..a97db23 100644 --- a/test/app_test.dart +++ b/test/app_test.dart @@ -1,20 +1,17 @@ +import 'dart:async'; import 'dart:io'; import 'package:clock/clock.dart'; +import 'package:dotenv/dotenv.dart'; +import 'package:fake_async/fake_async.dart'; +import 'package:firebase_admin/firebase_admin.dart'; +import 'package:firebase_admin/src/app.dart'; import 'package:firebase_admin/src/auth/credential.dart'; -import 'package:firebase_admin/src/credential.dart'; +import 'package:firebase_admin/src/service.dart'; import 'package:firebase_admin/src/testing.dart'; import 'package:test/test.dart'; -import 'package:firebase_admin/firebase_admin.dart'; -import 'package:firebase_admin/src/service.dart'; -import 'dart:async'; import 'resources/mocks.dart' as mocks; -import 'package:fake_async/fake_async.dart'; -import 'package:dotenv/dotenv.dart'; - -import 'resources/mocks.dart'; -import 'package:firebase_admin/src/app.dart'; Matcher throwsAppError([String? message]) => throwsA(TypeMatcher() @@ -586,7 +583,7 @@ void main() { group('App.internals.addAuthTokenListener()', () { test('is notified when the token changes', () async { - var calledWithValue; + String? calledWithValue; mockApp.internals.addAuthTokenListener(expectAsync1((v) { calledWithValue = v; }, count: 1)); diff --git a/test/auth_test.dart b/test/auth_test.dart index d7de079..262c9c6 100644 --- a/test/auth_test.dart +++ b/test/auth_test.dart @@ -1,16 +1,14 @@ import 'dart:convert'; import 'dart:io'; -import 'package:firebase_admin/firebase_admin.dart'; import 'package:firebase_admin/src/auth/auth_api_request.dart'; -import 'package:firebase_admin/src/auth/user_record.dart'; import 'package:firebase_admin/testing.dart'; import 'package:jose/jose.dart'; +import 'package:mockito/mockito.dart'; import 'package:openid_client/openid_client.dart'; import 'package:test/test.dart'; -import 'resources/mocks.dart' as mocks; -import 'package:mockito/mockito.dart'; +import 'resources/mocks.dart' as mocks; import 'resources/mocks.dart'; Matcher throwsFirebaseError([String? code]) => throwsA( diff --git a/test/resources/mocks.dart b/test/resources/mocks.dart index 9823621..3d7f449 100644 --- a/test/resources/mocks.dart +++ b/test/resources/mocks.dart @@ -1,7 +1,6 @@ import 'package:firebase_admin/src/auth/credential.dart'; import 'package:firebase_admin/src/testing.dart'; import 'package:firebase_admin/testing.dart'; -import 'package:firebase_admin/src/app.dart'; var projectId = 'project_id';