diff --git a/config/config.go b/config/config.go index b312f7c..1ee3b6d 100644 --- a/config/config.go +++ b/config/config.go @@ -1,20 +1,21 @@ package config type Config struct { - OfflineUUID bool `toml:"offlineUUID" comment:"为 true 则 uuid 生成方式于离线模式相同,若从离线模式切换不会丢失数据。\n已有用户数据的情况下勿更改此项"` - Port string `toml:"port"` - Log Log `toml:"log"` - Sql Sql `toml:"sql"` - Debug bool `toml:"debug" comment:"输出每条执行的 sql 语句"` - Cache Cache `toml:"cache"` - RaelIP bool `toml:"raelIP" comment:"位于反向代理后启用,用于记录真实 ip\n若直接提供服务,请勿打开,否则会被伪造 ip"` - MaxIpUser int `toml:"maxIpUser" comment:"ip 段最大注册用户,ipv4 为 /24 ipv6 为 /48"` - RsaPriKey string `toml:"rsaPriKey,multiline" comment:"运行后勿修改,若为集群需设置为一致"` - TexturePath string `toml:"texturePath" comment:"材质文件保存路径,如果需要对象存储可以把对象储存挂载到本地目录上"` - TextureBaseUrl string `toml:"textureBaseUrl" comment:"材质静态文件提供基础地址\n如果静态文件位于 oss 上,比如 https://s3.amazonaws.com/example/1.png\n则填写 https://s3.amazonaws.com/example \n若通过反向代理提供服务并启用了 https,请在在此处填写带有 https 的基础路径,否则游戏内无法加载皮肤"` - WebBaseUrl string `toml:"webBaseUrl" comment:"用于在支持的启动器中展示本站的注册地址\n填写类似 https://example.com"` - ServerName string `toml:"serverName" comment:"皮肤站名字,用于在多个地方展示"` - Captcha Captcha `toml:"captcha"` + OfflineUUID bool `toml:"offlineUUID" comment:"为 true 则 uuid 生成方式于离线模式相同,若从离线模式切换不会丢失数据。\n已有用户数据的情况下勿更改此项"` + Port string `toml:"port"` + Log Log `toml:"log"` + Sql Sql `toml:"sql"` + Debug bool `toml:"debug" comment:"输出每条执行的 sql 语句"` + Cache Cache `toml:"cache"` + RaelIP bool `toml:"raelIP" comment:"位于反向代理后启用,用于记录真实 ip\n若直接提供服务,请勿打开,否则会被伪造 ip"` + MaxIpUser int `toml:"maxIpUser" comment:"ip 段最大注册用户,ipv4 为 /24 ipv6 为 /48"` + RsaPriKey string `toml:"rsaPriKey,multiline" comment:"运行后勿修改,若为集群需设置为一致"` + TexturePath string `toml:"texturePath" comment:"材质文件保存路径,如果需要对象存储可以把对象储存挂载到本地目录上"` + TextureBaseUrl string `toml:"textureBaseUrl" comment:"材质静态文件提供基础地址\n如果静态文件位于 oss 上,比如 https://s3.amazonaws.com/example/1.png\n则填写 https://s3.amazonaws.com/example \n若通过反向代理提供服务并启用了 https,请在在此处填写带有 https 的基础路径,否则游戏内无法加载皮肤"` + WebBaseUrl string `toml:"webBaseUrl" comment:"用于在支持的启动器中展示本站的注册地址\n填写类似 https://example.com"` + ServerName string `toml:"serverName" comment:"皮肤站名字,用于在多个地方展示"` + Captcha Captcha `toml:"captcha"` + Email EmailConfig `toml:"email"` } type Log struct { @@ -40,6 +41,19 @@ type Captcha struct { Secret string `toml:"secret"` } +type EmailConfig struct { + Enable bool `toml:"enable" comment:"注册验证邮件,且允许使用邮箱找回账号"` + Smtp []SmtpUser `toml:"smtp"` +} + +type SmtpUser struct { + Host string `toml:"host"` + Port int `toml:"port"` + SSL bool `toml:"SSL"` + Name string `toml:"name"` + Pass string `toml:"password"` +} + func Default() Config { return Config{ OfflineUUID: true, diff --git a/handle/error.go b/handle/error.go index 677e6b0..834e30c 100644 --- a/handle/error.go +++ b/handle/error.go @@ -10,6 +10,7 @@ import ( "github.com/xmdhs/authlib-skin/model" "github.com/xmdhs/authlib-skin/service" "github.com/xmdhs/authlib-skin/service/auth" + "github.com/xmdhs/authlib-skin/service/captcha" ) func (h *Handel) handleErrorService(ctx context.Context, w http.ResponseWriter, err error) { @@ -25,7 +26,7 @@ func (h *Handel) handleErrorService(ctx context.Context, w http.ResponseWriter, h.handleError(ctx, w, err.Error(), model.ErrRegLimit, 400, slog.LevelDebug) return } - if errors.Is(err, service.ErrCaptcha) { + if errors.Is(err, captcha.ErrCaptcha) { h.handleError(ctx, w, err.Error(), model.ErrCaptcha, 400, slog.LevelDebug) return } diff --git a/server/provide.go b/server/provide.go index 53e8ff8..74ed9c2 100644 --- a/server/provide.go +++ b/server/provide.go @@ -95,7 +95,7 @@ func ProvidePriKey(c config.Config) (*rsa.PrivateKey, error) { return a.GetKey(), nil } -func ProvidePubKey(pri *rsa.PrivateKey) (yggdrasil.PubRsaKey, error) { +func ProvidePubKeyStr(pri *rsa.PrivateKey) (yggdrasil.PubRsaKey, error) { s, err := sign.NewAuthlibSignWithKey(pri).GetPKIXPubKeyWithOutRsa() if err != nil { return "", fmt.Errorf("ProvidePubKey: %w", err) @@ -103,8 +103,12 @@ func ProvidePubKey(pri *rsa.PrivateKey) (yggdrasil.PubRsaKey, error) { return yggdrasil.PubRsaKey(s), nil } +func ProvidePubKey(pri *rsa.PrivateKey) *rsa.PublicKey { + return &pri.PublicKey +} + func ProvideHttpClient() *http.Client { return &http.Client{} } -var Set = wire.NewSet(ProvideSlog, ProvideDB, ProvideEnt, ProvideValidate, ProvideCache, ProvidePriKey, ProvidePubKey, ProvideHttpClient) +var Set = wire.NewSet(ProvideSlog, ProvideDB, ProvideEnt, ProvideValidate, ProvideCache, ProvidePriKey, ProvidePubKey, ProvidePubKeyStr, ProvideHttpClient) diff --git a/server/wire.go b/server/wire.go index 27579e8..a819990 100644 --- a/server/wire.go +++ b/server/wire.go @@ -12,13 +12,19 @@ import ( "github.com/xmdhs/authlib-skin/handle/yggdrasil" "github.com/xmdhs/authlib-skin/server/route" "github.com/xmdhs/authlib-skin/service" + "github.com/xmdhs/authlib-skin/service/auth" + "github.com/xmdhs/authlib-skin/service/captcha" "github.com/xmdhs/authlib-skin/service/email" yggdrasilS "github.com/xmdhs/authlib-skin/service/yggdrasil" ) +var serviceSet = wire.NewSet(service.NewWebService, yggdrasilS.NewYggdrasil, email.NewEmail, auth.NewAuthService, + captcha.NewCaptchaService, +) + func InitializeRoute(ctx context.Context, c config.Config) (*http.Server, func(), error) { panic(wire.Build(Set, route.NewRoute, NewSlog, NewServer, handle.NewHandel, yggdrasil.NewYggdrasil, - service.NewWebService, yggdrasilS.NewYggdrasil, email.NewEmail, + serviceSet, )) } diff --git a/server/wire_gen.go b/server/wire_gen.go index 0a1a339..b45be66 100644 --- a/server/wire_gen.go +++ b/server/wire_gen.go @@ -13,6 +13,8 @@ import ( yggdrasil2 "github.com/xmdhs/authlib-skin/handle/yggdrasil" "github.com/xmdhs/authlib-skin/server/route" "github.com/xmdhs/authlib-skin/service" + "github.com/xmdhs/authlib-skin/service/auth" + "github.com/xmdhs/authlib-skin/service/email" "github.com/xmdhs/authlib-skin/service/yggdrasil" "net/http" ) @@ -43,8 +45,10 @@ func InitializeRoute(ctx context.Context, c config.Config) (*http.Server, func() cleanup() return nil, nil, err } - yggdrasilYggdrasil := yggdrasil.NewYggdrasil(client, cache, c, privateKey) - pubRsaKey, err := ProvidePubKey(privateKey) + publicKey := ProvidePubKey(privateKey) + authService := auth.NewAuthService(client, cache, publicKey, privateKey) + yggdrasilYggdrasil := yggdrasil.NewYggdrasil(client, cache, c, privateKey, authService) + pubRsaKey, err := ProvidePubKeyStr(privateKey) if err != nil { cleanup2() cleanup() @@ -52,8 +56,14 @@ func InitializeRoute(ctx context.Context, c config.Config) (*http.Server, func() } yggdrasil3 := yggdrasil2.NewYggdrasil(logger, validate, yggdrasilYggdrasil, c, pubRsaKey) httpClient := ProvideHttpClient() - webService := service.NewWebService(c, client, httpClient, cache, privateKey) - handel := handle.NewHandel(webService, validate, c, logger) + webService := service.NewWebService(c, client, httpClient, cache, privateKey, authService) + emailEmail, err := email.NewEmail(privateKey, c, cache) + if err != nil { + cleanup2() + cleanup() + return nil, nil, err + } + handel := handle.NewHandel(webService, validate, c, logger, emailEmail) httpHandler := route.NewRoute(yggdrasil3, handel, c, handler) server, cleanup3 := NewServer(c, httpHandler) return server, func() { diff --git a/service/captcha.go b/service/captcha.go deleted file mode 100644 index 8540d47..0000000 --- a/service/captcha.go +++ /dev/null @@ -1,84 +0,0 @@ -package service - -import ( - "bytes" - "context" - "encoding/json" - "errors" - "fmt" - "net/http" - "strings" - - "github.com/xmdhs/authlib-skin/model" -) - -func (w *WebService) GetConfig(ctx context.Context) model.Config { - return model.Config{ - Captcha: model.Captcha{ - Type: w.config.Captcha.Type, - SiteKey: w.config.Captcha.SiteKey, - }, - ServerName: w.config.ServerName, - AllowChangeName: !w.config.OfflineUUID, - } -} - -type turnstileRet struct { - Success bool `json:"success"` - ErrorCodes []string `json:"error-codes"` -} - -var ErrCaptcha = errors.New("验证码错误") - -type ErrTurnstile struct { - ErrorCodes []string -} - -func (e ErrTurnstile) Error() string { - return strings.Join(e.ErrorCodes, " ") -} - -func (w *WebService) verifyCaptcha(ctx context.Context, token, ip string) error { - if w.config.Captcha.Type != "turnstile" { - return nil - } - bw := &bytes.Buffer{} - err := json.NewEncoder(bw).Encode(turnstileResponse{ - Secret: w.config.Captcha.Secret, - Response: token, - Remoteip: ip, - }) - if err != nil { - return fmt.Errorf("verifyTurnstile: %w", err) - } - reqs, err := http.NewRequestWithContext(ctx, "POST", "https://challenges.cloudflare.com/turnstile/v0/siteverify", bw) - if err != nil { - return fmt.Errorf("verifyTurnstile: %w", err) - } - reqs.Header.Set("Accept", "*/*") - reqs.Header.Set("Content-Type", "application/json") - rep, err := w.httpClient.Do(reqs) - if err != nil { - return fmt.Errorf("verifyTurnstile: %w", err) - } - defer rep.Body.Close() - - var t turnstileRet - err = json.NewDecoder(rep.Body).Decode(&t) - if err != nil { - return fmt.Errorf("verifyTurnstile: %w", err) - } - - if !t.Success { - return fmt.Errorf("verifyTurnstile: %w", errors.Join(ErrTurnstile{ - ErrorCodes: t.ErrorCodes, - }, ErrCaptcha)) - } - return nil -} - -type turnstileResponse struct { - Secret string `json:"secret"` - Response string `json:"response"` - Remoteip string `json:"remoteip"` -} diff --git a/service/email/email.go b/service/email/email.go index dbf9a2d..a9630e2 100644 --- a/service/email/email.go +++ b/service/email/email.go @@ -33,13 +33,23 @@ type Email struct { cache cache.Cache } -func NewEmail(emailConfig []EmailConfig, pri *rsa.PrivateKey, config config.Config, cache cache.Cache) Email { - return Email{ - emailConfig: emailConfig, +func NewEmail(pri *rsa.PrivateKey, c config.Config, cache cache.Cache) (*Email, error) { + ec := lo.Map[config.SmtpUser, EmailConfig](c.Email.Smtp, func(item config.SmtpUser, index int) EmailConfig { + return EmailConfig{ + Host: item.Host, + Port: item.Port, + SSL: item.SSL, + Name: item.Name, + Pass: item.Pass, + } + }) + + return &Email{ + emailConfig: ec, pri: pri, - config: config, + config: c, cache: cache, - } + }, nil } func (e Email) getRandEmailUser() EmailConfig { diff --git a/service/user.go b/service/user.go index 3069b3b..56d4317 100644 --- a/service/user.go +++ b/service/user.go @@ -33,7 +33,7 @@ func (w *WebService) Reg(ctx context.Context, u model.UserReg, ipPrefix, ip stri userUuid = strings.ReplaceAll(uuid.New().String(), "-", "") } - err := w.verifyCaptcha(ctx, u.CaptchaToken, ip) + err := w.captchaService.VerifyCaptcha(ctx, u.CaptchaToken, ip) if err != nil { return model.LoginRep{}, fmt.Errorf("Reg: %w", err) } @@ -109,7 +109,7 @@ func (w *WebService) Reg(ctx context.Context, u model.UserReg, ipPrefix, ip stri } func (w *WebService) Login(ctx context.Context, l model.Login, ip string) (model.LoginRep, error) { - err := w.verifyCaptcha(ctx, l.CaptchaToken, ip) + err := w.captchaService.VerifyCaptcha(ctx, l.CaptchaToken, ip) if err != nil { return model.LoginRep{}, fmt.Errorf("Login: %w", err) } diff --git a/service/user_test.go b/service/user_test.go index a2502ac..1615baf 100644 --- a/service/user_test.go +++ b/service/user_test.go @@ -16,6 +16,7 @@ import ( "github.com/xmdhs/authlib-skin/db/ent/migrate" "github.com/xmdhs/authlib-skin/model" "github.com/xmdhs/authlib-skin/service/auth" + "github.com/xmdhs/authlib-skin/service/captcha" ) var webService *WebService @@ -36,7 +37,7 @@ func initWebService(ctx context.Context) func() { lo.Must0(c.Schema.Create(context.Background(), migrate.WithForeignKeys(false), migrate.WithDropIndex(true), migrate.WithDropColumn(true))) rsa4 := lo.Must(rsa.GenerateKey(rand.Reader, 4096)) cache := cache.NewFastCache(100000) - webService = NewWebService(config.Default(), c, &http.Client{}, cache, rsa4, auth.NewAuthService(c, cache, &rsa4.PublicKey, rsa4)) + webService = NewWebService(config.Default(), c, &http.Client{}, cache, rsa4, auth.NewAuthService(c, cache, &rsa4.PublicKey, rsa4), captcha.NewCaptchaService(config.Default(), &http.Client{})) return func() { c.User.Delete().Exec(ctx) diff --git a/service/web.go b/service/web.go index 65362e9..0da6387 100644 --- a/service/web.go +++ b/service/web.go @@ -9,28 +9,32 @@ import ( "github.com/xmdhs/authlib-skin/config" "github.com/xmdhs/authlib-skin/db/cache" "github.com/xmdhs/authlib-skin/db/ent" + "github.com/xmdhs/authlib-skin/model" "github.com/xmdhs/authlib-skin/service/auth" + "github.com/xmdhs/authlib-skin/service/captcha" "github.com/xmdhs/authlib-skin/utils" ) type WebService struct { - config config.Config - client *ent.Client - httpClient *http.Client - cache cache.Cache - prikey *rsa.PrivateKey - authService *auth.AuthService + config config.Config + client *ent.Client + httpClient *http.Client + cache cache.Cache + prikey *rsa.PrivateKey + authService *auth.AuthService + captchaService *captcha.CaptchaService } func NewWebService(c config.Config, e *ent.Client, hc *http.Client, - cache cache.Cache, prikey *rsa.PrivateKey, authService *auth.AuthService) *WebService { + cache cache.Cache, prikey *rsa.PrivateKey, authService *auth.AuthService, captchaService *captcha.CaptchaService) *WebService { return &WebService{ - config: c, - client: e, - httpClient: hc, - cache: cache, - prikey: prikey, - authService: authService, + config: c, + client: e, + httpClient: hc, + cache: cache, + prikey: prikey, + authService: authService, + captchaService: captchaService, } } @@ -40,3 +44,14 @@ func (w *WebService) validatePass(ctx context.Context, u *ent.User, password str } return nil } + +func (w *WebService) GetConfig(ctx context.Context) model.Config { + return model.Config{ + Captcha: model.Captcha{ + Type: w.config.Captcha.Type, + SiteKey: w.config.Captcha.SiteKey, + }, + ServerName: w.config.ServerName, + AllowChangeName: !w.config.OfflineUUID, + } +}