No description
Find a file
devilreef 98832b13b8
fix: kick during configuration phase to avoid Velocity RST race
PlayerNegotiationEvent is defined but never fired in NeoForge 1.21.x;
use RegisterConfigurationTasksEvent + custom ConfigurationTask instead.
terminal outcomes disconnect in config phase (clean disconnect packet
through Velocity); NeedsConfirmation stashes for PlayerLoggedInEvent.

bump 0.2.0 -> 0.2.2.
2026-05-18 14:14:13 +06:00
.claude feat: initial commit 2026-05-16 15:30:16 +06:00
gradle feat: initial commit 2026-05-16 15:30:16 +06:00
src fix: kick during configuration phase to avoid Velocity RST race 2026-05-18 14:14:13 +06:00
.gitignore feat: initial commit 2026-05-16 15:30:16 +06:00
build.gradle.kts feat: initial commit 2026-05-16 15:30:16 +06:00
gradle.properties fix: kick during configuration phase to avoid Velocity RST race 2026-05-18 14:14:13 +06:00
gradlew feat: initial commit 2026-05-16 15:30:16 +06:00
gradlew.bat feat: initial commit 2026-05-16 15:30:16 +06:00
README.md docs(readme): describe optional whitelist mode 2026-05-17 19:38:39 +06:00
settings.gradle.kts feat: initial commit 2026-05-16 15:30:16 +06:00
TESTING.md feat: initial commit 2026-05-16 15:30:16 +06:00

Telegram Auth

NeoForge 1.21.1 server-only mod that gates offline-mode logins through a Telegram bot.

  • First join — player is kicked with an 8-character bind code; sending /start <code> to the bot links the Minecraft account to that Telegram user.
  • Subsequent join — player is frozen on-spawn; the bot DMs Login attempt with Accept / Decline buttons.
  • Accept trusts the player's IP for a configurable window (default 7 days); reconnects from the same IP within the window pass silently.
  • Decline kicks the player and applies a soft-ban (default 30 min) clearable via /unlock in Telegram.
  • Telegram unreachable? Trusted-IP joins still pass; untrusted joins are kicked.

Requirements

  • NeoForge 1.21.1 (tested against 21.1.230+)
  • Kotlin For Forge 5.9.0+ (auto-loaded as required dep)
  • JDK 21
  • A Telegram bot token from @BotFather

Install

  1. Drop the jar into your server's mods/ folder.
  2. Make sure kotlinforforge-neoforge is also in mods/ (or let Forge fetch it).
  3. Start the server once — this generates config/mc_tg_auth-server.toml.
  4. Edit the config, set telegram.botToken and telegram.serverDisplayName.
  5. Restart.

Configuration (config/mc_tg_auth-server.toml)

[telegram]
botToken = ""                   # required; from @BotFather
serverDisplayName = "My Server" # shown in Telegram prompts

[session]
durationDays = 7                # how long an accepted IP stays trusted
declineCooldownMinutes = 30     # soft-ban duration after Decline
confirmationTimeoutSeconds = 120

[bind]
codeTtlMinutes = 10

[freeze]
strategy = "inplace"            # only 'inplace' implemented in v1

[ratelimit]
bindAttemptsPerTgUserPerMin = 5
joinKickPerIpPerMin = 3

[i18n]
defaultLocale = "en"            # en | ru

[whitelist]
enabled = false                 # optional chat-membership gate
chatId = 0                      # telegram chat id; negative for groups/supergroups/channels
cacheTtlSeconds = 300           # cache per-user membership for N seconds (30..3600)

Whitelist mode (optional)

When whitelist.enabled = true and whitelist.chatId is set, every join checks that the bound Telegram account is still a member of that chat (getChatMember). Non-members are kicked; new binds from non-members are refused. The check supersedes the trusted-IP shortcut, so removing someone from the chat kicks them on next join. Telegram API errors fail open. The bot must be a member of the chat (admin recommended) for getChatMember to work.

Bot DM commands

Command Action
/start [code] Bind a new MC account (with code) or list bindings
/accounts List bound MC names + active sessions
/unbind <name> Remove a binding
/revoke [name] Drop trusted IPs (one or all)
/unlock [name] Clear soft-ban (one or all)
/help Command list

Bare bind codes also work — paste the 8-char code without /start.

Admin console commands (op level 3)

Command Action
/tgauth status Bot reachability, stats
/tgauth info <player> Show binding
/tgauth unbind <player> Force-unbind
/tgauth revoke <player> Wipe their sessions
/tgauth unlock <player> Clear their soft-ban
/tgauth force-login <player> Unfreeze a stuck player
/tgauth reload Notice — config hot-reloads; restart needed only for token change

Freeze coverage

While awaiting auth, the player is fully restricted:

  • Movement: walking/flying speed forced to 0; position snapped back on drift; Slowness 255 + Blindness + neg-Jump mob effects applied client-side.
  • Damage: isInvulnerable = true plus LivingDamageEvent.Pre zeroes incoming damage; healing blocked too.
  • Inventory: containers auto-close on open; items can't be dropped (returned to inventory) or picked up; XP orbs ignored.
  • Interactions: left/right click on blocks, items, entities — all cancelled. Attack-entity blocked.
  • World: block break/place cancelled. Mounting horses/boats blocked.
  • Communication: chat and commands cancelled.

i18n

Lang files at assets/mc_tg_auth/lang/en_us.json and ru_ru.json. Every user-facing string — kick screens, bot replies, in-game chat — is keyed and editable. Format is MiniMessage; in Telegram messages the <b>/<i>/<code> tags pass through as Telegram HTML and color tags are stripped.

Build

./gradlew build

Produces build/libs/mc_tg_auth-<version>.jar.

Set MOD_VERSION_SUFFIX to append a tag to the jar version (e.g. for testing builds):

MOD_VERSION_SUFFIX=dirty+$(git rev-parse --short=8 HEAD) ./gradlew build

Tests

./gradlew test

Covers: Clock, BindCodeGenerator, RateLimiter, PendingState, AuthService decision table, AuthRepository against real SQLite, inbound command handlers, callback handler, Messages/i18n, plus an end-to-end integration test (bind → confirm → trust IP) against real SQLite.

Dev server

./gradlew runServer

Starts a dedicated NeoForge dev server in run/ with the mod loaded. Edit run/config/mc_tg_auth-server.toml to add a bot token, then connect with a 1.21.1 client to localhost:25565.

Architecture

PlayerGate (NeoForge events)
       ↓
AuthService (pure domain, sealed AuthOutcome)
       ↓
AuthRepository (raw JDBC + SQLite)        TelegramAdapter (JDK HttpClient + Bot API)
  • PlayerGate owns the freeze state machine and applies kicks/unfreezes on the MC main thread.
  • AuthService is pure Kotlin; takes UUID + name + IP, returns one of Trusted / NeedsBinding / NeedsConfirmation / SoftBanned / BotUnreachable / TelegramBlocked. Heavily tested.
  • AuthRepository uses raw java.sql against sqlite-jdbc. No ORM — keeps the runtime classpath narrow and avoids JPMS conflicts with KFF.
  • TelegramAdapter speaks the Bot API directly via java.net.http.HttpClient + kotlinx-serialization-json (provided by KFF). Long-polling at 25s.

License

MIT