diff --git a/frontend/src/views/Layout.tsx b/frontend/src/views/Layout.tsx index d903cec..87555b8 100644 --- a/frontend/src/views/Layout.tsx +++ b/frontend/src/views/Layout.tsx @@ -35,6 +35,7 @@ import SecurityIcon from '@mui/icons-material/Security'; import SettingsIcon from '@mui/icons-material/Settings'; import { Link } from "react-router-dom"; import GroupIcon from '@mui/icons-material/Group'; +import { ApiErr } from '@/apis/error'; const drawerWidth = 240; const DrawerOpen = atom(false) @@ -211,12 +212,16 @@ const MyDrawer = function MyDrawer() { const theme = useTheme(); const isLg = useMediaQuery(theme.breakpoints.up('lg')) const [open, setOpen] = useAtom(DrawerOpen) + const navigate = useNavigate(); const userinfo = useRequest(() => userInfo(nowToken), { refreshDeps: [nowToken], cacheKey: "/api/v1/user" + nowToken, staleTime: 60000, onError: e => { + if (e instanceof ApiErr && e.code == 5) { + navigate("/login") + } console.warn(e) setErr(String(e)) }, diff --git a/go.mod b/go.mod index f82579a..1b9fae0 100644 --- a/go.mod +++ b/go.mod @@ -38,6 +38,7 @@ require ( github.com/leodido/go-urn v1.2.4 // indirect github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/wneessen/go-mail v0.4.0 // indirect github.com/zclconf/go-cty v1.8.0 // indirect golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect golang.org/x/mod v0.10.0 // indirect diff --git a/go.sum b/go.sum index 4a64909..d721c23 100644 --- a/go.sum +++ b/go.sum @@ -97,6 +97,8 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= +github.com/wneessen/go-mail v0.4.0 h1:Oo4HLIV8My7G9JuZkoOX6eipXQD+ACvIqURYeIzUc88= +github.com/wneessen/go-mail v0.4.0/go.mod h1:zxOlafWCP/r6FEhAaRgH4IC1vg2YXxO0Nar9u0IScZ8= github.com/zclconf/go-cty v1.8.0 h1:s4AvqaeQzJIu3ndv4gVIhplVD0krU+bgrcLSVUnaWuA= github.com/zclconf/go-cty v1.8.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= diff --git a/handle/email.go b/handle/email.go new file mode 100644 index 0000000..bc23572 --- /dev/null +++ b/handle/email.go @@ -0,0 +1,9 @@ +package handle + +import "net/http" + +func (h *Handel) SendVerifyCode() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + + } +} diff --git a/handle/handle.go b/handle/handle.go index d0edf16..08f33da 100644 --- a/handle/handle.go +++ b/handle/handle.go @@ -15,21 +15,25 @@ import ( "github.com/xmdhs/authlib-skin/config" "github.com/xmdhs/authlib-skin/model" "github.com/xmdhs/authlib-skin/service" + "github.com/xmdhs/authlib-skin/service/email" ) type Handel struct { - webService *service.WebService - validate *validator.Validate - config config.Config - logger *slog.Logger + webService *service.WebService + validate *validator.Validate + emailService *email.Email + config config.Config + logger *slog.Logger } -func NewHandel(webService *service.WebService, validate *validator.Validate, config config.Config, logger *slog.Logger) *Handel { +func NewHandel(webService *service.WebService, validate *validator.Validate, + config config.Config, logger *slog.Logger, email *email.Email) *Handel { return &Handel{ - webService: webService, - validate: validate, - config: config, - logger: logger, + webService: webService, + validate: validate, + config: config, + logger: logger, + emailService: email, } } diff --git a/server/wire.go b/server/wire.go index e0993b8..27579e8 100644 --- a/server/wire.go +++ b/server/wire.go @@ -12,12 +12,13 @@ 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/email" yggdrasilS "github.com/xmdhs/authlib-skin/service/yggdrasil" ) 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, + service.NewWebService, yggdrasilS.NewYggdrasil, email.NewEmail, )) } diff --git a/service/email/email.go b/service/email/email.go new file mode 100644 index 0000000..1084a4b --- /dev/null +++ b/service/email/email.go @@ -0,0 +1,125 @@ +package email + +import ( + "context" + "crypto/rsa" + "errors" + "fmt" + "math/rand" + "time" + + "github.com/samber/lo" + "github.com/wneessen/go-mail" + "github.com/xmdhs/authlib-skin/config" + "github.com/xmdhs/authlib-skin/db/cache" +) + +type EmailConfig struct { + Host string + Port int + SSL bool + Name string + Pass string +} + +type Email struct { + emailConfig []EmailConfig + pri *rsa.PrivateKey + config config.Config + cache cache.Cache +} + +func NewEmail(emailConfig []EmailConfig, pri *rsa.PrivateKey, config config.Config, cache cache.Cache) Email { + return Email{ + emailConfig: emailConfig, + pri: pri, + config: config, + cache: cache, + } +} + +func (e Email) getRandEmailUser() EmailConfig { + i := rand.Intn(len(e.emailConfig)) + return e.emailConfig[i] +} + +func (e Email) SendEmail(ctx context.Context, to string, subject, body string) error { + u := e.getRandEmailUser() + m := mail.NewMsg() + + err := m.From(u.Name) + if err != nil { + return fmt.Errorf("SendRegVerify: %w", err) + } + + err = m.To(to) + if err != nil { + return fmt.Errorf("SendRegVerify: %w", err) + } + m.Subject(subject) + m.SetBodyString(mail.TypeTextHTML, body) + + c, err := mail.NewClient(u.Host, mail.WithPort(u.Port), mail.WithSMTPAuth(mail.SMTPAuthPlain), + mail.WithUsername(u.Name), mail.WithPassword(u.Pass)) + if err != nil { + return fmt.Errorf("SendRegVerify: %w", err) + } + if u.SSL { + c.SetSSL(true) + } + defer c.Close() + + err = c.DialAndSendWithContext(ctx, m) + if err != nil { + return fmt.Errorf("SendRegVerify: %w", err) + } + return nil +} + +func (e Email) SendVerifyCode(ctx context.Context, email string, interval int) error { + sendKey := []byte("SendEmail" + email) + sendB, err := e.cache.Get(sendKey) + if err != nil { + return fmt.Errorf("SendRegVerifyCode: %w", err) + } + if sendB == nil { + return fmt.Errorf("SendRegVerifyCode: %w", ErrSendLimit) + } + err = e.cache.Put(sendKey, []byte{1}, time.Now().Add(time.Second*time.Duration(interval))) + if err != nil { + return fmt.Errorf("SendRegVerifyCode: %w", err) + } + + code := lo.RandomString(8, append(lo.NumbersCharset, lo.UpperCaseLettersCharset...)) + + err = e.cache.Put([]byte("VerifyCode"+email), []byte(code), time.Now().Add(5*time.Minute)) + if err != nil { + return fmt.Errorf("SendRegVerifyCode: %w", err) + } + err = e.SendEmail(ctx, email, "验证你的邮箱", fmt.Sprintf("验证码:%v,五分钟内有效", code)) + if err != nil { + return fmt.Errorf("SendRegVerifyCode: %w", err) + } + return nil +} + +var ( + ErrCodeNotValid = errors.New("验证码无效") + ErrSendLimit = errors.New("邮件发送限制") +) + +func (e Email) VerifyCode(email, code string) error { + key := []byte("VerifyCode" + email) + codeb, err := e.cache.Get(key) + if err != nil { + return fmt.Errorf("VerifyCode: %w", err) + } + if string(codeb) != code { + err := e.cache.Del(key) + if err != nil { + return fmt.Errorf("VerifyCode: %w", err) + } + return ErrCodeNotValid + } + return nil +}