Skip to content

Commit c57563b

Browse files
authored
Fixes #1331 (#1332)
fix: enabled_clients silently dropped from connection exports
1 parent 008916a commit c57563b

3 files changed

Lines changed: 81 additions & 48 deletions

File tree

src/tools/auth0/handlers/connections.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -161,13 +161,19 @@ export const getConnectionEnabledClients = async (
161161
if (!connectionId) return null;
162162

163163
try {
164-
const enabledClients = await paginate<Management.ConnectionEnabledClient>(
165-
(params) => auth0Client.connections.clients.get(connectionId, params),
166-
{ checkpoint: true, take: 100 }
167-
);
164+
const allClients: Management.ConnectionEnabledClient[] = [];
165+
let page = await auth0Client.connections.clients.get(connectionId, { take: 100 });
166+
167+
allClients.push(...(page.data || []));
168+
169+
while (page.hasNextPage && page.hasNextPage()) {
170+
page = await page.getNextPage();
171+
allClients.push(...(page.data || []));
172+
}
168173

169-
return enabledClients.filter((client) => !!client?.client_id).map((client) => client.client_id);
174+
return allClients.filter((client) => !!client?.client_id).map((client) => client.client_id);
170175
} catch (error) {
176+
log.warn(`Unable to retrieve enabled clients for connection ${connectionId}: ${error?.message}`);
171177
return null;
172178
}
173179
};

test/tools/auth0/handlers/connections.tests.js

Lines changed: 64 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -151,10 +151,12 @@ describe('#connections handler', () => {
151151
let getEnabledClientsCalledOnce = false;
152152
const auth0 = {
153153
connections: {
154+
// Real API does NOT include enabled_clients in the list response;
155+
// they are fetched separately via connections.clients.get.
154156
list: (params) =>
155157
mockPagedData(params, 'connections', [
156-
{ id: 'con1', strategy: 'github', name: 'github', enabled_clients: [clientId] },
157-
{ id: 'con2', strategy: 'auth0', name: 'db-should-be-ignored', enabled_clients: [] },
158+
{ id: 'con1', strategy: 'github', name: 'github' },
159+
{ id: 'con2', strategy: 'auth0', name: 'db-should-be-ignored' },
158160
]),
159161
clients: {
160162
get: () => {
@@ -924,60 +926,85 @@ describe('#connections enabled clients functionality', () => {
924926
});
925927

926928
describe('#getConnectionEnabledClients', () => {
927-
it('should return array of client IDs with single page', async () => {
929+
it('should return array of client IDs from a single-page SDK PagedResponse', async () => {
928930
const connectionId = 'con_123';
929-
mockAuth0Client.connections.clients.get.resolves([
930-
{ client_id: 'client_1' },
931-
{ client_id: 'client_2' },
932-
{ client_id: 'client_3' },
933-
]);
931+
// Auth0 SDK v5 .get() returns a PagedResponse object, not a flat array
932+
mockAuth0Client.connections.clients.get.resolves(
933+
mockPagedData({}, 'clients', [
934+
{ client_id: 'client_1' },
935+
{ client_id: 'client_2' },
936+
{ client_id: 'client_3' },
937+
])
938+
);
934939

935940
const result = await getConnectionEnabledClients(mockAuth0Client, connectionId);
936941

937942
expect(result).to.deep.equal(['client_1', 'client_2', 'client_3']);
938943
sinon.assert.calledOnceWithExactly(mockAuth0Client.connections.clients.get, connectionId, {
939-
checkpoint: true,
940944
take: 100,
941945
});
942946
});
943947

944948
it('should return empty array when no enabled clients', async () => {
945949
const connectionId = 'con_123';
946-
mockAuth0Client.connections.clients.get.resolves([]);
950+
mockAuth0Client.connections.clients.get.resolves(mockPagedData({}, 'clients', []));
947951

948952
const result = await getConnectionEnabledClients(mockAuth0Client, connectionId);
949953

950954
expect(result).to.deep.equal([]);
951955
});
952956

953-
it('should handle multi-page pagination correctly', async () => {
957+
it('should follow hasNextPage/getNextPage to collect all pages', async () => {
954958
const connectionId = 'con_123';
955959

956-
// Pagination is handled by the paginate helper; mock returns all clients as a flat array
957-
mockAuth0Client.connections.clients.get.resolves([
958-
{ client_id: 'client_1' },
959-
{ client_id: 'client_2' },
960-
{ client_id: 'client_3' },
961-
{ client_id: 'client_4' },
962-
{ client_id: 'client_5' },
963-
{ client_id: 'client_6' },
964-
{ client_id: 'client_7' },
965-
{ client_id: 'client_8' },
966-
]);
960+
// Simulate two pages via the PagedResponse format
961+
mockAuth0Client.connections.clients.get.resolves(
962+
mockPagedData({}, 'clients', [{ client_id: 'client_1' }, { client_id: 'client_2' }], [
963+
[{ client_id: 'client_3' }, { client_id: 'client_4' }],
964+
])
965+
);
966+
967+
const result = await getConnectionEnabledClients(mockAuth0Client, connectionId);
968+
969+
expect(result).to.deep.equal(['client_1', 'client_2', 'client_3', 'client_4']);
970+
});
971+
972+
it('should return null without throwing when .get() rejects', async () => {
973+
const connectionId = 'con_123';
974+
mockAuth0Client.connections.clients.get.rejects(new Error('network error'));
967975

968976
const result = await getConnectionEnabledClients(mockAuth0Client, connectionId);
969977

970-
// Should include ALL clients from ALL 3 pages
971-
expect(result).to.deep.equal([
972-
'client_1',
973-
'client_2',
974-
'client_3',
975-
'client_4',
976-
'client_5',
977-
'client_6',
978-
'client_7',
979-
'client_8',
978+
expect(result).to.be.null;
979+
});
980+
981+
it('should return null without throwing when connectionId is empty', async () => {
982+
const result = await getConnectionEnabledClients(mockAuth0Client, '');
983+
expect(result).to.be.null;
984+
sinon.assert.notCalled(mockAuth0Client.connections.clients.get);
985+
});
986+
987+
// Regression test for the bug introduced in commit 7e07417:
988+
// paginate() was called with .get() instead of .list(), bypassing the pagedClient proxy.
989+
// The SDK v5 .get() returns a PagedResponse object; calling .filter() on it threw a
990+
// TypeError that was silently swallowed, causing enabled_clients to be absent from exports.
991+
it('should not silently drop clients when SDK returns a PagedResponse (regression: 7e07417)', async () => {
992+
const connectionId = 'con_regression';
993+
const pagedResponse = mockPagedData({}, 'clients', [
994+
{ client_id: 'client_a' },
995+
{ client_id: 'client_b' },
980996
]);
997+
998+
// Confirm the mock is a PagedResponse object (has .data), not a flat array
999+
expect(Array.isArray(pagedResponse)).to.equal(false);
1000+
expect(pagedResponse.data).to.be.an('array');
1001+
1002+
mockAuth0Client.connections.clients.get.resolves(pagedResponse);
1003+
1004+
const result = await getConnectionEnabledClients(mockAuth0Client, connectionId);
1005+
1006+
// Before the fix, result was null because .filter() threw on the PagedResponse object
1007+
expect(result).to.deep.equal(['client_a', 'client_b']);
9811008
});
9821009
});
9831010

@@ -1390,12 +1417,12 @@ describe('#connections enabled clients functionality', () => {
13901417
pool,
13911418
};
13921419

1393-
// Mock enabled clients responses
1420+
// Mock enabled clients responses — SDK v5 .get() returns a PagedResponse, not a flat array
13941421
getEnabledClientsStub
1395-
.withArgs('con_1')
1396-
.resolves([{ client_id: 'client_1' }, { client_id: 'client_2' }])
1397-
.withArgs('con_2')
1398-
.resolves([{ client_id: 'client_3' }]);
1422+
.withArgs('con_1', { take: 100 })
1423+
.resolves(mockPagedData({}, 'clients', [{ client_id: 'client_1' }, { client_id: 'client_2' }]))
1424+
.withArgs('con_2', { take: 100 })
1425+
.resolves(mockPagedData({}, 'clients', [{ client_id: 'client_3' }]));
13991426

14001427
const handler = new connections.default({ client: pageClient(auth0), config });
14011428
handler.scimHandler = scimHandlerMock;

test/tools/auth0/handlers/databases.tests.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -556,7 +556,7 @@ describe('#databases handler', () => {
556556
},
557557
clients: {
558558
get: () => {
559-
return Promise.resolve([{ client_id: clientId }]);
559+
return Promise.resolve(mockPagedData({}, 'clients', [{ client_id: clientId }]));
560560
},
561561
},
562562
},
@@ -2239,12 +2239,12 @@ describe('#databases handler with enabled clients integration', () => {
22392239
pool,
22402240
};
22412241

2242-
// Mock enabled clients responses
2242+
// Mock enabled clients responses — SDK v5 .get() returns a PagedResponse, not a flat array
22432243
getEnabledClientsStub
2244-
.withArgs('con_1')
2245-
.resolves([{ client_id: 'client_1' }, { client_id: 'client_2' }])
2246-
.withArgs('con_2')
2247-
.resolves([{ client_id: 'client_3' }]);
2244+
.withArgs('con_1', { take: 100 })
2245+
.resolves(mockPagedData({}, 'clients', [{ client_id: 'client_1' }, { client_id: 'client_2' }]))
2246+
.withArgs('con_2', { take: 100 })
2247+
.resolves(mockPagedData({}, 'clients', [{ client_id: 'client_3' }]));
22482248

22492249
const handler = new databases.default({ client: pageClient(auth0), config });
22502250

0 commit comments

Comments
 (0)