diff --git a/go.mod b/go.mod index 75c2a7b..d967bf8 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/a-h/templ v0.2.680 github.com/getsentry/sentry-go v0.27.0 github.com/golang-jwt/jwt/v5 v5.0.0 - github.com/gorilla/sessions v1.1.1 + github.com/gorilla/sessions v1.2.2 github.com/joho/godotenv v1.5.1 github.com/labstack/echo/v4 v4.12.0 github.com/labstack/gommon v0.4.2 @@ -19,15 +19,16 @@ require ( require ( cloud.google.com/go/compute/metadata v0.3.0 // indirect github.com/golang-jwt/jwt v3.2.2+incompatible // indirect - github.com/gorilla/context v1.1.1 // indirect + github.com/gorilla/context v1.1.2 // indirect github.com/gorilla/mux v1.6.2 // indirect - github.com/gorilla/securecookie v1.1.1 // indirect + github.com/gorilla/securecookie v1.1.2 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect github.com/jackc/pgx/v5 v5.5.5 // indirect github.com/jackc/puddle/v2 v2.2.1 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect + github.com/labstack/echo-contrib v0.17.1 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect diff --git a/go.sum b/go.sum index 31c85f2..950198f 100644 --- a/go.sum +++ b/go.sum @@ -17,12 +17,18 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o= +github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM= github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= +github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= +github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= github.com/gorilla/sessions v1.1.1 h1:YMDmfaK68mUixINzY/XjscuJ47uXFWSSHzFbBQM0PrE= github.com/gorilla/sessions v1.1.1/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w= +github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY= +github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA= @@ -37,6 +43,8 @@ github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/labstack/echo-contrib v0.17.1 h1:7I/he7ylVKsDUieaGRZ9XxxTYOjfQwVzHzUYrNykfCU= +github.com/labstack/echo-contrib v0.17.1/go.mod h1:SnsCZtwHBAZm5uBSAtQtXQHI3wqEA73hvTn0bYMKnZA= github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0= github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM= github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= @@ -61,6 +69,7 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= diff --git a/internal/auth/service.go b/internal/auth/service.go index b0e66bd..a709ae5 100644 --- a/internal/auth/service.go +++ b/internal/auth/service.go @@ -1,19 +1,9 @@ package auth -import ( - "sort" - - "github.com/gorilla/sessions" - "github.com/markbates/goth" - "github.com/markbates/goth/gothic" - "github.com/markbates/goth/providers/google" -) - type Service struct { - secret string - googleClientId string - googleSecret string - googleRedirectUrl string + secret string + admin string + adminSecret string } type ProviderIndex struct { @@ -21,28 +11,14 @@ type ProviderIndex struct { Providers []string } -func NewService(secret, googleClientId, googleSecret, googleRedirectUrl string) *Service { +func NewService(secret, admin, adminSecret string) *Service { return &Service{ secret, - googleClientId, - googleSecret, - googleRedirectUrl, + admin, + adminSecret, } } -func (as Service) GetProviderIndex() *ProviderIndex { - goth.UseProviders(google.New(as.googleClientId, as.googleSecret, as.googleRedirectUrl)) - - m := map[string]string{ - "google": "Google", - } - var keys []string - for k := range m { - keys = append(keys, k) - } - sort.Strings(keys) - - gothic.Store = sessions.NewCookieStore([]byte(as.secret)) - - return &ProviderIndex{Providers: keys, ProvidersMap: m} +func (as *Service) Authenticate(email, password string) bool { + return email == as.admin && password == as.adminSecret } diff --git a/internal/server/auth.go b/internal/server/auth.go index 5fff223..4383984 100644 --- a/internal/server/auth.go +++ b/internal/server/auth.go @@ -1,81 +1,38 @@ package server import ( + "fmt" "net/http" - "os" - "time" - "github.com/golang-jwt/jwt/v5" + "github.com/labstack/echo-contrib/session" "github.com/labstack/echo/v4" ) const ( cookieName = "rentuuid" - routeLogin = "/login" + routeLogin = "/" ) -var validityTime = time.Now().Add(time.Hour * 24) - -type Claims struct { - jwt.RegisteredClaims -} - func MakeAuthMiddleware(secretKey string) echo.MiddlewareFunc { return func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { - cookie, err := c.Cookie(cookieName) - if err != nil { - return c.Redirect(http.StatusSeeOther, routeLogin) - } - signedToken := cookie.Value - token, err := jwt.Parse( - signedToken, - func(*jwt.Token) (interface{}, error) { - return []byte(secretKey), nil - }, - jwt.WithValidMethods([]string{jwt.SigningMethodHS256.Name}), - ) - if err != nil { - return c.Redirect(http.StatusSeeOther, routeLogin) - } - if !token.Valid { - return c.Redirect(http.StatusSeeOther, routeLogin) + if c.Request().RequestURI == routeLogin { + return next(c) } - _, err = token.Claims.GetSubject() - if err != nil { - return c.Redirect(http.StatusSeeOther, routeLogin) + s, err := readSession(c) + if s != "foo" || err != nil { + return c.Redirect(http.StatusUnauthorized, routeLogin) } - return next(c) } } } -// TODO: refactor to use a `AuthService` -func writeCookie(c echo.Context, email string) error { - claims := &Claims{ - jwt.RegisteredClaims{ - Subject: email, - ExpiresAt: jwt.NewNumericDate(validityTime), - }, - } - token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) - signedToken, err := token.SignedString([]byte(os.Getenv("SECRET_KEY"))) +func readSession(c echo.Context) (string, error) { + sess, err := session.Get(sessionName, c) if err != nil { - return err + return "", err } - - cookie := new(http.Cookie) - cookie.Name = cookieName - cookie.Value = signedToken - cookie.Expires = validityTime - cookie.HttpOnly = true - cookie.Domain = os.Getenv("DOMAIN") - cookie.Secure = true - cookie.SameSite = http.SameSiteStrictMode - - c.SetCookie(cookie) - - return nil + return fmt.Sprintf("foo=%v\n", sess.Values["foo"]), nil } diff --git a/internal/server/handle_auth.go b/internal/server/handle_auth.go index 890f6ce..91b19ed 100644 --- a/internal/server/handle_auth.go +++ b/internal/server/handle_auth.go @@ -1,77 +1,49 @@ package server import ( - "fmt" - "html/template" "net/http" + "github.com/gorilla/sessions" + "github.com/labstack/echo-contrib/session" "github.com/labstack/echo/v4" - "github.com/markbates/goth/gothic" - + "github.com/labstack/gommon/log" "github.com/rjNemo/rentease/internal/auth" "github.com/rjNemo/rentease/internal/view" ) -var indexTemplate = `{{range $key,$value:=.Providers}} -
Log in with {{index $.ProvidersMap $value}}
-{{end}}` +const ( + sessionName = "rentease" + sessionAge = 86400 * 7 // 7 days +) -var userTemplate = ` - -Name: {{.Name}} [{{.LastName}}, {{.FirstName}}]
-Email: {{.Email}}
-NickName: {{.NickName}}
-Location: {{.Location}}
-AvatarURL: {{.AvatarURL}}
Description: {{.Description}}
-UserID: {{.UserID}}
-AccessToken: {{.AccessToken}}
-ExpiresAt: {{.ExpiresAt}}
-RefreshToken: {{.RefreshToken}}
-` - -func handleProviderCallback() echo.HandlerFunc { +func handleLoginPage() echo.HandlerFunc { return func(c echo.Context) error { - res := c.Response() - req := c.Request() - user, err := gothic.CompleteUserAuth(res, req) + return renderTempl(c, http.StatusOK, view.Login()) + } +} + +func handleLogin(as *auth.Service) echo.HandlerFunc { + return func(c echo.Context) error { + sess, err := session.Get(sessionName, c) if err != nil { - fmt.Fprintln(res, err) - return nil + log.Warn(err) + return err } - t, _ := template.New("foo").Parse(userTemplate) - return t.Execute(res, user) - } -} - -func handleProviderLogout() echo.HandlerFunc { - return func(c echo.Context) error { - res := c.Response() - req := c.Request() - err := gothic.Logout(res, req) - res.Header().Set("Location", "/") - res.WriteHeader(http.StatusTemporaryRedirect) - return err - } -} - -func handleProvider() echo.HandlerFunc { - return func(c echo.Context) error { - res := c.Response() - req := c.Request() - // try to get the user without re-authenticating - if gothUser, err := gothic.CompleteUserAuth(res, req); err == nil { - t, _ := template.New("foo").Parse(userTemplate) - return t.Execute(res, gothUser) - } else { - gothic.BeginAuthHandler(res, req) - return nil + sess.Options = &sessions.Options{ + Path: "/", + MaxAge: sessionAge, + HttpOnly: true, } - } -} -func handleLoginPage(as *auth.Service) echo.HandlerFunc { - return func(c echo.Context) error { - return renderTempl(c, http.StatusOK, view.Login(as.GetProviderIndex())) + if !as.Authenticate(c.FormValue("email"), c.FormValue("password")) { + return c.Redirect(http.StatusTemporaryRedirect, routeLogin) + } + + sess.Values["foo"] = "bar" + if err := sess.Save(c.Request(), c.Response()); err != nil { + log.Warn(err) + return err + } + return c.Redirect(http.StatusSeeOther, "/bookings") } } diff --git a/internal/server/routes.go b/internal/server/routes.go index 2f256a1..1a075ab 100644 --- a/internal/server/routes.go +++ b/internal/server/routes.go @@ -10,14 +10,11 @@ import ( func (s Server) MountHandlers() { // public s.Router.GET("/debug/pprof/*", echo.WrapHandler(http.DefaultServeMux)) - s.Router.POST("/", handleExtension()) - // authentication - s.Router.GET("/", handleLoginPage(s.as)) - s.Router.GET("/auth", handleProvider()) - s.Router.GET("/auth/callback", handleProviderCallback()) - s.Router.GET("/logout", handleProviderLogout()) + s.Router.GET("/", handleLoginPage()) + s.Router.POST("/", handleLogin(s.as)) // admin g := s.Router.Group("") + g.Use(MakeAuthMiddleware(s.secretKey)) g.GET("/bookings", handleListBookingPage(s.bs, s.hc)) g.GET("/bookings/new", handleNewBookingPage(s.hc)) g.POST("/bookings/new", handleCreateBooking(s.bs)) diff --git a/internal/server/server.go b/internal/server/server.go index 2d01f85..fd0ac82 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -14,6 +14,8 @@ import ( "github.com/a-h/templ" "github.com/getsentry/sentry-go" sentryecho "github.com/getsentry/sentry-go/echo" + "github.com/gorilla/sessions" + "github.com/labstack/echo-contrib/session" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" @@ -83,7 +85,7 @@ func New(bs *booking.Service, as *auth.Service, ps *pdf.PdfService, hc *config.H } s := &Server{ - Router: NewRouter(*option.fs, *option.debug), + Router: NewRouter(*option.fs, *option.debug, *option.secretKey), bs: bs, as: as, ps: ps, @@ -125,7 +127,7 @@ func renderTempl(c echo.Context, status int, t templ.Component) error { return nil } -func NewRouter(fs embed.FS, debug bool) *echo.Echo { +func NewRouter(fs embed.FS, debug bool, secret string) *echo.Echo { e := echo.New() // config e.HideBanner = true @@ -153,9 +155,10 @@ func NewRouter(fs embed.FS, debug bool) *echo.Echo { e.Use(middleware.Recover()) e.Use(middleware.Secure()) e.Use(middleware.Gzip()) - e.Use(middleware.CSRF()) + // e.Use(middleware.CSRF()) e.Use(sentryecho.New(sentryecho.Options{})) e.Use(SentryTracingMiddleware) + e.Use(session.Middleware(sessions.NewCookieStore([]byte(secret)))) // static assets e.StaticFS("/static", echo.MustSubFS(fs, "assets")) diff --git a/internal/view/login.templ b/internal/view/login.templ index 9678838..31449eb 100644 --- a/internal/view/login.templ +++ b/internal/view/login.templ @@ -1,32 +1,19 @@ package view import ( - "github.com/rjNemo/rentease/internal/auth" "github.com/rjNemo/rentease/internal/view/layout" ) -// inject the providers into the template even if we don't use it -templ Login(providers *auth.ProviderIndex) { - @layout.PublicLayout() { +templ Login() { + @layout.BaseLayout() {