package controllers import ( "fmt" "net/http" "net/url" "time" userctx "git.kealoha.me/lks/lenslocked/context" "git.kealoha.me/lks/lenslocked/models" "git.kealoha.me/lks/lenslocked/templates" "git.kealoha.me/lks/lenslocked/views" ) type Users struct { Templates struct { Signup Template Signin Template ForgotPass Template ResetUrlSent Template ResetPass Template } UserService *models.UserService SessionService *models.SessionService PassResetService *models.PasswordResetService EmailService *models.EmailService } func (u Users) GetSignup(w http.ResponseWriter, r *http.Request) { var data struct { Email string } data.Email = r.FormValue("email") u.Templates.Signup.Execute(w, r, data) } func (u Users) PostSignup(w http.ResponseWriter, r *http.Request) { email := r.FormValue("email") password := r.FormValue("password") user, err := u.UserService.Create(email, password) if err != nil { fmt.Println(err) http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } session, err := u.SessionService.Create(user.ID) if err != nil { fmt.Println(err) http.Redirect(w, r, "/signin", http.StatusFound) return } cookie := http.Cookie{ Name: "session", Value: session.Token, Path: "/", HttpOnly: true, } http.SetCookie(w, &cookie) http.Redirect(w, r, "/user", http.StatusFound) } func (u Users) GetSignin(w http.ResponseWriter, r *http.Request) { var data struct { Email string } data.Email = r.FormValue("email") u.Templates.Signin.Execute(w, r, data) } func (u Users) PostSignin(w http.ResponseWriter, r *http.Request) { var data struct { Email string Password string } data.Email = r.FormValue("email") data.Password = r.FormValue("password") user, err := u.UserService.Authenticate(data.Email, data.Password) if err != nil { fmt.Println(err) http.Error(w, "Something went wrong.", http.StatusInternalServerError) return } session, err := u.SessionService.Create(user.ID) if err != nil { fmt.Println(err) http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } cookie := http.Cookie{ Name: "session", Value: session.Token, Path: "/", HttpOnly: true, } http.SetCookie(w, &cookie) fmt.Fprintf(w, "Current user: %s\n", user.Email) //http.Redirect(w, r, "/user", http.StatusFound) } func (u Users) GetSignout(w http.ResponseWriter, r *http.Request) { sessionCookie, err := r.Cookie("session") if err != nil { http.Redirect(w, r, "/signin", http.StatusFound) return } err = u.SessionService.Delete(sessionCookie.Value) if err != nil { fmt.Println(err) http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } c := http.Cookie{ Name: "session", MaxAge: -1, } http.SetCookie(w, &c) http.Redirect(w, r, "/signin", http.StatusFound) } func (u Users) GetForgotPassword(w http.ResponseWriter, r *http.Request) { var data struct { Email string } data.Email = r.FormValue("email") u.Templates.ForgotPass.Execute(w, r, data) } func (u Users) PostForgotPassword(w http.ResponseWriter, r *http.Request) { var data struct { Email string } data.Email = r.FormValue("email") pwReset, err := u.PassResetService.Create(data.Email) if err != nil { fmt.Println(err) http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } vals := url.Values{ "token": {pwReset.Token}, } // TODO: Make the URL here configurable and use https resetURL := "http://" + r.Host + "/reset-pw?" + vals.Encode() fmt.Println(resetURL) err = u.EmailService.SendPasswordReset(data.Email, resetURL) if err != nil { fmt.Println(err) http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } u.Templates.ResetUrlSent.Execute(w, r, data) } func (u Users) GetResetPass(w http.ResponseWriter, r *http.Request) { var data struct { Token string } data.Token = r.FormValue("token") u.Templates.ResetPass.Execute(w, r, data) } func (u Users) PostResetPass(w http.ResponseWriter, r *http.Request) { var data struct { Token, Password string } data.Token = r.FormValue("token") data.Password = r.FormValue("password") user, err := u.PassResetService.Consume(data.Token) if err != nil { fmt.Println(err) // TODO: Distinguish between server errors and invalid token errors. http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } err = u.UserService.UpdatePassword(user.ID, data.Password) if err != nil { fmt.Println(err) http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } // Sign the user in now that they have reset their password. // Any errors from this point onward should redirect to the sign in page. session, err := u.SessionService.Create(user.ID) if err != nil { fmt.Println(err) http.Redirect(w, r, "/signin", http.StatusFound) return } //setCookie(w, CookieSession, session.Token) cookie := http.Cookie{ Name: "session", Value: session.Token, Path: "/", HttpOnly: true, } http.SetCookie(w, &cookie) http.Redirect(w, r, "/users/me", http.StatusFound) } func (u Users) CurrentUser(w http.ResponseWriter, r *http.Request) { user := userctx.User(r.Context()) if user == nil { http.Redirect(w, r, "/signin", http.StatusFound) return } fmt.Fprintf(w, "Current user: %s\n", user.Email) } func WithTemplates(user_service *models.UserService, session_service *models.SessionService, email_service *models.EmailService, signup, signin, forgotPass, resetUrlSent, resetPass Template) Users { u := Users{} u.Templates.Signup = signup u.Templates.Signin = signin u.Templates.ForgotPass = forgotPass u.Templates.ResetUrlSent = resetUrlSent u.Templates.ResetPass = resetPass u.UserService = user_service u.SessionService = session_service u.EmailService = email_service u.PassResetService = &models.PasswordResetService{ DB: u.UserService.DB, Duration: time.Hour / 2, } return u } func Default(user_service *models.UserService, session_service *models.SessionService, email_service *models.EmailService) Users { signup_tpl := views.Must(views.FromFS(templates.FS, "signup.gohtml", "tailwind.gohtml")) signin_tpl := views.Must(views.FromFS(templates.FS, "signin.gohtml", "tailwind.gohtml")) pwReset_tpl := views.Must(views.FromFS(templates.FS, "pwReset.gohtml", "tailwind.gohtml")) pwResetSent_tpl := views.Must(views.FromFS(templates.FS, "pwResetSent.gohtml", "tailwind.gohtml")) resetPass_tpl := views.Must(views.FromFS(templates.FS, "pwChange.gohtml", "tailwind.gohtml")) err := signup_tpl.TestTemplate(nil) if err != nil { panic(err) } err = signin_tpl.TestTemplate(nil) if err != nil { panic(err) } err = pwReset_tpl.TestTemplate(nil) if err != nil { panic(err) } err = pwResetSent_tpl.TestTemplate(nil) if err != nil { panic(err) } return WithTemplates(user_service, session_service, email_service, signup_tpl, signin_tpl, pwReset_tpl, pwResetSent_tpl, resetPass_tpl) }