feat: auth & admin
This commit is contained in:
@@ -16,80 +16,80 @@ Next.js documentation explicitly states: "Treat Server Actions with the same sec
|
||||
**Incorrect (no authentication check):**
|
||||
|
||||
```typescript
|
||||
'use server'
|
||||
'use server';
|
||||
|
||||
export async function deleteUser(userId: string) {
|
||||
// Anyone can call this! No auth check
|
||||
await db.user.delete({ where: { id: userId } })
|
||||
return { success: true }
|
||||
await db.user.delete({ where: { id: userId } });
|
||||
return { success: true };
|
||||
}
|
||||
```
|
||||
|
||||
**Correct (authentication inside the action):**
|
||||
|
||||
```typescript
|
||||
'use server'
|
||||
'use server';
|
||||
|
||||
import { verifySession } from '@/lib/auth'
|
||||
import { unauthorized } from '@/lib/errors'
|
||||
import { verifySession } from '@/lib/auth';
|
||||
import { unauthorized } from '@/lib/errors';
|
||||
|
||||
export async function deleteUser(userId: string) {
|
||||
// Always check auth inside the action
|
||||
const session = await verifySession()
|
||||
|
||||
const session = await verifySession();
|
||||
|
||||
if (!session) {
|
||||
throw unauthorized('Must be logged in')
|
||||
throw unauthorized('Must be logged in');
|
||||
}
|
||||
|
||||
|
||||
// Check authorization too
|
||||
if (session.user.role !== 'admin' && session.user.id !== userId) {
|
||||
throw unauthorized('Cannot delete other users')
|
||||
throw unauthorized('Cannot delete other users');
|
||||
}
|
||||
|
||||
await db.user.delete({ where: { id: userId } })
|
||||
return { success: true }
|
||||
|
||||
await db.user.delete({ where: { id: userId } });
|
||||
return { success: true };
|
||||
}
|
||||
```
|
||||
|
||||
**With input validation:**
|
||||
|
||||
```typescript
|
||||
'use server'
|
||||
'use server';
|
||||
|
||||
import { verifySession } from '@/lib/auth'
|
||||
import { z } from 'zod'
|
||||
import { verifySession } from '@/lib/auth';
|
||||
import { z } from 'zod';
|
||||
|
||||
const updateProfileSchema = z.object({
|
||||
userId: z.string().uuid(),
|
||||
name: z.string().min(1).max(100),
|
||||
email: z.string().email()
|
||||
})
|
||||
email: z.string().email(),
|
||||
});
|
||||
|
||||
export async function updateProfile(data: unknown) {
|
||||
// Validate input first
|
||||
const validated = updateProfileSchema.parse(data)
|
||||
|
||||
const validated = updateProfileSchema.parse(data);
|
||||
|
||||
// Then authenticate
|
||||
const session = await verifySession()
|
||||
const session = await verifySession();
|
||||
if (!session) {
|
||||
throw new Error('Unauthorized')
|
||||
throw new Error('Unauthorized');
|
||||
}
|
||||
|
||||
|
||||
// Then authorize
|
||||
if (session.user.id !== validated.userId) {
|
||||
throw new Error('Can only update own profile')
|
||||
throw new Error('Can only update own profile');
|
||||
}
|
||||
|
||||
|
||||
// Finally perform the mutation
|
||||
await db.user.update({
|
||||
where: { id: validated.userId },
|
||||
data: {
|
||||
name: validated.name,
|
||||
email: validated.email
|
||||
}
|
||||
})
|
||||
|
||||
return { success: true }
|
||||
email: validated.email,
|
||||
},
|
||||
});
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Reference in New Issue
Block a user