mirror of
https://github.com/rjNemo/rentease.git
synced 2026-06-12 13:46:51 +00:00
session login
This commit is contained in:
parent
f66ada145a
commit
e1766812c4
10 changed files with 85 additions and 185 deletions
7
go.mod
7
go.mod
|
|
@ -6,7 +6,7 @@ require (
|
||||||
github.com/a-h/templ v0.2.680
|
github.com/a-h/templ v0.2.680
|
||||||
github.com/getsentry/sentry-go v0.27.0
|
github.com/getsentry/sentry-go v0.27.0
|
||||||
github.com/golang-jwt/jwt/v5 v5.0.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/joho/godotenv v1.5.1
|
||||||
github.com/labstack/echo/v4 v4.12.0
|
github.com/labstack/echo/v4 v4.12.0
|
||||||
github.com/labstack/gommon v0.4.2
|
github.com/labstack/gommon v0.4.2
|
||||||
|
|
@ -19,15 +19,16 @@ require (
|
||||||
require (
|
require (
|
||||||
cloud.google.com/go/compute/metadata v0.3.0 // indirect
|
cloud.google.com/go/compute/metadata v0.3.0 // indirect
|
||||||
github.com/golang-jwt/jwt v3.2.2+incompatible // 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/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/pgpassfile v1.0.0 // indirect
|
||||||
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect
|
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect
|
||||||
github.com/jackc/pgx/v5 v5.5.5 // indirect
|
github.com/jackc/pgx/v5 v5.5.5 // indirect
|
||||||
github.com/jackc/puddle/v2 v2.2.1 // indirect
|
github.com/jackc/puddle/v2 v2.2.1 // indirect
|
||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
github.com/jinzhu/now v1.1.5 // 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-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
|
|
|
||||||
9
go.sum
9
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/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 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
|
||||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
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 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk=
|
||||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
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 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.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 h1:YMDmfaK68mUixINzY/XjscuJ47uXFWSSHzFbBQM0PrE=
|
||||||
github.com/gorilla/sessions v1.1.1/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w=
|
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 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
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 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/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 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
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 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0=
|
||||||
github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM=
|
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 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.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 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
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 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
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 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,9 @@
|
||||||
package auth
|
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 {
|
type Service struct {
|
||||||
secret string
|
secret string
|
||||||
googleClientId string
|
admin string
|
||||||
googleSecret string
|
adminSecret string
|
||||||
googleRedirectUrl string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProviderIndex struct {
|
type ProviderIndex struct {
|
||||||
|
|
@ -21,28 +11,14 @@ type ProviderIndex struct {
|
||||||
Providers []string
|
Providers []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewService(secret, googleClientId, googleSecret, googleRedirectUrl string) *Service {
|
func NewService(secret, admin, adminSecret string) *Service {
|
||||||
return &Service{
|
return &Service{
|
||||||
secret,
|
secret,
|
||||||
googleClientId,
|
admin,
|
||||||
googleSecret,
|
adminSecret,
|
||||||
googleRedirectUrl,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (as Service) GetProviderIndex() *ProviderIndex {
|
func (as *Service) Authenticate(email, password string) bool {
|
||||||
goth.UseProviders(google.New(as.googleClientId, as.googleSecret, as.googleRedirectUrl))
|
return email == as.admin && password == as.adminSecret
|
||||||
|
|
||||||
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}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,81 +1,38 @@
|
||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/golang-jwt/jwt/v5"
|
"github.com/labstack/echo-contrib/session"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
cookieName = "rentuuid"
|
cookieName = "rentuuid"
|
||||||
routeLogin = "/login"
|
routeLogin = "/"
|
||||||
)
|
)
|
||||||
|
|
||||||
var validityTime = time.Now().Add(time.Hour * 24)
|
|
||||||
|
|
||||||
type Claims struct {
|
|
||||||
jwt.RegisteredClaims
|
|
||||||
}
|
|
||||||
|
|
||||||
func MakeAuthMiddleware(secretKey string) echo.MiddlewareFunc {
|
func MakeAuthMiddleware(secretKey string) echo.MiddlewareFunc {
|
||||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||||
return func(c echo.Context) error {
|
return func(c echo.Context) error {
|
||||||
cookie, err := c.Cookie(cookieName)
|
if c.Request().RequestURI == routeLogin {
|
||||||
if err != nil {
|
return next(c)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = token.Claims.GetSubject()
|
s, err := readSession(c)
|
||||||
if err != nil {
|
if s != "foo" || err != nil {
|
||||||
return c.Redirect(http.StatusSeeOther, routeLogin)
|
return c.Redirect(http.StatusUnauthorized, routeLogin)
|
||||||
}
|
}
|
||||||
|
|
||||||
return next(c)
|
return next(c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: refactor to use a `AuthService`
|
func readSession(c echo.Context) (string, error) {
|
||||||
func writeCookie(c echo.Context, email string) error {
|
sess, err := session.Get(sessionName, c)
|
||||||
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")))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
|
return fmt.Sprintf("foo=%v\n", sess.Values["foo"]), nil
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,77 +1,49 @@
|
||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"html/template"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gorilla/sessions"
|
||||||
|
"github.com/labstack/echo-contrib/session"
|
||||||
"github.com/labstack/echo/v4"
|
"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/auth"
|
||||||
"github.com/rjNemo/rentease/internal/view"
|
"github.com/rjNemo/rentease/internal/view"
|
||||||
)
|
)
|
||||||
|
|
||||||
var indexTemplate = `{{range $key,$value:=.Providers}}
|
const (
|
||||||
<p><a href="/auth?provider={{$value}}">Log in with {{index $.ProvidersMap $value}}</a></p>
|
sessionName = "rentease"
|
||||||
{{end}}`
|
sessionAge = 86400 * 7 // 7 days
|
||||||
|
)
|
||||||
|
|
||||||
var userTemplate = `
|
func handleLoginPage() echo.HandlerFunc {
|
||||||
<p><a href="/logout?provider={{.Provider}}">logout</a></p>
|
|
||||||
<p>Name: {{.Name}} [{{.LastName}}, {{.FirstName}}]</p>
|
|
||||||
<p>Email: {{.Email}}</p>
|
|
||||||
<p>NickName: {{.NickName}}</p>
|
|
||||||
<p>Location: {{.Location}}</p>
|
|
||||||
<p>AvatarURL: {{.AvatarURL}} <img src="{{.AvatarURL}}"></p>
|
|
||||||
<p>Description: {{.Description}}</p>
|
|
||||||
<p>UserID: {{.UserID}}</p>
|
|
||||||
<p>AccessToken: {{.AccessToken}}</p>
|
|
||||||
<p>ExpiresAt: {{.ExpiresAt}}</p>
|
|
||||||
<p>RefreshToken: {{.RefreshToken}}</p>
|
|
||||||
`
|
|
||||||
|
|
||||||
func handleProviderCallback() echo.HandlerFunc {
|
|
||||||
return func(c echo.Context) error {
|
return func(c echo.Context) error {
|
||||||
res := c.Response()
|
return renderTempl(c, http.StatusOK, view.Login())
|
||||||
req := c.Request()
|
}
|
||||||
user, err := gothic.CompleteUserAuth(res, req)
|
}
|
||||||
|
|
||||||
|
func handleLogin(as *auth.Service) echo.HandlerFunc {
|
||||||
|
return func(c echo.Context) error {
|
||||||
|
sess, err := session.Get(sessionName, c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintln(res, err)
|
log.Warn(err)
|
||||||
return nil
|
return err
|
||||||
}
|
}
|
||||||
t, _ := template.New("foo").Parse(userTemplate)
|
sess.Options = &sessions.Options{
|
||||||
return t.Execute(res, user)
|
Path: "/",
|
||||||
}
|
MaxAge: sessionAge,
|
||||||
}
|
HttpOnly: true,
|
||||||
|
|
||||||
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 {
|
if !as.Authenticate(c.FormValue("email"), c.FormValue("password")) {
|
||||||
return func(c echo.Context) error {
|
return c.Redirect(http.StatusTemporaryRedirect, routeLogin)
|
||||||
return renderTempl(c, http.StatusOK, view.Login(as.GetProviderIndex()))
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,14 +10,11 @@ import (
|
||||||
func (s Server) MountHandlers() {
|
func (s Server) MountHandlers() {
|
||||||
// public
|
// public
|
||||||
s.Router.GET("/debug/pprof/*", echo.WrapHandler(http.DefaultServeMux))
|
s.Router.GET("/debug/pprof/*", echo.WrapHandler(http.DefaultServeMux))
|
||||||
s.Router.POST("/", handleExtension())
|
s.Router.GET("/", handleLoginPage())
|
||||||
// authentication
|
s.Router.POST("/", handleLogin(s.as))
|
||||||
s.Router.GET("/", handleLoginPage(s.as))
|
|
||||||
s.Router.GET("/auth", handleProvider())
|
|
||||||
s.Router.GET("/auth/callback", handleProviderCallback())
|
|
||||||
s.Router.GET("/logout", handleProviderLogout())
|
|
||||||
// admin
|
// admin
|
||||||
g := s.Router.Group("")
|
g := s.Router.Group("")
|
||||||
|
g.Use(MakeAuthMiddleware(s.secretKey))
|
||||||
g.GET("/bookings", handleListBookingPage(s.bs, s.hc))
|
g.GET("/bookings", handleListBookingPage(s.bs, s.hc))
|
||||||
g.GET("/bookings/new", handleNewBookingPage(s.hc))
|
g.GET("/bookings/new", handleNewBookingPage(s.hc))
|
||||||
g.POST("/bookings/new", handleCreateBooking(s.bs))
|
g.POST("/bookings/new", handleCreateBooking(s.bs))
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@ import (
|
||||||
"github.com/a-h/templ"
|
"github.com/a-h/templ"
|
||||||
"github.com/getsentry/sentry-go"
|
"github.com/getsentry/sentry-go"
|
||||||
sentryecho "github.com/getsentry/sentry-go/echo"
|
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"
|
||||||
"github.com/labstack/echo/v4/middleware"
|
"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{
|
s := &Server{
|
||||||
Router: NewRouter(*option.fs, *option.debug),
|
Router: NewRouter(*option.fs, *option.debug, *option.secretKey),
|
||||||
bs: bs,
|
bs: bs,
|
||||||
as: as,
|
as: as,
|
||||||
ps: ps,
|
ps: ps,
|
||||||
|
|
@ -125,7 +127,7 @@ func renderTempl(c echo.Context, status int, t templ.Component) error {
|
||||||
return nil
|
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()
|
e := echo.New()
|
||||||
// config
|
// config
|
||||||
e.HideBanner = true
|
e.HideBanner = true
|
||||||
|
|
@ -153,9 +155,10 @@ func NewRouter(fs embed.FS, debug bool) *echo.Echo {
|
||||||
e.Use(middleware.Recover())
|
e.Use(middleware.Recover())
|
||||||
e.Use(middleware.Secure())
|
e.Use(middleware.Secure())
|
||||||
e.Use(middleware.Gzip())
|
e.Use(middleware.Gzip())
|
||||||
e.Use(middleware.CSRF())
|
// e.Use(middleware.CSRF())
|
||||||
e.Use(sentryecho.New(sentryecho.Options{}))
|
e.Use(sentryecho.New(sentryecho.Options{}))
|
||||||
e.Use(SentryTracingMiddleware)
|
e.Use(SentryTracingMiddleware)
|
||||||
|
e.Use(session.Middleware(sessions.NewCookieStore([]byte(secret))))
|
||||||
// static assets
|
// static assets
|
||||||
e.StaticFS("/static", echo.MustSubFS(fs, "assets"))
|
e.StaticFS("/static", echo.MustSubFS(fs, "assets"))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,19 @@
|
||||||
package view
|
package view
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/rjNemo/rentease/internal/auth"
|
|
||||||
"github.com/rjNemo/rentease/internal/view/layout"
|
"github.com/rjNemo/rentease/internal/view/layout"
|
||||||
)
|
)
|
||||||
|
|
||||||
// inject the providers into the template even if we don't use it
|
templ Login() {
|
||||||
templ Login(providers *auth.ProviderIndex) {
|
@layout.BaseLayout() {
|
||||||
@layout.PublicLayout() {
|
|
||||||
<main class="container">
|
<main class="container">
|
||||||
<section>
|
<section>
|
||||||
<h1>Login</h1>
|
<h1>Welcome</h1>
|
||||||
<button class="gsi-material-button" onclick="location.href='/auth?provider=google'">
|
<form hx-post="/">
|
||||||
<div class="gsi-material-button-state"></div>
|
<input type="email" name="email" placeholder="john@email.com" aria-label="email" autocomplete="email" autofocus required=""/>
|
||||||
<div class="gsi-material-button-content-wrapper">
|
<input type="password" name="password" placeholder="p4Ssw0rD" aria-label="password" autocomplete="password" required=""/>
|
||||||
<div class="gsi-material-button-icon">
|
<button type="submit">Log in</button>
|
||||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" xmlns:xlink="http://www.w3.org/1999/xlink" style="display: block;">
|
</form>
|
||||||
<path fill="#EA4335" d="M24 9.5c3.54 0 6.71 1.22 9.21 3.6l6.85-6.85C35.9 2.38 30.47 0 24 0 14.62 0 6.51 5.38 2.56 13.22l7.98 6.19C12.43 13.72 17.74 9.5 24 9.5z"></path>
|
|
||||||
<path fill="#4285F4" d="M46.98 24.55c0-1.57-.15-3.09-.38-4.55H24v9.02h12.94c-.58 2.96-2.26 5.48-4.78 7.18l7.73 6c4.51-4.18 7.09-10.36 7.09-17.65z"></path>
|
|
||||||
<path fill="#FBBC05" d="M10.53 28.59c-.48-1.45-.76-2.99-.76-4.59s.27-3.14.76-4.59l-7.98-6.19C.92 16.46 0 20.12 0 24c0 3.88.92 7.54 2.56 10.78l7.97-6.19z"></path>
|
|
||||||
<path fill="#34A853" d="M24 48c6.48 0 11.93-2.13 15.89-5.81l-7.73-6c-2.15 1.45-4.92 2.3-8.16 2.3-6.26 0-11.57-4.22-13.47-9.91l-7.98 6.19C6.51 42.62 14.62 48 24 48z"></path>
|
|
||||||
<path fill="none" d="M0 0h48v48H0z"></path>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<span class="gsi-material-button-contents">Continue with Google</span>
|
|
||||||
<span style="display: none;">Continue with Google</span>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,12 +11,10 @@ import "io"
|
||||||
import "bytes"
|
import "bytes"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/rjNemo/rentease/internal/auth"
|
|
||||||
"github.com/rjNemo/rentease/internal/view/layout"
|
"github.com/rjNemo/rentease/internal/view/layout"
|
||||||
)
|
)
|
||||||
|
|
||||||
// inject the providers into the template even if we don't use it
|
func Login() templ.Component {
|
||||||
func Login(providers *auth.ProviderIndex) templ.Component {
|
|
||||||
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
||||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
||||||
if !templ_7745c5c3_IsBuffer {
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
|
@ -35,7 +33,7 @@ func Login(providers *auth.ProviderIndex) templ.Component {
|
||||||
templ_7745c5c3_Buffer = templ.GetBuffer()
|
templ_7745c5c3_Buffer = templ.GetBuffer()
|
||||||
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<main class=\"container\"><section><h1>Login</h1><button class=\"gsi-material-button\" onclick=\"location.href='/auth?provider=google'\"><div class=\"gsi-material-button-state\"></div><div class=\"gsi-material-button-content-wrapper\"><div class=\"gsi-material-button-icon\"><svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 48 48\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" style=\"display: block;\"><path fill=\"#EA4335\" d=\"M24 9.5c3.54 0 6.71 1.22 9.21 3.6l6.85-6.85C35.9 2.38 30.47 0 24 0 14.62 0 6.51 5.38 2.56 13.22l7.98 6.19C12.43 13.72 17.74 9.5 24 9.5z\"></path> <path fill=\"#4285F4\" d=\"M46.98 24.55c0-1.57-.15-3.09-.38-4.55H24v9.02h12.94c-.58 2.96-2.26 5.48-4.78 7.18l7.73 6c4.51-4.18 7.09-10.36 7.09-17.65z\"></path> <path fill=\"#FBBC05\" d=\"M10.53 28.59c-.48-1.45-.76-2.99-.76-4.59s.27-3.14.76-4.59l-7.98-6.19C.92 16.46 0 20.12 0 24c0 3.88.92 7.54 2.56 10.78l7.97-6.19z\"></path> <path fill=\"#34A853\" d=\"M24 48c6.48 0 11.93-2.13 15.89-5.81l-7.73-6c-2.15 1.45-4.92 2.3-8.16 2.3-6.26 0-11.57-4.22-13.47-9.91l-7.98 6.19C6.51 42.62 14.62 48 24 48z\"></path> <path fill=\"none\" d=\"M0 0h48v48H0z\"></path></svg></div><span class=\"gsi-material-button-contents\">Continue with Google</span> <span style=\"display: none;\">Continue with Google</span></div></button></section></main>")
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<main class=\"container\"><section><h1>Welcome</h1><form hx-post=\"/\"><input type=\"email\" name=\"email\" placeholder=\"john@email.com\" aria-label=\"email\" autocomplete=\"email\" autofocus required=\"\"> <input type=\"password\" name=\"password\" placeholder=\"p4Ssw0rD\" aria-label=\"password\" autocomplete=\"password\" required=\"\"> <button type=\"submit\">Log in</button></form></section></main>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
|
|
@ -44,7 +42,7 @@ func Login(providers *auth.ProviderIndex) templ.Component {
|
||||||
}
|
}
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
})
|
})
|
||||||
templ_7745c5c3_Err = layout.PublicLayout().Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
|
templ_7745c5c3_Err = layout.BaseLayout().Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
2
main.go
2
main.go
|
|
@ -62,7 +62,7 @@ func run(ctx context.Context, getEnv func(string) string) error {
|
||||||
return fmt.Errorf("error starting pdf service %s", err)
|
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"))
|
as := auth.NewService(os.Getenv("SESSION_SECRET"), getEnv("ADMIN"), getEnv("ADMIN_SECRET"))
|
||||||
|
|
||||||
p := getEnv("PORT")
|
p := getEnv("PORT")
|
||||||
port, err := strconv.Atoi(p)
|
port, err := strconv.Atoi(p)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue