Skip to content

Commit a6fec39

Browse files
committed
add protection
1 parent aaea83e commit a6fec39

File tree

8 files changed

+139
-14
lines changed

8 files changed

+139
-14
lines changed

backend/src/chat/chat.model.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
import { forwardRef } from '@nestjs/common';
1717
import { Message } from 'src/chat/message.model';
1818
import { SystemBaseModel } from 'src/system-base-model/system-base.model';
19+
import { User } from 'src/user/user.model';
1920

2021
@Entity()
2122
@ObjectType()
@@ -31,6 +32,10 @@ export class Chat extends SystemBaseModel {
3132
@Field(() => [Message], { nullable: true })
3233
@OneToMany(() => Message, (message) => message.chat, { cascade: true })
3334
messages: Message[];
35+
36+
@ManyToOne(() => User, (user) => user.chats)
37+
@Field(() => User)
38+
user: User;
3439
}
3540

3641
@ObjectType('ChatCompletionDeltaType')

backend/src/chat/chat.module.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,23 @@ import { TypeOrmModule } from '@nestjs/typeorm';
88
import { User } from 'src/user/user.model';
99
import { Chat } from './chat.model';
1010
import { Message } from 'src/chat/message.model';
11+
import { ChatGuard } from '../guard/chat.guard';
12+
import { AuthModule } from '../auth/auth.module';
13+
import { UserService } from 'src/user/user.service';
1114

1215
@Module({
13-
imports: [HttpModule, TypeOrmModule.forFeature([Chat, User, Message])],
14-
providers: [ChatResolver, ChatProxyService, ChatService],
15-
exports: [ChatService],
16+
imports: [
17+
HttpModule,
18+
TypeOrmModule.forFeature([Chat, User, Message]),
19+
AuthModule,
20+
],
21+
providers: [
22+
ChatResolver,
23+
ChatProxyService,
24+
ChatService,
25+
ChatGuard,
26+
UserService,
27+
],
28+
exports: [ChatService, ChatGuard],
1629
})
1730
export class ChatModule {}

backend/src/chat/chat.resolver.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,24 @@
11
import { Resolver, Subscription, Args, Query, Mutation } from '@nestjs/graphql';
22
import { ChatCompletionChunk } from './chat.model';
33
import { ChatProxyService, ChatService } from './chat.service';
4+
import { UserService } from 'src/user/user.service';
45
import { Chat } from './chat.model';
56
import { Message } from 'src/chat/message.model';
67
import {
78
NewChatInput,
89
UpateChatTitleInput,
910
ChatInput,
1011
} from 'src/chat/dto/chat.input';
12+
import { UseGuards } from '@nestjs/common';
13+
import { ChatGuard } from '../guard/chat.guard';
14+
import { GetUserIdFromToken } from '../decorator/get-auth-token';
1115

1216
@Resolver('Chat')
1317
export class ChatResolver {
1418
constructor(
1519
private chatProxyService: ChatProxyService,
1620
private chatService: ChatService,
21+
private userService: UserService,
1722
) {}
1823

1924
@Subscription(() => ChatCompletionChunk, {
@@ -26,7 +31,7 @@ export class ChatResolver {
2631
for await (const chunk of iterator) {
2732
if (chunk) {
2833
await this.chatService.saveMessage(
29-
input.id,
34+
input.chatId,
3035
chunk.id,
3136
chunk.choices[0].delta.content,
3237
);
@@ -39,8 +44,15 @@ export class ChatResolver {
3944
}
4045
}
4146

47+
@Query(() => [Chat], { nullable: true })
48+
async getUserChats(@GetUserIdFromToken() userId: string): Promise<Chat[]> {
49+
const user = await this.userService.getUserChats(userId);
50+
return user ? user.chats : []; // Return chats if user exists, otherwise return an empty array
51+
}
52+
4253
@Query(() => Message, { nullable: true })
4354
async getMessageDetail(
55+
@GetUserIdFromToken() userId: string,
4456
@Args('messageId') messageId: string,
4557
): Promise<Message> {
4658
return this.chatService.getMessageById(messageId);
@@ -51,6 +63,7 @@ export class ChatResolver {
5163
return this.chatService.getChatHistory(chatId);
5264
}
5365

66+
@UseGuards(ChatGuard)
5467
@Query(() => Chat, { nullable: true })
5568
async getChatDetails(@Args('chatId') chatId: string): Promise<Chat> {
5669
return this.chatService.getChatDetails(chatId);
@@ -63,21 +76,25 @@ export class ChatResolver {
6376

6477
@Mutation(() => Chat)
6578
async createChat(
79+
@GetUserIdFromToken() userId: string,
6680
@Args('newChatInput') newChatInput: NewChatInput,
6781
): Promise<Chat> {
68-
return this.chatService.createChat(newChatInput);
82+
return this.chatService.createChat(userId, newChatInput);
6983
}
7084

85+
@UseGuards(ChatGuard)
7186
@Mutation(() => Boolean)
7287
async deleteChat(@Args('chatId') chatId: string): Promise<boolean> {
7388
return this.chatService.deleteChat(chatId);
7489
}
7590

91+
@UseGuards(ChatGuard)
7692
@Mutation(() => Boolean)
7793
async clearChatHistory(@Args('chatId') chatId: string): Promise<boolean> {
7894
return this.chatService.clearChatHistory(chatId);
7995
}
8096

97+
@UseGuards(ChatGuard)
8198
@Mutation(() => Chat, { nullable: true })
8299
async updateChatTitle(
83100
@Args('upateChatTitleInput') upateChatTitleInput: UpateChatTitleInput,

backend/src/chat/chat.service.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,13 +171,22 @@ export class ChatService {
171171
});
172172
}
173173

174-
async createChat(newChatInput: NewChatInput): Promise<Chat> {
174+
async createChat(userId: string, newChatInput: NewChatInput): Promise<Chat> {
175+
// Fetch the user entity using the userId
176+
const user = await this.userRepository.findOne({ where: { id: userId } });
177+
if (!user) {
178+
throw new Error('User not found');
179+
}
180+
181+
// Create a new chat and associate it with the user
175182
const newChat = this.chatRepository.create({
176183
title: newChatInput.title,
177184
messages: [],
178185
createdAt: new Date(),
179186
updatedAt: new Date(),
187+
user: user, // Associate the user with the chat
180188
});
189+
181190
return await this.chatRepository.save(newChat);
182191
}
183192

@@ -204,7 +213,7 @@ export class ChatService {
204213
upateChatTitleInput: UpateChatTitleInput,
205214
): Promise<Chat> {
206215
const chat = await this.chatRepository.findOne({
207-
where: { id: upateChatTitleInput.id },
216+
where: { id: upateChatTitleInput.chatId },
208217
});
209218
if (chat) {
210219
chat.title = upateChatTitleInput.title;

backend/src/chat/dto/chat.input.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export class NewChatInput {
1111
@InputType()
1212
export class UpateChatTitleInput {
1313
@Field()
14-
id: string;
14+
chatId: string;
1515

1616
@Field({ nullable: true })
1717
title: string;
@@ -20,7 +20,7 @@ export class UpateChatTitleInput {
2020
@InputType('ChatInputType')
2121
export class ChatInput {
2222
@Field()
23-
id: string;
23+
chatId: string;
2424

2525
@Field()
2626
message: string;

backend/src/guard/chat.guard.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import {
2+
Injectable,
3+
CanActivate,
4+
ExecutionContext,
5+
UnauthorizedException,
6+
} from '@nestjs/common';
7+
import { GqlExecutionContext } from '@nestjs/graphql';
8+
import { JwtService } from '@nestjs/jwt';
9+
import { ChatService } from '../chat/chat.service';
10+
11+
@Injectable()
12+
export class ChatGuard implements CanActivate {
13+
constructor(
14+
private readonly chatService: ChatService, // Inject ChatService to fetch chat details
15+
private readonly jwtService: JwtService, // JWT Service to verify tokens
16+
) {}
17+
18+
async canActivate(context: ExecutionContext): Promise<boolean> {
19+
const gqlContext = GqlExecutionContext.create(context);
20+
const request = gqlContext.getContext().req;
21+
22+
// Extract the authorization header
23+
const authHeader = request.headers.authorization;
24+
if (!authHeader || !authHeader.startsWith('Bearer ')) {
25+
throw new UnauthorizedException('Authorization token is missing');
26+
}
27+
28+
// Decode the token to get user information
29+
const token = authHeader.split(' ')[1];
30+
let user: any;
31+
try {
32+
user = this.jwtService.verify(token);
33+
} catch (error) {
34+
throw new UnauthorizedException('Invalid token');
35+
}
36+
37+
// Extract chatId from the request arguments
38+
const args = gqlContext.getArgs();
39+
const { chatId } = args;
40+
41+
// check if the user is part of the chat
42+
const chat = await this.chatService.getChatDetails(chatId);
43+
if (!chat) {
44+
throw new UnauthorizedException('Chat not found');
45+
}
46+
47+
if (chat.user.id !== user.userId) {
48+
throw new UnauthorizedException(
49+
'User is not authorized to access this chat',
50+
);
51+
}
52+
53+
return true;
54+
}
55+
}

backend/src/schema.gql

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@ type Chat {
77
id: ID!
88
isActive: Boolean!
99
isDeleted: Boolean!
10-
messages: [Message!]!
10+
messages: [Message!]
1111
title: String
1212
updatedAt: Date!
13+
user: User!
1314
}
1415

1516
type ChatCompletionChoiceType {
@@ -32,6 +33,7 @@ type ChatCompletionDeltaType {
3233
}
3334

3435
input ChatInputType {
36+
chatId: String!
3537
message: String!
3638
}
3739

@@ -75,23 +77,27 @@ type Message {
7577

7678
type Mutation {
7779
clearChatHistory(chatId: String!): Boolean!
78-
createChat: Chat!
80+
createChat(newChatInput: NewChatInput!): Chat!
7981
deleteChat(chatId: String!): Boolean!
8082
deleteProject(projectId: String!): Boolean!
8183
login(input: LoginUserInput!): LoginResponse!
8284
registerUser(input: RegisterUserInput!): User!
8385
removePackageFromProject(packageId: String!, projectId: String!): Boolean!
84-
updateChatTitle(chatId: String!, title: String!): Chat
86+
updateChatTitle(upateChatTitleInput: UpateChatTitleInput!): Chat
8587
updateProjectPath(newPath: String!, projectId: String!): Boolean!
8688
upsertProject(upsertProjectInput: UpsertProjectInput!): Project!
8789
}
8890

91+
input NewChatInput {
92+
title: String
93+
}
94+
8995
type Project {
9096
createdAt: Date!
9197
id: ID!
9298
isActive: Boolean!
9399
isDeleted: Boolean!
94-
path: String!
100+
path: String
95101
projectName: String!
96102
projectPackages: [ProjectPackages!]
97103
updatedAt: Date!
@@ -112,7 +118,9 @@ type Query {
112118
checkToken(input: CheckTokenInput!): Boolean!
113119
getChatDetails(chatId: String!): Chat
114120
getChatHistory(chatId: String!): [Message!]!
121+
getMessageDetail(messageId: String!): Message
115122
getProjectDetails(projectId: String!): Project!
123+
getUserChats: [Chat!]
116124
getUserProjects: [Project!]!
117125
logout: Boolean!
118126
}
@@ -132,6 +140,11 @@ type Subscription {
132140
chatStream(input: ChatInputType!): ChatCompletionChunkType
133141
}
134142

143+
input UpateChatTitleInput {
144+
chatId: String!
145+
title: String
146+
}
147+
135148
input UpsertProjectInput {
136149
projectId: ID
137150
projectName: String!

backend/src/user/user.service.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,17 @@ import { LoginUserInput } from './dto/login-user.input';
1313
import { ConfigService } from '@nestjs/config';
1414

1515
@Injectable()
16-
export class UserService {}
16+
export class UserService {
17+
constructor(
18+
@InjectRepository(User)
19+
private userRepository: Repository<User>,
20+
) {}
21+
22+
// Method to get all chats of a user
23+
async getUserChats(userId: string): Promise<User> {
24+
return this.userRepository.findOne({
25+
where: { id: userId },
26+
relations: ['chats'], // Ensure 'chats' relation is loaded
27+
});
28+
}
29+
}

0 commit comments

Comments
 (0)