Skip to content

Commit e32d02f

Browse files
committed
refactor: simplify token verification and app initialization
- Replace `skipTokenVerification` flag in token checking with `adminApp` presence check - Use `firebase.$env` for accessing environment details - Consolidate token extraction tests to reflect simplified API - Resolve trace context testing issues
1 parent a1f712f commit e32d02f

File tree

5 files changed

+67
-119
lines changed

5 files changed

+67
-119
lines changed

lib/logger.dart

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,8 @@
5050
library;
5151

5252
export 'src/logger/logger.dart'
53-
hide createLogger, projectIdZoneKey, traceIdZoneKey;
53+
hide
54+
createLogger,
55+
projectIdZoneKey,
56+
traceIdZoneKey,
57+
cloudTraceContextHeader;

lib/src/firebase.dart

Lines changed: 30 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,45 +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() : _env = FirebaseEnv() {
43-
_initializeAdminSDK();
44-
}
45-
46-
FirebaseApp? _adminApp;
47-
gfs.Firestore? _firestoreInstance;
48-
49-
/// Initialize the Firebase Admin SDK
50-
void _initializeAdminSDK() {
51-
if (_env.isEmulator) {
52-
// TODO: Implement direct REST API calls to emulator
53-
// For now, we'll skip document fetching in emulator mode
54-
return;
55-
}
41+
factory Firebase() {
42+
final env = FirebaseEnv();
43+
44+
// Initialize Admin SDK
45+
final adminApp = FirebaseApp.initializeApp(
46+
options: AppOptions(
47+
credential: Credential.fromApplicationDefaultCredentials(),
48+
projectId: env.projectId,
49+
),
50+
);
5651

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

59+
Firebase._({
60+
required this.adminApp,
61+
required this.firestoreAdmin,
62+
required FirebaseEnv env,
63+
}) : _env = env;
64+
7465
final FirebaseEnv _env;
7566

76-
/// Get the Firestore instance
77-
gfs.Firestore? get firestoreAdmin => _firestoreInstance;
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;
7872

79-
/// Get the Firebase Admin App instance
80-
FirebaseApp? get adminApp => _adminApp;
73+
/// The Firestore admin client instance.
74+
///
75+
/// Provides elevated server-side access to the Firestore database.
76+
final gfs.Firestore firestoreAdmin;
8177

8278
/// HTTPS triggers namespace.
8379
HttpsNamespace get https => HttpsNamespace(this);

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: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -100,11 +100,12 @@ class HttpsNamespace extends FunctionsNamespace {
100100
}
101101

102102
// Extract auth and app check tokens
103-
final skipVerification = firebase.$env.skipTokenVerification;
103+
104104
final tokens = await checkTokens(
105105
request,
106-
skipTokenVerification: skipVerification,
107-
adminApp: firebase.adminApp,
106+
adminApp: firebase.$env.skipTokenVerification
107+
? null
108+
: firebase.adminApp,
108109
);
109110

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

182183
// Extract auth and app check tokens
183-
final skipVerification = firebase.$env.skipTokenVerification;
184+
184185
final tokens = await checkTokens(
185186
request,
186-
skipTokenVerification: skipVerification,
187-
adminApp: firebase.adminApp,
187+
adminApp: firebase.$env.skipTokenVerification
188+
? null
189+
: firebase.adminApp,
188190
);
189191

190192
// 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)