Files
story/internal/config/tencent_sms_file.go
T

93 lines
3.3 KiB
Go
Raw Normal View History

package config
import (
"bytes"
"encoding/json"
"errors"
"io"
"os"
"path/filepath"
"strings"
)
const defaultTencentSMSConfigFile = "/run/secrets/voice-runtime/tencent-sms.json"
type tencentSMSFile struct {
TemplateParams map[string][]string `json:"template_params"`
Enabled *bool `json:"enabled"`
SecretID string `json:"secret_id"`
SecretKey string `json:"secret_key"`
SDKAppID string `json:"sdk_app_id"`
SignName string `json:"sign_name"`
Templates map[string]string `json:"templates"`
}
// Only API and worker consume SMS configuration. In particular, a mounted
// configuration must not enable onboarding in callbacks or other services.
func (c *OnboardingConfig) loadTencentSMSFile(serviceName string) error {
if serviceName != "api" && serviceName != "worker" {
return nil
}
path := strings.TrimSpace(os.Getenv("TENCENT_SMS_CONFIG_FILE"))
explicit := path != ""
if !explicit {
path = defaultTencentSMSConfigFile
// The conventional mount exists only on Unix hosts/containers. Windows
// development can still opt in with an explicit absolute file path.
if !filepath.IsAbs(path) {
return nil
}
}
return c.readTencentSMSFile(path, explicit)
}
func (c *OnboardingConfig) readTencentSMSFile(path string, required bool) error {
if !filepath.IsAbs(path) {
return errors.New("TENCENT_SMS_CONFIG_FILE must be an absolute path")
}
file, err := os.Open(filepath.Clean(path))
if errors.Is(err, os.ErrNotExist) && !required {
return nil
}
if err != nil {
return errors.New("Tencent SMS configuration file is unavailable")
}
defer file.Close()
info, err := file.Stat()
if err != nil || !info.Mode().IsRegular() || info.Size() > 64*1024 {
return errors.New("Tencent SMS configuration must be a regular file of at most 64 KiB")
}
raw, err := io.ReadAll(io.LimitReader(file, 64*1024+1))
if err != nil || len(raw) > 64*1024 {
return errors.New("Tencent SMS configuration file could not be read")
}
// Accept UTF-8 BOM from Windows editors. Never include raw JSON or parser
// errors in startup errors: unknown fields may accidentally contain secrets.
raw = bytes.TrimPrefix(raw, []byte{0xef, 0xbb, 0xbf})
decoder := json.NewDecoder(bytes.NewReader(raw))
decoder.DisallowUnknownFields()
var in tencentSMSFile
if err := decoder.Decode(&in); err != nil || in.Enabled == nil {
return errors.New("Tencent SMS configuration must match the JSON template and include enabled")
}
if err := decoder.Decode(new(any)); err != io.EOF {
return errors.New("Tencent SMS configuration must contain exactly one JSON object")
}
for key, value := range in.Templates {
if key != "login" && key != "register" && key != "change_password" && key != "change_phone" {
return errors.New("Tencent SMS template names must be login, register, change_password or change_phone")
}
in.Templates[key] = strings.TrimSpace(value)
}
c.SMSProvider = "tencent"
c.SMSLoginEnabled = *in.Enabled
c.TencentSMSSecretID = strings.TrimSpace(in.SecretID)
c.TencentSMSSecretKey = strings.TrimSpace(in.SecretKey)
c.TencentSMSAppID = strings.TrimSpace(in.SDKAppID)
c.TencentSMSSignName = strings.TrimSpace(in.SignName)
c.TencentSMSTemplateID = ""
c.TencentSMSTemplates = in.Templates
c.TencentSMSTemplateParams = in.TemplateParams
return nil
}