123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101 |
- package jwt
- import (
- "crypto/rand"
- "encoding/hex"
- "errors"
- "fmt"
- "net/url"
- "time"
- "github.com/golang-jwt/jwt/v5"
- )
- // JWTClient handles JWT operations for email verification
- type JWTClient struct {
- secret []byte
- }
- // EmailVerificationClaims contains claims specific to email verification tokens
- type EmailVerificationClaims struct {
- Email string `json:"email"`
- jwt.RegisteredClaims
- }
- func NewJWTClient(secret []byte) *JWTClient {
- return &JWTClient{secret: secret}
- }
- // GenerateEmailVerificationToken creates a JWT token for email verification
- // The token includes the email in both the claims and subject for additional security
- // and expires after 24 hours
- func (j *JWTClient) GenerateEmailVerificationToken(email string) (string, error) {
- uniqueID, err := generateUniqueID()
- if err != nil {
- return "", fmt.Errorf("failed to generate unique ID: %w", err)
- }
- claims := EmailVerificationClaims{
- Email: email,
- RegisteredClaims: jwt.RegisteredClaims{
- ExpiresAt: jwt.NewNumericDate(time.Now().UTC().Add(24 * time.Hour)),
- IssuedAt: jwt.NewNumericDate(time.Now().UTC()),
- NotBefore: jwt.NewNumericDate(time.Now().UTC()),
- Subject: email,
- ID: uniqueID,
- },
- }
- token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
- return token.SignedString(j.secret)
- }
- func (j *JWTClient) GenerateEmailVerificationURL(email, baseURL string) (string, error) {
- token, err := j.GenerateEmailVerificationToken(email)
- if err != nil {
- return "", fmt.Errorf("failed to generate token: %w", err)
- }
- // Ensure the token is URL-safe
- return fmt.Sprintf("%s?token=%s", baseURL, url.QueryEscape(token)), nil
- }
- func (j *JWTClient) VerifyEmailToken(tokenString string) (string, error) {
- token, err := jwt.ParseWithClaims(
- tokenString,
- &EmailVerificationClaims{},
- func(token *jwt.Token) (interface{}, error) {
- if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
- return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
- }
- return j.secret, nil
- },
- )
- if err != nil {
- return "", fmt.Errorf("invalid token: %w", err)
- }
- if !token.Valid {
- return "", errors.New("token is not valid")
- }
- claims, ok := token.Claims.(*EmailVerificationClaims)
- if !ok {
- return "", errors.New("invalid claims type")
- }
- if claims.Subject != claims.Email {
- return "", errors.New("token subject does not match email")
- }
- return claims.Email, nil
- }
- func generateUniqueID() (string, error) {
- bytes := make([]byte, 16)
- if _, err := rand.Read(bytes); err != nil {
- return "", err
- }
- return hex.EncodeToString(bytes), nil
- }
|