Files
xmpp-server-prosody/references/user-management-registration-disable.md
2026-09-06 13:51:11 +00:00

3.4 KiB

User management & disabling in-band registration (Prosody 0.11, verified 2026-08-29)

Session-proven on the nixg.ru Prosody (Docker) — creates/verifies accounts, and the CORRECT way to shut off self-registration.

Account operations (prosodyctl — the ONLY path once registration is off)

# create (same command re-run = password change)
docker exec icq-prosody prosodyctl register <user> nixg.ru 'PASS'
# delete
docker exec icq-prosody prosodyctl unregister <user> nixg.ru
# list accounts (URL-encoded domain dir on host)
ls /opt/icq/data/nixg%2eru/accounts/          # *.dat per account

prosodyctl check accounts is NOT a valid subcommand (0.11). Verify creation via the accounts dir, then by logging in.

Disabling in-band self-registration — do BOTH, not just the flag

modules_enabled = {                          -- comment the module OUT:
    -- "register";
    ...
}
VirtualHost "nixg.ru"
    allow_registration = false               -- AND flip the flag
  • allow_registration = false alone leaves mod_register loaded and answering XEP-0077 (it just refuses) — but only unloading the module makes Prosody reply service-unavailable, which is the clean "registration closed" signal clients understand.
  • After edit: docker exec icq-prosody prosodyctl check config → "All checks passed", then docker compose restart prosody.

Verification: raw XEP-0077 probe from a client

Don't rely on client plugins (slixmpp's xep_0077 stanza may not be registered). Send a raw IQ with an existing authenticated session:

import asyncio, ssl
import slixmpp

async def main():
    bot = slixmpp.ClientXMPP('user@nixg.ru', 'PASS')
    ctx = ssl.create_default_context(); ctx.check_hostname=False; ctx.verify_mode=ssl.CERT_NONE
    bot.ssl_context = ctx
    result = asyncio.Event()
    async def on_start(ev):
        iq = bot.make_iq_set(sub=None, ito='nixg.ru')
        iq['id'] = 'regtest1'
        q = iq.xml.makeelement('{jabber:iq:register}query', {})
        u = q.makeelement('username', {}); u.text = 'probeuser'
        p = q.makeelement('password', {}); p.text = 'ProbePass1'
        q.append(u); q.append(p); iq.xml.append(q)
        def cb(resp):
            print('REJECTED:', resp['type'], '/', resp['error']['condition'])
            result.set(); bot.disconnect()
        iq.send(callback=cb)
    bot.add_event_handler('session_start', on_start)
    await bot.connect(('localhost', 5222))
    await asyncio.wait_for(result.wait(), timeout=15)

asyncio.run(main())

Expected with registration off: REJECTED: error / service-unavailable (no account created). With registration on: error conflict only if the username exists, otherwise success — so a probe also proves whether it is on.

Verifying a login works (the reliable slixmpp pattern)

await bot.connect(...) then waiting on bot.disconnected.wait() CRASHES ('_asyncio.Future' object has no attribute 'wait'). Use event handlers:

done = asyncio.Event()
def on_session(ev): print('AUTH OK'); done.set()
def on_fail(ev):    print('AUTH FAILED'); done.set()
bot.add_event_handler('session_start', on_session)
bot.add_event_handler('failed_auth', on_fail)
await bot.connect(('localhost', 5222))
await asyncio.wait_for(done.wait(), timeout=15)

AUTH OK: session_start = credentials valid. Works over c2s (localhost:5222) or wss://xmpp.nixg.ru/xmpp-websocket — good for smoke-testing new accounts and password resets.