diff --git a/.env.example b/.env.example index dc6957b..30b7fde 100644 --- a/.env.example +++ b/.env.example @@ -1,9 +1,13 @@ ROCKETCHAT_SERVER_URL=https://chat.yourcompany.com ROCKETCHAT_BOT_USERNAME=geekbot -ROCKETCHAT_BOT_PASSWORD=your-password -# Alternative: read password from a file (avoids shell/env escaping issues) -# ROCKETCHAT_BOT_PASSWORD_FILE=/run/secrets/bot_password +# Auth method 1: Personal Access Token (recommended — no special char issues) +# Generate at: Account → Personal Access Tokens +ROCKETCHAT_BOT_TOKEN= +ROCKETCHAT_BOT_USER_ID= + +# Auth method 2: Password (alternative if you don't use PAT) +# ROCKETCHAT_BOT_PASSWORD=your-password ROCKETCHAT_MAIN_ADMIN=admin_username STANDUP_DB_PATH=/data/standup-bot.db diff --git a/README.md b/README.md index e2d0261..c693efa 100644 --- a/README.md +++ b/README.md @@ -56,8 +56,9 @@ cp .env.example .env |----------|----------|---------|-------------| | `ROCKETCHAT_SERVER_URL` | Yes | — | Rocket.Chat server URL | | `ROCKETCHAT_BOT_USERNAME` | Yes | — | Bot account username | -| `ROCKETCHAT_BOT_PASSWORD` | One of | — | Bot account password (use if password has no special chars) | -| `ROCKETCHAT_BOT_PASSWORD_FILE` | One of | — | Path to file containing the bot password (avoids shell/env escaping) | +| `ROCKETCHAT_BOT_TOKEN` | One of | — | Personal Access Token (recommended — no special char issues) | +| `ROCKETCHAT_BOT_USER_ID` | One of | — | User ID for the PAT | +| `ROCKETCHAT_BOT_PASSWORD` | One of | — | Bot account password (alternative auth method) | | `ROCKETCHAT_MAIN_ADMIN` | Yes | — | Rocket.Chat username of the main bot administrator | | `STANDUP_DB_PATH` | No | `~/standup-bot.db` | Path to the SQLite database file | diff --git a/cmd/bot/main.go b/cmd/bot/main.go index 3425a9f..9010110 100644 --- a/cmd/bot/main.go +++ b/cmd/bot/main.go @@ -104,7 +104,7 @@ func main() { } }) - if err := client.Connect(cfg.BotUser, cfg.BotPass); err != nil { + if err := client.Connect(cfg.BotUser, cfg.BotPass, cfg.BotToken, cfg.BotUserID); err != nil { log.Printf("Failed to connect: %v", err) if strings.Contains(err.Error(), "authentication failed") { log.Println("Auth failure is permanent — exiting to prevent Docker restart loop") diff --git a/internal/config/config.go b/internal/config/config.go index 5ad9034..ef56026 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -7,10 +7,12 @@ import ( ) type Config struct { - ServerURL string - BotUser string - BotPass string - MainAdmin string + ServerURL string + BotUser string + BotPass string + BotToken string + BotUserID string + MainAdmin string } func Load() (*Config, error) { @@ -29,6 +31,8 @@ func Load() (*Config, error) { ServerURL: os.Getenv("ROCKETCHAT_SERVER_URL"), BotUser: os.Getenv("ROCKETCHAT_BOT_USERNAME"), BotPass: botPass, + BotToken: os.Getenv("ROCKETCHAT_BOT_TOKEN"), + BotUserID: os.Getenv("ROCKETCHAT_BOT_USER_ID"), MainAdmin: os.Getenv("ROCKETCHAT_MAIN_ADMIN"), } @@ -38,8 +42,12 @@ func Load() (*Config, error) { if cfg.BotUser == "" { return nil, fmt.Errorf("ROCKETCHAT_BOT_USERNAME is required") } - if cfg.BotPass == "" { - return nil, fmt.Errorf("ROCKETCHAT_BOT_PASSWORD is required") + + hasToken := cfg.BotToken != "" && cfg.BotUserID != "" + hasPass := cfg.BotPass != "" + + if !hasToken && !hasPass { + return nil, fmt.Errorf("either ROCKETCHAT_BOT_PASSWORD or (ROCKETCHAT_BOT_TOKEN + ROCKETCHAT_BOT_USER_ID) must be set") } if cfg.MainAdmin == "" { return nil, fmt.Errorf("ROCKETCHAT_MAIN_ADMIN is required") diff --git a/internal/rocket/client.go b/internal/rocket/client.go index 1bc068a..13e772d 100644 --- a/internal/rocket/client.go +++ b/internal/rocket/client.go @@ -41,6 +41,8 @@ type Client struct { connected bool user string password string + token string + userID string } type MessageHandler func(msg IncomingMessage) @@ -66,12 +68,14 @@ func New(serverURL string) (*Client, error) { }, nil } -func (c *Client) Connect(user, password string) error { +func (c *Client) Connect(user, password, token, userID string) error { c.mu.Lock() defer c.mu.Unlock() c.user = user c.password = password + c.token = token + c.userID = userID log.Printf("Connecting to Rocket.Chat at %s", c.serverURL.String()) @@ -93,10 +97,19 @@ func (c *Client) connect() error { return fmt.Errorf("realtime connect: %w", err) } - creds := &models.UserCredentials{ - Email: c.user, - Password: c.password, + var creds *models.UserCredentials + if c.token != "" && c.userID != "" { + creds = &models.UserCredentials{ + ID: c.userID, + Token: c.token, + } + } else { + creds = &models.UserCredentials{ + Email: c.user, + Password: c.password, + } } + if _, err := rt.Login(creds); err != nil { rt.Close() return fmt.Errorf("realtime login: %w", err)