Skip to content

Commit 3bd13f9

Browse files
committed
feat: new api endpoint for user edit own data is added
1 parent 7feca0a commit 3bd13f9

File tree

5 files changed

+99
-23
lines changed

5 files changed

+99
-23
lines changed

docs/api/index.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,30 @@ Get the current user's profile information.
204204
**Error Responses:**
205205
- `401 Unauthorized`: No authentication token or invalid token
206206

207+
### Update Profile
208+
209+
**Endpoint:** `PATCH /api/nuxt-users/me`
210+
211+
Update the current user's profile information (e.g., name, email).
212+
213+
**Request Body:**
214+
```json
215+
{
216+
"name": "Johnathan Doe",
217+
"email": "john.doe@new-email.com"
218+
}
219+
```
220+
221+
**Notes:**
222+
- Users cannot change their own `role` using this endpoint.
223+
224+
**Response:**
225+
Returns the updated user object.
226+
227+
**Error Responses:**
228+
- `400 Bad Request`: Invalid data, such as an email that is already taken.
229+
- `401 Unauthorized`: No authentication token or invalid token
230+
207231
### Update Password
208232

209233
**Endpoint:** `PATCH /api/nuxt-users/password`

src/module.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,12 @@ export default defineNuxtModule<RuntimeModuleOptions>({
130130
handler: resolver.resolve('./runtime/server/api/nuxt-users/me.get')
131131
})
132132

133+
addServerHandler({
134+
route: `${base}/me`,
135+
method: 'patch',
136+
handler: resolver.resolve('./runtime/server/api/nuxt-users/me.patch')
137+
})
138+
133139
// Password
134140
addServerHandler({
135141
route: `${base}/password`,
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { createError, defineEventHandler, readBody } from 'h3'
2+
import type { ModuleOptions } from '../../../../types'
3+
import { useRuntimeConfig } from '#imports'
4+
import { updateUser } from '../../utils/user'
5+
6+
export default defineEventHandler(async (event) => {
7+
const { nuxtUsers } = useRuntimeConfig()
8+
const options = nuxtUsers as ModuleOptions
9+
10+
// The user is authenticated by the server middleware and available in the event context.
11+
const user = event.context.user
12+
13+
if (!user) {
14+
throw createError({
15+
statusCode: 401,
16+
statusMessage: 'Unauthorized'
17+
})
18+
}
19+
20+
const body = await readBody(event)
21+
22+
// Users should not be able to change their role via this endpoint.
23+
if (body.role) {
24+
delete body.role
25+
}
26+
27+
try {
28+
const updatedUser = await updateUser(user.id, body, options)
29+
return { user: updatedUser }
30+
}
31+
catch (error: unknown) {
32+
if (error instanceof Error) {
33+
throw createError({
34+
statusCode: 400,
35+
statusMessage: error.message
36+
})
37+
}
38+
throw createError({
39+
statusCode: 400,
40+
statusMessage: 'An unknown error occurred'
41+
})
42+
}
43+
})

src/runtime/server/api/nuxt-users/password/forgot.post.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { defineEventHandler, readBody, createError } from 'h3'
2-
import { sendPasswordResetLink } from '../../../services/password' // Adjusted path
2+
import { sendPasswordResetLink } from '../../../services/password'
33

44
export default defineEventHandler(async (event) => {
55
const body = await readBody(event)

src/runtime/server/utils/user.ts

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -124,34 +124,37 @@ export const updateUser = async (id: number, userData: Partial<User>, options: M
124124
const db = await useDb(options)
125125
const usersTable = options.tables.users
126126

127-
const currentUser = await findUserById(id, options)
128-
if (!currentUser) {
129-
throw new Error('User not found.')
127+
// Explicitly define which fields are allowed to be updated.
128+
// This prevents mass-assignment vulnerabilities.
129+
const allowedFields: (keyof User)[] = ['name', 'email', 'role']
130+
const updates: string[] = []
131+
const values: (string | number)[] = []
132+
133+
for (const field of allowedFields) {
134+
if (userData[field] !== undefined) {
135+
updates.push(`${field} = ?`)
136+
values.push(userData[field])
137+
}
130138
}
131139

132-
const fieldsToUpdate = { ...userData }
140+
// If no valid fields are provided, there's nothing to update.
141+
if (updates.length === 0) {
142+
const currentUser = await findUserById(id, options)
143+
if (!currentUser) {
144+
throw new Error('User not found.')
145+
}
146+
return currentUser
147+
}
133148

134-
// remove from update list
135-
delete fieldsToUpdate.id
136-
delete fieldsToUpdate.created_at
137-
delete fieldsToUpdate.updated_at
138-
delete fieldsToUpdate.last_login_at
139-
delete fieldsToUpdate.password // TODO: handle password update
149+
// Add the updated_at timestamp and the user ID for the WHERE clause
150+
updates.push('updated_at = CURRENT_TIMESTAMP')
151+
values.push(id)
140152

141-
if (Object.keys(fieldsToUpdate).length === 0) {
142-
return currentUser as UserWithoutPassword
143-
}
153+
// Use db.sql with template parts for dynamic column names
154+
const setClause = updates.map(update => update.replace(' = ?', '')).join(', ')
144155

145-
const dirtyFields = Object.keys(fieldsToUpdate).filter(key => currentUser[key as keyof UserWithoutPassword] != fieldsToUpdate[key as keyof typeof fieldsToUpdate])
156+
await db.sql`UPDATE {${usersTable}} SET {${setClause}} WHERE id = ${id}`
146157

147-
// Update each field individually for security
148-
for (const key of dirtyFields) {
149-
await db.sql`
150-
UPDATE {${usersTable}}
151-
SET {${key}} = ${fieldsToUpdate[key as keyof typeof fieldsToUpdate]}, updated_at = CURRENT_TIMESTAMP
152-
WHERE id = ${id}
153-
`
154-
}
155158
const updatedUser = await findUserById(id, options)
156159

157160
if (!updatedUser) {

0 commit comments

Comments
 (0)