tsk-3: Add Token support (#10)

Closes #3

Reviewed-on: phoenix/textsender-api#10
Co-authored-by: phoenix <kundeng00@pm.me>
Co-committed-by: phoenix <kundeng00@pm.me>
This commit is contained in:
phoenix
2025-11-04 17:51:35 +00:00
committed by phoenix
parent 96fc87ea5e
commit d332cde84f
8 changed files with 113 additions and 4 deletions
+2
View File
@@ -16,6 +16,7 @@ type Config struct {
DBConnString string
ServerPort string
ResetDB bool
JWTSecret string `env:"JWT_SECRET" required:"true"`
}
type ConnectionInfo struct {
@@ -68,6 +69,7 @@ func Load() *Config {
DBConnString: dbConnString,
ServerPort: *port,
ResetDB: *resetDb,
JWTSecret: os.Getenv("JWT_SECRET"),
}
}
+47
View File
@@ -0,0 +1,47 @@
package middleware
import (
"context"
"net/http"
"strings"
"git.kundeng.us/phoenix/textsender-api/internal/services"
)
type contextKey string
const (
UserContextKey contextKey = "user"
)
func AuthMiddleware(authService *services.JWTService) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
authHeader := r.Header.Get("Authorization")
if authHeader == "" {
http.Error(w, "Authorization header required", http.StatusUnauthorized)
return
}
// Extract token from "Bearer <token>"
parts := strings.Split(authHeader, " ")
if len(parts) != 2 || parts[0] != "Bearer" {
http.Error(w, "Invalid authorization header format", http.StatusUnauthorized)
return
}
token := parts[1]
// Validate token with auth service
user, err := authService.ValidateToken(token)
if err != nil {
http.Error(w, "Invalid token", http.StatusUnauthorized)
return
}
// Add user to context
ctx := context.WithValue(r.Context(), UserContextKey, user)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
}
+50
View File
@@ -0,0 +1,50 @@
package services
import (
"time"
"github.com/golang-jwt/jwt/v5"
txtmodels_token "git.kundeng.us/phoenix/textsender-models/pkg/token"
txtmodels_user "git.kundeng.us/phoenix/textsender-models/pkg/user"
)
type JWTService struct {
secretKey []byte
}
func NewJWTService(secretKey string) *JWTService {
return &JWTService{
secretKey: []byte(secretKey),
}
}
func (s *JWTService) ValidateToken(tokenString string) (*txtmodels_user.User, error) {
// TODO: Include more user information in the claims to populate user
claims := &txtmodels_token.Claims{}
token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
// Validate the signing method
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, jwt.ErrSignatureInvalid
}
return s.secretKey, nil
})
if err != nil {
return nil, err
}
if !token.Valid {
return nil, jwt.ErrSignatureInvalid
}
// Check token expiration
if time.Now().After(claims.ExpiresAt.Time) {
return nil, jwt.ErrTokenExpired
}
return &txtmodels_user.User{
Id: claims.UserId,
}, nil
}