diff --git a/cmd/server/main.go b/cmd/server/main.go index 8242f8a..004c762 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -60,6 +60,9 @@ func main() { token := c.Get("user").(*jwt.Token) claims := token.Claims.(jwt.MapClaims) c.Set("user_id", claims["user_id"]) + c.Set("user_role", claims["user_role"]) + c.Set("user_email", claims["user_email"]) + c.Set("user_username", claims["user_username"]) }, }) @@ -68,9 +71,12 @@ func main() { protected.GET("/auth/profile", authHandler.GetProfile) protected.PUT("/auth/profile", authHandler.UpdateProfile) protected.GET("/auth/users", authHandler.ListUsers) - protected.POST("/auth/ebook-folders", authHandler.AddEbookFolder) - protected.GET("/auth/ebook-folders", authHandler.GetEbookFolders) - protected.DELETE("/auth/ebook-folders", authHandler.DeleteEbookFolder) + + // Admin-only routes for folder management + admin := protected.Group("/auth", handlers.AdminMiddleware) + admin.POST("/ebook-folders", authHandler.AddEbookFolder) + admin.GET("/ebook-folders", authHandler.GetEbookFolders) + admin.DELETE("/ebook-folders", authHandler.DeleteEbookFolder) protected.DELETE("/auth/account", authHandler.DeleteAccount) protected.PUT("/library/scan-settings", authHandler.UpdateScanSettings) @@ -90,6 +96,28 @@ func main() { // Routes handlers.SetupRoutes(protected, queries) + // Dashboard route (protected) + protected.GET("/dashboard", func(c echo.Context) error { + userID := c.Get("user_id").(string) + userEmail := c.Get("user_email").(string) + userUsername := c.Get("user_username").(string) + userRole := c.Get("user_role").(string) + + user := templates.User{ + ID: userID, + Email: userEmail, + Username: userUsername, + Role: userRole, + } + + var buf bytes.Buffer + err := templates.Dashboard(user).Render(c.Request().Context(), &buf) + if err != nil { + return err + } + return c.HTML(http.StatusOK, buf.String()) + }) + dummyUser := templates.User{ID: "", Username: "Admin", Email: "admin@example.com"} // Routes diff --git a/internal/handlers/auth.go b/internal/handlers/auth.go index 8d6ca24..31572a5 100644 --- a/internal/handlers/auth.go +++ b/internal/handlers/auth.go @@ -53,6 +53,7 @@ type UserProfile struct { Username string `json:"username"` FirstName string `json:"first_name"` LastName string `json:"last_name"` + Role string `json:"role"` } type UpdateProfileRequest struct { @@ -138,8 +139,13 @@ func (h *AuthHandler) Register(c echo.Context) error { return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()}) } - // Generate JWT - token, err := h.generateJWT(uuid.UUID(user.ID.Bytes).String()) + // Generate JWT with user details + token, err := h.generateJWTWithAllClaims( + uuid.UUID(user.ID.Bytes).String(), + user.Role, + user.Email, + user.Username, + ) if err != nil { if c.Request().Header.Get("HX-Request") == "true" { return c.HTML(http.StatusInternalServerError, `
Failed to generate token
`) @@ -155,7 +161,7 @@ func (h *AuthHandler) Register(c echo.Context) error { localStorage.setItem('token', '%s'); localStorage.setItem('user', JSON.stringify(%s)); document.cookie = 'token=%s; path=/; max-age=86400'; -window.location.href = '/'; +window.location.href = '/api/dashboard'; `, token, fmt.Sprintf(`{"id":"%s","email":"%s","username":"%s"}`, uuid.UUID(user.ID.Bytes).String(), user.Email, user.Username), token) return c.HTML(http.StatusCreated, html) } @@ -166,6 +172,7 @@ window.location.href = '/'; ID: uuid.UUID(user.ID.Bytes).String(), Email: user.Email, Username: user.Username, + Role: user.Role, }, }) } @@ -229,8 +236,13 @@ func (h *AuthHandler) Login(c echo.Context) error { return c.JSON(http.StatusUnauthorized, map[string]string{"error": "invalid credentials"}) } - // Generate JWT - token, err := h.generateJWT(uuid.UUID(user.ID.Bytes).String()) + // Generate JWT with user details + token, err := h.generateJWTWithAllClaims( + uuid.UUID(user.ID.Bytes).String(), + user.Role, + user.Email, + user.Username, + ) if err != nil { if c.Request().Header.Get("HX-Request") == "true" { return c.HTML(http.StatusInternalServerError, `
Failed to generate token
`) @@ -246,7 +258,7 @@ func (h *AuthHandler) Login(c echo.Context) error { localStorage.setItem('token', '%s'); localStorage.setItem('user', JSON.stringify(%s)); document.cookie = 'token=%s; path=/; max-age=86400'; -window.location.href = '/'; +window.location.href = '/api/dashboard'; `, token, fmt.Sprintf(`{"id":"%s","email":"%s","username":"%s","first_name":"%s","last_name":"%s"}`, uuid.UUID(user.ID.Bytes).String(), user.Email, user.Username, user.FirstName.String, user.LastName.String), token) return c.HTML(http.StatusOK, html) } @@ -267,6 +279,7 @@ window.location.href = '/'; Username: user.Username, FirstName: firstName, LastName: lastName, + Role: user.Role, }, }) } @@ -301,6 +314,7 @@ func (h *AuthHandler) GetProfile(c echo.Context) error { Username: user.Username, FirstName: firstName, LastName: lastName, + Role: user.Role, }) } @@ -760,12 +774,34 @@ func (h *AuthHandler) GetScanSettings(c echo.Context) error { }) } -func (h *AuthHandler) generateJWT(userID string) (string, error) { +// AdminMiddleware checks if the user has admin role +func AdminMiddleware(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + userRole, exists := c.Get("user_role").(string) + if !exists || userRole != "admin" { + return c.JSON(http.StatusForbidden, map[string]string{"error": "admin access required"}) + } + return next(c) + } +} + +func (h *AuthHandler) generateJWTWithAllClaims(userID, userRole, userEmail, userUsername string) (string, error) { claims := jwt.MapClaims{ - "user_id": userID, - "exp": time.Now().Add(24 * time.Hour).Unix(), - "iat": time.Now().Unix(), + "user_id": userID, + "user_role": userRole, + "user_email": userEmail, + "user_username": userUsername, + "exp": time.Now().Add(24 * time.Hour).Unix(), + "iat": time.Now().Unix(), } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) return token.SignedString(h.jwtKey) } + +func (h *AuthHandler) generateJWTWithRole(userID, userRole string) (string, error) { + return h.generateJWTWithAllClaims(userID, userRole, "", "") +} + +func (h *AuthHandler) generateJWT(userID string) (string, error) { + return h.generateJWTWithRole(userID, "user") +}