Skip to main content
GET
/
avatar
Get Avatar
curl --request GET \
  --url https://auth.nullpass.xyz/api/avatar \
  --header 'Authorization: Bearer <token>'

Endpoints

GET /api/avatar
POST /api/avatar

GET /api/avatar

Retrieves the authenticated user’s avatar image. Supports both local file storage and external URLs.

Response

Returns the avatar image file with appropriate content type headers. If avatar is an external URL, returns a 302 redirect.

POST /api/avatar

Uploads a new avatar image for the authenticated user. Old avatar is automatically deleted if it was a local file.

Request

avatar
file
required
Image file (multipart/form-data). Maximum size: 2MB. Allowed types: JPEG, PNG, WebP, GIF.

Response

avatar
string
Relative path to the uploaded avatar file
message
string
“Avatar uploaded successfully”

Implementation Details

Code Reference

export async function GET(request: NextRequest) {
  const corsResponse = handleCors(request)
  if (corsResponse) return corsResponse

  const blocked = await protectRoute(request)
  if (blocked) return blocked

  const auth = await requireAuth(request)
  if ('error' in auth) return auth.error

  try {
    const avatarPath = await getUserAvatarPath(auth.userId)

    if (!avatarPath) {
      return errorResponse('Avatar not found', 404, request.headers.get('origin'))
    }

    if (avatarPath.startsWith('http://') || avatarPath.startsWith('https://')) {
      return Response.redirect(avatarPath, 302)
    }

    const fileBuffer = await fs.readFile(avatarPath)
    const ext = path.extname(avatarPath).toLowerCase()
    const contentType = 
      ext === '.jpg' || ext === '.jpeg' ? 'image/jpeg' :
      ext === '.png' ? 'image/png' :
      ext === '.webp' ? 'image/webp' :
      ext === '.gif' ? 'image/gif' :
      'image/jpeg'

    return new Response(fileBuffer, {
      headers: {
        ...corsHeaders(request.headers.get('origin')),
        'Content-Type': contentType,
        'Cache-Control': 'public, max-age=3600',
      },
    })
  } catch (error) {
    logger.error('Get avatar error:', error)
    return errorResponse('Internal server error', 500, request.headers.get('origin'))
  }
}

export async function POST(request: NextRequest) {
  const corsResponse = handleCors(request)
  if (corsResponse) return corsResponse

  const blocked = await protectRoute(request)
  if (blocked) return blocked

  const auth = await requireAuth(request)
  if ('error' in auth) return auth.error

  try {
    const formData = await request.formData()
    const file = formData.get('avatar') as File | null

    if (!file) {
      return errorResponse('No file provided', 400, request.headers.get('origin'))
    }

    if (file.size > MAX_FILE_SIZE) {
      return errorResponse('File too large. Maximum size is 2MB', 400, request.headers.get('origin'))
    }

    if (!ALLOWED_MIME_TYPES.includes(file.type)) {
      return errorResponse(
        'Invalid file type. Allowed types: JPEG, PNG, WebP, GIF',
        400,
        request.headers.get('origin')
      )
    }

    await deleteOldAvatar(auth.userId)

    const userDir = await ensureUserAvatarDir(auth.userId)

    const ext = file.type === 'image/jpeg' ? '.jpg' :
                file.type === 'image/png' ? '.png' :
                file.type === 'image/webp' ? '.webp' :
                file.type === 'image/gif' ? '.gif' :
                '.jpg'
    
    const filename = `avatar_${Date.now()}${ext}`
    const filePath = path.join(userDir, filename)

    const arrayBuffer = await file.arrayBuffer()
    const buffer = Buffer.from(arrayBuffer)
    await fs.writeFile(filePath, buffer)

    const relativePath = `${auth.userId}/${filename}`
    await prisma.user.update({
      where: { id: auth.userId },
      data: { avatar: relativePath },
    })

    await createAuditLog(auth.userId, 'USER_UPDATE', {
      fields: ['avatar'],
    })

    return jsonResponse(
      {
        avatar: relativePath,
        message: 'Avatar uploaded successfully',
      },
      200,
      request.headers.get('origin')
    )
  } catch (error: any) {
    logger.error('Upload avatar error:', error)
    return errorResponse('Internal server error', 500, request.headers.get('origin'))
  }
}

Status Codes

200
OK
Success
302
Found
Redirect to external avatar URL (GET only)
400
Bad Request
No file provided, file too large, or invalid file type
401
Unauthorized
Missing or invalid authentication token
404
Not Found
Avatar not found (GET only)

File Requirements

  • Maximum size: 2MB
  • Allowed formats: JPEG, PNG, WebP, GIF
  • Storage: Local file system (user-specific directories)

Example Requests

Get Avatar

curl -X GET https://auth.nullpass.xyz/api/avatar \
  -H "Authorization: Bearer YOUR_TOKEN"

Upload Avatar

curl -X POST https://auth.nullpass.xyz/api/avatar \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -F "avatar=@/path/to/image.jpg"

Environment Variables

AVATARS_PATH
string
Base path for avatar storage (default: src/avatars)

Security Notes

  • Old avatars are automatically deleted when uploading new ones
  • External URLs are supported (redirects to URL)
  • Files are stored in user-specific directories
  • Content-Type headers are set based on file extension
  • Cache-Control header set to 1 hour for GET requests

Authorizations

Authorization
string
header
required

Bearer authentication header of the form Bearer <token>, where <token> is your auth token.

Response

200 - image/*

Avatar image