From 0e7c8b2026d24d41a354355ad2434b59ec19060e Mon Sep 17 00:00:00 2001 From: Anand Chowdhary Date: Fri, 23 Oct 2020 15:45:59 +0530 Subject: [PATCH] :sparkles: Add session endpoints --- src/app.module.ts | 2 + src/modules/prisma/prisma.interface.ts | 5 +- src/modules/prisma/prisma.service.ts | 4 +- src/modules/sessions/sessions.controller.ts | 65 ++++++++++++++++++ src/modules/sessions/sessions.module.ts | 11 +++ src/modules/sessions/sessions.service.ts | 76 +++++++++++++++++++++ 6 files changed, 161 insertions(+), 2 deletions(-) create mode 100644 src/modules/sessions/sessions.controller.ts create mode 100644 src/modules/sessions/sessions.module.ts create mode 100644 src/modules/sessions/sessions.service.ts diff --git a/src/app.module.ts b/src/app.module.ts index 1c6ad32ba..36df8b0f7 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -8,6 +8,7 @@ import configuration from './config/configuration'; import { AuthModule } from './modules/auth/auth.module'; import { EmailModule } from './modules/email/email.module'; import { PrismaModule } from './modules/prisma/prisma.module'; +import { SessionsModule } from './modules/sessions/sessions.module'; import { UsersModule } from './modules/user/user.module'; @Module({ @@ -23,6 +24,7 @@ import { UsersModule } from './modules/user/user.module'; duration: 60, }), EmailModule, + SessionsModule, ], controllers: [AppController], providers: [ diff --git a/src/modules/prisma/prisma.interface.ts b/src/modules/prisma/prisma.interface.ts index fe35efeee..f41d8baa4 100644 --- a/src/modules/prisma/prisma.interface.ts +++ b/src/modules/prisma/prisma.interface.ts @@ -1 +1,4 @@ -export type Expose = Omit, 'twoFactorSecret'>; +export type Expose = Omit< + Omit, 'twoFactorSecret'>, + 'token' +>; diff --git a/src/modules/prisma/prisma.service.ts b/src/modules/prisma/prisma.service.ts index fc115d416..db7298f7f 100644 --- a/src/modules/prisma/prisma.service.ts +++ b/src/modules/prisma/prisma.service.ts @@ -1,5 +1,5 @@ import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common'; -import { PrismaClient, users } from '@prisma/client'; +import { PrismaClient, sessions, users } from '@prisma/client'; import { Expose } from 'src/modules/prisma/prisma.interface'; @Injectable() @@ -13,10 +13,12 @@ export class PrismaService extends PrismaClient await this.$disconnect(); } + /** Delete sensitive keys from an object */ expose(item: T): Expose { if (!item) return null; delete ((item as any) as users).password; delete ((item as any) as users).twoFactorSecret; + delete ((item as any) as sessions).token; return item; } } diff --git a/src/modules/sessions/sessions.controller.ts b/src/modules/sessions/sessions.controller.ts new file mode 100644 index 000000000..d458b386b --- /dev/null +++ b/src/modules/sessions/sessions.controller.ts @@ -0,0 +1,65 @@ +import { + Controller, + Delete, + Get, + Param, + ParseIntPipe, + Query, + UseGuards, +} from '@nestjs/common'; +import { sessions } from '@prisma/client'; +import { Expose } from 'src/modules/prisma/prisma.interface'; +import { CursorPipe } from 'src/pipes/cursor.pipe'; +import { OptionalIntPipe } from 'src/pipes/optional-int.pipe'; +import { OrderByPipe } from 'src/pipes/order-by.pipe'; +import { WherePipe } from 'src/pipes/where.pipe'; +import { JwtAuthGuard } from '../auth/jwt-auth.guard'; +import { Scopes } from '../auth/scope.decorator'; +import { ScopesGuard } from '../auth/scope.guard'; +import { SessionsService } from './sessions.service'; + +@Controller('users/:userId/sessions') +@UseGuards(JwtAuthGuard) +export class SessionController { + constructor(private sessionsService: SessionsService) {} + + @Get() + @UseGuards(ScopesGuard) + @Scopes('user{userId}:read', 'session:read') + async getAll( + @Param('userId', ParseIntPipe) userId: number, + @Query('skip', OptionalIntPipe) skip?: number, + @Query('take', OptionalIntPipe) take?: number, + @Query('cursor', CursorPipe) cursor?: Record, + @Query('where', WherePipe) where?: Record, + @Query('orderBy', OrderByPipe) orderBy?: Record, + ): Promise[]> { + return this.sessionsService.getSessions(userId, { + skip, + take, + orderBy, + cursor, + where, + }); + } + + @Get(':id') + @UseGuards(ScopesGuard) + @Scopes('user{userId}:read', 'session{id}:read') + async get( + @Param('userId', ParseIntPipe) userId: number, + @Param('id', ParseIntPipe) id: number, + ): Promise> { + return this.sessionsService.getSession(userId, { id: Number(id) }); + } + + @Delete(':id') + @UseGuards(ScopesGuard) + @Scopes('user{userId}:delete', 'session{id}:delete') + async remove( + @Param('userId', ParseIntPipe) userId: number, + @Param('id', ParseIntPipe) id: number, + ): Promise> { + return this.sessionsService.deleteSession(userId, { id: Number(id) }); + } +} diff --git a/src/modules/sessions/sessions.module.ts b/src/modules/sessions/sessions.module.ts new file mode 100644 index 000000000..fab207f92 --- /dev/null +++ b/src/modules/sessions/sessions.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { PrismaModule } from '../prisma/prisma.module'; +import { SessionController } from './sessions.controller'; +import { SessionsService } from './sessions.service'; + +@Module({ + imports: [PrismaModule], + controllers: [SessionController], + providers: [SessionsService], +}) +export class SessionsModule {} diff --git a/src/modules/sessions/sessions.service.ts b/src/modules/sessions/sessions.service.ts new file mode 100644 index 000000000..49bcbc103 --- /dev/null +++ b/src/modules/sessions/sessions.service.ts @@ -0,0 +1,76 @@ +import { + HttpException, + HttpStatus, + Injectable, + UnauthorizedException, +} from '@nestjs/common'; +import { + sessions, + sessionsCreateInput, + sessionsOrderByInput, + sessionsWhereInput, + sessionsWhereUniqueInput, +} from '@prisma/client'; +import { Expose } from 'src/modules/prisma/prisma.interface'; +import { PrismaService } from '../prisma/prisma.service'; + +@Injectable() +export class SessionsService { + constructor(private prisma: PrismaService) {} + async createSession( + userId: number, + data: sessionsCreateInput, + ): Promise { + return this.prisma.sessions.create({ + data: { ...data, user: { connect: { id: userId } } }, + }); + } + + async getSessions( + userId: number, + params: { + skip?: number; + take?: number; + cursor?: sessionsWhereUniqueInput; + where?: sessionsWhereInput; + orderBy?: sessionsOrderByInput; + }, + ): Promise[]> { + const { skip, take, cursor, where, orderBy } = params; + const sessions = await this.prisma.sessions.findMany({ + skip, + take, + cursor, + where: { ...where, user: { id: userId } }, + orderBy, + }); + return sessions.map(user => this.prisma.expose(user)); + } + + async getSession( + userId: number, + sessionWhereUniqueInput: sessionsWhereUniqueInput, + ): Promise | null> { + const session = await this.prisma.sessions.findOne({ + where: sessionWhereUniqueInput, + }); + if (session.userId !== userId) throw new UnauthorizedException(); + if (!session) + throw new HttpException('Session not found', HttpStatus.NOT_FOUND); + return this.prisma.expose(session); + } + + async deleteSession( + userId: number, + where: sessionsWhereUniqueInput, + ): Promise> { + const testSession = await this.prisma.sessions.findOne({ + where, + }); + if (testSession.userId !== userId) throw new UnauthorizedException(); + const session = await this.prisma.sessions.delete({ + where, + }); + return this.prisma.expose(session); + } +}