package user import ( "bytes" "embed" "encoding/base64" "fmt" "github.com/dchest/captcha" "github.com/go-chi/chi/v5" "github.com/go-chi/jwtauth/v5" "github.com/rs/zerolog/log" "github.com/unrolled/render" "net/mail" "regexp" gomail "gopkg.in/mail.v2" "io" "net/http" "time" ) // content holds our static web server content. //go:embed templates/* var webserver embed.FS var ren *render.Render var uc UserClient var tokenAuth *jwtauth.JWTAuth var d *gomail.Dialer type UserConfig struct { RegisterMail *gomail.Message URLValidationToken string } var userconfig UserConfig func NewUserConfig() UserConfig { uc := UserConfig{} m := gomail.NewMessage() m.SetHeader("From", "test@keks.cloud") m.SetHeader("To", "to@example.com") m.SetHeader("Subject", "Activate your Account") uc.RegisterMail = m uc.URLValidationToken = "http://localhost:3000/validate/%s" return uc } func Register(router chi.Router, template func(http.Handler) http.Handler, token *jwtauth.JWTAuth, userClient UserClient, dialer *gomail.Dialer, config UserConfig) { uc = userClient userconfig = config ren = render.New(render.Options{ //Layout: "layout", FileSystem: &render.EmbedFileSystem{ FS: webserver, }, }) router.Group(func(r chi.Router) { r.Use(template) r.Get("/login", loginForm) r.Post("/login", login) r.Get("/register", registerForm) r.Post("/register", register) r.Get("/logout", logout) }) router.Get("/captcha/{id}.png", captchaImage) tokenAuth = token d = dialer router.Group(func(r chi.Router) { r.Use(jwtauth.Verifier(tokenAuth)) r.Get("/me", func(w http.ResponseWriter, r *http.Request) { _, claims, _ := jwtauth.FromContext(r.Context()) if val, ok := claims["username"]; ok { w.Write([]byte(fmt.Sprintf("hi %v", val))) } else { w.Write([]byte("Du bist nicht eingeloggt")) } }) }) } func captchaImage(w http.ResponseWriter, r *http.Request) { id := chi.URLParam(r, "id") w.Header().Set("Content-Type", "image/png") captcha.WriteImage(w, id, 400, 200) } func loginForm(w http.ResponseWriter, r *http.Request) { ren.HTML(w, http.StatusOK, "login", map[string]string {"captcha": captcha.New()}) } func registerForm(w http.ResponseWriter, r *http.Request) { ren.HTML(w, http.StatusOK, "register", map[string]string {"captcha": captcha.New()}) } func login(w http.ResponseWriter, r *http.Request) { r.ParseForm() if captcha.VerifyString(r.FormValue("captchaid"), r.FormValue("captcha")) == false { log.Debug().Str("captchaid", r.FormValue("captchaid")).Str("input", r.FormValue("captcha")).Interface("digits", []byte(r.FormValue("captcha"))).Msg("Captcha wrong") ren.HTML(w, http.StatusOK, "login", map[string]string {"captcha": captcha.New(), "msg":"Captcha code was wrong", "username": r.FormValue("username")}) return } res, err := uc.login(r.FormValue("username"), r.FormValue("password"), true) if err != nil { ren.HTML(w, http.StatusOK, "login", map[string]string {"captcha": captcha.New(), "msg":err.Error(), "username": r.FormValue("username")}) return } if res { _, tokenstring, err := tokenAuth.Encode(map[string]interface{}{"username": r.FormValue("username")}) if err != nil { panic(err) } expiration := time.Now().Add(365 * 24 * time.Hour) cookie := http.Cookie{Name: "jwt",Value:tokenstring,Expires:expiration} http.SetCookie(w, &cookie) w.Write([]byte("Login ok")) } else { w.Write([]byte("Login failed")) } } func logout(w http.ResponseWriter, r *http.Request) { cookie := http.Cookie{ Name: "jwt", Value: "", } http.SetCookie(w, &cookie) w.Write([]byte("Du wurdest ausgeloggt")) } func register(w http.ResponseWriter, r *http.Request) { r.ParseForm() if captcha.VerifyString(r.FormValue("captchaid"), r.FormValue("captcha")) == false { log.Debug().Str("captchaid", r.FormValue("captchaid")).Str("input", r.FormValue("captcha")).Interface("digits", []byte(r.FormValue("captcha"))).Msg("Captcha wrong") ren.HTML(w, http.StatusOK, "register", map[string]string {"captcha": captcha.New(), "msg":"Captcha code was wrong", "email": r.FormValue("email"), "username": r.FormValue("username")}) return } if validEmail(r.FormValue("email")) == false { ren.HTML(w, http.StatusOK, "register", map[string]string {"captcha": captcha.New(), "msg":"E-Mail address is not valide.", "email": r.FormValue("email"), "username": r.FormValue("username")}) return } matched, _ := regexp.MatchString(`^[a-z0-9]{5,25}$`, r.FormValue("username")) if matched == false { ren.HTML(w, http.StatusOK, "register", map[string]string {"captcha": captcha.New(), "msg":"Username must be between 5 and 25 lowercase letters and numbers", "email": r.FormValue("email"), "username": r.FormValue("username")}) return } if len(r.FormValue("password")) < 8 { ren.HTML(w, http.StatusOK, "register", map[string]string {"captcha": captcha.New(), "msg":"Your password must be at least 8 letters", "email": r.FormValue("email"), "username": r.FormValue("username")}) return } res, err := uc.register(r.FormValue("username"), r.FormValue("password"), r.FormValue("email")) if err != nil || res == false { ren.HTML(w, http.StatusOK, "register", map[string]string {"captcha": captcha.New(), "msg":"Registration Failed", "email": r.FormValue("email"), "username": r.FormValue("username")}) return } rw := &LayoutMiddlewareResponseWriter{ ResponseWriter: w, buf: &bytes.Buffer{}, } token, err := uc.getMailValidationToken(r.FormValue("email"), true) if err != nil { panic(err) } ren.HTML(rw, http.StatusOK, "token", map[string]string{"username": r.FormValue("username"), "url": fmt.Sprintf(userconfig.URLValidationToken, string(base64.StdEncoding.EncodeToString([]byte(r.FormValue("email"))) + "/" + token))}) content, err := io.ReadAll(rw.buf) if err != nil { panic(err) } mail := *userconfig.RegisterMail mail.SetBody("text/html", string(content)) if err := d.DialAndSend(&mail); err != nil { fmt.Println(err) panic(err) } w.Write([]byte("registration successful, please check your mail")) } type LayoutMiddlewareResponseWriter struct { http.ResponseWriter buf *bytes.Buffer } func (myrw *LayoutMiddlewareResponseWriter) Write(p []byte) (int, error) { return myrw.buf.Write(p) } func validEmail(email string) bool { _, err := mail.ParseAddress(email) return err == nil }