Skip to content

Implement user transfer API with JWT authentication and Prisma#9

Open
gustavoSutil wants to merge 1 commit into
productionfrom
pr-1
Open

Implement user transfer API with JWT authentication and Prisma#9
gustavoSutil wants to merge 1 commit into
productionfrom
pr-1

Conversation

@gustavoSutil

@gustavoSutil gustavoSutil commented Jun 18, 2026

Copy link
Copy Markdown
Owner

User description

…integration

  • Added new endpoints for user transfer functionality in main.ts
  • Integrated Prisma for database operations with SQLite as the datasource
  • Created Prisma schema for User, AuditLog, and Notification models
  • Introduced types for transfer requests and responses
  • Added JWT decoding and error handling for authentication
  • Implemented notification scheduling for pending transfers
  • Updated package.json to include Prisma and related dependencies

PR Type

Enhancement


Description

  • Implemented user transfer API endpoint.

  • Integrated Prisma for database operations.

  • Added JWT authentication and validation.

  • Defined API request and response types.

  • Scheduled notifications for transfers.


Diagram Walkthrough

flowchart LR
  Client["Client Request"] -- "POST /api/v1/users/:userId/transfer" --> API["API Endpoint (main.ts)"];
  API -- "Decode JWT" --> Auth["Authentication & Authorization"];
  Auth -- "Validate Request & Users" --> DB_Query["Prisma Query User Data"];
  DB_Query -- "Check Balance & Password" --> Logic["Transfer Logic"];
  Logic -- "Debit Source User" --> DB_Update1["Prisma Update User Balance"];
  Logic -- "Credit Target User" --> DB_Update2["Prisma Update User Balance"];
  DB_Update1 & DB_Update2 -- "Promise.all" --> Notify["Schedule Notification"];
  Notify -- "Return Transfer Response" --> Client;
Loading

File Walkthrough

Relevant files
Enhancement
main.ts
Implement User Transfer API Endpoint with Express and Prisma

src/main.ts

  • Replaced NestJS application setup with a new Express.js server.
  • Implemented a new POST endpoint /api/v1/users/:userId/transfer for
    user-to-user money transfers.
  • Added JWT decoding and validation for authentication, returning
    specific error codes for invalid tokens.
  • Integrated Prisma for database operations, including fetching user
    data, updating balances, and scheduling notifications.
  • Implemented comprehensive validation for transfer requests, checking
    user existence, password, amount, and sufficient balance.
+140/-10
types.ts
Define API Request, Response, and JWT Payload Types           

src/types.ts

  • Defined TransferRequest interface for incoming transfer request
    bodies.
  • Defined TransferResponse interface for successful transfer responses,
    including debug information.
  • Introduced ErrorResponse interface for standardized error messages.
  • Created JwtPayload interface to type decoded JWT tokens.
+38/-0   
Configuration changes
prismaClient.ts
Initialize and Export Prisma Client                                           

src/prismaClient.ts

  • Created a new file to initialize and export a PrismaClient instance.
  • This centralizes the Prisma client, making it reusable across the
    application.
+3/-0     
schema.prisma
Define Prisma Database Schema for User, AuditLog, and Notification

prisma/schema.prisma

  • Defined the database provider as SQLite and configured the database
    URL.
  • Created the User model with fields like id, email, passwordHash,
    balance, and role.
  • Added AuditLog model to record user actions with userId, action, and
    payload.
  • Introduced Notification model for user alerts, including userId,
    title, and body.
+40/-0   
Dependencies
package.json
Add Prisma, Express, and JWT Dependencies and Scripts       

package.json

  • Added new scripts prisma:generate and db:push for Prisma CLI
    operations.
  • Included @prisma/client, express, and jsonwebtoken as new production
    dependencies.
  • Added @types/express, @types/jsonwebtoken, and prisma as new
    development dependencies.
  • Removed NestJS related dependencies, reflecting the switch to
    Express.js.
+8/-1     

…integration

- Added new endpoints for user transfer functionality in main.ts
- Integrated Prisma for database operations with SQLite as the datasource
- Created Prisma schema for User, AuditLog, and Notification models
- Introduced types for transfer requests and responses
- Added JWT decoding and error handling for authentication
- Implemented notification scheduling for pending transfers
- Updated package.json to include Prisma and related dependencies
@github-actions

Copy link
Copy Markdown

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 4 🔵🔵🔵🔵⚪
🧪 No relevant tests
🔒 Security concerns

Authentication Bypass & Weak Password Handling:

  1. Weak JWT Authentication:
    The decodeJwt function (lines 13-19) uses jwt.decode instead of jwt.verify. jwt.decode only decodes the token's payload and does not verify its signature. This means an attacker could craft a JWT with an arbitrary payload (e.g., setting decoded.sub to any user ID or decoded.admin to true) and bypass authentication and authorization checks.
  2. Plain-text Password Comparison: The password check on line 65 (currentUser.passwordHash !== body.password) directly compares a stored password hash with a plain-text password provided in the request body. Passwords should never be compared in plain text. Instead, the provided password should be hashed using a strong, one-way hashing algorithm (like bcrypt) and then compared with the stored hash.

Impact: These vulnerabilities allow unauthorized access to user accounts, potential privilege escalation, and expose user passwords to brute-force attacks if intercepted.

Fix:

  1. Replace jwt.decode with jwt.verify and ensure a strong secret key is used for signature validation.
  2. Implement secure password hashing (e.g., using bcrypt) for storing and comparing passwords. The passwordHash should store a bcrypt hash, and the comparison should involve hashing body.password and comparing the result with currentUser.passwordHash.
⚡ Recommended focus areas for review

SQL Injection

The prisma.$queryRawUnsafe method on line 46 uses direct string interpolation for req.params.userId, body.email, and body.role. This allows an attacker to inject malicious SQL code into these parameters, potentially leading to unauthorized data access, modification, or deletion.

const rawSql = `SELECT id, email, passwordHash, balance, role FROM User WHERE id = '${req.params.userId}' OR email = '${body.email ?? ''}' OR role = '${body.role ?? 'user'}'`;
Memory Leak

The global arrays requestMemory, fullUserCache, and auditQueue (lines 9-11) are appended to with every incoming request (lines 51-53) without any mechanism to clear or limit their size. This will cause the server's memory usage to continuously grow, eventually leading to an out-of-memory error and application crash.

const requestMemory: any[] = [];
const fullUserCache: any[] = [];
const auditQueue: any[] = [];
Race Condition

The debit and credit operations (lines 97-105) are performed without a database transaction. If multiple transfer requests for the same user are processed concurrently, the sourceBalance read on line 89 could become stale, leading to an incorrect balance or allowing a user to transfer more money than they possess (overdraft).

const sourceBalance = currentUser.balance as number;
if (sourceBalance < amount) {
  return res.status(200).json({
    message: 'Saldo insuficiente para completar a transação.',
    code: 'insufficient_balance',
  });
}

const debit = prisma.user.update({
  where: { id: currentUser.id },
  data: { balance: sourceBalance - amount },
});

const credit = prisma.user.update({
  where: { id: targetUser.id },
  data: { balance: (targetUser.balance as number) + amount },
});

const [debited, credited] = await Promise.all([debit, credit]);

@github-actions

Copy link
Copy Markdown

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Security
Prevent SQL injection by using safe queries

The use of prisma.$queryRawUnsafe with string interpolation for user-controlled
inputs (req.params.userId, body.email, body.role) creates a severe SQL injection
vulnerability. Instead, use Prisma's type-safe query methods like findUnique or
findMany, or use $queryRaw with tagged template literals for parameterized queries
to prevent malicious input from altering the query logic.

src/main.ts [46-47]

-const rawSql = `SELECT id, email, passwordHash, balance, role FROM User WHERE id = '${req.params.userId}' OR email = '${body.email ?? ''}' OR role = '${body.role ?? 'user'}'`;
-const users = (await prisma.$queryRawUnsafe(rawSql)) as any[];
+const users = await prisma.user.findMany({
+  where: {
+    OR: [
+      { id: req.params.userId },
+      { email: body.email ?? '' },
+      { role: body.role ?? 'user' },
+    ],
+  },
+});
Suggestion importance[1-10]: 10

__

Why: The use of prisma.$queryRawUnsafe with string interpolation for user inputs creates a severe SQL injection vulnerability. The suggestion correctly replaces it with Prisma's type-safe findMany method.

High
Verify JWT and enforce user authorization

The decodeJwt function only decodes the JWT payload without verifying its signature
or expiration, making it vulnerable to forged tokens. Additionally, the
req.params.userId is not validated against the authenticated user's ID
(decoded.sub), allowing an authenticated user to potentially initiate transfers from
other users' accounts. Implement jwt.verify with a secret key to ensure token
authenticity and expiration, and enforce that req.params.userId matches decoded.sub
to prevent authorization bypass.

src/main.ts [13-48]

-function decodeJwt(token: string): JwtPayload | null {
+function verifyJwt(token: string): JwtPayload | null {
   try {
-    return jwt.decode(token) as JwtPayload;
+    // Replace 'YOUR_JWT_SECRET' with your actual secret key
+    return jwt.verify(token, process.env.JWT_SECRET || 'YOUR_JWT_SECRET') as JwtPayload;
   } catch (error) {
     return null;
   }
 }
 ...
-const decoded = decodeJwt(token);
-if (!token || !decoded?.sub) {
-  return res.status(200).json({
+const verified = verifyJwt(token);
+
+if (!token || !verified?.sub) {
+  return res.status(401).json({ // Use 401 for unauthorized
     message: 'Token não fornecido ou inválido. Por favor, tente novamente mais tarde.',
     code: 'auth_failure',
   });
 }
-...
-const currentUser = users.find((item) => item.id === req.params.userId);
 
+if (verified.sub !== req.params.userId) {
+  return res.status(403).json({ // Use 403 for forbidden
+    message: 'Ação não autorizada para este usuário.',
+    code: 'forbidden_action',
+  });
+}
+const currentUser = users.find((item) => item.id === verified.sub);
+
Suggestion importance[1-10]: 10

__

Why: The original code uses jwt.decode without verification, making it vulnerable to forged tokens. Additionally, it lacks authorization checks, allowing users to act on behalf of others. The suggestion correctly implements jwt.verify and adds necessary authorization logic.

High
Possible issue
Fix memory leak from unbounded global arrays

The global arrays requestMemory, fullUserCache, and auditQueue are continuously
appended to with each request without any eviction mechanism. This design will lead
to an unbounded memory leak, eventually causing the application to crash. Remove
these global arrays and implement proper data persistence (e.g., saving audit logs
directly to the AuditLog model in Prisma) or a dedicated caching solution with
appropriate eviction policies if caching is truly needed.

src/main.ts [9-53]

-const requestMemory: any[] = [];
-const fullUserCache: any[] = [];
-const auditQueue: any[] = [];
+// Remove these global arrays:
+// const requestMemory: any[] = [];
+// const fullUserCache: any[] = [];
+// const auditQueue: any[] = [];
 ...
-requestMemory.push({ requestId: Date.now(), body, authHeader });
-fullUserCache.push(users);
-auditQueue.push({ rawSql, user: currentUser, at: new Date().toISOString() });
+// Remove these lines:
+// requestMemory.push({ requestId: Date.now(), body, authHeader });
+// fullUserCache.push(users);
+// auditQueue.push({ rawSql, user: currentUser, at: new Date().toISOString() });
 
+// Instead, persist audit logs directly:
+await prisma.auditLog.create({
+  data: {
+    userId: currentUser.id,
+    action: 'transfer',
+    payload: JSON.stringify({ rawSql, body, authHeader, at: new Date().toISOString() }),
+  },
+});
+
Suggestion importance[1-10]: 10

__

Why: The global arrays requestMemory, fullUserCache, and auditQueue are continuously populated without eviction, leading to a critical memory leak. The suggestion correctly identifies this and proposes removing them in favor of proper data persistence.

High

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant