lens @ master

  1package ext
  2
  3import (
  4	"context"
  5	"encoding/base64"
  6	"errors"
  7	"net/http"
  8	"time"
  9
 10	"golang.org/x/exp/slog"
 11
 12	"git.sr.ht/~gabrielgio/img/pkg/database/repository"
 13	"git.sr.ht/~gabrielgio/img/pkg/service"
 14)
 15
 16func HTML(next http.HandlerFunc) http.HandlerFunc {
 17	return func(w http.ResponseWriter, r *http.Request) {
 18		w.Header().Set("Content-Type", "text/html")
 19		next(w, r)
 20	}
 21}
 22
 23type (
 24	User string
 25
 26	LogMiddleware struct {
 27		logger *slog.Logger
 28	}
 29)
 30
 31const (
 32	UserKey User = "user"
 33)
 34
 35func NewLogMiddleare(log *slog.Logger) *LogMiddleware {
 36	return &LogMiddleware{
 37		logger: log,
 38	}
 39}
 40
 41func (l *LogMiddleware) HTTP(next http.HandlerFunc) http.HandlerFunc {
 42	return func(w http.ResponseWriter, r *http.Request) {
 43		start := time.Now()
 44		next(w, r)
 45		elapsed := time.Since(start)
 46		l.logger.Info(
 47			r.Method,
 48			slog.Duration("elapsed", elapsed),
 49			slog.String("path", r.URL.Path),
 50		)
 51	}
 52}
 53
 54type AuthMiddleware struct {
 55	key            []byte
 56	logger         *slog.Logger
 57	userRepository repository.UserRepository
 58}
 59
 60func NewAuthMiddleware(
 61	key []byte,
 62	logger *slog.Logger,
 63	userRepository repository.UserRepository,
 64) *AuthMiddleware {
 65	return &AuthMiddleware{
 66		key:            key,
 67		logger:         logger,
 68		userRepository: userRepository,
 69	}
 70}
 71
 72func (a *AuthMiddleware) LoggedIn(next http.HandlerFunc) http.HandlerFunc {
 73	return func(w http.ResponseWriter, r *http.Request) {
 74		path := r.URL.Path
 75		if path == "/login" || path == "/initial" {
 76			next(w, r)
 77			return
 78		}
 79
 80		redirectLogin := "/login?redirect=" + path
 81		authBase64, err := r.Cookie("auth")
 82		if errors.Is(err, http.ErrNoCookie) {
 83			a.logger.Info("No auth provided")
 84			http.Redirect(w, r, redirectLogin, http.StatusTemporaryRedirect)
 85			return
 86		}
 87
 88		auth, err := base64.StdEncoding.DecodeString(authBase64.Value)
 89		if err != nil {
 90			a.logger.Error(err.Error())
 91			return
 92		}
 93
 94		token, err := service.ReadToken(auth, a.key)
 95		if err != nil {
 96			a.logger.Error(err.Error())
 97			http.Redirect(w, r, redirectLogin, http.StatusTemporaryRedirect)
 98			return
 99		}
100
101		user, err := a.userRepository.Get(r.Context(), token.UserID)
102		if err != nil {
103			a.logger.Error(err.Error())
104			return
105		}
106
107		r = r.WithContext(context.WithValue(r.Context(), UserKey, user))
108		a.logger.
109			Info(
110				"user recognized",
111				slog.Uint64("userid", uint64(token.UserID)),
112				slog.String("username", token.Username),
113			)
114		next(w, r)
115	}
116}
117
118func GetUserFromCtx(r *http.Request) *repository.User {
119	tokenValue := r.Context().Value(UserKey)
120	if token, ok := tokenValue.(*repository.User); ok {
121		return token
122	}
123	return nil
124}
125
126type InitialSetupMiddleware struct {
127	userRepository repository.UserRepository
128}
129
130func NewInitialSetupMiddleware(userRepository repository.UserRepository) *InitialSetupMiddleware {
131	return &InitialSetupMiddleware{
132		userRepository: userRepository,
133	}
134}
135
136func (i *InitialSetupMiddleware) Check(next http.HandlerFunc) http.HandlerFunc {
137	return func(w http.ResponseWriter, r *http.Request) {
138		// if user has been set to context it is logged in already
139		token := GetUserFromCtx(r)
140		if token != nil {
141			next(w, r)
142			return
143		}
144
145		path := r.URL.Path
146		if path == "/initial" {
147			next(w, r)
148			return
149		}
150
151		exists, err := i.userRepository.Any(r.Context())
152		if err != nil {
153			InternalServerError(w, err)
154			return
155		}
156
157		if exists {
158			next(w, r)
159			return
160		}
161		http.Redirect(w, r, "/initial", http.StatusTemporaryRedirect)
162	}
163}