Skip to content

Added captcha validation service to the engine - #2235

Merged
anushasunkada merged 2 commits into
mosip:develop-gofrom
anushasunkada:captcha
Jul 28, 2026
Merged

anushasunkada merged 2 commits into
mosip:develop-gofrom
anushasunkada:captcha

Conversation

@anushasunkada

@anushasunkada anushasunkada commented Jul 27, 2026 •

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features

    • Added Captcha validation support during service initialization.
    • Added atomic conditional updates for runtime-stored JSON values while preserving expiration.
    • Improved outbound HTTP client configuration with tuned timeouts and connection settings.
  • Bug Fixes

    • Enforced “essential” consent attributes: denied essential attributes now fail consent recording with an appropriate error.
    • Conditional updates safely avoid changes when values are missing, expired, mismatched, or malformed.
  • Tests

    • Expanded coverage for HTTP behavior, security enforcement, and conditional runtime updates across supported storage modes.

@coderabbitai

coderabbitai Bot commented Jul 27, 2026 •

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The change centralizes outbound HTTP client configuration, wires Captcha and identity providers to the shared client, updates consent metadata parsing and essential-attribute enforcement, and adds JSON field compare-and-swap support to in-memory and Redis runtime stores.

Changes

Outbound HTTP Client and Provider Wiring

Layer / File(s) Summary
HTTP client configuration and defaults
esignet-service/internal/config/*
Adds configurable outbound HTTP timeouts and connection limits, applies defaults, and updates related configuration tests.
Shared HTTP client provider wiring
esignet-service/cmd/esignet/main.go, esignet-service/internal/engine/{idsystem_factory,mock,mosip,sunbird}/*
Constructs one tuned HTTP client and passes it to identity providers, audit handling, and Captcha validation; Sunbird-specific timeout configuration is removed.
HTTP wiring validation
esignet-service/cmd/esignet/main_test.go, esignet-service/internal/engine/**/*_test.go, esignet-service/go.mod
Tests client settings, middleware behavior, provider initialization, and updates the ThunderID backend replacement.

Consent Metadata and Enforcement

Layer / File(s) Summary
Consent parsing and essential-attribute enforcement
esignet-service/internal/engine/consent_provider.go, esignet-service/internal/engine/consent_provider_test.go
Parses runtime claims and scopes, separates standard and authorization scopes, and rejects denied essential attributes without persisting consent.

Runtime Compare-and-Swap

Layer / File(s) Summary
In-memory conditional update
esignet-service/internal/engine/runtimestores/inmemory/*
Compares a top-level JSON string field under lock, replaces matching values, preserves expiration, and handles invalid or expired entries.
Redis atomic conditional update
esignet-service/internal/engine/runtimestores/redisstore/*
Uses a Lua script to compare and replace JSON values atomically with KEEPTTL; tests cover success, skips, missing keys, and errors.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

Poem

One client carries time and trust,
Consent guards what consent must.
JSON fields compare just right,
Redis keeps their lifetime tight.
Captcha joins the guarded stream—
Clean state flows through every seam.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 77.78% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title matches a real part of the change set, since the engine now wires in captcha validation, though the PR also includes broader HTTP client and store updates.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.12.2)

level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies"


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov-commenter

codecov-commenter commented Jul 27, 2026 •

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 95.74468% with 4 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (develop-go@ca835e1). Learn more about missing BASE report.

Files with missing lines Patch % Lines
esignet-service/cmd/esignet/main.go 81.25% 3 Missing ⚠️
...signet-service/internal/engine/idsystem_factory.go 66.66% 1 Missing ⚠️
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.
Additional details and impacted files
@@              Coverage Diff              @@
##             develop-go    #2235   +/-   ##
=============================================
  Coverage              ?   81.88%           
=============================================
  Files                 ?       59           
  Lines                 ?     4410           
  Branches              ?        0           
=============================================
  Hits                  ?     3611           
  Misses                ?      606           
  Partials              ?      193           
Flag Coverage Δ
go 81.88% <95.74%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@esignet-service/cmd/esignet/main_test.go`:
- Around line 54-59: Extend the “scope enforcement enabled wraps request time
middleware” test around getSecurityMiddleware to execute the returned middleware
with a request, rather than only asserting it is non-nil. Verify the enabled
branch produces the expected authorization rejection, or configure a test JWKS
server and valid token to assert the successful authorized path.

In `@esignet-service/cmd/esignet/main.go`:
- Around line 181-191: Update the HTTP transport created in the
client-construction function around the existing DialContext and timeout
settings to enforce a finite outbound connection limit, using MaxConnsPerHost or
equivalent admission control. Preserve the current timeout behavior, and add or
update coverage for the degraded Captcha-provider path to verify outbound
concurrency is bounded.

In `@esignet-service/internal/engine/runtimestores/inmemory/service_test.go`:
- Around line 241-248: Update the CompareFieldAndSwap test around inMemoryStore
to capture the key’s original expiresAt before the swap, then assert the
post-swap timestamp exactly matches that captured value rather than only
checking that it is non-zero. Preserve the existing read-locking pattern and
failure context.

In `@esignet-service/internal/engine/runtimestores/inmemory/service.go`:
- Around line 162-169: Update CompareFieldAndSwap in
esignet-service/internal/engine/runtimestores/inmemory/service.go and its Redis
counterpart in
esignet-service/internal/engine/runtimestores/redisstore/service.go to require a
top-level JSON object and an explicitly string-valued field before comparing;
reject null fields and scalar roots without swapping. Add matching null-field
and scalar-root parity tests in
esignet-service/internal/engine/runtimestores/inmemory/service_test.go:200-288
and
esignet-service/internal/engine/runtimestores/redisstore/service_test.go:349-400.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 1a8d83b3-8a06-4ea4-aa0c-d16ad3dc4313

📥 Commits

Reviewing files that changed from the base of the PR and between 85edce2 and d476651.

⛔ Files ignored due to path filters (1)
  • esignet-service/go.sum is excluded by !**/*.sum
📒 Files selected for processing (7)
  • esignet-service/cmd/esignet/main.go
  • esignet-service/cmd/esignet/main_test.go
  • esignet-service/go.mod
  • esignet-service/internal/engine/runtimestores/inmemory/service.go
  • esignet-service/internal/engine/runtimestores/inmemory/service_test.go
  • esignet-service/internal/engine/runtimestores/redisstore/service.go
  • esignet-service/internal/engine/runtimestores/redisstore/service_test.go

Comment thread esignet-service/cmd/esignet/main_test.go
Comment thread esignet-service/cmd/esignet/main.go
Comment thread esignet-service/internal/engine/runtimestores/inmemory/service_test.go Outdated
Comment thread esignet-service/internal/engine/runtimestores/inmemory/service.go Outdated
Signed-off-by: anushasunkada <anushasunkada@gmail.com>
Signed-off-by: anushasunkada <anushasunkada@gmail.com>
@anushasunkada
anushasunkada merged commit 8cc7c95 into mosip:develop-go Jul 28, 2026
17 of 23 checks passed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@esignet-service/cmd/esignet/main.go`:
- Around line 186-198: The newHTTPClient function must preserve
environment-based proxy support while applying its custom transport settings.
Initialize the transport from http.DefaultTransport.Clone() or set Proxy to
http.ProxyFromEnvironment, then override only DialContext and the configured
timeout, connection, and limit fields.

In `@esignet-service/internal/config/app.go`:
- Around line 183-184: Restore the fallback assignments for
cfg.Flow.DefaultAuthFlowHandle and cfg.Flow.UserOnboardingFlowHandle in the
configuration initialization, using the existing "default" and "user-onboarding"
values. Keep YAML-provided handles authoritative while ensuring deployments
without those settings receive non-empty defaults.

In `@esignet-service/internal/engine/consent_provider.go`:
- Around line 130-135: Update the essential-claims collection around
requestedClaims so essentiality is evaluated per claim section rather than by
flattened claim name, preserving essential userinfo.email when id_token.email is
non-essential. Ensure the consent flow returns essential_consent_denied before
persistence for this combination, and add a regression case covering essential
userinfo.email with non-essential id_token.email.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 5819bf2b-3e4a-44c2-a7ec-5f2899c12323

📥 Commits

Reviewing files that changed from the base of the PR and between d476651 and fb3d435.

⛔ Files ignored due to path filters (1)
  • esignet-service/go.sum is excluded by !**/*.sum
📒 Files selected for processing (24)
  • esignet-service/cmd/esignet/main.go
  • esignet-service/cmd/esignet/main_test.go
  • esignet-service/go.mod
  • esignet-service/internal/config/app.go
  • esignet-service/internal/config/app_test.go
  • esignet-service/internal/engine/consent_provider.go
  • esignet-service/internal/engine/consent_provider_test.go
  • esignet-service/internal/engine/idsystem_factory.go
  • esignet-service/internal/engine/idsystem_factory_test.go
  • esignet-service/internal/engine/mock/authenticator.go
  • esignet-service/internal/engine/mock/authenticator_test.go
  • esignet-service/internal/engine/mock/init.go
  • esignet-service/internal/engine/mock/init_test.go
  • esignet-service/internal/engine/mosip/init.go
  • esignet-service/internal/engine/mosip/init_test.go
  • esignet-service/internal/engine/runtimestores/inmemory/service.go
  • esignet-service/internal/engine/runtimestores/inmemory/service_test.go
  • esignet-service/internal/engine/runtimestores/redisstore/service.go
  • esignet-service/internal/engine/runtimestores/redisstore/service_test.go
  • esignet-service/internal/engine/sunbird/authenticator.go
  • esignet-service/internal/engine/sunbird/authenticator_test.go
  • esignet-service/internal/engine/sunbird/config.go
  • esignet-service/internal/engine/sunbird/config_test.go
  • esignet-service/internal/engine/sunbird/init.go
💤 Files with no reviewable changes (1)
  • esignet-service/internal/engine/sunbird/config.go

Comment on lines +186 to +198
func newHTTPClient(cfg config.HTTPClientConfig) *http.Client {
return &http.Client{
Timeout: time.Duration(cfg.TimeoutSecs) * time.Second,
Transport: &http.Transport{
DialContext: (&net.Dialer{
Timeout: time.Duration(cfg.DialTimeoutSecs) * time.Second,
KeepAlive: time.Duration(cfg.DialKeepAliveSecs) * time.Second,
}).DialContext,
TLSHandshakeTimeout: time.Duration(cfg.TLSHandshakeTimeoutSecs) * time.Second,
ResponseHeaderTimeout: time.Duration(cfg.ResponseHeaderTimeoutSecs) * time.Second,
IdleConnTimeout: time.Duration(cfg.IdleConnTimeoutSecs) * time.Second,
MaxConnsPerHost: cfg.MaxConnsPerHost,
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n -i -C 2 'ProxyFromEnvironment|HTTP_PROXY|HTTPS_PROXY|NO_PROXY' esignet-service

Repository: mosip/esignet

Length of output: 151


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== candidate files =="
git ls-files 'esignet-service/cmd/esignet/*.go' | sed -n '1,120p'

echo
echo "== main.go relevant section =="
sed -n '150,220p' esignet-service/cmd/esignet/main.go

echo
echo "== http client config references =="
rg -n -C 3 'HTTPClientConfig|newHTTPClient|HttpClient|HttpClientConfig' esignet-service

Repository: mosip/esignet

Length of output: 7396


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import os, subprocess, tempfile

env = os.environ.copy()
env["NO_PROXY"] = ""
env["HTTP_PROXY"] = "http://net-proxy.example:8080"
env["HTTPS_PROXY"] = "http://net-proxy.example:8080"

prog = r"""
package main

import (
	"context"
	"fmt"
	"net/http"
	"net/url"
	"time"
)

func newHTTPClient() *http.Client {
	return &http.Client{
		Timeout: time.Second,
		Transport: &http.Transport{
			DialContext: (&net.Dialer{
				Timeout: time.Second,
				KeepAlive: time.Second,
			}).DialContext,
		},
	}
}

func main() {
	req, _:= http.NewRequestWithContext(context.Background(), http.MethodGet, "https://example.invalid/", nil)
	p, err := newHTTPClient().Transport.(*http.Transport).Proxy(req)
	fmt.Println("proxy")
	if p != nil {
		fmt.Println("present=", p.String(), "matches=", p.String() == "http://net-proxy.example:8080")
	} else {
		fmt.Println("present=false")
	}
	fmt.Println("err=", err)
}
"""
fd, path = tempfile.mkstemp(suffix=".go")
try:
    os.write(fd, prog.encode())
    out = subprocess.run(["go", "run", path], env=env, text=True, capture_output=True, check=True)
    print(out.stdout)
finally:
    os.close(fd)
    os.remove(path)
PY

Repository: mosip/esignet

Length of output: 450


🌐 Web query:

Go net/http Transport Proxy field nil disables proxy default transport ProxyFromEnvironment

💡 Result:

Yes, setting the Proxy field to nil in a net/http.Transport disables all proxy usage for that transport [1][2][3]. The Transport struct's Proxy field is a function of type func(*Request) (*url.URL, error) [1][2]. According to the official Go documentation, if this field is nil (or if the function it points to returns a nil *URL), the Transport will not use a proxy for any requests [1][2]. By default, the net/http package's DefaultTransport uses ProxyFromEnvironment, which is a helper function that reads proxy settings from environment variables like HTTP_PROXY, HTTPS_PROXY, and NO_PROXY [4][3][5]. If you explicitly initialize a Transport with a nil Proxy field, you override this default behavior and effectively disable proxy discovery and usage entirely for that Transport instance [1][2].

Citations:


Preserve ProxyFromEnvironment in the outbound HTTP client.

newHTTPClient assigns a zero-value *http.Transport, so its Proxy field is nil and proxy env vars (HTTP_PROXY/HTTPS_PROXY/NO_PROXY) are ignored. Use http.DefaultTransport.(*http.Transport).Clone() or set Proxy: http.ProxyFromEnvironment before overriding only DialContext and the timeout/connection settings. This is required for GDPR/MOSIP-compliant deployments that must route all egress traffic through controlled proxies.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@esignet-service/cmd/esignet/main.go` around lines 186 - 198, The
newHTTPClient function must preserve environment-based proxy support while
applying its custom transport settings. Initialize the transport from
http.DefaultTransport.Clone() or set Proxy to http.ProxyFromEnvironment, then
override only DialContext and the configured timeout, connection, and limit
fields.

Comment on lines +183 to +184
//cfg.Flow.DefaultAuthFlowHandle = "default"
//cfg.Flow.UserOnboardingFlowHandle = "user-onboarding"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n -C 3 'DefaultAuthFlowHandle|UserOnboardingFlowHandle' esignet-service
rg -n --glob '*.yaml' --glob '*.yml' 'default_auth_flow_handle|user_onboarding_flow_handle' esignet-service

Repository: mosip/esignet

Length of output: 796


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== app config around Flow ==="
sed -n '150,200p' esignet-service/internal/config/app.go

echo
echo "=== Flow type definition ==="
rg -n -C 6 'type .*Flow|DefaultAuthFlowHandle|UserOnboardingFlowHandle|MaxVersionHistory' esignet-service/internal esignet-service | head -200

echo
echo "=== YAML config files containing flow-related keys ==="
rg -n -i --glob '*.yaml' --glob '*.yml' 'flow|auth_flow|onboarding|default_auth|user_onboarding' esignet-service || true

echo
echo "=== all occurrences ==="
rg -n 'DefaultAuthFlowHandle|UserOnboardingFlowHandle|default_auth_flow_handle|user_onboarding_flow_handle|Flow\.Default|Flow\.User' . || true

Repository: mosip/esignet

Length of output: 50370


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== app.go around flow defaults ==="
sed -n '160,190p' esignet-service/internal/config/app.go

echo
echo "=== focused occurrences in tracked files ==="
git ls-files | xargs rg -n --no-heading 'DefaultAuthFlowHandle|UserOnboardingFlowHandle|default_auth_flow_handle|user_onboarding_flow_handle' || true

echo
echo "=== focused non-i18n occurrences ==="
git ls-files 'esignet-service/internal/**' 'esignet-service/*[!.yaml]!' 'esignet-service/**/![i18n]/**' | xargs rg -n --no-heading 'DefaultAuthFlowHandle|UserOnboardingFlowHandle|default_auth_flow_handle|user_onboarding_flow_handle' || true

Repository: mosip/esignet

Length of output: 5590


Restore flow-handle fallback defaults.

esignet-service/internal/config/app.go:183-184 is the only place these handles are assigned, so deployments without YAML default_auth_flow_handle / user_onboarding_flow_handle config will receive empty handles. This breaks flow resolution and can fail MOSIP/OpenID eSignet authentication/onboarding flows.

Proposed fix if this removal was unintended
-	//	cfg.Flow.DefaultAuthFlowHandle = "default"
-	//	cfg.Flow.UserOnboardingFlowHandle = "user-onboarding"
+	cfg.Flow.DefaultAuthFlowHandle = "default"
+	cfg.Flow.UserOnboardingFlowHandle = "user-onboarding"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
//cfg.Flow.DefaultAuthFlowHandle = "default"
//cfg.Flow.UserOnboardingFlowHandle = "user-onboarding"
cfg.Flow.DefaultAuthFlowHandle = "default"
cfg.Flow.UserOnboardingFlowHandle = "user-onboarding"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@esignet-service/internal/config/app.go` around lines 183 - 184, Restore the
fallback assignments for cfg.Flow.DefaultAuthFlowHandle and
cfg.Flow.UserOnboardingFlowHandle in the configuration initialization, using the
existing "default" and "user-onboarding" values. Keep YAML-provided handles
authoritative while ensuring deployments without those settings receive
non-empty defaults.

Comment on lines +130 to +135
essentialClaims := map[string]bool{}
for name, v := range requestedClaims(req) {
if isEssentialClaim(v) {
essentialClaims[name] = true
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Preserve essentiality across userinfo and id_token.

Line 131 uses requestedClaims, which flattens sections by claim name. A non-essential id_token.email overwrites an essential userinfo.email, allowing denied email consent to persist instead of returning essential_consent_denied.

Proposed fix
 for name, v := range section {
   if name == "verified_claims" {
     continue
   }
-  out[name] = v
+  existing, exists := out[name]
+  if !exists || isEssentialClaim(v) || !isEssentialClaim(existing) {
+    out[name] = v
+  }
 }

Add a regression case with essential userinfo.email and non-essential id_token.email. Based on the PR objective that denied essential attributes must fail before persistence.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@esignet-service/internal/engine/consent_provider.go` around lines 130 - 135,
Update the essential-claims collection around requestedClaims so essentiality is
evaluated per claim section rather than by flattened claim name, preserving
essential userinfo.email when id_token.email is non-essential. Ensure the
consent flow returns essential_consent_denied before persistence for this
combination, and add a regression case covering essential userinfo.email with
non-essential id_token.email.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants