From 0466d9d28882ceae1e88a2055f63081c27aab5ef Mon Sep 17 00:00:00 2001 From: Adam French Date: Tue, 25 Nov 2025 16:03:05 +0000 Subject: [PATCH] adding jwt tokens --- backend/go.mod | 3 +- backend/go.sum | 2 + backend/handlers/handle_user.go | 74 +++++++++++++++++++++++++++++++++ backend/handlers/store.go | 2 + backend/main.go | 48 +++++++++++---------- backend/models/user.go | 1 + backend/services/database.go | 4 +- backend/services/spotify.go | 2 +- 8 files changed, 111 insertions(+), 25 deletions(-) diff --git a/backend/go.mod b/backend/go.mod index 6ba638c..fd7d833 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -4,7 +4,9 @@ go 1.24.0 require ( github.com/gin-gonic/gin v1.11.0 + github.com/golang-jwt/jwt/v5 v5.3.0 github.com/zmb3/spotify/v2 v2.4.3 + golang.org/x/crypto v0.43.0 golang.org/x/oauth2 v0.0.0-20210810183815-faf39c7919d5 gorm.io/driver/postgres v1.6.0 gorm.io/gorm v1.31.1 @@ -41,7 +43,6 @@ require ( github.com/ugorji/go/codec v1.3.0 // indirect go.uber.org/mock v0.5.0 // indirect golang.org/x/arch v0.20.0 // indirect - golang.org/x/crypto v0.43.0 // indirect golang.org/x/mod v0.29.0 // indirect golang.org/x/net v0.46.0 // indirect golang.org/x/sync v0.17.0 // indirect diff --git a/backend/go.sum b/backend/go.sum index 36598e1..2a201cb 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -73,6 +73,8 @@ github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= +github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= +github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= diff --git a/backend/handlers/handle_user.go b/backend/handlers/handle_user.go index 5ac8282..1f06271 100644 --- a/backend/handlers/handle_user.go +++ b/backend/handlers/handle_user.go @@ -1 +1,75 @@ package handlers + +import ( + "net/http" + + "adam-french.co.uk/backend/models" + "github.com/gin-gonic/gin" + "golang.org/x/crypto/bcrypt" +) + +type UserCredentials struct { + Username string `json:"username" binding:"required"` + Password string `json:"password" binding:"required"` +} + +func (store *Store) CreateUser(ctx *gin.Context) { + var input UserCredentials + if err := ctx.ShouldBindBodyWithJSON(&input); err != nil { + ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + hashedPassword, err := bcrypt.GenerateFromPassword([]byte(input.Password), bcrypt.DefaultCost) + if err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + user := models.User{Username: input.Username, Password: hashedPassword} + store.DB.Create(&user) + + // Generate JWT token + + ctx.JSON(http.StatusOK, gin.H{"data": user}) +} + +func (store *Store) LoginUser(ctx *gin.Context) { + var input UserCredentials + if err := ctx.ShouldBindBodyWithJSON(&input); err != nil { + ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + user := models.User{Username: input.Username} + if err := store.DB.Where("username = ?", input.Username).First(&user).Error; err != nil { + ctx.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) + return + } + + if err := bcrypt.CompareHashAndPassword(user.Password, []byte(input.Password)); err != nil { + ctx.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()}) + return + } + + // Generate JWT token + + ctx.JSON(http.StatusAccepted, gin.H{"data": user}) +} + +func (store *Store) GetUser(ctx *gin.Context) { + +} + +func (store *Store) GetUsers(ctx *gin.Context) { + var users []models.User + if err := store.DB.Find(&users).Error; err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + ctx.JSON(http.StatusOK, gin.H{"data": models.Post{}}) +} + +func (store *Store) UpdateUser(c *gin.Context) { + +} diff --git a/backend/handlers/store.go b/backend/handlers/store.go index 95a9371..5033741 100644 --- a/backend/handlers/store.go +++ b/backend/handlers/store.go @@ -1,6 +1,7 @@ package handlers import ( + "adam-french.co.uk/backend/services" "github.com/zmb3/spotify/v2" spotifyauth "github.com/zmb3/spotify/v2/auth" "gorm.io/gorm" @@ -10,4 +11,5 @@ type Store struct { DB *gorm.DB SpotifyAuth *spotifyauth.Authenticator SpotifyClient *spotify.Client + Auth *services.Auth } diff --git a/backend/main.go b/backend/main.go index da29f85..afe0891 100644 --- a/backend/main.go +++ b/backend/main.go @@ -12,38 +12,44 @@ import ( ) func main() { - db_user := os.Getenv("POSTGRES_USER") - db_password := os.Getenv("POSTGRES_PASSWORD") - dbname := os.Getenv("POSTGRES_DB") - db_host := os.Getenv("POSTGRES_HOST") - db_port := os.Getenv("POSTGRES_PORT") - db_config := services.SQLConfig{User: db_user, Password: db_password, DBName: dbname, Host: db_host, Port: db_port} - db, err := services.InitDatabase(db_config) + dbUser := os.Getenv("POSTGRES_USER") + dbPassword := os.Getenv("POSTGRES_PASSWORD") + dbName := os.Getenv("POSTGRES_DB") + dbHost := os.Getenv("POSTGRES_HOST") + dbPort := os.Getenv("POSTGRES_PORT") + dbConfig := services.SQLConfig{User: dbUser, Password: dbPassword, DBName: dbName, Host: dbHost, Port: dbPort} + db, err := services.InitDatabase(&dbConfig) if err != nil { log.Fatal(err) } - if err != nil { - log.Fatal(err) - } + spotifyAuthState := os.Getenv("SPOTIFY_AUTH_STATE") + spotifyRedirectURL := os.Getenv("SPOTIFY_REDIRECT_URI") + spotifyClientID := os.Getenv("SPOTIFY_CLIENT_ID") + spotifyClientSecret := os.Getenv("SPOTIFY_CLIENT_SECRET") + spotifyConfig := services.SpotifyConfig{AuthState: spotifyAuthState, RedirectURL: spotifyRedirectURL, ClientID: spotifyClientID, ClientSecret: spotifyClientSecret} + spotifyAuth, client := services.InitSpotifyAuth(&spotifyConfig) + + authSecret := os.Getenv("BACKEND_SECRET") + authConfig := services.AuthConfig{Secret: []byte(authSecret)} + auth := services.InitAuth(&authConfig) + + store := handlers.Store{DB: db, SpotifyAuth: spotifyAuth, SpotifyClient: client, Auth: auth} r := gin.Default() - authState := os.Getenv("SPOTIFY_AUTH_STATE") - redirectURL := os.Getenv("SPOTIFY_REDIRECT_URI") - clientID := os.Getenv("SPOTIFY_CLIENT_ID") - clientSecret := os.Getenv("SPOTIFY_CLIENT_SECRET") - spotifyConfig := services.SpotifyConfig{AuthState: authState, RedirectURL: redirectURL, ClientID: clientID, ClientSecret: clientSecret} - - auth, client := services.InitSpotifyAuth(spotifyConfig) - - store := handlers.Store{DB: db, SpotifyAuth: auth, SpotifyClient: client} - r.GET("/posts", store.GetPosts) r.POST("/posts", store.CreatePost) r.PUT("/posts/:id", store.UpdatePost) - r.GET("/callback", store.CompleteAuth) + r.GET("/user/:id", store.GetUser) + r.GET("/user", store.GetUsers) + r.POST("/user", store.CreateUser) + r.PUT("/user", store.UpdateUser) + + r.POST("/refresh", store.RefreshToken) + + r.GET("/callback", store.CompleteSpotifyAuth) r.GET("/spotify", store.ListeningTo) // r.POST("/spotify", store.SendSong) diff --git a/backend/models/user.go b/backend/models/user.go index d1d7607..fd341a6 100644 --- a/backend/models/user.go +++ b/backend/models/user.go @@ -6,4 +6,5 @@ type User struct { gorm.Model // includes ID, CreatedAt, UpdatedAt, DeletedAt Username string `gorm:"uniqueIndex"` Password []byte + Admin bool } diff --git a/backend/services/database.go b/backend/services/database.go index 0badf9d..a753fa9 100644 --- a/backend/services/database.go +++ b/backend/services/database.go @@ -16,7 +16,7 @@ type SQLConfig struct { Port string } -func connectToPostgreSQL(config SQLConfig) (*gorm.DB, error) { +func connectToPostgreSQL(config *SQLConfig) (*gorm.DB, error) { dsn := fmt.Sprintf( "user=%s password=%s dbname=%s host=%s port=%s sslmode=disable", config.User, config.Password, config.DBName, config.Host, config.Port, @@ -43,7 +43,7 @@ func migrateDatabase(db *gorm.DB) error { return nil } -func InitDatabase(config SQLConfig) (*gorm.DB, error) { +func InitDatabase(config *SQLConfig) (*gorm.DB, error) { db, err := connectToPostgreSQL(config) if err != nil { return nil, err diff --git a/backend/services/spotify.go b/backend/services/spotify.go index bfc732c..a7715dd 100644 --- a/backend/services/spotify.go +++ b/backend/services/spotify.go @@ -70,7 +70,7 @@ func LoadSpotifyToken(path string) (*oauth2.Token, error) { return tok, nil } -func InitSpotifyAuth(config SpotifyConfig) (*spotifyauth.Authenticator, *spotify.Client) { +func InitSpotifyAuth(config *SpotifyConfig) (*spotifyauth.Authenticator, *spotify.Client) { auth := spotifyauth.New( spotifyauth.WithRedirectURL(config.RedirectURL), spotifyauth.WithClientID(config.ClientID),