initial commit. Cloned timetracker repository
This commit is contained in:
118
app/delivery/middleware/auth.go
Normal file
118
app/delivery/middleware/auth.go
Normal file
@@ -0,0 +1,118 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"git.ma-al.com/goc_marek/timetracker/app/config"
|
||||
"git.ma-al.com/goc_marek/timetracker/app/model"
|
||||
"git.ma-al.com/goc_marek/timetracker/app/service/authService"
|
||||
|
||||
"github.com/gofiber/fiber/v3"
|
||||
)
|
||||
|
||||
// AuthMiddleware creates authentication middleware
|
||||
func AuthMiddleware() fiber.Handler {
|
||||
authService := authService.NewAuthService()
|
||||
|
||||
return func(c fiber.Ctx) error {
|
||||
// Get token from Authorization header
|
||||
authHeader := c.Get("Authorization")
|
||||
if authHeader == "" {
|
||||
// Try to get from cookie
|
||||
authHeader = c.Cookies("access_token")
|
||||
if authHeader == "" {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
|
||||
"error": "authorization token required",
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// Extract token from "Bearer <token>"
|
||||
parts := strings.Split(authHeader, " ")
|
||||
if len(parts) != 2 || parts[0] != "Bearer" {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
|
||||
"error": "invalid authorization header format",
|
||||
})
|
||||
}
|
||||
authHeader = parts[1]
|
||||
}
|
||||
|
||||
// Validate token
|
||||
claims, err := authService.ValidateToken(authHeader)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
|
||||
"error": "invalid or expired token",
|
||||
})
|
||||
}
|
||||
|
||||
// Get user from database
|
||||
user, err := authService.GetUserByID(claims.UserID)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
|
||||
"error": "user not found",
|
||||
})
|
||||
}
|
||||
|
||||
// Check if user is active
|
||||
if !user.IsActive {
|
||||
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{
|
||||
"error": "user account is inactive",
|
||||
})
|
||||
}
|
||||
|
||||
// Set user in context
|
||||
c.Locals("user", user.ToSession())
|
||||
c.Locals("userID", user.ID)
|
||||
|
||||
return c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// RequireAdmin creates admin-only middleware
|
||||
func RequireAdmin() fiber.Handler {
|
||||
return func(c fiber.Ctx) error {
|
||||
user := c.Locals("user")
|
||||
if user == nil {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
|
||||
"error": "not authenticated",
|
||||
})
|
||||
}
|
||||
|
||||
userSession, ok := user.(*model.UserSession)
|
||||
if !ok {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||
"error": "invalid user session",
|
||||
})
|
||||
}
|
||||
|
||||
if userSession.Role != model.RoleAdmin {
|
||||
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{
|
||||
"error": "admin access required",
|
||||
})
|
||||
}
|
||||
|
||||
return c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// GetUserID extracts user ID from context
|
||||
func GetUserID(c fiber.Ctx) uint {
|
||||
userID, ok := c.Locals("userID").(uint)
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
return userID
|
||||
}
|
||||
|
||||
// GetUser extracts user from context
|
||||
func GetUser(c fiber.Ctx) *model.UserSession {
|
||||
user, ok := c.Locals("user").(*model.UserSession)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return user
|
||||
}
|
||||
|
||||
// GetConfig returns the app config
|
||||
func GetConfig() *config.Config {
|
||||
return config.Get()
|
||||
}
|
||||
18
app/delivery/middleware/cors.go
Normal file
18
app/delivery/middleware/cors.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package middleware
|
||||
|
||||
import "github.com/gofiber/fiber/v3"
|
||||
|
||||
// CORSMiddleware creates CORS middleware
|
||||
func CORSMiddleware() fiber.Handler {
|
||||
return func(c fiber.Ctx) error {
|
||||
c.Set("Access-Control-Allow-Origin", "*")
|
||||
c.Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
|
||||
c.Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
|
||||
|
||||
if c.Method() == "OPTIONS" {
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
}
|
||||
|
||||
return c.Next()
|
||||
}
|
||||
}
|
||||
114
app/delivery/middleware/language.go
Normal file
114
app/delivery/middleware/language.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"git.ma-al.com/goc_marek/timetracker/app/service/langs"
|
||||
"github.com/gofiber/fiber/v3"
|
||||
)
|
||||
|
||||
// LanguageMiddleware discovers client's language and stores it in context
|
||||
// Priority: Query param > Cookie > Accept-Language header > Default language
|
||||
func LanguageMiddleware() fiber.Handler {
|
||||
langService := langs.LangSrv
|
||||
|
||||
return func(c fiber.Ctx) error {
|
||||
var langID uint
|
||||
|
||||
// 1. Check query parameter
|
||||
langIDStr := c.Query("lang_id", "")
|
||||
if langIDStr != "" {
|
||||
if id, err := strconv.ParseUint(langIDStr, 10, 32); err == nil {
|
||||
langID = uint(id)
|
||||
if langID > 0 {
|
||||
lang, err := langService.GetLanguageById(langID)
|
||||
if err == nil {
|
||||
c.Locals("langID", langID)
|
||||
c.Locals("lang", lang)
|
||||
return c.Next()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Check cookie
|
||||
cookieLang := c.Cookies("lang_id", "")
|
||||
if cookieLang != "" {
|
||||
if id, err := strconv.ParseUint(cookieLang, 10, 32); err == nil {
|
||||
langID = uint(id)
|
||||
if langID > 0 {
|
||||
lang, err := langService.GetLanguageById(langID)
|
||||
if err == nil {
|
||||
c.Locals("langID", langID)
|
||||
c.Locals("lang", lang)
|
||||
return c.Next()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Check Accept-Language header
|
||||
acceptLang := c.Get("Accept-Language", "")
|
||||
if acceptLang != "" {
|
||||
// Parse the Accept-Language header (e.g., "en-US,en;q=0.9,pl;q=0.8")
|
||||
isoCode := parseAcceptLanguage(acceptLang)
|
||||
if isoCode != "" {
|
||||
lang, err := langService.GetLanguageByISOCode(isoCode)
|
||||
if err == nil && lang != nil {
|
||||
langID = uint(lang.ID)
|
||||
c.Locals("langID", langID)
|
||||
c.Locals("lang", lang)
|
||||
return c.Next()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Fall back to default language
|
||||
defaultLang, err := langService.GetDefaultLanguage()
|
||||
if err == nil && defaultLang != nil {
|
||||
langID = uint(defaultLang.ID)
|
||||
c.Locals("langID", langID)
|
||||
c.Locals("lang", defaultLang)
|
||||
}
|
||||
|
||||
return c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// parseAcceptLanguage extracts the primary language ISO code from Accept-Language header
|
||||
func parseAcceptLanguage(header string) string {
|
||||
// Split by comma
|
||||
parts := strings.Split(header, ",")
|
||||
if len(parts) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Get the first part (highest priority)
|
||||
first := strings.TrimSpace(parts[0])
|
||||
if first == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Remove any quality value (e.g., ";q=0.9")
|
||||
if idx := strings.Index(first, ";"); idx != -1 {
|
||||
first = strings.TrimSpace(first[:idx])
|
||||
}
|
||||
|
||||
// Handle cases like "en-US" or "en"
|
||||
// Return the primary language code (first part before dash)
|
||||
if idx := strings.Index(first, "-"); idx != -1 {
|
||||
return strings.ToLower(first[:idx])
|
||||
}
|
||||
|
||||
return strings.ToLower(first)
|
||||
}
|
||||
|
||||
// GetLanguageID extracts language ID from context
|
||||
func GetLanguageID(c fiber.Ctx) uint {
|
||||
langID, ok := c.Locals("langID").(uint)
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
return langID
|
||||
}
|
||||
Reference in New Issue
Block a user