Compare commits
	
		
			6 commits
		
	
	
		
			db2bedd1d4
			...
			daa90869cf
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
							 | 
						daa90869cf | ||
| 
							 | 
						afd12684b6 | ||
| 
							 | 
						7620a938f5 | ||
| 
							 | 
						56c8d3a786 | ||
| 
							 | 
						a32b2a4210 | ||
| 
							 | 
						18b6f74ab5 | 
					 5 changed files with 55 additions and 13 deletions
				
			
		| 
						 | 
					@ -10,3 +10,10 @@ tmp/
 | 
				
			||||||
*.log
 | 
					*.log
 | 
				
			||||||
coverage.out
 | 
					coverage.out
 | 
				
			||||||
rideaware-api
 | 
					rideaware-api
 | 
				
			||||||
 | 
					__pycache__/
 | 
				
			||||||
 | 
					*.py[cod]
 | 
				
			||||||
 | 
					.venv/
 | 
				
			||||||
 | 
					venv/
 | 
				
			||||||
 | 
					dist/
 | 
				
			||||||
 | 
					build/
 | 
				
			||||||
 | 
					node_modules/
 | 
				
			||||||
| 
						 | 
					@ -51,8 +51,10 @@ PG_USER=your_postgres_user
 | 
				
			||||||
PG_PASSWORD=your_postgres_password
 | 
					PG_PASSWORD=your_postgres_password
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Application Configuration
 | 
					# Application Configuration
 | 
				
			||||||
 | 
					# SECRET_KEY should be a random 32+ byte string; rotate on compromise.
 | 
				
			||||||
SECRET_KEY=your_secret_key_for_sessions
 | 
					SECRET_KEY=your_secret_key_for_sessions
 | 
				
			||||||
PORT=8080
 | 
					PORT=8080
 | 
				
			||||||
 | 
					PG_SSLMODE=require  # use "disable" only for local dev
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Email Configuration (Optional)
 | 
					# Email Configuration (Optional)
 | 
				
			||||||
SMTP_SERVER=your_smtp_server
 | 
					SMTP_SERVER=your_smtp_server
 | 
				
			||||||
| 
						 | 
					@ -69,7 +71,7 @@ SMTP_PASSWORD=your_email_password
 | 
				
			||||||
go run main.go
 | 
					go run main.go
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
The application will be available at http://localhost:8080.
 | 
					The application will be available at [http://localhost:8080](http://localhost:8080).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#### Production Mode
 | 
					#### Production Mode
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -100,7 +102,7 @@ docker build -t rideaware-api .
 | 
				
			||||||
docker run -d -p 8080:8080 --env-file .env rideaware-api
 | 
					docker run -d -p 8080:8080 --env-file .env rideaware-api
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
The application will be available at http://localhost:8080.
 | 
					The application will be available at [http://localhost:8080](http://localhost:8080).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Example Dockerfile
 | 
					### Example Dockerfile
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,6 +4,7 @@ import (
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"log"
 | 
						"log"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"gorm.io/driver/postgres"
 | 
						"gorm.io/driver/postgres"
 | 
				
			||||||
	"gorm.io/gorm"
 | 
						"gorm.io/gorm"
 | 
				
			||||||
| 
						 | 
					@ -16,7 +17,6 @@ func InitDB() *gorm.DB {
 | 
				
			||||||
	user := os.Getenv("PG_USER")
 | 
						user := os.Getenv("PG_USER")
 | 
				
			||||||
	password := os.Getenv("PG_PASSWORD")
 | 
						password := os.Getenv("PG_PASSWORD")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Try with quoted password
 | 
					 | 
				
			||||||
	dsn := fmt.Sprintf(`host=%s port=%s user=%s password='%s' dbname=%s sslmode=disable`,
 | 
						dsn := fmt.Sprintf(`host=%s port=%s user=%s password='%s' dbname=%s sslmode=disable`,
 | 
				
			||||||
		host, port, user, password, database)
 | 
							host, port, user, password, database)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -24,5 +24,19 @@ func InitDB() *gorm.DB {
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		log.Fatal("Failed to connect to database:", err)
 | 
							log.Fatal("Failed to connect to database:", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						sqlDB, err := db.DB()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Fatal("Failed to get sql.DB from gorm:", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						sqlDB.SetMaxOpenConns(25)
 | 
				
			||||||
 | 
						sqlDB.SetMaxIdleConns(25)
 | 
				
			||||||
 | 
						sqlDB.SetConnMaxLifetime(30 * time.Minute)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Fatal("Database ping failed:", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return db
 | 
						return db
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										19
									
								
								main.go
									
										
									
									
									
								
							
							
						
						
									
										19
									
								
								main.go
									
										
									
									
									
								
							| 
						 | 
					@ -2,6 +2,7 @@ package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"log"
 | 
						"log"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/gin-contrib/cors"
 | 
						"github.com/gin-contrib/cors"
 | 
				
			||||||
| 
						 | 
					@ -36,8 +37,22 @@ func main() {
 | 
				
			||||||
	r.Use(cors.Default())
 | 
						r.Use(cors.Default())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Session middleware
 | 
						// Session middleware
 | 
				
			||||||
	store := cookie.NewStore([]byte(os.Getenv("SECRET_KEY")))
 | 
						secret := os.Getenv("SECRET_KEY")
 | 
				
			||||||
	r.Use(sessions.Sessions("session", store))
 | 
						if len(secret) < 32 {
 | 
				
			||||||
 | 
							log.Fatal("SECRET_KEY must be at least 32 bytes")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						authKey := []byte(secret)
 | 
				
			||||||
 | 
						encKey := []byte(secret[:32])
 | 
				
			||||||
 | 
						store := cookie.NewStore(authKey, encKey)
 | 
				
			||||||
 | 
						store.Options(sessions.Options{
 | 
				
			||||||
 | 
							Path:     "/",
 | 
				
			||||||
 | 
							MaxAge:   60 * 80 * 24 * 7, // 7 days
 | 
				
			||||||
 | 
							HttpOnly: true,
 | 
				
			||||||
 | 
							Secure:   os.Getenv("ENV") == "production",
 | 
				
			||||||
 | 
							SameSite: http.SameSiteLaxMode,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						r.Use(sessions.Sessions("rideaware-session", store))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Health check endpoint
 | 
						// Health check endpoint
 | 
				
			||||||
	r.GET("/health", func(c *gin.Context) {
 | 
						r.GET("/health", func(c *gin.Context) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,6 +3,7 @@ package services
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"errors"
 | 
						"errors"
 | 
				
			||||||
	"log"
 | 
						"log"
 | 
				
			||||||
 | 
						"net/mail"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/rideaware/rideaware-api/models"
 | 
						"github.com/rideaware/rideaware-api/models"
 | 
				
			||||||
| 
						 | 
					@ -27,7 +28,7 @@ func (s *UserService) CreateUser(username, email, password string) (*models.User
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Basic email validation
 | 
						// Basic email validation
 | 
				
			||||||
	if !strings.Contains(email, "@") {
 | 
						if _, err := mail.ParseAddress(email); err != nil {
 | 
				
			||||||
		return nil, errors.New("invalid email format")
 | 
							return nil, errors.New("invalid email format")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -57,17 +58,20 @@ func (s *UserService) CreateUser(username, email, password string) (*models.User
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s *UserService) VerifyUser(username, password string) (*models.User, error) {
 | 
					func (s *UserService) VerifyUser(username, password string) (*models.User, error) {
 | 
				
			||||||
	var user models.User
 | 
						var user models.User
 | 
				
			||||||
	// Allow login with either username or email
 | 
						identifier := strings.TrimSpace(username)
 | 
				
			||||||
	if err := s.db.Where("username = ? OR email = ?", username, username).First(&user).Error; err != nil {
 | 
						if err := s.db.Where("username = ? OR email = ?", identifier, strings.ToLower(identifier)).First(&user).Error; err != nil {
 | 
				
			||||||
		log.Printf("User not found: %s", username)
 | 
							if errors.Is(err, gorm.ErrRecordNotFound) {
 | 
				
			||||||
 | 
								return nil, errors.New("invalid username or password")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							log.Printf("DB error during VerifyUser: %v", err)
 | 
				
			||||||
		return nil, errors.New("invalid username or password")
 | 
							return nil, errors.New("invalid username or password")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if !user.CheckPassword(password) {
 | 
						if !user.CheckPassword(password) {
 | 
				
			||||||
		log.Printf("Invalid password for user: %s", username)
 | 
							log.Printf("Invalid credentials")
 | 
				
			||||||
		return nil, errors.New("invalid username or password")
 | 
							return nil, errors.New("invalid username or password")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	log.Printf("User verified: %s", username)
 | 
						log.Printf("User login succeeded")
 | 
				
			||||||
	return &user, nil
 | 
						return &user, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue