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
Binary file added .github/screenshots/01-landing-page.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added .github/screenshots/02-login-page.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added .github/screenshots/03-dashboard.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added .github/screenshots/04-admin-hub.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added .github/screenshots/05-admin-users.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added .github/screenshots/06-user-sessions.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added .github/screenshots/07-keycloak-landing.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added .github/screenshots/08-keycloak-local-login.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added .github/screenshots/09-keycloak-login-form.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added .github/screenshots/12-keycloak-user-dropdown.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added .github/screenshots/13-keycloak-admin-hub.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added .github/screenshots/14-keycloak-admin-users.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -447,3 +447,7 @@ template/SimpleModule.Host/storage/
# Temporary refactor baseline — not committed
baseline/
.claude/settings.local.json

# Verification artifacts
.verify/
.qa/
2 changes: 2 additions & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer.NetTopologySuite" Version="10.0.3" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite.NetTopologySuite" Version="10.0.3" />
<!-- Identity -->
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.3" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="10.0.3" />
<PackageVersion Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="10.0.3" />
<!-- OpenIddict -->
<PackageVersion Include="OpenIddict.AspNetCore" Version="6.3.0" />
Expand Down
3 changes: 3 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,11 @@ COPY modules/Dashboard/src/SimpleModule.Dashboard.Contracts/*.csproj modules/Das
COPY modules/Dashboard/src/SimpleModule.Dashboard/*.csproj modules/Dashboard/src/SimpleModule.Dashboard/
COPY modules/Users/src/SimpleModule.Users.Contracts/*.csproj modules/Users/src/SimpleModule.Users.Contracts/
COPY modules/Users/src/SimpleModule.Users/*.csproj modules/Users/src/SimpleModule.Users/
COPY modules/Identity/src/SimpleModule.Identity.Contracts/*.csproj modules/Identity/src/SimpleModule.Identity.Contracts/
COPY modules/OpenIddict/src/SimpleModule.OpenIddict.Contracts/*.csproj modules/OpenIddict/src/SimpleModule.OpenIddict.Contracts/
COPY modules/OpenIddict/src/SimpleModule.OpenIddict/*.csproj modules/OpenIddict/src/SimpleModule.OpenIddict/
COPY modules/Keycloak/src/SimpleModule.Keycloak.Contracts/*.csproj modules/Keycloak/src/SimpleModule.Keycloak.Contracts/
COPY modules/Keycloak/src/SimpleModule.Keycloak/*.csproj modules/Keycloak/src/SimpleModule.Keycloak/
COPY modules/Permissions/src/SimpleModule.Permissions.Contracts/*.csproj modules/Permissions/src/SimpleModule.Permissions.Contracts/
COPY modules/Permissions/src/SimpleModule.Permissions/*.csproj modules/Permissions/src/SimpleModule.Permissions/
COPY modules/Admin/src/SimpleModule.Admin.Contracts/*.csproj modules/Admin/src/SimpleModule.Admin.Contracts/
Expand Down
3 changes: 3 additions & 0 deletions Dockerfile.worker
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,11 @@ COPY modules/Dashboard/src/SimpleModule.Dashboard.Contracts/*.csproj modules/Das
COPY modules/Dashboard/src/SimpleModule.Dashboard/*.csproj modules/Dashboard/src/SimpleModule.Dashboard/
COPY modules/Users/src/SimpleModule.Users.Contracts/*.csproj modules/Users/src/SimpleModule.Users.Contracts/
COPY modules/Users/src/SimpleModule.Users/*.csproj modules/Users/src/SimpleModule.Users/
COPY modules/Identity/src/SimpleModule.Identity.Contracts/*.csproj modules/Identity/src/SimpleModule.Identity.Contracts/
COPY modules/OpenIddict/src/SimpleModule.OpenIddict.Contracts/*.csproj modules/OpenIddict/src/SimpleModule.OpenIddict.Contracts/
COPY modules/OpenIddict/src/SimpleModule.OpenIddict/*.csproj modules/OpenIddict/src/SimpleModule.OpenIddict/
COPY modules/Keycloak/src/SimpleModule.Keycloak.Contracts/*.csproj modules/Keycloak/src/SimpleModule.Keycloak.Contracts/
COPY modules/Keycloak/src/SimpleModule.Keycloak/*.csproj modules/Keycloak/src/SimpleModule.Keycloak/
COPY modules/Permissions/src/SimpleModule.Permissions.Contracts/*.csproj modules/Permissions/src/SimpleModule.Permissions.Contracts/
COPY modules/Permissions/src/SimpleModule.Permissions/*.csproj modules/Permissions/src/SimpleModule.Permissions/
COPY modules/Admin/src/SimpleModule.Admin.Contracts/*.csproj modules/Admin/src/SimpleModule.Admin.Contracts/
Expand Down
43 changes: 41 additions & 2 deletions SimpleModule.AppHost/AppHost.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
var builder = DistributedApplication.CreateBuilder(args);
var builder = DistributedApplication.CreateBuilder(args);

var postgres = builder
.AddPostgres("postgres")
Expand All @@ -8,12 +8,51 @@

var db = postgres.AddDatabase("simplemoduledb");

builder
// Keycloak identity provider (opt-in via --launch-profile Keycloak)
var useKeycloak = builder.Configuration["Identity:Provider"] == "Keycloak";

IResourceBuilder<ContainerResource>? keycloak = null;
if (useKeycloak)
{
var realmImportPath = Path.Combine(builder.AppHostDirectory, "keycloak");

keycloak = builder
.AddContainer("keycloak", "quay.io/keycloak/keycloak", "26.2")
.WithHttpEndpoint(port: 8080, targetPort: 8080, name: "http")
.WithEnvironment("KC_BOOTSTRAP_ADMIN_USERNAME", "admin")
.WithEnvironment("KC_BOOTSTRAP_ADMIN_PASSWORD", "admin")
.WithEnvironment("KC_HTTP_ENABLED", "true")
.WithEnvironment("KC_HOSTNAME_STRICT", "false")
.WithEnvironment("KC_HEALTH_ENABLED", "true")
.WithBindMount(realmImportPath, "/opt/keycloak/data/import", isReadOnly: true)
.WithArgs("start-dev", "--import-realm")
.WithLifetime(ContainerLifetime.Persistent);
}

var host = builder
.AddProject<Projects.SimpleModule_Host>("simplemodule-host")
.WithExternalHttpEndpoints()
.WithReference(db)
.WaitFor(db);

if (keycloak is not null)
{
host.WithReference(keycloak.GetEndpoint("http"))
.WaitFor(keycloak)
.WithEnvironment("Identity__Provider", "Keycloak")
.WithEnvironment("Keycloak__Authority", "http://localhost:8080/realms/simplemodule")
.WithEnvironment("Keycloak__ClientId", "simplemodule-app")
.WithEnvironment("Keycloak__ClientSecret", "simplemodule-dev-secret")
.WithEnvironment("Keycloak__Realm", "simplemodule")
.WithEnvironment(
"Keycloak__AdminApiBaseUrl",
"http://localhost:8080/admin/realms/simplemodule"
)
.WithEnvironment("Keycloak__AdminClientId", "simplemodule-admin")
.WithEnvironment("Keycloak__AdminClientSecret", "simplemodule-admin-secret")
.WithEnvironment("Keycloak__RequireHttpsMetadata", "false");
}

builder
.AddProject<Projects.SimpleModule_Worker>("simplemodule-worker")
.WithReference(db)
Expand Down
14 changes: 14 additions & 0 deletions SimpleModule.AppHost/Properties/launchSettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,20 @@
"ASPIRE_DASHBOARD_MCP_ENDPOINT_URL": "http://localhost:18194",
"ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20023"
}
},
"keycloak": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:17119;http://localhost:15076",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"DOTNET_ENVIRONMENT": "Development",
"ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21159",
"ASPIRE_DASHBOARD_MCP_ENDPOINT_URL": "https://localhost:23210",
"ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22061",
"Identity__Provider": "Keycloak"
}
}
}
}
120 changes: 120 additions & 0 deletions SimpleModule.AppHost/keycloak/simplemodule-realm.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
{
"realm": "simplemodule",
"enabled": true,
"registrationAllowed": false,
"loginWithEmailAllowed": true,
"duplicateEmailsAllowed": false,
"sslRequired": "none",
"roles": {
"realm": [
{
"name": "Admin",
"description": "Full administrative access"
},
{
"name": "User",
"description": "Standard user access"
}
]
},
"clients": [
{
"clientId": "simplemodule-app",
"name": "SimpleModule Application",
"enabled": true,
"publicClient": false,
"secret": "simplemodule-dev-secret",
"redirectUris": [
"https://localhost:5001/keycloak/callback",
"http://localhost:5000/keycloak/callback",
"https://localhost:*/keycloak/callback",
"http://localhost:*/keycloak/callback"
],
"webOrigins": ["*"],
"standardFlowEnabled": true,
"directAccessGrantsEnabled": true,
"serviceAccountsEnabled": true,
"authorizationServicesEnabled": false,
"protocol": "openid-connect",
"attributes": {
"pkce.code.challenge.method": "S256",
"post.logout.redirect.uris": "+"
},
"defaultClientScopes": [
"web-origins",
"acr",
"profile",
"roles",
"email"
],
"protocolMappers": [
{
"name": "realm-roles",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-realm-role-mapper",
"consentRequired": false,
"config": {
"multivalued": "true",
"userinfo.token.claim": "true",
"id.token.claim": "true",
"access.token.claim": "true",
"claim.name": "realm_access.roles",
"jsonType.label": "String"
}
}
]
},
{
"clientId": "simplemodule-admin",
"name": "SimpleModule Admin Service Account",
"enabled": true,
"publicClient": false,
"secret": "simplemodule-admin-secret",
"serviceAccountsEnabled": true,
"standardFlowEnabled": false,
"directAccessGrantsEnabled": false,
"protocol": "openid-connect"
}
],
"users": [
{
"username": "admin@simplemodule.dev",
"email": "admin@simplemodule.dev",
"emailVerified": true,
"enabled": true,
"firstName": "Admin",
"lastName": "User",
"credentials": [
{
"type": "password",
"value": "Admin123!",
"temporary": false
}
],
"realmRoles": ["Admin", "User"]
},
{
"username": "user@simplemodule.dev",
"email": "user@simplemodule.dev",
"emailVerified": true,
"enabled": true,
"firstName": "Test",
"lastName": "User",
"credentials": [
{
"type": "password",
"value": "User123!",
"temporary": false
}
],
"realmRoles": ["User"]
}
],
"defaultDefaultClientScopes": [
"web-origins",
"acr",
"profile",
"roles",
"email"
]
}
7 changes: 7 additions & 0 deletions SimpleModule.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,18 @@
<Project Path="modules/Permissions/src/SimpleModule.Permissions/SimpleModule.Permissions.csproj" />
<Project Path="modules/Permissions/tests/SimpleModule.Permissions.Tests/SimpleModule.Permissions.Tests.csproj" />
</Folder>
<Folder Name="/modules/Identity/">
<Project Path="modules/Identity/src/SimpleModule.Identity.Contracts/SimpleModule.Identity.Contracts.csproj" />
</Folder>
<Folder Name="/modules/OpenIddict/">
<Project Path="modules/OpenIddict/src/SimpleModule.OpenIddict.Contracts/SimpleModule.OpenIddict.Contracts.csproj" />
<Project Path="modules/OpenIddict/src/SimpleModule.OpenIddict/SimpleModule.OpenIddict.csproj" />
<Project Path="modules/OpenIddict/tests/SimpleModule.OpenIddict.Tests/SimpleModule.OpenIddict.Tests.csproj" />
</Folder>
<Folder Name="/modules/Keycloak/">
<Project Path="modules/Keycloak/src/SimpleModule.Keycloak.Contracts/SimpleModule.Keycloak.Contracts.csproj" />
<Project Path="modules/Keycloak/src/SimpleModule.Keycloak/SimpleModule.Keycloak.csproj" />
</Folder>
<Folder Name="/modules/Admin/">
<Project Path="modules/Admin/src/SimpleModule.Admin.Contracts/SimpleModule.Admin.Contracts.csproj" />
<Project Path="modules/Admin/src/SimpleModule.Admin/SimpleModule.Admin.csproj" />
Expand Down
Loading
Loading