Skip to content

Commit a2d695d

Browse files
committed
feat: firestore auth trigger
1 parent 7ec8df3 commit a2d695d

File tree

10 files changed

+977
-9
lines changed

10 files changed

+977
-9
lines changed

example/firestore/lib/main.dart

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,35 @@ void main(List<String> args) async {
4848
}
4949
});
5050

51+
// Firestore WithAuthContext trigger examples
52+
// These variants include authentication context identifying
53+
// the principal that triggered the Firestore write.
54+
firebase.firestore.onDocumentCreatedWithAuthContext(
55+
document: 'orders/{orderId}',
56+
(event) async {
57+
print('Document created by: ${event.authType} (${event.authId})');
58+
final data = event.data?.data();
59+
print(' Order: ${data?['product']}');
60+
print(' Params: ${event.params}');
61+
},
62+
);
63+
64+
firebase.firestore.onDocumentWrittenWithAuthContext(
65+
document: 'orders/{orderId}',
66+
(event) async {
67+
print('Document written by: ${event.authType} (${event.authId})');
68+
final before = event.data?.before;
69+
final after = event.data?.after;
70+
if (before == null || !before.exists) {
71+
print(' Operation: CREATE');
72+
} else if (after == null || !after.exists) {
73+
print(' Operation: DELETE');
74+
} else {
75+
print(' Operation: UPDATE');
76+
}
77+
},
78+
);
79+
5180
// Nested collection trigger example
5281
firebase.firestore.onDocumentCreated(
5382
document: 'posts/{postId}/comments/{commentId}',

example/firestore_test/lib/main.dart

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,34 @@ void main(List<String> args) async {
8787
}
8888
});
8989

90+
// Test onDocumentCreatedWithAuthContext
91+
firebase.firestore.onDocumentCreatedWithAuthContext(
92+
document: 'orders/{orderId}',
93+
(event) async {
94+
print('=== ORDER CREATED WITH AUTH CONTEXT ===');
95+
print('Order created: ${event.document}');
96+
print('Auth type: ${event.authType}');
97+
print('Auth ID: ${event.authId}');
98+
print('Params: ${event.params}');
99+
100+
if (event.data != null) {
101+
final data = event.data!.data();
102+
print('Order data: $data');
103+
}
104+
},
105+
);
106+
107+
// Test onDocumentWrittenWithAuthContext
108+
firebase.firestore.onDocumentWrittenWithAuthContext(
109+
document: 'orders/{orderId}',
110+
(event) async {
111+
print('=== ORDER WRITTEN WITH AUTH CONTEXT ===');
112+
print('Order written: ${event.document}');
113+
print('Auth type: ${event.authType}');
114+
print('Auth ID: ${event.authId}');
115+
},
116+
);
117+
90118
// Test onDocumentWritten (catches all operations)
91119
firebase.firestore.onDocumentWritten(document: 'users/{userId}', (
92120
event,

lib/builder.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,10 @@ class _FirebaseFunctionsVisitor extends RecursiveAstVisitor<void> {
117117
'onDocumentUpdated',
118118
'onDocumentDeleted',
119119
'onDocumentWritten',
120+
'onDocumentCreatedWithAuthContext',
121+
'onDocumentUpdatedWithAuthContext',
122+
'onDocumentDeletedWithAuthContext',
123+
'onDocumentWrittenWithAuthContext',
120124
],
121125
),
122126
_Namespace(

lib/src/builder/manifest.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,14 @@ String _mapFirestoreEventType(String methodName) => switch (methodName) {
414414
'onDocumentUpdated' => 'google.cloud.firestore.document.v1.updated',
415415
'onDocumentDeleted' => 'google.cloud.firestore.document.v1.deleted',
416416
'onDocumentWritten' => 'google.cloud.firestore.document.v1.written',
417+
'onDocumentCreatedWithAuthContext' =>
418+
'google.cloud.firestore.document.v1.created.withAuthContext',
419+
'onDocumentUpdatedWithAuthContext' =>
420+
'google.cloud.firestore.document.v1.updated.withAuthContext',
421+
'onDocumentDeletedWithAuthContext' =>
422+
'google.cloud.firestore.document.v1.deleted.withAuthContext',
423+
'onDocumentWrittenWithAuthContext' =>
424+
'google.cloud.firestore.document.v1.written.withAuthContext',
417425
_ => throw ArgumentError('Unknown Firestore event type: $methodName'),
418426
};
419427

lib/src/firestore/event.dart

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,43 @@
11
import '../common/cloud_event.dart';
22

3+
/// The type of principal that triggered the event.
4+
///
5+
/// Matches the Node.js SDK's `AuthType` type.
6+
enum AuthType {
7+
/// A non-user principal used to identify a workload or machine user.
8+
serviceAccount('service_account'),
9+
10+
/// A non-user client API key.
11+
apiKey('api_key'),
12+
13+
/// An obscured identity used when Cloud Platform or another system
14+
/// triggered the event (e.g., TTL-based database deletion).
15+
system('system'),
16+
17+
/// An unauthenticated action.
18+
unauthenticated('unauthenticated'),
19+
20+
/// A general type to capture all other principals not captured
21+
/// in other auth types.
22+
unknown('unknown');
23+
24+
const AuthType(this.value);
25+
26+
/// The wire value as sent in CloudEvent headers.
27+
final String value;
28+
29+
/// Parses an [AuthType] from the wire value string.
30+
///
31+
/// Returns [AuthType.unknown] if the value is not recognized.
32+
static AuthType fromString(String value) => switch (value) {
33+
'service_account' => AuthType.serviceAccount,
34+
'api_key' => AuthType.apiKey,
35+
'system' => AuthType.system,
36+
'unauthenticated' => AuthType.unauthenticated,
37+
_ => AuthType.unknown,
38+
};
39+
}
40+
341
/// A CloudEvent that contains a DocumentSnapshot or a `Change<DocumentSnapshot>`.
442
///
543
/// This event type extends the base [CloudEvent] with Firestore-specific fields.
@@ -93,3 +131,58 @@ class FirestoreEvent<T extends Object?> extends CloudEvent<T> {
93131
return json;
94132
}
95133
}
134+
135+
/// A [FirestoreEvent] that includes authentication context.
136+
///
137+
/// This event type is used by the `WithAuthContext` trigger variants
138+
/// (e.g., [FirestoreNamespace.onDocumentCreatedWithAuthContext]).
139+
///
140+
/// The auth context identifies which principal triggered the Firestore write.
141+
/// Note: this is different from HTTPS callable auth — it identifies the
142+
/// principal type (service account, API key, etc.), not a Firebase Auth user.
143+
///
144+
/// Example:
145+
/// ```dart
146+
/// firebase.firestore.onDocumentCreatedWithAuthContext(
147+
/// document: 'users/{userId}',
148+
/// (event) async {
149+
/// print('Auth type: ${event.authType}');
150+
/// print('Auth ID: ${event.authId}');
151+
/// },
152+
/// );
153+
/// ```
154+
class FirestoreAuthEvent<T extends Object?> extends FirestoreEvent<T> {
155+
const FirestoreAuthEvent({
156+
super.data,
157+
required super.id,
158+
required super.source,
159+
required super.specversion,
160+
super.subject,
161+
required super.time,
162+
required super.type,
163+
required super.location,
164+
required super.project,
165+
required super.database,
166+
required super.namespace,
167+
required super.document,
168+
required super.params,
169+
required this.authType,
170+
this.authId,
171+
});
172+
173+
/// The type of principal that triggered the event.
174+
final AuthType authType;
175+
176+
/// The unique identifier for the principal.
177+
///
178+
/// May be `null` for system-triggered or unauthenticated events.
179+
final String? authId;
180+
181+
@override
182+
Map<String, dynamic> toJson(Map<String, dynamic> Function(T) dataEncoder) {
183+
final json = super.toJson(dataEncoder);
184+
json['authType'] = authType.value;
185+
if (authId != null) json['authId'] = authId;
186+
return json;
187+
}
188+
}

0 commit comments

Comments
 (0)