Boost Azure Demo
This commit is contained in:
193
Backend/internal/middleware/auth.go
Normal file
193
Backend/internal/middleware/auth.go
Normal file
@@ -0,0 +1,193 @@
|
||||
// Path: Backend/internal/middleware/auth.go
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"boostai-backend/internal/config"
|
||||
"boostai-backend/internal/sqlc"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
)
|
||||
|
||||
const DefaultTokenTTL = 7 * 24 * time.Hour
|
||||
|
||||
type AuthClaims struct {
|
||||
UserID int64 `json:"user_id"`
|
||||
Role sqlc.UserRole `json:"role"`
|
||||
Email string `json:"email"`
|
||||
jwt.RegisteredClaims
|
||||
}
|
||||
|
||||
type AuthMiddleware struct {
|
||||
cfg *config.Config
|
||||
}
|
||||
|
||||
func NewAuthMiddleware(cfg *config.Config) *AuthMiddleware {
|
||||
return &AuthMiddleware{cfg: cfg}
|
||||
}
|
||||
|
||||
func (m *AuthMiddleware) CreateToken(userID int64, role sqlc.UserRole, email string, ttl time.Duration) (string, error) {
|
||||
if ttl <= 0 {
|
||||
ttl = DefaultTokenTTL
|
||||
}
|
||||
|
||||
now := time.Now().UTC()
|
||||
claims := AuthClaims{
|
||||
UserID: userID,
|
||||
Role: role,
|
||||
Email: email,
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
Subject: email,
|
||||
IssuedAt: jwt.NewNumericDate(now),
|
||||
ExpiresAt: jwt.NewNumericDate(now.Add(ttl)),
|
||||
},
|
||||
}
|
||||
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
return token.SignedString([]byte(m.cfg.JWTSecret))
|
||||
}
|
||||
|
||||
func (m *AuthMiddleware) RequireAuth() fiber.Handler {
|
||||
return func(c *fiber.Ctx) error {
|
||||
claims, err := m.parseClaims(c)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
|
||||
"error": "unauthorized",
|
||||
"message": "Authentication required",
|
||||
})
|
||||
}
|
||||
|
||||
c.Locals("auth.user_id", claims.UserID)
|
||||
c.Locals("auth.role", claims.Role)
|
||||
c.Locals("auth.email", claims.Email)
|
||||
return c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func (m *AuthMiddleware) RequireTeacher() fiber.Handler {
|
||||
return func(c *fiber.Ctx) error {
|
||||
if CurrentUserRole(c) != sqlc.UserRoleTeacher {
|
||||
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{
|
||||
"error": "forbidden",
|
||||
"message": "Teacher access required",
|
||||
})
|
||||
}
|
||||
|
||||
return c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func (m *AuthMiddleware) RequireTeacherSelf(param string) fiber.Handler {
|
||||
return func(c *fiber.Ctx) error {
|
||||
if CurrentUserRole(c) != sqlc.UserRoleTeacher {
|
||||
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{
|
||||
"error": "forbidden",
|
||||
"message": "Teacher access required",
|
||||
})
|
||||
}
|
||||
|
||||
paramID, err := parsePositiveParam(c, param)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||
"error": "invalid_request",
|
||||
"message": "Invalid path parameter: " + param,
|
||||
})
|
||||
}
|
||||
|
||||
if CurrentUserID(c) != paramID {
|
||||
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{
|
||||
"error": "forbidden",
|
||||
"message": "You can only access your own teacher resources",
|
||||
})
|
||||
}
|
||||
|
||||
return c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func (m *AuthMiddleware) RequireStudentSelfOrTeacher(param string) fiber.Handler {
|
||||
return func(c *fiber.Ctx) error {
|
||||
paramID, err := parsePositiveParam(c, param)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||
"error": "invalid_request",
|
||||
"message": "Invalid path parameter: " + param,
|
||||
})
|
||||
}
|
||||
|
||||
if CurrentUserRole(c) == sqlc.UserRoleTeacher || CurrentUserID(c) == paramID {
|
||||
return c.Next()
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{
|
||||
"error": "forbidden",
|
||||
"message": "You can only access your own student resources",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func CurrentUserID(c *fiber.Ctx) int64 {
|
||||
value, ok := c.Locals("auth.user_id").(int64)
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
func CurrentUserRole(c *fiber.Ctx) sqlc.UserRole {
|
||||
value, ok := c.Locals("auth.role").(sqlc.UserRole)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
func (m *AuthMiddleware) parseClaims(c *fiber.Ctx) (*AuthClaims, error) {
|
||||
tokenValue := strings.TrimSpace(c.Cookies(m.cfg.SessionCookie))
|
||||
if tokenValue == "" {
|
||||
authorization := strings.TrimSpace(c.Get("Authorization"))
|
||||
if strings.HasPrefix(strings.ToLower(authorization), "bearer ") {
|
||||
tokenValue = strings.TrimSpace(authorization[7:])
|
||||
}
|
||||
}
|
||||
|
||||
if tokenValue == "" {
|
||||
return nil, errors.New("missing token")
|
||||
}
|
||||
|
||||
parsed, err := jwt.ParseWithClaims(tokenValue, &AuthClaims{}, func(token *jwt.Token) (any, error) {
|
||||
if token.Method != jwt.SigningMethodHS256 {
|
||||
return nil, errors.New("unexpected signing method")
|
||||
}
|
||||
|
||||
return []byte(m.cfg.JWTSecret), nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
claims, ok := parsed.Claims.(*AuthClaims)
|
||||
if !ok || !parsed.Valid {
|
||||
return nil, errors.New("invalid token")
|
||||
}
|
||||
|
||||
return claims, nil
|
||||
}
|
||||
|
||||
func parsePositiveParam(c *fiber.Ctx, param string) (int64, error) {
|
||||
value := strings.TrimSpace(c.Params(param))
|
||||
parsed, err := strconv.ParseInt(value, 10, 64)
|
||||
if err != nil || parsed <= 0 {
|
||||
return 0, errors.New("invalid param")
|
||||
}
|
||||
|
||||
return parsed, nil
|
||||
}
|
||||
Reference in New Issue
Block a user