192 lines
5.9 KiB
Go
192 lines
5.9 KiB
Go
package web
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"errors"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"gardomatic.kleiax.de/lib/client"
|
|
)
|
|
|
|
type signInForm struct {
|
|
CSRFToken string `form:"csrf_token"`
|
|
Email string `form:"email"`
|
|
Password string `form:"password"`
|
|
RememberEmail bool `form:"remember_email"`
|
|
Errors map[string]string
|
|
Message string
|
|
}
|
|
|
|
type activationForm struct {
|
|
CSRFToken string `form:"csrf_token"`
|
|
Token string `form:"token"`
|
|
Password string `form:"password"`
|
|
PasswordConfirm string `form:"password_confirm"`
|
|
SetPassword bool `form:"set_password"`
|
|
Errors map[string]string
|
|
Message string
|
|
}
|
|
|
|
func (app *application) signIn(w http.ResponseWriter, r *http.Request) {
|
|
if app.isAuthenticated(r) {
|
|
http.Redirect(w, r, app.authenticatedLandingPage(r), http.StatusSeeOther)
|
|
return
|
|
}
|
|
data := app.newTemplateData(r)
|
|
form := signInForm{Errors: make(map[string]string)}
|
|
if cookie, err := r.Cookie("gardomatic_remembered_email"); err == nil {
|
|
if decoded, decodeErr := base64.RawURLEncoding.DecodeString(cookie.Value); decodeErr == nil {
|
|
form.Email, form.RememberEmail = string(decoded), true
|
|
}
|
|
}
|
|
data.Form = form
|
|
app.render(w, http.StatusOK, "signin.tmpl", data)
|
|
}
|
|
|
|
func (app *application) signInPost(w http.ResponseWriter, r *http.Request) {
|
|
var form signInForm
|
|
if err := app.decodePostForm(r, &form); err != nil {
|
|
app.clientError(w, http.StatusBadRequest)
|
|
return
|
|
}
|
|
form.Email = strings.TrimSpace(form.Email)
|
|
form.Errors = make(map[string]string)
|
|
if form.Email == "" {
|
|
form.Errors["email"] = "E-Mail-Adresse ist erforderlich."
|
|
}
|
|
if form.Password == "" {
|
|
form.Errors["password"] = "Passwort ist erforderlich."
|
|
}
|
|
if len(form.Errors) != 0 {
|
|
data := app.newTemplateData(r)
|
|
data.Form = form
|
|
app.render(w, http.StatusUnprocessableEntity, "signin.tmpl", data)
|
|
return
|
|
}
|
|
|
|
apiClient := client.FromContext(r.Context())
|
|
user, response, err := apiClient.CreateSession(r.Context(), client.Credentials{Email: form.Email, Password: form.Password})
|
|
if err != nil {
|
|
var apiError *client.APIError
|
|
if errors.As(err, &apiError) && (apiError.StatusCode == http.StatusUnauthorized || apiError.StatusCode == http.StatusUnprocessableEntity) {
|
|
form.Message = "E-Mail-Adresse oder Passwort ist ungültig."
|
|
data := app.newTemplateData(r)
|
|
data.Form = form
|
|
app.render(w, http.StatusUnprocessableEntity, "signin.tmpl", data)
|
|
return
|
|
}
|
|
app.serverError(w, err)
|
|
return
|
|
}
|
|
client.ForwardCookies(w, response)
|
|
remembered := &http.Cookie{Name: "gardomatic_remembered_email", Path: webPath("login"), HttpOnly: true, Secure: app.config.CookieSecure, SameSite: http.SameSiteLaxMode}
|
|
if form.RememberEmail {
|
|
remembered.Value = base64.RawURLEncoding.EncodeToString([]byte(form.Email))
|
|
remembered.Expires = time.Now().Add(365 * 24 * time.Hour)
|
|
remembered.MaxAge = 365 * 24 * 60 * 60
|
|
} else if _, err := r.Cookie("gardomatic_remembered_email"); err == nil {
|
|
remembered.Expires = time.Unix(1, 0)
|
|
remembered.MaxAge = -1
|
|
} else {
|
|
remembered = nil
|
|
}
|
|
if remembered != nil {
|
|
http.SetCookie(w, remembered)
|
|
}
|
|
if !user.Activated {
|
|
http.Redirect(w, r, webPath("activate"), http.StatusSeeOther)
|
|
return
|
|
}
|
|
http.Redirect(w, r, pathWithQuery(webPath("gardens"), "auto", 1), http.StatusSeeOther)
|
|
}
|
|
|
|
func (app *application) activateUser(w http.ResponseWriter, r *http.Request) {
|
|
if user, ok := userFromContext(r.Context()); ok && user.Activated {
|
|
http.Redirect(w, r, webPath("gardens"), http.StatusSeeOther)
|
|
return
|
|
}
|
|
|
|
data := app.newTemplateData(r)
|
|
data.Form = activationForm{
|
|
Token: strings.TrimSpace(r.URL.Query().Get("token")),
|
|
SetPassword: r.URL.Query().Get("set-password") == "1",
|
|
Errors: make(map[string]string),
|
|
}
|
|
app.render(w, http.StatusOK, "activate.tmpl", data)
|
|
}
|
|
|
|
func (app *application) activateUserPost(w http.ResponseWriter, r *http.Request) {
|
|
var form activationForm
|
|
if err := app.decodePostForm(r, &form); err != nil {
|
|
app.clientError(w, http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
form.Token = strings.TrimSpace(form.Token)
|
|
form.Errors = make(map[string]string)
|
|
if form.Token == "" {
|
|
form.Errors["token"] = "Aktivierungstoken ist erforderlich."
|
|
}
|
|
if form.SetPassword {
|
|
if len(form.Password) < 8 {
|
|
form.Errors["password"] = "Das Passwort muss mindestens 8 Zeichen lang sein."
|
|
} else if len(form.Password) > 72 {
|
|
form.Errors["password"] = "Das Passwort darf höchstens 72 Zeichen lang sein."
|
|
}
|
|
if form.Password != form.PasswordConfirm {
|
|
form.Errors["password_confirm"] = "Die Passwörter stimmen nicht überein."
|
|
}
|
|
}
|
|
|
|
if len(form.Errors) == 0 {
|
|
apiClient := client.FromContext(r.Context())
|
|
var err error
|
|
if form.SetPassword {
|
|
_, _, err = apiClient.ActivateInvitedUser(r.Context(), form.Token, form.Password)
|
|
} else {
|
|
_, _, err = apiClient.ActivateUser(r.Context(), form.Token)
|
|
}
|
|
if err == nil {
|
|
http.Redirect(w, r, webPath("gardens"), http.StatusSeeOther)
|
|
return
|
|
}
|
|
|
|
var apiError *client.APIError
|
|
if errors.As(err, &apiError) && apiError.StatusCode == http.StatusUnprocessableEntity {
|
|
if _, ok := apiError.Validation["token"]; ok {
|
|
form.Errors["token"] = "Aktivierungstoken ist ungültig oder abgelaufen."
|
|
} else {
|
|
form.Errors = apiError.Validation
|
|
form.Message = "Der Account konnte nicht aktiviert werden."
|
|
}
|
|
} else {
|
|
app.handleAPIError(w, r, err)
|
|
return
|
|
}
|
|
}
|
|
|
|
data := app.newTemplateData(r)
|
|
data.Form = form
|
|
app.render(w, http.StatusUnprocessableEntity, "activate.tmpl", data)
|
|
}
|
|
|
|
func (app *application) authenticatedLandingPage(r *http.Request) string {
|
|
if user, ok := userFromContext(r.Context()); ok && !user.Activated {
|
|
return webPath("activate")
|
|
}
|
|
return webPath("gardens")
|
|
}
|
|
|
|
func (app *application) signOutPost(w http.ResponseWriter, r *http.Request) {
|
|
apiClient := client.FromContext(r.Context())
|
|
response, err := apiClient.DeleteSession(r.Context())
|
|
if err != nil {
|
|
app.handleAPIError(w, r, err)
|
|
return
|
|
}
|
|
client.ForwardCookies(w, response)
|
|
http.Redirect(w, r, webPath("home"), http.StatusSeeOther)
|
|
}
|