Phase 1.5 — auth layer (Redis sessions, allowlist, requireAuth)
Some checks failed
Docker Build / build-and-push-image (push) Failing after 18s
Some checks failed
Docker Build / build-and-push-image (push) Failing after 18s
Adds an authentication layer in front of the bot handlers : - Auth handler on the principal bot (@arcodange_factory_bot, slug factory) parses /start, /auth <code>, /whoami, /logout. On a successful /auth, the message containing the code is best-effort deleted from the user's chat (replay defense). - Redis-backed sessions (key tg-gw:auth:<from.id>, TTL 24h, configurable via AUTH_SESSION_TTL). Constant-time secret compare via crypto/subtle. - ALLOWED_USERS env (CSV of Telegram user IDs) — silent-drops anyone not in the list before the auth gate runs. - New per-bot field 'requireAuth' (pointer-bool). Default = true (secure by default). Auto-forced to false for handler=auth (chicken-and-egg). - Server gates: allowlist first, then requireAuth before handler dispatch. - Fail-at-startup if a bot is configured with handler=auth or requireAuth: true while AUTH_SECRET is unset. Design: factory/docs/adr/20260509-telegram-gateway-auth.md (in factory PR). User docs: AUTH.md (new), HOWTO_ADD_BOT.md (Cas 2 updated for default true and gated flow). New deps: github.com/redis/go-redis/v9. Refs ~/.claude/plans/pour-les-notifications-on-inherited-seal.md § Phase 1.5.
This commit is contained in:
52
handlers.go
52
handlers.go
@@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
)
|
||||
|
||||
type Handler interface {
|
||||
@@ -10,22 +11,27 @@ type Handler interface {
|
||||
}
|
||||
|
||||
type Bot struct {
|
||||
Slug string
|
||||
Token string
|
||||
Secret string
|
||||
Handler Handler
|
||||
Slug string
|
||||
Token string
|
||||
Secret string
|
||||
RequireAuth bool
|
||||
Handler Handler
|
||||
}
|
||||
|
||||
type Registry struct {
|
||||
bots map[string]Bot
|
||||
}
|
||||
|
||||
func NewRegistry(cfg *Config) (*Registry, error) {
|
||||
// NewRegistry builds the bot routing map from the parsed YAML config + the
|
||||
// per-bot secrets (already merged into BotConfig from env). It receives the
|
||||
// shared TelegramClient and Auth so per-handler instances can use them.
|
||||
// `auth` may be nil (no AUTH_SECRET set) ; in that case any bot configured
|
||||
// with `handler: auth` or `requireAuth: true` is a fatal config error.
|
||||
func NewRegistry(cfg *Config, tg *TelegramClient, auth *Auth) (*Registry, error) {
|
||||
if len(cfg.Bots) == 0 {
|
||||
return nil, fmt.Errorf("no bots configured")
|
||||
}
|
||||
|
||||
tg := NewTelegramClient()
|
||||
bots := make(map[string]Bot, len(cfg.Bots))
|
||||
|
||||
for slug, b := range cfg.Bots {
|
||||
@@ -38,10 +44,35 @@ func NewRegistry(cfg *Config) (*Registry, error) {
|
||||
return nil, fmt.Errorf("bot %s: secret missing", slug)
|
||||
}
|
||||
|
||||
// Default requireAuth = true (secure by default). Explicit
|
||||
// `requireAuth: false` is required to expose a bot publicly.
|
||||
requireAuth := true
|
||||
if b.RequireAuth != nil {
|
||||
requireAuth = *b.RequireAuth
|
||||
}
|
||||
|
||||
// Chicken-and-egg : the auth handler itself can't be gated, otherwise
|
||||
// no one could ever authenticate. Force off and warn loudly.
|
||||
if b.Handler == "auth" {
|
||||
if requireAuth {
|
||||
log.Printf("bot %s: handler=auth implies requireAuth=false (otherwise unreachable) — overriding", slug)
|
||||
}
|
||||
requireAuth = false
|
||||
}
|
||||
|
||||
if requireAuth && auth == nil {
|
||||
return nil, fmt.Errorf("bot %s has requireAuth: true (default) but AUTH_SECRET is unset; either set AUTH_SECRET or add `requireAuth: false` to the bot config", slug)
|
||||
}
|
||||
|
||||
var h Handler
|
||||
switch b.Handler {
|
||||
case "echo":
|
||||
h = &EchoHandler{tg: tg}
|
||||
case "auth":
|
||||
if auth == nil {
|
||||
return nil, fmt.Errorf("bot %s uses handler=auth but AUTH_SECRET is unset", slug)
|
||||
}
|
||||
h = &AuthHandler{tg: tg, auth: auth}
|
||||
case "":
|
||||
return nil, fmt.Errorf("bot %s: handler missing", slug)
|
||||
default:
|
||||
@@ -49,10 +80,11 @@ func NewRegistry(cfg *Config) (*Registry, error) {
|
||||
}
|
||||
|
||||
bots[slug] = Bot{
|
||||
Slug: slug,
|
||||
Token: token,
|
||||
Secret: secret,
|
||||
Handler: h,
|
||||
Slug: slug,
|
||||
Token: token,
|
||||
Secret: secret,
|
||||
RequireAuth: requireAuth,
|
||||
Handler: h,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user