Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/backend/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ services:
POSTGRES_USER: ${POSTGRES_USER:-safeswap_user}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-safeswap_password}
ports:
- "${POSTGRES_PORT:-5432}:5432"
- "${POSTGRES_PORT:-5433}:5432"
volumes:
- safeswap_data:/var/lib/postgresql/data
restart: unless-stopped
Expand Down
37 changes: 37 additions & 0 deletions apps/backend/src/auth/auth.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import {
Body,
Controller,
Get,
Post,
Request,
UseGuards,
} from "@nestjs/common";
import { AuthService } from "./auth.service";
import { JwtAuthGuard } from "./guards/jwt-auth.guard";

@Controller("auth")
export class AuthController {
constructor(private readonly authService: AuthService) {}

@Post("token")
getToken(@Body() body: { userId: string; walletAddress?: string }) {
// This is a simplified version for testing only
// In a real application, you'd validate the user or wallet signature
const token = this.authService.signJwt({
sub: body.userId,
walletAddress: body.walletAddress,
});

return { token };
}

@UseGuards(JwtAuthGuard)
@Get("profile")
getProfile(@Request() req) {
// The JWT payload is attached to the request by the JwtStrategy
return {
message: "You have successfully accessed a protected route!",
user: req.user,
};
}
}
27 changes: 27 additions & 0 deletions apps/backend/src/auth/auth.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Module } from "@nestjs/common";
import { ConfigModule, ConfigService } from "@nestjs/config";
import { JwtModule } from "@nestjs/jwt";
import { PassportModule } from "@nestjs/passport";
import { AuthController } from "./auth.controller";
import { AuthService } from "./auth.service";
import { JwtStrategy } from "./strategies/jwt.strategy";

@Module({
imports: [
ConfigModule,
PassportModule.register({ defaultStrategy: "jwt" }),
JwtModule.registerAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: async (configService: ConfigService) => ({
secret: configService.get<string>("JWT_SECRET"),
signOptions: {
expiresIn: configService.get<string>("JWT_EXPIRATION") || "1h", // Default to 1 hour if not set,
},
}),
}),
],
controllers: [AuthController],
providers: [AuthService, JwtStrategy],
})
export class AuthModule {}
31 changes: 31 additions & 0 deletions apps/backend/src/auth/auth.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Injectable } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { JwtService } from "@nestjs/jwt";

interface JwtPayload {
sub: string;
walletAddress?: string;
}

@Injectable()
export class AuthService {
constructor(
private jwtService: JwtService,
private configService: ConfigService,
) {}

/**
* Signs a JWT token with the provided payload
* @param payload Data to include in the JWT
* @returns Signed JWT token
*/

signJwt(payload: JwtPayload): string {
return this.jwtService.sign(payload, {
secret: this.configService.get<string>("JWT_SECRET"),
expiresIn: this.configService.get<string>("JWT_EXPIRATION"),
});
}

// Future methods for wallet authentication will go here
}
5 changes: 5 additions & 0 deletions apps/backend/src/auth/guards/jwt-auth.guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { Injectable, UnauthorizedException } from "@nestjs/common";
import { AuthGuard } from "@nestjs/passport";

@Injectable()
export class JwtAuthGuard extends AuthGuard("jwt") {}
4 changes: 4 additions & 0 deletions apps/backend/src/auth/interfaces/jwt-payload.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface JwtPayload {
sub: string; // User ID
walletAddress?: string; // For future wallet integration
}
27 changes: 27 additions & 0 deletions apps/backend/src/auth/strategies/jwt.strategy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Injectable } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { PassportStrategy } from "@nestjs/passport";
import { ExtractJwt, Strategy } from "passport-jwt";

interface JwtPayload {
sub: string;
walletAddress?: string;
}

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(private configService: ConfigService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: configService.get<string>("JWT_SECRET"),
});
}

// This method will be called for each request with a valid JWT token
async validate(payload: JwtPayload) {
// This is a placeholder for future wallet validation logic
// For now, simply return the payload which will be attached to the request object
return payload;
}
}
2 changes: 2 additions & 0 deletions apps/backend/src/core/core.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ import { IS_DEV_ENV } from "src/shared/utils/is-dev.util";
import { getGraphQLConfig } from "./config/graphql.config";
import { PrismaModule } from "./prisma/prisma.module";

import { AuthModule } from "src/auth/auth.module";
@Module({
imports: [
AuthModule,
ConfigModule.forRoot({
ignoreEnvFile: !IS_DEV_ENV,
isGlobal: true,
Expand Down
126 changes: 63 additions & 63 deletions apps/backend/src/core/graphql/schema.gql
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,33 @@
# ------------------------------------------------------

type Category {
createdAt: DateTime!
id: ID!
imageUrl: String
name: String!
updatedAt: DateTime!
createdAt: DateTime!
id: ID!
imageUrl: String
name: String!
updatedAt: DateTime!
}

input CreateCategoryInput {
name: String!
name: String!
}

input CreateProductInput {
categoryId: String!
description: String
name: String!
price: Float!
slug: String!
categoryId: String!
description: String
name: String!
price: Float!
slug: String!
}

input CreateUserInput {
country: String!
email: String!
isSeller: Boolean! = false
name: String!
surname: String!
telegramUsername: String
walletAddress: String!
country: String!
email: String!
isSeller: Boolean! = false
name: String!
surname: String!
telegramUsername: String
walletAddress: String!
}

"""
Expand All @@ -38,66 +38,66 @@ A date-time string at UTC, such as 2019-12-03T09:54:33Z, compliant with the date
scalar DateTime

type Mutation {
createCategory(data: CreateCategoryInput!): Category!
createProduct(data: CreateProductInput!): ProductDTO!
createProductImage(createProductImage: ProductImageDTO!): ProductImage!
createUser(data: CreateUserInput!): User!
updateUser(data: UpdateUserInput!, walletAddress: String!): User!
createCategory(data: CreateCategoryInput!): Category!
createProduct(data: CreateProductInput!): ProductDTO!
createProductImage(createProductImage: ProductImageDTO!): ProductImage!
createUser(data: CreateUserInput!): User!
updateUser(data: UpdateUserInput!, walletAddress: String!): User!
}

type ProductDTO {
categoryId: String!
createdAt: DateTime!
description: String
id: ID!
name: String!
price: Float!
slug: String!
updatedAt: DateTime!
categoryId: String!
createdAt: DateTime!
description: String
id: ID!
name: String!
price: Float!
slug: String!
updatedAt: DateTime!
}

type ProductImage {
createdAt: DateTime!
id: ID!
imageUrl: String!
productId: String!
updatedAt: DateTime!
createdAt: DateTime!
id: ID!
imageUrl: String!
productId: String!
updatedAt: DateTime!
}

input ProductImageDTO {
imageUrl: String!
productId: String!
imageUrl: String!
productId: String!
}

type Query {
categories: [Category!]!
category(id: String!): Category
product(id: String!): ProductDTO
productImage(id: String!): ProductImage
productImages: [ProductImage!]!
products: [ProductDTO!]!
user(walletAddress: String!): User
users: [User!]!
categories: [Category!]!
category(id: String!): Category
product(id: String!): ProductDTO
productImage(id: String!): ProductImage
productImages: [ProductImage!]!
products: [ProductDTO!]!
user(walletAddress: String!): User
users: [User!]!
}

input UpdateUserInput {
country: String
email: String
isSeller: Boolean = false
name: String
surname: String
telegramUsername: String
walletAddress: String
country: String
email: String
isSeller: Boolean = false
name: String
surname: String
telegramUsername: String
walletAddress: String
}

type User {
country: String!
createdAt: DateTime!
email: String!
isSeller: Boolean!
name: String!
surname: String!
telegramUsername: String
updatedAt: DateTime!
walletAddress: String!
}
country: String!
createdAt: DateTime!
email: String!
isSeller: Boolean!
name: String!
surname: String!
telegramUsername: String
updatedAt: DateTime!
walletAddress: String!
}
Loading