Admin User Verification API
Overviewβ
The Admin User Verification API allows administrators to manage user verification status. This endpoint enables admins to verify or unverify users, which affects their access levels and trust indicators throughout the platform.
Endpoint Detailsβ
- URL:
/api/admin/users/[id]/verification
- Method:
PUT
- Authentication: Required (Admin role)
- Content-Type:
application/json
URL Parametersβ
Parameter | Type | Required | Description |
---|---|---|---|
id | string | Yes | The unique identifier of the user to update verification status |
Authenticationβ
This endpoint requires admin authentication. Only users with the admin
role can access this endpoint:
import { getSession } from 'next-auth/react'
const session = await getSession()
if (!session || session.user.role !== 'admin') {
// Handle unauthorized access
}
Request Bodyβ
Field | Type | Required | Description |
---|---|---|---|
isVerified | boolean | Yes | The verification status to set for the user |
Example Requestβ
{
"isVerified": true
}
Response Formatβ
Success Response (200)β
{
"success": true,
"message": "User verification status updated successfully"
}
Error Responsesβ
Unauthorized (401)β
{
"error": "Unauthorized"
}
Bad Request (400)β
{
"error": "Invalid verification status"
}
Internal Server Error (500)β
{
"error": "Internal server error"
}
Implementation Examplesβ
React Admin Componentβ
import { useState } from 'react'
import { useSession } from 'next-auth/react'
interface User {
id: string
email: string
name: string
isVerified: boolean
role: string
createdAt: string
}
function UserVerificationToggle({ user, onUpdate }: {
user: User
onUpdate: (userId: string, isVerified: boolean) => void
}) {
const { data: session } = useSession()
const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
const handleToggleVerification = async () => {
if (!session || session.user.role !== 'admin') return
setLoading(true)
setError(null)
const newVerificationStatus = !user.isVerified
try {
const response = await fetch(`/api/admin/users/${user.id}/verification`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
isVerified: newVerificationStatus
}),
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.error || 'Failed to update verification status')
}
onUpdate(user.id, newVerificationStatus)
} catch (err) {
setError(err instanceof Error ? err.message : 'Unknown error occurred')
} finally {
setLoading(false)
}
}
if (session?.user.role !== 'admin') {
return (
<span className={`px-2 py-1 text-xs rounded-full ${
user.isVerified
? 'bg-green-100 text-green-800'
: 'bg-gray-100 text-gray-800'
}`}>
{user.isVerified ? 'Verified' : 'Unverified'}
</span>
)
}
return (
<div className="flex items-center space-x-2">
<button
onClick={handleToggleVerification}
disabled={loading}
className={`px-3 py-1 text-sm rounded transition-colors ${
user.isVerified
? 'bg-green-500 text-white hover:bg-green-600'
: 'bg-gray-300 text-gray-700 hover:bg-gray-400'
} disabled:opacity-50`}
>
{loading ? 'Updating...' : user.isVerified ? 'Verified' : 'Unverified'}
</button>
{error && (
<span className="text-xs text-red-600">{error}</span>
)}
</div>
)
}
Admin User Management Tableβ
import { useState, useEffect } from 'react'
import { useSession } from 'next-auth/react'
function AdminUserTable() {
const { data: session } = useSession()
const [users, setUsers] = useState<User[]>([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
useEffect(() => {
if (!session || session.user.role !== 'admin') return
const fetchUsers = async () => {
try {
const response = await fetch('/api/admin/users')
if (!response.ok) throw new Error('Failed to fetch users')
const data = await response.json()
setUsers(data.users)
} catch (err) {
setError(err instanceof Error ? err.message : 'Unknown error')
} finally {
setLoading(false)
}
}
fetchUsers()
}, [session])
const handleVerificationUpdate = (userId: string, isVerified: boolean) => {
setUsers(prev =>
prev.map(user =>
user.id === userId
? { ...user, isVerified }
: user
)
)
}
if (session?.user.role !== 'admin') {
return <div className="text-red-500">Access denied. Admin role required.</div>
}
if (loading) return <div>Loading users...</div>
if (error) return <div className="text-red-500">Error: {error}</div>
return (
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
User
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Role
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Verification
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Actions
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{users.map((user) => (
<tr key={user.id}>
<td className="px-6 py-4 whitespace-nowrap">
<div className="flex items-center">
<div className="flex-shrink-0 h-10 w-10">
<div className="h-10 w-10 rounded-full bg-gray-300 flex items-center justify-center">
<span className="text-sm font-medium text-gray-700">
{user.name?.charAt(0).toUpperCase() || user.email.charAt(0).toUpperCase()}
</span>
</div>
</div>
<div className="ml-4">
<div className="text-sm font-medium text-gray-900">{user.name || 'No name'}</div>
<div className="text-sm text-gray-500">{user.email}</div>
</div>
</div>
</td>
<td className="px-6 py-4 whitespace-nowrap">
<span className="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-100 text-blue-800">
{user.role}
</span>
</td>
<td className="px-6 py-4 whitespace-nowrap">
<UserVerificationToggle
user={user}
onUpdate={handleVerificationUpdate}
/>
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
<div className="flex space-x-2">
<button className="text-indigo-600 hover:text-indigo-900">
Edit
</button>
<button className="text-red-600 hover:text-red-900">
Delete
</button>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
)
}
Custom Hook for Admin Operationsβ
import { useState, useCallback } from 'react'
import { useSession } from 'next-auth/react'
export function useAdminUserOperations() {
const { data: session } = useSession()
const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
const updateVerificationStatus = useCallback(async (
userId: string,
isVerified: boolean
) => {
if (!session || session.user.role !== 'admin') {
return { success: false, error: 'Unauthorized' }
}
setLoading(true)
setError(null)
try {
const response = await fetch(`/api/admin/users/${userId}/verification`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ isVerified }),
})
if (!response.ok) {
const errorData = await response.json()
throw new Error(errorData.error || 'Failed to update verification status')
}
return { success: true }
} catch (err) {
const errorMessage = err instanceof Error ? err.message : 'Unknown error occurred'
setError(errorMessage)
return { success: false, error: errorMessage }
} finally {
setLoading(false)
}
}, [session])
const batchUpdateVerification = useCallback(async (
userIds: string[],
isVerified: boolean
) => {
if (!session || session.user.role !== 'admin') {
return { success: false, error: 'Unauthorized' }
}
setLoading(true)
setError(null)
try {
const results = await Promise.allSettled(
userIds.map(userId =>
fetch(`/api/admin/users/${userId}/verification`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ isVerified }),
})
)
)
const failed = results.filter(r => r.status === 'rejected').length
const successful = results.length - failed
if (failed > 0) {
throw new Error(`${failed} out of ${results.length} operations failed`)
}
return {
success: true,
message: `Successfully updated ${successful} users`
}
} catch (err) {
const errorMessage = err instanceof Error ? err.message : 'Unknown error occurred'
setError(errorMessage)
return { success: false, error: errorMessage }
} finally {
setLoading(false)
}
}, [session])
return {
updateVerificationStatus,
batchUpdateVerification,
loading,
error,
clearError: () => setError(null)
}
}
Server Action (Next.js 13+)β
'use server'
import { auth } from '@/auth'
import { redirect } from 'next/navigation'
import { revalidatePath } from 'next/cache'
export async function updateUserVerificationAction(
userId: string,
isVerified: boolean
) {
const session = await auth()
if (!session || session.user.role !== 'admin') {
redirect('/unauthorized')
}
try {
const response = await fetch(`${process.env.NEXTAUTH_URL}/api/admin/users/${userId}/verification`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Cookie': request.headers.get('cookie') || '',
},
body: JSON.stringify({ isVerified }),
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.error || 'Failed to update verification status')
}
// Revalidate admin pages
revalidatePath('/admin/users')
revalidatePath(`/admin/users/${userId}`)
return { success: true }
} catch (error) {
console.error('Server action error:', error)
throw error
}
}
cURL Exampleβ
# Verify a user
curl -X PUT https://ring.ck.ua/api/admin/users/user123/verification \
-H "Content-Type: application/json" \
-H "Cookie: next-auth.session-token=your-admin-session-token" \
-d '{
"isVerified": true
}'
# Unverify a user
curl -X PUT https://ring.ck.ua/api/admin/users/user123/verification \
-H "Content-Type: application/json" \
-H "Cookie: next-auth.session-token=your-admin-session-token" \
-d '{
"isVerified": false
}'
Business Logicβ
The endpoint performs the following operations:
- Authentication Check: Verifies admin session
- Role Authorization: Ensures user has admin role
- Input Validation: Validates the isVerified boolean value
- User Lookup: Verifies the target user exists
- Database Update: Updates user verification status and timestamp
- Response: Returns success confirmation
Database Schemaβ
User Document Structureβ
{
"id": "user123",
"email": "user@example.com",
"name": "John Doe",
"role": "member",
"isVerified": false,
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T10:30:00Z"
}
After Verification Updateβ
{
"id": "user123",
"email": "user@example.com",
"name": "John Doe",
"role": "member",
"isVerified": true,
"verifiedAt": "2024-01-15T14:22:00Z",
"verifiedBy": "admin456",
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T14:22:00Z"
}
Security Considerationsβ
- Admin Only: Requires admin role for access
- Input Validation: Validates boolean values strictly
- Audit Trail: Tracks who performed the verification action and when
- Session Verification: Ensures valid admin session before processing
Performance Notesβ
- Single Query: Efficient single document update
- Indexed Queries: User ID lookups are indexed for performance
- Atomic Operations: Uses atomic updates to prevent race conditions
Rate Limitingβ
- Admin-based: 30 requests per minute per admin user
- Global: 100 requests per minute across all admins
Audit Loggingβ
All verification status changes are logged for compliance:
{
"action": "user_verification_updated",
"adminId": "admin456",
"targetUserId": "user123",
"previousStatus": false,
"newStatus": true,
"timestamp": "2024-01-15T14:22:00Z",
"ipAddress": "192.168.1.100",
"userAgent": "Mozilla/5.0..."
}
Testingβ
Unit Test Exampleβ
import { PUT } from '@/app/api/admin/users/[id]/verification/route'
import { NextRequest } from 'next/server'
// Mock authentication
jest.mock('next-auth/next')
describe('/api/admin/users/[id]/verification', () => {
it('should update user verification status successfully', async () => {
// Mock admin session
(getServerSession as jest.Mock).mockResolvedValue({
user: { id: 'admin123', role: 'admin' }
})
const request = new NextRequest('http://localhost:3000/api/admin/users/user123/verification', {
method: 'PUT',
body: JSON.stringify({ isVerified: true })
})
const response = await PUT(request, { params: Promise.resolve({ id: 'user123' }) })
const data = await response.json()
expect(response.status).toBe(200)
expect(data.success).toBe(true)
expect(data.message).toBe('User verification status updated successfully')
})
it('should reject non-admin users', async () => {
(getServerSession as jest.Mock).mockResolvedValue({
user: { id: 'user123', role: 'member' }
})
const request = new NextRequest('http://localhost:3000/api/admin/users/user123/verification', {
method: 'PUT',
body: JSON.stringify({ isVerified: true })
})
const response = await PUT(request, { params: Promise.resolve({ id: 'user123' }) })
expect(response.status).toBe(401)
})
})
Related Endpointsβ
GET /api/admin/users
- List all users (admin)GET /api/admin/users/[id]
- Get specific user details (admin)PUT /api/admin/users/[id]/role
- Update user role (admin)GET /api/profile
- Get user profile information