Skip to content

Commit 6f83cb1

Browse files
committed
Refactor Firebase app initialization to fail fast and remove nullable fields
- Replaced nullable and accessors with properties. - Removed the `try...catch` wrapper around `FirebaseApp.initializeApp()` to fail hard on initialization errors rather than suppressing them. - Removed the early emulator exit check; the SDK now proceeds with resolving default credentials even under emulator conditions. - Simplify token extractors (`extractAuthToken`, `extractAppCheckToken`) to just take a nullable to simplify the "skip" behavior - Updated `https_auth_test.dart`
1 parent c165655 commit 6f83cb1

File tree

4 files changed

+65
-119
lines changed

4 files changed

+65
-119
lines changed

lib/src/firebase.dart

Lines changed: 35 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ import 'eventarc/eventarc_namespace.dart';
2727
import 'firestore/firestore_namespace.dart';
2828
import 'https/https_namespace.dart';
2929
import 'identity/identity_namespace.dart';
30-
import 'logger/logger.dart';
3130
import 'pubsub/pubsub_namespace.dart';
3231
import 'remote_config/remote_config_namespace.dart';
3332
import 'scheduler/scheduler_namespace.dart';
@@ -39,44 +38,42 @@ import 'test_lab/test_lab_namespace.dart';
3938
///
4039
/// Provides access to all function namespaces (https, pubsub, firestore, etc.).
4140
class Firebase {
42-
Firebase() {
43-
_initializeAdminSDK();
44-
}
45-
46-
FirebaseApp? _adminApp;
47-
gfs.Firestore? _firestoreInstance;
48-
49-
/// Initialize the Firebase Admin SDK
50-
void _initializeAdminSDK() {
41+
factory Firebase() {
5142
final env = FirebaseEnv();
52-
if (env.isEmulator) {
53-
// TODO: Implement direct REST API calls to emulator
54-
// For now, we'll skip document fetching in emulator mode
55-
return;
56-
}
5743

58-
// Production mode only
59-
try {
60-
// Initialize Admin SDK
61-
_adminApp = FirebaseApp.initializeApp(
62-
options: AppOptions(
63-
credential: Credential.fromApplicationDefaultCredentials(),
64-
projectId: env.projectId,
65-
),
66-
);
67-
68-
// Create Firestore instance
69-
_firestoreInstance = _adminApp!.firestore();
70-
} catch (e) {
71-
logger.warn('Failed to initialize Firebase Admin SDK: $e');
72-
}
44+
// Initialize Admin SDK
45+
final adminApp = FirebaseApp.initializeApp(
46+
options: AppOptions(
47+
credential: Credential.fromApplicationDefaultCredentials(),
48+
projectId: env.projectId,
49+
),
50+
);
51+
52+
return Firebase._(
53+
adminApp: adminApp,
54+
firestoreAdmin: adminApp.firestore(),
55+
env: env,
56+
);
7357
}
7458

75-
/// Get the Firestore instance
76-
gfs.Firestore? get firestoreAdmin => _firestoreInstance;
59+
Firebase._({
60+
required this.adminApp,
61+
required this.firestoreAdmin,
62+
required FirebaseEnv env,
63+
}) : _env = env;
7764

78-
/// Get the Firebase Admin App instance
79-
FirebaseApp? get adminApp => _adminApp;
65+
final FirebaseEnv _env;
66+
67+
/// The initialized Firebase Admin SDK application instance.
68+
///
69+
/// This app represents the server-side SDK and has elevated privileges
70+
/// corresponding to the environment's credentials.
71+
final FirebaseApp adminApp;
72+
73+
/// The Firestore admin client instance.
74+
///
75+
/// Provides elevated server-side access to the Firestore database.
76+
final gfs.Firestore firestoreAdmin;
8077

8178
/// HTTPS triggers namespace.
8279
HttpsNamespace get https => HttpsNamespace(this);
@@ -250,3 +247,7 @@ abstract class FunctionsNamespace {
250247
const FunctionsNamespace(this.firebase);
251248
final Firebase firebase;
252249
}
250+
251+
/// Explicitly not a public property to avoid leaking this!
252+
@internal
253+
FirebaseEnv extractEnv(Firebase app) => app._env;

lib/src/https/auth.dart

Lines changed: 9 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ library;
1717

1818
import 'dart:convert';
1919

20+
import 'package:dart_firebase_admin/app_check.dart';
21+
import 'package:dart_firebase_admin/auth.dart';
2022
import 'package:dart_firebase_admin/dart_firebase_admin.dart';
2123
import 'package:shelf/shelf.dart';
2224

@@ -59,8 +61,7 @@ final _jwtRegex = RegExp(
5961
/// Returns a tuple of (TokenStatus, AuthData?).
6062
Future<(TokenStatus, AuthData?)> extractAuthToken(
6163
Request request, {
62-
required bool skipTokenVerification,
63-
FirebaseApp? adminApp,
64+
Auth? auth,
6465
}) async {
6566
final authorization = request.headers['authorization'];
6667
if (authorization == null || authorization.isEmpty) {
@@ -82,7 +83,7 @@ Future<(TokenStatus, AuthData?)> extractAuthToken(
8283
String uid;
8384
Map<String, dynamic>? decodedToken;
8485

85-
if (skipTokenVerification) {
86+
if (auth == null) {
8687
// In emulator mode, just decode without verification
8788
decodedToken = _unsafeDecodeIdToken(idToken);
8889

@@ -93,12 +94,6 @@ Future<(TokenStatus, AuthData?)> extractAuthToken(
9394
'';
9495
} else {
9596
// In production, verify the token using Firebase Admin SDK
96-
if (adminApp == null) {
97-
// Can't verify without admin app
98-
return (TokenStatus.invalid, null);
99-
}
100-
101-
final auth = adminApp.auth();
10297
final decoded = await auth.verifyIdToken(idToken);
10398
uid = decoded.uid;
10499
decodedToken = {
@@ -140,8 +135,7 @@ Future<(TokenStatus, AuthData?)> extractAuthToken(
140135
/// Returns a tuple of (TokenStatus, AppCheckData?).
141136
Future<(TokenStatus, AppCheckData?)> extractAppCheckToken(
142137
Request request, {
143-
required bool skipTokenVerification,
144-
FirebaseApp? adminApp,
138+
AppCheck? appCheck,
145139
}) async {
146140
final appCheckToken = request.headers['x-firebase-appcheck'];
147141
if (appCheckToken == null || appCheckToken.isEmpty) {
@@ -151,7 +145,7 @@ Future<(TokenStatus, AppCheckData?)> extractAppCheckToken(
151145
try {
152146
String appId;
153147

154-
if (skipTokenVerification) {
148+
if (appCheck == null) {
155149
// In emulator mode, just decode without verification
156150
final decodedToken = _unsafeDecodeAppCheckToken(appCheckToken);
157151

@@ -161,12 +155,6 @@ Future<(TokenStatus, AppCheckData?)> extractAppCheckToken(
161155
'';
162156
} else {
163157
// In production, verify the token using Firebase Admin SDK
164-
if (adminApp == null) {
165-
// Can't verify without admin app
166-
return (TokenStatus.invalid, null);
167-
}
168-
169-
final appCheck = adminApp.appCheck();
170158
final decoded = await appCheck.verifyToken(appCheckToken);
171159
appId = decoded.appId;
172160
}
@@ -194,21 +182,15 @@ Future<
194182
AppCheckData? appCheckData,
195183
})
196184
>
197-
checkTokens(
198-
Request request, {
199-
required bool skipTokenVerification,
200-
FirebaseApp? adminApp,
201-
}) async {
185+
checkTokens(Request request, {FirebaseApp? adminApp}) async {
202186
final (authStatus, authData) = await extractAuthToken(
203187
request,
204-
skipTokenVerification: skipTokenVerification,
205-
adminApp: adminApp,
188+
auth: adminApp?.auth(),
206189
);
207190

208191
final (appStatus, appCheckData) = await extractAppCheckToken(
209192
request,
210-
skipTokenVerification: skipTokenVerification,
211-
adminApp: adminApp,
193+
appCheck: adminApp?.appCheck(),
212194
);
213195

214196
return (

lib/src/https/https_namespace.dart

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import 'dart:convert';
1818
import 'package:meta/meta.dart';
1919
import 'package:shelf/shelf.dart';
2020

21-
import '../common/environment.dart';
2221
import '../common/utilities.dart';
2322
import '../firebase.dart';
2423
import 'auth.dart';
@@ -101,11 +100,11 @@ class HttpsNamespace extends FunctionsNamespace {
101100
}
102101

103102
// Extract auth and app check tokens
104-
final skipVerification = FirebaseEnv().skipTokenVerification;
105103
final tokens = await checkTokens(
106104
request,
107-
skipTokenVerification: skipVerification,
108-
adminApp: firebase.adminApp,
105+
adminApp: extractEnv(firebase).skipTokenVerification
106+
? null
107+
: firebase.adminApp,
109108
);
110109

111110
// Check for invalid auth token
@@ -181,11 +180,11 @@ class HttpsNamespace extends FunctionsNamespace {
181180
final body = await request.json as Map<String, dynamic>?;
182181

183182
// Extract auth and app check tokens
184-
final skipVerification = FirebaseEnv().skipTokenVerification;
185183
final tokens = await checkTokens(
186184
request,
187-
skipTokenVerification: skipVerification,
188-
adminApp: firebase.adminApp,
185+
adminApp: extractEnv(firebase).skipTokenVerification
186+
? null
187+
: firebase.adminApp,
189188
);
190189

191190
// Check for invalid auth token

test/unit/https_auth_test.dart

Lines changed: 15 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,7 @@ void main() {
2828
test('returns missing when no Authorization header', () async {
2929
final request = _createRequest();
3030

31-
final (status, auth) = await extractAuthToken(
32-
request,
33-
skipTokenVerification: true,
34-
);
31+
final (status, auth) = await extractAuthToken(request);
3532

3633
expect(status, TokenStatus.missing);
3734
expect(auth, isNull);
@@ -44,10 +41,7 @@ void main() {
4441
headers: {'authorization': 'Basic abc123'},
4542
);
4643

47-
final (status, auth) = await extractAuthToken(
48-
request,
49-
skipTokenVerification: true,
50-
);
44+
final (status, auth) = await extractAuthToken(request);
5145

5246
expect(status, TokenStatus.invalid);
5347
expect(auth, isNull);
@@ -58,10 +52,7 @@ void main() {
5852
final jwt = _createJwt({'email': 'test@example.com'});
5953
final request = _createRequest(headers: {'authorization': 'Bearer $jwt'});
6054

61-
final (status, auth) = await extractAuthToken(
62-
request,
63-
skipTokenVerification: true,
64-
);
55+
final (status, auth) = await extractAuthToken(request);
6556

6657
expect(status, TokenStatus.invalid);
6758
expect(auth, isNull);
@@ -75,10 +66,7 @@ void main() {
7566
});
7667
final request = _createRequest(headers: {'authorization': 'Bearer $jwt'});
7768

78-
final (status, auth) = await extractAuthToken(
79-
request,
80-
skipTokenVerification: true,
81-
);
69+
final (status, auth) = await extractAuthToken(request);
8270

8371
expect(status, TokenStatus.valid);
8472
expect(auth, isNotNull);
@@ -92,10 +80,7 @@ void main() {
9280
final jwt = _createJwt({'user_id': 'user456'});
9381
final request = _createRequest(headers: {'authorization': 'Bearer $jwt'});
9482

95-
final (status, auth) = await extractAuthToken(
96-
request,
97-
skipTokenVerification: true,
98-
);
83+
final (status, auth) = await extractAuthToken(request);
9984

10085
expect(status, TokenStatus.valid);
10186
expect(auth?.uid, 'user456');
@@ -105,10 +90,7 @@ void main() {
10590
final jwt = _createJwt({'sub': 'user123'});
10691
final request = _createRequest(headers: {'authorization': 'bearer $jwt'});
10792

108-
final (status, auth) = await extractAuthToken(
109-
request,
110-
skipTokenVerification: true,
111-
);
93+
final (status, auth) = await extractAuthToken(request);
11294

11395
expect(status, TokenStatus.valid);
11496
expect(auth?.uid, 'user123');
@@ -119,10 +101,7 @@ void main() {
119101
headers: {'authorization': 'Bearer not-a-valid-jwt'},
120102
);
121103

122-
final (status, auth) = await extractAuthToken(
123-
request,
124-
skipTokenVerification: true,
125-
);
104+
final (status, auth) = await extractAuthToken(request);
126105

127106
expect(status, TokenStatus.invalid);
128107
expect(auth, isNull);
@@ -132,10 +111,7 @@ void main() {
132111
final jwt = _createJwt({});
133112
final request = _createRequest(headers: {'authorization': 'Bearer $jwt'});
134113

135-
final (status, auth) = await extractAuthToken(
136-
request,
137-
skipTokenVerification: true,
138-
);
114+
final (status, auth) = await extractAuthToken(request);
139115

140116
expect(status, TokenStatus.invalid);
141117
expect(auth, isNull);
@@ -146,10 +122,7 @@ void main() {
146122
test('returns missing when no X-Firebase-AppCheck header', () async {
147123
final request = _createRequest();
148124

149-
final (status, appCheck) = await extractAppCheckToken(
150-
request,
151-
skipTokenVerification: true,
152-
);
125+
final (status, appCheck) = await extractAppCheckToken(request);
153126

154127
expect(status, TokenStatus.missing);
155128
expect(appCheck, isNull);
@@ -159,10 +132,7 @@ void main() {
159132
final jwt = _createJwt({'other': 'value'});
160133
final request = _createRequest(headers: {'x-firebase-appcheck': jwt});
161134

162-
final (status, appCheck) = await extractAppCheckToken(
163-
request,
164-
skipTokenVerification: true,
165-
);
135+
final (status, appCheck) = await extractAppCheckToken(request);
166136

167137
expect(status, TokenStatus.invalid);
168138
expect(appCheck, isNull);
@@ -172,10 +142,7 @@ void main() {
172142
final jwt = _createJwt({'sub': 'app123'});
173143
final request = _createRequest(headers: {'x-firebase-appcheck': jwt});
174144

175-
final (status, appCheck) = await extractAppCheckToken(
176-
request,
177-
skipTokenVerification: true,
178-
);
145+
final (status, appCheck) = await extractAppCheckToken(request);
179146

180147
expect(status, TokenStatus.valid);
181148
expect(appCheck, isNotNull);
@@ -187,10 +154,7 @@ void main() {
187154
final jwt = _createJwt({'sub': 'sub-value', 'app_id': 'explicit-app-id'});
188155
final request = _createRequest(headers: {'x-firebase-appcheck': jwt});
189156

190-
final (status, appCheck) = await extractAppCheckToken(
191-
request,
192-
skipTokenVerification: true,
193-
);
157+
final (status, appCheck) = await extractAppCheckToken(request);
194158

195159
expect(status, TokenStatus.valid);
196160
expect(appCheck?.appId, 'explicit-app-id');
@@ -208,7 +172,7 @@ void main() {
208172
},
209173
);
210174

211-
final result = await checkTokens(request, skipTokenVerification: true);
175+
final result = await checkTokens(request);
212176

213177
expect(result.result.auth, TokenStatus.valid);
214178
expect(result.result.app, TokenStatus.valid);
@@ -219,7 +183,7 @@ void main() {
219183
test('returns missing status when headers are absent', () async {
220184
final request = _createRequest();
221185

222-
final result = await checkTokens(request, skipTokenVerification: true);
186+
final result = await checkTokens(request);
223187

224188
expect(result.result.auth, TokenStatus.missing);
225189
expect(result.result.app, TokenStatus.missing);
@@ -237,7 +201,7 @@ void main() {
237201
},
238202
);
239203

240-
final result = await checkTokens(request, skipTokenVerification: true);
204+
final result = await checkTokens(request);
241205

242206
expect(result.result.auth, TokenStatus.valid);
243207
expect(result.result.app, TokenStatus.invalid);

0 commit comments

Comments
 (0)