Skip to content
This repository has been archived by the owner on Apr 19, 2023. It is now read-only.

Commit

Permalink
✨ Add endpoints for access tokens
Browse files Browse the repository at this point in the history
  • Loading branch information
AnandChowdhary committed Oct 23, 2020
1 parent 0e7c8b2 commit 8233d52
Show file tree
Hide file tree
Showing 6 changed files with 306 additions and 9 deletions.
2 changes: 2 additions & 0 deletions src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { RateLimiterInterceptor, RateLimiterModule } from 'nestjs-rate-limiter';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import configuration from './config/configuration';
import { AccessTokensModule } from './modules/access-tokens/access-tokens.module';
import { AuthModule } from './modules/auth/auth.module';
import { EmailModule } from './modules/email/email.module';
import { PrismaModule } from './modules/prisma/prisma.module';
Expand All @@ -25,6 +26,7 @@ import { UsersModule } from './modules/user/user.module';
}),
EmailModule,
SessionsModule,
AccessTokensModule,
],
controllers: [AppController],
providers: [
Expand Down
106 changes: 106 additions & 0 deletions src/modules/access-tokens/access-tokens.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import {
Body,
Controller,
Delete,
Get,
Param,
ParseIntPipe,
Patch,
Post,
Put,
Query,
UseGuards,
} from '@nestjs/common';
import { accessTokens } 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 {
CreateAccessTokenDto,
UpdateAccessTokenDto,
ReplaceAccessTokenDto,
} from './access-tokens.dto';
import { AccessTokensService } from './access-tokens.service';

@Controller('users/:userId/access-tokens')
@UseGuards(JwtAuthGuard)
export class AccessTokenController {
constructor(private accessTokensService: AccessTokensService) {}

@Post()
@UseGuards(ScopesGuard)
@Scopes('user{userId}:write', 'access-token:write')
async create(
@Param('userId', ParseIntPipe) userId: number,
@Body() data: CreateAccessTokenDto,
): Promise<Expose<accessTokens>> {
return this.accessTokensService.createAccessToken(userId, data);
}

@Get()
@UseGuards(ScopesGuard)
@Scopes('user{userId}:read', 'access-token:read')
async getAll(
@Param('userId', ParseIntPipe) userId: number,
@Query('skip', OptionalIntPipe) skip?: number,
@Query('take', OptionalIntPipe) take?: number,
@Query('cursor', CursorPipe) cursor?: Record<string, number | string>,
@Query('where', WherePipe) where?: Record<string, number | string>,
@Query('orderBy', OrderByPipe) orderBy?: Record<string, 'asc' | 'desc'>,
): Promise<Expose<accessTokens>[]> {
return this.accessTokensService.getAccessTokens(userId, {
skip,
take,
orderBy,
cursor,
where,
});
}

@Get(':id')
@UseGuards(ScopesGuard)
@Scopes('user{userId}:read', 'access-token{id}:read')
async get(
@Param('userId', ParseIntPipe) userId: number,
@Param('id', ParseIntPipe) id: number,
): Promise<Expose<accessTokens>> {
return this.accessTokensService.getAccessToken(userId, Number(id));
}

@Patch(':id')
@UseGuards(ScopesGuard)
@Scopes('user{userId}:write', 'access-token{id}:write')
async update(
@Body() data: UpdateAccessTokenDto,
@Param('userId', ParseIntPipe) userId: number,
@Param('id', ParseIntPipe) id: number,
): Promise<Expose<accessTokens>> {
return this.accessTokensService.updateAccessToken(userId, Number(id), data);
}

@Put(':id')
@UseGuards(ScopesGuard)
@Scopes('user{userId}:write', 'access-token{id}:write')
async replace(
@Body() data: ReplaceAccessTokenDto,
@Param('userId', ParseIntPipe) userId: number,
@Param('id', ParseIntPipe) id: number,
): Promise<Expose<accessTokens>> {
return this.accessTokensService.updateAccessToken(userId, Number(id), data);
}

@Delete(':id')
@UseGuards(ScopesGuard)
@Scopes('user{userId}:delete', 'access-token{id}:delete')
async remove(
@Param('userId', ParseIntPipe) userId: number,
@Param('id', ParseIntPipe) id: number,
): Promise<Expose<accessTokens>> {
return this.accessTokensService.deleteAccessToken(userId, Number(id));
}
}
76 changes: 76 additions & 0 deletions src/modules/access-tokens/access-tokens.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { IsArray, IsNotEmpty, IsOptional, IsString } from 'class-validator';

export class CreateAccessTokenDto {
@IsString()
@IsOptional()
description?: string;

@IsString()
@IsOptional()
name?: string;

@IsArray()
@IsString({ each: true })
@IsOptional()
scopes?: string[];

@IsArray()
@IsString({ each: true })
@IsOptional()
ipRestrictions?: string[];

@IsArray()
@IsString({ each: true })
@IsOptional()
referrerRestrictions?: string[];
}

export class UpdateAccessTokenDto {
@IsString()
@IsOptional()
description?: string;

@IsString()
@IsOptional()
name?: string;

@IsArray()
@IsString({ each: true })
@IsOptional()
scopes?: string[];

@IsArray()
@IsString({ each: true })
@IsOptional()
ipRestrictions?: string[];

@IsArray()
@IsString({ each: true })
@IsOptional()
referrerRestrictions?: string[];
}

export class ReplaceAccessTokenDto {
@IsString()
@IsNotEmpty()
description: string;

@IsString()
@IsNotEmpty()
name: string;

@IsArray()
@IsString({ each: true })
@IsNotEmpty()
scopes: string[];

@IsArray()
@IsString({ each: true })
@IsNotEmpty()
ipRestrictions: string[];

@IsArray()
@IsString({ each: true })
@IsNotEmpty()
referrerRestrictions: string[];
}
11 changes: 11 additions & 0 deletions src/modules/access-tokens/access-tokens.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Module } from '@nestjs/common';
import { PrismaModule } from '../prisma/prisma.module';
import { AccessTokenController } from './access-tokens.controller';
import { AccessTokensService } from './access-tokens.service';

@Module({
imports: [PrismaModule],
controllers: [AccessTokenController],
providers: [AccessTokensService],
})
export class AccessTokensModule {}
111 changes: 111 additions & 0 deletions src/modules/access-tokens/access-tokens.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import {
HttpException,
HttpStatus,
Injectable,
UnauthorizedException,
} from '@nestjs/common';
import { randomStringGenerator } from '@nestjs/common/utils/random-string-generator.util';
import {
accessTokens,
accessTokensCreateInput,
accessTokensOrderByInput,
accessTokensUpdateInput,
accessTokensWhereInput,
accessTokensWhereUniqueInput,
} from '@prisma/client';
import { Expose } from 'src/modules/prisma/prisma.interface';
import { PrismaService } from '../prisma/prisma.service';

@Injectable()
export class AccessTokensService {
constructor(private prisma: PrismaService) {}
async createAccessToken(
userId: number,
data: Omit<Omit<accessTokensCreateInput, 'accessToken'>, 'user'>,
): Promise<accessTokens> {
const accessToken = randomStringGenerator();
return this.prisma.accessTokens.create({
data: { ...data, accessToken, user: { connect: { id: userId } } },
});
}

async getAccessTokens(
userId: number,
params: {
skip?: number;
take?: number;
cursor?: accessTokensWhereUniqueInput;
where?: accessTokensWhereInput;
orderBy?: accessTokensOrderByInput;
},
): Promise<Expose<accessTokens>[]> {
const { skip, take, cursor, where, orderBy } = params;
const accessTokens = await this.prisma.accessTokens.findMany({
skip,
take,
cursor,
where: { ...where, user: { id: userId } },
orderBy,
});
return accessTokens.map(user => this.prisma.expose<accessTokens>(user));
}

async getAccessToken(
userId: number,
id: number,
): Promise<Expose<accessTokens> | null> {
const accessToken = await this.prisma.accessTokens.findOne({
where: { id },
});
if (accessToken.userId !== userId) throw new UnauthorizedException();
if (!accessToken)
throw new HttpException('AccessToken not found', HttpStatus.NOT_FOUND);
return this.prisma.expose<accessTokens>(accessToken);
}

async updateAccessToken(
userId: number,
id: number,
data: accessTokensUpdateInput,
): Promise<Expose<accessTokens>> {
const testAccessToken = await this.prisma.accessTokens.findOne({
where: { id },
});
if (testAccessToken.userId !== userId) throw new UnauthorizedException();
const accessToken = await this.prisma.accessTokens.update({
where: { id },
data,
});
return this.prisma.expose<accessTokens>(accessToken);
}

async replaceAccessToken(
userId: number,
id: number,
data: accessTokensCreateInput,
): Promise<Expose<accessTokens>> {
const testAccessToken = await this.prisma.accessTokens.findOne({
where: { id },
});
if (testAccessToken.userId !== userId) throw new UnauthorizedException();
const accessToken = await this.prisma.accessTokens.update({
where: { id },
data,
});
return this.prisma.expose<accessTokens>(accessToken);
}

async deleteAccessToken(
userId: number,
id: number,
): Promise<Expose<accessTokens>> {
const testAccessToken = await this.prisma.accessTokens.findOne({
where: { id },
});
if (testAccessToken.userId !== userId) throw new UnauthorizedException();
const accessToken = await this.prisma.accessTokens.delete({
where: { id },
});
return this.prisma.expose<accessTokens>(accessToken);
}
}
9 changes: 0 additions & 9 deletions src/modules/sessions/sessions.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,6 @@ import { PrismaService } from '../prisma/prisma.service';
@Injectable()
export class SessionsService {
constructor(private prisma: PrismaService) {}
async createSession(
userId: number,
data: sessionsCreateInput,
): Promise<sessions> {
return this.prisma.sessions.create({
data: { ...data, user: { connect: { id: userId } } },
});
}

async getSessions(
userId: number,
params: {
Expand Down

0 comments on commit 8233d52

Please sign in to comment.