Skip to content
Snippets Groups Projects
handler-user.go 4.36 KiB
Newer Older
gbe's avatar
gbe committed
package main

import (
	"errors"
gbe's avatar
gbe committed
	"fmt"
gbe's avatar
gbe committed
	"html/template"
	"net/http"
gbe's avatar
gbe committed
	"sync"
gbe's avatar
gbe committed

	"git.c3pb.de/gbe/invinoveritas/auth"
gbe's avatar
gbe committed
	"git.c3pb.de/gbe/invinoveritas/log"
gbe's avatar
gbe committed
	"git.c3pb.de/gbe/invinoveritas/session"
gbe's avatar
gbe committed
func (h Handler) userCreate(w http.ResponseWriter, r *http.Request) (string, error) {
	name := r.PostForm.Get("new-user")
	if name == "" {
		return "", errors.New("name can't be empty")
	}

	if len(name) > 80 {
		return "", fmt.Errorf("user name too long %q", name)
	}

	generatedPassword, err := h.up.CreateUser(r.Context(), name)
gbe's avatar
gbe committed
	if err != nil {
		return "", err
	}

	return generatedPassword, nil
}

type pageName string

const (
	pageYou      pageName = "you"
	pageAllUsers pageName = "all-users"
	pageSessions pageName = "sessions"
)

func (h Handler) user(page pageName) http.Handler {
gbe's avatar
gbe committed
	var (
		once sync.Once
		tpl  *template.Template
	)

	type templateData struct {
		Page pageName

gbe's avatar
gbe committed
		User        auth.User
		AllUsers    []string // User names
gbe's avatar
gbe committed
		AllSessions []session.Info
gbe's avatar
gbe committed
		FormErrors  map[string]error
		Success     bool
		ShowResult  bool

		GeneratedPassword string // For new users
		CreatedUser       string // Name for new users
	}

	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
gbe's avatar
gbe committed
		once.Do(func() {
			tpl = template.Must(template.ParseFS(templateFS, "templates/base.tpl", "templates/user.tpl"))
		})

		user := auth.Get(r)

gbe's avatar
gbe committed
		data := templateData{
			Page: page,

			User:       user,
			FormErrors: make(map[string]error),
		}
		// If set to true, we don't render the template at function exit
		var skipRender bool

			if skipRender {
				return
			}

			var err error

			data.AllUsers, err = h.up.ListUsers(r.Context())
			if err != nil {
gbe's avatar
gbe committed
				httpError(w, r, "can't get list of all users", nil, http.StatusInternalServerError)
			data.AllSessions, err = h.sp.ListSessions(r.Context())
			if err != nil {
gbe's avatar
gbe committed
				httpError(w, r, "can't get list of sessions", err, http.StatusInternalServerError)
gbe's avatar
gbe committed
			err = tpl.ExecuteTemplate(w, "user.tpl", data)
			if err != nil {
gbe's avatar
gbe committed
				log.Get(r).
gbe's avatar
gbe committed
					Log("error", err,
						"msg", "can't execute details template")
gbe's avatar
gbe committed

		if r.Method == "GET" {
gbe's avatar
gbe committed
			// No need to do anything here, users and sessions will be loaded by deferred func.
			// That will also take care of rendering things.
gbe's avatar
gbe committed
			return
		}

		if r.Method != "POST" {
			skipRender = true
gbe's avatar
gbe committed
			httpError(w, r, "bad method", errors.New(r.Method), http.StatusMethodNotAllowed)
			return
		}

		err := r.ParseForm()
		if err != nil {
			skipRender = true
gbe's avatar
gbe committed
			httpError(w, r, "can't parse POST form", err, http.StatusInternalServerError)
gbe's avatar
gbe committed
			return
		}

		switch r.PostForm.Get("action") {
		case "update":
			data.ShowResult = true
		case "create":
			generatedPw, err := h.userCreate(w, r)
			if err != nil {
				data.FormErrors["NewUser"] = err
			}

			data.GeneratedPassword = generatedPw
			data.CreatedUser = r.PostForm.Get("new-user")

gbe's avatar
gbe committed
			log.Get(r).
gbe's avatar
gbe committed
				Log("name", data.CreatedUser,
					"msg", "user created")
gbe's avatar
gbe committed

			return
		case "delete":
			err := h.up.DeleteUser(r.Context(), r.PostForm.Get("username"))
			if err != nil {
				data.FormErrors["DeleteUser"] = err
			}

			return
		case "logout":
			cookie, err := r.Cookie("session")
			if err != nil {
				skipRender = true

				// We shouldn't end up here: if we do, someone managed to bypass the authentication
				// middleware.
gbe's avatar
gbe committed
				log.Get(r).
gbe's avatar
gbe committed
					Log("error", err,
						"msg", "can't get session token: possible auth bypass")

				http.Error(w, err.Error(), http.StatusInternalServerError)
				return
			}

			err = h.sp.DeleteSession(r.Context(), cookie.Value)
			if err != nil {
				skipRender = true

gbe's avatar
gbe committed
				log.Get(r).
gbe's avatar
gbe committed
					Log("error", err,
						"msg", "can't delete session")

				http.Error(w, err.Error(), http.StatusInternalServerError)
				return
			}

			http.Redirect(w, r, "/", http.StatusFound)

			return
		default:
			skipRender = true

gbe's avatar
gbe committed
			httpError(w, r, "unknown action", nil, http.StatusBadRequest)
		if r.PostForm.Get("new-pw") != r.PostForm.Get("new-pw-repeat") {
			data.FormErrors["NewPW"] = errors.New("new passwords don't match")
			return
		if len(r.PostForm.Get("new-pw")) == 0 {
			data.FormErrors["NewPW"] = errors.New("new password can't be empty")
			return
		}
gbe's avatar
gbe committed

		err = h.up.UpdatePassword(r.Context(), user.Name, r.PostForm.Get("old-pw"), r.PostForm.Get("new-pw"))
gbe's avatar
gbe committed
		if err != nil {
			data.FormErrors["OldPW"] = err
			return
gbe's avatar
gbe committed
		}

		data.Success = true
	})