11 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project Overview
iauthd-ts is a TypeScript IAuth daemon for Nefarious IRCd. It performs real-time DNS blacklist (DNSBL) lookups on connecting IRC clients and can block, mark, whitelist, or assign connection classes based on results. It also handles SASL PLAIN authentication directly (without routing to services).
This is a port of the original Perl iauthd.pl. The only runtime dependency is ldapts for LDAP authentication support.
Build & Development Commands
npm install # Install dependencies
npm run build # Compile TypeScript to dist/
npm run dev -- -c /path/to/config # Run directly from TypeScript (no compile)
npm test # Run all tests
npm run test:watch # Watch mode
npm run test:coverage # Coverage report
npm run stress # Memory stress test (5000 clients)
npm run stress -- --clients=10000 # Custom client count
Running
node dist/index.js -c <configfile> [-v] [-d]
# -c, --config Config file to read (required)
# -v, --verbose Debug output in iauthd
# -d, --debug Debug output in IRCd
Architecture
Core Components
src/index.ts- CLI entry point, parses args and starts daemonsrc/iauth.ts- MainIAuthDaemonclass, handles IRCd communication via stdin/stdout using the IAuth protocolsrc/config.ts- Parses#IAUTHdirectives from config filessrc/dnsbl.ts- DNSBL lookup with caching, IP reversal utilitiessrc/sasl.ts- SASL PLAIN decoding, password hashing utilities (SHA-256/512, MD5, bcrypt)src/types.ts- TypeScript interfaces for all data structures
Authentication Module (src/auth/)
The auth module provides a modular, provider-based authentication system:
src/auth/types.ts- AuthProvider interface and config typessrc/auth/manager.ts- AuthManager coordinates multiple providers with fallback chainsrc/auth/config.ts- Parses AUTH directive configurationsrc/auth/providers/file.ts- Static file-based authentication (wraps sasl.ts)src/auth/providers/ldap.ts- LDAP authentication with direct bind and search modessrc/auth/providers/keycloak.ts- Keycloak authentication using ROPC grant
IAuth Protocol Flow
The daemon communicates with Nefarious via stdin/stdout:
- Client connects → IRCd sends
C <id> <ip> <port> <serverip> <serverport> - DNSBL lookups → Started concurrently for all configured DNSBLs
- Results processed → Check index/bitmask matches, apply marks/blocks/whitelist
- Hurry received → IRCd sends
H <id> <class>when registration timeout approaches - Decision made → Send
D(accept),k(reject), orm(mark) back to IRCd
Key message handlers in IAuthDaemon.handleLine():
C- Client introduction, triggers DNSBL lookupsH- Hurry, forces immediate decisionR- Client authenticated via SASL/LOC (exempts fromblock=anonymous)A/a- SASL authentication start/continuew- Trusted WEBIRC, re-checks real IP
DNSBL Matching
DNSBLs return 127.0.0.X addresses where X indicates the listing reason:
index=2,3,4- Match if X equals any listed valuebitmask=8- Match if X AND bitmask is non-zero- Neither specified - Any response is a match
Caching
DNS results are cached globally in dnsbl.ts. Cache entries store:
- Query string (reversed IP + server)
- Result IPs or null (pending)
- Timestamp for expiration
SASL Authentication
iauthd-ts handles SASL PLAIN authentication directly using a modular provider system:
- IRCd sends
A <id> S :PLAIN - iauthd responds with challenge
c <id> :+ - Client sends base64 credentials via
a <id> :<data> - iauthd verifies against configured auth providers (file, LDAP, etc.)
- Sends
L(success) orf(fail) to IRCd
Auth Provider Fallback: When multiple providers are configured, they are tried in priority order (lower = first). First successful authentication wins.
Password formats for file provider: $5$salt$hash (SHA-256), $6$salt$hash (SHA-512), $1$salt$hash (MD5), $2a$ or $2b$ (bcrypt)
Testing
Tests use Vitest with mocked DNS resolution:
tests/config.test.ts- Configuration parsingtests/dnsbl.test.ts- IP functions, caching, matchingtests/dnsbl-integration.test.ts- Full DNSBL pipeline with mock DNStests/iauth.test.ts- Protocol integration teststests/sasl.test.ts- SASL authentication utilitiestests/auth/config.test.ts- AUTH directive parsingtests/auth/file.test.ts- FileAuthProvider teststests/auth/manager.test.ts- AuthManager fallback behaviortests/auth/keycloak.test.ts- KeycloakAuthProvider teststests/stress.ts- Memory stress test (not a vitest file)
Run a single test file:
npx vitest run tests/dnsbl.test.ts
npx vitest run tests/config.test.ts -t "parses DNSBL" # Run matching tests
Configuration
Configuration uses #IAUTH directives embedded in ircd.conf (IRCd ignores lines starting with #):
#IAUTH POLICY RTAWUwFrS
#IAUTH DNSTIMEOUT 5
#IAUTH CACHETIME 86400
#IAUTH BLOCKMSG Your connection has been rejected
#IAUTH DNSBL server=dnsbl.dronebl.org index=2,3,5 mark=dronebl block=anonymous
#IAUTH DNSBL server=whitelist.example.com whitelist
Authentication Providers
Use #IAUTH AUTH to configure authentication backends. Multiple providers can be configured; they are tried in priority order (lower = first).
Static File Provider
#IAUTH SASLDB /path/to/users # Legacy format (still supported)
#IAUTH AUTH provider=file path=/path/to/users # Explicit format
#IAUTH AUTH provider=file path=/path/to/users priority=50
LDAP Direct Bind Mode
Note: LDAP support has not yet been tested against real LDAP servers. Use with caution and please report any issues.
User binds directly with their credentials using a DN template:
#IAUTH AUTH provider=ldap uri=ldap://ldap.example.com:389 mode=direct userdn=uid=%s,ou=users,dc=example,dc=com
LDAP Search Mode (Admin Bind + Search)
Binds as admin, searches for user, optionally checks group membership, then binds as user:
#IAUTH AUTH provider=ldap uri=ldaps://ldap.example.com:636 mode=search basedn=ou=users,dc=example,dc=com binddn=cn=admin,dc=example,dc=com bindpass=secret userfilter=(uid=%s)
# With group membership check:
#IAUTH AUTH provider=ldap uri=ldaps://ldap.example.com:636 mode=search basedn=ou=users,dc=example,dc=com binddn=cn=admin,dc=example,dc=com bindpass=secret userfilter=(uid=%s) groupdn=cn=ircusers,ou=groups,dc=example,dc=com
LDAP Configuration Options
| Option | Required | Mode | Description |
|---|---|---|---|
uri |
Yes | Both | LDAP server URI (ldap:// or ldaps://) |
mode |
Yes | Both | direct or search |
userdn |
Yes | direct | DN template with %s for username |
basedn |
Yes | search | Base DN for user search |
binddn |
Yes | search | Admin bind DN |
bindpass |
Yes | search | Admin bind password |
userfilter |
Yes | search | Search filter with %s for username |
groupdn |
No | search | Group DN for membership check |
accountattr |
No | Both | Attribute to use as account name (default: uid/sAMAccountName) |
timeout |
No | Both | Connection timeout in ms (default: 5000) |
priority |
No | Both | Provider priority (default: 100, lower = first) |
Keycloak Provider
Uses Resource Owner Password Credentials (ROPC) grant to authenticate users against Keycloak. The client must have "Direct Access Grants" enabled in Keycloak.
#IAUTH AUTH provider=keycloak url=https://keycloak.example.com realm=myrealm clientid=irc-client
# With client secret (for confidential clients):
#IAUTH AUTH provider=keycloak url=https://keycloak.example.com realm=myrealm clientid=irc-client clientsecret=your-secret
# With custom account attribute:
#IAUTH AUTH provider=keycloak url=https://keycloak.example.com realm=myrealm clientid=irc-client accountattr=irc_nick
Keycloak Configuration Options
| Option | Required | Description |
|---|---|---|
url |
Yes | Keycloak server URL (e.g., https://keycloak.example.com) |
realm |
Yes | Keycloak realm name |
clientid |
Yes | OAuth2 client ID (must have Direct Access Grants enabled) |
clientsecret |
No | Client secret (for confidential clients) |
accountattr |
No | JWT claim to use as account name (default: preferred_username) |
timeout |
No | Request timeout in ms (default: 5000) |
priority |
No | Provider priority (default: 100, lower = first) |
Keycloak Server Setup
To configure Keycloak for IRC SASL authentication:
1. Create a Realm
- Go to Keycloak Admin Console
- Create a new realm (e.g.,
ircortestnet)
2. Create a Client
- Go to Clients → Create client
- Client ID:
irc-client(or your preferred name) - Client Protocol:
OpenID Connect(not SAML) - Click Next
- Client authentication:
OFF(for public client) orONif usingclientsecret - Direct access grants:
ON← Critical for ROPC authentication - Click Save
3. Configure User Profile (Optional but Recommended)
- Go to Realm Settings → User profile
- Click on
firstName→ set "Required field" toOFF→ Save - Click on
lastName→ set "Required field" toOFF→ Save - This prevents "Account is not fully set up" errors for users without names
4. Configure Authentication Flows (Optional)
- Go to Authentication → Flows → select direct grant
- Ensure no required actions block password auth (e.g., set Conditional OTP to
DISABLEDif not using 2FA)
5. Create Users
- Go to Users → Add user
- Set username, email (if required)
- Go to Credentials tab → Set password
- Important: Set Temporary to
OFFto avoid forced password change - Ensure Required user actions is empty (no "Update Password", "Configure OTP", etc.)
Common Issues
"Account is not fully set up"- User has pending required actions (check user's Required Actions field, or realm-level User Profile requirements)"Wrong client protocol"- Client is configured as SAML instead of OpenID Connect"unauthorized_client"- Direct Access Grants is not enabled on the client- Connection errors - Use
http://for dev mode (start-dev),https://for production
Testing Authentication
curl -s -X POST "http://localhost:8080/realms/REALM/protocol/openid-connect/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=password&client_id=CLIENT_ID&username=USER&password=PASS"
A successful response returns an access_token. Error responses include error and error_description fields.
Password Hashes
Generate password hashes for the file provider:
npx tsx src/genhash.ts mypassword sha256