feat: introducing rate-limiting on queries, mutations and most of the REST endpoints (HBE-111) (#46)

* feat: rate-limiting guard added and configured in app module

* feat: rate-limit annotation added in controllers and resolvers (query, mutation, not subscription)

* docs: added comments
This commit is contained in:
Mir Arif Hasan
2023-03-21 17:15:50 +06:00
committed by GitHub
parent f78354a377
commit fa8ca0569d
18 changed files with 115 additions and 4 deletions

View File

@@ -5,6 +5,10 @@ DATABASE_URL=postgresql://postgres:testpass@dev-db:5432/hoppscotch
MAILER_SMTP_URL="smtps://user@domain.com:pass@smtp.domain.com"
MAILER_ADDRESS_FROM='"From Name Here" <from@example.com>'
# Rate Limit Config
RATE_LIMIT_TTL=60 # In seconds
RATE_LIMIT_MAX=100 # Max requests per IP
# Auth Tokens Config
JWT_SECRET='add some secret here'
TOKEN_SALT_COMPLEXITY=10

View File

@@ -31,6 +31,7 @@
"@nestjs/jwt": "^10.0.1",
"@nestjs/passport": "^9.0.0",
"@nestjs/platform-express": "^9.2.1",
"@nestjs/throttler": "^4.0.0",
"@prisma/client": "^4.7.1",
"apollo-server-express": "^3.11.1",
"apollo-server-plugin-base": "^3.7.1",

View File

@@ -18,6 +18,7 @@ import { AdminModule } from './admin/admin.module';
import { UserCollectionModule } from './user-collection/user-collection.module';
import { ShortcodeModule } from './shortcode/shortcode.module';
import { COOKIES_NOT_FOUND } from './errors';
import { ThrottlerModule } from '@nestjs/throttler';
@Module({
imports: [
@@ -60,6 +61,10 @@ import { COOKIES_NOT_FOUND } from './errors';
}),
driver: ApolloDriver,
}),
ThrottlerModule.forRoot({
ttl: +process.env.RATE_LIMIT_TTL,
limit: +process.env.RATE_LIMIT_MAX,
}),
UserModule,
AuthModule,
AdminModule,

View File

@@ -2,8 +2,6 @@ import {
Body,
Controller,
Get,
HttpException,
HttpStatus,
Post,
Req,
Request,
@@ -20,11 +18,14 @@ import { JwtAuthGuard } from './guards/jwt-auth.guard';
import { GqlUser } from 'src/decorators/gql-user.decorator';
import { AuthUser } from 'src/types/AuthUser';
import { RTCookie } from 'src/decorators/rt-cookie.decorator';
import { AuthGuard } from '@nestjs/passport';
import { authCookieHandler, throwHTTPErr } from './helper';
import { GoogleSSOGuard } from './guards/google-sso.guard';
import { GithubSSOGuard } from './guards/github-sso.guard';
import { MicrosoftSSOGuard } from './guards/microsoft-sso-.guard';
import { ThrottlerBehindProxyGuard } from 'src/guards/throttler-behind-proxy.guard';
import { SkipThrottle } from '@nestjs/throttler';
@UseGuards(ThrottlerBehindProxyGuard)
@Controller({ path: 'auth', version: '1' })
export class AuthController {
constructor(private authService: AuthService) {}
@@ -82,6 +83,7 @@ export class AuthController {
* @see https://auth0.com/docs/get-started/authentication-and-authorization-flow/authorization-code-flow#how-it-works
*/
@Get('google/callback')
@SkipThrottle()
@UseGuards(GoogleSSOGuard)
async googleAuthRedirect(@Request() req, @Res() res) {
const authTokens = await this.authService.generateAuthTokens(req.user.uid);
@@ -106,6 +108,7 @@ export class AuthController {
* @see https://auth0.com/docs/get-started/authentication-and-authorization-flow/authorization-code-flow#how-it-works
*/
@Get('github/callback')
@SkipThrottle()
@UseGuards(GithubSSOGuard)
async githubAuthRedirect(@Request() req, @Res() res) {
const authTokens = await this.authService.generateAuthTokens(req.user.uid);
@@ -130,6 +133,7 @@ export class AuthController {
* @see https://auth0.com/docs/get-started/authentication-and-authorization-flow/authorization-code-flow#how-it-works
*/
@Get('microsoft/callback')
@SkipThrottle()
@UseGuards(MicrosoftSSOGuard)
async microsoftAuthRedirect(@Request() req, @Res() res) {
const authTokens = await this.authService.generateAuthTokens(req.user.uid);

View File

@@ -0,0 +1,12 @@
import { ExecutionContext, Injectable } from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { ThrottlerGuard } from '@nestjs/throttler';
@Injectable()
export class GqlThrottlerGuard extends ThrottlerGuard {
getRequestResponse(context: ExecutionContext) {
const gqlCtx = GqlExecutionContext.create(context);
const ctx = gqlCtx.getContext();
return { req: ctx.req, res: ctx.res };
}
}

View File

@@ -0,0 +1,10 @@
import { ThrottlerGuard } from '@nestjs/throttler';
import { Injectable } from '@nestjs/common';
@Injectable()
export class ThrottlerBehindProxyGuard extends ThrottlerGuard {
protected getTracker(req: Record<string, any>): string {
return req.ips.length ? req.ips[0] : req.ip; // individualize IP extraction to meet your own needs
// learn more: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For#directives
}
}

View File

@@ -20,7 +20,10 @@ import { PubSubService } from 'src/pubsub/pubsub.service';
import { AuthUser } from '../types/AuthUser';
import { JwtService } from '@nestjs/jwt';
import { PaginationArgs } from 'src/types/input-types.args';
import { GqlThrottlerGuard } from 'src/guards/gql-throttler.guard';
import { SkipThrottle } from '@nestjs/throttler';
@UseGuards(GqlThrottlerGuard)
@Resolver(() => Shortcode)
export class ShortcodeResolver {
constructor(
@@ -105,6 +108,7 @@ export class ShortcodeResolver {
description: 'Listen for shortcode creation',
resolve: (value) => value,
})
@SkipThrottle()
@UseGuards(GqlAuthGuard)
myShortcodesCreated(@GqlUser() user: AuthUser) {
return this.pubsub.asyncIterator(`shortcode/${user.uid}/created`);
@@ -114,6 +118,7 @@ export class ShortcodeResolver {
description: 'Listen for shortcode deletion',
resolve: (value) => value,
})
@SkipThrottle()
@UseGuards(GqlAuthGuard)
myShortcodesRevoked(@GqlUser() user: AuthUser): AsyncIterator<Shortcode> {
return this.pubsub.asyncIterator(`shortcode/${user.uid}/revoked`);

View File

@@ -29,7 +29,10 @@ import {
} from './input-type.args';
import * as E from 'fp-ts/Either';
import { throwErr } from 'src/utils';
import { GqlThrottlerGuard } from 'src/guards/gql-throttler.guard';
import { SkipThrottle } from '@nestjs/throttler';
@UseGuards(GqlThrottlerGuard)
@Resolver(() => TeamCollection)
export class TeamCollectionResolver {
constructor(
@@ -312,6 +315,7 @@ export class TeamCollectionResolver {
TeamMemberRole.EDITOR,
TeamMemberRole.VIEWER,
)
@SkipThrottle()
@UseGuards(GqlAuthGuard, GqlTeamMemberGuard)
teamCollectionAdded(
@Args({
@@ -333,6 +337,7 @@ export class TeamCollectionResolver {
TeamMemberRole.EDITOR,
TeamMemberRole.VIEWER,
)
@SkipThrottle()
@UseGuards(GqlAuthGuard, GqlTeamMemberGuard)
teamCollectionUpdated(
@Args({
@@ -354,6 +359,7 @@ export class TeamCollectionResolver {
TeamMemberRole.EDITOR,
TeamMemberRole.VIEWER,
)
@SkipThrottle()
@UseGuards(GqlAuthGuard, GqlTeamMemberGuard)
teamCollectionRemoved(
@Args({
@@ -375,6 +381,7 @@ export class TeamCollectionResolver {
TeamMemberRole.EDITOR,
TeamMemberRole.VIEWER,
)
@SkipThrottle()
@UseGuards(GqlAuthGuard, GqlTeamMemberGuard)
teamCollectionMoved(
@Args({
@@ -396,6 +403,7 @@ export class TeamCollectionResolver {
TeamMemberRole.EDITOR,
TeamMemberRole.VIEWER,
)
@SkipThrottle()
@UseGuards(GqlAuthGuard, GqlTeamMemberGuard)
collectionOrderUpdated(
@Args({

View File

@@ -1,8 +1,10 @@
import { UseGuards } from '@nestjs/common';
import { Resolver, Mutation, Args, Subscription, ID } from '@nestjs/graphql';
import { SkipThrottle } from '@nestjs/throttler';
import { pipe } from 'fp-ts/function';
import * as TE from 'fp-ts/TaskEither';
import { GqlAuthGuard } from 'src/guards/gql-auth.guard';
import { GqlThrottlerGuard } from 'src/guards/gql-throttler.guard';
import { PubSubService } from 'src/pubsub/pubsub.service';
import { RequiresTeamRole } from 'src/team/decorators/requires-team-role.decorator';
import { GqlTeamMemberGuard } from 'src/team/guards/gql-team-member.guard';
@@ -12,6 +14,7 @@ import { GqlTeamEnvTeamGuard } from './gql-team-env-team.guard';
import { TeamEnvironment } from './team-environments.model';
import { TeamEnvironmentsService } from './team-environments.service';
@UseGuards(GqlThrottlerGuard)
@Resolver(() => 'TeamEnvironment')
export class TeamEnvironmentsResolver {
constructor(
@@ -144,6 +147,7 @@ export class TeamEnvironmentsResolver {
description: 'Listen for Team Environment Updates',
resolve: (value) => value,
})
@SkipThrottle()
@UseGuards(GqlAuthGuard, GqlTeamMemberGuard)
@RequiresTeamRole(
TeamMemberRole.OWNER,
@@ -165,6 +169,7 @@ export class TeamEnvironmentsResolver {
description: 'Listen for Team Environment Creation Messages',
resolve: (value) => value,
})
@SkipThrottle()
@UseGuards(GqlAuthGuard, GqlTeamMemberGuard)
@RequiresTeamRole(
TeamMemberRole.OWNER,
@@ -186,6 +191,7 @@ export class TeamEnvironmentsResolver {
description: 'Listen for Team Environment Deletion Messages',
resolve: (value) => value,
})
@SkipThrottle()
@UseGuards(GqlAuthGuard, GqlTeamMemberGuard)
@RequiresTeamRole(
TeamMemberRole.OWNER,

View File

@@ -34,7 +34,10 @@ import { TeamInviteViewerGuard } from './team-invite-viewer.guard';
import { TeamInviteTeamOwnerGuard } from './team-invite-team-owner.guard';
import { UserService } from 'src/user/user.service';
import { PubSubService } from 'src/pubsub/pubsub.service';
import { GqlThrottlerGuard } from 'src/guards/gql-throttler.guard';
import { SkipThrottle } from '@nestjs/throttler';
@UseGuards(GqlThrottlerGuard)
@Resolver(() => TeamInvitation)
export class TeamInvitationResolver {
constructor(
@@ -199,6 +202,7 @@ export class TeamInvitationResolver {
description: 'Listens to when a Team Invitation is added',
resolve: (value) => value,
})
@SkipThrottle()
@UseGuards(GqlAuthGuard, GqlTeamMemberGuard)
@RequiresTeamRole(
TeamMemberRole.OWNER,
@@ -220,6 +224,7 @@ export class TeamInvitationResolver {
description: 'Listens to when a Team Invitation is removed',
resolve: (value) => value,
})
@SkipThrottle()
@UseGuards(GqlAuthGuard, GqlTeamMemberGuard)
@RequiresTeamRole(
TeamMemberRole.OWNER,

View File

@@ -30,7 +30,10 @@ import { PubSubService } from 'src/pubsub/pubsub.service';
import * as E from 'fp-ts/Either';
import * as O from 'fp-ts/Option';
import { throwErr } from 'src/utils';
import { GqlThrottlerGuard } from 'src/guards/gql-throttler.guard';
import { SkipThrottle } from '@nestjs/throttler';
@UseGuards(GqlThrottlerGuard)
@Resolver(() => TeamRequest)
export class TeamRequestResolver {
constructor(
@@ -242,6 +245,7 @@ export class TeamRequestResolver {
description: 'Emits when a new request is added to a team',
resolve: (value) => value,
})
@SkipThrottle()
@UseGuards(GqlAuthGuard, GqlTeamMemberGuard)
@RequiresTeamRole(
TeamMemberRole.VIEWER,
@@ -263,6 +267,7 @@ export class TeamRequestResolver {
description: 'Emitted when a request has been updated',
resolve: (value) => value,
})
@SkipThrottle()
@UseGuards(GqlAuthGuard, GqlTeamMemberGuard)
@RequiresTeamRole(
TeamMemberRole.VIEWER,
@@ -285,6 +290,7 @@ export class TeamRequestResolver {
'Emitted when a request has been deleted. Only the id of the request is emitted.',
resolve: (value) => value,
})
@SkipThrottle()
@UseGuards(GqlAuthGuard, GqlTeamMemberGuard)
@RequiresTeamRole(
TeamMemberRole.VIEWER,
@@ -307,6 +313,7 @@ export class TeamRequestResolver {
'Emitted when a requests position has been changed in its collection',
resolve: (value) => value,
})
@SkipThrottle()
@UseGuards(GqlAuthGuard, GqlTeamMemberGuard)
@RequiresTeamRole(
TeamMemberRole.VIEWER,
@@ -329,6 +336,7 @@ export class TeamRequestResolver {
'Emitted when a request has been moved from one collection into another',
resolve: (value) => value,
})
@SkipThrottle()
@UseGuards(GqlAuthGuard, GqlTeamMemberGuard)
@RequiresTeamRole(
TeamMemberRole.VIEWER,

View File

@@ -11,7 +11,6 @@ import {
ID,
} from '@nestjs/graphql';
import { TeamService } from './team.service';
import { User } from '../user/user.model';
import { GqlAuthGuard } from '../guards/gql-auth.guard';
import { GqlUser } from '../decorators/gql-user.decorator';
import { UseGuards } from '@nestjs/common';
@@ -21,7 +20,10 @@ import { PubSubService } from '../pubsub/pubsub.service';
import * as E from 'fp-ts/Either';
import { throwErr } from 'src/utils';
import { AuthUser } from 'src/types/AuthUser';
import { GqlThrottlerGuard } from 'src/guards/gql-throttler.guard';
import { SkipThrottle } from '@nestjs/throttler';
@UseGuards(GqlThrottlerGuard)
@Resolver(() => Team)
export class TeamResolver {
constructor(
@@ -271,6 +273,7 @@ export class TeamResolver {
TeamMemberRole.EDITOR,
TeamMemberRole.VIEWER,
)
@SkipThrottle()
@UseGuards(GqlAuthGuard, GqlTeamMemberGuard)
teamMemberAdded(
@Args({
@@ -293,6 +296,7 @@ export class TeamResolver {
TeamMemberRole.EDITOR,
TeamMemberRole.VIEWER,
)
@SkipThrottle()
@UseGuards(GqlAuthGuard, GqlTeamMemberGuard)
teamMemberUpdated(
@Args({
@@ -315,6 +319,7 @@ export class TeamResolver {
TeamMemberRole.EDITOR,
TeamMemberRole.VIEWER,
)
@SkipThrottle()
@UseGuards(GqlAuthGuard, GqlTeamMemberGuard)
teamMemberRemoved(
@Args({

View File

@@ -31,7 +31,10 @@ import {
} from './input-type.args';
import { ReqType } from 'src/types/RequestTypes';
import * as E from 'fp-ts/Either';
import { GqlThrottlerGuard } from 'src/guards/gql-throttler.guard';
import { SkipThrottle } from '@nestjs/throttler';
@UseGuards(GqlThrottlerGuard)
@Resolver(() => UserCollection)
export class UserCollectionResolver {
constructor(
@@ -352,6 +355,7 @@ export class UserCollectionResolver {
description: 'Listen for User Collection Creation',
resolve: (value) => value,
})
@SkipThrottle()
@UseGuards(GqlAuthGuard)
userCollectionCreated(@GqlUser() user: AuthUser) {
return this.pubSub.asyncIterator(`user_coll/${user.uid}/created`);
@@ -361,6 +365,7 @@ export class UserCollectionResolver {
description: 'Listen to when a User Collection has been updated.',
resolve: (value) => value,
})
@SkipThrottle()
@UseGuards(GqlAuthGuard)
userCollectionUpdated(@GqlUser() user: AuthUser) {
return this.pubSub.asyncIterator(`user_coll/${user.uid}/updated`);
@@ -370,6 +375,7 @@ export class UserCollectionResolver {
description: 'Listen to when a User Collection has been deleted',
resolve: (value) => value,
})
@SkipThrottle()
@UseGuards(GqlAuthGuard)
userCollectionRemoved(@GqlUser() user: AuthUser) {
return this.pubSub.asyncIterator(`user_coll/${user.uid}/deleted`);
@@ -379,6 +385,7 @@ export class UserCollectionResolver {
description: 'Listen to when a User Collection has been moved',
resolve: (value) => value,
})
@SkipThrottle()
@UseGuards(GqlAuthGuard)
userCollectionMoved(@GqlUser() user: AuthUser) {
return this.pubSub.asyncIterator(`user_coll/${user.uid}/moved`);
@@ -388,6 +395,7 @@ export class UserCollectionResolver {
description: 'Listen to when a User Collections position has changed',
resolve: (value) => value,
})
@SkipThrottle()
@UseGuards(GqlAuthGuard)
userCollectionOrderUpdated(@GqlUser() user: AuthUser) {
return this.pubSub.asyncIterator(`user_coll/${user.uid}/order_updated`);

View File

@@ -8,7 +8,10 @@ import { User } from '../user/user.model';
import { UserEnvironmentsService } from './user-environments.service';
import * as E from 'fp-ts/Either';
import { throwErr } from 'src/utils';
import { GqlThrottlerGuard } from 'src/guards/gql-throttler.guard';
import { SkipThrottle } from '@nestjs/throttler';
@UseGuards(GqlThrottlerGuard)
@Resolver()
export class UserEnvironmentsResolver {
constructor(
@@ -157,6 +160,7 @@ export class UserEnvironmentsResolver {
description: 'Listen for User Environment Creation',
resolve: (value) => value,
})
@SkipThrottle()
@UseGuards(GqlAuthGuard)
userEnvironmentCreated(@GqlUser() user: User) {
return this.pubsub.asyncIterator(`user_environment/${user.uid}/created`);
@@ -166,6 +170,7 @@ export class UserEnvironmentsResolver {
description: 'Listen for User Environment updates',
resolve: (value) => value,
})
@SkipThrottle()
@UseGuards(GqlAuthGuard)
userEnvironmentUpdated(@GqlUser() user: User) {
return this.pubsub.asyncIterator(`user_environment/${user.uid}/updated`);
@@ -175,6 +180,7 @@ export class UserEnvironmentsResolver {
description: 'Listen for User Environment deletion',
resolve: (value) => value,
})
@SkipThrottle()
@UseGuards(GqlAuthGuard)
userEnvironmentDeleted(@GqlUser() user: User) {
return this.pubsub.asyncIterator(`user_environment/${user.uid}/deleted`);
@@ -184,6 +190,7 @@ export class UserEnvironmentsResolver {
description: 'Listen for User Environment DeleteMany',
resolve: (value) => value,
})
@SkipThrottle()
@UseGuards(GqlAuthGuard)
userEnvironmentDeleteMany(@GqlUser() user: User) {
return this.pubsub.asyncIterator(

View File

@@ -8,7 +8,10 @@ import { GqlUser } from '../decorators/gql-user.decorator';
import { User } from '../user/user.model';
import { throwErr } from '../utils';
import * as E from 'fp-ts/Either';
import { GqlThrottlerGuard } from 'src/guards/gql-throttler.guard';
import { SkipThrottle } from '@nestjs/throttler';
@UseGuards(GqlThrottlerGuard)
@Resolver()
export class UserHistoryResolver {
constructor(
@@ -115,6 +118,7 @@ export class UserHistoryResolver {
description: 'Listen for User History Creation',
resolve: (value) => value,
})
@SkipThrottle()
@UseGuards(GqlAuthGuard)
userHistoryCreated(@GqlUser() user: User) {
return this.pubsub.asyncIterator(`user_history/${user.uid}/created`);
@@ -124,6 +128,7 @@ export class UserHistoryResolver {
description: 'Listen for User History update',
resolve: (value) => value,
})
@SkipThrottle()
@UseGuards(GqlAuthGuard)
userHistoryUpdated(@GqlUser() user: User) {
return this.pubsub.asyncIterator(`user_history/${user.uid}/updated`);
@@ -133,6 +138,7 @@ export class UserHistoryResolver {
description: 'Listen for User History deletion',
resolve: (value) => value,
})
@SkipThrottle()
@UseGuards(GqlAuthGuard)
userHistoryDeleted(@GqlUser() user: User) {
return this.pubsub.asyncIterator(`user_history/${user.uid}/deleted`);
@@ -142,6 +148,7 @@ export class UserHistoryResolver {
description: 'Listen for User History deleted many',
resolve: (value) => value,
})
@SkipThrottle()
@UseGuards(GqlAuthGuard)
userHistoryDeletedMany(@GqlUser() user: User) {
return this.pubsub.asyncIterator(`user_history/${user.uid}/deleted_many`);

View File

@@ -25,7 +25,10 @@ import {
import { AuthUser } from 'src/types/AuthUser';
import { User } from 'src/user/user.model';
import { ReqType } from 'src/user-history/user-history.model';
import { GqlThrottlerGuard } from 'src/guards/gql-throttler.guard';
import { SkipThrottle } from '@nestjs/throttler';
@UseGuards(GqlThrottlerGuard)
@Resolver(() => UserRequest)
export class UserRequestResolver {
constructor(
@@ -232,6 +235,7 @@ export class UserRequestResolver {
description: 'Listen for User Request Creation',
resolve: (value) => value,
})
@SkipThrottle()
@UseGuards(GqlAuthGuard)
userRequestCreated(@GqlUser() user: AuthUser) {
return this.pubSub.asyncIterator(`user_request/${user.uid}/created`);
@@ -241,6 +245,7 @@ export class UserRequestResolver {
description: 'Listen for User Request Update',
resolve: (value) => value,
})
@SkipThrottle()
@UseGuards(GqlAuthGuard)
userRequestUpdated(@GqlUser() user: AuthUser) {
return this.pubSub.asyncIterator(`user_request/${user.uid}/updated`);
@@ -250,6 +255,7 @@ export class UserRequestResolver {
description: 'Listen for User Request Deletion',
resolve: (value) => value,
})
@SkipThrottle()
@UseGuards(GqlAuthGuard)
userRequestDeleted(@GqlUser() user: AuthUser) {
return this.pubSub.asyncIterator(`user_request/${user.uid}/deleted`);
@@ -259,6 +265,7 @@ export class UserRequestResolver {
description: 'Listen for User Request Moved',
resolve: (value) => value,
})
@SkipThrottle()
@UseGuards(GqlAuthGuard)
userRequestMoved(@GqlUser() user: AuthUser) {
return this.pubSub.asyncIterator(`user_request/${user.uid}/moved`);

View File

@@ -9,7 +9,10 @@ import { UserSettings } from './user-settings.model';
import { UserSettingsService } from './user-settings.service';
import { PubSubService } from 'src/pubsub/pubsub.service';
import { AuthUser } from 'src/types/AuthUser';
import { GqlThrottlerGuard } from 'src/guards/gql-throttler.guard';
import { SkipThrottle } from '@nestjs/throttler';
@UseGuards(GqlThrottlerGuard)
@Resolver()
export class UserSettingsResolver {
constructor(
@@ -63,6 +66,7 @@ export class UserSettingsResolver {
description: 'Listen for user setting creation',
resolve: (value) => value,
})
@SkipThrottle()
@UseGuards(GqlAuthGuard)
userSettingsCreated(@GqlUser() user: User) {
return this.pubsub.asyncIterator(`user_settings/${user.uid}/created`);
@@ -72,6 +76,7 @@ export class UserSettingsResolver {
description: 'Listen for user setting updates',
resolve: (value) => value,
})
@SkipThrottle()
@UseGuards(GqlAuthGuard)
userSettingsUpdated(@GqlUser() user: User) {
return this.pubsub.asyncIterator(`user_settings/${user.uid}/updated`);

View File

@@ -10,7 +10,10 @@ import * as TE from 'fp-ts/TaskEither';
import { pipe } from 'fp-ts/function';
import { PubSubService } from 'src/pubsub/pubsub.service';
import { AuthUser } from 'src/types/AuthUser';
import { GqlThrottlerGuard } from 'src/guards/gql-throttler.guard';
import { SkipThrottle } from '@nestjs/throttler';
@UseGuards(GqlThrottlerGuard)
@Resolver(() => User)
export class UserResolver {
constructor(
@@ -73,6 +76,7 @@ export class UserResolver {
description: 'Listen for user updates',
resolve: (value) => value,
})
@SkipThrottle()
@UseGuards(GqlAuthGuard)
userUpdated(@GqlUser() user: User) {
return this.pubsub.asyncIterator(`user/${user.uid}/updated`);