PDNS Manager

Users & roles

PDNS Manager deliberately ships only two roles: admin and user. No fine-grained permission matrix because in 95 % of DNS use cases that's pure complexity without payoff. The fine control lives in zone assignments.

The roles

RoleWhat they can do
Admin Sees and changes everything: all servers, all zones, all records, all users, all settings, the audit log, templates, branding, ACME tokens.
User Sees only assigned zones. Inside a zone they have either read or full access. They can't see other zones, server settings, the audit log, or manage users.

Creating a user

Users → New user. Fields: username, email, password (or leave empty → reset mail is sent if SMTP is configured), role.

Zone assignment with permission level

For a user: Assign zones. Per zone:

  • Read – the user sees the zone and its records but can't change anything. Write API calls are server-side rejected with HTTP 403 – including records, DNSSEC toggles and NOTIFY.
  • Full – the user can do everything inside this zone. No server or settings rights, that stays admin.

The DB model (user_zone_access):

user_id  zone_name        permission
17       example.com.     manage
17       intern.example.  read
23       kunde-a.de.      manage

Two-factor auth (TOTP)

Each user can enable TOTP under Settings → API & Security → Enable 2FA:

  1. The backend generates a secret and a QR code (via qrcode).
  2. The user scans with an app (Aegis, Google Authenticator, 1Password, …).
  3. They enter a fresh 6-digit code → 2FA active.

On the next login the panel asks for username/password and then the TOTP code. Backend-side that's POST /api/v1/auth/login + POST /api/v1/auth/login/2fa.

Password reset

Three paths:

  1. User self-service: /forgot-password → mail with reset link → new password.
  2. Admin: in the user list click a user → "Reset password". Set manually or send a reset link.
  3. Emergency (console) – when nothing else works:
docker compose exec backend python -c "
from app.core.database import async_session
from app.models.models import User
from app.core.auth import hash_password
import asyncio

async def reset():
    async with async_session() as db:
        admin = await db.get(User, 1)   # user ID 1 = first admin
        admin.hashed_password = hash_password('new-password')
        await db.commit()
        print('Password reset.')

asyncio.run(reset())
"

Login rate limit

Since v2.3.7 the backend locks an IP for a few minutes after several failed logins (HTTP 429 with Retry-After). Brute force becomes pointless without legitimate users noticing – the window slides and resets after a successful login.

No image has been added yet. Drop it into src/assets/screenshots/<filename> and register it in the gallery list.