Skip to content

Commit 8a3aff7

Browse files
authored
feat: jwt authentication setup (#253)
1 parent 2871c4e commit 8a3aff7

10 files changed

Lines changed: 357 additions & 3 deletions

File tree

apps/backend/docker-compose.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ services:
99
POSTGRES_USER: ${POSTGRES_USER:-safeswap_user}
1010
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-safeswap_password}
1111
ports:
12-
- "${POSTGRES_PORT:-5432}:5432"
12+
- "${POSTGRES_PORT:-5433}:5432"
1313
volumes:
1414
- safeswap_data:/var/lib/postgresql/data
1515
restart: unless-stopped
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import {
2+
Body,
3+
Controller,
4+
Get,
5+
Post,
6+
Request,
7+
UseGuards,
8+
} from "@nestjs/common";
9+
import { AuthService } from "./auth.service";
10+
import { JwtAuthGuard } from "./guards/jwt-auth.guard";
11+
12+
@Controller("auth")
13+
export class AuthController {
14+
constructor(private readonly authService: AuthService) {}
15+
16+
@Post("token")
17+
getToken(@Body() body: { userId: string; walletAddress?: string }) {
18+
// This is a simplified version for testing only
19+
// In a real application, you'd validate the user or wallet signature
20+
const token = this.authService.signJwt({
21+
sub: body.userId,
22+
walletAddress: body.walletAddress,
23+
});
24+
25+
return { token };
26+
}
27+
28+
@UseGuards(JwtAuthGuard)
29+
@Get("profile")
30+
getProfile(@Request() req) {
31+
// The JWT payload is attached to the request by the JwtStrategy
32+
return {
33+
message: "You have successfully accessed a protected route!",
34+
user: req.user,
35+
};
36+
}
37+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { Module } from "@nestjs/common";
2+
import { ConfigModule, ConfigService } from "@nestjs/config";
3+
import { JwtModule } from "@nestjs/jwt";
4+
import { PassportModule } from "@nestjs/passport";
5+
import { AuthController } from "./auth.controller";
6+
import { AuthService } from "./auth.service";
7+
import { JwtStrategy } from "./strategies/jwt.strategy";
8+
9+
@Module({
10+
imports: [
11+
ConfigModule,
12+
PassportModule.register({ defaultStrategy: "jwt" }),
13+
JwtModule.registerAsync({
14+
imports: [ConfigModule],
15+
inject: [ConfigService],
16+
useFactory: async (configService: ConfigService) => ({
17+
secret: configService.get<string>("JWT_SECRET"),
18+
signOptions: {
19+
expiresIn: configService.get<string>("JWT_EXPIRATION") || "1h", // Default to 1 hour if not set,
20+
},
21+
}),
22+
}),
23+
],
24+
controllers: [AuthController],
25+
providers: [AuthService, JwtStrategy],
26+
})
27+
export class AuthModule {}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { Injectable } from "@nestjs/common";
2+
import { ConfigService } from "@nestjs/config";
3+
import { JwtService } from "@nestjs/jwt";
4+
5+
interface JwtPayload {
6+
sub: string;
7+
walletAddress?: string;
8+
}
9+
10+
@Injectable()
11+
export class AuthService {
12+
constructor(
13+
private jwtService: JwtService,
14+
private configService: ConfigService,
15+
) {}
16+
17+
/**
18+
* Signs a JWT token with the provided payload
19+
* @param payload Data to include in the JWT
20+
* @returns Signed JWT token
21+
*/
22+
23+
signJwt(payload: JwtPayload): string {
24+
return this.jwtService.sign(payload, {
25+
secret: this.configService.get<string>("JWT_SECRET"),
26+
expiresIn: this.configService.get<string>("JWT_EXPIRATION"),
27+
});
28+
}
29+
30+
// Future methods for wallet authentication will go here
31+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { Injectable, UnauthorizedException } from "@nestjs/common";
2+
import { AuthGuard } from "@nestjs/passport";
3+
4+
@Injectable()
5+
export class JwtAuthGuard extends AuthGuard("jwt") {}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export interface JwtPayload {
2+
sub: string; // User ID
3+
walletAddress?: string; // For future wallet integration
4+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { Injectable } from "@nestjs/common";
2+
import { ConfigService } from "@nestjs/config";
3+
import { PassportStrategy } from "@nestjs/passport";
4+
import { ExtractJwt, Strategy } from "passport-jwt";
5+
6+
interface JwtPayload {
7+
sub: string;
8+
walletAddress?: string;
9+
}
10+
11+
@Injectable()
12+
export class JwtStrategy extends PassportStrategy(Strategy) {
13+
constructor(private configService: ConfigService) {
14+
super({
15+
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
16+
ignoreExpiration: false,
17+
secretOrKey: configService.get<string>("JWT_SECRET"),
18+
});
19+
}
20+
21+
// This method will be called for each request with a valid JWT token
22+
async validate(payload: JwtPayload) {
23+
// This is a placeholder for future wallet validation logic
24+
// For now, simply return the payload which will be attached to the request object
25+
return payload;
26+
}
27+
}

apps/backend/src/core/core.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ import { IS_DEV_ENV } from "src/shared/utils/is-dev.util";
1111
import { getGraphQLConfig } from "./config/graphql.config";
1212
import { PrismaModule } from "./prisma/prisma.module";
1313

14+
import { AuthModule } from "src/auth/auth.module";
1415
@Module({
1516
imports: [
17+
AuthModule,
1618
ConfigModule.forRoot({
1719
ignoreEnvFile: !IS_DEV_ENV,
1820
isGlobal: true,

0 commit comments

Comments
 (0)