feat: expand companion platform, device controls, and memoir workflows
ci / verify (push) Canceled after 0s
ci / verify (push) Canceled after 0s
Add customer and operator login, BLE provisioning, device fleet controls, call memory, memoir generation, and refreshed family and maintenance portals. Include API contracts, migrations, firmware sources, tests, assets, and deployment documentation.
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
package onboarding
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
|
||||
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
|
||||
sms "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/sms/v20210111"
|
||||
)
|
||||
|
||||
// Tencent SMS accepts a request before the carrier delivers it. A failed or
|
||||
// ambiguous call is never retried here: the challenge remains subject to the
|
||||
// existing resend limits and cannot cause an accidental duplicate charge.
|
||||
type tencentSMSSender interface {
|
||||
SendSmsWithContext(context.Context, *sms.SendSmsRequest) (*sms.SendSmsResponse, error)
|
||||
}
|
||||
|
||||
type TencentSMSProvider struct {
|
||||
parameters map[string][]string
|
||||
client tencentSMSSender
|
||||
appID, signName string
|
||||
templates map[string]string
|
||||
}
|
||||
|
||||
var mainlandPhonePattern = regexp.MustCompile(`^\+86[1][3-9][0-9]{9}$`)
|
||||
var smsTemplateIDPattern = regexp.MustCompile(`^[0-9]+$`)
|
||||
|
||||
const (
|
||||
SMSPurposeLogin = "LOGIN"
|
||||
SMSPurposeRegister = "REGISTER"
|
||||
SMSPurposeChangePassword = "CHANGE_PASSWORD"
|
||||
SMSPurposeChangePhone = "CHANGE_PHONE"
|
||||
)
|
||||
|
||||
var smsTemplateKeys = map[string]string{
|
||||
SMSPurposeLogin: "login", SMSPurposeRegister: "register",
|
||||
SMSPurposeChangePassword: "change_password", SMSPurposeChangePhone: "change_phone",
|
||||
}
|
||||
|
||||
func NewTencentSMSProvider(secretID, secretKey, appID, signName, templateID string) (SMSProvider, error) {
|
||||
// Keep the original single-template environment configuration compatible.
|
||||
return NewTencentSMSProviderWithTemplates(secretID, secretKey, appID, signName, map[string]string{
|
||||
"login": templateID, "register": templateID, "change_password": templateID, "change_phone": templateID,
|
||||
})
|
||||
}
|
||||
|
||||
func NewTencentSMSProviderWithTemplates(secretID, secretKey, appID, signName string, templates map[string]string, parameterOptions ...map[string][]string) (SMSProvider, error) {
|
||||
if strings.TrimSpace(secretID) == "" || strings.TrimSpace(secretKey) == "" ||
|
||||
!smsTemplateIDPattern.MatchString(appID) || strings.TrimSpace(signName) == "" {
|
||||
return nil, errors.New("Tencent SMS requires SecretId, SecretKey, numeric SDK AppID and approved sign name")
|
||||
}
|
||||
copyTemplates := make(map[string]string, len(smsTemplateKeys))
|
||||
parameters := make(map[string][]string, len(smsTemplateKeys))
|
||||
for _, key := range smsTemplateKeys {
|
||||
if !smsTemplateIDPattern.MatchString(templates[key]) {
|
||||
return nil, errors.New("Tencent SMS requires numeric login, register, change_password and change_phone template IDs")
|
||||
}
|
||||
copyTemplates[key] = templates[key]
|
||||
parameters[key] = []string{"code"}
|
||||
}
|
||||
if len(parameterOptions) > 1 {
|
||||
return nil, errors.New("Tencent SMS accepts one template parameter configuration")
|
||||
}
|
||||
if len(parameterOptions) == 1 {
|
||||
for key, values := range parameterOptions[0] {
|
||||
if _, ok := copyTemplates[key]; !ok {
|
||||
return nil, errors.New("Tencent SMS template parameters have an unknown purpose")
|
||||
}
|
||||
seen := map[string]bool{}
|
||||
for _, value := range values {
|
||||
if (value != "code" && value != "expires_minutes") || seen[value] {
|
||||
return nil, errors.New("Tencent SMS template parameters must be code and optionally expires_minutes")
|
||||
}
|
||||
seen[value] = true
|
||||
}
|
||||
if !seen["code"] {
|
||||
return nil, errors.New("Tencent SMS template parameters must include code")
|
||||
}
|
||||
parameters[key] = append([]string(nil), values...)
|
||||
}
|
||||
}
|
||||
clientProfile := profile.NewClientProfile()
|
||||
clientProfile.HttpProfile.Endpoint = "sms.tencentcloudapi.com"
|
||||
clientProfile.HttpProfile.ReqTimeout = 10
|
||||
clientProfile.NetworkFailureMaxRetries = 0
|
||||
clientProfile.RateLimitExceededMaxRetries = 0
|
||||
clientProfile.UnsafeRetryOnConnectionFailure = false
|
||||
client, err := sms.NewClient(common.NewCredential(secretID, secretKey), "ap-guangzhou", clientProfile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &TencentSMSProvider{client: client, appID: appID, signName: signName, templates: copyTemplates, parameters: parameters}, nil
|
||||
}
|
||||
|
||||
func (p *TencentSMSProvider) Send(ctx context.Context, phone, code, id string) error {
|
||||
return p.SendForPurpose(ctx, phone, code, id, SMSPurposeLogin)
|
||||
}
|
||||
|
||||
func (p *TencentSMSProvider) SendForPurpose(ctx context.Context, phone, code, id, purpose string) error {
|
||||
templateID := p.templates[smsTemplateKeys[purpose]]
|
||||
if templateID == "" || !mainlandPhonePattern.MatchString(phone) || !smsCodePattern.MatchString(code) || !uuidV4(id) {
|
||||
return errors.New("SMS_GATEWAY_REJECTED")
|
||||
}
|
||||
req := sms.NewSendSmsRequest()
|
||||
req.PhoneNumberSet = common.StringPtrs([]string{phone})
|
||||
req.SmsSdkAppId = common.StringPtr(p.appID)
|
||||
req.SignName = common.StringPtr(p.signName)
|
||||
req.TemplateId = common.StringPtr(templateID)
|
||||
values := make([]string, 0, len(p.parameters[smsTemplateKeys[purpose]]))
|
||||
for _, name := range p.parameters[smsTemplateKeys[purpose]] {
|
||||
if name == "code" {
|
||||
values = append(values, code)
|
||||
} else {
|
||||
values = append(values, strconv.Itoa(int(smsCodeLifetime/time.Minute)))
|
||||
}
|
||||
}
|
||||
req.TemplateParamSet = common.StringPtrs(values)
|
||||
req.SessionContext = common.StringPtr(id)
|
||||
result, err := p.client.SendSmsWithContext(ctx, req)
|
||||
if err != nil {
|
||||
return errors.New("SMS_GATEWAY_UNCERTAIN")
|
||||
}
|
||||
if result == nil || result.Response == nil || len(result.Response.SendStatusSet) != 1 ||
|
||||
result.Response.SendStatusSet[0] == nil || result.Response.SendStatusSet[0].Code == nil ||
|
||||
*result.Response.SendStatusSet[0].Code != "Ok" {
|
||||
return errors.New("SMS_GATEWAY_REJECTED")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user