diff --git a/src/app.module.ts b/src/app.module.ts index 5f842a2d55e72c2e185bdf7db083829b1bb0595f..7c7d319b971b2f2e5fecdd32391873e545085dfd 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 0000000000000000000000000000000000000000..ebabd4b88339a8eba01b0d59c87066bc0a982a77 --- /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 0eaa07094f73d73b5e877efff045990bda6e032e..95ae5a8b9316f93f0460cef2175d5d0e99a924c0 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 4c1caf3cb744d2b805111f16c9d645ec356106d9..66614b21973648193d5b36b5b5fdb89b20eab717 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 6b7b65b0bff60ebd34e62404454efedffa2e03d1..843b0fe17025559f4fe9699c1823a610c672ddf7 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 58910d2f84205c5bf78cd9f606f292fad99d27c6..22af1ec29636dc7edea400fd8ad75e66007d2351 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 7f58805729dbfe064101d1f8c84c6f8bca5e959a..4bc0309d8ebc089435f6a02354f7500f90fb4c9a 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 7caa592d04e0432638a258671ede7b55487eb320..db0fa06071c65a63f1b55721e22708aa0a5c0a7d 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 0000000000000000000000000000000000000000..fb36c172afb7c7bbf514352e7f7532bc6f4943df --- /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 26a64a644fb84264cee3425ae9bd4f55e5a12367..dba4f8249c412cf07d2b49f6dd30da0bd2a2bdb0 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 0000000000000000000000000000000000000000..73bfd955b4d6bff66c29a507c25149ae2b3c100a --- /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