From 89af8027e8c8c40d2a864e44b57d24e85e1f717d Mon Sep 17 00:00:00 2001 From: Anand Chowdhary Date: Sun, 8 Nov 2020 12:38:55 +0530 Subject: [PATCH] :recycle: Add group/user methods in API kes --- src/modules/api-keys/api-keys.controller.ts | 14 +- src/modules/api-keys/api-keys.service.ts | 188 +++++++++++++++++++- 2 files changed, 188 insertions(+), 14 deletions(-) diff --git a/src/modules/api-keys/api-keys.controller.ts b/src/modules/api-keys/api-keys.controller.ts index 326ce3ede..ec6d5d97b 100644 --- a/src/modules/api-keys/api-keys.controller.ts +++ b/src/modules/api-keys/api-keys.controller.ts @@ -36,7 +36,7 @@ export class ApiKeyController { @Param('groupId', ParseIntPipe) groupId: number, @Body() data: CreateApiKeyDto, ): Promise> { - return this.apiKeysService.createApiKey(groupId, data); + return this.apiKeysService.createApiKeyForGroup(groupId, data); } @Get() @@ -49,7 +49,7 @@ export class ApiKeyController { @Query('where', WherePipe) where?: Record, @Query('orderBy', OrderByPipe) orderBy?: Record, ): Promise[]> { - return this.apiKeysService.getApiKeys(groupId, { + return this.apiKeysService.getApiKeysForGroup(groupId, { skip, take, orderBy, @@ -63,7 +63,7 @@ export class ApiKeyController { async scopes( @Param('groupId', ParseIntPipe) groupId: number, ): Promise> { - return this.apiKeysService.getApiKeyScopes(groupId); + return this.apiKeysService.getApiKeyScopesForGroup(groupId); } @Get(':id') @@ -72,7 +72,7 @@ export class ApiKeyController { @Param('groupId', ParseIntPipe) groupId: number, @Param('id', ParseIntPipe) id: number, ): Promise> { - return this.apiKeysService.getApiKey(groupId, Number(id)); + return this.apiKeysService.getApiKeyForGroup(groupId, Number(id)); } @Patch(':id') @@ -83,7 +83,7 @@ export class ApiKeyController { @Param('groupId', ParseIntPipe) groupId: number, @Param('id', ParseIntPipe) id: number, ): Promise> { - return this.apiKeysService.updateApiKey(groupId, Number(id), data); + return this.apiKeysService.updateApiKeyForGroup(groupId, Number(id), data); } @Put(':id') @@ -94,7 +94,7 @@ export class ApiKeyController { @Param('groupId', ParseIntPipe) groupId: number, @Param('id', ParseIntPipe) id: number, ): Promise> { - return this.apiKeysService.updateApiKey(groupId, Number(id), data); + return this.apiKeysService.updateApiKeyForGroup(groupId, Number(id), data); } @Delete(':id') @@ -104,6 +104,6 @@ export class ApiKeyController { @Param('groupId', ParseIntPipe) groupId: number, @Param('id', ParseIntPipe) id: number, ): Promise> { - return this.apiKeysService.deleteApiKey(groupId, Number(id)); + return this.apiKeysService.deleteApiKeyForGroup(groupId, Number(id)); } } diff --git a/src/modules/api-keys/api-keys.service.ts b/src/modules/api-keys/api-keys.service.ts index bc71dc0df..857bc0943 100644 --- a/src/modules/api-keys/api-keys.service.ts +++ b/src/modules/api-keys/api-keys.service.ts @@ -32,7 +32,7 @@ export class ApiKeysService { private configService: ConfigService, ) {} - async createApiKey( + async createApiKeyForGroup( groupId: number, data: Omit, 'group'>, ): Promise { @@ -41,8 +41,17 @@ export class ApiKeysService { data: { ...data, apiKey, group: { connect: { id: groupId } } }, }); } + async createApiKeyForUser( + userId: number, + data: Omit, 'user'>, + ): Promise { + const apiKey = this.tokensService.generateUuid(); + return this.prisma.apiKeys.create({ + data: { ...data, apiKey, user: { connect: { id: userId } } }, + }); + } - async getApiKeys( + async getApiKeysForGroup( groupId: number, params: { skip?: number; @@ -62,8 +71,31 @@ export class ApiKeysService { }); return apiKeys.map((group) => this.prisma.expose(group)); } + async getApiKeysForUser( + userId: number, + params: { + skip?: number; + take?: number; + cursor?: apiKeysWhereUniqueInput; + where?: apiKeysWhereInput; + orderBy?: apiKeysOrderByInput; + }, + ): Promise[]> { + const { skip, take, cursor, where, orderBy } = params; + const apiKeys = await this.prisma.apiKeys.findMany({ + skip, + take, + cursor, + where: { ...where, user: { id: userId } }, + orderBy, + }); + return apiKeys.map((user) => this.prisma.expose(user)); + } - async getApiKey(groupId: number, id: number): Promise> { + async getApiKeyForGroup( + groupId: number, + id: number, + ): Promise> { const apiKey = await this.prisma.apiKeys.findOne({ where: { id }, }); @@ -72,6 +104,15 @@ export class ApiKeysService { if (apiKey.groupId !== groupId) throw new UnauthorizedException(); return this.prisma.expose(apiKey); } + async getApiKeyForUser(userId: number, id: number): Promise> { + const apiKey = await this.prisma.apiKeys.findOne({ + where: { id }, + }); + if (!apiKey) + throw new HttpException('ApiKey not found', HttpStatus.NOT_FOUND); + if (apiKey.userId !== userId) throw new UnauthorizedException(); + return this.prisma.expose(apiKey); + } async getApiKeyFromKey(key: string): Promise> { const apiKey = await this.prisma.apiKeys.findFirst({ @@ -84,7 +125,7 @@ export class ApiKeysService { return this.prisma.expose(apiKey); } - async updateApiKey( + async updateApiKeyForGroup( groupId: number, id: number, data: apiKeysUpdateInput, @@ -102,8 +143,26 @@ export class ApiKeysService { this.lru.delete(testApiKey.apiKey); return this.prisma.expose(apiKey); } + async updateApiKeyForUser( + userId: number, + id: number, + data: apiKeysUpdateInput, + ): Promise> { + const testApiKey = await this.prisma.apiKeys.findOne({ + where: { id }, + }); + if (!testApiKey) + throw new HttpException('ApiKey not found', HttpStatus.NOT_FOUND); + if (testApiKey.userId !== userId) throw new UnauthorizedException(); + const apiKey = await this.prisma.apiKeys.update({ + where: { id }, + data, + }); + this.lru.delete(testApiKey.apiKey); + return this.prisma.expose(apiKey); + } - async replaceApiKey( + async replaceApiKeyForGroup( groupId: number, id: number, data: apiKeysCreateInput, @@ -121,8 +180,29 @@ export class ApiKeysService { this.lru.delete(testApiKey.apiKey); return this.prisma.expose(apiKey); } + async replaceApiKeyForUser( + userId: number, + id: number, + data: apiKeysCreateInput, + ): Promise> { + const testApiKey = await this.prisma.apiKeys.findOne({ + where: { id }, + }); + if (!testApiKey) + throw new HttpException('ApiKey not found', HttpStatus.NOT_FOUND); + if (testApiKey.userId !== userId) throw new UnauthorizedException(); + const apiKey = await this.prisma.apiKeys.update({ + where: { id }, + data, + }); + this.lru.delete(testApiKey.apiKey); + return this.prisma.expose(apiKey); + } - async deleteApiKey(groupId: number, id: number): Promise> { + async deleteApiKeyForGroup( + groupId: number, + id: number, + ): Promise> { const testApiKey = await this.prisma.apiKeys.findOne({ where: { id }, }); @@ -135,8 +215,26 @@ export class ApiKeysService { this.lru.delete(testApiKey.apiKey); return this.prisma.expose(apiKey); } + async deleteApiKeyForUser( + userId: number, + id: number, + ): Promise> { + const testApiKey = await this.prisma.apiKeys.findOne({ + where: { id }, + }); + if (!testApiKey) + throw new HttpException('ApiKey not found', HttpStatus.NOT_FOUND); + if (testApiKey.userId !== userId) throw new UnauthorizedException(); + const apiKey = await this.prisma.apiKeys.delete({ + where: { id }, + }); + this.lru.delete(testApiKey.apiKey); + return this.prisma.expose(apiKey); + } - async getApiKeyScopes(groupId: number): Promise> { + async getApiKeyScopesForGroup( + groupId: number, + ): Promise> { const scopes: Record = {}; scopes[`group-${groupId}:read-info`] = 'Read group details'; scopes[`group-${groupId}:write-info`] = 'Update group details'; @@ -224,4 +322,80 @@ export class ApiKeysService { scopes[`group-${groupId}:read-audit-log-*`] = 'Read audit logs'; return scopes; } + + async getApiKeyScopesForUser( + userId: number, + ): Promise> { + const scopes: Record = {}; + scopes[`user-${userId}:read-info`] = 'Read user details'; + scopes[`user-${userId}:write-info`] = 'Update user details'; + scopes[`user-${userId}:delete`] = 'Delete user'; + + scopes[`user-${userId}:write-membership-*`] = 'Create new groups'; + scopes[`user-${userId}:read-membership-*`] = 'Read group memberships'; + for await (const membership of await this.prisma.memberships.findMany({ + where: { user: { id: userId } }, + select: { id: true, group: true }, + })) { + scopes[ + `user-${userId}:read-membership-${membership.id}` + ] = `Read membership: ${membership.group.name}`; + scopes[ + `user-${userId}:write-membership-${membership.id}` + ] = `Update membership: ${membership.group.name}`; + scopes[ + `user-${userId}:delete-membership-${membership.id}` + ] = `Delete membership: ${membership.group.name}`; + } + + scopes[`user-${userId}:write-email-*`] = 'Create and update emails'; + scopes[`user-${userId}:read-email-*`] = 'Read emails'; + for await (const email of await this.prisma.emails.findMany({ + where: { user: { id: userId } }, + select: { id: true, email: true }, + })) { + scopes[ + `user-${userId}:read-email-${email.id}` + ] = `Read email: ${email.email}`; + scopes[ + `user-${userId}:delete-email-${email.id}` + ] = `Delete email: ${email.email}`; + } + + scopes[`user-${userId}:read-session-*`] = 'Read sessions'; + for await (const session of await this.prisma.sessions.findMany({ + where: { user: { id: userId } }, + select: { id: true, browser: true }, + })) { + scopes[`user-${userId}:read-session-${session.id}`] = `Read session: ${ + session.browser ?? session.id + }`; + scopes[ + `user-${userId}:delete-session-${session.id}` + ] = `Delete session: ${session.browser ?? session.id}`; + } + + scopes[`user-${userId}:read-approved-subnet-*`] = 'Read approvedSubnets'; + for await (const subnet of await this.prisma.approvedSubnets.findMany({ + where: { user: { id: userId } }, + select: { id: true, subnet: true }, + })) { + scopes[ + `user-${userId}:read-approved-subnet-${subnet.id}` + ] = `Read subnet: ${subnet.subnet}`; + scopes[ + `user-${userId}:delete-approved-subnet-${subnet.id}` + ] = `Delete subnet: ${subnet.subnet}`; + } + + scopes[`user-${userId}:delete-mfa-*`] = + 'Disable multi-factor authentication'; + scopes[`user-${userId}:write-mfa-regenerate`] = + 'Regenerate MFA backup codes'; + scopes[`user-${userId}:write-mfa-totp`] = 'Enable TOTP-based MFA'; + scopes[`user-${userId}:write-mfa-sms`] = 'Enable SMS-based MFA'; + scopes[`user-${userId}:write-mfa-email`] = 'Enable email-based MFA'; + + return scopes; + } }