Skip to content

Commit 17ad20c

Browse files
committed
modify endpoint names; add listing group membership route
1 parent 63cfffd commit 17ad20c

File tree

2 files changed

+101
-2
lines changed

2 files changed

+101
-2
lines changed

src/api/functions/entraId.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,3 +233,60 @@ export async function modifyGroup(
233233
});
234234
}
235235
}
236+
237+
/**
238+
* Lists all members of an Entra ID group.
239+
* @param token - Entra ID token authorized to take this action.
240+
* @param group - The group ID to fetch members for.
241+
* @throws {EntraGroupError} If the group action fails.
242+
* @returns {Promise<Array<{ name: string; email: string }>>} List of members with name and email.
243+
*/
244+
export async function listGroupMembers(
245+
token: string,
246+
group: string,
247+
): Promise<Array<{ name: string; email: string }>> {
248+
try {
249+
const url = `https://graph.microsoft.com/v1.0/groups/${group}/members`;
250+
const response = await fetch(url, {
251+
method: "GET",
252+
headers: {
253+
Authorization: `Bearer ${token}`,
254+
"Content-Type": "application/json",
255+
},
256+
});
257+
258+
if (!response.ok) {
259+
const errorData = (await response.json()) as {
260+
error?: { message?: string };
261+
};
262+
throw new EntraGroupError({
263+
message: errorData?.error?.message ?? response.statusText,
264+
group,
265+
});
266+
}
267+
268+
const data = (await response.json()) as {
269+
value: Array<{
270+
displayName?: string;
271+
mail?: string;
272+
}>;
273+
};
274+
275+
// Map the response to the desired format
276+
const members = data.value.map((member) => ({
277+
name: member.displayName ?? "",
278+
email: member.mail ?? "",
279+
}));
280+
281+
return members;
282+
} catch (error) {
283+
if (error instanceof EntraGroupError) {
284+
throw error;
285+
}
286+
287+
throw new EntraGroupError({
288+
message: error instanceof Error ? error.message : String(error),
289+
group,
290+
});
291+
}
292+
}

src/api/routes/iam.ts

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { zodToJsonSchema } from "zod-to-json-schema";
44
import {
55
addToTenant,
66
getEntraIdToken,
7+
listGroupMembers,
78
modifyGroup,
89
} from "../functions/entraId.js";
910
import {
@@ -188,7 +189,7 @@ const iamRoutes: FastifyPluginAsync = async (fastify, _options) => {
188189
Body: GroupModificationPatchRequest;
189190
Querystring: { groupId: string };
190191
}>(
191-
"/groupMembership/:groupId",
192+
"/groups/:groupId",
192193
{
193194
schema: {
194195
querystring: {
@@ -222,7 +223,7 @@ const iamRoutes: FastifyPluginAsync = async (fastify, _options) => {
222223
throw new EntraGroupError({
223224
code: 403,
224225
message:
225-
"This group is protected and may not be modified by this service. You must log into Entra ID directly to modify this group.",
226+
"This group is protected and cannot be modified by this service. You must log into Entra ID directly to modify this group.",
226227
group: groupId,
227228
});
228229
}
@@ -282,6 +283,47 @@ const iamRoutes: FastifyPluginAsync = async (fastify, _options) => {
282283
reply.status(202).send(response);
283284
},
284285
);
286+
fastify.get<{
287+
Querystring: { groupId: string };
288+
}>(
289+
"/groups/:groupId",
290+
{
291+
schema: {
292+
querystring: {
293+
type: "object",
294+
properties: {
295+
groupId: {
296+
type: "string",
297+
},
298+
},
299+
},
300+
},
301+
onRequest: async (request, reply) => {
302+
await fastify.authorize(request, reply, [AppRoles.IAM_ADMIN]);
303+
},
304+
},
305+
async (request, reply) => {
306+
const groupId = (request.params as Record<string, string>).groupId;
307+
if (!groupId || groupId === "") {
308+
throw new NotFoundError({
309+
endpointName: request.url,
310+
});
311+
}
312+
if (genericConfig.ProtectedEntraIDGroups.includes(groupId)) {
313+
throw new EntraGroupError({
314+
code: 403,
315+
message:
316+
"This group is protected and cannot be read by this service. You must log into Entra ID directly to read this group.",
317+
group: groupId,
318+
});
319+
}
320+
const entraIdToken = await getEntraIdToken(
321+
fastify.environmentConfig.AadValidClientId,
322+
);
323+
const response = await listGroupMembers(entraIdToken, groupId);
324+
reply.status(200).send(response);
325+
},
326+
);
285327
};
286328

287329
export default iamRoutes;

0 commit comments

Comments
 (0)