Skip to content

Commit 07ab393

Browse files
authored
Merge pull request #188 from game-node-app/dev
Connection Sync and Collection Entry user id migration
2 parents e50334d + 74589b7 commit 07ab393

File tree

54 files changed

+817
-179
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+817
-179
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,4 @@ dist
5252
/build/
5353

5454
.idea
55+

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,13 @@
3838
"@nestjs/core": "^11.0.11",
3939
"@nestjs/mapped-types": "*",
4040
"@nestjs/platform-express": "^11.0.11",
41+
"@nestjs/platform-socket.io": "^11.1.5",
4142
"@nestjs/schedule": "^5.0.1",
4243
"@nestjs/swagger": "^11.0.6",
4344
"@nestjs/terminus": "^11.0.0",
4445
"@nestjs/throttler": "^6.4.0",
4546
"@nestjs/typeorm": "^11.0.0",
47+
"@nestjs/websockets": "^11.1.5",
4648
"@types/async-retry": "^1.4.9",
4749
"@types/bad-words": "^3.0.3",
4850
"@types/multer": "^1.4.12",
@@ -72,6 +74,7 @@
7274
"supertokens-node": "^22.0.1",
7375
"ts-pattern": "^5.6.2",
7476
"typeorm": "^0.3.21",
77+
"typeorm-transactional": "^0.5.0",
7578
"unique-username-generator": "^1.4.0"
7679
},
7780
"devDependencies": {

server_swagger.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

src/app.module.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,9 @@ import { ExternalGameModule } from "./game/external-game/external-game.module";
3636
import { XboxSyncModule } from "./sync/xbox/xbox-sync.module";
3737
import { createKeyv } from "@keyv/redis";
3838
import { GameAchievementModule } from "./game/game-achievement/game-achievement.module";
39-
import { JournalModule } from './journal/journal.module';
39+
import { JournalModule } from "./journal/journal.module";
40+
import { addTransactionalDataSource } from "typeorm-transactional";
41+
import { DataSource } from "typeorm";
4042

4143
/**
4244
* Should only be called after 'ConfigModule' is loaded (e.g. in useFactory)
@@ -109,6 +111,12 @@ function getRedisConfig(target: "cache" | "bullmq" = "cache") {
109111
},
110112
};
111113
},
114+
async dataSourceFactory(options) {
115+
if (!options) {
116+
throw new Error("Invalid datasource config");
117+
}
118+
return addTransactionalDataSource(new DataSource(options));
119+
},
112120
}),
113121
CacheModule.registerAsync({
114122
isGlobal: true,

src/auth/jwt-auth/jwt-auth.guard.ts

Lines changed: 12 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,10 @@ import * as JsonWebToken from "jsonwebtoken";
88
import { JwtHeader } from "jsonwebtoken";
99
import jwksClient from "jwks-rsa";
1010
import * as process from "process";
11-
import { Reflector } from "@nestjs/core";
11+
import { Request } from "express";
1212

1313
/**
14-
* Jwt based auth guard. Checks for valid JWT token which is signed by another service/microservice.
15-
* Should be used for microservice communication.
14+
* Jwt based auth guard. Can be used for microservice-microservice communication, or for websockets.
1615
*/
1716
@Injectable()
1817
export class JwtAuthGuard implements CanActivate {
@@ -22,8 +21,6 @@ export class JwtAuthGuard implements CanActivate {
2221
jwksUri: this.JWKS_URI,
2322
});
2423

25-
constructor(private readonly reflector: Reflector) {}
26-
2724
/**
2825
* @param jwtHeader - JWT header, from the decoded token
2926
* @private
@@ -39,35 +36,24 @@ export class JwtAuthGuard implements CanActivate {
3936
}
4037
}
4138

42-
/**
43-
* This same logic should be applied to all services/microservices.
44-
* @param context
45-
*/
4639
async canActivate(context: ExecutionContext): Promise<boolean> {
47-
const ctx = context.switchToHttp();
48-
const ctxType = context.getType<"http" | "rmq">();
40+
const ctxType = context.getType<"http" | "ws" | "rpc">();
4941

5042
if (ctxType !== "http") {
51-
this.logger.warn(
52-
`Warning: JwtAuthGuard can't be used in a non-HTTP context!`,
43+
throw new Error(
44+
`JwtAuthGuard not configured for context: ${ctxType}`,
5345
);
54-
55-
return true;
5646
}
5747

58-
const isPublic = this.reflector.get<boolean>(
59-
"isPublic",
60-
context.getHandler(),
61-
);
48+
const request: Request = context.switchToHttp().getRequest();
6249

63-
if (isPublic) {
64-
return true;
65-
}
50+
return this.validateToken(request.headers.authorization);
51+
}
52+
53+
private async validateToken(token: string | undefined) {
54+
const bearerToken = token?.split("Bearer ")[1];
6655

67-
const headers = ctx.getRequest().headers;
68-
const authorization = headers.authorization as string;
69-
const bearerToken = authorization?.split("Bearer ")[1];
70-
if (!authorization || !bearerToken) {
56+
if (!token || !bearerToken) {
7157
return false;
7258
}
7359

src/auth/public.decorator.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { SetMetadata } from "@nestjs/common";
22

33
/**
4-
* When used with AuthGuard on controllers/methods, makes authentication optional. <br>
5-
* User session info will still be available if the user is logged in, but will be null if not!
4+
* When used with {@link AuthGuard} on controllers/methods, makes authentication optional. <br>
5+
* User session info will still be available if the user is logged in, but will be null if not.
66
* @see AuthGuard
77
* @constructor
88
*/

src/auth/session.decorator.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,26 @@
11
import { createParamDecorator, ExecutionContext } from "@nestjs/common";
2+
import { Socket } from "socket.io";
23

34
/**
45
* Retrieves session info for the current logged in user. <br>
5-
* Needs to be used with AuthGuard. <br>
6+
* Needs to be used with {@link AuthGuard} or {@link WsAuthGuard}. <br>
67
* If the 'Public' decorator is also used, the 'session' object will be null if the user is not logged in.
78
* @see AuthGuard
9+
* @see WsAuthGuard
810
* @see Public
911
*/
1012
export const Session = createParamDecorator(
1113
(data: unknown, ctx: ExecutionContext) => {
12-
const request = ctx.switchToHttp().getRequest();
13-
return request.session;
14+
const ctxType = ctx.getType();
15+
if (ctxType === "http") {
16+
const request = ctx.switchToHttp().getRequest();
17+
return request.session;
18+
}
19+
20+
if (ctxType === "ws") {
21+
const client: Socket = ctx.switchToWs().getClient();
22+
23+
return client.data.session;
24+
}
1425
},
1526
);

src/auth/ws-auth/WsAuthGuard.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { CanActivate, ExecutionContext, Logger } from "@nestjs/common";
2+
import { Socket } from "socket.io";
3+
import { WsException } from "@nestjs/websockets";
4+
import Session from "supertokens-node/recipe/session";
5+
6+
export class WsAuthGuard implements CanActivate {
7+
private logger = new Logger(WsAuthGuard.name);
8+
9+
async canActivate(context: ExecutionContext): Promise<boolean> {
10+
const ctxType = context.getType<"http" | "ws" | "rpc">();
11+
12+
if (ctxType !== "ws") {
13+
throw new WsException(
14+
"WsAuthGuard can only be used in Websockets context.",
15+
);
16+
}
17+
18+
const client: Socket = context.switchToWs().getClient();
19+
const token: string | undefined =
20+
client.handshake?.auth?.token || client.handshake?.query?.token;
21+
22+
if (!token) {
23+
throw new WsException("Auth token not provided.");
24+
}
25+
26+
try {
27+
// Adds session as a property so that it can be retrieved with @Session later.
28+
client.data.session =
29+
await Session.getSessionWithoutRequestResponse(token);
30+
} catch (err) {
31+
this.logger.error(err);
32+
throw new WsException(err.message);
33+
}
34+
35+
return true;
36+
}
37+
}

src/collections/collections-entries/collections-entries.controller.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import { CreateUpdateCollectionEntryDto } from "./dto/create-update-collection-e
2121
import { AuthGuard } from "../../auth/auth.guard";
2222
import { FindCollectionEntriesDto } from "./dto/find-collection-entries.dto";
2323
import { CollectionEntry } from "./entities/collection-entry.entity";
24-
import { CreateFavoriteStatusCollectionEntryDto } from "./dto/create-favorite-status-collection-entry.dto";
24+
import { CollectionEntryFavoriteStatusDto } from "./dto/collection-entry-favorite-status.dto";
2525
import { PaginationInterceptor } from "../../interceptor/pagination.interceptor";
2626

2727
import { CollectionEntriesPaginatedResponseDto } from "./dto/collection-entries-paginated-response.dto";
@@ -55,6 +55,14 @@ export class CollectionsEntriesController {
5555
);
5656
}
5757

58+
@Get(":id/related")
59+
@Public()
60+
async findRelatedEntries(@Param("id") collectionEntryId: string) {
61+
return await this.collectionsEntriesService.findRelatedEntries(
62+
collectionEntryId,
63+
);
64+
}
65+
5866
/**
5967
* Returns a specific collection entry based on game ID
6068
* @param session
@@ -117,7 +125,7 @@ export class CollectionsEntriesController {
117125
async changeFavoriteStatus(
118126
@Session() session: SessionContainer,
119127
@Param("id") gameId: number,
120-
@Body() dto: CreateFavoriteStatusCollectionEntryDto,
128+
@Body() dto: CollectionEntryFavoriteStatusDto,
121129
) {
122130
const userId = session.getUserId();
123131
return await this.collectionsEntriesService.changeFavoriteStatus(

src/collections/collections-entries/collections-entries.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { AchievementsModule } from "../../achievements/achievements.module";
99
import { LevelModule } from "../../level/level.module";
1010
import { CollectionsModule } from "../collections.module";
1111
import { CollectionEntryToCollection } from "./entities/collection-entry-to-collection.entity";
12+
import { GameRepositoryModule } from "../../game/game-repository/game-repository.module";
1213

1314
@Module({
1415
imports: [
@@ -21,6 +22,7 @@ import { CollectionEntryToCollection } from "./entities/collection-entry-to-coll
2122
AchievementsModule,
2223
LevelModule,
2324
forwardRef(() => CollectionsModule),
25+
GameRepositoryModule,
2426
],
2527
controllers: [CollectionsEntriesController],
2628
providers: [CollectionsEntriesService],

0 commit comments

Comments
 (0)