93 lines
2.9 KiB
Go
93 lines
2.9 KiB
Go
package config
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"bytes"
|
||
|
|
"encoding/json"
|
||
|
|
"errors"
|
||
|
|
"io"
|
||
|
|
"os"
|
||
|
|
"path/filepath"
|
||
|
|
"strconv"
|
||
|
|
"strings"
|
||
|
|
)
|
||
|
|
|
||
|
|
// Credentials are server-only; policy is stored separately in PostgreSQL.
|
||
|
|
type CaptchaClientConfig struct {
|
||
|
|
AppID string `json:"captcha_app_id"`
|
||
|
|
AppSecretKey string `json:"app_secret_key"`
|
||
|
|
}
|
||
|
|
type TencentCaptchaConfig struct {
|
||
|
|
SecretID string `json:"secret_id"`
|
||
|
|
SecretKey string `json:"secret_key"`
|
||
|
|
SaaS CaptchaClientConfig `json:"saas"`
|
||
|
|
Mini CaptchaClientConfig `json:"mini_program"`
|
||
|
|
}
|
||
|
|
|
||
|
|
func (c *OnboardingConfig) loadTencentCaptchaFile(service string) error {
|
||
|
|
if service != "api" && service != "worker" {
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
path := strings.TrimSpace(os.Getenv("TENCENT_CAPTCHA_CONFIG_FILE"))
|
||
|
|
required := path != ""
|
||
|
|
if !required {
|
||
|
|
path = "/run/secrets/voice-runtime/tencent-captcha.json"
|
||
|
|
if !filepath.IsAbs(path) {
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if !filepath.IsAbs(path) {
|
||
|
|
return errors.New("TENCENT_CAPTCHA_CONFIG_FILE must be absolute")
|
||
|
|
}
|
||
|
|
f, err := os.Open(filepath.Clean(path))
|
||
|
|
if errors.Is(err, os.ErrNotExist) && !required {
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
if err != nil {
|
||
|
|
return errors.New("Tencent CAPTCHA configuration file unavailable")
|
||
|
|
}
|
||
|
|
defer f.Close()
|
||
|
|
info, err := f.Stat()
|
||
|
|
if err != nil || !info.Mode().IsRegular() || info.Size() > 16384 {
|
||
|
|
return errors.New("Tencent CAPTCHA configuration must be a regular file of at most 16 KiB")
|
||
|
|
}
|
||
|
|
raw, err := io.ReadAll(io.LimitReader(f, 16385))
|
||
|
|
if err != nil || len(raw) > 16384 {
|
||
|
|
return errors.New("Tencent CAPTCHA configuration cannot be read")
|
||
|
|
}
|
||
|
|
d := json.NewDecoder(bytes.NewReader(bytes.TrimPrefix(raw, []byte{0xef, 0xbb, 0xbf})))
|
||
|
|
d.DisallowUnknownFields()
|
||
|
|
var in TencentCaptchaConfig
|
||
|
|
if d.Decode(&in) != nil || d.Decode(new(any)) != io.EOF {
|
||
|
|
return errors.New("Tencent CAPTCHA configuration must match the JSON template")
|
||
|
|
}
|
||
|
|
if err = in.Validate(); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
c.TencentCaptcha = in
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (c *TencentCaptchaConfig) Validate() error {
|
||
|
|
c.SecretID = strings.TrimSpace(c.SecretID)
|
||
|
|
c.SecretKey = strings.TrimSpace(c.SecretKey)
|
||
|
|
if (c.SecretID == "") != (c.SecretKey == "") {
|
||
|
|
return errors.New("Tencent CAPTCHA requires both SecretId and SecretKey")
|
||
|
|
}
|
||
|
|
for _, client := range []*CaptchaClientConfig{&c.SaaS, &c.Mini} {
|
||
|
|
client.AppID = strings.TrimSpace(client.AppID)
|
||
|
|
client.AppSecretKey = strings.TrimSpace(client.AppSecretKey)
|
||
|
|
if client.AppID == "" && client.AppSecretKey == "" {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
id, err := strconv.ParseUint(client.AppID, 10, 64)
|
||
|
|
if err != nil || id == 0 || len(client.AppSecretKey) < 16 || len(client.AppSecretKey) > 256 || c.SecretID == "" {
|
||
|
|
return errors.New("Tencent CAPTCHA client requires numeric CaptchaAppId, AppSecretKey and API credentials")
|
||
|
|
}
|
||
|
|
client.AppID = strconv.FormatUint(id, 10)
|
||
|
|
}
|
||
|
|
if c.SaaS.AppID != "" && c.SaaS.AppID == c.Mini.AppID {
|
||
|
|
return errors.New("SaaS and mini-program CAPTCHA must use different CaptchaAppIds")
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|