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
5 changes: 3 additions & 2 deletions src/controllers/member.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export const createAMember =
(supabase: SupabaseClient) => async (req: Request, res: Response) => {
const {email, name, password, passoutYear, provider} = req.body;

if (!email || !name || !password || !passoutYear) {
if (!email || !name || !password || !passoutYear || !provider) {

@coderabbitai coderabbitai Bot Aug 4, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Enhanced validation looks good.

The addition of provider validation is consistent with the existing pattern and strengthens the member creation flow.

Verify that all API clients and tests include the provider field when calling this endpoint:


🏁 Script executed:

#!/bin/bash
# Description: Search for createAMember API calls to ensure they include provider field
# Expected: All calls should include provider field

echo "Searching for createAMember API calls..."
rg -A 10 -B 5 "createAMember|/members.*POST" --type js --type ts --type json

echo -e "\nSearching for member creation requests..."
rg -A 15 -B 5 "email.*name.*password.*passoutYear" --type js --type ts

Length of output: 6644


Tests and API docs must be updated for the new provider validation

The added provider—and previously existing passoutYear—fields now break the createAMember tests and out-of-date JSDoc. Please:

• tests/Member.test.ts
– Include both passoutYear and provider in the req.body fixtures for your createAMember tests.
– Example diff:

it('should respond with 201 and created member', async () => {
- const req = {
-   body: {
-     email: 'shruti@example.com',
-     name: 'Shruti',
-     password: 'password123',
-   }
- } as Request;
+ const req = {
+   body: {
+     email: 'shruti@example.com',
+     name: 'Shruti',
+     password: 'password123',
+     passoutYear: 2025,
+     provider: 'local',
+   }
+ } as Request;

• src/routes/members.ts
– Update your route JSDoc to surface the new parameters:

 * @apiBody {String} [name] Full name of the member.
+ * @apiBody {Number} passoutYear Graduation year of the member.
+ * @apiBody {String} provider  Authentication provider for the member.
 * @apiBody {File} [file]   Profile photo file (field name: "file").

Once tests and docs are aligned with the controller’s required fields, the build will pass and the validation enhancement remains effective.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (!email || !name || !password || !passoutYear || !provider) {
// tests/Member.test.ts
it('should respond with 201 and created member', async () => {
const req = {
body: {
email: 'shruti@example.com',
name: 'Shruti',
password: 'password123',
passoutYear: 2025,
provider: 'local',
}
} as Request;
const res = await createAMember(req, resStub);
expect(res.status).toHaveBeenCalledWith(201);
// …other assertions…
});
Suggested change
if (!email || !name || !password || !passoutYear || !provider) {
// src/routes/members.ts
/**
* @apiBody {String} [name] Full name of the member.
* @apiBody {Number} passoutYear Graduation year of the member.
* @apiBody {String} provider Authentication provider for the member.
* @apiBody {File} [file] Profile photo file (field name: "file").
*/
🤖 Prompt for AI Agents
In src/controllers/member.controller.ts at line 49, the validation now requires
'provider' and 'passoutYear' fields, which breaks existing tests and outdated
JSDoc. To fix this, update tests/Member.test.ts to include 'provider' and
'passoutYear' in the req.body fixtures for createAMember tests, and modify
src/routes/members.ts to add these new parameters in the route JSDoc. This will
align tests and documentation with the controller's validation requirements and
ensure the build passes.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

throw new ApiError("Required fields absent", 400);
}
Comment on lines +49 to 51

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Don't require password for OAuth signups; refine validation to depend on provider.

Requiring password unconditionally breaks Google/GitHub signups. Require it only when provider === "credentials".

Apply this diff:

-    if (!email || !name || !password || !passoutYear || !provider) {
+    if (!email || !name || !passoutYear || !provider || (provider === "credentials" && !password)) {
       throw new ApiError("Required fields absent", 400);
     }

Run to verify no callers use unsupported provider values (e.g., "local") and that tests align with status code 400:


🏁 Script executed:

#!/bin/bash
# Check for unsupported provider literals like "local"
rg -n "provider\\s*:\\s*['\"]local['\"]" -S --glob '!node_modules' .

# Check for tests expecting status 402 for 'Required fields absent'
rg -n "Required fields absent" --glob '!node_modules' tests | sed 's/:/ /' | while read -r f l _; do sed -n "${l}p" "$f"; done

Length of output: 276


🏁 Script executed:

#!/bin/bash
# Search for any code comparing provider to "local"
rg -nE 'provider\s*===?\s*["'"'"']local["'"'"']' -S --glob '!node_modules' .

# Show tests that reference the ApiError message "Required fields absent" with context
rg -n -A2 "Required fields absent" -S --glob '!node_modules' tests/

Length of output: 395


Refine password validation and align test expectations

Update the signup validation so that password is only required when provider === "credentials", and adjust your tests to expect a 400 error (not 402) for missing fields.

• In src/controllers/member.controller.ts (around lines 49–51):

-    if (!email || !name || !password || !passoutYear || !provider) {
+    if (
+      !email ||
+      !name ||
+      !passoutYear ||
+      !provider ||
+      (provider === "credentials" && !password)
+    ) {
       throw new ApiError("Required fields absent", 400);
     }

• In tests/Member.test.ts (line 68):

-  await expect(handler(req, res)).rejects.toThrow(new ApiError('Required fields absent', 402));
+  await expect(handler(req, res)).rejects.toThrow(new ApiError('Required fields absent', 400));

No references to a "local" provider were found in the codebase.

🤖 Prompt for AI Agents
In src/controllers/member.controller.ts around lines 49 to 51, the signup
validation currently requires password unconditionally and throws ApiError(...,
400) should be used for missing fields; change the validation so password is
only required when provider === "credentials" (i.e., require email, name,
passoutYear, provider always; require password only if provider ===
"credentials"), and ensure the thrown ApiError uses status code 400 for missing
fields; also update tests/Member.test.ts (line 68) to expect new
ApiError('Required fields absent', 400) instead of 402.


Expand Down Expand Up @@ -89,7 +89,8 @@ export const updateAMember =
}
if (imageUrl) parsedBody.profilePhoto = imageUrl;

await memberService.updateMember(memberId, parsedBody);
if(parsedBody.password) await memberService.updatePassword(memberId, parsedBody.password);
else await memberService.updateMember(memberId, parsedBody);
Comment on lines +92 to +93

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Password-only branch discards other field updates; update both.

If payload includes password plus other fields, the non-password fields are ignored. Update password and then update remaining fields.

Apply this diff:

-    if(parsedBody.password) await memberService.updatePassword(memberId, parsedBody.password);
-    else await memberService.updateMember(memberId, parsedBody);
+    const { password, ...rest } = parsedBody;
+    if (password) {
+      await memberService.updatePassword(memberId, password);
+    }
+    if (Object.keys(rest).length) {
+      await memberService.updateMember(memberId, rest);
+    }

For stronger consistency, consider wrapping both updates in a single service method using a Prisma transaction to make the operation atomic.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if(parsedBody.password) await memberService.updatePassword(memberId, parsedBody.password);
else await memberService.updateMember(memberId, parsedBody);
// ... around line 92 in src/controllers/member.controller.ts
- if(parsedBody.password) await memberService.updatePassword(memberId, parsedBody.password);
- else await memberService.updateMember(memberId, parsedBody);
+ const { password, ...rest } = parsedBody;
+ if (password) {
+ await memberService.updatePassword(memberId, password);
+ }
+ if (Object.keys(rest).length) {
+ await memberService.updateMember(memberId, rest);
+ }
// ... following code
🤖 Prompt for AI Agents
In src/controllers/member.controller.ts around lines 92-93, the current branch
returns early when parsedBody.password exists and thus ignores other fields;
change the flow to always apply non-password updates plus the password: if
parsedBody.password is present, call memberService.updatePassword(memberId,
parsedBody.password) and then call memberService.updateMember(memberId,
parsedBodyWithoutPassword) (ensure you strip password from the second call),
otherwise just call updateMember as before. Alternatively (preferred), add a new
service method (e.g., updateMemberWithPassword) that accepts memberId and the
full payload and performs both updates inside a single Prisma transaction to
make the operation atomic and avoid partial updates.


const updatedData = await memberService.getDetails(memberId);
res
Expand Down
131 changes: 91 additions & 40 deletions src/routes/members.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ export default function membersRouter(
* @apiGroup Member
*
* @apiSuccess {Object[]} unapprovedMembers List of unapproved members.
*
* @apiExample {curl} Example usage:
* curl -X GET http://localhost:3000/members/unapproved
*/
router.get("/unapproved", memberCtrl.getUnapprovedMembers);

Expand All @@ -27,6 +30,9 @@ export default function membersRouter(
*
* @apiSuccess {Object} user Member object.
* @apiError (Error 400) BadRequest No memberId provided.
*
* @apiExample {curl} Example usage:
* curl -X GET http://localhost:3000/members/123
*/
router.get("/:memberId", memberCtrl.getUserDetails);

Expand All @@ -35,17 +41,23 @@ export default function membersRouter(
* @apiName ListAllApprovedMembers
* @apiGroup Member
*
* @apiDescription
* @apiDescription
* - Returns a list of all approved members if no email query parameter is provided.
* - If `email` query parameter is provided, returns the member associated with that email.
*
* @apiQuery {String} [email] Optional email to fetch a specific member.
* @apiQuery {String} [email] Optional email to fetch a specific member.
*
* @apiSuccess {Object} user Single user object when email provided.
+ * @apiSuccess {Object[]} user Array of approved members when no email provided.
* @apiSuccess {Object[]} user Array of approved members when no email provided.
* @apiSuccess {String} [message] Message in case of full list fetch.
*
* @apiError (400) IncorrectEmail The provided email does not match any user.
*
* @apiExample {curl} Example usage (list all):
* curl -X GET http://localhost:3000/members
*
* @apiExample {curl} Example usage (get by email):
* curl -X GET "http://localhost:3000/members?email=john@example.com"
*/
router.get("/", memberCtrl.listAllApprovedMembers);

Expand All @@ -54,14 +66,27 @@ export default function membersRouter(
* @apiName CreateAMember
* @apiGroup Member
*
* @apiBody {String} email Email of the member.
* @apiBody {String} name Full name of the member.
* @apiBody {String} password Member's password.
* @apiBody {String} passoutYear Graduation year.
* @apiBody {String} imageUrl profile photo of the member.
* @apiBody {String} email Email of the member. (Required)
* @apiBody {String} name Full name of the member. (Required)
* @apiBody {String} password Member's password. (Required)
* @apiBody {String} passoutYear Graduation year (Required, e.g., "2026").
* @apiBody {String} provider Authentication provider (Required, e.g., "local", "google").
* @apiBody {File} [file] Profile photo file (field name: "file").
*
* @apiSuccess {Boolean} success Request status.
* @apiSuccess {Object} user Created member object.
*
* @apiSuccess {Object} user Created member object.
* @apiError (Error 402) ValidationError Required fields missing.
* @apiError (Error 400) ApiError Required fields absent.
* @apiError (Error 500) ServerError Error creating user.
*
* @apiExample {curl} Example usage:
* curl -X POST -F "file=@profile.jpg" \
* -F "email=john@example.com" \
* -F "name=John Doe" \
* -F "password=securePass123" \
* -F "passoutYear=2026" \
* -F "provider=local" \
* http://localhost:3000/members
*/
router.post("/", upload.single("file"), memberCtrl.createAMember(supabase));

Expand All @@ -72,64 +97,87 @@ export default function membersRouter(
*
* @apiParam {String} memberId Member's unique ID.
*
* @apiBody {String} memberData JSON string containing the member's updated details.
* @apiBody {File} [file] Profile photo file (field name: "file").
* @apiBody {String} [name] Full name of the member.
* @apiBody {String} [email] Email address.
* @apiBody {String} [phone] Phone number.
* @apiBody {String} [bio] Short bio.
* @apiBody {String} [github] GitHub handle.
* @apiBody {String} [linkedin] LinkedIn handle.
* @apiBody {String} [twitter] Twitter handle.
* @apiBody {String} [geeksforgeeks] GeeksforGeeks username.
* @apiBody {String} [leetcode] LeetCode username.
* @apiBody {String} [codechef] CodeChef username.
* @apiBody {String} [codeforces] Codeforces username.
* @apiBody {Date} [passoutYear] Graduation year (ISO string format).
*
* @apiSuccess {Object} member Updated member object.
*
* @apiBody (memberData fields) {String} [name] Full name of the member.
* @apiBody (memberData fields) {String} [email] Email address.
* @apiBody (memberData fields) {String} [phone] Phone number.
* @apiBody (memberData fields) {String} [bio] Short bio.
* @apiBody (memberData fields) {String} [github] GitHub handle.
* @apiBody (memberData fields) {String} [linkedin] LinkedIn handle.
* @apiBody (memberData fields) {String} [twitter] Twitter handle.
* @apiBody (memberData fields) {String} [geeksforgeeks] GeeksforGeeks username.
* @apiBody (memberData fields) {String} [leetcode] LeetCode username.
* @apiBody (memberData fields) {String} [codechef] CodeChef username.
* @apiBody (memberData fields) {String} [codeforces] Codeforces username.
* @apiBody (memberData fields) {Date} [passoutYear] Graduation year (ISO string format).
* @apiBody (memberData fields) {String} [profilePhoto] (Auto-assigned if a new file is uploaded).
*
* @apiSuccess {Boolean} success Request status.
* @apiSuccess {Object} user Updated member object.
*
* @apiError (Error 400) ApiError No memberId provided or invalid request data.
* @apiError (Error 404) NotFound Member not found.
* @apiError (Error 400) ValidationError Invalid or missing fields.
* @apiError (Error 500) ServerError Unexpected error occurred during update.
*
* @apiExample {curl} Example usage:
* curl -X PATCH -F "file=@profile.jpg" \
* -F 'memberData={"name":"John Doe","email":"john@example.com"}' \
* http://localhost:3000/members/123
*/
router.patch(
"/:memberId",
upload.single("file"),
memberCtrl.updateAMember(supabase),
);

/**
* @api {patch} /members/approve/:memberId Approve/reject a member

/**
* @api {patch} /members/approve/:memberId Approve a member
* @apiName UpdateApprovalRequest
* @apiGroup Member
*
* @apiParam (URL Params) {String} memberId Member ID.
* @apiBody {Boolean} isApproved Approval status.
* @apiBody {String} adminId Admin who approved.
* @apiBody {Boolean} isApproved Approval status (true = approved, false = rejected).
* @apiBody {String} adminId ID of the admin who approved/rejected.
*
* @apiSuccess {Object} update Approval status updated.
* @apiSuccess {Object} update Approval status update result.
* @apiError (Error 400) BadRequest Missing required fields.
*
* @apiExample {curl} Example usage:
* curl -X PATCH http://localhost:3000/members/approve/123 \
* -H "Content-Type: application/json" \
* -d '{"isApproved": true, "adminId": "admin123"}'
*/
router.patch("/approve/:memberId", memberCtrl.updateRequest);

/**
* @api {get} /members/:memberId/achievements Get member's achievements
* @apiName GetUserAchievements
* @apiGroup Member
*
* @apiParam (URL Params) {String} memberId Member ID.
*
* @apiSuccess {Object[]} achievements List of achievements.
*/

/**
* @api {get} /members/:memberId/achievements Get member's achievements
* @apiName GetUserAchievements
* @apiGroup Member
*
* @apiParam (URL Params) {String} memberId Member ID.
*
* @apiSuccess {Object[]} achievements List of achievements.
*
* @apiExample {curl} Example usage:
* curl -X GET http://localhost:3000/members/123/achievements
*/
router.get("/:memberId/achievements", memberCtrl.getUserAchievements);

/**
* @api {get} /api/members/:memberId/projects Get member's projects
* @api {get} /members/:memberId/projects Get member's projects
* @apiName GetUserProjects
* @apiGroup Member
*
* @apiParam (URL Params) {String} memberId Member ID.
*
* @apiSuccess {Object[]} projects List of projects.
*
* @apiExample {curl} Example usage:
* curl -X GET http://localhost:3000/members/123/projects
*/
router.get("/:memberId/projects", memberCtrl.getUserProjects);

Expand All @@ -141,6 +189,9 @@ export default function membersRouter(
* @apiParam (URL Params) {String} memberId Member ID.
*
* @apiSuccess {Object[]} interviews List of interviews.
*
* @apiExample {curl} Example usage:
* curl -X GET http://localhost:3000/members/123/interviews
*/
router.get("/:memberId/interviews", memberCtrl.getUserInterviews);

Expand Down
30 changes: 16 additions & 14 deletions src/services/member.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,33 +66,35 @@ export const createMember = async (

export const updateMember = async (
id: string,
payload: UpdateMemberPayload,
payload: UpdateMemberPayload
) => {
Comment on lines +69 to 70

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Confirm UpdateMemberPayload shape across modules.

Ensure UpdateMemberPayload excludes password now that password changes go through updatePassword, or if it includes password?, ensure callers strip it before passing to updateMember (as per controller change).


🏁 Script executed:

#!/bin/bash
# Inspect UpdateMemberPayload for a 'password' property
rg -n "interface\\s+UpdateMemberPayload" -A 20 src --glob '!node_modules'

Length of output: 771


🏁 Script executed:

#!/bin/bash
# Show the updateMember method and how it handles the payload
rg -n "updateMember" -C 5 src/services/member.service.ts

Length of output: 246


Remove password from UpdateMemberPayload and strip it out in updateMember
The UpdateMemberPayload type in src/types/members.d.ts still defines an optional password? field (line 18). Since password changes now go through the dedicated updatePassword flow, please:

  • In src/types/members.d.ts, remove the password?: string line from the UpdateMemberPayload interface.
  • In src/services/member.service.ts’s updateMember function, explicitly destructure password so it’s not passed through (e.g. const { name, password, ...rest } = payload;).

This guarantees that updateMember cannot be used to change passwords.

🤖 Prompt for AI Agents
In src/services/member.service.ts around lines 69-70 and src/types/members.d.ts
(line ~18), remove the optional password field from the UpdateMemberPayload type
in members.d.ts and update updateMember to strip password from the incoming
payload; specifically delete the password?: string line in the interface, and in
updateMember destructure the payload to extract password (e.g. const { password,
...rest } = payload) so the password is not forwarded or used when updating a
member.

const { name, ...rest } = payload;

const member = await prisma.member.findUnique({
where: {
id: id,
},
});

if (!member) {
throw new ApiError("Member not found", 404);
}

const dataToUpdate = Object.fromEntries(
Object.entries(rest).filter(([_, v]) => v !== undefined),
Object.entries({ name, ...rest }).filter(([_, v]) => v !== undefined)
);

if (JSON.stringify(dataToUpdate) === "{}")
throw new ApiError("No fields passed", 400);
if (JSON.stringify(dataToUpdate) === "{}") throw new ApiError("No fields passed", 400);

return await prisma.member.update({
where: { id },
data: dataToUpdate,
});
};

export const updatePassword = async(id: string, password: string) => {
const account = await prisma.account.findFirst({
where: { memberId: id },
});

if (!account) throw new ApiError("Associated account not found", 404);

return await prisma.account.update({
where: { id: account.id },
data: { password },
});
}
Comment on lines +85 to +96

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Hash passwords and block password updates for non-credentials accounts.

updatePassword stores plaintext and allows updates for OAuth accounts. Both are security issues.

Apply this diff to enforce provider and hash the password:

 export const updatePassword = async(id: string, password: string) => {
   const account = await prisma.account.findFirst({
       where: { memberId: id },
     });
 
     if (!account) throw new ApiError("Associated account not found", 404);
 
-    return await prisma.account.update({
+    if (account.provider !== "credentials") {
+      throw new ApiError("Password updates not allowed for OAuth accounts", 400);
+    }
+
+    const hashed = await bcrypt.hash(password, 12);
+    return await prisma.account.update({
       where: { id: account.id },
-      data: { password }, 
+      data: { password: hashed }, 
     });
 }

Add the import (outside this hunk):

import bcrypt from "bcryptjs";

To ensure no other code paths write plaintext passwords, search for raw password assignments:


🏁 Script executed:

#!/bin/bash
rg -n "password\\s*:\\s*password\\b" --glob '!node_modules' .
rg -n "argon2|bcrypt" --glob '!node_modules' .

Length of output: 109


🏁 Script executed:

#!/bin/bash
# Find all prisma.account.create/update calls to see where password fields may be set
rg -n 'prisma\.account\.(create|update)' --glob '!node_modules' -A3 .

Length of output: 563


🏁 Script executed:

#!/bin/bash
# Show full prisma.account.create call to check for password assignments
rg -n 'prisma\.account\.create' --glob '!node_modules' -A10 src/services/member.service.ts

Length of output: 346


Enforce password hashing at creation and restrict updates to credentials-only accounts

The current implementation stores plaintext passwords on signup and allows password changes for OAuth users. To address both issues:

• Add bcrypt import at the top of src/services/member.service.ts
• On account creation (around line 55), hash the password when provider === "credentials"
• In updatePassword (around line 85), block non-credentials providers and hash the new password

Diff for src/services/member.service.ts:

--- a/src/services/member.service.ts
+++ b/src/services/member.service.ts
@@
+ import bcrypt from "bcryptjs";
 
 // ...signup or createMember function...
- await prisma.account.create({
-   data: {
-     provider,
-     providerAccountId: email,
-     password: provider === "credentials" ? password : null,
-     memberId: newMember.id,
-   },
- });
+ const hashedPassword = provider === "credentials"
+   ? await bcrypt.hash(password, 12)
+   : null;
+ await prisma.account.create({
+   data: {
+     provider,
+     providerAccountId: email,
+     password: hashedPassword,
+     memberId: newMember.id,
+   },
+ });
 
 // ...later in the file...
 export const updatePassword = async (id: string, password: string) => {
   const account = await prisma.account.findFirst({
     where: { memberId: id },
   });
 
   if (!account) throw new ApiError("Associated account not found", 404);
-
-  return await prisma.account.update({
-    where: { id: account.id },
-    data: { password },
-  });
+  if (account.provider !== "credentials") {
+    throw new ApiError("Password updates not allowed for OAuth accounts", 400);
+  }
+
+  const newHashed = await bcrypt.hash(password, 12);
+  return await prisma.account.update({
+    where: { id: account.id },
+    data: { password: newHashed },
+  });
 };

This ensures no plaintext passwords are stored and prevents OAuth accounts from updating passwords.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const updatePassword = async(id: string, password: string) => {
const account = await prisma.account.findFirst({
where: { memberId: id },
});
if (!account) throw new ApiError("Associated account not found", 404);
return await prisma.account.update({
where: { id: account.id },
data: { password },
});
}
// At the top of src/services/member.service.ts
import bcrypt from "bcryptjs";
// … other imports …
// …signup or createMember function…
export const createMember = async (
/* params */
) => {
// … prior logic …
- await prisma.account.create({
- data: {
- provider,
- providerAccountId: email,
- password: provider === "credentials" ? password : null,
- memberId: newMember.id,
- },
- });
+ const hashedPassword = provider === "credentials"
+ ? await bcrypt.hash(password, 12)
+ : null;
+ await prisma.account.create({
+ data: {
+ provider,
+ providerAccountId: email,
+ password: hashedPassword,
+ memberId: newMember.id,
+ },
+ });
// … remainder of createMember …
};
// …later in the file…
export const updatePassword = async (id: string, password: string) => {
const account = await prisma.account.findFirst({
where: { memberId: id },
});
if (!account) {
throw new ApiError("Associated account not found", 404);
}
- return await prisma.account.update({
- where: { id: account.id },
- data: { password },
- });
+ if (account.provider !== "credentials") {
+ throw new ApiError("Password updates not allowed for OAuth accounts", 400);
+ }
+
+ const newHashed = await bcrypt.hash(password, 12);
+ return await prisma.account.update({
+ where: { id: account.id },
+ data: { password: newHashed },
+ });
};
🤖 Prompt for AI Agents
In src/services/member.service.ts around lines 55 and 85-96, the code stores
plaintext passwords on signup and allows password updates for non-credentials
(OAuth) accounts; import bcrypt at the top, and when creating an account (around
line 55) only hash the password if provider === "credentials" (replace the
stored plaintext with the bcrypt hash), and in updatePassword (lines 85-96)
first fetch the account/provider, throw an ApiError if the provider is not
"credentials", then bcrypt.hash the new password before calling
prisma.account.update to persist the hashed password.


export const unapprovedMembers = async () => {
return await prisma.member.findMany({
where: { isApproved: false },
Expand Down
1 change: 1 addition & 0 deletions src/types/members.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@ declare global {
leetcode?: string;
codechef?: string;
codeforces?: string;
password?: string
}
}
Loading