metric f ton

This commit is contained in:
KS Jannette
2026-03-29 07:46:56 -04:00
parent 0920985a74
commit 0412ea3e99
20 changed files with 553 additions and 206 deletions

View File

@@ -8,7 +8,8 @@ import (
"net/http"
"strings"
fbauth "github.com/kjannette/koin-ping/backend/internal/firebase"
"github.com/golang-jwt/jwt/v5"
"github.com/kjannette/koin-ping/backend/internal/models"
)
@@ -31,10 +32,9 @@ func writeJSON(w http.ResponseWriter, status int, v interface{}) {
json.NewEncoder(w).Encode(v) //nolint:errcheck
}
// Authenticate verifies the Firebase ID token and auto-provisions a local user
// record. The local user UUID (not the Firebase UID) is placed into context so
// all downstream handlers use it as the canonical user identifier.
func Authenticate(userModel *models.UserModel) func(http.Handler) http.Handler {
// Authenticate verifies the JWT from the Authorization header, loads the local
// user from Postgres, and injects the user UUID + email + tier into context.
func Authenticate(userModel *models.UserModel, jwtSecret string) 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")
@@ -47,9 +47,8 @@ func Authenticate(userModel *models.UserModel) func(http.Handler) http.Handler {
return
}
token := strings.TrimPrefix(authHeader, "Bearer ")
if token == "" {
log.Println("Empty token")
rawToken := strings.TrimPrefix(authHeader, "Bearer ")
if rawToken == "" {
writeJSON(w, http.StatusUnauthorized, errorResponse{
Error: "UNAUTHORIZED",
Message: "Invalid token format",
@@ -57,12 +56,19 @@ func Authenticate(userModel *models.UserModel) func(http.Handler) http.Handler {
return
}
log.Println("Verifying Firebase token...")
decoded, err := fbauth.Auth().VerifyIDToken(r.Context(), token)
if err != nil {
log.Printf("Token verification failed: %v", err)
parsed, err := jwt.Parse(rawToken, func(t *jwt.Token) (interface{}, error) {
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, jwt.ErrSignatureInvalid
}
return []byte(jwtSecret), nil
})
if err != nil || !parsed.Valid {
log.Printf("JWT verification failed: %v", err)
errMsg := err.Error()
errMsg := ""
if err != nil {
errMsg = err.Error()
}
if strings.Contains(errMsg, "expired") {
writeJSON(w, http.StatusUnauthorized, errorResponse{
Error: "TOKEN_EXPIRED",
@@ -78,20 +84,37 @@ func Authenticate(userModel *models.UserModel) func(http.Handler) http.Handler {
return
}
firebaseUID := decoded.UID
email, _ := decoded.Claims["email"].(string)
user, err := userModel.FindOrCreateByFirebaseUID(r.Context(), firebaseUID, email)
if err != nil {
log.Printf("Failed to provision local user for Firebase UID %s: %v", firebaseUID, err)
writeJSON(w, http.StatusInternalServerError, errorResponse{
Error: "INTERNAL_ERROR",
Message: "Failed to initialize user account",
claims, ok := parsed.Claims.(jwt.MapClaims)
if !ok {
writeJSON(w, http.StatusUnauthorized, errorResponse{
Error: "UNAUTHORIZED",
Message: "Invalid token claims",
})
return
}
log.Printf("Token verified! User UUID: %s, Email: %s", user.ID, email)
userID, _ := claims["sub"].(string)
email, _ := claims["email"].(string)
if userID == "" {
writeJSON(w, http.StatusUnauthorized, errorResponse{
Error: "UNAUTHORIZED",
Message: "Invalid token: missing user ID",
})
return
}
user, err := userModel.GetByID(r.Context(), userID)
if err != nil || user == nil {
log.Printf("JWT auth: user %s not found in DB: %v", userID, err)
writeJSON(w, http.StatusUnauthorized, errorResponse{
Error: "UNAUTHORIZED",
Message: "User account not found",
})
return
}
log.Printf("JWT verified — User UUID: %s, Email: %s", user.ID, email)
ctx := context.WithValue(r.Context(), UserIDKey, user.ID)
ctx = context.WithValue(ctx, UserEmailKey, email)