Skip to main content

Endpoint

POST /api/auth/delete-account

Overview

Permanently deletes the authenticated user’s account. This action is irreversible and will:
  • Delete all user data
  • Cancel all Polar subscriptions
  • Delete all sessions
  • Delete all service entitlements
  • Delete all audit logs (cascade delete)

Request

password
string
required
User password for verification
verificationCode
string
Required if 2FA is enabled. TOTP code from authenticator app.

Response

success
boolean
Always true on success
message
string
“Account deleted successfully”

Implementation Details

Code Reference

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

  const blocked = await protectRoute(request, { requested: 2 })
  if (blocked) return blocked

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

  try {
    const body = await request.json()
    const validated = deleteAccountSchema.parse(body)

    const user = await prisma.user.findUnique({
      where: { id: auth.userId },
      include: {
        serviceAccess: true,
      },
    })

    if (!user || !user.passwordHash) {
      return errorResponse('User not found', 404, request.headers.get('origin'))
    }

    const isValid = await bcrypt.compare(validated.password, user.passwordHash)
    if (!isValid) {
      logger.warn('Account deletion failed: Invalid password', auth.userId)
      return errorResponse('Invalid password', 401, request.headers.get('origin'))
    }

    if (user.twoFactorEnabled) {
      if (!validated.verificationCode) {
        return errorResponse('2FA verification code is required', 401, request.headers.get('origin'))
      }

      if (!user.twoFactorSecret) {
        return errorResponse('2FA is enabled but secret is missing', 500, request.headers.get('origin'))
      }

      const isValid2FA = speakeasy.totp.verify({
        secret: user.twoFactorSecret,
        encoding: 'base32',
        token: validated.verificationCode,
        window: 2,
      })

      if (!isValid2FA) {
        logger.warn('Account deletion failed: Invalid 2FA code', auth.userId)
        return errorResponse('Invalid 2FA verification code', 401, request.headers.get('origin'))
      }
    }

    for (const service of user.serviceAccess) {
      if (service.polarSubscriptionId && process.env.POLAR_ACCESS_TOKEN) {
        try {
          await fetch(`https://api.polar.sh/v1/subscriptions/${service.polarSubscriptionId}`, {
            method: 'DELETE',
            headers: {
              'Authorization': `Bearer ${process.env.POLAR_ACCESS_TOKEN}`,
              'Content-Type': 'application/json',
            },
          })
          logger.info(`Canceled Polar subscription: ${service.polarSubscriptionId}`, auth.userId)
        } catch (error) {
          logger.error('Failed to cancel Polar subscription:', error)
        }
      }
    }

    await createAuditLog(auth.userId, 'USER_DELETE', {
      email: user.email,
    })

    await prisma.user.delete({
      where: { id: auth.userId },
    })

    logger.info(`User account deleted: ${user.email}`, auth.userId)

    return jsonResponse(
      { success: true, message: 'Account deleted successfully' },
      200,
      request.headers.get('origin')
    )
  } catch (error: any) {
    if (error.name === 'ZodError') {
      logger.warn('Delete account validation error:', error.errors)
      return errorResponse(error.errors[0].message, 400, request.headers.get('origin'))
    }
    logger.error('Delete account error:', error)
    return errorResponse('Internal server error', 500, request.headers.get('origin'))
  }
}

Status Codes

200
OK
Account deleted successfully
400
Bad Request
Validation error
401
Unauthorized
Invalid password or 2FA code
404
Not Found
User not found

Example Request

curl -X POST https://auth.nullpass.xyz/api/auth/delete-account \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "password": "yourpassword123",
    "verificationCode": "123456"
  }'

Example Response

{
  "success": true,
  "message": "Account deleted successfully"
}
Irreversible Action: Account deletion is permanent and cannot be undone. All user data, sessions, entitlements, and audit logs will be permanently deleted.

Security Notes

  • Requires password verification
  • Requires 2FA code if 2FA is enabled
  • All Polar subscriptions are canceled before deletion
  • Cascade deletes remove all related data
  • Final audit log entry is created before deletion

Audit Events

  • USER_DELETE: Account deleted (created before deletion)