Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<VersionPrefix>2.3.0</VersionPrefix>
<VersionPrefix>2.4.0</VersionPrefix>
<!-- SPDX license identifier for MIT -->
<PackageLicenseExpression>MIT</PackageLicenseExpression>

Expand Down
2 changes: 1 addition & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<PackageVersion Include="Microsoft.Testing.Extensions.CodeCoverage" Version="18.5.2" />
</ItemGroup>
<ItemGroup Label="AWS">
<PackageVersion Include="Amazon.CDK.Lib" Version="2.246.0" />
<PackageVersion Include="Amazon.CDK.Lib" Version="2.248.0" />
</ItemGroup>
<ItemGroup Label="Testing">
<PackageVersion Include="AutoFixture" Version="4.18.1" />
Expand Down
12 changes: 12 additions & 0 deletions src/LayeredCraft.Cdk.Constructs/CognitoUserPoolConstruct.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
using Amazon.CDK;
using Amazon.CDK.AWS.CertificateManager;
using Amazon.CDK.AWS.Cognito;
using Amazon.CDK.AWS.IAM;
using Amazon.CDK.AWS.Lambda;
using Amazon.CDK.AWS.Route53;
using Amazon.CDK.AWS.Route53.Targets;
using Constructs;
Expand Down Expand Up @@ -63,6 +65,16 @@ public CognitoUserPoolConstruct(
RemovalPolicy = props.RemovalPolicy,
});

if (props.PostConfirmationTrigger is not null)
{
UserPool.AddTrigger(UserPoolOperation.POST_CONFIRMATION, props.PostConfirmationTrigger);
props.PostConfirmationTrigger.AddToRolePolicy(new PolicyStatement(new PolicyStatementProps
{
Actions = ["cognito-idp:AdminAddUserToGroup"],
Resources = ["*"],
}));
}

var resourceServers = CreateResourceServers(props);

ResourceServers = resourceServers;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Amazon.CDK;
using Amazon.CDK.AWS.Cognito;
using Amazon.CDK.AWS.Lambda;

namespace LayeredCraft.Cdk.Constructs.Models;

Expand All @@ -21,6 +22,7 @@ public interface ICognitoUserPoolConstructProps
IReadOnlyList<ICognitoUserPoolAppClientProps> AppClients { get; }
ICognitoUserPoolDomainProps? Domain { get; }
IReadOnlyCollection<ICognitoUserPoolGroupProps>? Groups { get; }
IFunction? PostConfirmationTrigger { get; }
}

public sealed record CognitoUserPoolConstructProps : ICognitoUserPoolConstructProps
Expand All @@ -40,4 +42,5 @@ public sealed record CognitoUserPoolConstructProps : ICognitoUserPoolConstructPr
public IReadOnlyList<ICognitoUserPoolAppClientProps> AppClients { get; init; } = [];
public ICognitoUserPoolDomainProps? Domain { get; init; }
public IReadOnlyCollection<ICognitoUserPoolGroupProps>? Groups { get; init; } = [];
public IFunction? PostConfirmationTrigger { get; init; } = null;
}
31 changes: 30 additions & 1 deletion src/LayeredCraft.Cdk.Constructs/StaticSiteConstruct.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,27 @@ public StaticSiteConstruct(Construct scope, string id, IStaticSiteConstructProps
SubjectAlternativeNames = props.AlternateDomains,
});

// CloudFront Function: rewrite SPA routes (no file extension) to /index.html
// so client-side routes are handled by the app rather than returning 404.
// Requests with a file extension pass through unchanged, preserving normal
// asset serving and ensuring API 404s (served via the /api/* behavior) are
// never affected — they never reach this default behavior.
var spaRewriteFunction = new Amazon.CDK.AWS.CloudFront.Function(this, $"{id}-spa-rewrite",
new Amazon.CDK.AWS.CloudFront.FunctionProps
{
Code = FunctionCode.FromInline("""
function handler(event) {
var uri = event.request.uri;
if (!uri.match(/\.[a-zA-Z0-9]+$/)) {
event.request.uri = '/index.html';
}
return event.request;
}
"""),
Runtime = FunctionRuntime.JS_2_0,
Comment = "Rewrite SPA routes to /index.html for client-side routing"
});

// Create CloudFront distribution for global content delivery
var distribution = new Distribution(this, $"{id}-cdn", new DistributionProps
{
Expand All @@ -83,7 +104,15 @@ public StaticSiteConstruct(Construct scope, string id, IStaticSiteConstructProps
ProtocolPolicy = OriginProtocolPolicy.HTTP_ONLY
}),
AllowedMethods = AllowedMethods.ALLOW_GET_HEAD,
Compress = true
Compress = true,
FunctionAssociations =
[
new FunctionAssociation
{
Function = spaRewriteFunction,
EventType = FunctionEventType.VIEWER_REQUEST
}
]
},
Certificate = certificate,
ErrorResponses =
Expand Down
Loading