turash/bugulma/backend/internal/service/jwt_service.go
Damir Mukimov 000eab4740
Major repository reorganization and missing backend endpoints implementation
Repository Structure:
- Move files from cluttered root directory into organized structure
- Create archive/ for archived data and scraper results
- Create bugulma/ for the complete application (frontend + backend)
- Create data/ for sample datasets and reference materials
- Create docs/ for comprehensive documentation structure
- Create scripts/ for utility scripts and API tools

Backend Implementation:
- Implement 3 missing backend endpoints identified in gap analysis:
  * GET /api/v1/organizations/{id}/matching/direct - Direct symbiosis matches
  * GET /api/v1/users/me/organizations - User organizations
  * POST /api/v1/proposals/{id}/status - Update proposal status
- Add complete proposal domain model, repository, and service layers
- Create database migration for proposals table
- Fix CLI server command registration issue

API Documentation:
- Add comprehensive proposals.md API documentation
- Update README.md with Users and Proposals API sections
- Document all request/response formats, error codes, and business rules

Code Quality:
- Follow existing Go backend architecture patterns
- Add proper error handling and validation
- Match frontend expected response schemas
- Maintain clean separation of concerns (handler -> service -> repository)
2025-11-25 06:01:16 +01:00

113 lines
3.0 KiB
Go

package service
import (
"bugulma/backend/internal/domain"
"fmt"
"time"
"github.com/golang-jwt/jwt/v5"
)
// JWTClaims represents the JWT claims structure
type JWTClaims struct {
UserID string `json:"user_id"`
Email string `json:"email"`
Role string `json:"role"`
OrgID string `json:"org_id,omitempty"`
jwt.RegisteredClaims
}
// JWTService handles JWT token operations
type JWTService struct {
secretKey []byte
}
// NewJWTService creates a new JWT service
func NewJWTService(secretKey string) *JWTService {
return &JWTService{
secretKey: []byte(secretKey),
}
}
// GenerateToken generates a JWT token for a user
func (j *JWTService) GenerateToken(user *domain.User, orgID string) (string, error) {
claims := JWTClaims{
UserID: user.ID,
Email: user.Email,
Role: string(user.Role),
OrgID: orgID,
RegisteredClaims: jwt.RegisteredClaims{
Issuer: "bugulma-backend",
Subject: user.ID,
ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)), // 24 hours
IssuedAt: jwt.NewNumericDate(time.Now()),
NotBefore: jwt.NewNumericDate(time.Now()),
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(j.secretKey)
}
// ValidateToken validates and parses a JWT token
func (j *JWTService) ValidateToken(tokenString string) (*JWTClaims, error) {
token, err := jwt.ParseWithClaims(tokenString, &JWTClaims{}, func(token *jwt.Token) (interface{}, error) {
// Validate the signing method
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
return j.secretKey, nil
})
if err != nil {
return nil, fmt.Errorf("failed to parse token: %w", err)
}
if !token.Valid {
return nil, fmt.Errorf("invalid token")
}
claims, ok := token.Claims.(*JWTClaims)
if !ok {
return nil, fmt.Errorf("invalid token claims")
}
// Check if token is expired
if claims.ExpiresAt != nil && claims.ExpiresAt.Before(time.Now()) {
return nil, fmt.Errorf("token expired")
}
return claims, nil
}
// ExtractClaims extracts claims from a validated token
func (j *JWTService) ExtractClaims(tokenString string) (*JWTClaims, error) {
return j.ValidateToken(tokenString)
}
// RefreshToken generates a new token with updated expiration
func (j *JWTService) RefreshToken(tokenString string) (string, error) {
claims, err := j.ValidateToken(tokenString)
if err != nil {
return "", fmt.Errorf("cannot refresh invalid token: %w", err)
}
// Create new claims with updated expiration
newClaims := JWTClaims{
UserID: claims.UserID,
Email: claims.Email,
Role: claims.Role,
OrgID: claims.OrgID,
RegisteredClaims: jwt.RegisteredClaims{
Issuer: claims.Issuer,
Subject: claims.Subject,
ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)),
IssuedAt: jwt.NewNumericDate(time.Now()),
NotBefore: jwt.NewNumericDate(time.Now()),
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, newClaims)
return token.SignedString(j.secretKey)
}