feat: publish standalone worker
Separate worker packaging and service lifecycle from the control plane.
Этот коммит содержится в:
167
app/models/notification_credential.go
Обычный файл
167
app/models/notification_credential.go
Обычный файл
@@ -0,0 +1,167 @@
|
||||
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
|
||||
}
|
||||
Ссылка в новой задаче
Block a user