拆出 captcha

This commit is contained in:
xmdhs 2023-11-24 15:07:05 +08:00
parent 9147dc6b1d
commit af024ef00e
No known key found for this signature in database
GPG Key ID: E809D6D43DEFCC95
10 changed files with 104 additions and 127 deletions

View File

@ -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,

View File

@ -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
}

View File

@ -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)

View File

@ -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,
))
}

View File

@ -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() {

View File

@ -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"`
}

View File

@ -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 {

View File

@ -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)
}

View File

@ -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)

View File

@ -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,
}
}