init: Initial commit

This commit is contained in:
Björn Benouarets
2026-01-20 06:53:05 +01:00
commit fc8238759a
31 changed files with 1384 additions and 0 deletions

123
app/bot/auth.go Normal file
View File

@@ -0,0 +1,123 @@
package bot
import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"time"
"git.secnex.io/secnex/masterlog"
)
var AUTH *Auth
type Token struct {
Data map[string]interface{}
ExpiresIn int `json:"expires_in"`
ExpiresAt time.Time `json:"expires_at"`
AccessToken string `json:"access_token"`
}
type Auth struct {
TenantId string
AppId string
AppSecret string
token *Token
}
func NewAuth(tenantId, appId, appSecret string) *Auth {
auth := &Auth{
TenantId: tenantId,
AppId: appId,
AppSecret: appSecret,
}
AUTH = auth
return auth
}
func (a *Auth) getRequestUrl() string {
return fmt.Sprintf("https://login.microsoftonline.com/%s/oauth2/v2.0/token", a.TenantId)
}
func (a *Auth) requestToken() (*Token, error) {
if a.TenantId == "" {
return nil, fmt.Errorf("tenant ID is required but not set")
}
if a.AppId == "" {
return nil, fmt.Errorf("app ID is required but not set")
}
if a.AppSecret == "" {
return nil, fmt.Errorf("app secret is required but not set")
}
requestUrl := a.getRequestUrl()
masterlog.Debug("Requesting token", map[string]interface{}{
"url": requestUrl,
"tenantId": a.TenantId,
"appId": a.AppId,
})
body := url.Values{
"grant_type": []string{"client_credentials"},
"client_id": []string{a.AppId},
"client_secret": []string{a.AppSecret},
"scope": []string{"https://api.botframework.com/.default"},
}
req, err := http.NewRequest("POST", requestUrl, strings.NewReader(body.Encode()))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
bodyBytes, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
masterlog.Error("Token request failed", map[string]interface{}{
"statusCode": resp.StatusCode,
})
return nil, fmt.Errorf("token request failed with status %d: %s", resp.StatusCode, string(bodyBytes))
}
masterlog.Debug("Token request successful", map[string]interface{}{
"statusCode": resp.StatusCode,
})
if len(bodyBytes) == 0 {
return nil, fmt.Errorf("empty response body from token endpoint")
}
var token Token
err = json.Unmarshal(bodyBytes, &token)
if err != nil {
masterlog.Error("Failed to unmarshal token response", map[string]interface{}{
"error": err.Error(),
})
return nil, fmt.Errorf("failed to unmarshal token response: %w", err)
}
token.ExpiresAt = time.Now().Add(time.Duration(token.ExpiresIn) * time.Second)
masterlog.Debug("Requested token", map[string]interface{}{"accessToken": token.AccessToken, "expiresIn": token.ExpiresIn, "expiresAt": token.ExpiresAt})
return &token, nil
}
func (a *Auth) GetToken() (string, error) {
if a.token == nil || a.token.ExpiresAt.Before(time.Now()) {
token, err := a.requestToken()
if err != nil {
masterlog.Error("Failed to refresh token", map[string]interface{}{"error": err.Error()})
return "", err
}
a.token = token
masterlog.Debug("Refreshed token", map[string]interface{}{"token": a.token.AccessToken})
}
return a.token.AccessToken, nil
}

56
app/bot/conversation.go Normal file
View File

@@ -0,0 +1,56 @@
package bot
import (
"errors"
"fmt"
"io"
"net/http"
"strings"
"git.secnex.io/secnex/masterlog"
"git.secnex.io/secnex/taro-bot/config"
)
type Conversation struct {
auth Auth
}
func NewConversation(auth Auth) *Conversation {
return &Conversation{
auth: auth,
}
}
func (c *Conversation) SendMessage(message *Message) error {
token, err := c.auth.GetToken()
if err != nil {
return err
}
json, err := message.ToJSON()
if err != nil {
return err
}
trafficManagerUrl := config.CONFIG.MicrosoftTeamsBotUrl
requestUrl := fmt.Sprintf("%s/v3/conversations", trafficManagerUrl)
req, err := http.NewRequest("POST", requestUrl, strings.NewReader(json))
if err != nil {
return err
}
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
statusCode := resp.StatusCode
masterlog.Info("Sent message via Teams Bot successfully", map[string]interface{}{"statusCode": statusCode})
if statusCode >= 300 {
return errors.New(string(body))
}
return nil
}

53
app/bot/message.go Normal file
View File

@@ -0,0 +1,53 @@
package bot
import "encoding/json"
type Message struct {
IsGroup bool `json:"isGroup"`
ChannelData ChannelData `json:"channelData"`
Activity Activity `json:"activity"`
}
type ChannelData struct {
Channel Channel `json:"channel"`
}
type Channel struct {
ID string `json:"id"`
}
type Activity struct {
Type string `json:"type"`
Text string `json:"text"`
}
func NewMessage(isGroup bool, channelData ChannelData, activity Activity) *Message {
return &Message{
IsGroup: isGroup,
ChannelData: channelData,
Activity: activity,
}
}
func NewTextPost(channelId, text string) *Message {
return &Message{
IsGroup: true,
ChannelData: ChannelData{
Channel: Channel{
ID: channelId,
},
},
Activity: Activity{
Type: "message",
Text: text,
},
}
}
func (m *Message) ToJSON() (string, error) {
jsonBytes, err := json.Marshal(m)
if err != nil {
return "", err
}
return string(jsonBytes), nil
}