package models import ( "encoding/base64" "gorm.io/datatypes" "rocketgit.ru/rsmon/worker/app/models/concerns" ) // NotificationCredential kinds stored in the database. const ( CredentialKindSMTP = "smtp" CredentialKindTelegram = "telegram" ) // NotificationCredential stores per-method delivery credentials (SMTP login, // Telegram bot token, etc.) encrypted at rest. Credentials are pushed to // workers through the init/config websocket refresh (see docs/worker-protocol.md // "Credentials Push"). type NotificationCredential struct { concerns.Model // AccountID is nil for platform-managed credentials and set for credentials // owned by one customer account. AccountID *int64 `gorm:"type:bigint REFERENCES accounts(id) ON DELETE CASCADE;index" json:"account_id,omitempty"` Kind string `gorm:"not null" json:"kind"` Name string `gorm:"not null" json:"name"` // SMTP-specific Server *string `json:"server,omitempty"` Port *int `json:"port,omitempty"` Login *string `json:"login,omitempty"` FromName *string `json:"from_name,omitempty"` FromAddr *string `json:"from_address,omitempty"` InsecureSkipVerify bool `gorm:"default:false" json:"insecure_skip_verify"` // Telegram-specific BotName *string `json:"bot_name,omitempty"` APIURL *string `json:"api_url,omitempty"` WebhookToken string `gorm:"size:128" json:"webhook_token,omitempty"` // SecretEnc holds the encrypted (or "plain:"-prefixed fallback) secret // value — SMTP password or Telegram bot token. Decrypt via GetSecret. SecretEnc string `gorm:"column:secret;type:text" json:"-"` SecretMasked string `gorm:"-" json:"secret_masked,omitempty"` Enabled *bool `gorm:"not null;default:true" json:"enabled"` Meta datatypes.JSON `gorm:"type:jsonb;default:'{}'" json:"meta,omitempty"` concerns.Timestamped Audited } // SetSecret encrypts and stores the plaintext secret value. func (c *NotificationCredential) SetSecret(plaintext string) error { enc, err := encryptSecret(plaintext) if err != nil { return err } c.SecretEnc = enc return nil } // GetSecret decrypts and returns the secret value. func (c *NotificationCredential) GetSecret() (string, error) { return decryptSecret(c.SecretEnc) } // FillSecretMasked populates SecretMasked with a display-safe version of the credential secret. func (c *NotificationCredential) FillSecretMasked() { secret, err := c.GetSecret() if err != nil { return } c.SecretMasked = maskSecret(secret) } // EnsureWebhookToken creates the per-bot webhook URL token if it is missing. func (c *NotificationCredential) EnsureWebhookToken() { if c.WebhookToken != "" { return } c.WebhookToken = base64.RawURLEncoding.EncodeToString(concerns.RandomToken(32)) } func maskSecret(secret string) string { if secret == "" { return "" } if len(secret) == 1 { return secret } return secret[:1] + "***" + secret[len(secret)-1:] } // TableName overrides the default table name. func (NotificationCredential) TableName() string { return "notification_credentials" } // AllNotificationCredentials returns all credentials ordered by kind and name. func AllNotificationCredentials() ([]NotificationCredential, error) { var creds []NotificationCredential err := DB().Where("account_id IS NULL").Order("kind ASC, name ASC").Find(&creds).Error for i := range creds { creds[i].FillSecretMasked() } return creds, err } // AccountNotificationCredentials returns credentials owned by accountID. func AccountNotificationCredentials(accountID int64) ([]NotificationCredential, error) { var creds []NotificationCredential err := DB().Where("account_id = ?", accountID).Order("kind ASC, name ASC").Find(&creds).Error for i := range creds { creds[i].FillSecretMasked() } return creds, err } // EnabledCredentialsByKind returns enabled credentials of the given kind. func EnabledCredentialsByKind(kind string) ([]NotificationCredential, error) { var creds []NotificationCredential err := DB().Where("account_id IS NULL AND kind = ? AND enabled = ?", kind, true).Order("name ASC").Find(&creds).Error return creds, err } // EnabledCredentialsByAccountAndKind returns only enabled credentials owned by // accountID. It never falls back to platform credentials. func EnabledCredentialsByAccountAndKind(accountID int64, kind string) ([]NotificationCredential, error) { var creds []NotificationCredential err := DB().Where("account_id = ? AND kind = ? AND enabled = ?", accountID, kind, true).Order("name ASC").Find(&creds).Error return creds, err } // FindCredential returns a credential by id. func FindCredential(id int64) (*NotificationCredential, error) { var c NotificationCredential if err := DB().First(&c, id).Error; err != nil { return nil, err } c.FillSecretMasked() return &c, nil } // FindCredentialByName returns a credential by kind and name. func FindCredentialByName(kind, name string) (*NotificationCredential, error) { var c NotificationCredential if err := DB().Where("kind = ? AND name = ?", kind, name).First(&c).Error; err != nil { return nil, err } return &c, nil } // FindTelegramCredentialByWebhookToken returns an enabled Telegram credential by webhook token. func FindTelegramCredentialByWebhookToken(token string) (*NotificationCredential, error) { var c NotificationCredential if err := DB().Where("kind = ? AND webhook_token = ? AND enabled = ?", CredentialKindTelegram, token, true).First(&c).Error; err != nil { return nil, err } return &c, nil } // DeleteCredential removes a credential by id. func DeleteCredential(id int64) error { return DB().Delete(&NotificationCredential{}, id).Error }