From e6fa47105a29331b31199b172d1a2e02f47c7ebe Mon Sep 17 00:00:00 2001 From: "Leo A. D'Angelo" Date: Sun, 6 Jul 2025 00:17:39 -0500 Subject: [PATCH] Moved from Grains namespace too Aggregates namespace --- .claude/settings.local.json | 18 + .git-branches.toml | 27 + .github/workflows/dotnet.yml | 4 +- .projectile-cache.eld | 1 + CLAUDE.md | 2 + ...{realm-export-4.json => realm-export.json} | 1693 +++++++++-------- shared-types/Types/Address.cs | 34 +- shared-types/Types/User.cs | 18 +- shared-types/Types/UserPreferences.cs | 15 + .../Aggregates/Users/UserProfileTests.cs | 209 ++ .../Aggregates/Users/Commands/UserCommands.cs | 66 + .../Aggregates/Users/Events/UserEvents.cs | 27 + .../Aggregates/Users/UserHandler.cs | 36 + .../Aggregates/Users/UserProjection.cs | 45 + src/EventServer/Controllers/UserController.cs | 54 + .../Pages/UserProfile.razor | 349 ++++ .../FxExpert.Blazor.Client/Program.cs | 17 +- .../Services/UserService.cs | 133 ++ .../FxExpert.Blazor.Client/_Imports.razor | 2 + .../FxExpert.Blazor/FxExpert.Blazor.csproj | 27 +- .../FxExpert.Blazor/Program.cs | 393 ++-- .../appsettings.Development.json | 9 +- 22 files changed, 2209 insertions(+), 970 deletions(-) create mode 100644 .claude/settings.local.json create mode 100644 .git-branches.toml create mode 100644 .projectile-cache.eld rename docker/keycloak/{realm-export-4.json => realm-export.json} (74%) create mode 100644 shared-types/Types/UserPreferences.cs create mode 100644 src/EventServer.Tests/Aggregates/Users/UserProfileTests.cs create mode 100644 src/FxExpert.Blazor/FxExpert.Blazor.Client/Pages/UserProfile.razor create mode 100644 src/FxExpert.Blazor/FxExpert.Blazor.Client/Services/UserService.cs diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..b5bcca4 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,18 @@ +{ + "permissions": { + "allow": [ + "Bash(dotnet add:*)", + "Bash(dotnet build)", + "Bash(dotnet list package:*)", + "Bash(git add:*)", + "Bash(git commit:*)", + "mcp__node__create_comment", + "mcp__node__create_issue", + "mcp__node__get_issue", + "mcp__node__get_projects", + "mcp__node__search_issues", + "mcp__node__update_issue" + ], + "deny": [] + } +} \ No newline at end of file diff --git a/.git-branches.toml b/.git-branches.toml new file mode 100644 index 0000000..02675a4 --- /dev/null +++ b/.git-branches.toml @@ -0,0 +1,27 @@ +# More info around this file at https://www.git-town.com/configuration-file + +[branches] +main = "main" +perennials = [] +perennial-regex = "" + +[create] +new-branch-type = "feature" +share-new-branches = "push" + +[hosting] +dev-remote = "origin" +# forge-type = "" +# origin-hostname = "" + +[ship] +delete-tracking-branch = true +strategy = "squash-merge" + +[sync] +feature-strategy = "compress" +perennial-strategy = "rebase" +prototype-strategy = "compress" +push-hook = true +tags = true +upstream = true diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 07c6d01..2dc991d 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -25,5 +25,5 @@ jobs: run: dotnet restore - name: Build run: dotnet build --no-restore - - name: Test - run: dotnet test --no-build --verbosity normal +# - name: Test +#-- run: dotnet test --no-build --verbosity normal diff --git a/.projectile-cache.eld b/.projectile-cache.eld new file mode 100644 index 0000000..7046869 --- /dev/null +++ b/.projectile-cache.eld @@ -0,0 +1 @@ +("src/FxExpert.Blazor/FxExpert.Blazor.Client/App.razor" ".dir-locals.el" ".dockerenv" ".dockerignore" ".github/workflows/dotnet.yml" ".gitignore" "CLAUDE.md" "CONVENTIONS.md" "Justfile" "MeetingDetails.cshtml" "Properties/launchSettings.json" "Readme.org" "apm-server.docker.yml" "collector-config.yaml" "devbox.json" "diagrams/README.md" "diagrams/aggregates-flow.md" "diagrams/cqrs-flow.md" "diagrams/process-flow-diagram.mermaid" "diagrams/system-flow.md" "diagrams/wireframes/booking-interface-wireframe.svg" "diagrams/wireframes/homepage-wireframe.svg" "diagrams/wireframes/partner-selection-wireframe.svg" "diagrams/wireframes/payment-interface-wireframe.svg" "docker/grafana/README.md" "docker/grafana/grafana.ini" "docker/grafana/provisioning/alerting/alerts.yml" "docker/grafana/provisioning/dashboards/dashboard.yml" "docker/grafana/provisioning/dashboards/jvm-micrometer_rev9.json" "docker/grafana/provisioning/dashboards/microservices-spring-boot-2-1_rev1.json" "docker/grafana/provisioning/dashboards/prometheus-stats_rev2.json" "docker/grafana/provisioning/dashboards/spring-boot-hikaricp-jdbc_rev5.json" "docker/grafana/provisioning/dashboards/tea-api.json" "docker/grafana/provisioning/datasources/datasource.yml" "docker/grafana/tempo.yml" "docker/keycloak/realm-export-4.json" "docker/mysql/initdb/init.sql" "docker/postgres/initdb/init.sql" "docker/prometheus/prometheus.yml" "docker/toxiproxy/toxiproxy.json" "docker-compose.yml" "fx-orleans.sln" "infrastructure/k8s/Justfile" "infrastructure/k8s/elastic/crds.yaml" "infrastructure/k8s/elastic/kibana.yaml" "infrastructure/k8s/elastic/operator.yaml" "infrastructure/k8s/jaeger/jaeger-operator.yaml" "infrastructure/k8s/keycloak/keycloak-deployment.yaml" "infrastructure/k8s/keycloak/keycloak-pv.yaml" "infrastructure/k8s/keycloak/keycloak-pvc.yaml" "infrastructure/k8s/keycloak/keycloak-secrets.yaml" "infrastructure/k8s/keycloak/keycloak-service.yaml" "infrastructure/k8s/keycloak/keycloak-storageclass.yaml" "keycloak-extension/src/EmailDomainMapper.java" "process-compose.yaml" "pulumi/Program.cs" "repomix-output.txt" "shared-types/Types/Address.cs" "shared-types/Types/CalendarEvent.cs" "shared-types/Types/DollarAmount.cs" "shared-types/Types/Partner.cs" "shared-types/Types/PartnerSkill.cs" "shared-types/Types/Payment.cs" "shared-types/Types/User.cs" "shared-types/Types/VideoConference.cs" "shared-types/Types/WorkHistory.cs" "shared-types/shared-types.csproj" "src/EventServer/Aggregates/Calendar/CalendarEventProjection.cs" "src/EventServer/Aggregates/Calendar/Commands/CalendarCommands.cs" "src/EventServer/Aggregates/Calendar/Events/CalendarEvents.cs" "src/EventServer/Aggregates/Partners/Commands/PartnerCommands.cs" "src/EventServer/Aggregates/Partners/Events/PartnerEvents.cs" "src/EventServer/Aggregates/Partners/PartnerHandler.cs" "src/EventServer/Aggregates/Partners/PartnerProjection.cs" "src/EventServer/Aggregates/Payments/Commands/PaymentCommands.cs" "src/EventServer/Aggregates/Payments/Events/PaymentAuthorizedEvent.cs" "src/EventServer/Aggregates/Payments/Events/PaymentCapturedEvent.cs" "src/EventServer/Aggregates/Payments/PaymentProjection.cs" "src/EventServer/Aggregates/Users/Commands/UserCommands.cs" "src/EventServer/Aggregates/Users/Events/UserEvents.cs" "src/EventServer/Aggregates/Users/UserHandler.cs" "src/EventServer/Aggregates/Users/UserProjection.cs" "src/EventServer/Aggregates/VideoConference/Commands/CreateVideoConferenceCommand.cs" "src/EventServer/Aggregates/VideoConference/Events/VideoConferenceCreatedEvent.cs" "src/EventServer/Aggregates/VideoConference/VideoConferenceProjection.cs" "src/EventServer/Controllers/AIController.cs" "src/EventServer/Controllers/CalendarController.cs" "src/EventServer/Controllers/PartnerController.cs" "src/EventServer/Controllers/PaymentController.cs" "src/EventServer/Controllers/UserController.cs" "src/EventServer/Controllers/VideoConferenceController.cs" "src/EventServer/Dockerfile" "src/EventServer/EventServer.csproj" "src/EventServer/EventServer.http" "src/EventServer/GoogleApiSettings.cs" "src/EventServer/Program.cs" "src/EventServer/Properties/launchSettings.json" "src/EventServer/Services/CalendarService.cs" "src/EventServer/Services/ChatGPTWithRag.cs" "src/EventServer/Services/GoogleLocalServerCodeReceiver.cs" "src/EventServer/Services/IPartnerService.cs" "src/EventServer/Services/IPaymentService.cs" "src/EventServer/Services/PartnerService.cs" "src/EventServer/Services/PaymentService.cs" "src/EventServer/StartUp.cs" "src/EventServer/WeatherForecast.cs" "src/EventServer/appsettings.Development.json" "src/EventServer/appsettings.json" "src/EventServer/swagger.json" "src/EventServer.Tests/AiControllerTests.cs" "src/EventServer.Tests/CalendarControllerTests.cs" "src/EventServer.Tests/EventServer.Tests.csproj" "src/EventServer.Tests/IntegrationContext.cs" "src/EventServer.Tests/PartnerTests.cs" "src/EventServer.Tests/PaymentControllerTests.cs" "src/EventServer.Tests/UserTests.cs" "src/EventServer.Tests/VideoConferenceTests.cs" "src/FxExpert.Blazor/FxExpert.Blazor/Components/Account/AuthorizeRoles.cs" "src/FxExpert.Blazor/FxExpert.Blazor/Components/Account/IdentityComponentsEndpointRouteBuilderExtensions.cs" "src/FxExpert.Blazor/FxExpert.Blazor/Components/Account/IdentityNoOpEmailSender.cs" "src/FxExpert.Blazor/FxExpert.Blazor/Components/Account/IdentityRedirectManager.cs" "src/FxExpert.Blazor/FxExpert.Blazor/Components/Account/IdentityRevalidatingAuthenticationStateProvider.cs" "src/FxExpert.Blazor/FxExpert.Blazor/Components/Account/IdentityUserAccessor.cs" "src/FxExpert.Blazor/FxExpert.Blazor/Components/Account/Pages/AccessDenied.razor" "src/FxExpert.Blazor/FxExpert.Blazor/Components/Account/Pages/ConfirmEmail.razor" "src/FxExpert.Blazor/FxExpert.Blazor/Components/Account/Pages/ConfirmEmailChange.razor" "src/FxExpert.Blazor/FxExpert.Blazor/Components/Account/Pages/ExternalLogin.razor" "src/FxExpert.Blazor/FxExpert.Blazor/Components/Account/Pages/ForgotPassword.razor" "src/FxExpert.Blazor/FxExpert.Blazor/Components/Account/Pages/ForgotPasswordConfirmation.razor" "src/FxExpert.Blazor/FxExpert.Blazor/Components/Account/Pages/InvalidPasswordReset.razor" "src/FxExpert.Blazor/FxExpert.Blazor/Components/Account/Pages/InvalidUser.razor" "src/FxExpert.Blazor/FxExpert.Blazor/Components/Account/Pages/Lockout.razor" "src/FxExpert.Blazor/FxExpert.Blazor/Components/Account/Pages/Login.razor" "src/FxExpert.Blazor/FxExpert.Blazor/Components/Account/Pages/LoginWith2fa.razor" "src/FxExpert.Blazor/FxExpert.Blazor/Components/Account/Pages/LoginWithRecoveryCode.razor" "src/FxExpert.Blazor/FxExpert.Blazor/Components/Account/Pages/Manage/ChangePassword.razor" "src/FxExpert.Blazor/FxExpert.Blazor/Components/Account/Pages/Manage/DeletePersonalData.razor" "src/FxExpert.Blazor/FxExpert.Blazor/Components/Account/Pages/Manage/Disable2fa.razor" "src/FxExpert.Blazor/FxExpert.Blazor/Components/Account/Pages/Manage/Email.razor" "src/FxExpert.Blazor/FxExpert.Blazor/Components/Account/Pages/Manage/EnableAuthenticator.razor" "src/FxExpert.Blazor/FxExpert.Blazor/Components/Account/Pages/Manage/ExternalLogins.razor" "src/FxExpert.Blazor/FxExpert.Blazor/Components/Account/Pages/Manage/GenerateRecoveryCodes.razor" "src/FxExpert.Blazor/FxExpert.Blazor/Components/Account/Pages/Manage/Index.razor" "src/FxExpert.Blazor/FxExpert.Blazor/Components/Account/Pages/Manage/PersonalData.razor" "src/FxExpert.Blazor/FxExpert.Blazor/Components/Account/Pages/Manage/ResetAuthenticator.razor" "src/FxExpert.Blazor/FxExpert.Blazor/Components/Account/Pages/Manage/SetPassword.razor" "src/FxExpert.Blazor/FxExpert.Blazor/Components/Account/Pages/Manage/TwoFactorAuthentication.razor" "src/FxExpert.Blazor/FxExpert.Blazor/Components/Account/Pages/Manage/_Imports.razor" "src/FxExpert.Blazor/FxExpert.Blazor/Components/Account/Pages/Register.razor" "src/FxExpert.Blazor/FxExpert.Blazor/Components/Account/Pages/RegisterConfirmation.razor" "src/FxExpert.Blazor/FxExpert.Blazor/Components/Account/Pages/ResendEmailConfirmation.razor" "src/FxExpert.Blazor/FxExpert.Blazor/Components/Account/Pages/ResetPassword.razor" "src/FxExpert.Blazor/FxExpert.Blazor/Components/Account/Pages/ResetPasswordConfirmation.razor" "src/FxExpert.Blazor/FxExpert.Blazor/Components/Account/Pages/_Imports.razor" "src/FxExpert.Blazor/FxExpert.Blazor/Components/Account/RoleDisplay.razor" "src/FxExpert.Blazor/FxExpert.Blazor/Components/Account/Shared/ExternalLoginPicker.razor" "src/FxExpert.Blazor/FxExpert.Blazor/Components/Account/Shared/ManageLayout.razor" "src/FxExpert.Blazor/FxExpert.Blazor/Components/Account/Shared/ManageNavMenu.razor" "src/FxExpert.Blazor/FxExpert.Blazor/Components/Account/Shared/ShowRecoveryCodes.razor" "src/FxExpert.Blazor/FxExpert.Blazor/Components/Account/Shared/StatusMessage.razor" "src/FxExpert.Blazor/FxExpert.Blazor/Components/App.razor" "src/FxExpert.Blazor/FxExpert.Blazor/Components/Pages/Error.razor" "src/FxExpert.Blazor/FxExpert.Blazor/Components/_Imports.razor" "src/FxExpert.Blazor/FxExpert.Blazor/Data/ApplicationDbContext.cs" "src/FxExpert.Blazor/FxExpert.Blazor/Data/ApplicationUser.cs" "src/FxExpert.Blazor/FxExpert.Blazor/Data/Migrations/00000000000000_CreateIdentitySchema.Designer.cs" "src/FxExpert.Blazor/FxExpert.Blazor/Data/Migrations/00000000000000_CreateIdentitySchema.cs" "src/FxExpert.Blazor/FxExpert.Blazor/Data/Migrations/ApplicationDbContextModelSnapshot.cs" "src/FxExpert.Blazor/FxExpert.Blazor/Data/app.db" "src/FxExpert.Blazor/FxExpert.Blazor/FxExpert.Blazor.csproj" "src/FxExpert.Blazor/FxExpert.Blazor/Program.cs" "src/FxExpert.Blazor/FxExpert.Blazor/Properties/launchSettings.json" "src/FxExpert.Blazor/FxExpert.Blazor/appsettings.Development.json" "src/FxExpert.Blazor/FxExpert.Blazor/appsettings.json" "src/FxExpert.Blazor/FxExpert.Blazor.Client/AuthorizeRoles.cs" "src/FxExpert.Blazor/FxExpert.Blazor.Client/FxExpert.Blazor.Client.csproj" "src/FxExpert.Blazor/FxExpert.Blazor.Client/Layout/MainLayout.razor" "src/FxExpert.Blazor/FxExpert.Blazor.Client/Layout/NavMenu.razor" "src/FxExpert.Blazor/FxExpert.Blazor.Client/Pages/AccessDenied.razor" "src/FxExpert.Blazor/FxExpert.Blazor.Client/Pages/AuthenticationFailed.razor" "src/FxExpert.Blazor/FxExpert.Blazor.Client/Pages/ConfirmationPage.razor" "src/FxExpert.Blazor/FxExpert.Blazor.Client/Pages/Home.razor" "src/FxExpert.Blazor/FxExpert.Blazor.Client/Pages/LoginComponent.razor" "src/FxExpert.Blazor/FxExpert.Blazor.Client/Pages/PartnerCard.razor" "src/FxExpert.Blazor/FxExpert.Blazor.Client/Pages/PartnerInfo.razor" "src/FxExpert.Blazor/FxExpert.Blazor.Client/Program.cs" "src/FxExpert.Blazor/FxExpert.Blazor.Client/RedirectToLogin.razor" "src/FxExpert.Blazor/FxExpert.Blazor.Client/Routes.razor" "src/FxExpert.Blazor/FxExpert.Blazor.Client/_Imports.razor" "src/common/PartnerConnect/IPartnerConnectApi.cs" "src/common/PartnerConnect/PartnerConnectApi.cs" "src/common/PartnerConnect/User.cs" "src/common/ServiceCollectionExtension.cs" "src/common/common.csproj" "src/modules/EventServer.Tests/Aggregates/Partners/PartnerAggregateTests.cs" "src/modules/EventServer.Tests/Aggregates/Users/UserAggregateTests.cs" "src/modules/EventServer.Tests/Aggregates/VideoConference/VideoConferenceAggregateTests.cs" "src/modules/EventServer.Tests/EventServer.Tests.csproj" "src/modules/EventServer.Tests/FxTest.cs" "src/modules/EventServer.Tests/IntegrationContext.cs" "src/modules/EventServer.Tests/PartnerConnectApi/PartnerConnectApiTest.cs" "src/modules/EventServer.Tests/Startup.cs" "src/modules/EventServer.Tests/xunit.runner.json") \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md index 7e1fdb2..4757f25 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -20,6 +20,7 @@ Availability filter to only show partners who can meet soon Display of matched partners with relevance scores Brief profiles highlighting expertise relevant to the stated problem Availability calendar for each partner + ### Booking System Seamless transition to scheduling once a partner is selected @@ -40,6 +41,7 @@ Additionally confirmation e-mails will be sent out from the google meeting reque ### Implementation Approach + For the AI matching component, you could: Use a Large Language Model (LLM) like OpenAI's GPT or similar: diff --git a/docker/keycloak/realm-export-4.json b/docker/keycloak/realm-export.json similarity index 74% rename from docker/keycloak/realm-export-4.json rename to docker/keycloak/realm-export.json index 02240c0..ef6ccd2 100644 --- a/docker/keycloak/realm-export-4.json +++ b/docker/keycloak/realm-export.json @@ -1,6 +1,6 @@ { - "id": "8dedab4a-b50e-4e15-a510-4470d5bfacf0", - "realm": "spring", + "id": "6473cf86-542f-4e9a-8e2b-dabb274a2f52", + "realm": "fx-expert", "notBefore": 0, "defaultSignatureAlgorithm": "RS256", "revokeRefreshToken": false, @@ -14,8 +14,8 @@ "offlineSessionIdleTimeout": 2592000, "offlineSessionMaxLifespanEnabled": false, "offlineSessionMaxLifespan": 5184000, - "clientSessionIdleTimeout": 900, - "clientSessionMaxLifespan": 86400, + "clientSessionIdleTimeout": 0, + "clientSessionMaxLifespan": 0, "clientOfflineSessionIdleTimeout": 0, "clientOfflineSessionMaxLifespan": 0, "accessCodeLifespan": 60, @@ -38,6 +38,7 @@ "bruteForceProtected": false, "permanentLockout": false, "maxTemporaryLockouts": 0, + "bruteForceStrategy": "MULTIPLE", "maxFailureWaitSeconds": 900, "minimumQuickLoginWaitSeconds": 60, "waitIncrementSeconds": 60, @@ -47,403 +48,402 @@ "roles": { "realm": [ { - "id": "f18e10e7-ecca-4218-b0dc-861aab2d2a74", - "name": "default-roles-spring", - "description": "${role_default-roles}", + "id": "11dcc9be-9d57-4f67-b175-2b7c84ab8d28", + "name": "Partner", + "description": "Partner role", + "composite": false, + "clientRole": false, + "containerId": "6473cf86-542f-4e9a-8e2b-dabb274a2f52", + "attributes": {} + }, + { + "id": "3f010362-4aba-4a58-b724-68713dcbfd51", + "name": "User", + "description": "User role", "composite": true, "composites": { - "realm": [ - "offline_access", - "uma_authorization" - ], "client": { - "account": [ - "view-profile", - "manage-account" - ] + "fx-expert": ["User"] } }, "clientRole": false, - "containerId": "8dedab4a-b50e-4e15-a510-4470d5bfacf0", - "attributes": {} - }, - { - "id": "3329159e-ef8d-4f1b-9ecc-25c3b142ae07", - "name": "uma_authorization", - "description": "${role_uma_authorization}", - "composite": false, - "clientRole": false, - "containerId": "8dedab4a-b50e-4e15-a510-4470d5bfacf0", + "containerId": "6473cf86-542f-4e9a-8e2b-dabb274a2f52", "attributes": {} }, { - "id": "a8b463d4-abdb-4a28-b647-406031134676", - "name": "admin", - "description": "", - "composite": false, + "id": "f32fc9b3-0012-4ed5-8fb6-c0a88575ecfb", + "name": "default-roles-fx-expert", + "description": "${role_default-roles}", + "composite": true, + "composites": { + "realm": ["offline_access", "uma_authorization"], + "client": { + "account": ["view-profile", "manage-account"] + } + }, "clientRole": false, - "containerId": "8dedab4a-b50e-4e15-a510-4470d5bfacf0", + "containerId": "6473cf86-542f-4e9a-8e2b-dabb274a2f52", "attributes": {} }, { - "id": "f3b31ce5-7549-4bb6-8fa0-f902c7ec84c9", - "name": "user", - "description": "", + "id": "ca438a37-5567-4f2d-a71d-a4c1c7cd1b6d", + "name": "uma_authorization", + "description": "${role_uma_authorization}", "composite": false, "clientRole": false, - "containerId": "8dedab4a-b50e-4e15-a510-4470d5bfacf0", + "containerId": "6473cf86-542f-4e9a-8e2b-dabb274a2f52", "attributes": {} }, { - "id": "4ebaf157-4e26-47e0-8d16-04b194f450e8", + "id": "90201c85-6122-4110-9083-25f62ec98ccb", "name": "offline_access", "description": "${role_offline-access}", "composite": false, "clientRole": false, - "containerId": "8dedab4a-b50e-4e15-a510-4470d5bfacf0", + "containerId": "6473cf86-542f-4e9a-8e2b-dabb274a2f52", "attributes": {} } ], "client": { "realm-management": [ { - "id": "7afd3f29-c040-4dbf-92e5-d8311cd1dc88", - "name": "query-groups", - "description": "${role_query-groups}", - "composite": false, - "clientRole": true, - "containerId": "aa876965-bdfe-4d5e-9a4f-070287477513", - "attributes": {} - }, - { - "id": "c774ce72-535e-4345-a4e2-5fbfbbe59e38", + "id": "3d5f0961-6486-4cd1-9aec-66e2a2ba0274", "name": "view-authorization", "description": "${role_view-authorization}", "composite": false, "clientRole": true, - "containerId": "aa876965-bdfe-4d5e-9a4f-070287477513", + "containerId": "d7bdc0db-b2fc-4867-a6eb-dd902ef0b85c", "attributes": {} }, { - "id": "a6f1706a-e46b-4698-89e5-f170033b98c6", - "name": "manage-realm", - "description": "${role_manage-realm}", + "id": "2e612fb7-5b24-42ce-b535-8dc72ff23ddb", + "name": "query-users", + "description": "${role_query-users}", "composite": false, "clientRole": true, - "containerId": "aa876965-bdfe-4d5e-9a4f-070287477513", + "containerId": "d7bdc0db-b2fc-4867-a6eb-dd902ef0b85c", "attributes": {} }, { - "id": "cf8cd5ad-4497-4e7e-832b-85aefc58e3bc", - "name": "view-clients", - "description": "${role_view-clients}", + "id": "52731858-4ab2-448c-93b1-fde940f61c2b", + "name": "realm-admin", + "description": "${role_realm-admin}", "composite": true, "composites": { "client": { "realm-management": [ - "query-clients" + "view-authorization", + "query-users", + "view-identity-providers", + "manage-identity-providers", + "view-realm", + "manage-authorization", + "manage-events", + "query-realms", + "impersonation", + "view-events", + "query-groups", + "manage-clients", + "view-users", + "manage-users", + "manage-realm", + "create-client", + "query-clients", + "view-clients" ] } }, "clientRole": true, - "containerId": "aa876965-bdfe-4d5e-9a4f-070287477513", + "containerId": "d7bdc0db-b2fc-4867-a6eb-dd902ef0b85c", "attributes": {} }, { - "id": "3e5d7c2d-51e7-4cbf-9045-bd9b1b185e9b", - "name": "view-realm", - "description": "${role_view-realm}", + "id": "667cf388-e805-475b-8efd-c2b37f331687", + "name": "view-identity-providers", + "description": "${role_view-identity-providers}", "composite": false, "clientRole": true, - "containerId": "aa876965-bdfe-4d5e-9a4f-070287477513", + "containerId": "d7bdc0db-b2fc-4867-a6eb-dd902ef0b85c", "attributes": {} }, { - "id": "7c1ff2f9-e927-47ec-b721-24dcaf1e5794", - "name": "query-realms", - "description": "${role_query-realms}", + "id": "ddadd8e3-22db-42a1-8049-39e5a5ccf70b", + "name": "manage-identity-providers", + "description": "${role_manage-identity-providers}", "composite": false, "clientRole": true, - "containerId": "aa876965-bdfe-4d5e-9a4f-070287477513", + "containerId": "d7bdc0db-b2fc-4867-a6eb-dd902ef0b85c", "attributes": {} }, { - "id": "09f718c9-d48d-4527-9e7e-412d7a5ff0e4", - "name": "realm-admin", - "description": "${role_realm-admin}", - "composite": true, - "composites": { - "client": { - "realm-management": [ - "query-groups", - "view-authorization", - "manage-realm", - "view-realm", - "view-clients", - "query-realms", - "manage-clients", - "manage-users", - "impersonation", - "view-events", - "manage-identity-providers", - "manage-events", - "view-users", - "view-identity-providers", - "query-clients", - "manage-authorization", - "query-users", - "create-client" - ] - } - }, + "id": "3b0da856-8627-4ed0-918d-9971d9ba560f", + "name": "view-realm", + "description": "${role_view-realm}", + "composite": false, "clientRole": true, - "containerId": "aa876965-bdfe-4d5e-9a4f-070287477513", + "containerId": "d7bdc0db-b2fc-4867-a6eb-dd902ef0b85c", "attributes": {} }, { - "id": "4246b8b5-2aaa-40bb-816f-260d45a0c99c", - "name": "manage-clients", - "description": "${role_manage-clients}", + "id": "fc5ad30c-eb85-4e33-801d-144c17561681", + "name": "manage-authorization", + "description": "${role_manage-authorization}", "composite": false, "clientRole": true, - "containerId": "aa876965-bdfe-4d5e-9a4f-070287477513", + "containerId": "d7bdc0db-b2fc-4867-a6eb-dd902ef0b85c", "attributes": {} }, { - "id": "6e70c692-7f82-47af-b535-544bdc76c1a9", - "name": "manage-users", - "description": "${role_manage-users}", + "id": "8f702f5b-b038-4fa2-9d04-bba51f4fcde9", + "name": "manage-events", + "description": "${role_manage-events}", + "composite": false, + "clientRole": true, + "containerId": "d7bdc0db-b2fc-4867-a6eb-dd902ef0b85c", + "attributes": {} + }, + { + "id": "d489b8bc-5b8c-490d-b65e-e8f3a170a162", + "name": "query-realms", + "description": "${role_query-realms}", "composite": false, "clientRole": true, - "containerId": "aa876965-bdfe-4d5e-9a4f-070287477513", + "containerId": "d7bdc0db-b2fc-4867-a6eb-dd902ef0b85c", "attributes": {} }, { - "id": "765073b4-49fd-4d28-b2b0-1c1006659eeb", + "id": "942e6723-779b-4b44-8480-8e37817bbdb0", "name": "impersonation", "description": "${role_impersonation}", "composite": false, "clientRole": true, - "containerId": "aa876965-bdfe-4d5e-9a4f-070287477513", + "containerId": "d7bdc0db-b2fc-4867-a6eb-dd902ef0b85c", "attributes": {} }, { - "id": "927d44bb-dd82-4a80-8775-fead551cd36f", - "name": "view-events", - "description": "${role_view-events}", + "id": "ff01ac89-6fea-40a5-8c9f-8ceb19337e07", + "name": "query-groups", + "description": "${role_query-groups}", "composite": false, "clientRole": true, - "containerId": "aa876965-bdfe-4d5e-9a4f-070287477513", + "containerId": "d7bdc0db-b2fc-4867-a6eb-dd902ef0b85c", "attributes": {} }, { - "id": "22c3b5a9-4e74-444e-8d52-b65d47be5b21", - "name": "manage-identity-providers", - "description": "${role_manage-identity-providers}", + "id": "e02bc8e1-f7bf-413f-a935-a403a6bdb98d", + "name": "view-events", + "description": "${role_view-events}", "composite": false, "clientRole": true, - "containerId": "aa876965-bdfe-4d5e-9a4f-070287477513", + "containerId": "d7bdc0db-b2fc-4867-a6eb-dd902ef0b85c", "attributes": {} }, { - "id": "9ac81e16-437e-4892-96cb-a1f0dcad9feb", - "name": "manage-events", - "description": "${role_manage-events}", + "id": "ce6e6bdb-6286-4a39-aad4-b2b3af294894", + "name": "manage-clients", + "description": "${role_manage-clients}", "composite": false, "clientRole": true, - "containerId": "aa876965-bdfe-4d5e-9a4f-070287477513", + "containerId": "d7bdc0db-b2fc-4867-a6eb-dd902ef0b85c", "attributes": {} }, { - "id": "f9e29b5b-1a99-47aa-a6ed-ca88c15cd9d2", + "id": "2cfacc3a-bbe6-4cc3-8cbf-d802558e9551", "name": "view-users", "description": "${role_view-users}", "composite": true, "composites": { "client": { - "realm-management": [ - "query-groups", - "query-users" - ] + "realm-management": ["query-groups", "query-users"] } }, "clientRole": true, - "containerId": "aa876965-bdfe-4d5e-9a4f-070287477513", + "containerId": "d7bdc0db-b2fc-4867-a6eb-dd902ef0b85c", "attributes": {} }, { - "id": "fcdbbebd-cb77-4094-a690-803633dead63", - "name": "query-clients", - "description": "${role_query-clients}", + "id": "70007267-ac8d-48ca-b255-4ec37979db25", + "name": "manage-users", + "description": "${role_manage-users}", "composite": false, "clientRole": true, - "containerId": "aa876965-bdfe-4d5e-9a4f-070287477513", + "containerId": "d7bdc0db-b2fc-4867-a6eb-dd902ef0b85c", "attributes": {} }, { - "id": "41f84014-7b5a-4864-9213-de1353dd38d1", - "name": "view-identity-providers", - "description": "${role_view-identity-providers}", + "id": "a0df0f3e-b848-48df-92a9-f040e0edf9da", + "name": "manage-realm", + "description": "${role_manage-realm}", "composite": false, "clientRole": true, - "containerId": "aa876965-bdfe-4d5e-9a4f-070287477513", + "containerId": "d7bdc0db-b2fc-4867-a6eb-dd902ef0b85c", "attributes": {} }, { - "id": "10261a9e-6821-486f-ad69-0a35f40864e4", - "name": "manage-authorization", - "description": "${role_manage-authorization}", + "id": "9a3cd222-c3a0-4bb1-8afd-d2a1da9c9375", + "name": "create-client", + "description": "${role_create-client}", "composite": false, "clientRole": true, - "containerId": "aa876965-bdfe-4d5e-9a4f-070287477513", + "containerId": "d7bdc0db-b2fc-4867-a6eb-dd902ef0b85c", "attributes": {} }, { - "id": "522d0803-b997-4bab-a1a3-4131cd6ac983", - "name": "query-users", - "description": "${role_query-users}", + "id": "9ecb57e1-70d9-49e0-aafa-49429f169d88", + "name": "query-clients", + "description": "${role_query-clients}", "composite": false, "clientRole": true, - "containerId": "aa876965-bdfe-4d5e-9a4f-070287477513", + "containerId": "d7bdc0db-b2fc-4867-a6eb-dd902ef0b85c", "attributes": {} }, { - "id": "8c5edeb4-ff9b-45ed-ac2d-874ab0c55d6c", - "name": "create-client", - "description": "${role_create-client}", - "composite": false, + "id": "15392082-2547-46dc-8e3d-3f42a6e961e0", + "name": "view-clients", + "description": "${role_view-clients}", + "composite": true, + "composites": { + "client": { + "realm-management": ["query-clients"] + } + }, "clientRole": true, - "containerId": "aa876965-bdfe-4d5e-9a4f-070287477513", + "containerId": "d7bdc0db-b2fc-4867-a6eb-dd902ef0b85c", "attributes": {} } ], - "security-playground": [ + "security-admin-console": [], + "fx-expert": [ { - "id": "57e49b87-75bd-4e22-a308-4e6de497b77e", - "name": "user", - "description": "", + "id": "ee65acd7-0c0e-40ff-b691-40725d5513a4", + "name": "User", + "description": "Role for generic user.", "composite": false, "clientRole": true, - "containerId": "ecdb084e-47bf-4647-a65b-3780d2bdaf2f", + "containerId": "852a5a02-ffb8-48c3-abcd-70be2ca17e71", "attributes": {} }, { - "id": "f6ec51ab-7958-4775-93db-b7674ea915a3", - "name": "admin", - "description": "", + "id": "9d4c11e1-c514-4fb9-8f83-2ca71629ddbf", + "name": "Managing Partner", + "description": "Role for managing partners", "composite": false, "clientRole": true, - "containerId": "ecdb084e-47bf-4647-a65b-3780d2bdaf2f", + "containerId": "852a5a02-ffb8-48c3-abcd-70be2ca17e71", "attributes": {} }, { - "id": "55a04766-ad0e-4498-bbfe-0190688181d9", + "id": "e236f86c-e4f3-48b1-bbea-162c82b25b18", "name": "uma_protection", "composite": false, "clientRole": true, - "containerId": "ecdb084e-47bf-4647-a65b-3780d2bdaf2f", + "containerId": "852a5a02-ffb8-48c3-abcd-70be2ca17e71", + "attributes": {} + }, + { + "id": "5a419112-9e13-4eb2-a698-98cd1b90f25a", + "name": "Partner", + "description": "Role for Fortium Partners", + "composite": false, + "clientRole": true, + "containerId": "852a5a02-ffb8-48c3-abcd-70be2ca17e71", "attributes": {} } ], - "security-admin-console": [], "admin-cli": [], "account-console": [], "broker": [ { - "id": "0ff32ec7-9450-417d-80f4-e56ee7235994", + "id": "f401b37d-a435-46d4-b7c5-735c519b168b", "name": "read-token", "description": "${role_read-token}", "composite": false, "clientRole": true, - "containerId": "893b5acb-9394-4824-a964-0efcc2dc4dee", + "containerId": "babf4469-deff-43eb-bf8e-0e18ba0ba395", "attributes": {} } ], "account": [ { - "id": "68eed752-420f-42db-9ecc-afdae33acef6", - "name": "manage-account-links", - "description": "${role_manage-account-links}", - "composite": false, - "clientRole": true, - "containerId": "969f3c46-a479-4f4e-b092-9cca18e3fd14", - "attributes": {} - }, - { - "id": "e229cce9-91b8-499a-9ffc-70c15efa53ca", - "name": "view-groups", - "description": "${role_view-groups}", + "id": "4e85e80d-35f0-48d8-8346-cc8a4141a996", + "name": "view-applications", + "description": "${role_view-applications}", "composite": false, "clientRole": true, - "containerId": "969f3c46-a479-4f4e-b092-9cca18e3fd14", + "containerId": "e8c95ab8-0ee0-4962-a9d6-a791cbc871e9", "attributes": {} }, { - "id": "9bd23013-b9fe-4818-82d4-3c55ee168c25", + "id": "618c1212-1970-49a9-93d4-53b79700fc4a", "name": "view-profile", "description": "${role_view-profile}", "composite": false, "clientRole": true, - "containerId": "969f3c46-a479-4f4e-b092-9cca18e3fd14", + "containerId": "e8c95ab8-0ee0-4962-a9d6-a791cbc871e9", "attributes": {} }, { - "id": "65de784e-aa87-40e6-9692-76cd875da9d2", - "name": "manage-account", - "description": "${role_manage-account}", + "id": "f34d3266-5d5f-4c0a-acd1-40560fa9ee67", + "name": "manage-consent", + "description": "${role_manage-consent}", "composite": true, "composites": { "client": { - "account": [ - "manage-account-links" - ] + "account": ["view-consent"] } }, "clientRole": true, - "containerId": "969f3c46-a479-4f4e-b092-9cca18e3fd14", + "containerId": "e8c95ab8-0ee0-4962-a9d6-a791cbc871e9", "attributes": {} }, { - "id": "f4dd624c-4fff-4d1b-82db-e73d4eae13d0", + "id": "9cb9ae7c-a08e-48ef-9d67-1f4d221bcef6", "name": "view-consent", "description": "${role_view-consent}", "composite": false, "clientRole": true, - "containerId": "969f3c46-a479-4f4e-b092-9cca18e3fd14", + "containerId": "e8c95ab8-0ee0-4962-a9d6-a791cbc871e9", "attributes": {} }, { - "id": "b8e6fd99-2b95-4334-a723-89ad1a9adad8", - "name": "delete-account", - "description": "${role_delete-account}", + "id": "d5a1045c-3f35-4cf7-946f-b24337b6481c", + "name": "manage-account-links", + "description": "${role_manage-account-links}", "composite": false, "clientRole": true, - "containerId": "969f3c46-a479-4f4e-b092-9cca18e3fd14", + "containerId": "e8c95ab8-0ee0-4962-a9d6-a791cbc871e9", "attributes": {} }, { - "id": "df0beec5-8565-4d99-8e23-6cf23153fee3", - "name": "manage-consent", - "description": "${role_manage-consent}", + "id": "6ab2848b-686e-40a4-a415-7ee3dc700c5f", + "name": "manage-account", + "description": "${role_manage-account}", "composite": true, "composites": { "client": { - "account": [ - "view-consent" - ] + "account": ["manage-account-links"] } }, "clientRole": true, - "containerId": "969f3c46-a479-4f4e-b092-9cca18e3fd14", + "containerId": "e8c95ab8-0ee0-4962-a9d6-a791cbc871e9", "attributes": {} }, { - "id": "43c79b94-0cf6-42d2-a39d-aab6da346d56", - "name": "view-applications", - "description": "${role_view-applications}", + "id": "2d4cb469-5491-4d4b-8225-40754d404c38", + "name": "delete-account", + "description": "${role_delete-account}", + "composite": false, + "clientRole": true, + "containerId": "e8c95ab8-0ee0-4962-a9d6-a791cbc871e9", + "attributes": {} + }, + { + "id": "a9c5c8ad-c6f6-4cf2-a447-71cb941d6ddb", + "name": "view-groups", + "description": "${role_view-groups}", "composite": false, "clientRole": true, - "containerId": "969f3c46-a479-4f4e-b092-9cca18e3fd14", + "containerId": "e8c95ab8-0ee0-4962-a9d6-a791cbc871e9", "attributes": {} } ] @@ -451,46 +451,33 @@ }, "groups": [ { - "id": "84a4c759-340a-4481-a573-27c4cc7b19b0", - "name": "admins", - "path": "/admins", + "id": "c793ab9a-c271-4cec-9a05-f1ce632c6c80", + "name": "Partner", + "path": "/Partner", "subGroups": [], "attributes": {}, "realmRoles": [], - "clientRoles": { - "realm-management": [ - "realm-admin" - ], - "security-playground": [ - "admin" - ] - } + "clientRoles": {} }, { - "id": "f6b77090-a730-4e31-a9e7-f62a917128ea", - "name": "users", - "path": "/users", + "id": "defaf24c-2ec2-40a7-bd05-d87bc94d69c8", + "name": "User", + "path": "/User", "subGroups": [], "attributes": {}, "realmRoles": [], - "clientRoles": { - "security-playground": [ - "user" - ] - } + "clientRoles": {} } ], "defaultRole": { - "id": "f18e10e7-ecca-4218-b0dc-861aab2d2a74", - "name": "default-roles-spring", + "id": "f32fc9b3-0012-4ed5-8fb6-c0a88575ecfb", + "name": "default-roles-fx-expert", "description": "${role_default-roles}", "composite": true, "clientRole": false, - "containerId": "8dedab4a-b50e-4e15-a510-4470d5bfacf0" + "containerId": "6473cf86-542f-4e9a-8e2b-dabb274a2f52" }, - "requiredCredentials": [ - "password" - ], + "requiredCredentials": ["password"], "otpPolicyType": "totp", "otpPolicyAlgorithm": "HmacSHA1", "otpPolicyInitialCounter": 0, @@ -505,9 +492,7 @@ ], "localizationTexts": {}, "webAuthnPolicyRpEntityName": "keycloak", - "webAuthnPolicySignatureAlgorithms": [ - "ES256" - ], + "webAuthnPolicySignatureAlgorithms": ["ES256", "RS256"], "webAuthnPolicyRpId": "", "webAuthnPolicyAttestationConveyancePreference": "not specified", "webAuthnPolicyAuthenticatorAttachment": "not specified", @@ -518,9 +503,7 @@ "webAuthnPolicyAcceptableAaguids": [], "webAuthnPolicyExtraOrigins": [], "webAuthnPolicyPasswordlessRpEntityName": "keycloak", - "webAuthnPolicyPasswordlessSignatureAlgorithms": [ - "ES256" - ], + "webAuthnPolicyPasswordlessSignatureAlgorithms": ["ES256", "RS256"], "webAuthnPolicyPasswordlessRpId": "", "webAuthnPolicyPasswordlessAttestationConveyancePreference": "not specified", "webAuthnPolicyPasswordlessAuthenticatorAttachment": "not specified", @@ -530,39 +513,51 @@ "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister": false, "webAuthnPolicyPasswordlessAcceptableAaguids": [], "webAuthnPolicyPasswordlessExtraOrigins": [], + "users": [ + { + "id": "df3faa76-c858-430a-b611-709abe93d494", + "username": "service-account-fx-expert", + "emailVerified": false, + "createdTimestamp": 1736864673428, + "enabled": true, + "totp": false, + "serviceAccountClientId": "fx-expert", + "disableableCredentialTypes": [], + "requiredActions": [], + "realmRoles": ["default-roles-fx-expert"], + "clientRoles": { + "fx-expert": ["uma_protection"] + }, + "notBefore": 0, + "groups": [] + } + ], "scopeMappings": [ { "clientScope": "offline_access", - "roles": [ - "offline_access" - ] + "roles": ["offline_access"] } ], "clientScopeMappings": { "account": [ { "client": "account-console", - "roles": [ - "manage-account", - "view-groups" - ] + "roles": ["manage-account", "view-groups"] } ] }, "clients": [ { - "id": "969f3c46-a479-4f4e-b092-9cca18e3fd14", + "id": "e8c95ab8-0ee0-4962-a9d6-a791cbc871e9", "clientId": "account", "name": "${client_account}", "rootUrl": "${authBaseUrl}", - "baseUrl": "/realms/spring/account/", + "baseUrl": "/realms/fx-expert/account/", "surrogateAuthRequired": false, "enabled": true, "alwaysDisplayInConsole": false, "clientAuthenticatorType": "client-secret", - "redirectUris": [ - "/realms/spring/account/*" - ], + "redirectUris": ["/realms/fx-expert/account/*"], "webOrigins": [], "notBefore": 0, "bearerOnly": false, @@ -575,6 +570,7 @@ "frontchannelLogout": false, "protocol": "openid-connect", "attributes": { + "realm_client": "false", "post.logout.redirect.uris": "+" }, "authenticationFlowBindingOverrides": {}, @@ -592,22 +588,21 @@ "address", "phone", "offline_access", + "organization", "microprofile-jwt" ] }, { - "id": "c6f8b6f3-b330-4f1a-ac5c-e2b10c18d74c", + "id": "a3e06419-89ac-4857-b86f-151c242409cb", "clientId": "account-console", "name": "${client_account-console}", "rootUrl": "${authBaseUrl}", - "baseUrl": "/realms/spring/account/", + "baseUrl": "/realms/fx-expert/account/", "surrogateAuthRequired": false, "enabled": true, "alwaysDisplayInConsole": false, "clientAuthenticatorType": "client-secret", - "redirectUris": [ - "/realms/spring/account/*" - ], + "redirectUris": ["/realms/fx-expert/account/*"], "webOrigins": [], "notBefore": 0, "bearerOnly": false, @@ -620,6 +615,7 @@ "frontchannelLogout": false, "protocol": "openid-connect", "attributes": { + "realm_client": "false", "post.logout.redirect.uris": "+", "pkce.code.challenge.method": "S256" }, @@ -628,7 +624,7 @@ "nodeReRegistrationTimeout": 0, "protocolMappers": [ { - "id": "ae54cd2f-e37d-4ad3-b030-ca1345e35123", + "id": "93a7dae5-378a-410b-9d6e-b86f3420037e", "name": "audience resolve", "protocol": "openid-connect", "protocolMapper": "oidc-audience-resolve-mapper", @@ -648,11 +644,12 @@ "address", "phone", "offline_access", + "organization", "microprofile-jwt" ] }, { - "id": "836cbec5-8291-40ec-b502-f1a4ac3b993e", + "id": "096f0c33-dd8b-4202-b4b5-2289df0eb102", "clientId": "admin-cli", "name": "${client_admin-cli}", "surrogateAuthRequired": false, @@ -671,9 +668,12 @@ "publicClient": true, "frontchannelLogout": false, "protocol": "openid-connect", - "attributes": {}, + "attributes": { + "realm_client": "false", + "client.use.lightweight.access.token.enabled": "true" + }, "authenticationFlowBindingOverrides": {}, - "fullScopeAllowed": false, + "fullScopeAllowed": true, "nodeReRegistrationTimeout": 0, "defaultClientScopes": [ "web-origins", @@ -687,11 +687,12 @@ "address", "phone", "offline_access", + "organization", "microprofile-jwt" ] }, { - "id": "893b5acb-9394-4824-a964-0efcc2dc4dee", + "id": "babf4469-deff-43eb-bf8e-0e18ba0ba395", "clientId": "broker", "name": "${client_broker}", "surrogateAuthRequired": false, @@ -710,7 +711,9 @@ "publicClient": false, "frontchannelLogout": false, "protocol": "openid-connect", - "attributes": {}, + "attributes": { + "realm_client": "true" + }, "authenticationFlowBindingOverrides": {}, "fullScopeAllowed": false, "nodeReRegistrationTimeout": 0, @@ -726,99 +729,175 @@ "address", "phone", "offline_access", + "organization", "microprofile-jwt" ] }, { - "id": "aa876965-bdfe-4d5e-9a4f-070287477513", - "clientId": "realm-management", - "name": "${client_realm-management}", + "id": "852a5a02-ffb8-48c3-abcd-70be2ca17e71", + "clientId": "fx-expert", + "name": "FX Expert", + "description": "Oauth config for fx-expert", + "rootUrl": "https://localhost:8501/", + "adminUrl": "", + "baseUrl": "https://localhost:8501/", "surrogateAuthRequired": false, "enabled": true, - "alwaysDisplayInConsole": false, + "alwaysDisplayInConsole": true, "clientAuthenticatorType": "client-secret", - "redirectUris": [], - "webOrigins": [], + "secret": "**********", + "redirectUris": ["https://localhost:8501/signin-oidc"], + "webOrigins": ["*"], "notBefore": 0, - "bearerOnly": true, + "bearerOnly": false, "consentRequired": false, "standardFlowEnabled": true, "implicitFlowEnabled": false, - "directAccessGrantsEnabled": false, - "serviceAccountsEnabled": false, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": true, + "authorizationServicesEnabled": true, "publicClient": false, - "frontchannelLogout": false, + "frontchannelLogout": true, "protocol": "openid-connect", - "attributes": {}, + "attributes": { + "client.secret.creation.time": "1736864673", + "client.introspection.response.allow.jwt.claim.enabled": "false", + "post.logout.redirect.uris": "https://localhost:8501/signout-callback-oidc", + "oauth2.device.authorization.grant.enabled": "false", + "backchannel.logout.revoke.offline.tokens": "false", + "use.refresh.tokens": "true", + "realm_client": "false", + "oidc.ciba.grant.enabled": "false", + "client.use.lightweight.access.token.enabled": "false", + "backchannel.logout.session.required": "true", + "client_credentials.use_refresh_token": "false", + "tls.client.certificate.bound.access.tokens": "false", + "require.pushed.authorization.requests": "false", + "acr.loa.map": "{}", + "display.on.consent.screen": "false", + "token.response.type.bearer.lower-case": "false" + }, "authenticationFlowBindingOverrides": {}, - "fullScopeAllowed": false, - "nodeReRegistrationTimeout": 0, - "defaultClientScopes": [ - "web-origins", - "acr", - "roles", - "profile", - "basic", - "email" - ], - "optionalClientScopes": [ - "address", - "phone", - "offline_access", - "microprofile-jwt" - ] - }, - { - "id": "e803c407-e63d-4b7b-9fb3-0b44229a6155", - "clientId": "security-admin-console", - "name": "${client_security-admin-console}", - "rootUrl": "${authAdminUrl}", - "baseUrl": "/admin/spring/console/", - "surrogateAuthRequired": false, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "protocolMappers": [ + { + "id": "144bf973-d3c3-4ab6-92b4-013bb8b04fc7", + "name": "Client ID", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "client_id", + "id.token.claim": "true", + "introspection.token.claim": "true", + "access.token.claim": "true", + "claim.name": "client_id", + "jsonType.label": "String" + } + }, + { + "id": "e34c1549-b967-43e7-a1ed-57fc3a78d7af", + "name": "Client Host", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientHost", + "id.token.claim": "true", + "introspection.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientHost", + "jsonType.label": "String" + } + }, + { + "id": "c513e2b9-48b8-4aaa-a657-3207d6bcacad", + "name": "Client IP Address", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientAddress", + "id.token.claim": "true", + "introspection.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientAddress", + "jsonType.label": "String" + } + } + ], + "defaultClientScopes": [ + "web-origins", + "acr", + "openid", + "roles", + "profile", + "offline_access", + "api", + "basic", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "organization", + "microprofile-jwt" + ], + "authorizationSettings": { + "allowRemoteResourceManagement": true, + "policyEnforcementMode": "ENFORCING", + "resources": [ + { + "name": "Default Resource", + "type": "urn:fx-expert:resources:default", + "ownerManagedAccess": false, + "attributes": {}, + "uris": ["/*"] + } + ], + "policies": [ + { + "name": "Default Permission", + "description": "A permission that applies to the default resource type", + "type": "resource", + "logic": "POSITIVE", + "decisionStrategy": "UNANIMOUS", + "config": { + "defaultResourceType": "urn:fx-expert:resources:default" + } + } + ], + "scopes": [], + "decisionStrategy": "UNANIMOUS" + } + }, + { + "id": "d7bdc0db-b2fc-4867-a6eb-dd902ef0b85c", + "clientId": "realm-management", + "name": "${client_realm-management}", + "surrogateAuthRequired": false, "enabled": true, "alwaysDisplayInConsole": false, "clientAuthenticatorType": "client-secret", - "redirectUris": [ - "/admin/spring/console/*" - ], - "webOrigins": [ - "+" - ], + "redirectUris": [], + "webOrigins": [], "notBefore": 0, - "bearerOnly": false, + "bearerOnly": true, "consentRequired": false, "standardFlowEnabled": true, "implicitFlowEnabled": false, "directAccessGrantsEnabled": false, "serviceAccountsEnabled": false, - "publicClient": true, + "publicClient": false, "frontchannelLogout": false, "protocol": "openid-connect", "attributes": { - "post.logout.redirect.uris": "+", - "pkce.code.challenge.method": "S256" + "realm_client": "true" }, "authenticationFlowBindingOverrides": {}, "fullScopeAllowed": false, "nodeReRegistrationTimeout": 0, - "protocolMappers": [ - { - "id": "8516966a-b3e4-47ff-82ab-b66af5458e39", - "name": "locale", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-attribute-mapper", - "consentRequired": false, - "config": { - "introspection.token.claim": "true", - "userinfo.token.claim": "true", - "user.attribute": "locale", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "locale", - "jsonType.label": "String" - } - } - ], "defaultClientScopes": [ "web-origins", "acr", @@ -831,92 +910,55 @@ "address", "phone", "offline_access", + "organization", "microprofile-jwt" ] }, { - "id": "ecdb084e-47bf-4647-a65b-3780d2bdaf2f", - "clientId": "security-playground", - "name": "Spring Security Playground", - "description": "", - "rootUrl": "http://localhost:8080/", - "adminUrl": "http://localhost:8080/", - "baseUrl": "http://localhost:8080/", + "id": "90df1870-5423-450e-aa22-969ab8436353", + "clientId": "security-admin-console", + "name": "${client_security-admin-console}", + "rootUrl": "${authAdminUrl}", + "baseUrl": "/admin/fx-expert/console/", "surrogateAuthRequired": false, "enabled": true, - "alwaysDisplayInConsole": true, + "alwaysDisplayInConsole": false, "clientAuthenticatorType": "client-secret", - "redirectUris": [ - "http://localhost:8080/*" - ], - "webOrigins": [ - "*" - ], + "redirectUris": ["/admin/fx-expert/console/*"], + "webOrigins": ["+"], "notBefore": 0, "bearerOnly": false, "consentRequired": false, "standardFlowEnabled": true, "implicitFlowEnabled": false, - "directAccessGrantsEnabled": true, + "directAccessGrantsEnabled": false, "serviceAccountsEnabled": false, "publicClient": true, - "frontchannelLogout": true, + "frontchannelLogout": false, "protocol": "openid-connect", "attributes": { - "oidc.ciba.grant.enabled": "false", - "client.secret.creation.time": "1730478420", - "backchannel.logout.session.required": "true", - "login_theme": "keycloak", - "oauth2.device.authorization.grant.enabled": "false", - "display.on.consent.screen": "false", - "backchannel.logout.revoke.offline.tokens": "false" + "realm_client": "false", + "client.use.lightweight.access.token.enabled": "true", + "post.logout.redirect.uris": "+", + "pkce.code.challenge.method": "S256" }, "authenticationFlowBindingOverrides": {}, "fullScopeAllowed": true, - "nodeReRegistrationTimeout": -1, + "nodeReRegistrationTimeout": 0, "protocolMappers": [ { - "id": "afa7dc72-8699-4c4a-b3c2-d3243346c5ec", - "name": "Client ID", - "protocol": "openid-connect", - "protocolMapper": "oidc-usersessionmodel-note-mapper", - "consentRequired": false, - "config": { - "user.session.note": "client_id", - "id.token.claim": "true", - "introspection.token.claim": "true", - "access.token.claim": "true", - "claim.name": "client_id", - "jsonType.label": "String" - } - }, - { - "id": "48104aca-57d7-4fb7-8d80-ab064443bc15", - "name": "Client IP Address", + "id": "bce85211-3ef7-41be-af1e-d42e036cb98d", + "name": "locale", "protocol": "openid-connect", - "protocolMapper": "oidc-usersessionmodel-note-mapper", + "protocolMapper": "oidc-usermodel-attribute-mapper", "consentRequired": false, "config": { - "user.session.note": "clientAddress", - "id.token.claim": "true", "introspection.token.claim": "true", - "access.token.claim": "true", - "claim.name": "clientAddress", - "jsonType.label": "String" - } - }, - { - "id": "2c2af144-a07f-4f51-aa10-211c8a881cf6", - "name": "Client Host", - "protocol": "openid-connect", - "protocolMapper": "oidc-usersessionmodel-note-mapper", - "consentRequired": false, - "config": { - "user.session.note": "clientHost", + "userinfo.token.claim": "true", + "user.attribute": "locale", "id.token.claim": "true", - "introspection.token.claim": "true", "access.token.claim": "true", - "claim.name": "clientHost", + "claim.name": "locale", "jsonType.label": "String" } } @@ -933,37 +975,14 @@ "address", "phone", "offline_access", + "organization", "microprofile-jwt" ] } ], "clientScopes": [ { - "id": "90111d01-fc08-4fa9-9d29-efd77249eb05", - "name": "role_list", - "description": "SAML role list", - "protocol": "saml", - "attributes": { - "consent.screen.text": "${samlRoleListScopeConsentText}", - "display.on.consent.screen": "true" - }, - "protocolMappers": [ - { - "id": "a36c15d1-37b1-4324-894c-0071b06e705b", - "name": "role list", - "protocol": "saml", - "protocolMapper": "saml-role-list-mapper", - "consentRequired": false, - "config": { - "single": "false", - "attribute.nameformat": "Basic", - "attribute.name": "Role" - } - } - ] - }, - { - "id": "f8eb13c5-5b81-455f-b130-c19f59a1453a", + "id": "8625b937-e6f8-4ee6-83cf-b982fbd8bab4", "name": "roles", "description": "OpenID Connect scope for add user roles to the access token", "protocol": "openid-connect", @@ -974,33 +993,25 @@ }, "protocolMappers": [ { - "id": "5dea1b4f-60cf-4a2a-910b-f670233c354e", + "id": "d42771cb-4bf3-4d80-b249-ed2e2f6e4809", "name": "client roles", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-client-role-mapper", "consentRequired": false, "config": { - "user.attribute": "foo", "introspection.token.claim": "true", + "multivalued": "true", + "userinfo.token.claim": "true", + "user.attribute": "foo", + "id.token.claim": "true", + "lightweight.claim": "true", "access.token.claim": "true", - "claim.name": "resource_access.${client_id}.roles", - "jsonType.label": "String", - "multivalued": "true" - } - }, - { - "id": "2229e5db-1ebb-4a99-b3f4-6ae5904b239f", - "name": "audience resolve", - "protocol": "openid-connect", - "protocolMapper": "oidc-audience-resolve-mapper", - "consentRequired": false, - "config": { - "introspection.token.claim": "true", - "access.token.claim": "true" + "claim.name": "role", + "jsonType.label": "String" } }, { - "id": "b30ce684-4de5-4537-808b-ee31e008f42d", + "id": "6b1bbba6-aa23-4757-bcba-216a330f95c0", "name": "realm roles", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-realm-role-mapper", @@ -1010,256 +1021,170 @@ "multivalued": "true", "userinfo.token.claim": "true", "user.attribute": "foo", - "id.token.claim": "false", - "lightweight.claim": "false", + "id.token.claim": "true", + "lightweight.claim": "true", "access.token.claim": "true", - "claim.name": "realm_access.roles", + "claim.name": "role", "jsonType.label": "String" } - } - ] - }, - { - "id": "1f91ccb7-2f5c-4fb5-961c-1edef536b5f0", - "name": "basic", - "description": "OpenID Connect scope for add all basic claims to the token", - "protocol": "openid-connect", - "attributes": { - "include.in.token.scope": "false", - "display.on.consent.screen": "false" - }, - "protocolMappers": [ - { - "id": "adbe8d10-2220-46d0-92b8-4781cd70b1b7", - "name": "sub", - "protocol": "openid-connect", - "protocolMapper": "oidc-sub-mapper", - "consentRequired": false, - "config": { - "introspection.token.claim": "true", - "access.token.claim": "true" - } }, { - "id": "91e27805-e1d9-4060-92de-495993985449", - "name": "auth_time", + "id": "1842eb84-d8cb-4938-87e4-6c201bcbdfd0", + "name": "audience resolve", "protocol": "openid-connect", - "protocolMapper": "oidc-usersessionmodel-note-mapper", + "protocolMapper": "oidc-audience-resolve-mapper", "consentRequired": false, "config": { - "user.session.note": "AUTH_TIME", - "id.token.claim": "true", "introspection.token.claim": "true", - "access.token.claim": "true", - "claim.name": "auth_time", - "jsonType.label": "long" + "access.token.claim": "true" } } ] }, { - "id": "533679f6-87bd-46e1-968e-0bc1e813e173", - "name": "microprofile-jwt", - "description": "Microprofile - JWT built-in scope", + "id": "55efc699-e9aa-4d1b-8856-6b78bf62f819", + "name": "api", + "description": "Allow api access", "protocol": "openid-connect", "attributes": { - "include.in.token.scope": "true", - "display.on.consent.screen": "false" - }, - "protocolMappers": [ - { - "id": "2a92cd88-a3d8-4748-9256-fa20e242e177", - "name": "groups", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-realm-role-mapper", - "consentRequired": false, - "config": { - "introspection.token.claim": "true", - "multivalued": "true", - "user.attribute": "foo", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "groups", - "jsonType.label": "String" - } - }, - { - "id": "5940fa4f-972b-4c39-88b6-d8365a06879f", - "name": "upn", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-attribute-mapper", - "consentRequired": false, - "config": { - "introspection.token.claim": "true", - "userinfo.token.claim": "true", - "user.attribute": "username", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "upn", - "jsonType.label": "String" - } - } - ] + "include.in.token.scope": "false", + "display.on.consent.screen": "true", + "gui.order": "", + "consent.screen.text": "" + } + }, + { + "id": "fe1192a9-8ba7-4602-a71b-c9eafcb94924", + "name": "offline_access", + "description": "OpenID Connect built-in scope: offline_access", + "protocol": "openid-connect", + "attributes": { + "consent.screen.text": "${offlineAccessScopeConsentText}", + "display.on.consent.screen": "true" + } }, { - "id": "f5197eab-9e83-4913-bdf1-2c0306622132", - "name": "phone", - "description": "OpenID Connect built-in scope: phone", + "id": "82c6dc00-eb9f-4c98-9564-614dfdb37f7b", + "name": "profile", + "description": "OpenID Connect built-in scope: profile", "protocol": "openid-connect", "attributes": { "include.in.token.scope": "true", - "consent.screen.text": "${phoneScopeConsentText}", + "consent.screen.text": "${profileScopeConsentText}", "display.on.consent.screen": "true" }, "protocolMappers": [ { - "id": "47e256ec-de9b-46c8-8a69-6b8a7f9b7da0", - "name": "phone number verified", + "id": "9dfa2b30-55c1-41a9-832d-7f78b4a8a05c", + "name": "nickname", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-attribute-mapper", "consentRequired": false, "config": { "introspection.token.claim": "true", "userinfo.token.claim": "true", - "user.attribute": "phoneNumberVerified", + "user.attribute": "nickname", "id.token.claim": "true", "access.token.claim": "true", - "claim.name": "phone_number_verified", - "jsonType.label": "boolean" + "claim.name": "nickname", + "jsonType.label": "String" } }, { - "id": "cb4c3989-6b51-49c8-a09c-8fc152a98628", - "name": "phone number", + "id": "7d64e1c6-3842-4a2f-b6ee-1e933603d9a6", + "name": "middle name", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-attribute-mapper", "consentRequired": false, "config": { "introspection.token.claim": "true", "userinfo.token.claim": "true", - "user.attribute": "phoneNumber", + "user.attribute": "middleName", "id.token.claim": "true", "access.token.claim": "true", - "claim.name": "phone_number", + "claim.name": "middle_name", "jsonType.label": "String" } - } - ] - }, - { - "id": "df43b507-debf-43bb-9edf-07c5eb8e74cb", - "name": "email", - "description": "OpenID Connect built-in scope: email", - "protocol": "openid-connect", - "attributes": { - "include.in.token.scope": "true", - "consent.screen.text": "${emailScopeConsentText}", - "display.on.consent.screen": "true" - }, - "protocolMappers": [ + }, { - "id": "ae176b0d-7d52-4bf4-a998-30dbaede14f5", - "name": "email", + "id": "6c271cec-5f24-4a91-8a3e-92f58f815782", + "name": "zoneinfo", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-attribute-mapper", "consentRequired": false, "config": { "introspection.token.claim": "true", "userinfo.token.claim": "true", - "user.attribute": "email", + "user.attribute": "zoneinfo", "id.token.claim": "true", "access.token.claim": "true", - "claim.name": "email", + "claim.name": "zoneinfo", "jsonType.label": "String" } }, { - "id": "f4a0727e-d05a-4370-b7ab-c611d2f1d91e", - "name": "email verified", + "id": "e6f144d0-3bf5-4ec5-8993-d53b6d0463c6", + "name": "full name", "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-property-mapper", + "protocolMapper": "oidc-full-name-mapper", "consentRequired": false, "config": { - "introspection.token.claim": "true", - "userinfo.token.claim": "true", - "user.attribute": "emailVerified", "id.token.claim": "true", + "introspection.token.claim": "true", "access.token.claim": "true", - "claim.name": "email_verified", - "jsonType.label": "boolean" + "userinfo.token.claim": "true" } - } - ] - }, - { - "id": "44d88bd6-2bbe-4fbd-a755-2076a935b71d", - "name": "acr", - "description": "OpenID Connect scope for add acr (authentication context class reference) to the token", - "protocol": "openid-connect", - "attributes": { - "include.in.token.scope": "false", - "display.on.consent.screen": "false" - }, - "protocolMappers": [ + }, { - "id": "51e89f67-d633-4d2d-843a-b69f127ad75b", - "name": "acr loa level", + "id": "3d891e69-23cf-41c5-8b12-b3c2644cb345", + "name": "gender", "protocol": "openid-connect", - "protocolMapper": "oidc-acr-mapper", + "protocolMapper": "oidc-usermodel-attribute-mapper", "consentRequired": false, "config": { - "id.token.claim": "true", "introspection.token.claim": "true", - "access.token.claim": "true" + "userinfo.token.claim": "true", + "user.attribute": "gender", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "gender", + "jsonType.label": "String" } - } - ] - }, - { - "id": "3a9e3bf6-4339-46ba-8cee-7ce1ad79884f", - "name": "profile", - "description": "OpenID Connect built-in scope: profile", - "protocol": "openid-connect", - "attributes": { - "include.in.token.scope": "true", - "consent.screen.text": "${profileScopeConsentText}", - "display.on.consent.screen": "true" - }, - "protocolMappers": [ + }, { - "id": "06dd3a74-7fe4-40f5-beeb-181f6e3317fb", - "name": "nickname", + "id": "334a8721-602f-4edd-bc0c-a16c21737628", + "name": "birthdate", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-attribute-mapper", "consentRequired": false, "config": { "introspection.token.claim": "true", "userinfo.token.claim": "true", - "user.attribute": "nickname", + "user.attribute": "birthdate", "id.token.claim": "true", "access.token.claim": "true", - "claim.name": "nickname", + "claim.name": "birthdate", "jsonType.label": "String" } }, { - "id": "5412e4b7-74c7-4c78-bccb-f7fe33d537e5", - "name": "middle name", + "id": "e59dc4a3-1062-4296-bbc1-26750519d0c5", + "name": "locale", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-attribute-mapper", "consentRequired": false, "config": { "introspection.token.claim": "true", "userinfo.token.claim": "true", - "user.attribute": "middleName", + "user.attribute": "locale", "id.token.claim": "true", "access.token.claim": "true", - "claim.name": "middle_name", + "claim.name": "locale", "jsonType.label": "String" } }, { - "id": "52ecbdcc-185b-4a1c-bae3-50dd4331011a", + "id": "e9654ad5-f2b4-4de8-b468-dee555b72c7d", "name": "username", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-attribute-mapper", @@ -1275,7 +1200,7 @@ } }, { - "id": "7c2c9672-9d6a-47bb-8778-a68449e6770e", + "id": "7efc1c88-2bbd-4eac-b987-07971646dec3", "name": "profile", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-attribute-mapper", @@ -1291,23 +1216,23 @@ } }, { - "id": "c7445486-0420-4986-96e3-4a4c5f932a93", - "name": "updated at", + "id": "f2475e52-54cb-4835-b595-9ded6ad5950f", + "name": "family name", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-attribute-mapper", "consentRequired": false, "config": { "introspection.token.claim": "true", "userinfo.token.claim": "true", - "user.attribute": "updatedAt", + "user.attribute": "lastName", "id.token.claim": "true", "access.token.claim": "true", - "claim.name": "updated_at", - "jsonType.label": "long" + "claim.name": "family_name", + "jsonType.label": "String" } }, { - "id": "b1a5dc1f-74dd-4947-817c-e4f7542bcea7", + "id": "91fc70ec-1425-4040-951b-924dc9b6eaf8", "name": "given name", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-attribute-mapper", @@ -1323,134 +1248,222 @@ } }, { - "id": "fb82d38e-c08a-483b-b2c1-0fd214820d50", - "name": "birthdate", + "id": "d54cc792-702f-4660-8dc8-a14acaeaf4f7", + "name": "website", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-attribute-mapper", "consentRequired": false, "config": { "introspection.token.claim": "true", "userinfo.token.claim": "true", - "user.attribute": "birthdate", + "user.attribute": "website", "id.token.claim": "true", "access.token.claim": "true", - "claim.name": "birthdate", + "claim.name": "website", "jsonType.label": "String" } }, { - "id": "89a002d5-3d2c-4618-a3db-695e54cf0c4f", - "name": "gender", + "id": "93cddf36-bbf7-4f5a-a90d-a9ffa0b386ee", + "name": "picture", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-attribute-mapper", "consentRequired": false, "config": { "introspection.token.claim": "true", "userinfo.token.claim": "true", - "user.attribute": "gender", + "user.attribute": "picture", "id.token.claim": "true", "access.token.claim": "true", - "claim.name": "gender", + "claim.name": "picture", "jsonType.label": "String" } }, { - "id": "056cb89c-6596-489b-b456-647002eec649", - "name": "full name", + "id": "77a24be0-2eab-4c93-b82d-1e6513c9b779", + "name": "updated at", "protocol": "openid-connect", - "protocolMapper": "oidc-full-name-mapper", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "updatedAt", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "updated_at", + "jsonType.label": "long" + } + } + ] + }, + { + "id": "5bb0853c-12ca-4fb6-bb00-4464dfec9618", + "name": "acr", + "description": "OpenID Connect scope for add acr (authentication context class reference) to the token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "76d4e1f5-799f-4a52-884c-7ae3cf6024b9", + "name": "acr loa level", + "protocol": "openid-connect", + "protocolMapper": "oidc-acr-mapper", + "consentRequired": false, + "config": { + "id.token.claim": "true", + "introspection.token.claim": "true", + "access.token.claim": "true" + } + } + ] + }, + { + "id": "d234c775-8fe8-49be-85cb-76d7ea1f1671", + "name": "organization", + "description": "Additional claims about the organization a subject belongs to", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "consent.screen.text": "${organizationScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "9b00be9a-e3cf-44e6-9cfb-9d959b329e68", + "name": "organization", + "protocol": "openid-connect", + "protocolMapper": "oidc-organization-membership-mapper", "consentRequired": false, "config": { "id.token.claim": "true", "introspection.token.claim": "true", "access.token.claim": "true", - "userinfo.token.claim": "true" + "claim.name": "organization", + "jsonType.label": "String", + "multivalued": "true" } - }, + } + ] + }, + { + "id": "6e0ed294-a414-4eac-8bc7-966ccaee143f", + "name": "email", + "description": "OpenID Connect built-in scope: email", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "consent.screen.text": "${emailScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ { - "id": "bbaea1a3-3be2-4818-bf1b-484188343721", - "name": "website", + "id": "9a4e13cb-1722-42f2-8130-c546e55c6de9", + "name": "email", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-attribute-mapper", "consentRequired": false, "config": { "introspection.token.claim": "true", "userinfo.token.claim": "true", - "user.attribute": "website", + "user.attribute": "email", "id.token.claim": "true", "access.token.claim": "true", - "claim.name": "website", + "claim.name": "email", "jsonType.label": "String" } }, { - "id": "80cf6b3a-6713-4bf5-adea-998f257100d9", - "name": "locale", + "id": "d3fcf5e6-7879-4a3c-ab53-2ec81127c254", + "name": "email verified", "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-attribute-mapper", + "protocolMapper": "oidc-usermodel-property-mapper", "consentRequired": false, "config": { "introspection.token.claim": "true", "userinfo.token.claim": "true", - "user.attribute": "locale", + "user.attribute": "emailVerified", "id.token.claim": "true", "access.token.claim": "true", - "claim.name": "locale", - "jsonType.label": "String" + "claim.name": "email_verified", + "jsonType.label": "boolean" } - }, + } + ] + }, + { + "id": "1f21984f-447c-47d3-8f98-00001a6d3f9c", + "name": "microprofile-jwt", + "description": "Microprofile - JWT built-in scope", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ { - "id": "fb649e34-19ba-45f6-8c5d-bd4323dc95c8", - "name": "picture", + "id": "ef439971-56ed-4465-ad7c-afa75c68fc61", + "name": "groups", "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-attribute-mapper", + "protocolMapper": "oidc-usermodel-realm-role-mapper", "consentRequired": false, "config": { "introspection.token.claim": "true", - "userinfo.token.claim": "true", - "user.attribute": "picture", + "multivalued": "true", + "user.attribute": "foo", "id.token.claim": "true", "access.token.claim": "true", - "claim.name": "picture", + "claim.name": "groups", "jsonType.label": "String" } }, { - "id": "97806abf-76d9-48c9-9764-eda87fe86801", - "name": "family name", + "id": "988c2f15-4ae4-448e-96c2-f49150b3eeb5", + "name": "upn", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-attribute-mapper", "consentRequired": false, "config": { "introspection.token.claim": "true", "userinfo.token.claim": "true", - "user.attribute": "lastName", + "user.attribute": "username", "id.token.claim": "true", "access.token.claim": "true", - "claim.name": "family_name", + "claim.name": "upn", "jsonType.label": "String" } - }, + } + ] + }, + { + "id": "6ae69bae-02af-40dc-b665-4c07aecf129a", + "name": "role_list", + "description": "SAML role list", + "protocol": "saml", + "attributes": { + "consent.screen.text": "${samlRoleListScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ { - "id": "d9725a08-bdac-4e48-91a3-4d8157d4b06b", - "name": "zoneinfo", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-attribute-mapper", + "id": "f4e8406b-9af5-4ada-bf45-a733f2229c2a", + "name": "role list", + "protocol": "saml", + "protocolMapper": "saml-role-list-mapper", "consentRequired": false, "config": { - "introspection.token.claim": "true", - "userinfo.token.claim": "true", - "user.attribute": "zoneinfo", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "zoneinfo", - "jsonType.label": "String" + "single": "false", + "attribute.nameformat": "Basic", + "attribute.name": "Role" } } ] }, { - "id": "f295422a-4c05-433f-a9a7-017569848b56", + "id": "cde74e55-87db-422b-9802-c13b84af45e5", "name": "address", "description": "OpenID Connect built-in scope: address", "protocol": "openid-connect", @@ -1461,7 +1474,7 @@ }, "protocolMappers": [ { - "id": "b104209c-1469-4f3b-b7af-ed39168712c0", + "id": "3eebbb4d-5eb3-4ed8-8dab-c0d869710b6c", "name": "address", "protocol": "openid-connect", "protocolMapper": "oidc-address-mapper", @@ -1482,7 +1495,83 @@ ] }, { - "id": "06e18fe3-0a82-4389-bf4c-1b8c97d893d4", + "id": "559691d6-20cd-4890-b326-ca48a310bf8a", + "name": "openid", + "description": "", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "true", + "gui.order": "", + "consent.screen.text": "" + } + }, + { + "id": "5daba11b-8778-4879-a9ba-ae48f1bb6b11", + "name": "saml_organization", + "description": "Organization Membership", + "protocol": "saml", + "attributes": { + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "92c6879c-604e-4386-be18-4a8c103d6d13", + "name": "organization", + "protocol": "saml", + "protocolMapper": "saml-organization-membership-mapper", + "consentRequired": false, + "config": {} + } + ] + }, + { + "id": "626f340d-c6d4-4278-827e-f8597819afc1", + "name": "phone", + "description": "OpenID Connect built-in scope: phone", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "consent.screen.text": "${phoneScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "ffd8bd26-8a20-45fa-8111-53eaceea9d4c", + "name": "phone number verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "phoneNumberVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number_verified", + "jsonType.label": "boolean" + } + }, + { + "id": "b6d1dd48-1c8e-4757-afbb-86a4f52d95b9", + "name": "phone number", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "phoneNumber", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "0bb6dc94-cc6b-4de3-ae35-f22162efbd4c", "name": "web-origins", "description": "OpenID Connect scope for add allowed web origins to the access token", "protocol": "openid-connect", @@ -1493,7 +1582,7 @@ }, "protocolMappers": [ { - "id": "ec239815-4f85-4251-86f2-e967dbe87ee5", + "id": "f72e6155-4f35-4c45-accc-629ad8676b62", "name": "allowed web origins", "protocol": "openid-connect", "protocolMapper": "oidc-allowed-origins-mapper", @@ -1506,30 +1595,62 @@ ] }, { - "id": "2d417e6f-3edf-4848-83f8-d98d82a5442e", - "name": "offline_access", - "description": "OpenID Connect built-in scope: offline_access", + "id": "5e4cfbd5-f698-40c6-b378-0121016cb779", + "name": "basic", + "description": "OpenID Connect scope for add all basic claims to the token", "protocol": "openid-connect", "attributes": { - "consent.screen.text": "${offlineAccessScopeConsentText}", - "display.on.consent.screen": "true" - } + "include.in.token.scope": "false", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "11b72912-a000-4de8-91b9-c5da4423055a", + "name": "auth_time", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "AUTH_TIME", + "id.token.claim": "true", + "introspection.token.claim": "true", + "access.token.claim": "true", + "claim.name": "auth_time", + "jsonType.label": "long" + } + }, + { + "id": "0d41e010-1f62-4f2e-a11b-49b1fe74b893", + "name": "sub", + "protocol": "openid-connect", + "protocolMapper": "oidc-sub-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "access.token.claim": "true" + } + } + ] } ], "defaultDefaultClientScopes": [ "role_list", + "saml_organization", "profile", "email", "roles", "web-origins", "acr", - "basic" + "basic", + "api", + "offline_access", + "openid" ], "defaultOptionalClientScopes": [ - "offline_access", "address", "phone", - "microprofile-jwt" + "microprofile-jwt", + "organization" ], "browserSecurityHeaders": { "contentSecurityPolicyReportOnly": "", @@ -1537,181 +1658,183 @@ "referrerPolicy": "no-referrer", "xRobotsTag": "none", "xFrameOptions": "SAMEORIGIN", - "contentSecurityPolicy": "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", + "contentSecurityPolicy": "frame-src 'self'; object-src 'none';", "xXSSProtection": "1; mode=block", "strictTransportSecurity": "max-age=31536000; includeSubDomains" }, "smtpServer": {}, - "loginTheme": "keycloak", + "loginTheme": "keycloak.v2", "accountTheme": "keycloak.v3", "adminTheme": "keycloak.v2", "emailTheme": "keycloak", "eventsEnabled": false, - "eventsListeners": [ - "jboss-logging" - ], + "eventsListeners": ["jboss-logging"], "enabledEventTypes": [], "adminEventsEnabled": false, "adminEventsDetailsEnabled": false, - "identityProviders": [], + "identityProviders": [ + { + "alias": "google", + "displayName": "", + "internalId": "2865cecd-f90f-4bd9-bc5c-b40f95710464", + "providerId": "google", + "enabled": true, + "updateProfileFirstLoginMode": "on", + "trustEmail": true, + "storeToken": true, + "addReadTokenRoleOnCreate": false, + "authenticateByDefault": false, + "linkOnly": false, + "hideOnLogin": false, + "config": { + "offlineAccess": "true", + "clientId": "512600641044-g3fq5cedsflptgcfg9cjma4k25uif117.apps.googleusercontent.com", + "acceptsPromptNoneForwardFromClient": "false", + "disableUserInfo": "false", + "filteredByClaim": "false", + "syncMode": "LEGACY", + "userIp": "true", + "clientSecret": "**********", + "caseSensitiveOriginalUsername": "false" + } + } + ], "identityProviderMappers": [], "components": { "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy": [ { - "id": "35e9ce19-ab6f-42fc-ac07-a7c0478f1636", - "name": "Max Clients Limit", - "providerId": "max-clients", + "id": "4c0239f0-0bdb-459e-aad7-22ffc63f27ea", + "name": "Full Scope Disabled", + "providerId": "scope", "subType": "anonymous", "subComponents": {}, - "config": { - "max-clients": [ - "200" - ] - } + "config": {} }, { - "id": "4aef2db1-0e86-4bda-98f1-6c4b73a591b3", + "id": "c7d57929-fe08-4ab0-8b0a-fc9def18faef", "name": "Trusted Hosts", "providerId": "trusted-hosts", "subType": "anonymous", "subComponents": {}, "config": { - "host-sending-registration-request-must-match": [ - "true" - ], - "client-uris-must-match": [ - "true" - ] + "host-sending-registration-request-must-match": ["true"], + "client-uris-must-match": ["true"] } }, { - "id": "b9d5239a-5fd3-4e1c-a6fd-c991c0e72d6f", + "id": "44274c07-b981-4976-81ca-0ccbc5533bc5", "name": "Allowed Protocol Mapper Types", "providerId": "allowed-protocol-mappers", - "subType": "authenticated", + "subType": "anonymous", "subComponents": {}, "config": { "allowed-protocol-mapper-types": [ + "oidc-address-mapper", "saml-user-attribute-mapper", - "oidc-sha256-pairwise-sub-mapper", - "oidc-usermodel-property-mapper", + "oidc-full-name-mapper", "saml-user-property-mapper", + "oidc-sha256-pairwise-sub-mapper", "oidc-usermodel-attribute-mapper", - "oidc-full-name-mapper", - "oidc-address-mapper", + "oidc-usermodel-property-mapper", "saml-role-list-mapper" ] } }, { - "id": "813bc210-48fc-4a6d-ad43-86d65cfd70cb", + "id": "fe2d0076-bfcb-4dce-bcfd-7d377f8680fd", "name": "Allowed Client Scopes", "providerId": "allowed-client-templates", - "subType": "authenticated", + "subType": "anonymous", "subComponents": {}, "config": { - "allow-default-scopes": [ - "true" - ] + "allow-default-scopes": ["true"] } }, { - "id": "a09e42d7-90e6-4953-aff7-a311110662d2", - "name": "Full Scope Disabled", - "providerId": "scope", + "id": "037c529d-c642-49c6-b424-42df96548ac7", + "name": "Consent Required", + "providerId": "consent-required", "subType": "anonymous", "subComponents": {}, "config": {} }, { - "id": "9c8540e2-14a6-4c71-a2b3-b30d96f305b1", + "id": "ae9a46b0-1325-4b18-ba91-3452420aad9c", "name": "Allowed Protocol Mapper Types", "providerId": "allowed-protocol-mappers", - "subType": "anonymous", + "subType": "authenticated", "subComponents": {}, "config": { "allowed-protocol-mapper-types": [ + "oidc-full-name-mapper", "saml-user-property-mapper", + "oidc-usermodel-property-mapper", "oidc-address-mapper", - "saml-role-list-mapper", "oidc-usermodel-attribute-mapper", "saml-user-attribute-mapper", - "oidc-usermodel-property-mapper", - "oidc-full-name-mapper", - "oidc-sha256-pairwise-sub-mapper" + "oidc-sha256-pairwise-sub-mapper", + "saml-role-list-mapper" ] } }, { - "id": "877e6d3b-1d4e-4e6e-bdd5-cc09a287ddb9", + "id": "ee4fe4d3-5590-473f-9d1a-6cada9f212ba", "name": "Allowed Client Scopes", "providerId": "allowed-client-templates", - "subType": "anonymous", + "subType": "authenticated", "subComponents": {}, "config": { - "allow-default-scopes": [ - "true" - ] + "allow-default-scopes": ["true"] } }, { - "id": "ed49f302-4477-4a82-bbec-03d12ae21d7d", - "name": "Consent Required", - "providerId": "consent-required", + "id": "d09ce19d-4a63-46cf-a360-783c5cb80360", + "name": "Max Clients Limit", + "providerId": "max-clients", "subType": "anonymous", "subComponents": {}, - "config": {} + "config": { + "max-clients": ["200"] + } } ], "org.keycloak.keys.KeyProvider": [ { - "id": "7f9ca30f-f8f1-4f08-bc6f-676c575b6b59", - "name": "rsa-enc-generated", - "providerId": "rsa-enc-generated", + "id": "1ffa2e75-f408-4036-bae3-5af0c28c934f", + "name": "rsa-generated", + "providerId": "rsa-generated", "subComponents": {}, "config": { - "priority": [ - "100" - ], - "algorithm": [ - "RSA-OAEP" - ] + "priority": ["100"] } }, { - "id": "0656308c-fca3-4184-9112-baaf36b5c905", - "name": "aes-generated", - "providerId": "aes-generated", + "id": "ff0e73de-2018-4162-9929-24112d7418ae", + "name": "rsa-enc-generated", + "providerId": "rsa-enc-generated", "subComponents": {}, "config": { - "priority": [ - "100" - ] + "priority": ["100"], + "algorithm": ["RSA-OAEP"] } }, { - "id": "310bb299-5c4b-443b-951e-711a20a4949c", + "id": "9e6762a5-a543-4a5a-b077-2f42c80851d3", "name": "hmac-generated-hs512", "providerId": "hmac-generated", "subComponents": {}, "config": { - "priority": [ - "100" - ], - "algorithm": [ - "HS512" - ] + "priority": ["100"], + "algorithm": ["HS512"] } }, { - "id": "fbdb52db-270b-4fa9-9de9-22a8b89a2935", - "name": "rsa-generated", - "providerId": "rsa-generated", + "id": "b58fdc8b-c3cd-4710-8795-03661b178348", + "name": "aes-generated", + "providerId": "aes-generated", "subComponents": {}, "config": { - "priority": [ - "100" - ] + "priority": ["100"] } } ] @@ -1720,7 +1843,7 @@ "supportedLocales": [], "authenticationFlows": [ { - "id": "77f3e3e3-eb08-41d5-9752-25c93b3d9a9e", + "id": "d541c616-1432-4146-b62b-ef808fac066d", "alias": "Account verification options", "description": "Method with which to verity the existing account", "providerId": "basic-flow", @@ -1746,7 +1869,7 @@ ] }, { - "id": "1a051f4c-4ded-4343-83cc-01a6dc0141d8", + "id": "dbf52516-59c1-42c5-9718-e1db057fe99c", "alias": "Browser - Conditional OTP", "description": "Flow to determine if the OTP is required for the authentication", "providerId": "basic-flow", @@ -1772,7 +1895,33 @@ ] }, { - "id": "e808ed53-5b6c-48f5-ab30-ef1f5521cae4", + "id": "cfe0d5de-8820-48dc-ad11-95a3023ae48c", + "alias": "Browser - Conditional Organization", + "description": "Flow to determine if the organization identity-first login is to be used", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "organization", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "836af4b4-98dc-4f58-bf00-12c49d921a48", "alias": "Direct Grant - Conditional OTP", "description": "Flow to determine if the OTP is required for the authentication", "providerId": "basic-flow", @@ -1798,7 +1947,33 @@ ] }, { - "id": "86f5e893-cc3d-46b5-ad6d-6d97d71cd590", + "id": "b8fe64c6-b09f-40f4-8434-1c849007a5d2", + "alias": "First Broker Login - Conditional Organization", + "description": "Flow to determine if the authenticator that adds organization members is to be used", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "idp-add-organization-member", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "d900e835-a8f9-4c2e-9d6d-7cf316fcf653", "alias": "First broker login - Conditional OTP", "description": "Flow to determine if the OTP is required for the authentication", "providerId": "basic-flow", @@ -1824,7 +1999,7 @@ ] }, { - "id": "76eca95b-1082-43a1-8860-ce06e1ac28f5", + "id": "d7e59ed8-f25b-4cd1-81e7-98554f8fc1ce", "alias": "Handle Existing Account", "description": "Handle what to do if there is existing account with same email/username like authenticated identity provider", "providerId": "basic-flow", @@ -1850,7 +2025,24 @@ ] }, { - "id": "ac32da84-f6c1-40f1-a6f8-69d76d5d87f3", + "id": "b5b455da-cfb9-4072-90ad-24676e164deb", + "alias": "Organization", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 10, + "autheticatorFlow": true, + "flowAlias": "Browser - Conditional Organization", + "userSetupAllowed": false + } + ] + }, + { + "id": "8b9dee2c-8002-43d2-a791-7be6e91817ab", "alias": "Reset - Conditional OTP", "description": "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.", "providerId": "basic-flow", @@ -1876,7 +2068,7 @@ ] }, { - "id": "d45f45c1-6676-4e0c-9ea5-ff9f77aa3d61", + "id": "a1bd5b3a-8c17-433e-b1d7-034061407ef3", "alias": "User creation or linking", "description": "Flow for the existing/non-existing user alternatives", "providerId": "basic-flow", @@ -1903,7 +2095,7 @@ ] }, { - "id": "54839324-0a86-42cb-b2b2-92604cf1de2f", + "id": "e1ab7315-a52c-424a-a35e-db5e5865fd8d", "alias": "Verify Existing Account by Re-authentication", "description": "Reauthentication of existing account", "providerId": "basic-flow", @@ -1929,9 +2121,9 @@ ] }, { - "id": "91a94303-9dff-4a3b-a498-11cc8a8cf047", + "id": "cf69a702-05fa-4bd9-b662-510d67b583ad", "alias": "browser", - "description": "browser based authentication", + "description": "Browser based authentication", "providerId": "basic-flow", "topLevel": true, "builtIn": true, @@ -1960,6 +2152,14 @@ "autheticatorFlow": false, "userSetupAllowed": false }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 26, + "autheticatorFlow": true, + "flowAlias": "Organization", + "userSetupAllowed": false + }, { "authenticatorFlow": true, "requirement": "ALTERNATIVE", @@ -1971,7 +2171,7 @@ ] }, { - "id": "30897a20-ef1d-4653-9af9-e52a978c3476", + "id": "b850c117-b879-4918-8b92-4326d8b1a5c8", "alias": "clients", "description": "Base authentication for clients", "providerId": "client-flow", @@ -2013,7 +2213,7 @@ ] }, { - "id": "7d1e1f5b-170f-4df7-9bc5-b0c93e4a2773", + "id": "e1ec8c52-6cd7-47f6-b582-aa95d5562dd7", "alias": "direct grant", "description": "OpenID Connect Resource Owner Grant", "providerId": "basic-flow", @@ -2047,7 +2247,7 @@ ] }, { - "id": "87c2c4ac-3fb8-49ee-a005-7f39618d4585", + "id": "43b33cd8-55ee-474c-b475-5d12bc5c0b0b", "alias": "docker auth", "description": "Used by Docker clients to authenticate against the IDP", "providerId": "basic-flow", @@ -2065,7 +2265,7 @@ ] }, { - "id": "13efacc1-10f6-456c-a6cc-291c9faab821", + "id": "651f66a6-6e87-4582-af95-e16ee70f4623", "alias": "first broker login", "description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", "providerId": "basic-flow", @@ -2088,11 +2288,19 @@ "autheticatorFlow": true, "flowAlias": "User creation or linking", "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 50, + "autheticatorFlow": true, + "flowAlias": "First Broker Login - Conditional Organization", + "userSetupAllowed": false } ] }, { - "id": "e86d6c78-0c73-4a78-aa95-e632bf342185", + "id": "6de6aa16-b2d1-4879-a8a0-b1d1dbc74903", "alias": "forms", "description": "Username, password, otp and other auth forms.", "providerId": "basic-flow", @@ -2118,9 +2326,9 @@ ] }, { - "id": "971302e5-1846-447f-9d72-15127d421fb4", + "id": "b00156e1-d398-4c22-b7e2-ceb49647bd43", "alias": "registration", - "description": "registration flow", + "description": "Registration flow", "providerId": "basic-flow", "topLevel": true, "builtIn": true, @@ -2137,9 +2345,9 @@ ] }, { - "id": "1de854db-321e-4697-801b-66be2735b1bc", + "id": "46a9d46b-de53-48bd-8260-d3d026912881", "alias": "registration form", - "description": "registration form", + "description": "Registration form", "providerId": "form-flow", "topLevel": false, "builtIn": true, @@ -2179,7 +2387,7 @@ ] }, { - "id": "6f41f05a-5701-49e0-a549-e37ba9bfc66a", + "id": "0f6674f0-4b21-4e28-865e-bcb838a8cbd3", "alias": "reset credentials", "description": "Reset credentials for a user if they forgot their password or something", "providerId": "basic-flow", @@ -2221,7 +2429,7 @@ ] }, { - "id": "20e4d8e2-a4e6-428d-9384-857003a6ab1b", + "id": "f47151c7-90da-4b24-9680-e5a5a2dc0e5a", "alias": "saml ecp", "description": "SAML ECP Profile Authentication Flow", "providerId": "basic-flow", @@ -2241,14 +2449,14 @@ ], "authenticatorConfig": [ { - "id": "d010d86d-6069-497f-aea2-ae6476bc61d2", + "id": "ce61fe6f-dd81-4ec6-bad3-197027b0a366", "alias": "create unique user config", "config": { "require.password.update.after.registration": "false" } }, { - "id": "ccd2dda0-58bc-49e9-8354-0d95d49b350c", + "id": "082a69cb-776c-43b5-a7ae-4e5c112d55e6", "alias": "review profile config", "config": { "update.profile.on.first.login": "missing" @@ -2365,20 +2573,19 @@ "firstBrokerLoginFlow": "first broker login", "attributes": { "cibaBackchannelTokenDeliveryMode": "poll", + "cibaExpiresIn": "120", "cibaAuthRequestedUserHint": "login_hint", + "oauth2DeviceCodeLifespan": "600", "oauth2DevicePollingInterval": "5", "clientOfflineSessionMaxLifespan": "0", - "clientSessionIdleTimeout": "900", + "clientSessionIdleTimeout": "0", + "parRequestUriLifespan": "60", + "clientSessionMaxLifespan": "0", "clientOfflineSessionIdleTimeout": "0", "cibaInterval": "5", - "realmReusableOtpCode": "false", - "cibaExpiresIn": "120", - "oauth2DeviceCodeLifespan": "600", - "parRequestUriLifespan": "60", - "clientSessionMaxLifespan": "86400", - "organizationsEnabled": "false" + "realmReusableOtpCode": "false" }, - "keycloakVersion": "25.0.1", + "keycloakVersion": "26.0.8", "userManagedAccessAllowed": false, "organizationsEnabled": false, "clientProfiles": { @@ -2387,4 +2594,4 @@ "clientPolicies": { "policies": [] } -} \ No newline at end of file +} diff --git a/shared-types/Types/Address.cs b/shared-types/Types/Address.cs index 39cd6b1..b4075c4 100644 --- a/shared-types/Types/Address.cs +++ b/shared-types/Types/Address.cs @@ -1,22 +1,32 @@ +using System.Diagnostics; + namespace Fortium.Types; public enum AddressType { Home, Work, Other } -public record Address { - public string? StreetAddress1 { get; } - public string? StreetAddress2 { get; } - public string? City { get; } - public string? State { get; } - public string? ZipCode { get; } - public AddressType? AddressType { get; } +[Serializable] +[DebuggerDisplay("{DebuggerDisplay,nq}")] +public class Address { + public string? Street1 { get; set; } + public string? Street2 { get; set; } + public string? City { get; set; } + public string? State { get; set; } + public string? ZipCode { get; set; } + public string? Country { get; set; } + public AddressType AddressType { get; set; } = AddressType.Home; + + public Address() { } - public Address(string address1, string? address2, string city, string state, string zip, AddressType type) { - StreetAddress1 = address1; - StreetAddress2 = address2; + public Address(string street1, string? street2, string city, string state, string zipCode, string? country = null, AddressType addressType = AddressType.Home) { + Street1 = street1; + Street2 = street2; City = city; State = state; - ZipCode = zip; - AddressType = type; + ZipCode = zipCode; + Country = country; + AddressType = addressType; } + + private string DebuggerDisplay => $"{Street1}, {City}, {State} {ZipCode}"; } diff --git a/shared-types/Types/User.cs b/shared-types/Types/User.cs index 9f7c2ce..0ae23f0 100644 --- a/shared-types/Types/User.cs +++ b/shared-types/Types/User.cs @@ -7,19 +7,26 @@ namespace Fortium.Types; [DebuggerDisplay($"{{{nameof(GetDebuggerDisplay)}(),nq}}")] public class User { - public User() {} + public User() { } public string? FirstName { get; set; } public string? LastName { get; set; } - [Identity] public string? EmailAddress { get; set; } + + [Identity] + public string EmailAddress { get; set; } = ""; + public string? PhoneNumber { get; set; } + public string? ProfilePictureUrl { get; set; } + public Address? Address { get; set; } + public UserPreferences? Preferences { get; set; } public DateTime? CreateDate { get; set; } public DateTime? UpdateDate { get; set; } public DateTime? LoginDate { get; set; } public DateTime? LogoffDate { get; set; } public bool LoggedIn { get; set; } public bool Active { get; set; } - public List VideoConferences { get; set; } = new (); - public bool IsLoggedIn() + public List VideoConferences { get; set; } = new(); + + public bool IsLoggedIn() { return LoggedIn; } @@ -31,7 +38,6 @@ private string GetDebuggerDisplay() public override string ToString() { - return - $"IsActive: {Active}, EmailAddress: {EmailAddress}, FirstName: {FirstName}, LastName: {LastName}"; + return $"IsActive: {Active}, EmailAddress: {EmailAddress}, FirstName: {FirstName}, LastName: {LastName}, PhoneNumber: {PhoneNumber}"; } } diff --git a/shared-types/Types/UserPreferences.cs b/shared-types/Types/UserPreferences.cs new file mode 100644 index 0000000..eeb585e --- /dev/null +++ b/shared-types/Types/UserPreferences.cs @@ -0,0 +1,15 @@ +using System.Diagnostics; + +namespace Fortium.Types; + +[Serializable] +public class UserPreferences +{ + public UserPreferences() {} + + public bool ReceiveEmailNotifications { get; set; } = true; + public bool ReceiveSmsNotifications { get; set; } = false; + public string? PreferredLanguage { get; set; } = "en-US"; + public string? TimeZone { get; set; } = "UTC"; + public string? Theme { get; set; } = "Light"; +} \ No newline at end of file diff --git a/src/EventServer.Tests/Aggregates/Users/UserProfileTests.cs b/src/EventServer.Tests/Aggregates/Users/UserProfileTests.cs new file mode 100644 index 0000000..1cf81b0 --- /dev/null +++ b/src/EventServer.Tests/Aggregates/Users/UserProfileTests.cs @@ -0,0 +1,209 @@ +using EventServer.Aggregates.Users.Commands; +using EventServer.Aggregates.Users.Events; +using Fortium.Types; +using Shouldly; +using Xunit.Abstractions; + +namespace EventServer.Tests.Aggregates.Users; + +public class UserProfileTests : IntegrationContext +{ + public UserProfileTests(AppFixture fixture, ITestOutputHelper output) : base(fixture, output) + { + } + + [Fact] + public async Task UpdateUserProfile_ShouldReturnUserProfileUpdatedEvent() + { + // Arrange + var command = new UpdateUserProfileCommand( + EmailAddress: "test@example.com", + FirstName: "John", + LastName: "Doe", + PhoneNumber: "+1-555-123-4567", + ProfilePictureUrl: "https://example.com/profile.jpg" + ); + + // Act & Assert + var result = await Host.Scenario(_ => + { + _.Post.Json(command).ToUrl($"/users/profile/{command.EmailAddress}"); + _.StatusCodeShouldBe(204); + }); + } + + [Fact] + public async Task UpdateUserAddress_ShouldReturnUserAddressUpdatedEvent() + { + // Arrange + var command = new UpdateUserAddressCommand( + EmailAddress: "test@example.com", + Street1: "123 Main St", + Street2: "Apt 4B", + City: "New York", + State: "NY", + ZipCode: "10001", + Country: "USA" + ); + + // Act & Assert + var result = await Host.Scenario(_ => + { + _.Post.Json(command).ToUrl($"/users/address/{command.EmailAddress}"); + _.StatusCodeShouldBe(204); + }); + } + + [Fact] + public async Task UpdateUserPreferences_ShouldReturnUserPreferencesUpdatedEvent() + { + // Arrange + var command = new UpdateUserPreferencesCommand( + EmailAddress: "test@example.com", + ReceiveEmailNotifications: true, + ReceiveSmsNotifications: false, + PreferredLanguage: "en-US", + TimeZone: "America/New_York", + Theme: "Dark" + ); + + // Act & Assert + var result = await Host.Scenario(_ => + { + _.Post.Json(command).ToUrl($"/users/preferences/{command.EmailAddress}"); + _.StatusCodeShouldBe(204); + }); + } + + [Fact] + public async Task CompleteUserProfileWorkflow_ShouldUpdateAllUserFields() + { + // Arrange + var emailAddress = "complete.test@example.com"; + + // Create user first + var createCommand = new CreateUserCommand("Jane", "Smith", emailAddress); + await Host.Scenario(_ => + { + _.Post.Json(createCommand).ToUrl("/users"); + _.StatusCodeShouldBe(201); + }); + + // Update profile + var profileCommand = new UpdateUserProfileCommand( + EmailAddress: emailAddress, + FirstName: "Jane", + LastName: "Smith-Updated", + PhoneNumber: "+1-555-987-6543", + ProfilePictureUrl: "https://example.com/jane.jpg" + ); + + await Host.Scenario(_ => + { + _.Post.Json(profileCommand).ToUrl($"/users/profile/{emailAddress}"); + _.StatusCodeShouldBe(204); + }); + + // Update address + var addressCommand = new UpdateUserAddressCommand( + EmailAddress: emailAddress, + Street1: "456 Oak Avenue", + Street2: null, + City: "San Francisco", + State: "CA", + ZipCode: "94102", + Country: "USA" + ); + + await Host.Scenario(_ => + { + _.Post.Json(addressCommand).ToUrl($"/users/address/{emailAddress}"); + _.StatusCodeShouldBe(204); + }); + + // Update preferences + var preferencesCommand = new UpdateUserPreferencesCommand( + EmailAddress: emailAddress, + ReceiveEmailNotifications: false, + ReceiveSmsNotifications: true, + PreferredLanguage: "es-ES", + TimeZone: "America/Los_Angeles", + Theme: "Light" + ); + + await Host.Scenario(_ => + { + _.Post.Json(preferencesCommand).ToUrl($"/users/preferences/{emailAddress}"); + _.StatusCodeShouldBe(204); + }); + + // Verify user data + var user = await Host.Scenario(_ => + { + _.Get.Url($"/users/{emailAddress}"); + _.StatusCodeShouldBe(200); + }); + } + + [Fact] + public void UpdateUserProfileCommand_WithInvalidEmail_ShouldFailValidation() + { + // Arrange + var command = new UpdateUserProfileCommand( + EmailAddress: "invalid-email", + FirstName: "John", + LastName: "Doe", + PhoneNumber: "+1-555-123-4567" + ); + + // Act & Assert + var validator = new UpdateUserProfileCommandValidator(); + var result = validator.Validate(command); + + result.IsValid.ShouldBeFalse(); + result.Errors.ShouldContain(e => e.ErrorMessage.Contains("Email address")); + } + + [Fact] + public void UpdateUserAddressCommand_WithInvalidEmail_ShouldFailValidation() + { + // Arrange + var command = new UpdateUserAddressCommand( + EmailAddress: "", + Street1: "123 Main St", + Street2: null, + City: "New York", + State: "NY", + ZipCode: "10001", + Country: "USA" + ); + + // Act & Assert + var validator = new UpdateUserAddressCommandValidator(); + var result = validator.Validate(command); + + result.IsValid.ShouldBeFalse(); + result.Errors.ShouldContain(e => e.ErrorMessage.Contains("Email address")); + } + + [Fact] + public void UpdateUserPreferencesCommand_WithInvalidEmail_ShouldFailValidation() + { + // Arrange + var command = new UpdateUserPreferencesCommand( + EmailAddress: null!, + ReceiveEmailNotifications: true, + ReceiveSmsNotifications: false, + PreferredLanguage: "en-US", + TimeZone: "UTC", + Theme: "Light" + ); + + // Act & Assert + var validator = new UpdateUserPreferencesCommandValidator(); + var result = validator.Validate(command); + + result.IsValid.ShouldBeFalse(); + result.Errors.ShouldContain(e => e.ErrorMessage.Contains("Email address")); + } +} diff --git a/src/EventServer/Aggregates/Users/Commands/UserCommands.cs b/src/EventServer/Aggregates/Users/Commands/UserCommands.cs index 692e29b..0065b26 100644 --- a/src/EventServer/Aggregates/Users/Commands/UserCommands.cs +++ b/src/EventServer/Aggregates/Users/Commands/UserCommands.cs @@ -18,6 +18,33 @@ public record UserLoggedInCommand(string EmailAddress, DateTime LoginDate): IUse [Serializable] public record UserLoggedOutCommand(string EmailAddress, DateTime LogoutDate): IUserCommand {} +[Serializable] +public record UpdateUserProfileCommand( + string EmailAddress, + string? FirstName, + string? LastName, + string? PhoneNumber, + string? ProfilePictureUrl = null): IUserCommand {} + +[Serializable] +public record UpdateUserAddressCommand( + string EmailAddress, + string? Street1, + string? Street2, + string? City, + string? State, + string? ZipCode, + string? Country): IUserCommand {} + +[Serializable] +public record UpdateUserPreferencesCommand( + string EmailAddress, + bool ReceiveEmailNotifications, + bool ReceiveSmsNotifications, + string? PreferredLanguage, + string? TimeZone, + string? Theme): IUserCommand {} + public class CreateUserCommandValdator : AbstractValidator { public CreateUserCommandValdator() @@ -50,3 +77,42 @@ public AddVideoConferenceToUserCommandValidator() .WithMessage("Conference Id is required."); } } + +public class UpdateUserProfileCommandValidator + : AbstractValidator +{ + public UpdateUserProfileCommandValidator() + { + RuleFor(command => command.EmailAddress) + .NotNull() + .NotEmpty() + .EmailAddress() + .WithMessage("Email address is required and must be valid."); + } +} + +public class UpdateUserAddressCommandValidator + : AbstractValidator +{ + public UpdateUserAddressCommandValidator() + { + RuleFor(command => command.EmailAddress) + .NotNull() + .NotEmpty() + .EmailAddress() + .WithMessage("Email address is required and must be valid."); + } +} + +public class UpdateUserPreferencesCommandValidator + : AbstractValidator +{ + public UpdateUserPreferencesCommandValidator() + { + RuleFor(command => command.EmailAddress) + .NotNull() + .NotEmpty() + .EmailAddress() + .WithMessage("Email address is required and must be valid."); + } +} diff --git a/src/EventServer/Aggregates/Users/Events/UserEvents.cs b/src/EventServer/Aggregates/Users/Events/UserEvents.cs index 8e88001..de7a723 100644 --- a/src/EventServer/Aggregates/Users/Events/UserEvents.cs +++ b/src/EventServer/Aggregates/Users/Events/UserEvents.cs @@ -15,3 +15,30 @@ public record UserLoggedInEvent(string EmailAddress, DateTime LoginTime): IUserE [Serializable] public record UserLoggedOutEvent(string EmailAddress, DateTime LogoutTime): IUserEvent; + +[Serializable] +public record UserProfileUpdatedEvent( + string EmailAddress, + string? FirstName, + string? LastName, + string? PhoneNumber, + string? ProfilePictureUrl): IUserEvent; + +[Serializable] +public record UserAddressUpdatedEvent( + string EmailAddress, + string? Street1, + string? Street2, + string? City, + string? State, + string? ZipCode, + string? Country): IUserEvent; + +[Serializable] +public record UserPreferencesUpdatedEvent( + string EmailAddress, + bool ReceiveEmailNotifications, + bool ReceiveSmsNotifications, + string? PreferredLanguage, + string? TimeZone, + string? Theme): IUserEvent; diff --git a/src/EventServer/Aggregates/Users/UserHandler.cs b/src/EventServer/Aggregates/Users/UserHandler.cs index d331a29..e769d0d 100644 --- a/src/EventServer/Aggregates/Users/UserHandler.cs +++ b/src/EventServer/Aggregates/Users/UserHandler.cs @@ -26,4 +26,40 @@ public static void Handle(VideoConferenceAddedToUserEvent @event, User user) { user.VideoConferences.AddRange(@event.conferenceId); user.UpdateDate = DateTime.Now; } + + public static void Handle(UserProfileUpdatedEvent @event, User user) { + Log.Information("UserHandler: Applying {type} to {EmailAddress}", typeof(UserProfileUpdatedEvent), @event.EmailAddress); + user.FirstName = @event.FirstName; + user.LastName = @event.LastName; + user.PhoneNumber = @event.PhoneNumber; + user.ProfilePictureUrl = @event.ProfilePictureUrl; + user.UpdateDate = DateTime.Now; + } + + public static void Handle(UserAddressUpdatedEvent @event, User user) { + Log.Information("UserHandler: Applying {type} to {EmailAddress}", typeof(UserAddressUpdatedEvent), @event.EmailAddress); + if (user.Address == null) { + user.Address = new Address(); + } + user.Address.Street1 = @event.Street1; + user.Address.Street2 = @event.Street2; + user.Address.City = @event.City; + user.Address.State = @event.State; + user.Address.ZipCode = @event.ZipCode; + user.Address.Country = @event.Country; + user.UpdateDate = DateTime.Now; + } + + public static void Handle(UserPreferencesUpdatedEvent @event, User user) { + Log.Information("UserHandler: Applying {type} to {EmailAddress}", typeof(UserPreferencesUpdatedEvent), @event.EmailAddress); + if (user.Preferences == null) { + user.Preferences = new UserPreferences(); + } + user.Preferences.ReceiveEmailNotifications = @event.ReceiveEmailNotifications; + user.Preferences.ReceiveSmsNotifications = @event.ReceiveSmsNotifications; + user.Preferences.PreferredLanguage = @event.PreferredLanguage; + user.Preferences.TimeZone = @event.TimeZone; + user.Preferences.Theme = @event.Theme; + user.UpdateDate = DateTime.Now; + } } diff --git a/src/EventServer/Aggregates/Users/UserProjection.cs b/src/EventServer/Aggregates/Users/UserProjection.cs index 92590df..67bfd95 100644 --- a/src/EventServer/Aggregates/Users/UserProjection.cs +++ b/src/EventServer/Aggregates/Users/UserProjection.cs @@ -49,4 +49,49 @@ public static User Apply(UserLoggedOutEvent @event, User user) return user; } + + public static User Apply(UserProfileUpdatedEvent @event, User user) + { + Log.Information("User Projection: Applying {type} to {EmailAddress}", typeof(UserProfileUpdatedEvent), @event.EmailAddress); + user.FirstName = @event.FirstName; + user.LastName = @event.LastName; + user.PhoneNumber = @event.PhoneNumber; + user.ProfilePictureUrl = @event.ProfilePictureUrl; + user.UpdateDate = DateTime.Now; + + return user; + } + + public static User Apply(UserAddressUpdatedEvent @event, User user) + { + Log.Information("User Projection: Applying {type} to {EmailAddress}", typeof(UserAddressUpdatedEvent), @event.EmailAddress); + if (user.Address == null) { + user.Address = new Address(); + } + user.Address.Street1 = @event.Street1; + user.Address.Street2 = @event.Street2; + user.Address.City = @event.City; + user.Address.State = @event.State; + user.Address.ZipCode = @event.ZipCode; + user.Address.Country = @event.Country; + user.UpdateDate = DateTime.Now; + + return user; + } + + public static User Apply(UserPreferencesUpdatedEvent @event, User user) + { + Log.Information("User Projection: Applying {type} to {EmailAddress}", typeof(UserPreferencesUpdatedEvent), @event.EmailAddress); + if (user.Preferences == null) { + user.Preferences = new UserPreferences(); + } + user.Preferences.ReceiveEmailNotifications = @event.ReceiveEmailNotifications; + user.Preferences.ReceiveSmsNotifications = @event.ReceiveSmsNotifications; + user.Preferences.PreferredLanguage = @event.PreferredLanguage; + user.Preferences.TimeZone = @event.TimeZone; + user.Preferences.Theme = @event.Theme; + user.UpdateDate = DateTime.Now; + + return user; + } } diff --git a/src/EventServer/Controllers/UserController.cs b/src/EventServer/Controllers/UserController.cs index 8c0a674..ce1d40a 100644 --- a/src/EventServer/Controllers/UserController.cs +++ b/src/EventServer/Controllers/UserController.cs @@ -77,4 +77,58 @@ public static User GetUser([Document("EmailAddress")] User user) Log.Information("Getting user {emailAddress}", user.EmailAddress); return user; } + + [WolverinePost("/users/profile/{userId}")] + [EmptyResponse] + public static UserProfileUpdatedEvent UpdateUserProfile( + [FromBody] UpdateUserProfileCommand command, + [Aggregate] User user + ) + { + Log.Information("Updating profile for user {emailAddress}", command.EmailAddress); + return new UserProfileUpdatedEvent( + command.EmailAddress, + command.FirstName, + command.LastName, + command.PhoneNumber, + command.ProfilePictureUrl + ); + } + + [WolverinePost("/users/address/{userId}")] + [EmptyResponse] + public static UserAddressUpdatedEvent UpdateUserAddress( + [FromBody] UpdateUserAddressCommand command, + [Aggregate] User user + ) + { + Log.Information("Updating address for user {emailAddress}", command.EmailAddress); + return new UserAddressUpdatedEvent( + command.EmailAddress, + command.Street1, + command.Street2, + command.City, + command.State, + command.ZipCode, + command.Country + ); + } + + [WolverinePost("/users/preferences/{userId}")] + [EmptyResponse] + public static UserPreferencesUpdatedEvent UpdateUserPreferences( + [FromBody] UpdateUserPreferencesCommand command, + [Aggregate] User user + ) + { + Log.Information("Updating preferences for user {emailAddress}", command.EmailAddress); + return new UserPreferencesUpdatedEvent( + command.EmailAddress, + command.ReceiveEmailNotifications, + command.ReceiveSmsNotifications, + command.PreferredLanguage, + command.TimeZone, + command.Theme + ); + } } diff --git a/src/FxExpert.Blazor/FxExpert.Blazor.Client/Pages/UserProfile.razor b/src/FxExpert.Blazor/FxExpert.Blazor.Client/Pages/UserProfile.razor new file mode 100644 index 0000000..dfbd3f4 --- /dev/null +++ b/src/FxExpert.Blazor/FxExpert.Blazor.Client/Pages/UserProfile.razor @@ -0,0 +1,349 @@ +@page "/profile" +@using Fortium.Types +@using Microsoft.AspNetCore.Authorization +@using Microsoft.AspNetCore.Components.Authorization +@using System.Net.Http.Json +@using System.Security.Claims +@using System.ComponentModel.DataAnnotations +@using FxExpert.Blazor.Client.Services +@inject UserService UserService +@inject NavigationManager Navigation +@inject ISnackbar Snackbar +@inject AuthenticationStateProvider AuthenticationStateProvider +@attribute [Authorize] + +User Profile + + + + + Profile Information + + @if (isLoading) + { + + } + else + { + + + + + + + + + + + + + + + + + + + Save Changes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Save Address + + + + + + + + + + + + + + + + + English (US) + Spanish + French + + + + UTC + Eastern Time (US & Canada) + Central Time (US & Canada) + Mountain Time (US & Canada) + Pacific Time (US & Canada) + + + + Light + Dark + System Default + + + + Save Preferences + + + + + + } + + + + +@code { + private bool isLoading = true; + private string userEmail = ""; + + // Model classes for the form + private class ProfileModel + { + [Required(ErrorMessage = "Email address is required")] + [EmailAddress(ErrorMessage = "Invalid email format")] + public string? EmailAddress { get; set; } + + [Required(ErrorMessage = "First name is required")] + [StringLength(50, ErrorMessage = "First name must be less than 50 characters")] + public string? FirstName { get; set; } + + [Required(ErrorMessage = "Last name is required")] + [StringLength(50, ErrorMessage = "Last name must be less than 50 characters")] + public string? LastName { get; set; } + + [Phone(ErrorMessage = "Invalid phone number format")] + public string? PhoneNumber { get; set; } + + [Url(ErrorMessage = "Invalid URL format")] + public string? ProfilePictureUrl { get; set; } + } + + private class AddressModel + { + public string? Street1 { get; set; } + public string? Street2 { get; set; } + public string? City { get; set; } + public string? State { get; set; } + public string? ZipCode { get; set; } + public string? Country { get; set; } + } + + private class PreferencesModel + { + public bool ReceiveEmailNotifications { get; set; } = true; + public bool ReceiveSmsNotifications { get; set; } = false; + public string? PreferredLanguage { get; set; } = "en-US"; + public string? TimeZone { get; set; } = "UTC"; + public string? Theme { get; set; } = "Light"; + } + + private ProfileModel profileModel = new(); + private AddressModel addressModel = new(); + private PreferencesModel preferencesModel = new(); + + protected override async Task OnInitializedAsync() + { + try + { + // Get the current user's email from claims + var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync(); + var user = authState.User; + userEmail = user.FindFirst(ClaimTypes.Email)?.Value ?? "test@example.com"; + + // Ensure userEmail is never null to prevent null reference warnings + if (userEmail == null) + { + userEmail = "test@example.com"; + } + + if (string.IsNullOrEmpty(userEmail)) + { + Navigation.NavigateTo("/login"); + return; + } + + await LoadUserDataAsync(); + } + catch (Exception ex) + { + Snackbar.Add($"Error loading profile: {ex.Message}", Severity.Error); + } + finally + { + isLoading = false; + } + } + + private async Task LoadUserDataAsync() + { + try + { + // Load user profile data + var userData = await UserService.GetUserProfileAsync(userEmail); + + if (userData != null) + { + // Map user data to form models + profileModel.EmailAddress = userData.EmailAddress; + profileModel.FirstName = userData.FirstName; + profileModel.LastName = userData.LastName; + profileModel.PhoneNumber = userData.PhoneNumber; + profileModel.ProfilePictureUrl = userData.ProfilePictureUrl; + + if (userData.Address != null) + { + addressModel.Street1 = userData.Address.Street1; + addressModel.Street2 = userData.Address.Street2; + addressModel.City = userData.Address.City; + addressModel.State = userData.Address.State; + addressModel.ZipCode = userData.Address.ZipCode; + addressModel.Country = userData.Address.Country; + } + + if (userData.Preferences != null) + { + preferencesModel.ReceiveEmailNotifications = userData.Preferences.ReceiveEmailNotifications; + preferencesModel.ReceiveSmsNotifications = userData.Preferences.ReceiveSmsNotifications; + preferencesModel.PreferredLanguage = userData.Preferences.PreferredLanguage; + preferencesModel.TimeZone = userData.Preferences.TimeZone; + preferencesModel.Theme = userData.Preferences.Theme; + } + } + } + catch (Exception ex) + { + Snackbar.Add($"Error loading user data: {ex.Message}", Severity.Error); + } + } + + private async Task SaveProfileAsync() + { + try + { + var success = await UserService.UpdateUserProfileAsync( + userEmail, + profileModel.FirstName, + profileModel.LastName, + profileModel.PhoneNumber, + profileModel.ProfilePictureUrl + ); + + if (success) + { + Snackbar.Add("Profile updated successfully", Severity.Success); + } + else + { + Snackbar.Add("Failed to update profile", Severity.Error); + } + } + catch (Exception ex) + { + Snackbar.Add($"Error updating profile: {ex.Message}", Severity.Error); + } + } + + private async Task SaveAddressAsync() + { + try + { + var success = await UserService.UpdateUserAddressAsync( + userEmail, + addressModel.Street1, + addressModel.Street2, + addressModel.City, + addressModel.State, + addressModel.ZipCode, + addressModel.Country + ); + + if (success) + { + Snackbar.Add("Address updated successfully", Severity.Success); + } + else + { + Snackbar.Add("Failed to update address", Severity.Error); + } + } + catch (Exception ex) + { + Snackbar.Add($"Error updating address: {ex.Message}", Severity.Error); + } + } + + private async Task SavePreferencesAsync() + { + try + { + var success = await UserService.UpdateUserPreferencesAsync( + userEmail, + preferencesModel.ReceiveEmailNotifications, + preferencesModel.ReceiveSmsNotifications, + preferencesModel.PreferredLanguage, + preferencesModel.TimeZone, + preferencesModel.Theme + ); + + if (success) + { + Snackbar.Add("Preferences updated successfully", Severity.Success); + } + else + { + Snackbar.Add("Failed to update preferences", Severity.Error); + } + } + catch (Exception ex) + { + Snackbar.Add($"Error updating preferences: {ex.Message}", Severity.Error); + } + } +} \ No newline at end of file diff --git a/src/FxExpert.Blazor/FxExpert.Blazor.Client/Program.cs b/src/FxExpert.Blazor/FxExpert.Blazor.Client/Program.cs index 373fe0d..dcaf0bb 100644 --- a/src/FxExpert.Blazor/FxExpert.Blazor.Client/Program.cs +++ b/src/FxExpert.Blazor/FxExpert.Blazor.Client/Program.cs @@ -5,9 +5,24 @@ var builder = WebAssemblyHostBuilder.CreateDefault(args); -builder.Services.AddMudServices(); +builder.Services.AddMudServices(config => +{ + config.SnackbarConfiguration.PositionClass = MudBlazor.Defaults.Classes.Position.BottomRight; + config.SnackbarConfiguration.PreventDuplicates = false; + config.SnackbarConfiguration.NewestOnTop = false; + config.SnackbarConfiguration.ShowCloseIcon = true; + config.SnackbarConfiguration.VisibleStateDuration = 10000; + config.SnackbarConfiguration.HideTransitionDuration = 500; + config.SnackbarConfiguration.ShowTransitionDuration = 500; +}); builder.Services.AddTransient(); +// Configure HttpClient +builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); + +// Register services +builder.Services.AddScoped(); + // Add authentication and authorization builder.Services.AddAuthorizationCore(); diff --git a/src/FxExpert.Blazor/FxExpert.Blazor.Client/Services/UserService.cs b/src/FxExpert.Blazor/FxExpert.Blazor.Client/Services/UserService.cs new file mode 100644 index 0000000..c3406ed --- /dev/null +++ b/src/FxExpert.Blazor/FxExpert.Blazor.Client/Services/UserService.cs @@ -0,0 +1,133 @@ +using System.Net.Http.Json; +using Fortium.Types; + +namespace FxExpert.Blazor.Client.Services; + +public class UserService +{ + private readonly HttpClient _httpClient; + + public UserService(HttpClient httpClient) + { + _httpClient = httpClient; + } + + public async Task GetUserProfileAsync(string emailAddress) + { + if (string.IsNullOrEmpty(emailAddress)) + { + Console.WriteLine("Email address is null or empty"); + return null; + } + + try + { + return await _httpClient.GetFromJsonAsync($"api/users/{emailAddress}"); + } + catch (Exception ex) + { + Console.WriteLine($"Error fetching user profile: {ex.Message}"); + return null; + } + } + + public async Task UpdateUserProfileAsync(string emailAddress, string? firstName, string? lastName, string? phoneNumber, string? profilePictureUrl) + { + if (string.IsNullOrEmpty(emailAddress)) + { + Console.WriteLine("Email address is null or empty"); + return false; + } + + try + { + var response = await _httpClient.PostAsJsonAsync($"api/users/profile/{emailAddress}", new + { + EmailAddress = emailAddress, + FirstName = firstName, + LastName = lastName, + PhoneNumber = phoneNumber, + ProfilePictureUrl = profilePictureUrl + }); + + return response.IsSuccessStatusCode; + } + catch (Exception ex) + { + Console.WriteLine($"Error updating user profile: {ex.Message}"); + return false; + } + } + + public async Task UpdateUserAddressAsync( + string emailAddress, + string? street1, + string? street2, + string? city, + string? state, + string? zipCode, + string? country) + { + if (string.IsNullOrEmpty(emailAddress)) + { + Console.WriteLine("Email address is null or empty"); + return false; + } + + try + { + var response = await _httpClient.PostAsJsonAsync($"api/users/address/{emailAddress}", new + { + EmailAddress = emailAddress, + Street1 = street1, + Street2 = street2, + City = city, + State = state, + ZipCode = zipCode, + Country = country + }); + + return response.IsSuccessStatusCode; + } + catch (Exception ex) + { + Console.WriteLine($"Error updating user address: {ex.Message}"); + return false; + } + } + + public async Task UpdateUserPreferencesAsync( + string emailAddress, + bool receiveEmailNotifications, + bool receiveSmsNotifications, + string? preferredLanguage, + string? timeZone, + string? theme) + { + if (string.IsNullOrEmpty(emailAddress)) + { + Console.WriteLine("Email address is null or empty"); + return false; + } + + try + { + var response = await _httpClient.PostAsJsonAsync($"api/users/preferences/{emailAddress}", new + { + EmailAddress = emailAddress, + ReceiveEmailNotifications = receiveEmailNotifications, + ReceiveSmsNotifications = receiveSmsNotifications, + PreferredLanguage = preferredLanguage, + TimeZone = timeZone, + Theme = theme + }); + + return response.IsSuccessStatusCode; + } + catch (Exception ex) + { + Console.WriteLine($"Error updating user preferences: {ex.Message}"); + return false; + } + } +} \ No newline at end of file diff --git a/src/FxExpert.Blazor/FxExpert.Blazor.Client/_Imports.razor b/src/FxExpert.Blazor/FxExpert.Blazor.Client/_Imports.razor index 6d3e885..5e962d8 100644 --- a/src/FxExpert.Blazor/FxExpert.Blazor.Client/_Imports.razor +++ b/src/FxExpert.Blazor/FxExpert.Blazor.Client/_Imports.razor @@ -13,4 +13,6 @@ @using Fortium.Types @using FxExpert.Blazor.Client @using Microsoft.AspNetCore.Authorization +@inject AuthenticationStateProvider AuthenticationStateProvider +@inject ISnackbar Snackbar @attribute [Authorize] \ No newline at end of file diff --git a/src/FxExpert.Blazor/FxExpert.Blazor/FxExpert.Blazor.csproj b/src/FxExpert.Blazor/FxExpert.Blazor/FxExpert.Blazor.csproj index 6ebf7d9..cf1de07 100644 --- a/src/FxExpert.Blazor/FxExpert.Blazor/FxExpert.Blazor.csproj +++ b/src/FxExpert.Blazor/FxExpert.Blazor/FxExpert.Blazor.csproj @@ -7,22 +7,21 @@ aspnet-FxExpert.Blazor-cce2c8ea-02bd-422f-b9ee-ce707124478f - + - - - - - - - - - - - - - + + + + + + + + + + + + diff --git a/src/FxExpert.Blazor/FxExpert.Blazor/Program.cs b/src/FxExpert.Blazor/FxExpert.Blazor/Program.cs index e90679c..0a8115d 100644 --- a/src/FxExpert.Blazor/FxExpert.Blazor/Program.cs +++ b/src/FxExpert.Blazor/FxExpert.Blazor/Program.cs @@ -55,212 +55,229 @@ CookieAuthenticationDefaults.AuthenticationScheme, options => { - options.Cookie.HttpOnly = true; - options.Cookie.SameSite = SameSiteMode.Lax; - options.Cookie.SecurePolicy = CookieSecurePolicy.Always; + options.Cookie.HttpOnly = true; + options.Cookie.SameSite = SameSiteMode.Lax; + options.Cookie.SecurePolicy = CookieSecurePolicy.Always; } ) .AddOpenIdConnect( MS_OIDC_SCHEME, options => { - // Get settings from configuration - var config = builder.Configuration.GetSection("OpenIdConnect"); - options.Authority = config["Authority"]; - options.ClientId = config["ClientId"]; - options.ClientSecret = config["ClientSecret"]; - options.MapInboundClaims = false; - options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; - - options.ResponseType = OpenIdConnectResponseType.Code; - options.RequireHttpsMetadata = config.GetValue("RequireHttpsMetadata"); - options.UsePkce = config.GetValue("UsePkce"); - options.SaveTokens = config.GetValue("SaveTokens"); - options.GetClaimsFromUserInfoEndpoint = config.GetValue( - "GetClaimsFromUserInfoEndpoint" - ); - - // Set the callback paths - options.CallbackPath = config["CallbackPath"] ?? "/signin-oidc"; - options.SignedOutCallbackPath = - config["SignedOutCallbackPath"] ?? "/signout-callback-oidc"; - options.RemoteSignOutPath = config["RemoteSignOutPath"] ?? "/signout-oidc"; - - // Log the configuration for debugging - var callbackPath = options.CallbackPath; - var signedOutCallbackPath = options.SignedOutCallbackPath; - var remoteSignOutPath = options.RemoteSignOutPath; - var errorPath = config["ErrorPath"] ?? "/authentication-failed"; - - Console.WriteLine("OIDC Configuration:"); - Console.WriteLine($"Authority: {options.Authority}"); - Console.WriteLine($"ClientId: {options.ClientId}"); - Console.WriteLine($"CallbackPath: {callbackPath}"); - Console.WriteLine($"SignedOutCallbackPath: {signedOutCallbackPath}"); - Console.WriteLine($"RemoteSignOutPath: {remoteSignOutPath}"); - Console.WriteLine($"ErrorPath: {errorPath}"); - - // Get host from configuration or use default - var scheme = builder.Environment.IsDevelopment() ? "http" : "https"; - var host = builder.Configuration["ApplicationUrl"] ?? "localhost:8500"; - - // Extract host from applicationUrl in launchSettings if available - var urls = builder.WebHost.GetSetting("urls")?.Split(';'); - if (urls?.Length > 0 && !string.IsNullOrEmpty(urls[0])) + // Get settings from configuration + var config = builder.Configuration.GetSection("OpenIdConnect"); + options.Authority = config["Authority"]; + options.ClientId = config["ClientId"]; + options.ClientSecret = config["ClientSecret"]; + options.MapInboundClaims = false; + options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; + + options.ResponseType = OpenIdConnectResponseType.Code; + options.RequireHttpsMetadata = config.GetValue("RequireHttpsMetadata"); + options.UsePkce = config.GetValue("UsePkce"); + options.SaveTokens = config.GetValue("SaveTokens"); + options.GetClaimsFromUserInfoEndpoint = config.GetValue( + "GetClaimsFromUserInfoEndpoint" + ); + + // Set the callback paths + options.CallbackPath = config["CallbackPath"] ?? "/signin-oidc"; + options.SignedOutCallbackPath = + config["SignedOutCallbackPath"] ?? "/signout-callback-oidc"; + options.RemoteSignOutPath = config["RemoteSignOutPath"] ?? "/signout-oidc"; + + // Log the configuration for debugging + var callbackPath = options.CallbackPath; + var signedOutCallbackPath = options.SignedOutCallbackPath; + var remoteSignOutPath = options.RemoteSignOutPath; + var errorPath = config["ErrorPath"] ?? "/authentication-failed"; + + Console.WriteLine("OIDC Configuration:"); + Console.WriteLine($"Authority: {options.Authority}"); + Console.WriteLine($"ClientId: {options.ClientId}"); + Console.WriteLine($"CallbackPath: {callbackPath}"); + Console.WriteLine($"SignedOutCallbackPath: {signedOutCallbackPath}"); + Console.WriteLine($"RemoteSignOutPath: {remoteSignOutPath}"); + Console.WriteLine($"ErrorPath: {errorPath}"); + + // Get host and scheme from configuration or use default + var scheme = "http"; // Default fallback + var host = builder.Configuration["ApplicationUrl"] ?? "localhost:8500"; + + // Extract host and scheme from applicationUrl in launchSettings if available + var urls = builder.WebHost.GetSetting("urls")?.Split(';'); + if (urls?.Length > 0 && !string.IsNullOrEmpty(urls[0])) + { + var url = new Uri(urls[0]); + host = url.Authority; + scheme = url.Scheme; // Use the actual scheme from the URL + } + else if (!builder.Environment.IsDevelopment()) + { + // In production, default to HTTPS if no URL is specified + scheme = "https"; + } + + Console.WriteLine($"Application host: {host}"); + Console.WriteLine($"Redirect URI: {scheme}://{host}{callbackPath}"); + Console.WriteLine($"Signout Redirect URI: {scheme}://{host}{signedOutCallbackPath}"); + Console.WriteLine($"Error Redirect URI: {scheme}://{host}{errorPath}"); + + // Add scopes + foreach ( + var scope in config.GetSection("Scope").Get() ?? Array.Empty() + ) + { + Console.WriteLine($"Adding Scope: {scope}"); + options.Scope.Add(scope); + } + + // Disable PAR to avoid issues + options.PushedAuthorizationBehavior = PushedAuthorizationBehavior.Disable; + + // Configure token validation parameters + options.TokenValidationParameters = new TokenValidationParameters + { + NameClaimType = config["TokenValidationParameters:NameClaimType"] ?? "name", + RoleClaimType = config["TokenValidationParameters:RoleClaimType"] ?? "role", + ValidateIssuer = true, + ValidateAudience = false, + ValidateLifetime = true, + ClockSkew = TimeSpan.FromMinutes(5) + }; + + // Custom handling for roles and errors + options.Events = new OpenIdConnectEvents + { + OnRedirectToIdentityProvider = context => { - var url = new Uri(urls[0]); - host = url.Authority; - } - - Console.WriteLine($"Application host: {host}"); - Console.WriteLine($"Redirect URI: {scheme}://{host}{callbackPath}"); - Console.WriteLine($"Signout Redirect URI: {scheme}://{host}{signedOutCallbackPath}"); - Console.WriteLine($"Error Redirect URI: {scheme}://{host}{errorPath}"); - - // Add scopes - foreach ( - var scope in config.GetSection("Scope").Get() ?? Array.Empty() - ) + // Handle any redirects to identity provider + Console.WriteLine($"Redirecting to: {context.ProtocolMessage.AuthorizationEndpoint}"); + return Task.CompletedTask; + }, + OnTokenValidated = context => { - Console.WriteLine($"Adding Scope: {scope}"); - options.Scope.Add(scope); - } + // Map Keycloak roles to claims + var identity = context.Principal?.Identity as ClaimsIdentity; + if (identity == null) + return Task.CompletedTask; + + Console.WriteLine("OnTokenValidated: Token validated successfully"); + Console.WriteLine($"Identity Name: {identity.Name}"); + + // Log all claims in the token + Console.WriteLine("Claims in the token:"); + if (context.Principal != null) + { + foreach (var claim in context.Principal.Claims) + Console.WriteLine($"Claim Type: {claim.Type}, Value: {claim.Value}"); + } + + // Look for Keycloak-specific role claims + // 1. Check standard resource_access claim from Keycloak + var resourceAccessClaim = context.Principal?.FindFirst("resource_access"); + if (resourceAccessClaim != null) + { + Console.WriteLine( + $"Found resource_access claim: {resourceAccessClaim.Value}" + ); - // Configure token validation parameters - options.TokenValidationParameters = new TokenValidationParameters - { - NameClaimType = config["TokenValidationParameters:NameClaimType"] ?? "name", - RoleClaimType = config["TokenValidationParameters:RoleClaimType"] ?? "role", - ValidateIssuer = true, - ValidateAudience = false - }; - - // Custom handling for roles and errors - options.Events = new OpenIdConnectEvents - { - OnTokenValidated = context => + // Parse the JSON and extract roles + try { - // Map Keycloak roles to claims - var identity = context.Principal?.Identity as ClaimsIdentity; - if (identity == null) - return Task.CompletedTask; - - Console.WriteLine("OnTokenValidated: Token validated successfully"); - Console.WriteLine($"Identity Name: {identity.Name}"); - - // Log all claims in the token - Console.WriteLine("Claims in the token:"); - if (context.Principal != null) + var resourceAccess = JsonSerializer.Deserialize< + Dictionary + >(resourceAccessClaim.Value ?? "{}"); + if (resourceAccess != null && resourceAccess.ContainsKey("fx-expert")) + { + var clientRoles = JsonSerializer.Deserialize< + Dictionary + >(resourceAccess["fx-expert"].ToString() ?? "{}"); + + if (clientRoles != null && clientRoles.ContainsKey("roles")) { - foreach (var claim in context.Principal.Claims) - Console.WriteLine($"Claim Type: {claim.Type}, Value: {claim.Value}"); + var roles = JsonSerializer.Deserialize( + clientRoles["roles"].ToString() ?? "[]" + ) ?? Array.Empty(); + foreach (var role in roles) + { + Console.WriteLine($"Adding client role: {role}"); + identity.AddClaim(new Claim("Role", role)); + } } + } + } + catch (Exception ex) + { + Console.WriteLine($"Error parsing resource_access claim: {ex.Message}"); + } + } - // Look for Keycloak-specific role claims - // 1. Check standard resource_access claim from Keycloak - var resourceAccessClaim = context.Principal?.FindFirst("resource_access"); - if (resourceAccessClaim != null) - { - Console.WriteLine( - $"Found resource_access claim: {resourceAccessClaim.Value}" - ); - - // Parse the JSON and extract roles - try - { - var resourceAccess = JsonSerializer.Deserialize< - Dictionary - >(resourceAccessClaim.Value ?? "{}"); - if (resourceAccess != null && resourceAccess.ContainsKey("fx-expert")) - { - var clientRoles = JsonSerializer.Deserialize< - Dictionary - >(resourceAccess["fx-expert"].ToString() ?? "{}"); - - if (clientRoles != null && clientRoles.ContainsKey("roles")) - { - var roles = JsonSerializer.Deserialize( - clientRoles["roles"].ToString() ?? "[]" - ) ?? Array.Empty(); - foreach (var role in roles) - { - Console.WriteLine($"Adding client role: {role}"); - identity.AddClaim(new Claim("Role", role)); - } - } - } - } - catch (Exception ex) - { - Console.WriteLine($"Error parsing resource_access claim: {ex.Message}"); - } - } + // 2. Check realm_access claim from Keycloak + var realmAccessClaim = context.Principal?.FindFirst("realm_access"); + if (realmAccessClaim != null) + { + Console.WriteLine($"Found realm_access claim: {realmAccessClaim.Value}"); - // 2. Check realm_access claim from Keycloak - var realmAccessClaim = context.Principal?.FindFirst("realm_access"); - if (realmAccessClaim != null) + try + { + var realmAccess = JsonSerializer.Deserialize< + Dictionary + >(realmAccessClaim.Value ?? "{}"); + if (realmAccess != null && realmAccess.ContainsKey("roles")) + { + var roles = JsonSerializer.Deserialize( + realmAccess["roles"].ToString() ?? "[]" + ) ?? Array.Empty(); + foreach (var role in roles) { - Console.WriteLine($"Found realm_access claim: {realmAccessClaim.Value}"); - - try - { - var realmAccess = JsonSerializer.Deserialize< - Dictionary - >(realmAccessClaim.Value ?? "{}"); - if (realmAccess != null && realmAccess.ContainsKey("roles")) - { - var roles = JsonSerializer.Deserialize( - realmAccess["roles"].ToString() ?? "[]" - ) ?? Array.Empty(); - foreach (var role in roles) - { - Console.WriteLine($"Adding realm role: {role}"); - identity.AddClaim(new Claim(ClaimTypes.Role, role)); - } - } - } - catch (Exception ex) - { - Console.WriteLine($"Error parsing realm_access claim: {ex.Message}"); - } + Console.WriteLine($"Adding realm role: {role}"); + identity.AddClaim(new Claim(ClaimTypes.Role, role)); } + } + } + catch (Exception ex) + { + Console.WriteLine($"Error parsing realm_access claim: {ex.Message}"); + } + } - // 3. Fall back to the specified role claim type - var roleClaimType = config["TokenValidationParameters:RoleClaimType"] ?? "role"; - Console.WriteLine($"Looking for role claims with type: {roleClaimType}"); + // 3. Fall back to the specified role claim type + var roleClaimType = config["TokenValidationParameters:RoleClaimType"] ?? "role"; + Console.WriteLine($"Looking for role claims with type: {roleClaimType}"); - var newRoleClaims = context.Principal?.Claims - .Where(claim => claim.Type.Contains(roleClaimType)) - .Select(claim => new Claim(ClaimTypes.Role, claim.Value)) - .ToList() ?? new List(); + var newRoleClaims = context.Principal?.Claims + .Where(claim => claim.Type.Contains(roleClaimType)) + .Select(claim => new Claim(ClaimTypes.Role, claim.Value)) + .ToList() ?? new List(); - foreach (var newRoleClaim in newRoleClaims) - { - Console.WriteLine( - $"Adding role from {roleClaimType} claim: {newRoleClaim.Value}" - ); - identity.AddClaim(newRoleClaim); - } + foreach (var newRoleClaim in newRoleClaims) + { + Console.WriteLine( + $"Adding role from {roleClaimType} claim: {newRoleClaim.Value}" + ); + identity.AddClaim(newRoleClaim); + } - // Display the final roles - Console.WriteLine("Final roles after mapping:"); - foreach (var claim in identity.Claims) - Console.WriteLine($"Role: {claim.Type}:{claim.Value}"); + // Display the final roles + Console.WriteLine("Final roles after mapping:"); + foreach (var claim in identity.Claims) + Console.WriteLine($"Role: {claim.Type}:{claim.Value}"); - return Task.CompletedTask; - }, + return Task.CompletedTask; + }, - // Handle authentication errors - OnRemoteFailure = context => - { - context.Response.Redirect( - "/authentication-failed?error=" - + Uri.EscapeDataString(context.Failure?.Message ?? "Unknown error") - ); - context.HandleResponse(); - return Task.CompletedTask; - } - }; + // Handle authentication errors + OnRemoteFailure = context => + { + context.Response.Redirect( + "/authentication-failed?error=" + + Uri.EscapeDataString(context.Failure?.Message ?? "Unknown error") + ); + context.HandleResponse(); + return Task.CompletedTask; + } + }; } ); @@ -278,13 +295,13 @@ var scope in config.GetSection("Scope").Get() ?? Array.Empty() // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { - app.UseWebAssemblyDebugging(); + app.UseWebAssemblyDebugging(); } else { - app.UseExceptionHandler("/Error", true); - // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. - app.UseHsts(); + app.UseExceptionHandler("/Error", true); + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + app.UseHsts(); } app.UseHttpsRedirection(); diff --git a/src/FxExpert.Blazor/FxExpert.Blazor/appsettings.Development.json b/src/FxExpert.Blazor/FxExpert.Blazor/appsettings.Development.json index b46e7bd..104aba7 100644 --- a/src/FxExpert.Blazor/FxExpert.Blazor/appsettings.Development.json +++ b/src/FxExpert.Blazor/FxExpert.Blazor/appsettings.Development.json @@ -30,15 +30,16 @@ "openid", "profile", "email", - "roles" + "roles", + "offline_access" ], "TokenValidationParameters": { "NameClaimType": "name", "RoleClaimType": "role" }, - "CallbackPath": "/auth/login", - "SignedOutCallbackPath": "/auth/logout", - "RemoteSignOutPath": "/auth/logout", + "CallbackPath": "/signin-oidc", + "SignedOutCallbackPath": "/signout-callback-oidc", + "RemoteSignOutPath": "/signout-oidc", "ErrorPath": "/authentication-failed" } }