Skip to content

Commit c9eb814

Browse files
author
Nick Campanini
committed
Merge branch 'feat/DC-153-request-replacement-card-flow' into feat/DC-160-address-validation-errors
2 parents 44a49a8 + 63b3cac commit c9eb814

21 files changed

Lines changed: 265 additions & 150 deletions

File tree

packages/design-system/content/scripts/generate-locales.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -301,10 +301,11 @@ function parseContentKey(contentKey) {
301301
function buildStateLocaleData(rows, state) {
302302
const [headerRow, ...dataRows] = rows;
303303

304-
// Find column indices (handle emoji prefixes like "🟡 Content")
305-
const contentIdx = headerRow.findIndex((h) =>
306-
h.toLowerCase().includes('content')
307-
);
304+
// Find column indices (handle emoji prefixes like "🟡 Content" and renamed headers like "Variable Name/Key")
305+
const contentIdx = headerRow.findIndex((h) => {
306+
const lower = h.toLowerCase()
307+
return lower.includes('content') || lower.includes('variable name')
308+
});
308309
const englishIdx = headerRow.findIndex((h) =>
309310
h.toLowerCase().includes('english')
310311
);

packages/design-system/content/scripts/generate-locales.test.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,5 +65,28 @@ assert.ok(!enrollmentContent.includes('dashboard'), 'enrollment barrel must NOT
6565
// Test: locale JSON was written to --out-dir (not to script's own directory)
6666
assert.ok(existsSync(join(tmpDir, 'locales', 'en', 'co', 'landing.json')), 'locale JSON must be written to --out-dir')
6767

68+
// Test: new CSV column headers ("Variable Name/Key", "🟢 CO English", "🟢 CO Español")
69+
setup()
70+
const csvNewHeaders = [
71+
'Variable Name/Key,🟢 CO English,⚪ SOURCE English,🟢 CO Español,⚪ SOURCE Español,Notes',
72+
'GLOBAL - Button Continue,Continue,,Continuar,,',
73+
'S1 - Landing Page - Title,New Header Landing,,New Header Landing ES,,',
74+
].join('\n')
75+
writeFileSync(join(tmpDir, 'states', 'co.csv'), csvNewHeaders)
76+
77+
execFileSync('node', [
78+
script,
79+
'--csv-dir', join(tmpDir, 'states'),
80+
'--out-dir', join(tmpDir, 'locales'),
81+
'--ts-out', join(tmpDir, 'ts-out', 'new-header-resources.ts'),
82+
'--app', 'portal',
83+
], { stdio: 'inherit' })
84+
85+
const newHeaderLanding = JSON.parse(readFileSync(join(tmpDir, 'locales', 'en', 'co', 'landing.json'), 'utf8'))
86+
assert.equal(newHeaderLanding.title, 'New Header Landing', 'must parse content from "Variable Name/Key" column header')
87+
88+
const newHeaderCommon = JSON.parse(readFileSync(join(tmpDir, 'locales', 'es', 'co', 'common.json'), 'utf8'))
89+
assert.equal(newHeaderCommon.buttonContinue, 'Continuar', 'must parse Spanish from state-prefixed Español column')
90+
6891
console.log('✅ All generate-locales CLI arg tests passed')
6992
teardown()

packages/design-system/content/states/co.csv

Lines changed: 52 additions & 37 deletions
Large diffs are not rendered by default.

packages/design-system/content/states/dc.csv

Lines changed: 47 additions & 30 deletions
Large diffs are not rendered by default.

src/SEBT.Portal.Api/Controllers/Auth/OidcController.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,10 @@ public async Task<IActionResult> CompleteLogin(
110110
ValidateAudience = false,
111111
ValidateLifetime = true,
112112
ClockSkew = TimeSpan.FromMinutes(1),
113-
IssuerSigningKey = key
113+
// Use resolver instead of IssuerSigningKey to bypass kid-matching;
114+
// jose (Next.js) signs without a kid header, which causes IDX10517
115+
// when JwtSecurityTokenHandler tries to match by kid.
116+
IssuerSigningKeyResolver = (token, securityToken, kid, parameters) => [key]
114117
};
115118
var handler = new JwtSecurityTokenHandler();
116119
handler.MapInboundClaims = false; // Preserve original JWT claim names (sub, email)
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using System.Security.Claims;
2+
3+
namespace SEBT.Portal.Core.Models.Auth;
4+
5+
/// <summary>
6+
/// Extension methods for resolving authentication-related claims from a ClaimsPrincipal.
7+
/// </summary>
8+
public static class ClaimsPrincipalExtensions
9+
{
10+
/// <summary>
11+
/// Resolves the user's Identity Assurance Level from the IAL claim.
12+
/// Returns <see cref="UserIalLevel.None"/> if the claim is missing or unrecognized.
13+
/// </summary>
14+
public static UserIalLevel GetIalLevel(this ClaimsPrincipal user)
15+
{
16+
var ialClaim = user.FindFirst(JwtClaimTypes.Ial)?.Value;
17+
18+
if (string.IsNullOrWhiteSpace(ialClaim))
19+
{
20+
return UserIalLevel.None;
21+
}
22+
23+
var normalized = ialClaim.Trim().ToLowerInvariant();
24+
return normalized switch
25+
{
26+
"1" => UserIalLevel.IAL1,
27+
"1plus" => UserIalLevel.IAL1plus,
28+
"2" => UserIalLevel.IAL2,
29+
"0" => UserIalLevel.None,
30+
_ => UserIalLevel.None
31+
};
32+
}
33+
}

src/SEBT.Portal.Infrastructure/Repositories/MockHouseholdRepository.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,8 @@ private void SeedMockData()
190190
verified.Email = verifiedEmail;
191191
verified.UserProfile = new UserProfile { FirstName = "John", MiddleName = "Robert", LastName = "Doe" };
192192
_households[verifiedEmail] = verified;
193+
// To test CO OIDC login locally, uncomment and replace with your PingOne sandbox user email:
194+
// _households["sebt.co+YOUR_PHONE@codeforamerica.org"] = verified;
193195

194196
// Scenario 3: Pending application without address (not ID verified)
195197
// Note: Address should not be included for non-ID-verified users, but we set it here

src/SEBT.Portal.UseCases/Household/GetHouseholdData/GetHouseholdDataQueryHandler.cs

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public async Task<Result<HouseholdData>> Handle(GetHouseholdDataQuery query, Can
3232

3333
logger.LogDebug("Household data request received for identifier type {Type}", identifier.Type);
3434

35-
var userIalLevel = GetUserIalLevel(query.User);
35+
var userIalLevel = query.User.GetIalLevel();
3636
var piiVisibility = idProofingRequirementsService.GetPiiVisibility(userIalLevel);
3737

3838
logger.LogDebug(
@@ -58,23 +58,4 @@ public async Task<Result<HouseholdData>> Handle(GetHouseholdDataQuery query, Can
5858
return Result<HouseholdData>.Success(householdData);
5959
}
6060

61-
private static UserIalLevel GetUserIalLevel(ClaimsPrincipal user)
62-
{
63-
var ialClaim = user.FindFirst(JwtClaimTypes.Ial)?.Value;
64-
65-
if (string.IsNullOrWhiteSpace(ialClaim))
66-
{
67-
return UserIalLevel.None;
68-
}
69-
70-
var normalized = ialClaim.Trim().ToLowerInvariant();
71-
return normalized switch
72-
{
73-
"1" => UserIalLevel.IAL1,
74-
"1plus" => UserIalLevel.IAL1plus,
75-
"2" => UserIalLevel.IAL2,
76-
"0" => UserIalLevel.None,
77-
_ => UserIalLevel.None
78-
};
79-
}
8061
}

src/SEBT.Portal.UseCases/Household/RequestCardReplacement/RequestCardReplacementCommandHandler.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,12 @@ public async Task<Result> Handle(
4141
return Result.Unauthorized("Unable to identify user from token.");
4242
}
4343

44+
var userIalLevel = command.User.GetIalLevel();
45+
4446
var household = await repository.GetHouseholdByIdentifierAsync(
4547
identifier,
4648
new PiiVisibility(IncludeAddress: false, IncludeEmail: false, IncludePhone: false),
47-
UserIalLevel.None,
49+
userIalLevel,
4850
cancellationToken);
4951

5052
if (household == null)
@@ -72,7 +74,7 @@ public async Task<Result> Handle(
7274
// Stubbed — returns success without calling the state system.
7375

7476
logger.LogInformation(
75-
"Card replacement completed for household identifier kind {Kind}",
77+
"Card replacement request recorded for household identifier kind {Kind}",
7678
identifierKind);
7779

7880
return Result.Success();

src/SEBT.Portal.Web/next.config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ const nextConfig: NextConfig = {
3333
/* SASS Configuration for USWDS */
3434
sassOptions: {
3535
implementation: 'sass-embedded',
36+
quietDeps: true,
3637
includePaths: [
3738
path.join(designSystemPath, 'design/sass'),
3839
path.join(__dirname, 'node_modules/@uswds/uswds/packages'),
@@ -49,6 +50,7 @@ const nextConfig: NextConfig = {
4950
options: {
5051
implementation: 'sass-embedded',
5152
sassOptions: {
53+
quietDeps: true,
5254
loadPaths: [
5355
path.join(designSystemPath, 'design/sass'),
5456
path.join(__dirname, 'node_modules/@uswds/uswds/packages'),

0 commit comments

Comments
 (0)