lens @ c3ea735c0f03a0827a8e753a5b5adf6e31f4c925

  1package ext
  2
  3import (
  4	"context"
  5	"encoding/base64"
  6	"errors"
  7	"log/slog"
  8	"net/http"
  9	"time"
 10
 11	"git.sr.ht/~gabrielgio/img/pkg/database/repository"
 12	"git.sr.ht/~gabrielgio/img/pkg/service"
 13)
 14
 15func HTML(next http.HandlerFunc) http.HandlerFunc {
 16	return func(w http.ResponseWriter, r *http.Request) {
 17		w.Header().Set("Content-Type", "text/html")
 18		next(w, r)
 19	}
 20}
 21
 22type (
 23	User string
 24
 25	LogMiddleware struct {
 26		logger *slog.Logger
 27	}
 28)
 29
 30const (
 31	UserKey User = "user"
 32)
 33
 34func NewLogMiddleare(log *slog.Logger) *LogMiddleware {
 35	return &LogMiddleware{
 36		logger: log,
 37	}
 38}
 39
 40func (l *LogMiddleware) HTTP(next http.HandlerFunc) http.HandlerFunc {
 41	return func(w http.ResponseWriter, r *http.Request) {
 42		start := time.Now()
 43		next(w, r)
 44		elapsed := time.Since(start)
 45		l.logger.Info(
 46			r.Method,
 47			slog.Duration("elapsed", elapsed),
 48			slog.String("path", r.URL.Path),
 49		)
 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
139		// if user has been set to context it is logged in already
140		token := GetUserFromCtx(r)
141		if token != nil {
142			next(w, r)
143			return
144		}
145
146		path := r.URL.Path
147		if path == "/initial" {
148			next(w, r)
149			return
150		}
151
152		exists, err := i.userRepository.Any(r.Context())
153		if err != nil {
154			InternalServerError(w, err)
155			return
156		}
157
158		if exists {
159			next(w, r)
160			return
161		}
162		http.Redirect(w, r, "/initial", http.StatusTemporaryRedirect)
163	}
164}