feat(auth): 登录注册 - 添加短信通知与组合验证支持
This commit is contained in:
1 parent
6c013ebf7e
commit
bc0d109428
55 files changed
+4013
-103
No files matched your search
@@ -117,7 +117,9 @@ func (s *Service) authDomain() *auth.Service {
|
||||
if s.auth != nil {
|
||||
return s.auth
|
||||
}
|
||||
return auth.New(s.repo, authHost{svc: s}, nil)
|
||||
domain := auth.New(s.repo, authHost{svc: s}, nil)
|
||||
domain.SetSMSDelivery(s.smsDomain())
|
||||
return domain
|
||||
}
|
||||
|
||||
func (s *Service) PublicAuthSettings() (*PublicAuthSettings, error) {
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"infinite-canvas/backend/internal/auth"
|
||||
"infinite-canvas/backend/internal/model"
|
||||
"infinite-canvas/backend/internal/sms"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
type SMSChannelRequest = sms.ChannelRequest
|
||||
type VerificationPolicy = auth.VerificationPolicy
|
||||
type VerificationRequest = auth.VerificationRequest
|
||||
type VerificationConfirm = auth.VerificationConfirm
|
||||
|
||||
func (s *Service) newSMSDomain() *sms.Service {
|
||||
return sms.New(s.repo, authHost{svc: s}, func(id string) (bool, error) {
|
||||
items := s.Plugins()
|
||||
runtime, exists := runtimePluginByID(items, id)
|
||||
if !exists || runtime.Status != "enabled" {
|
||||
return false, nil
|
||||
}
|
||||
state, err := s.pluginStateForUser(nil, id, items)
|
||||
return state.EffectiveEnabled, err
|
||||
})
|
||||
}
|
||||
func (s *Service) smsDomain() *sms.Service {
|
||||
if s.sms != nil {
|
||||
return s.sms
|
||||
}
|
||||
return s.newSMSDomain()
|
||||
}
|
||||
func (s *Service) AdminSMSChannels(actor *model.User) ([]sms.ChannelView, error) {
|
||||
return s.smsDomain().Channels(actor)
|
||||
}
|
||||
func (s *Service) SaveSMSChannel(actor *model.User, id string, req SMSChannelRequest) (*sms.ChannelView, error) {
|
||||
result, err := s.smsDomain().SaveChannel(actor, id, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := s.appendAdminAudit(actor, "sms.channel.save", "sms_channel", result.ID, "保存短信渠道", map[string]any{"provider": result.Provider, "enabled": result.Enabled}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
func (s *Service) DeleteSMSChannel(actor *model.User, id string) error {
|
||||
if err := s.smsDomain().DeleteChannel(actor, id); err != nil {
|
||||
return err
|
||||
}
|
||||
return s.appendAdminAudit(actor, "sms.channel.delete", "sms_channel", id, "删除短信渠道", nil)
|
||||
}
|
||||
func (s *Service) AdminSMSRecords(actor *model.User, channel, state string, page, size int) (*sms.RecordPage, error) {
|
||||
return s.smsDomain().Records(actor, channel, state, page, size)
|
||||
}
|
||||
func (s *Service) TestSMSChannel(actor *model.User, ctx context.Context, id, purpose, phone string) (*model.SMSRecord, error) {
|
||||
if err := s.RequireAdmin(actor); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
value, err := rand.Int(rand.Reader, big.NewInt(1000000))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.smsDomain().Test(actor, ctx, id, purpose, phone, fmt.Sprintf("%06d", value.Int64()))
|
||||
}
|
||||
func (s *Service) AdminVerificationPolicy(actor *model.User) (*VerificationPolicy, error) {
|
||||
return s.authDomain().AdminVerificationPolicy(actor)
|
||||
}
|
||||
func (s *Service) UpdateVerificationPolicy(actor *model.User, p VerificationPolicy) (*VerificationPolicy, error) {
|
||||
result, err := s.authDomain().UpdateVerificationPolicy(actor, p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := s.appendAdminAudit(actor, "auth.policy.update", "setting", "auth_verification_policy", "更新验证码登录注册策略", nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
func (s *Service) StartVerification(ctx context.Context, actor *model.User, req VerificationRequest) (*auth.VerificationTicket, error) {
|
||||
return s.authDomain().StartVerification(ctx, actor, req)
|
||||
}
|
||||
func (s *Service) LoginVerification(req VerificationConfirm) (*AuthSessionResult, error) {
|
||||
return s.authDomain().LoginVerification(req)
|
||||
}
|
||||
func (s *Service) BindVerification(actor *model.User, req VerificationConfirm) (*AuthUser, error) {
|
||||
return s.authDomain().BindVerification(actor, req)
|
||||
}
|
||||
@@ -23,6 +23,7 @@ const (
|
||||
PluginKindProtocol = "protocol"
|
||||
PluginKindApplication = "application"
|
||||
PluginKindPayment = "payment"
|
||||
PluginKindSMS = "sms"
|
||||
|
||||
PluginScopeSystem = "system"
|
||||
PluginScopeUser = "user"
|
||||
@@ -112,6 +113,9 @@ func pluginManagement(pluginID string, source string) PluginManagementView {
|
||||
if policy, ok := systemPaymentPolicies[strings.TrimSpace(pluginID)]; ok {
|
||||
return policy
|
||||
}
|
||||
if policy, ok := systemSMSPolicies[strings.TrimSpace(pluginID)]; ok {
|
||||
return policy
|
||||
}
|
||||
return PluginManagementView{
|
||||
Origin: PluginOriginOfficial, Kind: PluginKindProtocol,
|
||||
ActivationScope: PluginScopeSystem, ConfigurationScope: PluginConfigurationSystem,
|
||||
@@ -147,6 +151,10 @@ func knownPluginIDs(items []PluginView) []string {
|
||||
seen[id] = struct{}{}
|
||||
ids = append(ids, id)
|
||||
}
|
||||
for id := range systemSMSPolicies {
|
||||
seen[id] = struct{}{}
|
||||
ids = append(ids, id)
|
||||
}
|
||||
for _, item := range items {
|
||||
if _, exists := seen[item.Manifest.ID]; exists {
|
||||
continue
|
||||
@@ -338,6 +346,9 @@ func (s *Service) SetPluginPlatformAvailability(actor *model.User, pluginID stri
|
||||
}
|
||||
|
||||
func isKnownRuntimeOptionalPlugin(pluginID string) bool {
|
||||
if isSystemSMSPluginID(pluginID) {
|
||||
return true
|
||||
}
|
||||
if _, known := officialApplicationPolicies[pluginID]; known {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -191,6 +191,7 @@ func (c *pluginRuntime) bootstrapBuiltInPlugins() error {
|
||||
byID[id] = record
|
||||
}
|
||||
bundledManifests := append(bundledWorkflowPluginManifests(), bundledPaymentPluginManifests()...)
|
||||
bundledManifests = append(bundledManifests, bundledSMSPluginManifests()...)
|
||||
for _, bundled := range bundledManifests {
|
||||
builtInIDs[bundled.Metadata.ID] = struct{}{}
|
||||
if existing, exists := byID[bundled.Metadata.ID]; exists && existing.PackagePath != "" && isSystemPaymentPluginID(bundled.Metadata.ID) {
|
||||
@@ -222,6 +223,9 @@ func (c *pluginRuntime) bootstrapBuiltInPlugins() error {
|
||||
if _, systemPayment := systemPaymentPolicies[bundled.Metadata.ID]; systemPayment {
|
||||
source = PluginOriginSystem
|
||||
}
|
||||
if isSystemSMSPluginID(bundled.Metadata.ID) {
|
||||
source = PluginOriginSystem
|
||||
}
|
||||
record.UpdatedAt = now
|
||||
record.ID, record.Raw, record.Source, record.PackagePath = bundled.Metadata.ID, data, source, ""
|
||||
byID[bundled.Metadata.ID] = record
|
||||
@@ -343,7 +347,12 @@ func (c *pluginRuntime) reload() error {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if len(manifest.Contributes.PaymentProviders) > 0 && len(manifest.Contributes.Providers) == 0 {
|
||||
if len(manifest.Contributes.SMSProviders) > 0 && (record.Source == PluginOriginUploaded || !isSystemSMSPluginID(id)) {
|
||||
record.Metadata.Enabled, record.Status, record.Error = false, "invalid", "短信插件仅支持系统宿主适配器"
|
||||
plugins[id] = record
|
||||
continue
|
||||
}
|
||||
if (len(manifest.Contributes.PaymentProviders) > 0 || len(manifest.Contributes.SMSProviders) > 0) && len(manifest.Contributes.Providers) == 0 {
|
||||
if record.Metadata.Enabled {
|
||||
record.Status = "enabled"
|
||||
} else {
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"infinite-canvas/backend/internal/prompts"
|
||||
"infinite-canvas/backend/internal/repository"
|
||||
"infinite-canvas/backend/internal/skills"
|
||||
"infinite-canvas/backend/internal/sms"
|
||||
"infinite-canvas/backend/internal/tools"
|
||||
)
|
||||
|
||||
@@ -71,6 +72,7 @@ type Service struct {
|
||||
tools *tools.Service
|
||||
prompts *prompts.Service
|
||||
auth *auth.Service
|
||||
sms *sms.Service
|
||||
canvas *canvas.Service
|
||||
}
|
||||
|
||||
@@ -121,6 +123,8 @@ func newService(repo *repository.Repository, dataDir string) *Service {
|
||||
service.tools = tools.New(service.repo)
|
||||
service.prompts = prompts.New(service.repo, promptAdminGate{svc: service})
|
||||
service.auth = auth.New(service.repo, authHost{svc: service}, nil)
|
||||
service.sms = service.newSMSDomain()
|
||||
service.auth.SetSMSDelivery(service.sms)
|
||||
service.canvas = canvas.New(service.repo, canvasHost{svc: service})
|
||||
service.platform = platform.New(service.repo, coordinator, platformHost{svc: service})
|
||||
return service
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"infinite-canvas/backend/internal/protocol"
|
||||
"infinite-canvas/backend/internal/sms"
|
||||
)
|
||||
|
||||
var systemSMSPolicies = map[string]PluginManagementView{
|
||||
sms.PluginAliyun: {Origin: PluginOriginSystem, Kind: "sms", ActivationScope: PluginScopeSystem, ConfigurationScope: PluginConfigurationSystem},
|
||||
sms.PluginTencent: {Origin: PluginOriginSystem, Kind: "sms", ActivationScope: PluginScopeSystem, ConfigurationScope: PluginConfigurationSystem},
|
||||
}
|
||||
|
||||
func bundledSMSPluginManifests() []protocol.Manifest {
|
||||
result := make([]protocol.Manifest, 0, 2)
|
||||
for _, p := range []struct{ ID, Provider, Name string }{{sms.PluginAliyun, sms.Aliyun, "阿里云短信"}, {sms.PluginTencent, sms.Tencent, "腾讯云短信"}} {
|
||||
result = append(result, protocol.Manifest{
|
||||
APIVersion: "yingce.plugin/v1",
|
||||
Metadata: protocol.Metadata{ID: p.ID, Version: "1.0.0", Name: p.Name, Vendor: p.Name, Enabled: false, Installable: true, Description: "系统短信插件;支持多渠道、场景模板和发送记录。凭据在短信渠道管理中配置。", Documentation: "# " + p.Name + "\n\n使用 go-sms-sender v0.25.0 聚合库及受控传输扩展。发送受理不等于送达,不自动重试。"},
|
||||
Surfaces: []string{"settings"}, Runtime: protocol.ManifestRuntime{Backend: "host:sms-" + p.Provider, Web: "host"},
|
||||
Permissions: []string{"sms.send"}, Contributes: protocol.ManifestContributions{SMSProviders: []protocol.ManifestSMSProvider{{ID: p.Provider, Label: p.Name}}},
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func isSystemSMSPluginID(id string) bool { _, ok := systemSMSPolicies[id]; return ok }
|
||||
Reference in new issue
Block a user