Skip to content

Commit 1f54504

Browse files
committed
Fix bridge contact event filtering: scope by authorship and bridge streams
- eventIsAccessible: bridges filter by eventIsFromContact + bridge stream tree (prevents wildcard * from showing all account data under bridge contact) - eventIsFromContact: also check clientData.previousAccessIds (bridge recreate pattern) - initStreamCache: bridges with * resolve own streams from access name instead of wildcard - getContacts: include deleted accesses for bridge modifiedBy matching
1 parent d11adcc commit 1f54504

2 files changed

Lines changed: 50 additions & 6 deletions

File tree

ts/appTemplates/AppClientAccount.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,8 @@ export class AppClientAccount extends Application {
131131
}
132132

133133
// Other accesses (bridges, orphan collectors, custom apps)
134-
const allAccesses = await this.connection.apiOne('accesses.get', {}, 'accesses');
134+
// Include deletions so bridge contacts can match events created by old (recreated) accesses
135+
const allAccesses = await this.connection.apiOne('accesses.get', { includeDeletions: true }, 'accesses');
135136
const collectorAccessNames = new Set(collectorClients.map(cc => cc.key));
136137
for (const access of allAccesses) {
137138
if (collectorAccessNames.has(access.name)) continue;
@@ -191,6 +192,18 @@ export class AppClientAccount extends Application {
191192
contact.addAccessObject(accessById[source.accessId]);
192193
}
193194
}
195+
196+
// For bridge contacts: also add deleted accesses with the same name
197+
// so eventIsFromContact can match events created by old (recreated) access IDs
198+
if (!contact.isPerson) {
199+
const contactAccessNames = new Set(contact.accessObjects.map((a: any) => a.name));
200+
for (const access of allAccesses) {
201+
if (!access.deleted) continue;
202+
if (contactAccessNames.has(access.name) && !contact.accessObjects.some((a: any) => a.id === access.id)) {
203+
contact.addAccessObject(access);
204+
}
205+
}
206+
}
194207
}
195208

196209
return contacts;

ts/appTemplates/Contact.ts

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,18 @@ export class Contact {
8989
for (const p of access.permissions) {
9090
if (!p.streamId) continue;
9191
if (p.streamId === '*') {
92+
if (!this.isPerson) {
93+
// Bridge with wildcard: resolve bridge's own streams from access name
94+
// (access.name is typically the bridge's base stream ID, e.g. "bridge-mira")
95+
const bridgeStream = access.name ? streamsById[access.name] : null;
96+
if (bridgeStream) {
97+
const ids = getStreamIdAndChildrenIds(bridgeStream);
98+
ids.forEach((id: string) => this.#accessibleStreamIds!.add(id));
99+
}
100+
continue; // don't add wildcard for bridges
101+
}
92102
this.#accessibleStreamIds.add('*');
93-
return; // wildcard covers everything
103+
return; // wildcard covers everything for person contacts
94104
}
95105
const stream = streamsById[p.streamId];
96106
if (!stream) continue;
@@ -100,8 +110,26 @@ export class Contact {
100110
}
101111
}
102112

103-
/** Check if an event is in a stream accessible by this contact */
113+
/**
114+
* Check if an event belongs to this contact's scope.
115+
* - Person contacts (doctors): event is in a stream covered by their access permissions
116+
* - Bridge/service contacts: event was created by the bridge OR is in the bridge's streams
117+
* (bridges typically have wildcard `*` permissions but should only show their own data)
118+
*/
104119
eventIsAccessible (event: pryv.Event): boolean {
120+
if (!this.isPerson) {
121+
// Bridge/service contacts: check authorship first (fastest)
122+
if (this.eventIsFromContact(event)) return true;
123+
// Also check if event is in the bridge's own stream tree
124+
// (covers data created by older accesses not in previousAccessIds chain)
125+
if (this.#accessibleStreamIds && !this.#accessibleStreamIds.has('*') && event.streamIds) {
126+
for (const streamId of event.streamIds) {
127+
if (this.#accessibleStreamIds.has(streamId)) return true;
128+
}
129+
}
130+
return false;
131+
}
132+
// Person contacts: filter by stream permissions
105133
if (!this.#accessibleStreamIds) return false;
106134
if (this.#accessibleStreamIds.has('*')) return true;
107135
if (!event.streamIds) return false;
@@ -115,9 +143,12 @@ export class Contact {
115143
eventIsFromContact (event: pryv.Event): boolean {
116144
for (const access of this.accessObjects) {
117145
if (access.id && event.modifiedBy === access.id) return true;
118-
// Check previous access IDs from replaced accesses (access update chain)
119-
const prevIds = access.clientData?.hdsCollectorClient?.previousAccessIds;
120-
if (Array.isArray(prevIds) && prevIds.includes(event.modifiedBy)) return true;
146+
// Check previous access IDs from replaced accesses (collector pattern)
147+
const collectorPrevIds = access.clientData?.hdsCollectorClient?.previousAccessIds;
148+
if (Array.isArray(collectorPrevIds) && collectorPrevIds.includes(event.modifiedBy)) return true;
149+
// Check previous access IDs from bridge access recreate pattern
150+
const bridgePrevIds = access.clientData?.previousAccessIds;
151+
if (Array.isArray(bridgePrevIds) && bridgePrevIds.includes(event.modifiedBy)) return true;
121152
}
122153
return false;
123154
}

0 commit comments

Comments
 (0)