Skip to content

Commit 6ba7934

Browse files
necampaniniNick Campanini
andauthored
Fix CO deployment crash caused by Socure startup validation (#101)
* Add Socure Enabled flag, validator short circuit, and DisabledSocureClient * Wire DisabledSocureClient into DI and move Socure config to state-specific templates --------- Co-authored-by: Nick Campanini <ncampanini@codeforamerica.org>
1 parent 8bfd872 commit 6ba7934

12 files changed

Lines changed: 126 additions & 29 deletions

File tree

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,10 @@ scripts/scratch/
2525
*.sln.docstates
2626
*.env
2727

28-
# Local development config (use appsettings.Development.example.json as template)
28+
# Local/deployed config (use matching .example.json as template)
2929
src/SEBT.Portal.Api/appsettings.Development.json
30+
src/SEBT.Portal.Api/appsettings.dc.json
31+
src/SEBT.Portal.Api/appsettings.co.json
3032

3133
# User-specific files (MonoDevelop/Xamarin Studio)
3234
*.userprefs

src/SEBT.Portal.Api/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@
114114

115115
// Adds use cases (i.e., query and command handlers) for portal business logic
116116
builder.Services.AddUseCases();
117-
builder.Services.AddPortalInfrastructureServices();
117+
builder.Services.AddPortalInfrastructureServices(builder.Configuration);
118118
builder.Services.AddPortalDbContext(builder.Configuration, options => options.ConfigureDevelopmentSeeding());
119119
builder.Services.AddPortalInfrastructureRepositories(builder.Configuration);
120120
builder.Services.AddPortalInfrastructureAppSettings(builder.Configuration);

src/SEBT.Portal.Api/appsettings.co.example.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
{
2+
"Socure": {
3+
"Enabled": false
4+
},
25
"IdProofingRequirements": {
36
"address+view": "IAL1plus",
47
"email+view": "IAL1plus",
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"Socure": {
3+
"Enabled": true,
4+
"UseStub": true,
5+
"BaseUrl": "https://riskos.sandbox.socure.com",
6+
"ChallengeExpirationMinutes": 30,
7+
"ApiVersion": "2025-01-01.orion",
8+
"Workflow": "consumer_onboarding",
9+
"DocvEnrichmentName": "SocureDocRequest"
10+
},
11+
"FeatureManagement": {
12+
"show_application_number": false,
13+
"show_case_number": false,
14+
"show_card_last4": false
15+
}
16+
}

src/SEBT.Portal.Api/appsettings.dc.json

Lines changed: 0 additions & 7 deletions
This file was deleted.

src/SEBT.Portal.Api/appsettings.json

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -87,14 +87,6 @@
8787
"ProfileId": ""
8888
}
8989
},
90-
"Socure": {
91-
"UseStub": true,
92-
"BaseUrl": "https://riskos.sandbox.socure.com",
93-
"ChallengeExpirationMinutes": 30,
94-
"ApiVersion": "2025-01-01.orion",
95-
"Workflow": "consumer_onboarding",
96-
"DocvEnrichmentName": "SocureDocRequest"
97-
},
9890
"FeatureManagement": {
9991
"email_dob_opt_in": false,
10092
"show_application_number": true,

src/SEBT.Portal.Core/AppSettings/SocureSettings.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ public class SocureSettings
1111
{
1212
public static readonly string SectionName = "Socure";
1313

14+
/// <summary>
15+
/// When false, Socure integration is disabled entirely — validation is skipped
16+
/// and a no-op client is registered. States that don't use Socure leave this false.
17+
/// </summary>
18+
public bool Enabled { get; set; }
19+
1420
/// <summary>
1521
/// When true, uses the StubSocureClient instead of the real HTTP client.
1622
/// Automatically true in Development when no API key is configured.

src/SEBT.Portal.Infrastructure/Configuration/SocureSettingsValidator.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ public ValidateOptionsResult Validate(string? name, SocureSettings options)
1919
return ValidateOptionsResult.Fail("Socure configuration section is not present.");
2020
}
2121

22+
if (!options.Enabled)
23+
{
24+
return ValidateOptionsResult.Success;
25+
}
26+
2227
if (options.ChallengeExpirationMinutes < 1 || options.ChallengeExpirationMinutes > 1440)
2328
{
2429
return ValidateOptionsResult.Fail(

src/SEBT.Portal.Infrastructure/Dependencies.cs

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ namespace SEBT.Portal.Infrastructure;
1616

1717
public static class Dependencies
1818
{
19-
public static IServiceCollection AddPortalInfrastructureServices(this IServiceCollection services)
19+
public static IServiceCollection AddPortalInfrastructureServices(this IServiceCollection services, IConfiguration configuration)
2020
{
2121
// Otp Services
2222
services.AddTransient<IOtpSenderService, EmailOtpSenderService>();
@@ -39,17 +39,25 @@ public static IServiceCollection AddPortalInfrastructureServices(this IServiceCo
3939
// Expose SocureSettings directly for use case injection (avoids IOptions dependency in UseCases layer)
4040
services.AddSingleton(sp => sp.GetRequiredService<IOptions<SocureSettings>>().Value);
4141

42-
// Socure client — stub or real based on SocureSettings.UseStub
43-
services.AddTransient<StubSocureClient>();
44-
services.AddTransient<HttpSocureClient>();
45-
services.AddTransient<ISocureClient>(sp =>
42+
// Socure client — disabled, stub, or real based on configuration
43+
var socureEnabled = configuration.GetValue<bool>("Socure:Enabled");
44+
if (socureEnabled)
4645
{
47-
var settings = sp.GetRequiredService<IOptions<SocureSettings>>().Value;
48-
if (settings.UseStub)
49-
return sp.GetRequiredService<StubSocureClient>();
46+
services.AddTransient<StubSocureClient>();
47+
services.AddTransient<HttpSocureClient>();
48+
services.AddTransient<ISocureClient>(sp =>
49+
{
50+
var settings = sp.GetRequiredService<IOptions<SocureSettings>>().Value;
51+
if (settings.UseStub)
52+
return sp.GetRequiredService<StubSocureClient>();
5053

51-
return sp.GetRequiredService<HttpSocureClient>();
52-
});
54+
return sp.GetRequiredService<HttpSocureClient>();
55+
});
56+
}
57+
else
58+
{
59+
services.AddTransient<ISocureClient, DisabledSocureClient>();
60+
}
5361

5462
return services;
5563
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
using SEBT.Portal.Core.Models.DocVerification;
2+
using SEBT.Portal.Core.Services;
3+
using SEBT.Portal.Kernel;
4+
using SEBT.Portal.Kernel.Results;
5+
6+
namespace SEBT.Portal.Infrastructure.Services;
7+
8+
/// <summary>
9+
/// No-op implementation of <see cref="ISocureClient"/> used when Socure integration is disabled.
10+
/// Returns a <see cref="DependencyFailedReason.NotConfigured"/> result for all operations.
11+
/// </summary>
12+
public class DisabledSocureClient : ISocureClient
13+
{
14+
private const string DisabledMessage = "Socure integration is not enabled for this deployment.";
15+
16+
public Task<Result<IdProofingAssessmentResult>> RunIdProofingAssessmentAsync(
17+
int userId,
18+
string email,
19+
string dateOfBirth,
20+
string? idType,
21+
string? idValue,
22+
CancellationToken cancellationToken = default)
23+
{
24+
return Task.FromResult(
25+
Result<IdProofingAssessmentResult>.DependencyFailed(
26+
DependencyFailedReason.NotConfigured, DisabledMessage));
27+
}
28+
29+
public Task<Result<SocureDocvSession>> StartDocvSessionAsync(
30+
int userId,
31+
string email,
32+
CancellationToken cancellationToken = default)
33+
{
34+
return Task.FromResult(
35+
Result<SocureDocvSession>.DependencyFailed(
36+
DependencyFailedReason.NotConfigured, DisabledMessage));
37+
}
38+
}

0 commit comments

Comments
 (0)