diff --git a/auth/auth.go b/auth/auth.go index a99abdb..1a6ae57 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -5,7 +5,7 @@ import ( "github.com/markbates/goth/gothic" "log" "net/http" - "sponsorahacker/config" + db "sponsorahacker/db" ) func Login(c *gin.Context) { @@ -17,11 +17,14 @@ func Login(c *gin.Context) { } func Callback(c *gin.Context) { - sessionStore, err := NewSessionManager(config.GetEnvVar("DATABASE_URL")) + dbclient, err := db.NewDbClient() + if err != nil { - panic(err) + log.Println(err) + return } + sessionStore := NewSessionManager(dbclient) user, err := gothic.CompleteUserAuth(c.Writer, c.Request) if err != nil { @@ -30,10 +33,10 @@ func Callback(c *gin.Context) { return } - c.SetCookie("user_id", user.UserID, 3600, "/", "localhost", false, true) - err = sessionStore.SetSession(user.Name, c) + err = sessionStore.CreateSession(user, c) + if err != nil { - log.Println("failed to set session:", err) + log.Println("failed to create session in db:", err) } // For now, redirect to profile page after successful login @@ -41,14 +44,24 @@ func Callback(c *gin.Context) { } func Logout(c *gin.Context) { - sessionStore, err := NewSessionManager(config.GetEnvVar("DATABASE_URL")) + dbClient, err := db.NewDbClient() + + if err != nil { + log.Fatal(err) + return + } + + sessionStore := NewSessionManager(dbClient) if err != nil { - panic(err) + log.Fatal(err) + return } - c.SetCookie("user_id", "", -1, "/", "localhost", false, true) + err = sessionStore.DeleteSession(c) + if err != nil { - log.Println("failed to delete session:", err) + log.Fatal("failed to delete session:", err) } + c.Redirect(http.StatusTemporaryRedirect, "/") } diff --git a/auth/sessions.go b/auth/sessions.go index 9469ab3..1bf3c6e 100644 --- a/auth/sessions.go +++ b/auth/sessions.go @@ -3,105 +3,153 @@ package auth import ( "encoding/json" "fmt" + "github.com/gin-contrib/sessions" "github.com/gin-gonic/gin" - "github.com/gorilla/sessions" + "github.com/gorilla/securecookie" + "github.com/markbates/goth" + "net/url" "sponsorahacker/config" "sponsorahacker/db" + "strconv" + "time" ) -type SessionManager interface { - SetSession(username string, c *gin.Context) error - GetSession(c *gin.Context) (string, error) +type SessionStore interface { + CreateSession(*gin.Context) error + GetSession(string) (Session, error) + DeleteSession(string) error } -type SessionStore struct { - SessionDB db.Database - Store *sessions.CookieStore +type SessionManager struct { + DB *db.DBClient } -func NewSessionManager(dbUrl string) (*SessionStore, error) { - db, err := db.NewDbClient(dbUrl) - if err != nil { - return nil, err - } - - // Create sessions table if not exist - _, err = db.Exec(` - CREATE TABLE IF NOT EXISTS sessions ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - session_id TEXT NOT NULL UNIQUE, - data BLOB NOT NULL, - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP - ); - `) +type Session struct { + sessionId string + sessionData string + createdOn string + modifiedOn string + expiresOn string +} - if err != nil { - return nil, err - } +var secureCookie *securecookie.SecureCookie - sessionSecret := config.GetEnvVar("SESSION_SECRET") +func NewSessionManager(db *db.DBClient) SessionManager { - store := sessions.NewCookieStore([]byte(sessionSecret)) - return &SessionStore{db, store}, nil + return SessionManager{DB: db} } -func (s *SessionStore) SetSession(username string, c *gin.Context) error { - session, err := s.Store.Get(c.Request, "session") +func (s *SessionManager) CreateSession(user goth.User, c *gin.Context) error { + // create a new row that will store the user data + sessionData, err := json.Marshal(user) if err != nil { - return err + fmt.Printf("error marshalling user: %v", err) + } + + auth := Session{ + sessionData: string(sessionData), + createdOn: time.Now().Format("20060102150405"), + modifiedOn: time.Now().Format("20060102150405"), + expiresOn: user.ExpiresAt.Format("20060102150405"), } - session.Values["username"] = username - if err := session.Save(c.Request, c.Writer); err != nil { + // todo: insert the query once you figure the rest out + result, err := s.DB.Exec(` + INSERT INTO sessions (sessionData, createdOn, modifiedOn, expiresOn) + VALUES (?, ?, ?, ?);`, auth.sessionData, auth.createdOn, auth.modifiedOn, auth.expiresOn) + + if result == nil { + fmt.Printf("error getting result from database while creating the session: %v", err) return err } - return s.saveSessionToDB(session) -} + sessionId, err := result.LastInsertId() -func (s *SessionStore) GetSession(c *gin.Context) (string, error) { - session, err := s.Store.Get(c.Request, "session") if err != nil { - return "", err + fmt.Printf("error getting session id from database while creating the session: %v", err) } - username, ok := session.Values["username"].(string) - if !ok { - return "", fmt.Errorf("username not found in session") + hash := []byte(config.GetEnvVar("COOKIE_HASH")) + block := []byte(config.GetEnvVar("COOKIE_BLOCK")) + secureCookie = securecookie.New(hash, block) + cookieValue := map[string]string{ + "sessionId": strconv.Itoa(int(sessionId)), } - return username, nil -} + encoded, err := secureCookie.Encode("_session", cookieValue) -func (s *SessionStore) DeleteSession(c *gin.Context) error { - session, err := s.Store.Get(c.Request, "session") if err != nil { + fmt.Printf("error encoding cookie value: %v", err) return err } - session.Values["username"] = make(map[interface{}]interface{}) + c.SetCookie("_session", encoded, 3600, "/", "localhost", false, true) - if err := session.Save(c.Request, c.Writer); err != nil { - return err + return nil +} + +func (s *SessionManager) GetSession(session sessions.Session) (Session, error) { + // query for one row + result, err := s.DB.Query(`SELECT sessionData FROM sessions WHERE sessionId=$1 LIMIT 1`, session.ID()) + + // if err, then return an empty struct + if err != nil { + return Session{}, err } - _, err = s.SessionDB.Exec("DELETE FROM sessions WHERE session_id = ?", session.ID) + // else go through the results and create a Session + for result.Next() { + var s Session + + // unless there is an error, of course, then return an empty struct + if err := result.StructScan(&s); err != nil { + return Session{}, err + } + + return s, nil + } - return err + // if we get nothing, well, we go nothing + return Session{}, nil } -func (s *SessionStore) saveSessionToDB(session *sessions.Session) error { - data, err := json.Marshal(session.Values) - if err != nil { - return err +func (s *SessionManager) DeleteSession(c *gin.Context) error { + if cookie, err := c.Request.Cookie("_session"); err == nil { + value := make(map[string]string) + + cookieValue, _ := url.QueryUnescape(cookie.Value) + + hash := []byte(config.GetEnvVar("COOKIE_HASH")) + block := []byte(config.GetEnvVar("COOKIE_BLOCK")) + secureCookie := securecookie.New(hash, block) + + err = secureCookie.Decode("_session", cookieValue, &value) + + if err != nil { + fmt.Printf("error decoding cookie value: %v", err) + return err + } + + sessionId := value["sessionId"] + sessionIdInt, err := strconv.Atoi(sessionId) + + if err != nil { + fmt.Printf("error converting sessionId to int: %v", err) + return err + } + + _, err = s.DB.Exec(`DELETE FROM sessions WHERE ID = ?`, sessionIdInt) + + if err != nil { + return err + } + + if err != nil { + return err + } } - _, err = s.SessionDB.Exec(` - INSERT INTO sessions (session_id, data, updated_at) - VALUES (?, ?, CURRENT_TIMESTAMP) - ON CONFLICT(session_id) DO UPDATE SET data = ?, updated_at = CURRENT_TIMESTAMP - `, session.ID, data, data) + c.SetCookie("_session", "", -1, "/", "localhost", false, true) - return err + return nil } diff --git a/db/db.go b/db/db.go index 4a41527..25acaec 100644 --- a/db/db.go +++ b/db/db.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/jmoiron/sqlx" _ "github.com/tursodatabase/go-libsql" + "os" ) type Database interface { @@ -17,7 +18,8 @@ type DBClient struct { db *sqlx.DB } -func NewDbClient(dsn string) (Database, error) { +func NewDbClient() (*DBClient, error) { + dsn := os.Getenv("DATABASE_URL") db, err := sqlx.Open("libsql", dsn) if err != nil { return nil, fmt.Errorf("failed to open db: %w", err) diff --git a/go.mod b/go.mod index c3427ea..aae2ea4 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,9 @@ module sponsorahacker go 1.22 require ( + github.com/gin-contrib/sessions v1.0.1 github.com/gin-gonic/gin v1.10.0 - github.com/google/uuid v1.6.0 + github.com/gorilla/securecookie v1.1.2 github.com/jmoiron/sqlx v1.4.0 github.com/joho/godotenv v1.5.1 github.com/markbates/goth v1.80.0 @@ -13,8 +14,6 @@ require ( ) require ( - cloud.google.com/go/compute v1.20.1 // indirect - cloud.google.com/go/compute/metadata v0.2.3 // indirect github.com/antlr4-go/antlr/v4 v4.13.0 // indirect github.com/bytedance/sonic v1.11.6 // indirect github.com/bytedance/sonic/loader v0.1.1 // indirect @@ -28,10 +27,9 @@ require ( github.com/goccy/go-json v0.10.2 // indirect github.com/golang-jwt/jwt v3.2.1+incompatible // indirect github.com/golang/protobuf v1.5.3 // 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/sessions v1.1.1 // indirect + github.com/gorilla/sessions v1.2.2 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/kr/text v0.2.0 // indirect @@ -40,7 +38,6 @@ require ( github.com/mattn/go-isatty v0.0.20 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/mrjones/oauth v0.0.0-20180629183705-f4e24b6d100c // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect diff --git a/go.sum b/go.sum index a831a68..e057be8 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,3 @@ -cloud.google.com/go/compute v1.20.1 h1:6aKEtlUiwEpJzM001l0yFkpXmUVXaN8W+fbkb2AZNbg= -cloud.google.com/go/compute v1.20.1/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= -cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= -cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= @@ -20,6 +16,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +github.com/gin-contrib/sessions v1.0.1 h1:3hsJyNs7v7N8OtelFmYXFrulAf6zSR7nW/putcPEHxI= +github.com/gin-contrib/sessions v1.0.1/go.mod h1:ouxSFM24/OgIud5MJYQJLpy6AwxQ5EYO9yLhbtObGkM= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= @@ -46,18 +44,16 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -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/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +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/pat v0.0.0-20180118222023-199c85a7f6d1 h1:LqbZZ9sNMWVjeXS4NN5oVvhMjDyLhmA1LG86oSo+IqY= -github.com/gorilla/pat v0.0.0-20180118222023-199c85a7f6d1/go.mod h1:YeAe0gNeiNT5hoiZRI4yiOky6jVdNvfO2N6Kav/HmxY= -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/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= +github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= +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/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= @@ -89,8 +85,6 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/mrjones/oauth v0.0.0-20180629183705-f4e24b6d100c h1:3wkDRdxK92dF+c1ke2dtj7ZzemFWBHB9plnJOtlwdFA= -github.com/mrjones/oauth v0.0.0-20180629183705-f4e24b6d100c/go.mod h1:skjdDftzkFALcuGzYSklqYd8gvat6F1gZJ4YPVbkZpM= github.com/nicklaw5/helix v1.25.0 h1:Mrz537izZVsGdM3I46uGAAlslj61frgkhS/9xQqyT/M= github.com/nicklaw5/helix v1.25.0/go.mod h1:yvXZFapT6afIoxnAvlWiJiUMsYnoHl7tNs+t0bloAMw= github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= diff --git a/utils/util.go b/utils/util.go index c61c22b..bfc1195 100644 --- a/utils/util.go +++ b/utils/util.go @@ -3,8 +3,8 @@ package utils import "github.com/gin-gonic/gin" func CheckIfLoggedIn(c *gin.Context) bool { - userID, err := c.Cookie("user_id") - if err != nil || userID == "" { + cookie, err := c.Cookie("_session") + if err != nil || cookie == "" { return false }