diff --git a/src/auth/auth.controller.ts b/src/auth/auth.controller.ts index 0f8adf1..960eae2 100644 --- a/src/auth/auth.controller.ts +++ b/src/auth/auth.controller.ts @@ -1,4 +1,4 @@ -import { Body, Controller, Get, Headers, Post, UnauthorizedException } from '@nestjs/common'; +import { Body, Controller, Get, Headers, Param, Post, UnauthorizedException } from '@nestjs/common'; import { AuthService } from './auth.service'; import { LoginDto } from './dto.login.dto'; import { RefreshDto } from './dto.refresh.dto'; @@ -30,6 +30,20 @@ export class AuthController { return this.authService.listMyGuilds(token); } + @Post('me/guilds/join') + joinGuild(@Headers('authorization') authorization: string | undefined, @Body() body: { guildNodeId?: string }) { + const token = authorization?.startsWith('Bearer ') ? authorization.slice(7) : ''; + if (!token) throw new UnauthorizedException('missing bearer token'); + return this.authService.joinGuild(token, String(body?.guildNodeId ?? '')); + } + + @Get('guilds/:guildNodeId/members') + guildMembers(@Headers('authorization') authorization: string | undefined, @Param('guildNodeId') guildNodeId: string) { + const token = authorization?.startsWith('Bearer ') ? authorization.slice(7) : ''; + if (!token) throw new UnauthorizedException('missing bearer token'); + return this.authService.listGuildMembers(token, guildNodeId); + } + @Post('introspect') introspect(@Body() body: { token?: string; guildNodeId?: string }) { return this.authService.introspectGuildToken(body?.token ?? '', body?.guildNodeId ?? ''); diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts index 1c59861..7b989b7 100644 --- a/src/auth/auth.service.ts +++ b/src/auth/auth.service.ts @@ -50,26 +50,10 @@ export class AuthService { ) {} private async getUserGuildsAndTokens(userId: string, email: string) { - let memberships = await this.guildUserRepo.find({ + const memberships = await this.guildUserRepo.find({ where: { userId, status: 'active' }, }); - if (!memberships.length) { - const activeNodes = await this.guildNodeRepo.find({ where: { status: 'active' } }); - if (activeNodes.length) { - await this.guildUserRepo.save( - activeNodes.map((n) => - this.guildUserRepo.create({ - userId, - guildNodeId: n.nodeId, - status: 'active', - }), - ), - ); - memberships = await this.guildUserRepo.find({ where: { userId, status: 'active' } }); - } - } - const nodeIds = memberships.map((x) => x.guildNodeId); if (!nodeIds.length) { return { guilds: [], guildAccessTokens: [] as Array<{ guildNodeId: string; token: string; tokenType: string }> }; @@ -170,6 +154,50 @@ export class AuthService { return this.getUserGuildsAndTokens(userId, email); } + async joinGuild(accessToken: string, guildNodeId: string) { + const payload = this.verifyCenterAccessToken(accessToken); + const userId = String(payload.sub ?? ''); + if (!userId) throw new UnauthorizedException('invalid access token'); + + const node = await this.guildNodeRepo.findOne({ where: { nodeId: guildNodeId, status: 'active' } }); + if (!node) throw new UnauthorizedException('guild node not found or inactive'); + + const existed = await this.guildUserRepo.findOne({ where: { userId, guildNodeId } }); + if (!existed) { + await this.guildUserRepo.save( + this.guildUserRepo.create({ userId, guildNodeId, status: 'active' }), + ); + } else if (existed.status !== 'active') { + existed.status = 'active'; + await this.guildUserRepo.save(existed); + } + + return { status: 'ok' as const }; + } + + async listGuildMembers(accessToken: string, guildNodeId: string) { + const payload = this.verifyCenterAccessToken(accessToken); + const userId = String(payload.sub ?? ''); + if (!userId) throw new UnauthorizedException('invalid access token'); + + const selfMembership = await this.guildUserRepo.findOne({ where: { userId, guildNodeId, status: 'active' } }); + if (!selfMembership) throw new UnauthorizedException('not a guild member'); + + const members = await this.guildUserRepo.find({ where: { guildNodeId, status: 'active' } }); + const userIds = [...new Set(members.map((m) => m.userId))]; + if (!userIds.length) return []; + + const users = await this.userRepo + .createQueryBuilder('u') + .where('u.id IN (:...userIds)', { userIds }) + .getMany(); + + const userMap = new Map(users.map((u) => [u.id, u])); + return members + .map((m) => ({ userId: m.userId, email: userMap.get(m.userId)?.email ?? '', status: m.status })) + .filter((x) => !!x.email); + } + verifyCenterAccessToken(accessToken: string): jwt.JwtPayload { try { const payload = jwt.verify(accessToken, process.env.FABRIC_BACKEND_CENTER_JWT_ACCESS_SECRET as string) as jwt.JwtPayload;