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:
37
telegram.go
37
telegram.go
@@ -162,5 +162,42 @@ func (c *TelegramClient) GetWebhookInfo(ctx context.Context, token string) (*Web
|
||||
return &info, nil
|
||||
}
|
||||
|
||||
type DeleteMessageParams struct {
|
||||
ChatID int64 `json:"chat_id"`
|
||||
MessageID int64 `json:"message_id"`
|
||||
}
|
||||
|
||||
// DeleteMessage removes a message from a chat. Used as best-effort replay
|
||||
// defense after a successful /auth (we delete the message that contained
|
||||
// the secret). See factory/docs/adr/20260509-telegram-gateway-auth.md.
|
||||
func (c *TelegramClient) DeleteMessage(ctx context.Context, token string, chatID, messageID int64) error {
|
||||
body, err := json.Marshal(DeleteMessageParams{ChatID: chatID, MessageID: messageID})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
endpoint := fmt.Sprintf("%s/bot%s/deleteMessage", c.apiBase, token)
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
respBody, _ := io.ReadAll(resp.Body)
|
||||
|
||||
var ar apiResponse
|
||||
if jerr := json.Unmarshal(respBody, &ar); jerr != nil {
|
||||
return fmt.Errorf("decode telegram response (status %d): %w", resp.StatusCode, jerr)
|
||||
}
|
||||
if !ar.OK {
|
||||
return fmt.Errorf("telegram deleteMessage error (code=%d): %s", ar.ErrorCode, ar.Description)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// botBuildURL is exposed for tests; not used directly.
|
||||
var _ = url.Parse
|
||||
|
||||
Reference in New Issue
Block a user