func LoginHandler(service *user.Service, permissonService *permission.Service, auditService *audit.Service, locale *i18n.Locale) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		type request struct {
			Username string `json:"username"`
			Password string `json:"password"`
		}
		type userResponse struct {
			ID          int      `json:"id"`
			Name        string   `json:"name"`
			IsSuperuser bool     `json:"is_superuser"`
			Permissions []string `json:"permissions"`
		}
		type response struct {
			User   userResponse      `json:"user"`
			Ok     bool              `json:"ok"`
			Errors validation.Errors `json:"errors"`
		}
		var req request
		err := json.NewDecoder(r.Body).Decode(&req)
		if err != nil {
			render.JSON(w, http.StatusUnprocessableEntity, response{Ok: false, Errors: validation.ConvertError("username", err)})
			return
		}
		v := user.ValidateAuthRequest(req.Username, req.Password)
		if v.Failed() {
			render.JSON(w, http.StatusUnprocessableEntity, response{Ok: false, Errors: v.TranslatedErrors(locale)})
			return
		}
		u, err := service.Login(r.Context(), req.Username, req.Password)
		if err != nil {
			if err == entity.ErrUserInvalidPassword {
				render.JSON(w, http.StatusUnprocessableEntity, response{Ok: false, Errors: validation.NewFromString("password", locale.Get("user.errors.wrong_password"))})
			} else {
				render.JSON(w, http.StatusUnprocessableEntity, response{Ok: false, Errors: validation.NewFromString("username", locale.Get("user.errors.unknown"))})
			}
			return
		}
		auditService.Create(r.Context(), nil, &entity.AuditLog{
			EntityType: entity.KindUser,
			EntityID:   null.IntFrom(int64(u.ID)),
			UserID:     u.ID,
			Action:     audit.ActionLoggedIn,
		})
		var permissionStrings []string
		for _, permission := range permissonService.GetForUserID(r.Context(), u.ID) {
			permissionStrings = append(permissionStrings, permission.CodeLevel())
		}
		render.JSON(w, http.StatusOK, response{Ok: true, User: userResponse{
			ID:          u.ID,
			Name:        u.Name,
			IsSuperuser: u.IsSuperuser,
			Permissions: permissionStrings,
		}})
	}
}
LoginHandler takes in a username and password and creates a new user session.
nolint:errcheck,funlen
package auth
import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"go-webapp-example/internal/pkg/audit"
	"go-webapp-example/internal/pkg/entity"
	"go-webapp-example/internal/pkg/permission"
	"go-webapp-example/internal/pkg/user"
	"go-webapp-example/pkg/i18n"
	"go-webapp-example/pkg/log"
	"go-webapp-example/pkg/render"
	"go-webapp-example/pkg/session"
	"go-webapp-example/pkg/validation"
	"gopkg.in/guregu/null.v3"
)