shiori/internal/http/server.go
Felipe Martin 514df1e8ab
fix: auth validation on existing sessions, rely on token only (#1069)
* chore: use http.NoBody

* fix: remove cookie token on logout

* fix: remove token cookie on middleware and redirect

* fix: frontend sets cookie token if authenticated

* refactor: remove session-id, rely on token only

* docs: make swagger

* fix: redirect

* fix: archive route handler

* fix: properly unset cookie
2025-02-28 20:30:07 +01:00

190 lines
6.2 KiB
Go

package http
import (
"context"
"fmt"
"net/http"
"os"
"os/signal"
"syscall"
"github.com/go-shiori/shiori/internal/config"
"github.com/go-shiori/shiori/internal/dependencies"
"github.com/go-shiori/shiori/internal/http/handlers"
api_v1 "github.com/go-shiori/shiori/internal/http/handlers/api/v1"
"github.com/go-shiori/shiori/internal/http/middleware"
"github.com/go-shiori/shiori/internal/http/templates"
"github.com/go-shiori/shiori/internal/model"
"github.com/sirupsen/logrus"
)
type HttpServer struct {
mux *http.ServeMux
server *http.Server
logger *logrus.Logger
}
func (s *HttpServer) Setup(cfg *config.Config, deps *dependencies.Dependencies) (*HttpServer, error) {
s.mux = http.NewServeMux()
if err := templates.SetupTemplates(); err != nil {
return nil, fmt.Errorf("failed to setup templates: %w", err)
}
globalMiddleware := []model.HttpMiddleware{
middleware.NewAuthMiddleware(deps),
middleware.NewRequestIDMiddleware(deps),
}
if cfg.Http.AccessLog {
globalMiddleware = append(globalMiddleware, middleware.NewLoggingMiddleware())
}
// System routes with logging middleware
s.mux.HandleFunc("GET /system/liveness", ToHTTPHandler(deps,
handlers.HandleLiveness,
globalMiddleware...,
))
// Bookmark routes
s.mux.HandleFunc("GET /bookmark/{id}/content", ToHTTPHandler(deps, handlers.HandleBookmarkContent, globalMiddleware...))
s.mux.HandleFunc("GET /bookmark/{id}/archive", ToHTTPHandler(deps, handlers.HandleBookmarkArchive, globalMiddleware...))
s.mux.HandleFunc("GET /bookmark/{id}/archive/file/{path...}", ToHTTPHandler(deps, handlers.HandleBookmarkArchiveFile, globalMiddleware...))
s.mux.HandleFunc("GET /bookmark/{id}/thumb", ToHTTPHandler(deps, handlers.HandleBookmarkThumbnail, globalMiddleware...))
s.mux.HandleFunc("GET /bookmark/{id}/ebook", ToHTTPHandler(deps, handlers.HandleBookmarkEbook, globalMiddleware...))
// Add this inside Setup() where other routes are registered
if cfg.Http.ServeSwagger {
s.mux.HandleFunc("/swagger/", ToHTTPHandler(deps,
handlers.HandleSwagger,
globalMiddleware...,
))
}
// API v1 routes
s.mux.HandleFunc("GET /api/v1/system/info", ToHTTPHandler(deps,
api_v1.HandleSystemInfo,
globalMiddleware...,
))
// Legacy API routes
// TODO: Remove this once the legacy API is removed
legacyHandler := handlers.NewLegacyHandler(deps)
s.mux.HandleFunc("GET /api/tags", ToHTTPHandler(deps, legacyHandler.HandleGetTags, globalMiddleware...))
s.mux.HandleFunc("PUT /api/tags", ToHTTPHandler(deps, legacyHandler.HandleRenameTag, globalMiddleware...))
s.mux.HandleFunc("GET /api/bookmarks", ToHTTPHandler(deps, legacyHandler.HandleGetBookmarks, globalMiddleware...))
s.mux.HandleFunc("POST /api/bookmarks", ToHTTPHandler(deps, legacyHandler.HandleInsertBookmark, globalMiddleware...))
s.mux.HandleFunc("DELETE /api/bookmarks", ToHTTPHandler(deps, legacyHandler.HandleDeleteBookmark, globalMiddleware...))
s.mux.HandleFunc("PUT /api/bookmarks", ToHTTPHandler(deps, legacyHandler.HandleUpdateBookmark, globalMiddleware...))
s.mux.HandleFunc("PUT /api/bookmarks/tags", ToHTTPHandler(deps, legacyHandler.HandleUpdateBookmarkTags, globalMiddleware...))
s.mux.HandleFunc("POST /api/bookmarks/ext", ToHTTPHandler(deps, legacyHandler.HandleInsertViaExtension, globalMiddleware...))
s.mux.HandleFunc("DELETE /api/bookmarks/ext", ToHTTPHandler(deps, legacyHandler.HandleDeleteViaExtension, globalMiddleware...))
// Register routes using standard http handlers
if cfg.Http.ServeWebUI {
// Frontend routes
s.mux.HandleFunc("/", ToHTTPHandler(deps,
handlers.HandleFrontend,
globalMiddleware...,
))
s.mux.HandleFunc("GET /assets/", ToHTTPHandler(deps,
handlers.HandleAssets,
globalMiddleware...,
))
}
// API v1 routes
// Auth
s.mux.HandleFunc("POST /api/v1/auth/login", ToHTTPHandler(deps,
api_v1.HandleLogin,
globalMiddleware...,
))
s.mux.HandleFunc("POST /api/v1/auth/refresh", ToHTTPHandler(deps,
api_v1.HandleRefreshToken,
globalMiddleware...,
))
s.mux.HandleFunc("GET /api/v1/auth/me", ToHTTPHandler(deps,
api_v1.HandleGetMe,
globalMiddleware...,
))
s.mux.HandleFunc("PATCH /api/v1/auth/account", ToHTTPHandler(deps,
api_v1.HandleUpdateLoggedAccount,
globalMiddleware...,
))
s.mux.HandleFunc("POST /api/v1/auth/logout", ToHTTPHandler(deps,
api_v1.HandleLogout,
globalMiddleware...,
))
// Accounts
s.mux.HandleFunc("GET /api/v1/accounts", ToHTTPHandler(deps,
api_v1.HandleListAccounts,
globalMiddleware...,
))
s.mux.HandleFunc("POST /api/v1/accounts", ToHTTPHandler(deps,
api_v1.HandleCreateAccount,
globalMiddleware...,
))
s.mux.HandleFunc("DELETE /api/v1/accounts/{id}", ToHTTPHandler(deps,
api_v1.HandleDeleteAccount,
globalMiddleware...,
))
s.mux.HandleFunc("PATCH /api/v1/accounts/{id}", ToHTTPHandler(deps,
api_v1.HandleUpdateAccount,
globalMiddleware...,
))
// Tags
s.mux.HandleFunc("GET /api/v1/tags", ToHTTPHandler(deps,
api_v1.HandleListTags,
globalMiddleware...,
))
// Bookmarks
s.mux.HandleFunc("PUT /api/v1/bookmarks/cache", ToHTTPHandler(deps,
api_v1.HandleUpdateCache,
globalMiddleware...,
))
s.mux.HandleFunc("GET /api/v1/bookmarks/{id}/readable", ToHTTPHandler(deps,
api_v1.HandleBookmarkReadable,
globalMiddleware...,
))
s.server = &http.Server{
Addr: fmt.Sprintf("%s%d", cfg.Http.Address, cfg.Http.Port),
Handler: s.mux,
}
return s, nil
}
func (s *HttpServer) Start(_ context.Context) error {
s.logger.WithField("addr", s.server.Addr).Info("starting http server")
go func() {
if err := s.server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
s.logger.Fatalf("listen and serve error: %s\n", err)
}
}()
return nil
}
func (s *HttpServer) Stop(ctx context.Context) error {
s.logger.WithField("addr", s.server.Addr).Info("stopping http server")
return s.server.Shutdown(ctx)
}
func (s *HttpServer) WaitStop(ctx context.Context) {
signals := make(chan os.Signal, 1)
signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM)
sig := <-signals
s.logger.WithField("signal", sig.String()).Info("signal received, shutting down")
if err := s.Stop(ctx); err != nil {
s.logger.WithError(err).Error("error stopping server")
}
}
func NewHttpServer(logger *logrus.Logger) *HttpServer {
return &HttpServer{
logger: logger,
}
}