Files
worker/app/models/notification_credential.go
Gleb Tv 2c7a0236da feat: publish standalone worker
Separate worker packaging and service lifecycle from the control plane.
2026-07-13 17:55:14 +03:00

168 строки
5.6 KiB
Go

package models
import (
"encoding/base64"
"gorm.io/datatypes"
"rsgit.ru/rsmon/rsmon/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
}