Skip to content

Commit dfa8d02

Browse files
authored
refactor(https): change token extraction functions to accept a Map of headers instead of a Request object (#107)
1 parent 22f3500 commit dfa8d02

File tree

3 files changed

+50
-69
lines changed

3 files changed

+50
-69
lines changed

lib/src/https/auth.dart

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import 'dart:convert';
2020
import 'package:dart_firebase_admin/app_check.dart';
2121
import 'package:dart_firebase_admin/auth.dart';
2222
import 'package:dart_firebase_admin/dart_firebase_admin.dart';
23-
import 'package:shelf/shelf.dart';
2423

2524
import 'callable.dart';
2625

@@ -60,10 +59,10 @@ final _jwtRegex = RegExp(
6059
///
6160
/// Returns a tuple of (TokenStatus, AuthData?).
6261
Future<(TokenStatus, AuthData?)> extractAuthToken(
63-
Request request, {
62+
Map<String, String> headers, {
6463
Auth? auth,
6564
}) async {
66-
final authorization = request.headers['authorization'];
65+
final authorization = headers['authorization'];
6766
if (authorization == null || authorization.isEmpty) {
6867
return (TokenStatus.missing, null);
6968
}
@@ -134,10 +133,10 @@ Future<(TokenStatus, AuthData?)> extractAuthToken(
134133
///
135134
/// Returns a tuple of (TokenStatus, AppCheckData?).
136135
Future<(TokenStatus, AppCheckData?)> extractAppCheckToken(
137-
Request request, {
136+
Map<String, String> headers, {
138137
AppCheck? appCheck,
139138
}) async {
140-
final appCheckToken = request.headers['x-firebase-appcheck'];
139+
final appCheckToken = headers['x-firebase-appcheck'];
141140
if (appCheckToken == null || appCheckToken.isEmpty) {
142141
return (TokenStatus.missing, null);
143142
}
@@ -182,14 +181,14 @@ Future<
182181
AppCheckData? appCheckData,
183182
})
184183
>
185-
checkTokens(Request request, {FirebaseApp? adminApp}) async {
184+
checkTokens(Map<String, String> headers, {FirebaseApp? adminApp}) async {
186185
final (authStatus, authData) = await extractAuthToken(
187-
request,
186+
headers,
188187
auth: adminApp?.auth(),
189188
);
190189

191190
final (appStatus, appCheckData) = await extractAppCheckToken(
192-
request,
191+
headers,
193192
appCheck: adminApp?.appCheck(),
194193
);
195194

lib/src/https/https_namespace.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ class HttpsNamespace extends FunctionsNamespace {
107107
// Extract auth and app check tokens
108108

109109
final tokens = await checkTokens(
110-
request,
110+
request.headers,
111111
adminApp: firebase.$env.skipTokenVerification
112112
? null
113113
: firebase.adminApp,
@@ -188,7 +188,7 @@ class HttpsNamespace extends FunctionsNamespace {
188188
// Extract auth and app check tokens
189189

190190
final tokens = await checkTokens(
191-
request,
191+
request.headers,
192192
adminApp: firebase.$env.skipTokenVerification
193193
? null
194194
: firebase.adminApp,

test/unit/https_auth_test.dart

Lines changed: 41 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import 'dart:convert';
1616

1717
import 'package:firebase_functions/src/https/auth.dart';
1818
import 'package:firebase_functions/src/https/callable.dart';
19-
import 'package:shelf/shelf.dart';
2019
import 'package:test/test.dart';
2120

2221
void main() {
@@ -26,9 +25,7 @@ void main() {
2625

2726
group('extractAuthToken', () {
2827
test('returns missing when no Authorization header', () async {
29-
final request = _createRequest();
30-
31-
final (status, auth) = await extractAuthToken(request);
28+
final (status, auth) = await extractAuthToken({});
3229

3330
expect(status, TokenStatus.missing);
3431
expect(auth, isNull);
@@ -37,11 +34,9 @@ void main() {
3734
test(
3835
'returns invalid when Authorization header is not Bearer format',
3936
() async {
40-
final request = _createRequest(
41-
headers: {'authorization': 'Basic abc123'},
42-
);
43-
44-
final (status, auth) = await extractAuthToken(request);
37+
final (status, auth) = await extractAuthToken({
38+
'authorization': 'Basic abc123',
39+
});
4540

4641
expect(status, TokenStatus.invalid);
4742
expect(auth, isNull);
@@ -50,9 +45,10 @@ void main() {
5045

5146
test('returns invalid when token has no uid/sub claim', () async {
5247
final jwt = _createJwt({'email': 'test@example.com'});
53-
final request = _createRequest(headers: {'authorization': 'Bearer $jwt'});
5448

55-
final (status, auth) = await extractAuthToken(request);
49+
final (status, auth) = await extractAuthToken({
50+
'authorization': 'Bearer $jwt',
51+
});
5652

5753
expect(status, TokenStatus.invalid);
5854
expect(auth, isNull);
@@ -64,9 +60,10 @@ void main() {
6460
'email': 'test@example.com',
6561
'custom_claim': 'value',
6662
});
67-
final request = _createRequest(headers: {'authorization': 'Bearer $jwt'});
6863

69-
final (status, auth) = await extractAuthToken(request);
64+
final (status, auth) = await extractAuthToken({
65+
'authorization': 'Bearer $jwt',
66+
});
7067

7168
expect(status, TokenStatus.valid);
7269
expect(auth, isNotNull);
@@ -78,40 +75,41 @@ void main() {
7875

7976
test('extracts uid from user_id claim as fallback', () async {
8077
final jwt = _createJwt({'user_id': 'user456'});
81-
final request = _createRequest(headers: {'authorization': 'Bearer $jwt'});
8278

83-
final (status, auth) = await extractAuthToken(request);
79+
final (status, auth) = await extractAuthToken({
80+
'authorization': 'Bearer $jwt',
81+
});
8482

8583
expect(status, TokenStatus.valid);
8684
expect(auth?.uid, 'user456');
8785
});
8886

8987
test('handles case-insensitive Bearer prefix', () async {
9088
final jwt = _createJwt({'sub': 'user123'});
91-
final request = _createRequest(headers: {'authorization': 'bearer $jwt'});
9289

93-
final (status, auth) = await extractAuthToken(request);
90+
final (status, auth) = await extractAuthToken({
91+
'authorization': 'bearer $jwt',
92+
});
9493

9594
expect(status, TokenStatus.valid);
9695
expect(auth?.uid, 'user123');
9796
});
9897

9998
test('returns invalid for malformed JWT', () async {
100-
final request = _createRequest(
101-
headers: {'authorization': 'Bearer not-a-valid-jwt'},
102-
);
103-
104-
final (status, auth) = await extractAuthToken(request);
99+
final (status, auth) = await extractAuthToken({
100+
'authorization': 'Bearer not-a-valid-jwt',
101+
});
105102

106103
expect(status, TokenStatus.invalid);
107104
expect(auth, isNull);
108105
});
109106

110107
test('returns invalid for JWT with empty payload', () async {
111108
final jwt = _createJwt({});
112-
final request = _createRequest(headers: {'authorization': 'Bearer $jwt'});
113109

114-
final (status, auth) = await extractAuthToken(request);
110+
final (status, auth) = await extractAuthToken({
111+
'authorization': 'Bearer $jwt',
112+
});
115113

116114
expect(status, TokenStatus.invalid);
117115
expect(auth, isNull);
@@ -120,29 +118,29 @@ void main() {
120118

121119
group('extractAppCheckToken', () {
122120
test('returns missing when no X-Firebase-AppCheck header', () async {
123-
final request = _createRequest();
124-
125-
final (status, appCheck) = await extractAppCheckToken(request);
121+
final (status, appCheck) = await extractAppCheckToken({});
126122

127123
expect(status, TokenStatus.missing);
128124
expect(appCheck, isNull);
129125
});
130126

131127
test('returns invalid when token has no sub claim', () async {
132128
final jwt = _createJwt({'other': 'value'});
133-
final request = _createRequest(headers: {'x-firebase-appcheck': jwt});
134129

135-
final (status, appCheck) = await extractAppCheckToken(request);
130+
final (status, appCheck) = await extractAppCheckToken({
131+
'x-firebase-appcheck': jwt,
132+
});
136133

137134
expect(status, TokenStatus.invalid);
138135
expect(appCheck, isNull);
139136
});
140137

141138
test('returns valid with AppCheckData for valid token', () async {
142139
final jwt = _createJwt({'sub': 'app123'});
143-
final request = _createRequest(headers: {'x-firebase-appcheck': jwt});
144140

145-
final (status, appCheck) = await extractAppCheckToken(request);
141+
final (status, appCheck) = await extractAppCheckToken({
142+
'x-firebase-appcheck': jwt,
143+
});
146144

147145
expect(status, TokenStatus.valid);
148146
expect(appCheck, isNotNull);
@@ -152,9 +150,10 @@ void main() {
152150

153151
test('extracts app_id from explicit claim', () async {
154152
final jwt = _createJwt({'sub': 'sub-value', 'app_id': 'explicit-app-id'});
155-
final request = _createRequest(headers: {'x-firebase-appcheck': jwt});
156153

157-
final (status, appCheck) = await extractAppCheckToken(request);
154+
final (status, appCheck) = await extractAppCheckToken({
155+
'x-firebase-appcheck': jwt,
156+
});
158157

159158
expect(status, TokenStatus.valid);
160159
expect(appCheck?.appId, 'explicit-app-id');
@@ -165,14 +164,11 @@ void main() {
165164
test('returns both auth and app check data when present', () async {
166165
final authJwt = _createJwt({'sub': 'user123'});
167166
final appCheckJwt = _createJwt({'sub': 'app123'});
168-
final request = _createRequest(
169-
headers: {
170-
'authorization': 'Bearer $authJwt',
171-
'x-firebase-appcheck': appCheckJwt,
172-
},
173-
);
174167

175-
final result = await checkTokens(request);
168+
final result = await checkTokens({
169+
'authorization': 'Bearer $authJwt',
170+
'x-firebase-appcheck': appCheckJwt,
171+
});
176172

177173
expect(result.result.auth, TokenStatus.valid);
178174
expect(result.result.app, TokenStatus.valid);
@@ -181,9 +177,7 @@ void main() {
181177
});
182178

183179
test('returns missing status when headers are absent', () async {
184-
final request = _createRequest();
185-
186-
final result = await checkTokens(request);
180+
final result = await checkTokens({});
187181

188182
expect(result.result.auth, TokenStatus.missing);
189183
expect(result.result.app, TokenStatus.missing);
@@ -194,14 +188,11 @@ void main() {
194188
test('handles mixed valid and invalid tokens', () async {
195189
final authJwt = _createJwt({'sub': 'user123'});
196190
final invalidAppCheckJwt = _createJwt({'no_sub': 'value'});
197-
final request = _createRequest(
198-
headers: {
199-
'authorization': 'Bearer $authJwt',
200-
'x-firebase-appcheck': invalidAppCheckJwt,
201-
},
202-
);
203191

204-
final result = await checkTokens(request);
192+
final result = await checkTokens({
193+
'authorization': 'Bearer $authJwt',
194+
'x-firebase-appcheck': invalidAppCheckJwt,
195+
});
205196

206197
expect(result.result.auth, TokenStatus.valid);
207198
expect(result.result.app, TokenStatus.invalid);
@@ -267,12 +258,3 @@ String _createJwt(Map<String, dynamic> payload) {
267258

268259
return '$header.$body.$signature';
269260
}
270-
271-
/// Creates a test request with optional headers.
272-
Request _createRequest({Map<String, String>? headers}) {
273-
return Request(
274-
'POST',
275-
Uri.parse('http://localhost:8080/test'),
276-
headers: headers,
277-
);
278-
}

0 commit comments

Comments
 (0)