diff --git a/go.mod b/go.mod index fa3b9a2..75c2a7b 100644 --- a/go.mod +++ b/go.mod @@ -5,24 +5,29 @@ go 1.22.1 require ( github.com/a-h/templ v0.2.680 github.com/getsentry/sentry-go v0.27.0 - github.com/golang-jwt/jwt v3.2.2+incompatible + github.com/golang-jwt/jwt/v5 v5.0.0 + github.com/gorilla/sessions v1.1.1 github.com/joho/godotenv v1.5.1 github.com/labstack/echo/v4 v4.12.0 github.com/labstack/gommon v0.4.2 + github.com/markbates/goth v1.79.0 github.com/rjNemo/underscore v0.6.1 gorm.io/driver/postgres v1.5.7 gorm.io/gorm v1.25.10 ) require ( - github.com/golang-jwt/jwt/v5 v5.0.0 // indirect + 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/mux v1.6.2 // indirect + github.com/gorilla/securecookie v1.1.1 // 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-jwt/v4 v4.2.0 // 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 @@ -30,6 +35,7 @@ require ( golang.org/x/crypto v0.23.0 // indirect golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect golang.org/x/net v0.25.0 // indirect + golang.org/x/oauth2 v0.20.0 // indirect golang.org/x/sync v0.7.0 // indirect golang.org/x/sys v0.20.0 // indirect golang.org/x/text v0.15.0 // indirect diff --git a/go.sum b/go.sum index c7bfa56..31c85f2 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/a-h/templ v0.2.663 h1:aa0WMm27InkYHGjimcM7us6hJ6BLhg98ZbfaiDPyjHE= -github.com/a-h/templ v0.2.663/go.mod h1:SA7mtYwVEajbIXFRh3vKdYm/4FYyLQAtPH1+KxzGPA8= +cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= +cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= github.com/a-h/templ v0.2.680 h1:TflYFucxp5rmOxAXB9Xy3+QHTk8s8xG9+nCT/cLzjeE= github.com/a-h/templ v0.2.680/go.mod h1:NQGQOycaPKBxRB14DmAaeIpcGC1AOBPJEMO4ozS7m90= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -15,12 +15,18 @@ github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJ github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= 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/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/sessions v1.1.1 h1:YMDmfaK68mUixINzY/XjscuJ47uXFWSSHzFbBQM0PrE= +github.com/gorilla/sessions v1.1.1/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w= 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= github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.5.4 h1:Xp2aQS8uXButQdnCMWNmvx6UysWQQC+u1EoizjguY+8= -github.com/jackc/pgx/v5 v5.5.4/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw= github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= @@ -31,12 +37,12 @@ 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-jwt/v4 v4.2.0 h1:odSISV9JgcSCuhgQSV/6Io3i7nUmfM/QkBeR5GVJj5c= -github.com/labstack/echo-jwt/v4 v4.2.0/go.mod h1:MA2RqdXdEn4/uEglx0HcUOgQSyBaTh5JcaHIan3biwU= 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= github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= +github.com/markbates/goth v1.79.0 h1:fUYi9R6VubVEK2bpmXvIUp7xRcxA68i8ovfUQx/i5Qc= +github.com/markbates/goth v1.79.0/go.mod h1:RBD+tcFnXul2NnYuODhnIweOcuVPkBohLfEvutPekcU= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= @@ -59,32 +65,20 @@ github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6Kllzaw github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= -golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= -golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc= -golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= -golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f h1:99ci1mjWVBWwJiEKYY6jWa4d2nTQVIEhZIptnrVb1XY= -golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI= golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= -golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= -golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo= +golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= @@ -95,7 +89,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/driver/postgres v1.5.7 h1:8ptbNJTDbEmhdr62uReG5BGkdQyeasu/FZHxI0IMGnM= gorm.io/driver/postgres v1.5.7/go.mod h1:3e019WlBaYI5o5LIdNV+LyxCMNtLOQETBXL2h4chKpA= -gorm.io/gorm v1.25.9 h1:wct0gxZIELDk8+ZqF/MVnHLkA1rvYlBWUMv2EdsK1g8= -gorm.io/gorm v1.25.9/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= gorm.io/gorm v1.25.10 h1:dQpO+33KalOA+aFYGlK+EfxcI5MbO7EP2yYygwh9h+s= gorm.io/gorm v1.25.10/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= diff --git a/internal/server/handle_auth.go b/internal/server/handle_auth.go index 2a09ece..12f86a1 100644 --- a/internal/server/handle_auth.go +++ b/internal/server/handle_auth.go @@ -1,43 +1,78 @@ package server import ( - "errors" "fmt" + "html/template" "net/http" - "os" "github.com/labstack/echo/v4" + "github.com/markbates/goth/gothic" - "github.com/rjNemo/rentease/internal/view" + "github.com/rjNemo/rentease/internal/auth" ) -func handleLoginPage() echo.HandlerFunc { +var indexTemplate = `{{range $key,$value:=.Providers}} +

Log in with {{index $.ProvidersMap $value}}

+{{end}}` + +var userTemplate = ` +

logout

+

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 { return func(c echo.Context) error { - return renderTempl(c, http.StatusOK, view.Login()) - } -} - -// TODO: move to auth service -func signIn(email, pwd string) error { - if email != os.Getenv("ADMIN") || pwd != os.Getenv("ADMIN_PASSWORD") { - return errors.New("unauthorized") - } - return nil -} - -func handleLogin() echo.HandlerFunc { - return func(c echo.Context) error { - email := c.FormValue("email") - pwd := c.FormValue("password") - - err := signIn(email, pwd) + res := c.Response() + req := c.Request() + user, err := gothic.CompleteUserAuth(res, req) if err != nil { - return c.Redirect(http.StatusSeeOther, fmt.Sprintf("%s?err=invalid+credentials", "/login")) + fmt.Fprintln(res, err) + return nil } - - if err = writeCookie(c, email); err != nil { - return c.Redirect(http.StatusSeeOther, fmt.Sprintf("%s?err=invalid+credentials", "/login")) - } - return c.Redirect(http.StatusFound, "/bookings") + 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 + } + } +} + +func handleLoginPage(as *auth.Service) echo.HandlerFunc { + return func(c echo.Context) error { + t, _ := template.New("foo").Parse(indexTemplate) + return t.Execute(c.Response(), as.GetProviderIndex()) + //return renderTempl(c, http.StatusOK, view.Login(as.GetProviderIndex())) } } diff --git a/internal/server/routes.go b/internal/server/routes.go index a9887e5..2f256a1 100644 --- a/internal/server/routes.go +++ b/internal/server/routes.go @@ -1,15 +1,23 @@ package server +import ( + "net/http" + _ "net/http/pprof" + + "github.com/labstack/echo/v4" +) + func (s Server) MountHandlers() { // public - s.Router.GET("/", handleHomePage()) - s.Router.POST("/request-booking", handleRequestBooking(s.bs)) + s.Router.GET("/debug/pprof/*", echo.WrapHandler(http.DefaultServeMux)) + s.Router.POST("/", handleExtension()) // authentication - s.Router.GET("/login", handleLoginPage()) - s.Router.POST("/login", handleLogin()) + s.Router.GET("/", handleLoginPage(s.as)) + s.Router.GET("/auth", handleProvider()) + s.Router.GET("/auth/callback", handleProviderCallback()) + s.Router.GET("/logout", handleProviderLogout()) // 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 5fe2fcb..2d01f85 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -18,6 +18,7 @@ import ( "github.com/labstack/echo/v4/middleware" "github.com/rjNemo/rentease/config" + "github.com/rjNemo/rentease/internal/auth" "github.com/rjNemo/rentease/internal/booking" "github.com/rjNemo/rentease/internal/pdf" ) @@ -25,6 +26,7 @@ import ( type Server struct { Router *echo.Echo bs *booking.Service + as *auth.Service ps *pdf.PdfService hc *config.Host addr string @@ -71,7 +73,7 @@ func WithSecretKey(secretKey string) Option { } } -func New(bs *booking.Service, ps *pdf.PdfService, hc *config.Host, opts ...Option) (*Server, error) { +func New(bs *booking.Service, as *auth.Service, ps *pdf.PdfService, hc *config.Host, opts ...Option) (*Server, error) { option := new(options) for _, opt := range opts { err := opt(option) @@ -83,6 +85,7 @@ func New(bs *booking.Service, ps *pdf.PdfService, hc *config.Host, opts ...Optio s := &Server{ Router: NewRouter(*option.fs, *option.debug), bs: bs, + as: as, ps: ps, hc: hc, addr: fmt.Sprintf("0.0.0.0:%d", *option.port), diff --git a/main.go b/main.go index a2171cb..58d44e8 100644 --- a/main.go +++ b/main.go @@ -15,6 +15,7 @@ import ( "gorm.io/gorm" "github.com/rjNemo/rentease/config" + "github.com/rjNemo/rentease/internal/auth" "github.com/rjNemo/rentease/internal/booking" "github.com/rjNemo/rentease/internal/pdf" "github.com/rjNemo/rentease/internal/server" @@ -61,17 +62,21 @@ func run(ctx context.Context, getEnv func(string) string) error { return fmt.Errorf("error starting pdf service %s", err) } + as := auth.NewService(os.Getenv("SESSION_SECRET"), getEnv("GOOGLE_KEY"), getEnv("GOOGLE_SECRET"), getEnv("GOOGLE_REDIRECT_URL")) + p := getEnv("PORT") port, err := strconv.Atoi(p) if err != nil { return fmt.Errorf("error parsing PORT env %s", err) } + srv, err := server.New( - booking.NewService(db), ps, config.NewHost(), + booking.NewService(db), as, ps, config.NewHost(), server.WithPort(port), server.WithFileSystem(static), server.WithDebug(strings.ToLower(getEnv("DEBUG")) == "true"), server.WithSecretKey(getEnv("SECRET_KEY"))) if err != nil { return fmt.Errorf("error starting server %s", err) } + srv.Start() return nil }