From 1f17dd832e54f99c199756855c00902711d023b8 Mon Sep 17 00:00:00 2001 From: "mdhumair.kankudti" Date: Thu, 22 Oct 2026 21:46:04 +0530 Subject: [PATCH 1/4] fixed 1,2,8,9 Signed-off-by: mdhumair.kankudti --- esignet-service/data/deployment.yaml | 2 +- esignet-service/internal/clientmgmt/validate.go | 3 +++ esignet-service/internal/config/app.go | 3 ++- esignet-service/internal/config/app_test.go | 4 ++-- esignet-service/internal/engine/captcha_provider.go | 6 +++--- .../internal/engine/captcha_provider_test.go | 4 +++- .../internal/engine/sunbird/authenticator.go | 13 ++++++++++++- 7 files changed, 26 insertions(+), 9 deletions(-) diff --git a/esignet-service/data/deployment.yaml b/esignet-service/data/deployment.yaml index 3cd8cb2ea..be44fe405 100644 --- a/esignet-service/data/deployment.yaml +++ b/esignet-service/data/deployment.yaml @@ -145,7 +145,7 @@ oauth: authorization_code: validity_period: 60 # override via MOSIP_ESIGNET_OAUTH_AUTH_CODE_LIFETIME_SECONDS par: - expires_in: 3600 # override via MOSIP_ESIGNET_OAUTH_PAR_EXPIRY_SECONDS + expires_in: 90 # RFC 9126 §2.2 short-lived; override via MOSIP_ESIGNET_OAUTH_PAR_EXPIRY_SECONDS dpop: required: false iat_window: 60 diff --git a/esignet-service/internal/clientmgmt/validate.go b/esignet-service/internal/clientmgmt/validate.go index d42e4f887..d1d72b7ec 100644 --- a/esignet-service/internal/clientmgmt/validate.go +++ b/esignet-service/internal/clientmgmt/validate.go @@ -397,6 +397,9 @@ func validateClaims(claims []string, minItems, maxItems int) error { if maxItems > 0 && len(claims) > maxItems { return validationErr("invalid_claim") } + if !hasUniqueStrings(claims) { + return validationErr("invalid_claim") + } if !containsAll(claims, allowedClaims) { return validationErr("invalid_claim") } diff --git a/esignet-service/internal/config/app.go b/esignet-service/internal/config/app.go index 8a4fb36ad..b9d95b458 100644 --- a/esignet-service/internal/config/app.go +++ b/esignet-service/internal/config/app.go @@ -388,7 +388,8 @@ func applyDefaults(cfg *AppConfig) { cfg.OAuth.AuthorizationCode.ValidityPeriod = 60 } if cfg.OAuth.PAR.ExpiresIn <= 0 { - cfg.OAuth.PAR.ExpiresIn = 3600 + // RFC 9126 §2.2 requires a short-lived request_uri; 90 s matches FAPI 2.0 guidance. + cfg.OAuth.PAR.ExpiresIn = 90 } cfg.OAuth.AuthClass.AcrAMR = map[string][]string{ "mosip:idp:acr:generated-code": {}, diff --git a/esignet-service/internal/config/app_test.go b/esignet-service/internal/config/app_test.go index 55169f03f..cc342c5ca 100644 --- a/esignet-service/internal/config/app_test.go +++ b/esignet-service/internal/config/app_test.go @@ -380,12 +380,12 @@ func (ts *AppConfigTestSuite) TestApplyDefaultsPARExpiresIn() { ts.Require().EqualValues(900, cfg.OAuth.PAR.ExpiresIn) }) - t.Run("defaults to 3600 when unset", func(_ *testing.T) { + t.Run("defaults to 90 when unset", func(_ *testing.T) { cfg := &AppConfig{} applyDefaults(cfg) - ts.Require().EqualValues(3600, cfg.OAuth.PAR.ExpiresIn) + ts.Require().EqualValues(90, cfg.OAuth.PAR.ExpiresIn) }) } diff --git a/esignet-service/internal/engine/captcha_provider.go b/esignet-service/internal/engine/captcha_provider.go index 4b4c9ceed..18839fcbc 100644 --- a/esignet-service/internal/engine/captcha_provider.go +++ b/esignet-service/internal/engine/captcha_provider.go @@ -34,7 +34,7 @@ type captchaProvider struct { func NewCaptchaProvider(config *config.CaptchaConfig, client *http.Client) providers.CaptchaValidationProvider { logger := applog.GetLogger() if !isEnabled(config) { - logger.Warn(context.Background(), "Captcha Validator URL not configured! captch token validation will be ignored.") + logger.Warn(context.Background(), "Captcha Validator URL not configured — all captcha tokens will be rejected (fail-closed). Set MOSIP_ESIGNET_CAPTCHA_VALIDATOR_URL to enable validation.") } return &captchaProvider{config: config, client: client, logger: logger} } @@ -66,8 +66,8 @@ func (p captchaProvider) Verify(ctx context.Context, token string) (*providers.C } if !isEnabled(p.config) { - p.logger.Warn(ctx, "Captcha Validator URL not configured! captch token validation will be ignored.") - return &providers.CaptchaVerificationResult{Success: true}, nil + p.logger.Warn(ctx, "Captcha Validator URL not configured — rejecting captcha token (fail-closed).") + return &providers.CaptchaVerificationResult{Success: false}, nil } payload, err := json.Marshal(captchaRequestWrapper{ diff --git a/esignet-service/internal/engine/captcha_provider_test.go b/esignet-service/internal/engine/captcha_provider_test.go index a3c69bd1a..567a774fd 100644 --- a/esignet-service/internal/engine/captcha_provider_test.go +++ b/esignet-service/internal/engine/captcha_provider_test.go @@ -29,12 +29,14 @@ func (ts *CaptchaProviderTestSuite) TestCaptchaProvider_Verify_EmptyToken() { } func (ts *CaptchaProviderTestSuite) TestCaptchaProvider_Verify_NotConfigured() { + // When ValidatorURL is not set the provider must fail closed (Success: false) + // so that a missing configuration does not silently accept arbitrary tokens. p := NewCaptchaProvider(&config.CaptchaConfig{}, http.DefaultClient) result, svcErr := p.Verify(context.Background(), "some-token") require.Nil(ts.T(), svcErr) require.NotNil(ts.T(), result) - require.True(ts.T(), result.Success) + require.False(ts.T(), result.Success) } func (ts *CaptchaProviderTestSuite) TestCaptchaProvider_Verify_Success() { diff --git a/esignet-service/internal/engine/sunbird/authenticator.go b/esignet-service/internal/engine/sunbird/authenticator.go index 5eb95f39c..74b5de9cb 100644 --- a/esignet-service/internal/engine/sunbird/authenticator.go +++ b/esignet-service/internal/engine/sunbird/authenticator.go @@ -92,7 +92,18 @@ func (p *sunbirdAuthnProvider) Authenticate(ctx context.Context, identifiers, cr for _, id := range p.kbiFieldIDs { val, ok := credentials[id].(string) if !ok || val == "" { - return nil, shared.InvalidRequestError + return nil, &common.ServiceError{ + Code: "AUTHN-MGR-1008", + Type: common.ClientErrorType, + Error: common.I18nMessage{ + Key: "missing_required_field", + DefaultValue: fmt.Sprintf("Required field '%s' is missing or blank", id), + }, + ErrorDescription: common.I18nMessage{ + Key: "missing_required_field_description", + DefaultValue: fmt.Sprintf("The required KBI field '%s' must not be blank", id), + }, + } } kbiFields[id] = val } From 9ff70ddd7bbd2fc07bbedda7f97adaaa7d13d3f9 Mon Sep 17 00:00:00 2001 From: "mdhumair.kankudti" Date: Wed, 4 Nov 2026 21:57:02 +0530 Subject: [PATCH 2/4] review comments fixed Signed-off-by: mdhumair.kankudti --- esignet-service/internal/clientmgmt/validate_test.go | 10 ++++++++++ .../internal/engine/sunbird/authenticator.go | 3 ++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/esignet-service/internal/clientmgmt/validate_test.go b/esignet-service/internal/clientmgmt/validate_test.go index 6d902415d..c1dc0b899 100644 --- a/esignet-service/internal/clientmgmt/validate_test.go +++ b/esignet-service/internal/clientmgmt/validate_test.go @@ -782,6 +782,16 @@ func (ts *ValidateTestSuite) TestValidateClaimsBounds() { assert.NoError(t, validateClaims(nil, 0, 0)) } +func (ts *ValidateTestSuite) TestValidateClaimsDuplicates() { + t := ts.T() + assert.Equal(t, "invalid_claim", errCode(t, validateClaims([]string{"name", "name"}, 0, 0)), + "duplicate claim entries must be rejected") + assert.Equal(t, "invalid_claim", errCode(t, validateClaims([]string{"name", "email", "name"}, 0, 0)), + "duplicate claim appearing non-adjacently must be rejected") + assert.NoError(t, validateClaims([]string{"name", "email"}, 0, 0), + "distinct claims must be accepted") +} + func (ts *ValidateTestSuite) TestValidateACRsBounds() { t := ts.T() assert.Equal(t, "invalid_acr", errCode(t, validateACRs([]string{"mosip:idp:acr:password"}, allowedACRAll, 2, 0))) diff --git a/esignet-service/internal/engine/sunbird/authenticator.go b/esignet-service/internal/engine/sunbird/authenticator.go index 74b5de9cb..f4017491f 100644 --- a/esignet-service/internal/engine/sunbird/authenticator.go +++ b/esignet-service/internal/engine/sunbird/authenticator.go @@ -13,6 +13,7 @@ import ( "errors" "fmt" "net/http" + "strings" "github.com/thunder-id/thunderid/pkg/thunderidengine/common" "github.com/thunder-id/thunderid/pkg/thunderidengine/providers" @@ -91,7 +92,7 @@ func (p *sunbirdAuthnProvider) Authenticate(ctx context.Context, identifiers, cr kbiFields := make(map[string]string, len(p.kbiFieldIDs)) for _, id := range p.kbiFieldIDs { val, ok := credentials[id].(string) - if !ok || val == "" { + if !ok || strings.TrimSpace(val) == "" { return nil, &common.ServiceError{ Code: "AUTHN-MGR-1008", Type: common.ClientErrorType, From e96232baf3d9ba8de1d72c1ba8e1e1ed7e8a886d Mon Sep 17 00:00:00 2001 From: "mdhumair.kankudti" Date: Wed, 4 Nov 2026 23:03:45 +0530 Subject: [PATCH 3/4] review comments fixed Signed-off-by: mdhumair.kankudti --- .../engine/sunbird/authenticator_test.go | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/esignet-service/internal/engine/sunbird/authenticator_test.go b/esignet-service/internal/engine/sunbird/authenticator_test.go index 147c8ace3..1dc24f42d 100644 --- a/esignet-service/internal/engine/sunbird/authenticator_test.go +++ b/esignet-service/internal/engine/sunbird/authenticator_test.go @@ -89,10 +89,12 @@ func (ts *AuthenticatorTestSuite) TestAuthenticate() { t.Run("missing credential field", func(t *testing.T) { p := newTestProvider("http://unused", "http://unused") identifiers := map[string]interface{}{sunbirdIndividualIDKey: "POL123"} - credentials := map[string]interface{}{"fullName": "Jane Doe"} + credentials := map[string]interface{}{"fullName": "Jane Doe"} // "dob" absent result, svcErr := p.Authenticate(context.Background(), identifiers, credentials, nil) require.Nil(t, result) - require.Same(t, shared.InvalidRequestError, svcErr) + require.NotNil(t, svcErr) + require.Equal(t, "AUTHN-MGR-1008", svcErr.Code) + require.Contains(t, svcErr.Error.DefaultValue, "dob") }) t.Run("empty credential value", func(t *testing.T) { @@ -101,7 +103,20 @@ func (ts *AuthenticatorTestSuite) TestAuthenticate() { credentials := map[string]interface{}{"fullName": "Jane Doe", "dob": ""} result, svcErr := p.Authenticate(context.Background(), identifiers, credentials, nil) require.Nil(t, result) - require.Same(t, shared.InvalidRequestError, svcErr) + require.NotNil(t, svcErr) + require.Equal(t, "AUTHN-MGR-1008", svcErr.Code) + require.Contains(t, svcErr.Error.DefaultValue, "dob") + }) + + t.Run("whitespace-only credential value", func(t *testing.T) { + p := newTestProvider("http://unused", "http://unused") + identifiers := map[string]interface{}{sunbirdIndividualIDKey: "POL123"} + credentials := map[string]interface{}{"fullName": "Jane Doe", "dob": " "} + result, svcErr := p.Authenticate(context.Background(), identifiers, credentials, nil) + require.Nil(t, result) + require.NotNil(t, svcErr) + require.Equal(t, "AUTHN-MGR-1008", svcErr.Code) + require.Contains(t, svcErr.Error.DefaultValue, "dob") }) t.Run("successful match", func(t *testing.T) { From a30b8284aa4be0025b82db02d22d0b1402787c8d Mon Sep 17 00:00:00 2001 From: "mdhumair.kankudti" Date: Wed, 4 Nov 2026 23:15:02 +0530 Subject: [PATCH 4/4] code rabbit fixes Signed-off-by: mdhumair.kankudti --- esignet-service/internal/config/app.go | 1 + 1 file changed, 1 insertion(+) diff --git a/esignet-service/internal/config/app.go b/esignet-service/internal/config/app.go index b9d95b458..49655b640 100644 --- a/esignet-service/internal/config/app.go +++ b/esignet-service/internal/config/app.go @@ -611,6 +611,7 @@ func ApplyEnvOverrides(cfg *AppConfig) error { } } if v := os.Getenv("MOSIP_ESIGNET_OAUTH_PAR_EXPIRY_SECONDS"); v != "" { + // Default: 90 s (RFC 9126 §2.2 short-lived; matches deployment.yaml and applyDefaults). secs, err := strconv.ParseInt(v, 10, 64) if err != nil { return fmt.Errorf("invalid MOSIP_ESIGNET_OAUTH_PAR_EXPIRY_SECONDS: %w", err)