From 58912a66d415d077e979420bc6b0817861ef3291 Mon Sep 17 00:00:00 2001 From: Jay Brown Date: Fri, 13 Feb 2026 01:38:32 +0000 Subject: [PATCH] Merged in feature/support-empty-email-jwt (pull request #209) handle missing email in jwt * handle missing email --- api/queryAPI/eulaHandlers_test.go | 47 ++++++++++++++++++++++-------- api/queryAPI/eulaPublicHandlers.go | 9 ++++-- 2 files changed, 42 insertions(+), 14 deletions(-) diff --git a/api/queryAPI/eulaHandlers_test.go b/api/queryAPI/eulaHandlers_test.go index 832d9999..3f11bbd4 100644 --- a/api/queryAPI/eulaHandlers_test.go +++ b/api/queryAPI/eulaHandlers_test.go @@ -244,22 +244,45 @@ func TestAgreeToEula(t *testing.T) { assert.Equal(t, http.StatusUnauthorized, rec.Code) }) - t.Run("succeeds with username fallback when email claim is missing", func(t *testing.T) { - // Simulate a Cognito access token that has 'sub' and 'cognito:username' - // but no 'email' claim. This is the default for Cognito access tokens. + t.Run("succeeds with username fallback when email is missing (access token)", func(t *testing.T) { + // Simulate a real Cognito access token: has 'sub' and 'username' (not + // 'cognito:username') and no 'email'. This is the default for access tokens. userSubject := "no-email-user-" + uuid.New().String()[:8] ctx, rec := setupEulaTestContext(http.MethodPost, "/eula/agree", nil) ctx.Request().RemoteAddr = "192.168.1.100:12345" - // Set claims with sub but without email (mimics access token) - ctx.Set("user_claims", map[string]interface{}{ - "sub": userSubject, - "cognito:username": "testuser", - }) - ctx.Set("user_info", cognitoauth.UserInfo{ - Username: "testuser", - Email: "", // empty - no email in access token - }) + // Mimic what the middleware produces from a real access token: + // ExtractUserInfo sees "username" (not "cognito:username") and no "email". + claims := map[string]interface{}{ + "sub": userSubject, + "username": "accessuser", + "token_use": "access", + } + ctx.Set("user_claims", claims) + ctx.Set("user_info", cognitoauth.ExtractUserInfo(claims)) + + err := cons.AgreeToEula(ctx) + require.NoError(t, err) + assert.Equal(t, http.StatusCreated, rec.Code) + + var response queryapi.EulaAgreementResponse + err = json.Unmarshal(rec.Body.Bytes(), &response) + require.NoError(t, err) + assert.Equal(t, versionStr, response.EulaVersion) + }) + + t.Run("succeeds with subject ID fallback when email and username are both missing", func(t *testing.T) { + // Edge case: token has sub but neither email nor any username claim. + userSubject := "bare-token-user-" + uuid.New().String()[:8] + ctx, rec := setupEulaTestContext(http.MethodPost, "/eula/agree", nil) + ctx.Request().RemoteAddr = "192.168.1.100:12345" + + claims := map[string]interface{}{ + "sub": userSubject, + "token_use": "access", + } + ctx.Set("user_claims", claims) + ctx.Set("user_info", cognitoauth.ExtractUserInfo(claims)) err := cons.AgreeToEula(ctx) require.NoError(t, err) diff --git a/api/queryAPI/eulaPublicHandlers.go b/api/queryAPI/eulaPublicHandlers.go index 4089fb87..d0ac367a 100644 --- a/api/queryAPI/eulaPublicHandlers.go +++ b/api/queryAPI/eulaPublicHandlers.go @@ -135,14 +135,19 @@ func (s *Controllers) AgreeToEula(ctx echo.Context) error { // Resolve the user identifier for the agreement record. // Cognito access tokens do not include the email claim by default (only ID tokens do). - // Fall back to the username (cognito:username), which is always present in access tokens. + // Fall back to username, then to subject ID (always present in any Cognito token). userIdentifier := userInfo.Email if userIdentifier == "" { userIdentifier = userInfo.Username - s.cfg.GetAuthLogger().Info("AgreeToEula: email claim missing from token, using username as fallback", + s.cfg.GetAuthLogger().Info("AgreeToEula: email missing from token, using username fallback", "user_subject", userSubject, "username", userInfo.Username) } + if userIdentifier == "" { + userIdentifier = userSubject + s.cfg.GetAuthLogger().Info("AgreeToEula: email and username missing from token, using subject ID fallback", + "user_subject", userSubject) + } // Get current EULA version currentVersion, err := s.svc.Eula.GetCurrentVersion(ctx.Request().Context())