> ## Documentation Index
> Fetch the complete documentation index at: https://docs.nullpass.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# Disable Account

> Disable or enable user account

## Endpoints

```
POST /api/auth/disable-account
DELETE /api/auth/disable-account
```

## POST /api/auth/disable-account

Disables the authenticated user's account. Requires password verification and 2FA if enabled. Cancels all active Polar subscriptions.

### Request

<ParamField body="password" type="string" required>
  User password for verification
</ParamField>

<ParamField body="verificationCode" type="string">
  Required if 2FA is enabled. TOTP code from authenticator app.
</ParamField>

### Response

<ResponseField name="success" type="boolean">
  Always `true` on success
</ResponseField>

<ResponseField name="message" type="string">
  "Account disabled successfully"
</ResponseField>

## DELETE /api/auth/disable-account

Enables a disabled account. Requires password verification and 2FA if enabled. Returns 400 error if account is not currently disabled.

### Request

<ParamField body="password" type="string" required>
  User password for verification
</ParamField>

<ParamField body="verificationCode" type="string">
  Required if 2FA is enabled. TOTP code from authenticator app.
</ParamField>

### Response

<ResponseField name="success" type="boolean">
  Always `true` on success
</ResponseField>

<ResponseField name="message" type="string">
  "Account enabled successfully"
</ResponseField>

## Implementation Details

### Code Reference

```17:239:nullpass_clean/src/app/api/auth/disable-account/route.ts theme={null}
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 = disableAccountSchema.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 disable 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 disable 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 {
          const response = await fetch(`https://api.polar.sh/v1/subscriptions/${service.polarSubscriptionId}`, {
            method: 'DELETE',
            headers: {
              'Authorization': `Bearer ${process.env.POLAR_ACCESS_TOKEN}`,
              'Content-Type': 'application/json',
            },
          })
          
          if (response.ok) {
            logger.info(`Canceled Polar subscription: ${service.polarSubscriptionId}`, auth.userId)
          } else {
            logger.warn(`Failed to cancel Polar subscription: ${service.polarSubscriptionId}`, response.status)
          }
        } catch (error) {
          logger.error('Failed to cancel Polar subscription:', error)
        }
      }
    }

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

    await prisma.user.update({
      where: { id: auth.userId },
      data: {
        disabled: true,
      },
    } as any)

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

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

## Status Codes

<ResponseField name="200" type="OK">
  Success
</ResponseField>

<ResponseField name="400" type="Bad Request">
  Validation error or account not disabled (DELETE only)
</ResponseField>

<ResponseField name="401" type="Unauthorized">
  Invalid password or 2FA code
</ResponseField>

<ResponseField name="404" type="Not Found">
  User not found
</ResponseField>

## Example Requests

### Disable Account

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

### Enable Account

```bash theme={null}
curl -X DELETE https://auth.nullpass.xyz/api/auth/disable-account \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "password": "yourpassword123",
    "verificationCode": "123456"
  }'
```

## Difference from Ban

* **Disable**: Temporary account suspension, can be re-enabled
* **Ban**: Permanent account restriction, requires unban action

Both prevent authentication, but disable is typically used for temporary suspensions while ban is for permanent restrictions.

## Security Notes

* Requires password verification
* Requires 2FA code if 2FA is enabled
* All Polar subscriptions are canceled when disabling
* Disabled accounts cannot authenticate
* Audit logs are created for disable/enable actions

## Audit Events

* **USER\_DISABLE**: Account disabled (POST)
* **USER\_UPDATE**: Account enabled (DELETE, with `action: 'account_enabled'`)
