Skip to content

Commit 1cb6566

Browse files
committed
Implement API key authentication and management; update Dockerfile and README for persistent storage
1 parent 7073e76 commit 1cb6566

6 files changed

Lines changed: 159 additions & 6 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*.test
88
*.out
99
go.work
10+
.api-key
1011

1112
# Binaries
1213
/beeapi

Dockerfile

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,15 @@ COPY --from=builder /app/beeapi /app/beeapi
2323
# Create puzzles directory
2424
RUN mkdir -p /app/puzzles && chmod -R 777 /app/puzzles
2525

26+
# Create a volume for persistent storage of API key
27+
VOLUME /app/data
28+
2629
# Environment variables
2730
ENV SERVER_NAME="Local"
2831
ENV SERVER_DESCRIPTION="Local Dev Server"
2932
ENV PYTHON_PATH="python"
3033

3134
EXPOSE 5000
3235

33-
CMD ["/app/beeapi"]
36+
# Create symbolic link to store API key in persistent volume
37+
CMD ln -sf /app/data/.api-key /app/.api-key && /app/beeapi

README.md

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,32 @@ docker build -t beeapi-go .
5656
Then, run the Docker container:
5757

5858
```bash
59-
docker run -d -p 8080:8080 --name beeapi-go -v $(pwd)/puzzles:/app/puzzles beeapi-go
59+
docker run -d -p 8080:8080 --name beeapi-go -v $(pwd)/puzzles:/app/puzzles -v $(pwd)/data:/app/data beeapi-go
6060
```
6161

62+
## API Authentication
63+
64+
BeeAPI Go uses API key authentication for protected endpoints (POST, PUT, DELETE). When the server starts for the first time, it generates a unique API key and saves it in a `.api-key` file in the root directory.
65+
66+
To authenticate your requests to protected endpoints:
67+
68+
1. Look for the API key in the `.api-key` file or in the server logs when it starts
69+
2. Include the API key in your requests' Authorization header:
70+
71+
```bash
72+
curl -X POST "http://localhost:5000/theme" \
73+
-H "Authorization: Bearer YOUR_API_KEY" \
74+
-d "name=new-theme"
75+
```
76+
77+
If you're using Docker, you can access the API key by:
78+
79+
```bash
80+
docker exec beeapi-go cat /app/.api-key
81+
```
82+
83+
Make sure to keep this key secure as it provides administrative access to your BeeAPI instance.
84+
6285
## API Documentation
6386

6487
The API documentation is available at `/swagger/index.html` when the server is running.

main.go

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"time"
1111

1212
"github.com/algohive/beeapi/controllers"
13+
"github.com/algohive/beeapi/middlewares"
1314
"github.com/algohive/beeapi/services"
1415
"github.com/gin-contrib/cors"
1516
"github.com/gin-gonic/gin"
@@ -36,6 +37,13 @@ func main() {
3637
// Load environment variables from .env file if it exists
3738
_ = godotenv.Load()
3839

40+
// Initialize API key manager
41+
apiKeyManager, err := services.NewAPIKeyManager(".")
42+
if err != nil {
43+
log.Fatalf("Failed to initialize API key manager: %v", err)
44+
}
45+
log.Printf("API key initialized: %s", apiKeyManager.GetAPIKey())
46+
3947
// Create services
4048
puzzlesLoader := services.NewPuzzlesLoader()
4149
pythonRunner := services.NewPythonRunner(os.Getenv("PYTHON_PATH")) // Get from env or use default
@@ -75,10 +83,10 @@ func main() {
7583
router.GET("/puzzle", puzzleController.GetPuzzle)
7684
router.GET("/puzzle/generate", puzzleController.GeneratePuzzle)
7785

78-
// Protected routes
86+
// Protected routes with API key authentication
7987
protected := router.Group("")
80-
// protected.Use(middleware.RequireAuth(authService))
81-
// {
88+
protected.Use(middlewares.RequireAPIKey(apiKeyManager))
89+
{
8290
// Theme management
8391
protected.POST("/theme", themeController.CreateTheme)
8492
protected.DELETE("/theme", themeController.DeleteTheme)
@@ -87,7 +95,7 @@ func main() {
8795
// Puzzle management
8896
protected.POST("/puzzle/upload", puzzleController.UploadPuzzle)
8997
protected.DELETE("/puzzle", puzzleController.DeletePuzzle)
90-
// }
98+
}
9199

92100

93101
// Make sure puzzles directory exists
@@ -119,6 +127,7 @@ func main() {
119127
// Run server in a goroutine so it doesn't block
120128
go func() {
121129
log.Printf("Server starting on port %s", port)
130+
log.Printf("API Key: %s", apiKeyManager.GetAPIKey())
122131
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
123132
log.Fatalf("Server error: %v", err)
124133
}

middlewares/auth.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package middlewares
2+
3+
import (
4+
"net/http"
5+
"strings"
6+
7+
"github.com/algohive/beeapi/services"
8+
"github.com/gin-gonic/gin"
9+
)
10+
11+
// RequireAPIKey crée un middleware qui valide la clé API
12+
func RequireAPIKey(keyManager *services.APIKeyManager) gin.HandlerFunc {
13+
return func(c *gin.Context) {
14+
// Récupère l'en-tête Authorization
15+
authHeader := c.GetHeader("Authorization")
16+
17+
// Vérifie si l'en-tête Authorization est présent et a le format correct
18+
if authHeader == "" {
19+
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Clé API manquante"})
20+
return
21+
}
22+
23+
// Le format doit être "Bearer <api-key>"
24+
parts := strings.SplitN(authHeader, " ", 2)
25+
if len(parts) != 2 || parts[0] != "Bearer" {
26+
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Format de clé API invalide"})
27+
return
28+
}
29+
30+
key := parts[1]
31+
32+
// Valide la clé API
33+
if !keyManager.ValidateKey(key) {
34+
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Clé API invalide"})
35+
return
36+
}
37+
38+
c.Next()
39+
}
40+
}

services/apikey.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package services
2+
3+
import (
4+
"crypto/rand"
5+
"encoding/base64"
6+
"os"
7+
"path/filepath"
8+
"strings"
9+
)
10+
11+
const (
12+
apiKeyLength = 32 // 32 bytes = 256 bits
13+
apiKeyFile = ".api-key"
14+
)
15+
16+
// APIKeyManager gère la génération et validation des clés API
17+
type APIKeyManager struct {
18+
apiKey string
19+
keyPath string
20+
}
21+
22+
// NewAPIKeyManager crée un nouveau gestionnaire de clé API
23+
func NewAPIKeyManager(basePath string) (*APIKeyManager, error) {
24+
keyPath := filepath.Join(basePath, apiKeyFile)
25+
manager := &APIKeyManager{
26+
keyPath: keyPath,
27+
}
28+
29+
// Essaie de charger une clé existante
30+
err := manager.loadKey()
31+
if err != nil {
32+
// Génère et sauvegarde une nouvelle clé si aucune n'existe
33+
err = manager.generateAndSaveKey()
34+
if err != nil {
35+
return nil, err
36+
}
37+
}
38+
39+
return manager, nil
40+
}
41+
42+
// loadKey charge la clé API depuis le fichier
43+
func (a *APIKeyManager) loadKey() error {
44+
data, err := os.ReadFile(a.keyPath)
45+
if err != nil {
46+
return err
47+
}
48+
a.apiKey = strings.TrimSpace(string(data))
49+
return nil
50+
}
51+
52+
// generateAndSaveKey génère une nouvelle clé API et la sauvegarde dans le fichier
53+
func (a *APIKeyManager) generateAndSaveKey() error {
54+
// Génère des octets aléatoires
55+
randBytes := make([]byte, apiKeyLength)
56+
_, err := rand.Read(randBytes)
57+
if err != nil {
58+
return err
59+
}
60+
61+
// Encode en base64 pour une clé API lisible
62+
a.apiKey = base64.StdEncoding.EncodeToString(randBytes)
63+
64+
// Sauvegarde dans le fichier
65+
return os.WriteFile(a.keyPath, []byte(a.apiKey), 0600)
66+
}
67+
68+
// GetAPIKey retourne la clé API courante
69+
func (a *APIKeyManager) GetAPIKey() string {
70+
return a.apiKey
71+
}
72+
73+
// ValidateKey vérifie si la clé fournie correspond à celle stockée
74+
func (a *APIKeyManager) ValidateKey(key string) bool {
75+
return key == a.apiKey
76+
}

0 commit comments

Comments
 (0)