93 lines
3.3 KiB
Go
93 lines
3.3 KiB
Go
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
|
||
|
|
}
|