Skip to main content

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​

ParameterTypeRequiredDescription
idstringYesThe 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​

FieldTypeRequiredDescription
isVerifiedbooleanYesThe 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:

  1. Authentication Check: Verifies admin session
  2. Role Authorization: Ensures user has admin role
  3. Input Validation: Validates the isVerified boolean value
  4. User Lookup: Verifies the target user exists
  5. Database Update: Updates user verification status and timestamp
  6. 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)
})
})