From 2a17f3a00db181cbf232ded2f6c17dee1c615d91 Mon Sep 17 00:00:00 2001 From: Daniel Gerdes <daniel.gerdes@ruhr-uni-bochum.de> Date: Wed, 1 Sep 2021 16:17:34 +0200 Subject: [PATCH] Added TokenCodes and Endpoints for redeeming and requesting --- src/app.module.ts | 4 +- src/auth/agent-auth.guard.ts | 27 ++++++ src/auth/graphql-auth.guard.ts | 8 +- src/main.ts | 3 +- src/resolvers/agent.resolver.ts | 4 +- src/resolvers/entry.resolver.ts | 4 +- src/resolvers/group.resolver.ts | 6 +- src/resolvers/models/agent.model.ts | 4 + src/resolvers/models/tokenCode.model.ts | 15 ++++ src/resolvers/resolver.module.ts | 3 +- src/resolvers/tokenCode.resolver.ts | 113 ++++++++++++++++++++++++ 11 files changed, 175 insertions(+), 16 deletions(-) create mode 100644 src/auth/agent-auth.guard.ts create mode 100644 src/resolvers/models/tokenCode.model.ts create mode 100644 src/resolvers/tokenCode.resolver.ts diff --git a/src/app.module.ts b/src/app.module.ts index 5f842a2..7c7d319 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -3,7 +3,7 @@ import { PrismaModule } from './prisma/prisma.module'; import { TelegramModule } from './telegram/telegram.module'; import { GraphQLModule } from '@nestjs/graphql'; import { ResolverModule } from './resolvers/resolver.module'; -import { GraphQLAuthGuard } from './auth/graphql-auth.guard'; +import { GroupAuthGuard } from './auth/graphql-auth.guard'; import { ImageController } from './images.controller'; import { WebAuthGuard } from './auth/web-auth.guard'; @@ -21,7 +21,7 @@ import { WebAuthGuard } from './auth/web-auth.guard'; }), }), ], - providers: [GraphQLAuthGuard, WebAuthGuard], + providers: [GroupAuthGuard, WebAuthGuard], controllers: [ImageController], }) export class AppModule {} diff --git a/src/auth/agent-auth.guard.ts b/src/auth/agent-auth.guard.ts new file mode 100644 index 0000000..ebabd4b --- /dev/null +++ b/src/auth/agent-auth.guard.ts @@ -0,0 +1,27 @@ +import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; +import { GqlExecutionContext } from '@nestjs/graphql'; +import { Request } from 'express'; +import { PrismaService } from '../prisma/prisma.service'; + +@Injectable() +export class AgentAuthGuard implements CanActivate { + constructor(private prismaService: PrismaService) {} + + async canActivate(context: ExecutionContext): Promise<boolean> { + const ctx = GqlExecutionContext.create(context); + + const req: Request = ctx.getContext().req; + + const token = req.headers.authorization; + + if (!token) return false; + + req.agent = await this.prismaService.agent.findFirst({ + where: { id: token }, + }); + + if (!req.agent) return false; + + return true; + } +} diff --git a/src/auth/graphql-auth.guard.ts b/src/auth/graphql-auth.guard.ts index 0eaa070..95ae5a8 100644 --- a/src/auth/graphql-auth.guard.ts +++ b/src/auth/graphql-auth.guard.ts @@ -4,7 +4,7 @@ import { Request } from 'express'; import { PrismaService } from '../prisma/prisma.service'; @Injectable() -export class GraphQLAuthGuard implements CanActivate { +export class GroupAuthGuard implements CanActivate { constructor(private prismaService: PrismaService) {} async canActivate(context: ExecutionContext): Promise<boolean> { @@ -16,13 +16,11 @@ export class GraphQLAuthGuard implements CanActivate { if (!token) return false; - const group = await this.prismaService.group.findFirst({ + req.group = await this.prismaService.group.findFirst({ where: { code: token }, }); - if (!group) return false; - - req.group = group; + if (!req.group) return false; return true; } diff --git a/src/main.ts b/src/main.ts index 4c1caf3..66614b2 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,9 +1,10 @@ import { NestFactory } from '@nestjs/core'; -import { Group } from '@prisma/client'; +import { Group, Agent, TokenCode } from '@prisma/client'; declare module 'express' { interface Request { group: Group; + agent: Agent; } } diff --git a/src/resolvers/agent.resolver.ts b/src/resolvers/agent.resolver.ts index 6b7b65b..843b0fe 100644 --- a/src/resolvers/agent.resolver.ts +++ b/src/resolvers/agent.resolver.ts @@ -10,11 +10,11 @@ import { Agent } from './models/agent.model'; import { PrismaService } from '../prisma/prisma.service'; import { GraphQLString } from 'graphql'; import { NotFoundException, UseGuards } from '@nestjs/common'; -import { GraphQLAuthGuard } from '../auth/graphql-auth.guard'; +import { GroupAuthGuard } from '../auth/graphql-auth.guard'; import { Request } from 'express'; @Resolver(() => Agent) -@UseGuards(GraphQLAuthGuard) +@UseGuards(GroupAuthGuard) export class AgentResolver { constructor(private prismaService: PrismaService) {} diff --git a/src/resolvers/entry.resolver.ts b/src/resolvers/entry.resolver.ts index 58910d2..22af1ec 100644 --- a/src/resolvers/entry.resolver.ts +++ b/src/resolvers/entry.resolver.ts @@ -16,7 +16,7 @@ import { UseGuards, } from '@nestjs/common'; import { Request } from 'express'; -import { GraphQLAuthGuard } from '../auth/graphql-auth.guard'; +import { GroupAuthGuard } from '../auth/graphql-auth.guard'; import { pubSub } from '../pubSub.instance'; @Resolver(() => Entry) @@ -38,7 +38,7 @@ export class EntryResolver { return pubSub.asyncIterator('newEntry'); } - @UseGuards(GraphQLAuthGuard) + @UseGuards(GroupAuthGuard) @Mutation(() => Entry) async unlockEntry( @Args({ name: 'id', type: () => GraphQLString }) id: string, diff --git a/src/resolvers/group.resolver.ts b/src/resolvers/group.resolver.ts index 7f58805..4bc0309 100644 --- a/src/resolvers/group.resolver.ts +++ b/src/resolvers/group.resolver.ts @@ -13,7 +13,7 @@ import { PrismaService } from '../prisma/prisma.service'; import { GraphQLString } from 'graphql'; import * as seedWords from 'mnemonic-words'; import { NotFoundException, UseGuards } from '@nestjs/common'; -import { GraphQLAuthGuard } from '../auth/graphql-auth.guard'; +import { GroupAuthGuard } from '../auth/graphql-auth.guard'; import { Request } from 'express'; import { pubSub } from '../pubSub.instance'; @@ -51,7 +51,7 @@ export class GroupResolver { } @Mutation(() => Group) - @UseGuards(GraphQLAuthGuard) + //@UseGuards(GraphQLAuthGuard) async createGroup( @Args({ name: 'name', type: () => GraphQLString }) name: string, ) { @@ -75,7 +75,7 @@ export class GroupResolver { } @Query(() => Group) - @UseGuards(GraphQLAuthGuard) + @UseGuards(GroupAuthGuard) async getGroup(@Context() { req }: { req: Request }) { return req.group; } diff --git a/src/resolvers/models/agent.model.ts b/src/resolvers/models/agent.model.ts index 7caa592..db0fa06 100644 --- a/src/resolvers/models/agent.model.ts +++ b/src/resolvers/models/agent.model.ts @@ -1,6 +1,7 @@ import { Field, ObjectType } from '@nestjs/graphql'; import { Entry } from './entry.model'; import { GraphQLBoolean } from 'graphql'; +import { TokenCode } from './tokenCode.model'; @ObjectType() export class Agent { @@ -18,4 +19,7 @@ export class Agent { @Field(() => [Entry]) entries: Entry[]; + + @Field(() => TokenCode, { nullable: true }) + tokenCode?: TokenCode; } diff --git a/src/resolvers/models/tokenCode.model.ts b/src/resolvers/models/tokenCode.model.ts new file mode 100644 index 0000000..fb36c17 --- /dev/null +++ b/src/resolvers/models/tokenCode.model.ts @@ -0,0 +1,15 @@ +import { Field, ObjectType } from '@nestjs/graphql'; +import { GraphQLInt } from 'graphql'; +import { Agent } from './agent.model'; + +@ObjectType() +export class TokenCode { + @Field() + id: string; + + @Field(() => GraphQLInt) + value: number; + + @Field(() => Agent) + agent: Agent; +} \ No newline at end of file diff --git a/src/resolvers/resolver.module.ts b/src/resolvers/resolver.module.ts index 26a64a6..dba4f82 100644 --- a/src/resolvers/resolver.module.ts +++ b/src/resolvers/resolver.module.ts @@ -2,8 +2,9 @@ import { Module } from '@nestjs/common'; import { GroupResolver } from './group.resolver'; import { EntryResolver } from './entry.resolver'; import { AgentResolver } from './agent.resolver'; +import { TokenCodeResolver } from './tokenCode.resolver'; @Module({ - providers: [GroupResolver, EntryResolver, AgentResolver], + providers: [GroupResolver, EntryResolver, AgentResolver, TokenCodeResolver], }) export class ResolverModule {} diff --git a/src/resolvers/tokenCode.resolver.ts b/src/resolvers/tokenCode.resolver.ts new file mode 100644 index 0000000..73bfd95 --- /dev/null +++ b/src/resolvers/tokenCode.resolver.ts @@ -0,0 +1,113 @@ +import { UseGuards, NotFoundException } from "@nestjs/common"; +import { Args, Context, Mutation, Parent, ResolveField, Resolver, Subscription } from "@nestjs/graphql"; +import { GraphQLString, GraphQLInt } from 'graphql'; +import { Request } from "express"; +import { GroupAuthGuard } from "../auth/graphql-auth.guard"; +import { PrismaService } from "../prisma/prisma.service"; +import { TokenCode } from "./models/tokenCode.model"; +import { AgentAuthGuard } from "../auth/agent-auth.guard"; +import { pubSub } from "../pubSub.instance"; + +@Resolver(() => TokenCode) +export class TokenCodeResolver { + constructor(private prismaService: PrismaService) {} + + // TODO: protect subscription + @Subscription(() => TokenCode, { + filter: (payload, variables) => + payload.newTokenCode.agent.id === variables.id, + }) + newTokenCode(@Args('id') id: string){ + return pubSub.asyncIterator('newTokenCode'); + } + + @UseGuards(AgentAuthGuard) + @Mutation(() => TokenCode) + async tokenCode(@Context() { req }: { req: Request }) { + let tokenCode = await this.prismaService.tokenCode.findFirst({ + where: { + id: req.agent.tokenCodeId, + }, + }); + + if (!tokenCode) { + tokenCode = await this.prismaService.tokenCode.create({ + data: { + value: 1, + agent: { + connect: { id: req.agent.id }, + }, + }, + }); + + pubSub.publish('newTokenCode', { + newTokenCode: tokenCode + }); + } + + return tokenCode; + } + + @UseGuards(GroupAuthGuard) + @Mutation(() => GraphQLInt) + async redeemCode( + @Args({ name: 'code', type: () => GraphQLString }) code: string, + @Context() { req }: { req: Request }, + ) { + const tokenCode = await this.prismaService.tokenCode.findFirst({ + where: { id: code }, + include: { + agent: true, + } + }); + + if (!tokenCode) + throw new NotFoundException(); + + + const newTokenCode = await this.prismaService.tokenCode.create({ + data: { + value: 1, + agent: { + connect: { id: tokenCode.agent.id }, + }, + }, + include: { + agent: true, + } + }); + + pubSub.publish('newTokenCode', { + newTokenCode: newTokenCode, + }); + + await this.prismaService.tokenCode.delete({ + where: { id: tokenCode.id }, + }); + + req.group = await this.prismaService.group.update({ + where: { id: req.group.id }, + data: { + tokens: { + increment: tokenCode.value, + }, + }, + }); + + pubSub.publish('updateToken', { + updateToken: req.group, + }); + + return tokenCode.value; + } + + @ResolveField('agent') + agent(@Parent() token: TokenCode) { + return this.prismaService.agent.findFirst({ + where: { + tokenCodeId: token.id, + }, + }); + } + +} \ No newline at end of file -- GitLab