diff --git a/EShopOnAbp.sln b/EShopOnAbp.sln
index e7a4264d..73d5919b 100644
--- a/EShopOnAbp.sln
+++ b/EShopOnAbp.sln
@@ -43,6 +43,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EShopOnAbp.BasketService",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EShopOnAbp.CmskitService.HttpApi.Host", "services\cmskit\src\EShopOnAbp.CmskitService.HttpApi.Host\EShopOnAbp.CmskitService.HttpApi.Host.csproj", "{D5B9D5A5-44AA-42F8-867C-95B54780C9DC}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EShopOnAbp.Keycloak.DbMigrator", "shared\EShopOnAbp.Keycloak.DbMigrator\EShopOnAbp.Keycloak.DbMigrator.csproj", "{774C6ADF-BDD0-431C-A9F3-8BAFD5A49C8C}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -113,6 +115,10 @@ Global
{D5B9D5A5-44AA-42F8-867C-95B54780C9DC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D5B9D5A5-44AA-42F8-867C-95B54780C9DC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D5B9D5A5-44AA-42F8-867C-95B54780C9DC}.Release|Any CPU.Build.0 = Release|Any CPU
+ {774C6ADF-BDD0-431C-A9F3-8BAFD5A49C8C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {774C6ADF-BDD0-431C-A9F3-8BAFD5A49C8C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {774C6ADF-BDD0-431C-A9F3-8BAFD5A49C8C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {774C6ADF-BDD0-431C-A9F3-8BAFD5A49C8C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -134,6 +140,7 @@ Global
{7AE4C613-780E-4DA5-9B57-76F0D40D5146} = {F415FFFD-E52D-4EBE-98DC-067C1EFFFFE3}
{E373DD66-3247-4D95-B325-064BBBC337B1} = {F415FFFD-E52D-4EBE-98DC-067C1EFFFFE3}
{D5B9D5A5-44AA-42F8-867C-95B54780C9DC} = {F415FFFD-E52D-4EBE-98DC-067C1EFFFFE3}
+ {774C6ADF-BDD0-431C-A9F3-8BAFD5A49C8C} = {B8B59303-2178-459B-91A8-DF353044E090}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {26F82565-C6A4-439D-93A4-3251E3E7D5B0}
diff --git a/apps/angular/src/environments/environment.ts b/apps/angular/src/environments/environment.ts
index a5f81a2f..293d9acc 100644
--- a/apps/angular/src/environments/environment.ts
+++ b/apps/angular/src/environments/environment.ts
@@ -9,11 +9,11 @@ export const environment = {
name: 'EShopOnAbp',
},
oAuthConfig: {
- issuer: 'https://localhost:44330',
+ issuer: 'http://localhost:8080/realms/master',
redirectUri: baseUrl,
clientId: 'Web',
responseType: 'code',
- scope: 'offline_access openid profile email phone AccountService IdentityService AdministrationService CatalogService OrderingService',
+ scope: 'offline_access openid profile email phone roles AdministrationService IdentityService BasketService CatalogService OrderingService PaymentService CmskitService',
//requireHttps: true,
},
apis: {
diff --git a/apps/public-web/src/EShopOnAbp.PublicWeb/EShopOnAbp.PublicWeb.csproj b/apps/public-web/src/EShopOnAbp.PublicWeb/EShopOnAbp.PublicWeb.csproj
index 37fe3838..0e9f9fdc 100644
--- a/apps/public-web/src/EShopOnAbp.PublicWeb/EShopOnAbp.PublicWeb.csproj
+++ b/apps/public-web/src/EShopOnAbp.PublicWeb/EShopOnAbp.PublicWeb.csproj
@@ -8,7 +8,6 @@
-
diff --git a/apps/public-web/src/EShopOnAbp.PublicWeb/EShopOnAbpPublicWebModule.cs b/apps/public-web/src/EShopOnAbp.PublicWeb/EShopOnAbpPublicWebModule.cs
index 42efd925..f36c9b6e 100644
--- a/apps/public-web/src/EShopOnAbp.PublicWeb/EShopOnAbpPublicWebModule.cs
+++ b/apps/public-web/src/EShopOnAbp.PublicWeb/EShopOnAbpPublicWebModule.cs
@@ -22,7 +22,14 @@ using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Polly;
using StackExchange.Redis;
using System;
+using System.Collections.Generic;
+using System.Linq;
using System.Net.Http.Headers;
+using System.Security.Claims;
+using Microsoft.AspNetCore.Authentication.OAuth.Claims;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Logging;
+using Microsoft.IdentityModel.Tokens;
using Volo.Abp;
using Volo.Abp.Account;
using Volo.Abp.AspNetCore.Authentication.OpenIdConnect;
@@ -37,11 +44,13 @@ using Volo.Abp.AspNetCore.SignalR;
using Volo.Abp.AutoMapper;
using Volo.Abp.Caching;
using Volo.Abp.Caching.StackExchangeRedis;
+using Volo.Abp.EventBus.Distributed;
using Volo.Abp.EventBus.RabbitMq;
using Volo.Abp.Http.Client;
using Volo.Abp.Http.Client.IdentityModel.Web;
using Volo.Abp.Modularity;
using Volo.Abp.MultiTenancy;
+using Volo.Abp.Security.Claims;
using Volo.Abp.UI.Navigation;
using Volo.Abp.UI.Navigation.Urls;
using Volo.Abp.VirtualFileSystem;
@@ -68,10 +77,7 @@ namespace EShopOnAbp.PublicWeb;
typeof(PaymentServiceHttpApiClientModule),
typeof(AbpAutoMapperModule),
typeof(CmskitServiceHttpApiClientModule),
- typeof(CmsKitDomainModule),
typeof(CmsKitPublicWebModule)
-
-
)]
public class EShopOnAbpPublicWebModule : AbpModule
{
@@ -142,26 +148,57 @@ public class EShopOnAbpPublicWebModule : AbpModule
.AddAbpOpenIdConnect("oidc", options =>
{
options.Authority = configuration["AuthServer:Authority"];
+ options.ClientId = configuration["AuthServer:ClientId"];
+ options.MetadataAddress = configuration["AuthServer:MetaAddress"];
options.RequireHttpsMetadata = Convert.ToBoolean(configuration["AuthServer:RequireHttpsMetadata"]);
options.ResponseType = OpenIdConnectResponseType.CodeIdToken;
-
- options.ClientId = configuration["AuthServer:ClientId"];
- options.ClientSecret = configuration["AuthServer:ClientSecret"];
-
- options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
-
- options.Scope.Add("role");
+ options.Scope.Add("openid");
+ options.Scope.Add("profile");
options.Scope.Add("email");
options.Scope.Add("phone");
- options.Scope.Add("AccountService");
+ options.Scope.Add("roles");
+ options.Scope.Add("offline_access");
+
options.Scope.Add("AdministrationService");
options.Scope.Add("BasketService");
options.Scope.Add("CatalogService");
options.Scope.Add("PaymentService");
options.Scope.Add("OrderingService");
options.Scope.Add("CmskitService");
+
+ options.SaveTokens = true;
+
+ //SameSite is needed for Chrome/Firefox, as they will give http error 500 back, if not set to unspecified.
+ // options.NonceCookie.SameSite = SameSiteMode.Unspecified;
+ // options.CorrelationCookie.SameSite = SameSiteMode.Unspecified;
+ //
+ options.TokenValidationParameters = new TokenValidationParameters
+ {
+ NameClaimType = "name",
+ RoleClaimType = ClaimTypes.Role,
+ ValidateIssuer = true
+ };
+
+ options.Events.OnAuthorizationCodeReceived = async (authContext) =>
+ {
+ var userLoggedInEto = CreateUserLoggedInEto(authContext.Principal, authContext.HttpContext);
+ if (userLoggedInEto != null)
+ {
+ var eventBus =
+ authContext.HttpContext.RequestServices.GetRequiredService();
+ await eventBus.PublishAsync(userLoggedInEto);
+ }
+ };
+
+ if (AbpClaimTypes.UserName != "preferred_username")
+ {
+ options.ClaimActions.MapJsonKey(AbpClaimTypes.UserName, "preferred_username");
+ options.ClaimActions.DeleteClaim("preferred_username");
+ options.ClaimActions.RemoveDuplicate(AbpClaimTypes.UserName);
+ }
});
+
if (Convert.ToBoolean(configuration["AuthServer:IsOnProd"]))
{
context.Services.Configure("oidc", options =>
@@ -173,18 +210,21 @@ public class EShopOnAbpPublicWebModule : AbpModule
options.Events.OnRedirectToIdentityProvider = async ctx =>
{
// Intercept the redirection so the browser navigates to the right URL in your host
- ctx.ProtocolMessage.IssuerAddress = configuration["AuthServer:Authority"].EnsureEndsWith('/') + "connect/authorize";
+ ctx.ProtocolMessage.IssuerAddress = configuration["AuthServer:Authority"].EnsureEndsWith('/') +
+ "connect/authorize";
if (previousOnRedirectToIdentityProvider != null)
{
await previousOnRedirectToIdentityProvider(ctx);
}
};
- var previousOnRedirectToIdentityProviderForSignOut = options.Events.OnRedirectToIdentityProviderForSignOut;
+ var previousOnRedirectToIdentityProviderForSignOut =
+ options.Events.OnRedirectToIdentityProviderForSignOut;
options.Events.OnRedirectToIdentityProviderForSignOut = async ctx =>
{
// Intercept the redirection for signout so the browser navigates to the right URL in your host
- ctx.ProtocolMessage.IssuerAddress = configuration["AuthServer:Authority"].EnsureEndsWith('/') + "connect/endsession";
+ ctx.ProtocolMessage.IssuerAddress = configuration["AuthServer:Authority"].EnsureEndsWith('/') +
+ "connect/endsession";
if (previousOnRedirectToIdentityProviderForSignOut != null)
{
@@ -226,32 +266,6 @@ public class EShopOnAbpPublicWebModule : AbpModule
});
}
- private void ConfigureBasketHttpClient(ServiceConfigurationContext context)
- {
- context.Services.AddStaticHttpClientProxies(
- typeof(BasketServiceContractsModule).Assembly, remoteServiceConfigurationName: BasketServiceConstants.RemoteServiceName
- );
-
- Configure(options =>
- {
- options.FileSets.AddEmbedded();
- });
- }
-
- private void ConfigurePayment(IConfiguration configuration)
- {
- Configure(options =>
- {
- options.PaymentSuccessfulCallbackUrl =
- configuration["App:SelfUrl"].EnsureEndsWith('/') + "PaymentCompleted";
- });
-
- Configure(options =>
- {
- options.ConfigureIcon(PaymentMethodNames.PayPal, "fa-cc-paypal paypal");
- });
- }
-
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
var app = context.GetApplicationBuilder();
@@ -294,4 +308,66 @@ public class EShopOnAbpPublicWebModule : AbpModule
// endpoints.MapMetrics();
});
}
+
+ private void ConfigureBasketHttpClient(ServiceConfigurationContext context)
+ {
+ context.Services.AddStaticHttpClientProxies(
+ typeof(BasketServiceContractsModule).Assembly,
+ remoteServiceConfigurationName: BasketServiceConstants.RemoteServiceName
+ );
+
+ Configure(options =>
+ {
+ options.FileSets.AddEmbedded();
+ });
+ }
+
+ private void ConfigurePayment(IConfiguration configuration)
+ {
+ Configure(options =>
+ {
+ options.PaymentSuccessfulCallbackUrl =
+ configuration["App:SelfUrl"].EnsureEndsWith('/') + "PaymentCompleted";
+ });
+
+ Configure(options =>
+ {
+ options.ConfigureIcon(PaymentMethodNames.PayPal, "fa-cc-paypal paypal");
+ });
+ }
+
+ private UserLoggedInEto CreateUserLoggedInEto(ClaimsPrincipal principal, HttpContext httpContext)
+ {
+ var logger = httpContext.RequestServices.GetRequiredService>();
+
+ if (principal == null)
+ {
+ logger.LogWarning($"AuthorizationCode does not contain principal to create/update user!");
+ return null;
+ }
+
+ var claims = principal.Claims.ToList();
+
+ var userNameClaim = claims.FirstOrDefault(x => x.Type == "preferred_username");
+ var emailClaim = claims.FirstOrDefault(x => x.Type == ClaimTypes.Email);
+ var isEmailVerified = claims.FirstOrDefault(x => x.Type == "email_verified")?.Value == "true";
+ var phoneNumberClaim = claims.FirstOrDefault(x => x.Type == "phone");
+ var userIdString = claims.First(t => t.Type == ClaimTypes.NameIdentifier).Value;
+
+ if (!Guid.TryParse(userIdString, out Guid userId))
+ {
+ logger.LogWarning(
+ $"Handling UserLoggedInEvent... User creation failed! {userIdString} can not be parsed!");
+ return null;
+ }
+
+ return new UserLoggedInEto
+ {
+ Id = userId,
+ Email = emailClaim?.Value,
+ UserName = userNameClaim?.Value,
+ Phone = phoneNumberClaim?.Value,
+ IsEmailVerified = isEmailVerified
+ };
+ }
}
\ No newline at end of file
diff --git a/apps/public-web/src/EShopOnAbp.PublicWeb/Pages/ProductDetail.cshtml.cs b/apps/public-web/src/EShopOnAbp.PublicWeb/Pages/ProductDetail.cshtml.cs
index 32bcbdd0..f8dbf4f6 100644
--- a/apps/public-web/src/EShopOnAbp.PublicWeb/Pages/ProductDetail.cshtml.cs
+++ b/apps/public-web/src/EShopOnAbp.PublicWeb/Pages/ProductDetail.cshtml.cs
@@ -29,8 +29,17 @@ namespace EShopOnAbp.PublicWeb.Pages
public async Task OnGet(Guid id)
{
- IsPurschased = (await _orderAppService.GetMyOrdersAsync(new GetMyOrdersInput())).Any(p => p.Items.Any(p => p.ProductId == id));
Product = await _productAppService.GetAsync(id);
+
+ try
+ {
+ IsPurschased = (await _orderAppService.GetMyOrdersAsync(new GetMyOrdersInput())).Any(p => p.Items.Any(p => p.ProductId == id));
+ }
+ catch (Exception e)
+ {
+ IsPurschased = false;
+ Console.WriteLine(e);
+ }
}
}
}
\ No newline at end of file
diff --git a/apps/public-web/src/EShopOnAbp.PublicWeb/UserLoggedInEto.cs b/apps/public-web/src/EShopOnAbp.PublicWeb/UserLoggedInEto.cs
new file mode 100644
index 00000000..f7ffa497
--- /dev/null
+++ b/apps/public-web/src/EShopOnAbp.PublicWeb/UserLoggedInEto.cs
@@ -0,0 +1,16 @@
+using System;
+using Volo.Abp.Domain.Entities.Events.Distributed;
+using Volo.Abp.EventBus;
+
+namespace EShopOnAbp.PublicWeb;
+
+[EventName("EShopOnAbp.Identity.UserLoggedIn")]
+[Serializable]
+public class UserLoggedInEto : EtoBase
+{
+ public Guid Id { get; set; }
+ public string Email { get; set; }
+ public string Phone { get; set; }
+ public string UserName { get; set; }
+ public bool IsEmailVerified { get; set; }
+}
\ No newline at end of file
diff --git a/apps/public-web/src/EShopOnAbp.PublicWeb/appsettings.json b/apps/public-web/src/EShopOnAbp.PublicWeb/appsettings.json
index 8d0d9e5b..dd89b1f3 100644
--- a/apps/public-web/src/EShopOnAbp.PublicWeb/appsettings.json
+++ b/apps/public-web/src/EShopOnAbp.PublicWeb/appsettings.json
@@ -36,12 +36,11 @@
"Url": "http://localhost:9200"
},
"AuthServer": {
- "Authority": "https://localhost:44330",
- "RequireHttpsMetadata": "true",
+ "Authority": "http://localhost:8080/realms/master",
+ "RequireHttpsMetadata": "false",
"ClientId": "PublicWeb",
- "ClientSecret": "1q2w3e*",
"IsOnProd": "false",
- "MetaAddress": "https://localhost:44330"
+ "MetaAddress": "http://localhost:8080/realms/master/.well-known/openid-configuration"
},
"ReverseProxy": {
"Routes": {
diff --git a/etc/docker/docker-compose.infrastructure.override.yml b/etc/docker/docker-compose.infrastructure.override.yml
index 3ae3deb5..7b9c83a2 100644
--- a/etc/docker/docker-compose.infrastructure.override.yml
+++ b/etc/docker/docker-compose.infrastructure.override.yml
@@ -1,4 +1,4 @@
-version: '3.7'
+version: '3.8'
services:
rabbitmq:
@@ -18,6 +18,7 @@ services:
ports:
- "5432:5432"
environment:
+ POSTGRES_DB: keycloak
POSTGRES_PASSWORD: "myPassw0rd"
pgadmin:
@@ -26,3 +27,19 @@ services:
environment:
PGADMIN_DEFAULT_EMAIL: ${PGADMIN_DEFAULT_EMAIL:-pgadmin4@pgadmin.org}
PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_DEFAULT_PASSWORD:-admin}
+
+ keycloak:
+ ports:
+ - "8080:8080"
+ environment:
+ DB_VENDOR: postgres
+ DB_ADDR: "postgres-db"
+ DB_DATABASE: "keycloak"
+ DB_USER: "postgres"
+ DB_PASSWORD: "myPassw0rd"
+ KEYCLOAK_ADMIN: admin
+ KEYCLOAK_ADMIN_PASSWORD: "1q2w3E*"
+ KC_HEALTH_ENABLED: "true"
+ entrypoint: ["/opt/keycloak/bin/kc.sh", "start-dev"]
+
+
\ No newline at end of file
diff --git a/etc/docker/docker-compose.infrastructure.yml b/etc/docker/docker-compose.infrastructure.yml
index 547b3aae..3f72762f 100644
--- a/etc/docker/docker-compose.infrastructure.yml
+++ b/etc/docker/docker-compose.infrastructure.yml
@@ -1,4 +1,4 @@
-version: '3.7'
+version: '3.8'
services:
rabbitmq:
@@ -46,12 +46,21 @@ services:
- eshoponabp-network
pgadmin:
- container_name: pgadmin_container
+ container_name: pgadmin
image: dpage/pgadmin4:6.2
volumes:
- pgadmin_data:/var/lib/pgadmin
networks:
- - eshoponabp-network
+ - eshoponabp-network
+
+ keycloak:
+ container_name: keycloak
+ image: quay.io/keycloak/keycloak:19.0.2
+ depends_on:
+ - postgres-db
+ restart: unless-stopped
+ networks:
+ - eshoponabp-network
volumes:
postgres_data:
diff --git a/etc/k8s/eshoponabp/values.azure.yaml b/etc/k8s/eshoponabp/values.azure.yaml
index 5df9c34e..7bd5b34b 100644
--- a/etc/k8s/eshoponabp/values.azure.yaml
+++ b/etc/k8s/eshoponabp/values.azure.yaml
@@ -18,8 +18,8 @@ authserver:
host: auth.eshoponabp.com
tlsSecret: eshop-wildcard-tls
image:
- repository: "volocr.azurecr.io/eshoponabp/app-authserver"
- tag: 1.0.5
+ repository: "ghcr.io/volosoft/eshoponabp/app-authserver"
+ tag: 1.0.0
# web sub-chart override
web:
@@ -35,8 +35,8 @@ web:
ingress:
host: admin.eshoponabp.com
image:
- repository: "volocr.azurecr.io/eshoponabp/app-web"
- tag: 1.0.5
+ repository: "ghcr.io/volosoft/eshoponabp/app-web"
+ tag: 1.0.0
# public-web sub-chart override
public-web:
@@ -56,8 +56,8 @@ public-web:
ingress:
host: eshoponabp.com
image:
- repository: "volocr.azurecr.io/eshoponabp/app-publicweb"
- tag: 1.0.6
+ repository: "ghcr.io/volosoft/eshoponabp/app-publicweb"
+ tag: 1.0.0
# identity-service sub-chart override
identity:
@@ -92,8 +92,8 @@ identity:
ingress:
host: identity.eshoponabp.com
image:
- repository: "volocr.azurecr.io/eshoponabp/service-identity"
- tag: 1.0.5
+ repository: "ghcr.io/volosoft/eshoponabp/service-identity"
+ tag: 1.0.0
# administration sub-chart override
administration:
@@ -119,8 +119,8 @@ administration:
ingress:
host: administration.eshoponabp.com
image:
- repository: "volocr.azurecr.io/eshoponabp/service-administration"
- tag: 1.0.5
+ repository: "ghcr.io/volosoft/eshoponabp/service-administration"
+ tag: 1.0.0
# gateway-web sub-chart override
gateway-web:
@@ -140,8 +140,8 @@ gateway-web:
ingress:
host: gateway.eshoponabp.com
image:
- repository: "volocr.azurecr.io/eshoponabp/gateway-web"
- tag: 1.0.5
+ repository: "ghcr.io/volosoft/eshoponabp/gateway-web"
+ tag: 1.0.0
reRoutes:
accountService:
url: http://eshop-az-authserver
@@ -172,8 +172,8 @@ gateway-web-public:
ingress:
host: gateway-public.eshoponabp.com
image:
- repository: "volocr.azurecr.io/eshoponabp/gateway-web-public"
- tag: 1.0.5
+ repository: "ghcr.io/volosoft/eshoponabp/gateway-web-public"
+ tag: 1.0.0
reRoutes:
accountService:
url: http://eshop-az-authserver
@@ -214,8 +214,8 @@ basket:
ingress:
host: basket.eshoponabp.com
image:
- repository: "volocr.azurecr.io/eshoponabp/service-basket"
- tag: 1.0.5
+ repository: "ghcr.io/volosoft/eshoponabp/service-basket"
+ tag: 1.0.0
# catalog-service sub-chart override
catalog:
@@ -242,8 +242,8 @@ catalog:
ingress:
host: catalog.eshoponabp.com
image:
- repository: "volocr.azurecr.io/eshoponabp/service-catalog"
- tag: 1.0.5
+ repository: "ghcr.io/volosoft/eshoponabp/service-catalog"
+ tag: 1.0.0
# ordering-service sub-chart override
ordering:
@@ -294,7 +294,7 @@ cmskit:
image:
repository: "volocr.azurecr.io/eshoponabp/service-cmskit"
tag: 1.0.5
-
+
# payment-service sub-chart override
payment:
config:
@@ -315,8 +315,8 @@ payment:
ingress:
host: payment.eshoponabp.com
image:
- repository: "volocr.azurecr.io/eshoponabp/service-payment"
- tag: 1.0.5
+ repository: "ghcr.io/volosoft/eshoponabp/service-payment"
+ tag: 1.0.0
# Default values for eshoponabp.
# This is a YAML-formatted file.
diff --git a/gateways/web-public/src/EShopOnAbp.WebPublicGateway/appsettings.json b/gateways/web-public/src/EShopOnAbp.WebPublicGateway/appsettings.json
index 119795be..c36665c8 100644
--- a/gateways/web-public/src/EShopOnAbp.WebPublicGateway/appsettings.json
+++ b/gateways/web-public/src/EShopOnAbp.WebPublicGateway/appsettings.json
@@ -4,7 +4,7 @@
"CorsOrigins": "https://localhost:44335"
},
"AuthServer": {
- "Authority": "https://localhost:44330",
+ "Authority": "http://localhost:8080/realms/master",
"RequireHttpsMetadata": "true",
"SwaggerClientId": "WebGateway_Swagger",
"SwaggerClientSecret": "1q2w3e*"
diff --git a/gateways/web/src/EShopOnAbp.WebGateway/appsettings.json b/gateways/web/src/EShopOnAbp.WebGateway/appsettings.json
index dea2bd3b..f9543eef 100644
--- a/gateways/web/src/EShopOnAbp.WebGateway/appsettings.json
+++ b/gateways/web/src/EShopOnAbp.WebGateway/appsettings.json
@@ -4,7 +4,7 @@
"CorsOrigins": "http://localhost:4200"
},
"AuthServer": {
- "Authority": "https://localhost:44330",
+ "Authority": "http://localhost:8080/realms/master",
"RequireHttpsMetadata": "true",
"SwaggerClientId": "WebGateway_Swagger",
"SwaggerClientSecret": "1q2w3e*"
diff --git a/services/administration/src/EShopOnAbp.AdministrationService.HttpApi.Host/AdministrationServiceHttpApiHostModule.cs b/services/administration/src/EShopOnAbp.AdministrationService.HttpApi.Host/AdministrationServiceHttpApiHostModule.cs
index 9b349ff8..7ecb1575 100644
--- a/services/administration/src/EShopOnAbp.AdministrationService.HttpApi.Host/AdministrationServiceHttpApiHostModule.cs
+++ b/services/administration/src/EShopOnAbp.AdministrationService.HttpApi.Host/AdministrationServiceHttpApiHostModule.cs
@@ -91,7 +91,7 @@ public class AdministrationServiceHttpApiHostModule : AbpModule
var configuration = context.ServiceProvider.GetRequiredService();
options.SwaggerEndpoint("/swagger/v1/swagger.json", "Administration Service API");
options.OAuthClientId(configuration["AuthServer:SwaggerClientId"]);
- options.OAuthClientSecret(configuration["AuthServer:SwaggerClientSecret"]);
+ // options.OAuthClientSecret(configuration["AuthServer:SwaggerClientSecret"]);
});
app.UseAbpSerilogEnrichers();
app.UseAuditing();
diff --git a/services/administration/src/EShopOnAbp.AdministrationService.HttpApi.Host/appsettings.json b/services/administration/src/EShopOnAbp.AdministrationService.HttpApi.Host/appsettings.json
index 8610b450..a9f4e973 100644
--- a/services/administration/src/EShopOnAbp.AdministrationService.HttpApi.Host/appsettings.json
+++ b/services/administration/src/EShopOnAbp.AdministrationService.HttpApi.Host/appsettings.json
@@ -1,13 +1,12 @@
{
"App": {
"SelfUrl": "https://localhost:44353",
- "CorsOrigins": "https://localhost:44372,https://localhost:44373"
+ "CorsOrigins": "https://localhost:44372,https://localhost:44373,http://localhost:4200"
},
"AuthServer": {
- "Authority": "https://localhost:44330",
- "RequireHttpsMetadata": "true",
- "SwaggerClientId": "WebGateway_Swagger",
- "SwaggerClientSecret": "1q2w3e*"
+ "Authority": "http://localhost:8080/realms/master",
+ "RequireHttpsMetadata": "false",
+ "SwaggerClientId": "SwaggerClient"
},
"RemoteServices": {
"AbpIdentity": {
@@ -20,7 +19,7 @@
"GrantType": "client_credentials",
"ClientId": "EShopOnAbp_AdministrationService",
"ClientSecret": "1q2w3e*",
- "Authority": "https://localhost:44330",
+ "Authority": "http://localhost:8080/realms/master",
"Scope": "IdentityService"
}
},
diff --git a/services/basket/src/EShopOnAbp.BasketService/appsettings.json b/services/basket/src/EShopOnAbp.BasketService/appsettings.json
index 93adbecc..7ef6ff5f 100644
--- a/services/basket/src/EShopOnAbp.BasketService/appsettings.json
+++ b/services/basket/src/EShopOnAbp.BasketService/appsettings.json
@@ -1,11 +1,11 @@
{
"App": {
"SelfUrl": "https://localhost:44355",
- "CorsOrigins": "https://localhost:44372,https://localhost:44373,https://localhost:44335"
+ "CorsOrigins": "https://localhost:44372,https://localhost:44373,,http://localhost:4200"
},
"AuthServer": {
- "Authority": "https://localhost:44330",
- "RequireHttpsMetadata": "true",
+ "Authority": "http://localhost:8080/realms/master",
+ "RequireHttpsMetadata": "false",
"SwaggerClientId": "WebGateway_Swagger",
"SwaggerClientSecret": "1q2w3e*"
},
diff --git a/services/catalog/src/EShopOnAbp.CatalogService.HttpApi.Host/appsettings.json b/services/catalog/src/EShopOnAbp.CatalogService.HttpApi.Host/appsettings.json
index 645ce941..7a8b88c1 100644
--- a/services/catalog/src/EShopOnAbp.CatalogService.HttpApi.Host/appsettings.json
+++ b/services/catalog/src/EShopOnAbp.CatalogService.HttpApi.Host/appsettings.json
@@ -1,11 +1,11 @@
{
"App": {
"SelfUrl": "https://localhost:44354",
- "CorsOrigins": "https://localhost:44372,https://localhost:44373,https://localhost:44335,http://localhost:4200"
+ "CorsOrigins": "https://localhost:44372,https://localhost:44373,http://localhost:4200"
},
"AuthServer": {
- "Authority": "https://localhost:44330",
- "RequireHttpsMetadata": "true",
+ "Authority": "http://localhost:8080/realms/master",
+ "RequireHttpsMetadata": "false",
"SwaggerClientId": "WebGateway_Swagger",
"SwaggerClientSecret": "1q2w3e*"
},
diff --git a/services/cmskit/src/EShopOnAbp.CmskitService.HttpApi.Host/appsettings.json b/services/cmskit/src/EShopOnAbp.CmskitService.HttpApi.Host/appsettings.json
index 05701248..6fefb7e9 100644
--- a/services/cmskit/src/EShopOnAbp.CmskitService.HttpApi.Host/appsettings.json
+++ b/services/cmskit/src/EShopOnAbp.CmskitService.HttpApi.Host/appsettings.json
@@ -1,11 +1,11 @@
{
"App": {
"SelfUrl": "https://localhost:44358",
- "CorsOrigins": "https://localhost:44372,https://localhost:44373"
+ "CorsOrigins": "https://localhost:44372,https://localhost:44373,http://localhost:4200"
},
"AuthServer": {
- "Authority": "https://localhost:44330",
- "RequireHttpsMetadata": "true",
+ "Authority": "http://localhost:8080/realms/master",
+ "RequireHttpsMetadata": "false",
"SwaggerClientId": "WebGateway_Swagger",
"SwaggerClientSecret": "1q2w3e*"
},
@@ -51,7 +51,7 @@
"GrantType": "client_credentials",
"ClientId": "EShopOnAbp_CmskitService",
"ClientSecret": "1q2w3e*",
- "Authority": "https://localhost:44330",
+ "Authority": "http://localhost:8080/realms/master",
"Scope": "IdentityService"
}
}
diff --git a/services/identity/src/EShopOnAbp.IdentityService.Application.Contracts/ETOs/UserLoggedInEto.cs b/services/identity/src/EShopOnAbp.IdentityService.Application.Contracts/ETOs/UserLoggedInEto.cs
new file mode 100644
index 00000000..d163d5f4
--- /dev/null
+++ b/services/identity/src/EShopOnAbp.IdentityService.Application.Contracts/ETOs/UserLoggedInEto.cs
@@ -0,0 +1,17 @@
+using System;
+using Volo.Abp.Domain.Entities.Events.Distributed;
+using Volo.Abp.EventBus;
+
+namespace EShopOnAbp.IdentityService.ETOs
+{
+ [EventName("EShopOnAbp.Identity.UserLoggedIn")]
+ [Serializable]
+ public class UserLoggedInEto : EtoBase
+ {
+ public Guid Id { get; set; }
+ public string Email { get; set; }
+ public string Phone { get; set; }
+ public string UserName { get; set; }
+ public bool IsEmailVerified { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/services/identity/src/EShopOnAbp.IdentityService.Application/UserLoggedInEventHandler.cs b/services/identity/src/EShopOnAbp.IdentityService.Application/UserLoggedInEventHandler.cs
new file mode 100644
index 00000000..058db2d8
--- /dev/null
+++ b/services/identity/src/EShopOnAbp.IdentityService.Application/UserLoggedInEventHandler.cs
@@ -0,0 +1,98 @@
+using System.Threading.Tasks;
+using EShopOnAbp.IdentityService.ETOs;
+using Microsoft.Extensions.Logging;
+using Volo.Abp;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.EventBus.Distributed;
+using Volo.Abp.Identity;
+using Volo.Abp.Uow;
+
+namespace EShopOnAbp.IdentityService;
+
+public class UserLoggedInEventHandler : IDistributedEventHandler, ITransientDependency
+{
+ private readonly IdentityUserManager _userManager;
+ private readonly ILogger _logger;
+
+ public UserLoggedInEventHandler(IdentityUserManager userManager, ILogger logger)
+ {
+ _userManager = userManager;
+ _logger = logger;
+ }
+
+
+ [UnitOfWork]
+ public async virtual Task HandleEventAsync(UserLoggedInEto eventData)
+ {
+ if (eventData == null)
+ {
+ _logger.LogWarning($"Handling UserLoggedInEvent failed! No user information found!");
+ return;
+ }
+
+ var user = await _userManager.FindByIdAsync(eventData.Id.ToString());
+
+ if (user == null)
+ {
+ await CreateCurrentUserAsync(eventData);
+ }
+ else
+ {
+ await UpdateCurrentUserAsync(user, eventData);
+ }
+ }
+
+ protected virtual async Task CreateCurrentUserAsync(UserLoggedInEto userInfo)
+ {
+ var user = new IdentityUser(
+ userInfo.Id,
+ userInfo.UserName,
+ userInfo.Email);
+
+ user.SetEmailConfirmed(userInfo.IsEmailVerified);
+
+ if (!string.IsNullOrEmpty(userInfo.Phone))
+ {
+ user.SetPhoneNumber(userInfo.Phone, false);
+ }
+
+ // This should run once to sync the admin userIds that seeded by IdentityModule and the Keycloak admin
+ if (userInfo.UserName == "admin")
+ {
+ var adminUser = await _userManager.FindByNameAsync("admin");
+ await _userManager.DeleteAsync(adminUser);
+ }
+
+ var result = await _userManager.CreateAsync(user);
+
+ if (!result.Succeeded)
+ {
+ throw new AbpException(string.Join('\n', result.Errors));
+ }
+
+ _logger.LogInformation($"Handling UserLoggedInEvent... Created new user with Id:{userInfo.Id}");
+ }
+
+ protected virtual async Task UpdateCurrentUserAsync(IdentityUser user, UserLoggedInEto userInfo)
+ {
+ if (user.Email != userInfo.Email)
+ {
+ _logger.LogInformation($"Handling UserLoggedInEvent... Updating the user email with:{userInfo.Email}");
+ await _userManager.SetEmailAsync(user, userInfo.Email);
+ }
+
+ if (user.PhoneNumber != userInfo.Phone)
+ {
+ _logger.LogInformation(
+ $"Handling UserLoggedInEvent... Updating the user phone with:{userInfo.Phone}");
+ await _userManager.SetPhoneNumberAsync(user, userInfo.Phone);
+ }
+
+ if (user.UserName != userInfo.UserName)
+ {
+ _logger.LogInformation(
+ $"Handling UserLoggedInEvent... Updating the user name with:{userInfo.UserName}");
+ await _userManager.SetUserNameAsync(user, userInfo.UserName);
+ }
+ }
+}
\ No newline at end of file
diff --git a/services/identity/src/EShopOnAbp.IdentityService.HttpApi.Host/IdentityServiceHttpApiHostModule.cs b/services/identity/src/EShopOnAbp.IdentityService.HttpApi.Host/IdentityServiceHttpApiHostModule.cs
index 98ed74e0..c979c499 100644
--- a/services/identity/src/EShopOnAbp.IdentityService.HttpApi.Host/IdentityServiceHttpApiHostModule.cs
+++ b/services/identity/src/EShopOnAbp.IdentityService.HttpApi.Host/IdentityServiceHttpApiHostModule.cs
@@ -12,6 +12,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
+using Microsoft.AspNetCore.Identity;
using Volo.Abp;
using Volo.Abp.Modularity;
@@ -40,10 +41,9 @@ public class IdentityServiceHttpApiHostModule : AbpModule
{
{ "IdentityService", "Identity Service API" }
},
- apiTitle: "IdentityService Gateway API"
+ apiTitle: "IdentityService API"
);
-
context.Services.AddCors(options =>
{
options.AddDefaultPolicy(builder =>
@@ -62,6 +62,12 @@ public class IdentityServiceHttpApiHostModule : AbpModule
.AllowCredentials();
});
});
+
+ // Keycloak handles the user creation that a user name can be multiple words
+ Configure(options =>
+ {
+ options.User.AllowedUserNameCharacters = null;
+ });
}
public override void OnApplicationInitialization(ApplicationInitializationContext context)
diff --git a/services/identity/src/EShopOnAbp.IdentityService.HttpApi.Host/appsettings.json b/services/identity/src/EShopOnAbp.IdentityService.HttpApi.Host/appsettings.json
index 9678ab64..9cdfa28f 100644
--- a/services/identity/src/EShopOnAbp.IdentityService.HttpApi.Host/appsettings.json
+++ b/services/identity/src/EShopOnAbp.IdentityService.HttpApi.Host/appsettings.json
@@ -1,11 +1,11 @@
{
"App": {
"SelfUrl": "https://localhost:44351",
- "CorsOrigins": "https://localhost:44372,https://localhost:44373"
+ "CorsOrigins": "https://localhost:44372,https://localhost:44373,http://localhost:4200"
},
"AuthServer": {
- "Authority": "https://localhost:44330",
- "RequireHttpsMetadata": "true",
+ "Authority": "http://localhost:8080/realms/master",
+ "RequireHttpsMetadata": "false",
"SwaggerClientId": "WebGateway_Swagger",
"SwaggerClientSecret": "1q2w3e*"
},
diff --git a/services/ordering/src/EShopOnAbp.OrderingService.Application/Orders/OrderAppService.cs b/services/ordering/src/EShopOnAbp.OrderingService.Application/Orders/OrderAppService.cs
index 93bcc00c..0ac0dfd8 100644
--- a/services/ordering/src/EShopOnAbp.OrderingService.Application/Orders/OrderAppService.cs
+++ b/services/ordering/src/EShopOnAbp.OrderingService.Application/Orders/OrderAppService.cs
@@ -40,6 +40,11 @@ public class OrderAppService : ApplicationService, IOrderAppService
[AllowAnonymous]
public async Task> GetMyOrdersAsync(GetMyOrdersInput input)
{
+ if (CurrentUser.Id == null)
+ {
+ return new List();
+ }
+
ISpecification specification = SpecificationFactory.Create(input.Filter);
var orders = await _orderRepository.GetOrdersByUserId(CurrentUser.GetId(), specification, true);
return CreateOrderDtoMapping(orders);
diff --git a/services/ordering/src/EShopOnAbp.OrderingService.HttpApi.Host/appsettings.json b/services/ordering/src/EShopOnAbp.OrderingService.HttpApi.Host/appsettings.json
index 5d6b54e1..865a6130 100644
--- a/services/ordering/src/EShopOnAbp.OrderingService.HttpApi.Host/appsettings.json
+++ b/services/ordering/src/EShopOnAbp.OrderingService.HttpApi.Host/appsettings.json
@@ -4,8 +4,8 @@
"CorsOrigins": "https://localhost:44372,https://localhost:44373,http://localhost:4200"
},
"AuthServer": {
- "Authority": "https://localhost:44330",
- "RequireHttpsMetadata": "true",
+ "Authority": "http://localhost:8080/realms/master",
+ "RequireHttpsMetadata": "false",
"SwaggerClientId": "WebGateway_Swagger",
"SwaggerClientSecret": "1q2w3e*"
},
diff --git a/services/payment/src/EShopOnAbp.PaymentService.HttpApi.Host/appsettings.json b/services/payment/src/EShopOnAbp.PaymentService.HttpApi.Host/appsettings.json
index 83d05c81..93582be1 100644
--- a/services/payment/src/EShopOnAbp.PaymentService.HttpApi.Host/appsettings.json
+++ b/services/payment/src/EShopOnAbp.PaymentService.HttpApi.Host/appsettings.json
@@ -4,8 +4,8 @@
"CorsOrigins": "https://localhost:44372,https://localhost:44373"
},
"AuthServer": {
- "Authority": "https://localhost:44330",
- "RequireHttpsMetadata": "true",
+ "Authority": "http://localhost:8080/realms/master",
+ "RequireHttpsMetadata": "false",
"SwaggerClientId": "WebGateway_Swagger",
"SwaggerClientSecret": "1q2w3e*"
},
diff --git a/shared/EShopOnAbp.Keycloak.DbMigrator/DbMigratorHostedService.cs b/shared/EShopOnAbp.Keycloak.DbMigrator/DbMigratorHostedService.cs
new file mode 100644
index 00000000..d28e0ecf
--- /dev/null
+++ b/shared/EShopOnAbp.Keycloak.DbMigrator/DbMigratorHostedService.cs
@@ -0,0 +1,47 @@
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Serilog;
+using Volo.Abp;
+
+namespace EShopOnAbp.DbMigrator;
+
+public class DbMigratorHostedService : IHostedService
+{
+ private readonly IHostApplicationLifetime _hostApplicationLifetime;
+ private readonly IConfiguration _configuration;
+
+ public DbMigratorHostedService(
+ IHostApplicationLifetime hostApplicationLifetime,
+ IConfiguration configuration)
+ {
+ _hostApplicationLifetime = hostApplicationLifetime;
+ _configuration = configuration;
+ }
+
+ public async Task StartAsync(CancellationToken cancellationToken)
+ {
+ using (var application = AbpApplicationFactory.Create(options =>
+ {
+ options.Services.ReplaceConfiguration(_configuration);
+ options.UseAutofac();
+ options.Services.AddLogging(c => c.AddSerilog());
+ }))
+ {
+ application.Initialize();
+
+ await application
+ .ServiceProvider
+ .GetRequiredService()
+ .MigrateAsync(cancellationToken);
+
+ application.Shutdown();
+
+ _hostApplicationLifetime.StopApplication();
+ }
+ }
+
+ public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
+}
\ No newline at end of file
diff --git a/shared/EShopOnAbp.Keycloak.DbMigrator/EShopOnAbp.Keycloak.DbMigrator.csproj b/shared/EShopOnAbp.Keycloak.DbMigrator/EShopOnAbp.Keycloak.DbMigrator.csproj
new file mode 100644
index 00000000..47d14f09
--- /dev/null
+++ b/shared/EShopOnAbp.Keycloak.DbMigrator/EShopOnAbp.Keycloak.DbMigrator.csproj
@@ -0,0 +1,33 @@
+
+
+
+ Exe
+ net6.0
+ EShopOnAbp.DbMigrator
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+ Always
+
+
+
+
diff --git a/shared/EShopOnAbp.Keycloak.DbMigrator/EShopOnAbpDbMigratorModule.cs b/shared/EShopOnAbp.Keycloak.DbMigrator/EShopOnAbpDbMigratorModule.cs
new file mode 100644
index 00000000..fcdb8df2
--- /dev/null
+++ b/shared/EShopOnAbp.Keycloak.DbMigrator/EShopOnAbpDbMigratorModule.cs
@@ -0,0 +1,25 @@
+using EShopOnAbp.Shared.Hosting;
+using Microsoft.Extensions.DependencyInjection;
+using Volo.Abp.Modularity;
+
+namespace EShopOnAbp.DbMigrator;
+
+[DependsOn(
+ typeof(EShopOnAbpSharedHostingModule)
+)]
+public class EShopOnAbpDbMigratorModule : AbpModule
+{
+ public override void ConfigureServices(ServiceConfigurationContext context)
+ {
+ var configuration = context.Services.GetConfiguration();
+
+ Configure(options =>
+ {
+ options.Url = configuration["Keycloak:url"];
+ options.AdminUserName = configuration["Keycloak:adminUsername"];
+ options.AdminPassword = configuration["Keycloak:adminPassword"];
+ options.RealmName = configuration["Keycloak:realmName"];
+ }
+ );
+ }
+}
\ No newline at end of file
diff --git a/shared/EShopOnAbp.Keycloak.DbMigrator/KeycloakClientOptions.cs b/shared/EShopOnAbp.Keycloak.DbMigrator/KeycloakClientOptions.cs
new file mode 100644
index 00000000..6c24a9b8
--- /dev/null
+++ b/shared/EShopOnAbp.Keycloak.DbMigrator/KeycloakClientOptions.cs
@@ -0,0 +1,9 @@
+namespace EShopOnAbp.DbMigrator;
+
+public class KeycloakClientOptions
+{
+ public string Url { get; set; }
+ public string AdminUserName { get; set; }
+ public string AdminPassword { get; set; }
+ public string RealmName { get; set; }
+}
\ No newline at end of file
diff --git a/shared/EShopOnAbp.Keycloak.DbMigrator/KeycloakDataSeeder.cs b/shared/EShopOnAbp.Keycloak.DbMigrator/KeycloakDataSeeder.cs
new file mode 100644
index 00000000..2956e3d4
--- /dev/null
+++ b/shared/EShopOnAbp.Keycloak.DbMigrator/KeycloakDataSeeder.cs
@@ -0,0 +1,307 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Keycloak.Net;
+using Keycloak.Net.Models.Clients;
+using Keycloak.Net.Models.ClientScopes;
+using Keycloak.Net.Models.ProtocolMappers;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using Volo.Abp.Data;
+using Volo.Abp.DependencyInjection;
+
+namespace EShopOnAbp.DbMigrator;
+
+public class KeyCloakDataSeeder : IDataSeedContributor, ITransientDependency
+{
+ private readonly KeycloakClient _keycloakClient;
+ private readonly KeycloakClientOptions _keycloakOptions;
+ private readonly ILogger _logger;
+ private readonly IConfiguration _configuration;
+
+ public KeyCloakDataSeeder(IOptions keycloakClientOptions, ILogger logger,
+ IConfiguration configuration)
+ {
+ _logger = logger;
+ _configuration = configuration;
+ _keycloakOptions = keycloakClientOptions.Value;
+
+ _keycloakClient = new KeycloakClient(
+ _keycloakOptions.Url,
+ _keycloakOptions.AdminUserName,
+ _keycloakOptions.AdminPassword
+ );
+ }
+
+ public async Task SeedAsync(DataSeedContext context)
+ {
+ await UpdateAdminUserAsync();
+ await CreateRoleMapperAsync();
+ await CreateClientScopesAsync();
+ await CreateClientsAsync();
+ }
+
+ private async Task CreateRoleMapperAsync()
+ {
+ var roleScope = (await _keycloakClient.GetClientScopesAsync(_keycloakOptions.RealmName))
+ .FirstOrDefault(q => q.Name == "roles");
+ if (roleScope == null)
+ return;
+
+ if (!roleScope.ProtocolMappers.Any(q => q.Name == "roles"))
+ {
+ await _keycloakClient.CreateProtocolMapperAsync(_keycloakOptions.RealmName, roleScope.Id,
+ new ProtocolMapper()
+ {
+ Name = "roles",
+ Protocol = "openid-connect",
+ _ProtocolMapper = "oidc-usermodel-realm-role-mapper",
+ Config = new Dictionary()
+ {
+ { "access.token.claim", "true" },
+ { "id.token.claim", "true" },
+ { "claim.name", "roles" },
+ { "multivalued", "true" },
+ { "userinfo.token.claim", "true" },
+ }
+ });
+ }
+ }
+
+ private async Task CreateClientScopesAsync()
+ {
+ await CreateScopeAsync("AdministrationService");
+ await CreateScopeAsync("IdentityService");
+ await CreateScopeAsync("BasketService");
+ await CreateScopeAsync("CatalogService");
+ await CreateScopeAsync("OrderingService");
+ await CreateScopeAsync("PaymentService");
+ await CreateScopeAsync("CmskitService");
+ }
+
+ private async Task CreateScopeAsync(string scopeName)
+ {
+ var scope = (await _keycloakClient.GetClientScopesAsync(_keycloakOptions.RealmName))
+ .FirstOrDefault(q => q.Name == scopeName);
+
+ if (scope == null)
+ {
+ scope = new ClientScope
+ {
+ Name = scopeName,
+ Description = scopeName + " scope",
+ Protocol = "openid-connect",
+ Attributes = new Attributes
+ {
+ ConsentScreenText = scopeName,
+ DisplayOnConsentScreen = "true",
+ IncludeInTokenScope = "true"
+ },
+ ProtocolMappers = new List()
+ {
+ new ProtocolMapper()
+ {
+ Name = scopeName,
+ Protocol = "openid-connect",
+ _ProtocolMapper = "oidc-audience-mapper",
+ Config =
+ new
+ Dictionary() //TODO: Update when //https://github.com/AnderssonPeter/Keycloak.Net/pull/5 is merged
+ {
+ { "id.token.claim", "false" },
+ { "access.token.claim", "true" },
+ { "included.custom.audience", scopeName }
+ }
+ }
+ }
+ };
+
+ await _keycloakClient.CreateClientScopeAsync(_keycloakOptions.RealmName, scope);
+ }
+ }
+
+ private async Task CreateClientsAsync()
+ {
+ await CreatePublicWebClientAsync();
+ await CreateSwaggerClientAsync(); // TODO: Test when Volo.Abp.Swashbuckle v6.0.1 is released (https://github.com/abpframework/abp/pull/14409)
+ await CreateWebClientAsync();
+ }
+
+ private async Task CreateWebClientAsync()
+ {
+ var webClient = (await _keycloakClient.GetClientsAsync(_keycloakOptions.RealmName, clientId: "Web"))
+ .FirstOrDefault();
+
+ if (webClient == null)
+ {
+ var webRootUrl = _configuration[$"Clients:Web:RootUrl"];
+ webClient = new Client
+ {
+ ClientId = "Web",
+ Name = "Angular Back-Office Web Application",
+ Protocol = "openid-connect",
+ Enabled = true,
+ BaseUrl = webRootUrl,
+ RedirectUris = new List
+ {
+ $"{webRootUrl.TrimEnd('/')}"
+ },
+ FrontChannelLogout = true,
+ PublicClient = true
+ };
+ webClient.Attributes = new Dictionary
+ {
+ { "post.logout.redirect.uris", $"{webRootUrl.TrimEnd('/')}" }
+ };
+
+ await _keycloakClient.CreateClientAsync(_keycloakOptions.RealmName, webClient);
+
+ await AddOptionalClientScopesAsync(
+ "Web",
+ new List
+ {
+ "AdministrationService", "IdentityService", "BasketService", "CatalogService",
+ "OrderingService", "PaymentService", "CmskitService"
+ }
+ );
+ }
+ }
+
+ private async Task CreateSwaggerClientAsync()
+ {
+ var swaggerClient =
+ (await _keycloakClient.GetClientsAsync(_keycloakOptions.RealmName, clientId: "SwaggerClient"))
+ .FirstOrDefault();
+
+ if (swaggerClient == null)
+ {
+ var webGatewaySwaggerRootUrl = _configuration[$"Clients:WebGateway:RootUrl"].TrimEnd('/');
+ var publicWebGatewayRootUrl = _configuration[$"Clients:PublicWebGateway:RootUrl"].TrimEnd('/');
+ var accountServiceRootUrl = _configuration[$"Clients:AccountService:RootUrl"].TrimEnd('/');
+ var identityServiceRootUrl = _configuration[$"Clients:IdentityService:RootUrl"].TrimEnd('/');
+ var administrationServiceRootUrl = _configuration[$"Clients:AdministrationService:RootUrl"].TrimEnd('/');
+ var catalogServiceRootUrl = _configuration[$"Clients:CatalogService:RootUrl"].TrimEnd('/');
+ var basketServiceRootUrl = _configuration[$"Clients:BasketService:RootUrl"].TrimEnd('/');
+ var orderingServiceRootUrl = _configuration[$"Clients:OrderingService:RootUrl"].TrimEnd('/');
+ var paymentServiceRootUrl = _configuration[$"Clients:PaymentService:RootUrl"].TrimEnd('/');
+ var cmskitServiceRootUrl = _configuration[$"Clients:CmskitService:RootUrl"].TrimEnd('/');
+
+ swaggerClient = new Client
+ {
+ ClientId = "SwaggerClient",
+ Name = "Swagger Client Application",
+ Protocol = "openid-connect",
+ Enabled = true,
+ RedirectUris = new List
+ {
+ $"{webGatewaySwaggerRootUrl}/swagger/oauth2-redirect.html", // WebGateway redirect uri
+ $"{publicWebGatewayRootUrl}/swagger/oauth2-redirect.html", // PublicWebGateway redirect uri
+ $"{accountServiceRootUrl}/swagger/oauth2-redirect.html", // AccountService redirect uri
+ $"{identityServiceRootUrl}/swagger/oauth2-redirect.html", // IdentityService redirect uri
+ $"{administrationServiceRootUrl}/swagger/oauth2-redirect.html", // AdministrationService redirect uri
+ $"{catalogServiceRootUrl}/swagger/oauth2-redirect.html", // CatalogService redirect uri
+ $"{basketServiceRootUrl}/swagger/oauth2-redirect.html", // BasketService redirect uri
+ $"{orderingServiceRootUrl}/swagger/oauth2-redirect.html", // OrderingService redirect uri
+ $"{paymentServiceRootUrl}/swagger/oauth2-redirect.html", // PaymentService redirect uri
+ $"{cmskitServiceRootUrl}/swagger/oauth2-redirect.html" // CmskitService redirect uri
+ },
+ FrontChannelLogout = true,
+ PublicClient = true
+ };
+
+ await _keycloakClient.CreateClientAsync(_keycloakOptions.RealmName, swaggerClient);
+ }
+ }
+
+ private async Task CreatePublicWebClientAsync()
+ {
+ var publicWebClient = (await _keycloakClient.GetClientsAsync(_keycloakOptions.RealmName, clientId: "PublicWeb"))
+ .FirstOrDefault();
+
+ if (publicWebClient == null)
+ {
+ var publicWebRootUrl = _configuration[$"Clients:PublicWeb:RootUrl"];
+ publicWebClient = new Client
+ {
+ ClientId = "PublicWeb",
+ Name = "Public Web Application",
+ Protocol = "openid-connect",
+ Enabled = true,
+ BaseUrl = publicWebRootUrl,
+ RedirectUris = new List
+ {
+ $"{publicWebRootUrl.TrimEnd('/')}/signin-oidc"
+ },
+ FrontChannelLogout = true,
+ PublicClient = true,
+ ImplicitFlowEnabled = true // for hybrid flow
+ };
+ publicWebClient.Attributes = new Dictionary
+ {
+ { "post.logout.redirect.uris", $"{publicWebRootUrl.TrimEnd('/')}/signout-callback-oidc" }
+ };
+
+ await _keycloakClient.CreateClientAsync(_keycloakOptions.RealmName, publicWebClient);
+
+ await AddOptionalClientScopesAsync(
+ "PublicWeb",
+ new List
+ {
+ "AdministrationService", "IdentityService", "BasketService", "CatalogService",
+ "OrderingService", "PaymentService", "CmskitService"
+ }
+ );
+ }
+ }
+
+ private async Task AddOptionalClientScopesAsync(string clientName, List scopes)
+ {
+ var client = (await _keycloakClient.GetClientsAsync(_keycloakOptions.RealmName, clientId: clientName))
+ .FirstOrDefault();
+ if (client == null)
+ {
+ _logger.LogError($"Couldn't find {clientName}! Could not seed optional scopes!");
+ return;
+ }
+
+ var clientOptionalScopes =
+ (await _keycloakClient.GetOptionalClientScopesAsync(_keycloakOptions.RealmName, client.Id)).ToList();
+
+ var clientScopes = (await _keycloakClient.GetClientScopesAsync(_keycloakOptions.RealmName)).ToList();
+
+ foreach (var scope in scopes)
+ {
+ if (!clientOptionalScopes.Any(q => q.Name == scope))
+ {
+ var serviceScope = clientScopes.First(q => q.Name == scope);
+ _logger.LogInformation($"Seeding {scope} scope to {clientName}.");
+ await _keycloakClient.UpdateOptionalClientScopeAsync(_keycloakOptions.RealmName, client.Id,
+ serviceScope.Id);
+ }
+ }
+ }
+
+ private async Task UpdateAdminUserAsync()
+ {
+ var users = await _keycloakClient.GetUsersAsync(_keycloakOptions.RealmName, username: "admin");
+ var adminUser = users.FirstOrDefault();
+ if (adminUser == null)
+ {
+ throw new Exception(
+ "Keycloak admin user is not provided, check if KEYCLOAK_ADMIN environment variable is passed properly.");
+ }
+
+ if (string.IsNullOrEmpty(adminUser.Email))
+ {
+ adminUser.Email = "admin@abp.io";
+ adminUser.FirstName = "admin";
+ adminUser.EmailVerified = true;
+
+ _logger.LogInformation("Updating admin user with email and first name...");
+ await _keycloakClient.UpdateUserAsync(_keycloakOptions.RealmName, adminUser.Id, adminUser);
+ }
+ }
+}
\ No newline at end of file
diff --git a/shared/EShopOnAbp.Keycloak.DbMigrator/MigrationService.cs b/shared/EShopOnAbp.Keycloak.DbMigrator/MigrationService.cs
new file mode 100644
index 00000000..aa0cf304
--- /dev/null
+++ b/shared/EShopOnAbp.Keycloak.DbMigrator/MigrationService.cs
@@ -0,0 +1,30 @@
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using Volo.Abp.Data;
+using Volo.Abp.DependencyInjection;
+
+namespace EShopOnAbp.DbMigrator;
+
+public class MigrationService: ITransientDependency
+{
+ private readonly ILogger _logger;
+ private readonly IDataSeeder _dataSeeder;
+
+ public MigrationService(ILogger logger, IDataSeeder dataSeeder)
+ {
+ _logger = logger;
+ _dataSeeder = dataSeeder;
+ }
+
+ public async Task MigrateAsync(CancellationToken cancellationToken)
+ {
+ // Check if keycloak api is available
+
+ //Seed data
+ await _dataSeeder.SeedAsync();
+
+ _logger.LogInformation("Migration completed!");
+ }
+
+}
\ No newline at end of file
diff --git a/shared/EShopOnAbp.Keycloak.DbMigrator/Program.cs b/shared/EShopOnAbp.Keycloak.DbMigrator/Program.cs
new file mode 100644
index 00000000..bd60e4fa
--- /dev/null
+++ b/shared/EShopOnAbp.Keycloak.DbMigrator/Program.cs
@@ -0,0 +1,35 @@
+using System.Threading.Tasks;
+using EShopOnAbp.DbMigrator;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+using Serilog;
+using Serilog.Events;
+
+class Program
+{
+ async static Task Main(string[] args)
+ {
+ Log.Logger = new LoggerConfiguration()
+#if DEBUG
+ .MinimumLevel.Debug()
+#else
+ .MinimumLevel.Information()
+#endif
+ .MinimumLevel.Override("Microsoft", LogEventLevel.Information)
+ .MinimumLevel.Override("Microsoft.EntityFrameworkCore", LogEventLevel.Warning)
+ .Enrich.FromLogContext()
+ .Enrich.WithProperty("Application", $"DbMigrator")
+ .WriteTo.Async(c => c.File("Logs/logs.txt"))
+ .WriteTo.Async(c => c.Console())
+ .CreateLogger();
+
+ await CreateHostBuilder(args).RunConsoleAsync();
+ }
+
+ public static IHostBuilder CreateHostBuilder(string[] args) =>
+ Host.CreateDefaultBuilder(args)
+ .AddAppSettingsSecretsJson()
+ .ConfigureLogging((context, logging) => logging.ClearProviders())
+ .ConfigureServices((hostContext, services) => { services.AddHostedService(); });
+}
\ No newline at end of file
diff --git a/shared/EShopOnAbp.Keycloak.DbMigrator/appsettings.json b/shared/EShopOnAbp.Keycloak.DbMigrator/appsettings.json
new file mode 100644
index 00000000..13bcec2d
--- /dev/null
+++ b/shared/EShopOnAbp.Keycloak.DbMigrator/appsettings.json
@@ -0,0 +1,46 @@
+{
+ "Keycloak": {
+ "url": "http://localhost:8080",
+ "adminUsername": "admin",
+ "adminPassword": "1q2w3E*",
+ "realmName": "master"
+ },
+ "Clients": {
+ "Web": {
+ "RootUrl": "http://localhost:4200"
+ },
+ "PublicWeb": {
+ "RootUrl": "https://localhost:44335"
+ },
+ "WebGateway": {
+ "RootUrl": "https://localhost:44372"
+ },
+ "PublicWebGateway": {
+ "RootUrl": "https://localhost:44373"
+ },
+ "AccountService": {
+ "RootUrl": "https://localhost:44330"
+ },
+ "IdentityService": {
+ "RootUrl": "https://localhost:44351"
+ },
+ "AdministrationService": {
+ "RootUrl": "https://localhost:44353"
+ },
+ "CatalogService": {
+ "RootUrl": "https://localhost:44354"
+ },
+ "BasketService": {
+ "RootUrl": "https://localhost:44355"
+ },
+ "OrderingService": {
+ "RootUrl": "https://localhost:44356"
+ },
+ "PaymentService": {
+ "RootUrl": "https://localhost:44357"
+ },
+ "CmskitService": {
+ "RootUrl": "https://localhost:44358"
+ }
+ }
+}
\ No newline at end of file
diff --git a/shared/EShopOnAbp.Shared.Hosting.Microservices/EShopOnAbpSharedHostingMicroservicesModule.cs b/shared/EShopOnAbp.Shared.Hosting.Microservices/EShopOnAbpSharedHostingMicroservicesModule.cs
index e497d6d5..fdad69fd 100644
--- a/shared/EShopOnAbp.Shared.Hosting.Microservices/EShopOnAbpSharedHostingMicroservicesModule.cs
+++ b/shared/EShopOnAbp.Shared.Hosting.Microservices/EShopOnAbpSharedHostingMicroservicesModule.cs
@@ -29,6 +29,7 @@ public class EShopOnAbpSharedHostingMicroservicesModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
+ Microsoft.IdentityModel.Logging.IdentityModelEventSource.ShowPII = true;
var configuration = context.Services.GetConfiguration();
Configure(options =>
diff --git a/tye.yaml b/tye.yaml
index 0edccd81..de0f4014 100644
--- a/tye.yaml
+++ b/tye.yaml
@@ -1,13 +1,13 @@
name: EShopOnAbp
services:
-- name: auth-server
- project: apps/auth-server/src/EShopOnAbp.AuthServer/EShopOnAbp.AuthServer.csproj
- bindings:
- - protocol: https
- port: 44330
- env:
- - Kestrel__Certificates__Default__Path=../../../../etc/dev-cert/localhost.pfx
- - Kestrel__Certificates__Default__Password=8b6039b6-c67a-448b-977b-0ce6d3fcfd49
+# - name: auth-server
+# project: apps/auth-server/src/EShopOnAbp.AuthServer/EShopOnAbp.AuthServer.csproj
+# bindings:
+# - protocol: https
+# port: 44330
+# env:
+# - Kestrel__Certificates__Default__Path=../../../../etc/dev-cert/localhost.pfx
+# - Kestrel__Certificates__Default__Password=8b6039b6-c67a-448b-977b-0ce6d3fcfd49
- name: identity-service
project: services/identity/src/EShopOnAbp.IdentityService.HttpApi.Host/EShopOnAbp.IdentityService.HttpApi.Host.csproj
@@ -101,4 +101,7 @@ services:
port: 44335
env:
- Kestrel__Certificates__Default__Path=../../../../etc/dev-cert/localhost.pfx
- - Kestrel__Certificates__Default__Password=8b6039b6-c67a-448b-977b-0ce6d3fcfd49
\ No newline at end of file
+ - Kestrel__Certificates__Default__Password=8b6039b6-c67a-448b-977b-0ce6d3fcfd49
+
+- name: keycloak-seeder
+ project: shared/EShopOnAbp.Keycloak.DbMigrator/EShopOnAbp.Keycloak.DbMigrator.csproj
\ No newline at end of file