package ext import ( "context" "encoding/base64" "errors" "log/slog" "net/http" "time" "git.sr.ht/~gabrielgio/img/pkg/database/repository" "git.sr.ht/~gabrielgio/img/pkg/service" ) func HTML(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/html") next(w, r) } } type ( User string LogMiddleware struct { logger *slog.Logger } ) const ( UserKey User = "user" ) func NewLogMiddleare(log *slog.Logger) *LogMiddleware { return &LogMiddleware{ logger: log, } } func (l *LogMiddleware) HTTP(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { start := time.Now() next(w, r) elapsed := time.Since(start) l.logger.Info( r.Method, slog.Duration("elapsed", elapsed), slog.String("path", r.URL.Path), ) } } type AuthMiddleware struct { key []byte logger *slog.Logger userRepository repository.UserRepository } func NewAuthMiddleware( key []byte, logger *slog.Logger, userRepository repository.UserRepository, ) *AuthMiddleware { return &AuthMiddleware{ key: key, logger: logger, userRepository: userRepository, } } func (a *AuthMiddleware) LoggedIn(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { path := r.URL.Path if path == "/login" || path == "/initial" { next(w, r) return } redirectLogin := "/login?redirect=" + path authBase64, err := r.Cookie("auth") if errors.Is(err, http.ErrNoCookie) { a.logger.Info("No auth provided") http.Redirect(w, r, redirectLogin, http.StatusTemporaryRedirect) return } auth, err := base64.StdEncoding.DecodeString(authBase64.Value) if err != nil { a.logger.Error(err.Error()) return } token, err := service.ReadToken(auth, a.key) if err != nil { a.logger.Error(err.Error()) http.Redirect(w, r, redirectLogin, http.StatusTemporaryRedirect) return } user, err := a.userRepository.Get(r.Context(), token.UserID) if err != nil { a.logger.Error(err.Error()) return } r = r.WithContext(context.WithValue(r.Context(), UserKey, user)) a.logger. Info( "user recognized", slog.Uint64("userid", uint64(token.UserID)), slog.String("username", token.Username), ) next(w, r) } } func GetUserFromCtx(r *http.Request) *repository.User { tokenValue := r.Context().Value(UserKey) if token, ok := tokenValue.(*repository.User); ok { return token } return nil } type InitialSetupMiddleware struct { userRepository repository.UserRepository } func NewInitialSetupMiddleware(userRepository repository.UserRepository) *InitialSetupMiddleware { return &InitialSetupMiddleware{ userRepository: userRepository, } } func (i *InitialSetupMiddleware) Check(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { // if user has been set to context it is logged in already token := GetUserFromCtx(r) if token != nil { next(w, r) return } path := r.URL.Path if path == "/initial" { next(w, r) return } exists, err := i.userRepository.Any(r.Context()) if err != nil { InternalServerError(w, err) return } if exists { next(w, r) return } http.Redirect(w, r, "/initial", http.StatusTemporaryRedirect) } }