// Package auth gère l'authentification JWT et les mots de passe. package auth import ( "crypto/hmac" "crypto/sha256" "encoding/base64" "encoding/json" "errors" "fmt" "strings" "time" ) var ( ErrInvalidToken = errors.New("invalid token") ErrExpiredToken = errors.New("token expired") ) // Claims représente les claims du JWT. type Claims struct { Sub int64 `json:"sub"` // User ID Email string `json:"email"` // Email Name string `json:"name"` // Nom App string `json:"app"` // Application ID Exp int64 `json:"exp"` // Expiration (Unix timestamp) Iat int64 `json:"iat"` // Issued at } // JWT gère la génération et validation des tokens. type JWT struct { secret []byte expiration time.Duration } // NewJWT crée un nouveau gestionnaire JWT. func NewJWT(secret string, expiration time.Duration) *JWT { return &JWT{ secret: []byte(secret), expiration: expiration, } } // Generate génère un nouveau token JWT. func (j *JWT) Generate(userID int64, email, name, appID string) (string, error) { now := time.Now() claims := Claims{ Sub: userID, Email: email, Name: name, App: appID, Iat: now.Unix(), Exp: now.Add(j.expiration).Unix(), } return j.encode(claims) } // Validate valide un token et retourne les claims. func (j *JWT) Validate(token string) (*Claims, error) { parts := strings.Split(token, ".") if len(parts) != 3 { return nil, ErrInvalidToken } // Vérifier la signature signatureInput := parts[0] + "." + parts[1] expectedSig := j.sign(signatureInput) if parts[2] != expectedSig { return nil, ErrInvalidToken } // Décoder les claims claimsJSON, err := base64.RawURLEncoding.DecodeString(parts[1]) if err != nil { return nil, ErrInvalidToken } var claims Claims if err := json.Unmarshal(claimsJSON, &claims); err != nil { return nil, ErrInvalidToken } // Vérifier l'expiration if time.Now().Unix() > claims.Exp { return nil, ErrExpiredToken } return &claims, nil } // encode encode les claims en JWT. func (j *JWT) encode(claims Claims) (string, error) { // Header header := map[string]string{ "alg": "HS256", "typ": "JWT", } headerJSON, _ := json.Marshal(header) headerB64 := base64.RawURLEncoding.EncodeToString(headerJSON) // Payload claimsJSON, err := json.Marshal(claims) if err != nil { return "", err } claimsB64 := base64.RawURLEncoding.EncodeToString(claimsJSON) // Signature signatureInput := headerB64 + "." + claimsB64 signature := j.sign(signatureInput) return signatureInput + "." + signature, nil } // sign signe les données avec HMAC-SHA256. func (j *JWT) sign(data string) string { h := hmac.New(sha256.New, j.secret) h.Write([]byte(data)) return base64.RawURLEncoding.EncodeToString(h.Sum(nil)) } // ExtractToken extrait le token du header Authorization. func ExtractToken(authHeader string) (string, error) { if authHeader == "" { return "", fmt.Errorf("missing authorization header") } parts := strings.SplitN(authHeader, " ", 2) if len(parts) != 2 || strings.ToLower(parts[0]) != "bearer" { return "", fmt.Errorf("invalid authorization header format") } return parts[1], nil }