diff --git a/src/auth/auth.controller.ts b/src/auth/auth.controller.ts index f86eaf2..371367f 100644 --- a/src/auth/auth.controller.ts +++ b/src/auth/auth.controller.ts @@ -67,4 +67,12 @@ export class AuthController { introspect(@Body() body: { token?: string; guildNodeId?: string }) { return this.authService.introspectGuildToken(body?.token ?? '', body?.guildNodeId ?? ''); } + + @Post('resolve-names') + resolveNames(@Body() body: { guildNodeId?: string; names?: string[] }) { + return this.authService.resolveNames( + String(body?.guildNodeId ?? ''), + Array.isArray(body?.names) ? body.names : [], + ); + } } diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts index 0cacc51..0674c49 100644 --- a/src/auth/auth.service.ts +++ b/src/auth/auth.service.ts @@ -240,6 +240,33 @@ export class AuthService { .filter((x) => !!x.email); } + // Resolve display names (or emails) to userIds, scoped to a guild node's + // active members. Called by guild nodes (api-key auth). Unresolved names + // are omitted from the result. + async resolveNames( + guildNodeId: string, + names: string[], + ): Promise<{ resolved: Record }> { + const wanted = [...new Set(names.map((n) => String(n ?? '').trim()).filter(Boolean))]; + if (!guildNodeId || !wanted.length) return { resolved: {} }; + + const members = await this.guildUserRepo.find({ where: { guildNodeId, status: 'active' } }); + const userIds = [...new Set(members.map((m) => m.userId))]; + if (!userIds.length) return { resolved: {} }; + + const users = await this.userRepo + .createQueryBuilder('u') + .where('u.id IN (:...userIds)', { userIds }) + .getMany(); + + const resolved: Record = {}; + for (const name of wanted) { + const hit = users.find((u) => u.name === name) ?? users.find((u) => u.email === name); + if (hit) resolved[name] = hit.id; + } + return { resolved }; + } + verifyCenterAccessToken(accessToken: string): jwt.JwtPayload { try { const payload = jwt.verify(accessToken, process.env.FABRIC_BACKEND_CENTER_JWT_ACCESS_SECRET as string) as jwt.JwtPayload;