diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 2704de9..fb04e9d 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -3,12 +3,12 @@ "hostRequirements": { "cpus": 4 }, - "onCreateCommand": "wget https://download.visualstudio.microsoft.com/download/pr/9144f37e-b370-41ee-a86f-2d2a69251652/bc1d544112ec134184a5aec7f7a1eaf9/dotnet-sdk-8.0.100-rc.2.23502.2-linux-x64.tar.gz -O $HOME/dotnet.tar.gz && export DOTNET_ROOT=$HOME/.dotnet && mkdir -p \"$DOTNET_ROOT\" && tar zxf $HOME/dotnet.tar.gz -C \"$DOTNET_ROOT\" && export PATH=$DOTNET_ROOT:$DOTNET_ROOT/tools:$PATH && dotnet dev-certs https --trust && dotnet tool install --global dotnet-ef --version 8.0.0-rc.1.23419.6 && dotnet ef database update --project src/Server/Api/Bit.TemplatePlayground.Server.Api.csproj && dotnet build src/Client/Web/Bit.TemplatePlayground.Client.Web.csproj -t:BeforeBuildTasks --no-restore", + "onCreateCommand": "wget https://download.visualstudio.microsoft.com/download/pr/5226a5fa-8c0b-474f-b79a-8984ad7c5beb/3113ccbf789c9fd29972835f0f334b7a/dotnet-sdk-8.0.100-linux-x64.tar.gz -O $HOME/dotnet.tar.gz && export DOTNET_ROOT=$HOME/.dotnet && mkdir -p \"$DOTNET_ROOT\" && tar zxf $HOME/dotnet.tar.gz -C \"$DOTNET_ROOT\" && export PATH=$DOTNET_ROOT:$DOTNET_ROOT/tools:$PATH && dotnet dev-certs https --trust && dotnet tool install --global dotnet-ef --version 8.0.0 && dotnet ef database update --project src/Server/Api/Bit.TemplatePlayground.Server.Api.csproj && dotnet build src/Client/Web/Bit.TemplatePlayground.Client.Web.csproj -t:BeforeBuildTasks --no-restore", "waitFor": "onCreateCommand", "customizations": { "codespaces": { "openFiles": [ - "src/Client/Core/Pages/Home/HomePage.razor" + "src/Client/Core/Pages/HomePage.razor" ] }, "vscode": { diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 52cbffd..cd130f5 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -4,8 +4,8 @@ name: Bit.TemplatePlayground CD env: WEB_APP_DEPLOYMENT_TYPE: 'Spa' - API_SERVER_ADDRESS: 'https://bit.templateplayground.bitplatform.dev/api/' - APP_SERVICE_NAME: 'app-service-ad-test' + API_SERVER_ADDRESS: 'https://bp.bitplatform.dev/api/' + APP_SERVICE_NAME: 'app-service-bp-test' IOS_CODE_SIGN_PROVISION: 'Bit.TemplatePlayground' on: @@ -35,6 +35,13 @@ jobs: - uses: actions/setup-node@v3 with: node-version: 18 + + - name: Update appsettings.json api server address + uses: microsoft/variable-substitution@v1 + with: + files: 'src/Client/Core/appsettings.json' + env: + ApiServerAddress: ${{ env.API_SERVER_ADDRESS }} - name: Switch to blazor web assembly run: sed -i 's/Microsoft.NET.Sdk.Web/Microsoft.NET.Sdk.BlazorWebAssembly/g' src/Client/Web/Bit.TemplatePlayground.Client.Web.csproj @@ -90,7 +97,7 @@ jobs: files: 'appsettings.json' env: ConnectionStrings.SqlServerConnectionString: ${{ secrets.DB_CONNECTION_STRING }} - AppSettings.JwtSettings.IdentityCertificatePassword: ${{ secrets.API_IDENTITY_CERTIFICATE_PASSWORD }} + AppSettings.IdentitySettings.IdentityCertificatePassword: ${{ secrets.API_IDENTITY_CERTIFICATE_PASSWORD }} - name: Delete IdentityCertificate.pfx run: | @@ -262,6 +269,10 @@ jobs: with: global-json-file: global.json + - uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: '15' + - uses: actions/setup-node@v3 with: node-version: 18 @@ -292,7 +303,7 @@ jobs: - name: Download Apple Provisioning Profiles uses: Apple-Actions/download-provisioning-profiles@v1 with: - bundle-id: 'com.bitplatform.Bit.TemplatePlayground.Template' + bundle-id: 'com.bitplatform.BP.Template' issuer-id: ${{ secrets.APPSTORE_API_KEY_ISSUER_ID }} api-key-id: ${{ secrets.APPSTORE_API_KEY_ID }} api-private-key: ${{ secrets.APPSTORE_API_KEY_PRIVATE_KEY }} diff --git a/.vscode/launch.json b/.vscode/launch.json index e8e4a9c..f1f8858 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -17,7 +17,7 @@ "cwd": "${workspaceFolder}/src/Server/Api", "program": "dotnet", "args": [ - "watch", + "run", "--project", ".", "verbose" diff --git a/Bit.TemplatePlayground.slnLaunch b/Bit.TemplatePlayground.slnLaunch new file mode 100644 index 0000000..93acd04 --- /dev/null +++ b/Bit.TemplatePlayground.slnLaunch @@ -0,0 +1,57 @@ +[ + { + "Name": "Api \u002B Blazor Hybrid", + "Projects": [ + { + "Name": "src\\Server\\Api\\Bit.TemplatePlayground.Server.Api.csproj", + "Action": "Start", + "DebugTarget": "Bit.TemplatePlayground.Server.Api-Swagger" + }, + { + "Name": "src\\Client\\App\\Bit.TemplatePlayground.Client.App.csproj", + "Action": "Start", + "DebugTarget": "Windows Machine" + } + ] + }, + { + "Name": "Api \u002B Blazor Server", + "Projects": [ + { + "Name": "src\\Server\\Api\\Bit.TemplatePlayground.Server.Api.csproj", + "Action": "Start", + "DebugTarget": "Bit.TemplatePlayground.Server.Api-Swagger" + }, + { + "Name": "src\\Client\\Web\\Bit.TemplatePlayground.Client.Web.csproj", + "Action": "Start", + "DebugTarget": "Bit.TemplatePlayground.Client.Web" + } + ] + }, + { + "Name": "Api \u002B Blazor Wasm", + "Projects": [ + { + "Name": "src\\Server\\Api\\Bit.TemplatePlayground.Server.Api.csproj", + "Action": "Start", + "DebugTarget": "Bit.TemplatePlayground.Server.Api-BlazorWebAssembly" + } + ] + }, + { + "Name": "Api \u002B Blazor Electron", + "Projects": [ + { + "Name": "src\\Server\\Api\\Bit.TemplatePlayground.Server.Api.csproj", + "Action": "Start", + "DebugTarget": "Bit.TemplatePlayground.Server.Api-Swagger" + }, + { + "Name": "src\\Client\\Web\\Bit.TemplatePlayground.Client.Web.csproj", + "Action": "Start", + "DebugTarget": "Bit.TemplatePlayground.Client.Web.Electron" + } + ] + } +] \ No newline at end of file diff --git a/global.json b/global.json index 273f0cf..c41d522 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "8.0.100-rc.2.23502.2", + "version": "8.0.100", "rollForward": "disable" } } \ No newline at end of file diff --git a/src/Client/App/Bit.TemplatePlayground.Client.App.csproj b/src/Client/App/Bit.TemplatePlayground.Client.App.csproj index 5b6fa1c..0f9e737 100644 --- a/src/Client/App/Bit.TemplatePlayground.Client.App.csproj +++ b/src/Client/App/Bit.TemplatePlayground.Client.App.csproj @@ -15,8 +15,8 @@ Bit.TemplatePlayground - com.bitplatform.Bit.TemplatePlayground.Template - 2CF3C9D5-3732-41B7-95A7-77338B8C8474 + com.bitplatform.BP.Template + 11F05C30-B7FE-415E-AABC-21FF361FB5FE 1.0 @@ -26,7 +26,6 @@ True $(NoWarn);ClassWithoutModifierAnalyzer - enable @@ -40,8 +39,8 @@ true True - all - + all + @@ -65,12 +64,12 @@ - + - + @@ -83,14 +82,14 @@ - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -102,10 +101,10 @@ - - - - + + + + true false - service-worker-assets.js + service-worker-assets.js false true @@ -14,7 +14,7 @@ true false - + BeforeBuildTasks; $(ResolveStaticWebAssetsInputsDependsOn) @@ -26,27 +26,27 @@ - + - + - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + @@ -58,12 +58,12 @@ - + - + PreserveNewest @@ -72,7 +72,7 @@ - + @@ -84,5 +84,5 @@ - + diff --git a/src/Client/Web/Extensions/HttpRequestExtensions.cs b/src/Client/Web/Extensions/HttpRequestExtensions.cs index 5d373f0..b8d735e 100644 --- a/src/Client/Web/Extensions/HttpRequestExtensions.cs +++ b/src/Client/Web/Extensions/HttpRequestExtensions.cs @@ -4,6 +4,20 @@ namespace Microsoft.AspNetCore.Http; public static class HttpRequestExtensions { + /// + /// https://blog.elmah.io/how-to-get-base-url-in-asp-net-core/ + /// + public static string GetBaseUrl(this HttpRequest req) + { + var uriBuilder = new UriBuilder(req.Scheme, req.Host.Host, req.Host.Port ?? -1); + if (uriBuilder.Uri.IsDefaultPort) + { + uriBuilder.Port = -1; + } + + return uriBuilder.Uri.AbsoluteUri; + } + public static bool ShouldRenderStaticMode(this HttpRequest request) { var agent = GetLoweredUserAgent(request); diff --git a/src/Client/Web/Extensions/IServiceCollectionExtensions.cs b/src/Client/Web/Extensions/IServiceCollectionExtensions.cs index c8c32bd..32fa422 100644 --- a/src/Client/Web/Extensions/IServiceCollectionExtensions.cs +++ b/src/Client/Web/Extensions/IServiceCollectionExtensions.cs @@ -7,8 +7,8 @@ public static class IServiceCollectionExtensions public static IServiceCollection AddClientWebServices(this IServiceCollection services) { // Services being registered here can get injected in web (blazor web assembly & blazor server) - services.AddScoped(); - services.AddScoped(); + services.AddTransient(); + services.AddTransient(); return services; } diff --git a/src/Client/Web/Pages/_Layout.cshtml b/src/Client/Web/Pages/_Layout.cshtml index fa20935..5433eba 100644 --- a/src/Client/Web/Pages/_Layout.cshtml +++ b/src/Client/Web/Pages/_Layout.cshtml @@ -49,7 +49,7 @@ - + diff --git a/src/Client/Web/Program.BlazorElectron.cs b/src/Client/Web/Program.BlazorElectron.cs index 4ebf199..81853e2 100644 --- a/src/Client/Web/Program.BlazorElectron.cs +++ b/src/Client/Web/Program.BlazorElectron.cs @@ -12,7 +12,7 @@ public static WebApplication CreateHostBuilder(string[] args) { var builder = WebApplication.CreateBuilder(args); - builder.Configuration.AddJsonStream(typeof(MainLayout).Assembly.GetManifestResourceStream("Bit.TemplatePlayground.Client.Core.appsettings.json")!); + builder.Configuration.AddClientConfigurations(); builder.WebHost.UseElectron(args); builder.Services.AddElectron(); diff --git a/src/Client/Web/Program.BlazorServer.cs b/src/Client/Web/Program.BlazorServer.cs index cc2095c..345cca3 100644 --- a/src/Client/Web/Program.BlazorServer.cs +++ b/src/Client/Web/Program.BlazorServer.cs @@ -8,7 +8,7 @@ public static WebApplication CreateHostBuilder(string[] args) { var builder = WebApplication.CreateBuilder(args); - builder.Configuration.AddJsonStream(typeof(MainLayout).Assembly.GetManifestResourceStream("Bit.TemplatePlayground.Client.Core.appsettings.json")!); + builder.Configuration.AddClientConfigurations(); #if DEBUG if (OperatingSystem.IsWindows()) diff --git a/src/Client/Web/Program.BlazorWebAssembly.cs b/src/Client/Web/Program.BlazorWebAssembly.cs index b4b97cb..c8bbd8e 100644 --- a/src/Client/Web/Program.BlazorWebAssembly.cs +++ b/src/Client/Web/Program.BlazorWebAssembly.cs @@ -1,4 +1,5 @@ #if BlazorWebAssembly +using Bit.TemplatePlayground.Client.Core.Services.HttpMessageHandlers; using Microsoft.AspNetCore.Components.WebAssembly.Hosting; using Microsoft.AspNetCore.Components.WebAssembly.Services; #endif @@ -12,14 +13,25 @@ public static WebAssemblyHost CreateHostBuilder(string[] args) { var builder = WebAssemblyHostBuilder.CreateDefault(); - builder.Configuration.AddJsonStream(typeof(MainLayout).Assembly.GetManifestResourceStream("Bit.TemplatePlayground.Client.Core.appsettings.json")!); - - var apiServerAddressConfig = builder.Configuration.GetApiServerAddress(); - - var apiServerAddress = new Uri($"{builder.HostEnvironment.BaseAddress}{apiServerAddressConfig}"); - - builder.Services.AddSingleton(sp => new HttpClient(sp.GetRequiredService()) { BaseAddress = apiServerAddress }); - builder.Services.AddScoped(); + builder.Configuration.AddClientConfigurations(); + + Uri.TryCreate(builder.Configuration.GetApiServerAddress(), UriKind.RelativeOrAbsolute, out var apiServerAddress); + + if (apiServerAddress!.IsAbsoluteUri is false) + { + apiServerAddress = new Uri($"{builder.HostEnvironment.BaseAddress}{apiServerAddress}"); + } + + builder.Services.AddTransient(sp => + { + var handler = sp.GetRequiredService(); + HttpClient httpClient = new(handler) + { + BaseAddress = apiServerAddress + }; + return httpClient; + }); + builder.Services.AddTransient(); builder.Services.AddTransient(); builder.Services.AddSharedServices(); diff --git a/src/Client/Web/Services/ServerSideAuthTokenProvider.cs b/src/Client/Web/Services/ServerSideAuthTokenProvider.cs index 03c3792..094338e 100644 --- a/src/Client/Web/Services/ServerSideAuthTokenProvider.cs +++ b/src/Client/Web/Services/ServerSideAuthTokenProvider.cs @@ -12,20 +12,20 @@ namespace Bit.TemplatePlayground.Client.Web.Services; #if BlazorServer public partial class ServerSideAuthTokenProvider : IAuthTokenProvider { - [AutoInject] private IJSRuntime _jsRuntime = default!; [AutoInject] private IHttpContextAccessor _httpContextAccessor = default!; + [AutoInject] private IJSRuntime _jsRuntime = default!; + + private static readonly PropertyInfo IsInitializedProp = Assembly.Load("Microsoft.AspNetCore.Components.Server")! + .GetType("Microsoft.AspNetCore.Components.Server.Circuits.RemoteJSRuntime")! + .GetProperty("IsInitialized")!; - private static readonly PropertyInfo? IsInitializedProp = Assembly.Load("Microsoft.AspNetCore.Components.Server") - .GetType("Microsoft.AspNetCore.Components.Server.Circuits.RemoteJSRuntime") - ?.GetProperty("IsInitialized"); + public bool IsInitialized => (bool)IsInitializedProp.GetValue(_jsRuntime)!; public async Task GetAccessTokenAsync() { - var isInitialized = (bool)(IsInitializedProp?.GetValue(_jsRuntime) ?? false); - - if (isInitialized) + if (IsInitialized) { - return await _jsRuntime.InvokeAsync("App.getCookie", "access_token"); + return await _jsRuntime.GetCookie("access_token"); } return _httpContextAccessor.HttpContext?.Request.Cookies["access_token"]; @@ -41,9 +41,10 @@ public ServerSideAuthTokenProvider(IHttpContextAccessor httpContextAccessor) _httpContextAccessor = httpContextAccessor; } + public bool IsInitialized => false; + public async Task GetAccessTokenAsync() { - await Task.CompletedTask; return _httpContextAccessor.HttpContext?.Request.Cookies["access_token"]; } } diff --git a/src/Client/Web/Startup/Middlewares.cs b/src/Client/Web/Startup/Middlewares.cs index c421a2f..422d70b 100644 --- a/src/Client/Web/Startup/Middlewares.cs +++ b/src/Client/Web/Startup/Middlewares.cs @@ -22,8 +22,6 @@ public static void Use(WebApplication app, IHostEnvironment env) } app.UseStaticFiles(); - app.UseRouting(); - #if MultilingualEnabled var supportedCultures = CultureInfoManager.SupportedCultures.Select(sc => CultureInfoManager.CreateCultureInfo(sc.code)).ToArray(); app.UseRequestLocalization(new RequestLocalizationOptions diff --git a/src/Client/Web/Startup/Services.cs b/src/Client/Web/Startup/Services.cs index ba1a964..1f51c5a 100644 --- a/src/Client/Web/Startup/Services.cs +++ b/src/Client/Web/Startup/Services.cs @@ -1,7 +1,8 @@ #if BlazorServer using System.IO.Compression; -using Microsoft.AspNetCore.ResponseCompression; +using Bit.TemplatePlayground.Client.Core.Services.HttpMessageHandlers; using Bit.TemplatePlayground.Client.Web.Services; +using Microsoft.AspNetCore.ResponseCompression; namespace Bit.TemplatePlayground.Client.Web.Startup; @@ -9,11 +10,13 @@ public static class Services { public static void Add(IServiceCollection services, IConfiguration configuration) { - services.AddScoped(sp => + services.AddTransient(sp => { - HttpClient httpClient = new(sp.GetRequiredService()) + Uri.TryCreate(configuration.GetApiServerAddress(), UriKind.Absolute, out var apiServerAddress); + var handler = sp.GetRequiredService(); + HttpClient httpClient = new(handler) { - BaseAddress = new Uri(sp.GetRequiredService().GetApiServerAddress()) + BaseAddress = apiServerAddress }; return httpClient; @@ -25,7 +28,7 @@ public static void Add(IServiceCollection services, IConfiguration configuration services.AddResponseCompression(opts => { opts.EnableForHttps = true; - opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(new[] { "application/octet-stream" }).ToArray(); + opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(["application/octet-stream"]).ToArray(); opts.Providers.Add(); opts.Providers.Add(); }) diff --git a/src/Client/Web/wwwroot/manifest.json b/src/Client/Web/wwwroot/manifest.json index 77faccb..626f656 100644 --- a/src/Client/Web/wwwroot/manifest.json +++ b/src/Client/Web/wwwroot/manifest.json @@ -13,7 +13,7 @@ "short_name": "bit Bit.TemplatePlayground", "prefer_related_applications": false, "display_override": [ "window-controls-overlay" ], - "description": "bit Bit.TemplatePlayground is a project template that includes all the necessary parts to create a fully-featured admin/management website.", + "description": "bit Bit.TemplatePlayground is a project template that includes all the necessary parts to create a fully-featured web app.", "features": [ "Cross Platform", "fast", diff --git a/src/Client/Web/wwwroot/service-worker.js b/src/Client/Web/wwwroot/service-worker.js index b8200d2..2a8350b 100644 --- a/src/Client/Web/wwwroot/service-worker.js +++ b/src/Client/Web/wwwroot/service-worker.js @@ -1,4 +1,4 @@ -// bit version: 7.1.0 +// bit version: 8.1.0 // https://github.com/bitfoundation/bitplatform/tree/develop/src/Bswup // Make sure to apply all changes you make here to the service-worker.published.js file too (if required). diff --git a/src/Client/Web/wwwroot/service-worker.published.js b/src/Client/Web/wwwroot/service-worker.published.js index f65bdd4..61e8568 100644 --- a/src/Client/Web/wwwroot/service-worker.published.js +++ b/src/Client/Web/wwwroot/service-worker.published.js @@ -1,4 +1,4 @@ -// bit version: 7.1.0 +// bit version: 8.1.0 // https://github.com/bitfoundation/bitplatform/tree/develop/src/Bswup self.assetsInclude = []; diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 08b19de..135d3fd 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -1,4 +1,4 @@ - + @@ -28,11 +28,11 @@ $(DefineConstants);Windows $(DefineConstants);Mac - 11.0 + 12.0 enable enable true - $(NoWarn);CS1591 + $(NoWarn);CS1998;CS1591 en true diff --git a/src/Infra/Iac/Bit.TemplatePlayground.Iac.csproj b/src/Infra/Iac/Bit.TemplatePlayground.Iac.csproj index 7bdae7f..ba13bf0 100644 --- a/src/Infra/Iac/Bit.TemplatePlayground.Iac.csproj +++ b/src/Infra/Iac/Bit.TemplatePlayground.Iac.csproj @@ -3,8 +3,6 @@ Exe net8.0 - enable - enable @@ -12,7 +10,7 @@ - + diff --git a/src/Infra/Iac/AdStack.cs b/src/Infra/Iac/BpStack.cs similarity index 89% rename from src/Infra/Iac/AdStack.cs rename to src/Infra/Iac/BpStack.cs index 3d51cb8..bc03716 100644 --- a/src/Infra/Iac/AdStack.cs +++ b/src/Infra/Iac/BpStack.cs @@ -20,16 +20,16 @@ namespace Bit.TemplatePlayground.Iac; -public class AdStack : Stack +public class BpStack : Stack { - public AdStack() + public BpStack() { string stackName = Pulumi.Deployment.Instance.StackName; Config pulumiConfig = new(); - var sqlDatabaseDbAdminId = pulumiConfig.Require("sql-server-ad-db-admin-id"); - var sqlDatabaseDbAdminPassword = pulumiConfig.RequireSecret("sql-server-ad-db-admin-password"); + var sqlDatabaseDbAdminId = pulumiConfig.Require("sql-server-bp-db-admin-id"); + var sqlDatabaseDbAdminPassword = pulumiConfig.RequireSecret("sql-server-bp-db-admin-password"); var defaultEmailFrom = pulumiConfig.Require("default-email-from"); var emailServerHost = pulumiConfig.Require("email-server-host"); @@ -39,22 +39,22 @@ public AdStack() var identityCertificatePassword = pulumiConfig.RequireSecret("identity-certificate-password"); - ResourceGroup resourceGroup = new($"ad-{stackName}", new ResourceGroupArgs + ResourceGroup resourceGroup = new($"bp-{stackName}", new ResourceGroupArgs { - ResourceGroupName = $"ad-{stackName}" - }, options: new() { ImportId = $"/subscriptions/{GetClientConfig.InvokeAsync().GetAwaiter().GetResult().SubscriptionId}/resourceGroups/ad-prod" }); + ResourceGroupName = $"bp-{stackName}" + }, options: new() { ImportId = $"/subscriptions/{GetClientConfig.InvokeAsync().GetAwaiter().GetResult().SubscriptionId}/resourceGroups/bp-prod" }); - Workspace appInsightsWorkspace = new($"insights-wkspc-ad-{stackName}", new() + Workspace appInsightsWorkspace = new($"insights-wkspc-bp-{stackName}", new() { - WorkspaceName = $"insights-wkspc-ad-{stackName}", + WorkspaceName = $"insights-wkspc-bp-{stackName}", ResourceGroupName = resourceGroup.Name, Location = resourceGroup.Location, RetentionInDays = 30 }); - AppInsights appInsights = new($"app-insights-ad-{stackName}", new() + AppInsights appInsights = new($"app-insights-bp-{stackName}", new() { - ResourceName = $"app-insights-ad-{stackName}", + ResourceName = $"app-insights-bp-{stackName}", ResourceGroupName = resourceGroup.Name, Location = resourceGroup.Location, ApplicationType = AppInsightsWebApplicationType.Web, @@ -68,18 +68,18 @@ public AdStack() }).Apply(workspace => workspace.Id) }); - SqlServer sqlServer = new($"sql-server-ad-{stackName}", new() + SqlServer sqlServer = new($"sql-server-bp-{stackName}", new() { - ServerName = $"sql-server-ad-{stackName}", + ServerName = $"sql-server-bp-{stackName}", ResourceGroupName = resourceGroup.Name, Location = resourceGroup.Location, AdministratorLogin = sqlDatabaseDbAdminId, AdministratorLoginPassword = sqlDatabaseDbAdminPassword }); - SqlDatabase sqlDatabase = new($"sql-database-ad-{stackName}", new() + SqlDatabase sqlDatabase = new($"sql-database-bp-{stackName}", new() { - DatabaseName = $"sql-database-ad-{stackName}", + DatabaseName = $"sql-database-bp-{stackName}", ResourceGroupName = resourceGroup.Name, Location = resourceGroup.Location, ServerName = sqlServer.Name, @@ -91,9 +91,9 @@ public AdStack() } }); - AppServicePlan appServicePlan = new($"app-plan-ad-{stackName}", new() + AppServicePlan appServicePlan = new($"app-plan-bp-{stackName}", new() { - Name = $"app-plan-ad-{stackName}", + Name = $"app-plan-bp-{stackName}", ResourceGroupName = resourceGroup.Name, Location = resourceGroup.Location, Kind = "Linux", @@ -108,14 +108,14 @@ public AdStack() } }); - string vaultName = $"vault-ad-{stackName}"; + string vaultName = $"vault-bp-{stackName}"; string sqlDatabaseConnectionStringSecretName = $"sql-connection-secret"; string emailServerPasswordSecretName = "email-server-password-secret"; string identityCertificatePasswordSecretName = "identity-certificate-password-secret"; - WebApp webApp = new($"app-service-ad-{stackName}", new() + WebApp webApp = new($"app-service-bp-{stackName}", new() { - Name = $"app-service-ad-{stackName}", + Name = $"app-service-bp-{stackName}", ResourceGroupName = resourceGroup.Name, Location = resourceGroup.Location, ServerFarmId = appServicePlan.Id, @@ -132,8 +132,8 @@ public AdStack() FtpsState = FtpsState.Disabled, LinuxFxVersion = "DOTNETCORE|8.0", AppCommandLine = "dotnet Bit.TemplatePlayground.Server.Api.dll", - AppSettings = new() - { + AppSettings = + [ new NameValuePairArgs { Name = "ApplicationInsights__InstrumentationKey", Value = appInsights.InstrumentationKey }, new NameValuePairArgs { Name = "APPINSIGHTS_INSTRUMENTATIONKEY", Value = appInsights.InstrumentationKey }, new NameValuePairArgs { Name = "ASPNETCORE_ENVIRONMENT", Value = stackName == "test" ? "Test" : "Production" }, @@ -157,19 +157,19 @@ public AdStack() }, new NameValuePairArgs { - Name = "AppSettings__JwtSettings__IdentityCertificatePassword", + Name = "AppSettings__IdentitySettings__IdentityCertificatePassword", Value = $"@Microsoft.KeyVault(VaultName={vaultName};SecretName={identityCertificatePasswordSecretName})" }, - }, - ConnectionStrings = new() - { + ], + ConnectionStrings = + [ new ConnStringInfoArgs { Name = "SqlServerConnectionString", Type = ConnectionStringType.SQLAzure, ConnectionString = $"@Microsoft.KeyVault(VaultName={vaultName};SecretName={sqlDatabaseConnectionStringSecretName})" } - } + ] } }); @@ -201,7 +201,7 @@ public AdStack() return string.Empty; }); - Vault vault = new Vault($"vault-ad-{stackName}", new() + Vault vault = new Vault($"vault-bp-{stackName}", new() { ResourceGroupName = resourceGroup.Name, Location = resourceGroup.Location, @@ -216,8 +216,7 @@ public AdStack() EnableSoftDelete = false, AccessPolicies = new List { - new AccessPolicyEntryArgs - { + new() { TenantId = Output.Create(GetClientConfig.InvokeAsync()).Apply(clientConfig => clientConfig.TenantId), ObjectId = Output.Tuple(resourceGroup.Name, webApp.Name).Apply(t => { diff --git a/src/Infra/Iac/Program.cs b/src/Infra/Iac/Program.cs index 430d40a..e3c7572 100644 --- a/src/Infra/Iac/Program.cs +++ b/src/Infra/Iac/Program.cs @@ -1,7 +1,7 @@ -using Pulumi; -using Bit.TemplatePlayground.Iac; +using Bit.TemplatePlayground.Iac; +using Pulumi; public class Program { - static Task Main() => Deployment.RunAsync(); + static Task Main() => Deployment.RunAsync(); } diff --git a/src/Infra/Iac/Readme.md b/src/Infra/Iac/Readme.md index 5669b43..c106c72 100644 --- a/src/Infra/Iac/Readme.md +++ b/src/Infra/Iac/Readme.md @@ -25,26 +25,26 @@ az provider register --namespace 'Microsoft.KeyVault' ``` -2- Create ad-prod resource group +2- Create bp-prod resource group ``` -az group create --name ad-prod --location eastus +az group create --name bp-prod --location eastus ``` Notes: -* `ad` is an abbreviation for Admin, use the acronym of your choice and replace ad with that (for example abc) using exact match - case sensitive find and replace in this file and AdStack.cs. +* `bp` is an abbreviation for Bit.TemplatePlayground, use the acronym of your choice and replace bp with that (for example abc) using exact match - case sensitive find and replace in this file and AdStack.cs. * You can use any location supported by azure cloud (run `az account list-locations -o table` to see full list of locations) 3- Create [service principals](https://docs.microsoft.com/en-us/azure/active-directory/develop/app-objects-and-service-principals) for prod using followings: ``` -az ad sp create-for-rbac -n "ad-prod" --role Contributor --scopes /subscriptions/{subscriptionId}/resourceGroups/ad-prod +az bp sp create-for-rbac -n "bp-prod" --role Contributor --scopes /subscriptions/{subscriptionId}/resourceGroups/bp-prod ``` Notes: * Replace `{subscriptionId}` with [your own subscription id](https://docs.microsoft.com/en-us/azure/media-services/latest/setup-azure-subscription-how-to) -* Running `az ad sp` will return a json like response that contains `appId`l, `password` and `tenant`. Store them somewhere safe. +* Running `az bp sp` will return a json like response that contains `appId`l, `password` and `tenant`. Store them somewhere safe. 4- Create the stacks folder first, then create `prod` folder in the `stacks` folder. @@ -86,8 +86,8 @@ pulumi config set azure-native:tenantId pulumi config set azure-native:subscriptionId # Provide SQL server's admin user/pass -pulumi config set Bit.TemplatePlayground.Iac:sql-server-ad-db-admin-id -pulumi config set Bit.TemplatePlayground.Iac:sql-server-ad-db-admin-password --secret +pulumi config set Bit.TemplatePlayground.Iac:sql-server-bp-db-admin-id +pulumi config set Bit.TemplatePlayground.Iac:sql-server-bp-db-admin-password --secret # Provide SMTP server's host, port, user, pass and default email sender. pulumi config set Bit.TemplatePlayground.Iac:default-email-from diff --git a/src/Server/Api/.config/dotnet-tools.json b/src/Server/Api/.config/dotnet-tools.json index a465223..e3cadb9 100644 --- a/src/Server/Api/.config/dotnet-tools.json +++ b/src/Server/Api/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "dotnet-ef": { - "version": "8.0.0-rc.2.23480.2", + "version": "8.0.0", "commands": [ "dotnet-ef" ] diff --git a/src/Server/Api/AppSettings.cs b/src/Server/Api/AppSettings.cs index ff92d06..addc122 100644 --- a/src/Server/Api/AppSettings.cs +++ b/src/Server/Api/AppSettings.cs @@ -4,8 +4,6 @@ public class AppSettings { public IdentitySettings IdentitySettings { get; set; } = default!; - public JwtSettings JwtSettings { get; set; } = default!; - public EmailSettings EmailSettings { get; set; } = default!; public HealthCheckSettings HealthCheckSettings { get; set; } = default!; @@ -22,6 +20,11 @@ public class HealthCheckSettings public class IdentitySettings { + public TimeSpan BearerTokenExpiration { get; set; } + public TimeSpan RefreshTokenExpiration { get; set; } + public string Issuer { get; set; } = default!; + public string Audience { get; set; } = default!; + public string IdentityCertificatePassword { get; set; } = default!; public bool PasswordRequireDigit { get; set; } public int PasswordRequiredLength { get; set; } public bool PasswordRequireNonAlphanumeric { get; set; } @@ -32,15 +35,6 @@ public class IdentitySettings public TimeSpan ResetPasswordEmailResendDelay { get; set; } } -public class JwtSettings -{ - public string IdentityCertificatePassword { get; set; } = default!; - public string Issuer { get; set; } = default!; - public string Audience { get; set; } = default!; - public int NotBeforeMinutes { get; set; } - public int ExpirationMinutes { get; set; } -} - public class EmailSettings { public string Host { get; set; } = default!; diff --git a/src/Server/Api/Bit.TemplatePlayground.Server.Api.csproj b/src/Server/Api/Bit.TemplatePlayground.Server.Api.csproj index 863b561..5df5468 100644 --- a/src/Server/Api/Bit.TemplatePlayground.Server.Api.csproj +++ b/src/Server/Api/Bit.TemplatePlayground.Server.Api.csproj @@ -5,43 +5,43 @@ - + - + - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + @@ -64,7 +64,6 @@ - diff --git a/src/Server/Api/Controllers/AppControllerBase.cs b/src/Server/Api/Controllers/AppControllerBase.cs index 9509a85..fb58053 100644 --- a/src/Server/Api/Controllers/AppControllerBase.cs +++ b/src/Server/Api/Controllers/AppControllerBase.cs @@ -7,6 +7,4 @@ public partial class AppControllerBase : ControllerBase [AutoInject] protected AppDbContext DbContext = default!; [AutoInject] protected IStringLocalizer Localizer = default!; - - [AutoInject] protected IUserInformationProvider UserInformationProvider = default!; } diff --git a/src/Server/Api/Controllers/AttachmentController.cs b/src/Server/Api/Controllers/AttachmentController.cs index da78e1f..cc1dd75 100644 --- a/src/Server/Api/Controllers/AttachmentController.cs +++ b/src/Server/Api/Controllers/AttachmentController.cs @@ -1,7 +1,7 @@ -using MimeTypes; -using Bit.TemplatePlayground.Server.Api.Models.Identity; -using SystemFile = System.IO.File; +using Bit.TemplatePlayground.Server.Api.Models.Identity; using ImageMagick; +using MimeTypes; +using SystemFile = System.IO.File; namespace Bit.TemplatePlayground.Server.Api.Controllers; @@ -20,7 +20,7 @@ public async Task UploadProfileImage(IFormFile? file, CancellationToken cancella if (file is null) throw new BadRequestException(); - var userId = UserInformationProvider.GetUserId(); + var userId = User.GetUserId(); var user = await _userManager.FindByIdAsync(userId.ToString()); @@ -74,7 +74,9 @@ public async Task UploadProfileImage(IFormFile? file, CancellationToken cancella user.ProfileImageName = destFileName; - await _userManager.UpdateAsync(user); + var result = await _userManager.UpdateAsync(user); + if (!result.Succeeded) + throw new ResourceValidationException(result.Errors.Select(err => new LocalizedString(err.Code, err.Description)).ToArray()); } catch { @@ -92,7 +94,7 @@ public async Task UploadProfileImage(IFormFile? file, CancellationToken cancella [HttpDelete] public async Task RemoveProfileImage() { - var userId = UserInformationProvider.GetUserId(); + var userId = User.GetUserId(); var user = await _userManager.FindByIdAsync(userId.ToString()); @@ -108,7 +110,9 @@ public async Task RemoveProfileImage() user.ProfileImageName = null; - await _userManager.UpdateAsync(user); + var result = await _userManager.UpdateAsync(user); + if (!result.Succeeded) + throw new ResourceValidationException(result.Errors.Select(err => new LocalizedString(err.Code, err.Description)).ToArray()); SystemFile.Delete(filePath); } @@ -116,7 +120,7 @@ public async Task RemoveProfileImage() [HttpGet] public async Task GetProfileImage() { - var userId = UserInformationProvider.GetUserId(); + var userId = User.GetUserId(); var user = await _userManager.FindByIdAsync(userId.ToString()); diff --git a/src/Server/Api/Controllers/Categories/CategoryController.cs b/src/Server/Api/Controllers/Categories/CategoryController.cs index a7c5479..f94720d 100644 --- a/src/Server/Api/Controllers/Categories/CategoryController.cs +++ b/src/Server/Api/Controllers/Categories/CategoryController.cs @@ -38,7 +38,7 @@ public async Task> GetCategories(ODataQueryOptions(await query.ToListAsync(cancellationToken), totalCount); + return new PagedResult(query.AsAsyncEnumerable(), totalCount); } [HttpPost] @@ -65,15 +65,13 @@ public async Task Update(CategoryDto dto, CancellationToken cancell await DbContext.SaveChangesAsync(cancellationToken); - categoryToUpdate.Patch(dto); - - return dto; + return categoryToUpdate.Map(); } [HttpDelete("{id:int}")] public async Task Delete(int id, CancellationToken cancellationToken) { - if (await DbContext.Products.AnyAsync(p => p.CategoryId == id)) + if (await DbContext.Products.AnyAsync(p => p.CategoryId == id, cancellationToken)) { throw new BadRequestException(Localizer[nameof(AppStrings.CategoryNotEmpty)]); } diff --git a/src/Server/Api/Controllers/Identity/AuthController.cs b/src/Server/Api/Controllers/Identity/IdentityController.cs similarity index 70% rename from src/Server/Api/Controllers/Identity/AuthController.cs rename to src/Server/Api/Controllers/Identity/IdentityController.cs index 1a7a5e9..9bf9cc9 100644 --- a/src/Server/Api/Controllers/Identity/AuthController.cs +++ b/src/Server/Api/Controllers/Identity/IdentityController.cs @@ -1,22 +1,21 @@ using System.Web; -using FluentEmail.Core; -using Bit.TemplatePlayground.Server.Api.Resources; +using Bit.TemplatePlayground.Server.Api.Models.Emailing; using Bit.TemplatePlayground.Server.Api.Models.Identity; +using Bit.TemplatePlayground.Server.Api.Resources; using Bit.TemplatePlayground.Shared.Dtos.Identity; -using Bit.TemplatePlayground.Server.Api.Models.Emailing; -using Microsoft.AspNetCore.Components.Web; +using FluentEmail.Core; +using Microsoft.AspNetCore.Authentication.BearerToken; using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Web; -namespace Bit.TemplatePlayground.Server.Api.Controllers; +namespace Bit.TemplatePlayground.Server.Api.Controllers.Identity; [Microsoft.AspNetCore.Mvc.Route("api/[controller]/[action]")] [ApiController, AllowAnonymous] -public partial class AuthController : AppControllerBase +public partial class IdentityController : AppControllerBase { [AutoInject] private UserManager _userManager = default!; - [AutoInject] private IJwtService _jwtService = default!; - [AutoInject] private SignInManager _signInManager = default!; [AutoInject] private IFluentEmail _fluentEmail = default!; @@ -25,6 +24,10 @@ public partial class AuthController : AppControllerBase [AutoInject] private HtmlRenderer _htmlRenderer = default!; + [AutoInject] private IStringLocalizer _identityLocalizer = default!; + + [AutoInject] private IOptionsMonitor _bearerTokenOptions = default!; + /// /// By leveraging summary tags in your controller's actions and DTO properties you can make your codes much easier to maintain. /// These comments will also be used in swagger docs and ui. @@ -44,16 +47,20 @@ public async Task SignUp(SignUpRequestDto signUpRequest, CancellationToken cance } else { - await _userManager.DeleteAsync(existingUser); + var deleteResult = await _userManager.DeleteAsync(existingUser); + if (!deleteResult.Succeeded) + throw new ResourceValidationException(deleteResult.Errors.Select(err => new LocalizedString(err.Code, err.Description)).ToArray()); userToAdd.ConfirmationEmailRequestedOn = existingUser.ConfirmationEmailRequestedOn; } } + userToAdd.LockoutEnabled = true; + var result = await _userManager.CreateAsync(userToAdd, signUpRequest.Password!); if (result.Succeeded is false) { - throw new ResourceValidationException(result.Errors.Select(e => Localizer.GetString(e.Code, signUpRequest.Email!)).ToArray()); + throw new ResourceValidationException(result.Errors.Select(e => new LocalizedString(e.Code, e.Description)).ToArray()); } await SendConfirmationEmail(new() { Email = userToAdd.Email }, userToAdd, cancellationToken); @@ -118,6 +125,76 @@ private async Task SendConfirmationEmail(SendConfirmationEmailRequestDto sendCon throw new ResourceValidationException(result.ErrorMessages.Select(err => Localizer[err]).ToArray()); } + [HttpGet] + public async Task ConfirmEmail(string email, string token) + { + var user = await _userManager.FindByEmailAsync(email); + + if (user is null) + throw new BadRequestException(Localizer.GetString(nameof(AppStrings.UserNameNotFound), email)); + + var emailConfirmed = user.EmailConfirmed; + var errors = string.Empty; + + if (emailConfirmed is false) + { + var result = await _userManager.ConfirmEmailAsync(user, token); + if (!result.Succeeded) + errors = string.Join(", ", result.Errors.Select(e => $"{e.Code}: {e.Description}")); + emailConfirmed = result.Succeeded; + } + + string url = $"/email-confirmation?email={email}&email-confirmed={emailConfirmed}{(string.IsNullOrEmpty(errors) ? "" : ($"&error={errors}"))}"; + + return Redirect(url); + } + + [HttpPost] + public async Task SignIn(SignInRequestDto signInRequest) + { + _signInManager.AuthenticationScheme = IdentityConstants.BearerScheme; + + var result = await _signInManager.PasswordSignInAsync(signInRequest.UserName!, signInRequest.Password!, isPersistent: false, lockoutOnFailure: true); + + if (result.IsLockedOut) + { + var user = await _userManager.FindByNameAsync(signInRequest.UserName!); + throw new BadRequestException(Localizer.GetString(nameof(AppStrings.UserLockedOut), (DateTimeOffset.UtcNow - user!.LockoutEnd!).Value.ToString("mm\\:ss"))); + } + + /* if (result.RequiresTwoFactor) + { + if (!string.IsNullOrEmpty(signInRequest.TwoFactorCode)) + { + result = await _signInManager.TwoFactorAuthenticatorSignInAsync(signInRequest.TwoFactorCode, rememberClient: true); + } + else if (!string.IsNullOrEmpty(signInRequest.TwoFactorRecoveryCode)) + { + result = await _signInManager.TwoFactorRecoveryCodeSignInAsync(signInRequest.TwoFactorRecoveryCode); + } + } */ + + if (result.Succeeded is false) + throw new UnauthorizedException(Localizer.GetString(nameof(AppStrings.InvalidUsernameOrPassword))); + } + + [HttpPost] + public async Task> Refresh(RefreshRequestDto refreshRequest) + { + var refreshTokenProtector = _bearerTokenOptions.Get(IdentityConstants.BearerScheme).RefreshTokenProtector; + var refreshTicket = refreshTokenProtector.Unprotect(refreshRequest.RefreshToken); + + if (refreshTicket?.Properties?.ExpiresUtc is not { } expiresUtc || DateTimeOffset.UtcNow >= expiresUtc || + await _signInManager.ValidateSecurityStampAsync(refreshTicket.Principal) is not User user) + { + return Challenge(); + } + + var newPrincipal = await _signInManager.CreateUserPrincipalAsync(user); + + return SignIn(newPrincipal, authenticationScheme: IdentityConstants.BearerScheme); + } + [HttpPost] public async Task SendResetPasswordEmail(SendResetPasswordEmailRequestDto sendResetPasswordEmailRequest , CancellationToken cancellationToken) @@ -136,11 +213,7 @@ public async Task SendResetPasswordEmail(SendResetPasswordEmailRequestDto sendRe var resetPasswordLink = $"reset-password?email={HttpUtility.UrlEncode(user.Email)}&token={HttpUtility.UrlEncode(token)}"; -#if BlazorServer - resetPasswordLink = $"{AppSettings.WebServerAddress}{resetPasswordLink}"; -#else resetPasswordLink = $"{new Uri($"{HttpContext.Request.Scheme}://{HttpContext.Request.Host}{HttpContext.Request.PathBase}")}{resetPasswordLink}"; -#endif var body = await _htmlRenderer.Dispatcher.InvokeAsync(async () => { @@ -173,27 +246,6 @@ public async Task SendResetPasswordEmail(SendResetPasswordEmailRequestDto sendRe throw new ResourceValidationException(result.ErrorMessages.Select(err => Localizer[err]).ToArray()); } - [HttpGet] - public async Task ConfirmEmail(string email, string token) - { - var user = await _userManager.FindByEmailAsync(email); - - if (user is null) - throw new BadRequestException(Localizer.GetString(nameof(AppStrings.UserNameNotFound), email)); - - var emailConfirmed = user.EmailConfirmed || (await _userManager.ConfirmEmailAsync(user, token)).Succeeded; - - string url = $"email-confirmation?email={email}&email-confirmed={emailConfirmed}"; - -#if BlazorServer - url = $"{AppSettings.WebServerAddress}{url}"; -#else - url = $"/{url}"; -#endif - - return Redirect(url); - } - [HttpPost] public async Task ResetPassword(ResetPasswordRequestDto resetPasswordRequest) { @@ -205,25 +257,6 @@ public async Task ResetPassword(ResetPasswordRequestDto resetPasswordRequest) var result = await _userManager.ResetPasswordAsync(user, resetPasswordRequest.Token!, resetPasswordRequest.Password!); if (!result.Succeeded) - throw new ResourceValidationException(result.Errors.Select(e => Localizer.GetString(e.Code, resetPasswordRequest.Email!)).ToArray()); - } - - [HttpPost] - public async Task SignIn(SignInRequestDto signInRequest) - { - var user = await _userManager.FindByNameAsync(signInRequest.UserName!); - - if (user is null) - throw new BadRequestException(Localizer.GetString(nameof(AppStrings.UserNameNotFound), signInRequest.UserName!)); - - var checkPasswordResult = await _signInManager.CheckPasswordSignInAsync(user, signInRequest.Password!, lockoutOnFailure: true); - - if (checkPasswordResult.IsLockedOut) - throw new BadRequestException(Localizer.GetString(nameof(AppStrings.UserLockedOut), (DateTimeOffset.UtcNow - user.LockoutEnd!).Value.ToString("mm\\:ss"))); - - if (!checkPasswordResult.Succeeded) - throw new BadRequestException(Localizer.GetString(nameof(AppStrings.InvalidUsernameOrPassword), signInRequest.UserName!)); - - return await _jwtService.GenerateToken(user); + throw new ResourceValidationException(result.Errors.Select(e => new LocalizedString(e.Code, e.Description)).ToArray()); } } diff --git a/src/Server/Api/Controllers/Identity/UserController.cs b/src/Server/Api/Controllers/Identity/UserController.cs index 84020e4..3747e2b 100644 --- a/src/Server/Api/Controllers/Identity/UserController.cs +++ b/src/Server/Api/Controllers/Identity/UserController.cs @@ -34,7 +34,9 @@ public async Task Update(EditUserDto userDto, CancellationToken cancell userDto.Patch(user); - await _userManager.UpdateAsync(user); + var result = await _userManager.UpdateAsync(user); + if (!result.Succeeded) + throw new ResourceValidationException(result.Errors.Select(err => new LocalizedString(err.Code, err.Description)).ToArray()); return await GetCurrentUser(cancellationToken); } @@ -42,11 +44,13 @@ public async Task Update(EditUserDto userDto, CancellationToken cancell [HttpDelete] public async Task Delete(CancellationToken cancellationToken) { - var userId = UserInformationProvider.GetUserId(); + var userId = User.GetUserId(); var user = await _userManager.Users.FirstOrDefaultAsync(user => user.Id == userId, cancellationToken) ?? throw new ResourceNotFoundException(); - await _userManager.DeleteAsync(user); + var result = await _userManager.DeleteAsync(user); + if (!result.Succeeded) + throw new ResourceValidationException(result.Errors.Select(err => new LocalizedString(err.Code, err.Description)).ToArray()); } } diff --git a/src/Server/Api/Controllers/Products/DashboardController.cs b/src/Server/Api/Controllers/Products/DashboardController.cs index 7388f7f..e771e04 100644 --- a/src/Server/Api/Controllers/Products/DashboardController.cs +++ b/src/Server/Api/Controllers/Products/DashboardController.cs @@ -53,7 +53,7 @@ public async Task> GetProductsPercentagePe if (productsTotalCount == 0) { - return new List(); + return []; } return await DbContext.Categories diff --git a/src/Server/Api/Controllers/Products/ProductController.cs b/src/Server/Api/Controllers/Products/ProductController.cs index 4864cb5..6609b4f 100644 --- a/src/Server/Api/Controllers/Products/ProductController.cs +++ b/src/Server/Api/Controllers/Products/ProductController.cs @@ -38,7 +38,7 @@ public async Task> GetProducts(ODataQueryOptions(await query.ToListAsync(cancellationToken), totalCount); + return new PagedResult(query.AsAsyncEnumerable(), totalCount); } [HttpPost] @@ -65,9 +65,7 @@ public async Task Update(ProductDto dto, CancellationToken cancellat await DbContext.SaveChangesAsync(cancellationToken); - productToUpdate.Patch(dto); - - return dto; + return productToUpdate.Map(); } [HttpDelete("{id:int}")] diff --git a/src/Server/Api/Data/AppDbContext.cs b/src/Server/Api/Data/AppDbContext.cs index a6bc95f..e8d7fa6 100644 --- a/src/Server/Api/Data/AppDbContext.cs +++ b/src/Server/Api/Data/AppDbContext.cs @@ -1,16 +1,15 @@ using Bit.TemplatePlayground.Server.Api.Models.Identity; using Bit.TemplatePlayground.Server.Api.Models.Categories; using Bit.TemplatePlayground.Server.Api.Models.Products; +using Microsoft.AspNetCore.DataProtection.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace Bit.TemplatePlayground.Server.Api.Data; -public class AppDbContext : IdentityDbContext +public class AppDbContext(DbContextOptions options) + : IdentityDbContext(options), IDataProtectionKeyContext { - public AppDbContext(DbContextOptions options) - : base(options) - { - } + public DbSet DataProtectionKeys { get; set; } public DbSet Categories { get; set; } public DbSet Products { get; set; } diff --git a/src/Server/Api/Data/Configurations/Identity/UserConfiguration.cs b/src/Server/Api/Data/Configurations/Identity/UserConfiguration.cs index 2e25df2..c7e110b 100644 --- a/src/Server/Api/Data/Configurations/Identity/UserConfiguration.cs +++ b/src/Server/Api/Data/Configurations/Identity/UserConfiguration.cs @@ -12,6 +12,7 @@ public void Configure(EntityTypeBuilder builder) { Id = 1, EmailConfirmed = true, + LockoutEnabled = true, Gender = Gender.Other, BirthDate = new DateTime(2023, 1, 1), FullName = "Bit.TemplatePlayground test account", diff --git a/src/Server/Api/Data/Configurations/Product/ProductConfiguration.cs b/src/Server/Api/Data/Configurations/Product/ProductConfiguration.cs index a2c197c..0405fba 100644 --- a/src/Server/Api/Data/Configurations/Product/ProductConfiguration.cs +++ b/src/Server/Api/Data/Configurations/Product/ProductConfiguration.cs @@ -6,7 +6,7 @@ public class ProductConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { - DateTime baseDate = DateTime.Parse("2023-09-20"); + DateTime baseDate = DateTime.Parse("2023-11-22"); builder.HasData( new Product() { Id = 1, Name = "Mustang", Price = 27155, Description = "The Ford Mustang is ranked #1 in Sports Cars", CreatedOn = baseDate.AddDays(-10), CategoryId = 1 }, diff --git a/src/Server/Api/Migrations/20230920195959_InitialMigration.Designer.cs b/src/Server/Api/Data/Migrations/20231122175600_InitialMigration.Designer.cs similarity index 89% rename from src/Server/Api/Migrations/20230920195959_InitialMigration.Designer.cs rename to src/Server/Api/Data/Migrations/20231122175600_InitialMigration.Designer.cs index 7f29f73..b1ff2f2 100644 --- a/src/Server/Api/Migrations/20230920195959_InitialMigration.Designer.cs +++ b/src/Server/Api/Data/Migrations/20231122175600_InitialMigration.Designer.cs @@ -7,17 +7,17 @@ #nullable disable -namespace Bit.TemplatePlayground.Server.Api.Migrations +namespace Bit.TemplatePlayground.Server.Api.Data.Migrations { [DbContext(typeof(AppDbContext))] - [Migration("20230920195959_InitialMigration")] + [Migration("20231122175600_InitialMigration")] partial class InitialMigration { /// protected override void BuildTargetModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "7.0.11"); + modelBuilder.HasAnnotation("ProductVersion", "8.0.0"); modelBuilder.Entity("Bit.TemplatePlayground.Server.Api.Models.Categories.Category", b => { @@ -185,17 +185,17 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) Id = 1, AccessFailedCount = 0, BirthDate = 1306790461440000060L, - ConcurrencyStamp = "2d516fa0-5d1d-4c2f-8fdf-a12901a81107", + ConcurrencyStamp = "ae209e4e-1401-4e10-b8c7-cdd62871f181", Email = "test@bitplatform.dev", EmailConfirmed = true, FullName = "Bit.TemplatePlayground test account", Gender = 2, - LockoutEnabled = false, + LockoutEnabled = true, NormalizedEmail = "TEST@BITPLATFORM.DEV", NormalizedUserName = "TEST@BITPLATFORM.DEV", - PasswordHash = "AQAAAAIAAYagAAAAEGOD8iNXQ7y87GUBkb86s9FlPq1zElgs0xidaW2/VIXzA5t+AUJ7UAYB24tHqbhzvw==", + PasswordHash = "AQAAAAIAAYagAAAAEAfqQ2WWaMftx62DMUSGQy52CRg3R5ZjjhpwsEYW8HxWt7SfzSWbA1m/C510G828DQ==", PhoneNumberConfirmed = false, - SecurityStamp = "8eecc946-5dd4-4086-93bc-bd804b4a7dc0", + SecurityStamp = "9cd0a3f1-7be9-42d4-bed2-6397a91b948d", TwoFactorEnabled = false, UserName = "test@bitplatform.dev" }); @@ -236,7 +236,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) { Id = 1, CategoryId = 1, - CreatedOn = 1307236368384000120L, + CreatedOn = 1307347845120000060L, Description = "The Ford Mustang is ranked #1 in Sports Cars", Name = "Mustang", Price = 27155m @@ -245,7 +245,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) { Id = 2, CategoryId = 1, - CreatedOn = 1307227521024000120L, + CreatedOn = 1307338997760000060L, Description = "The Ford GT is a mid-engine two-seater sports car manufactured and marketed by American automobile manufacturer", Name = "GT", Price = 500000m @@ -254,7 +254,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) { Id = 3, CategoryId = 1, - CreatedOn = 1307209826304000120L, + CreatedOn = 1307321303040000120L, Description = "Ford Ranger is a nameplate that has been used on multiple model lines of pickup trucks sold by Ford worldwide.", Name = "Ranger", Price = 25000m @@ -263,7 +263,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) { Id = 4, CategoryId = 1, - CreatedOn = 1307200978944000120L, + CreatedOn = 1307312455680000120L, Description = "Raptor is a SCORE off-road trophy truck living in a asphalt world", Name = "Raptor", Price = 53205m @@ -272,7 +272,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) { Id = 5, CategoryId = 1, - CreatedOn = 1307192131584000120L, + CreatedOn = 1307303608320000120L, Description = "The Ford Maverick is a compact pickup truck produced by Ford Motor Company.", Name = "Maverick", Price = 22470m @@ -281,7 +281,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) { Id = 6, CategoryId = 2, - CreatedOn = 1307236368384000120L, + CreatedOn = 1307347845120000060L, Description = "A powerful convertible sports car", Name = "Roadster", Price = 42800m @@ -290,7 +290,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) { Id = 7, CategoryId = 2, - CreatedOn = 1307227521024000120L, + CreatedOn = 1307338997760000060L, Description = "A perfectly adequate family sedan with sharp looks", Name = "Altima", Price = 24550m @@ -299,7 +299,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) { Id = 8, CategoryId = 2, - CreatedOn = 1307209826304000120L, + CreatedOn = 1307321303040000120L, Description = "Legendary supercar with AWD, 4 seats, a powerful V6 engine and the latest tech", Name = "GT-R", Price = 113540m @@ -308,7 +308,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) { Id = 9, CategoryId = 2, - CreatedOn = 1307192131584000120L, + CreatedOn = 1307303608320000120L, Description = "A new smart SUV", Name = "Juke", Price = 28100m @@ -317,7 +317,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) { Id = 10, CategoryId = 3, - CreatedOn = 1307236368384000120L, + CreatedOn = 1307347845120000060L, Description = "", Name = "H247", Price = 54950m @@ -326,7 +326,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) { Id = 11, CategoryId = 3, - CreatedOn = 1307227521024000120L, + CreatedOn = 1307338997760000060L, Description = "", Name = "V297", Price = 103360m @@ -335,7 +335,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) { Id = 12, CategoryId = 3, - CreatedOn = 1307192131584000120L, + CreatedOn = 1307303608320000120L, Description = "", Name = "R50", Price = 2000000m @@ -344,7 +344,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) { Id = 13, CategoryId = 4, - CreatedOn = 1307236368384000120L, + CreatedOn = 1307347845120000060L, Description = "", Name = "M550i", Price = 77790m @@ -353,7 +353,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) { Id = 14, CategoryId = 4, - CreatedOn = 1307227521024000120L, + CreatedOn = 1307338997760000060L, Description = "", Name = "540i", Price = 60945m @@ -362,7 +362,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) { Id = 15, CategoryId = 4, - CreatedOn = 1307218673664000120L, + CreatedOn = 1307330150400000060L, Description = "", Name = "530e", Price = 56545m @@ -371,7 +371,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) { Id = 16, CategoryId = 4, - CreatedOn = 1307209826304000120L, + CreatedOn = 1307321303040000120L, Description = "", Name = "530i", Price = 55195m @@ -380,7 +380,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) { Id = 17, CategoryId = 4, - CreatedOn = 1307200978944000120L, + CreatedOn = 1307312455680000120L, Description = "", Name = "M850i", Price = 100045m @@ -389,7 +389,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) { Id = 18, CategoryId = 4, - CreatedOn = 1307192131584000120L, + CreatedOn = 1307303608320000120L, Description = "", Name = "X7", Price = 77980m @@ -398,7 +398,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) { Id = 19, CategoryId = 4, - CreatedOn = 1307183284224000120L, + CreatedOn = 1307294760960000120L, Description = "", Name = "IX", Price = 87000m @@ -407,7 +407,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) { Id = 20, CategoryId = 5, - CreatedOn = 1307236368384000120L, + CreatedOn = 1307347845120000060L, Description = "rapid acceleration and dynamic handling", Name = "Model 3", Price = 61990m @@ -416,7 +416,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) { Id = 21, CategoryId = 5, - CreatedOn = 1307227521024000120L, + CreatedOn = 1307338997760000060L, Description = "finishes near the top of our luxury electric car rankings.", Name = "Model S", Price = 135000m @@ -425,7 +425,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) { Id = 22, CategoryId = 5, - CreatedOn = 1307218673664000120L, + CreatedOn = 1307330150400000060L, Description = "Heart-pumping acceleration, long drive range", Name = "Model X", Price = 138890m @@ -434,13 +434,30 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) { Id = 23, CategoryId = 5, - CreatedOn = 1307192131584000120L, + CreatedOn = 1307303608320000120L, Description = "extensive driving range, lots of standard safety features", Name = "Model Y", Price = 67790m }); }); + modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("FriendlyName") + .HasColumnType("TEXT"); + + b.Property("Xml") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("DataProtectionKeys"); + }); + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => { b.Property("Id") diff --git a/src/Server/Api/Migrations/20230920195959_InitialMigration.cs b/src/Server/Api/Data/Migrations/20231122175600_InitialMigration.cs similarity index 84% rename from src/Server/Api/Migrations/20230920195959_InitialMigration.cs rename to src/Server/Api/Data/Migrations/20231122175600_InitialMigration.cs index 92b4f59..130db33 100644 --- a/src/Server/Api/Migrations/20230920195959_InitialMigration.cs +++ b/src/Server/Api/Data/Migrations/20231122175600_InitialMigration.cs @@ -4,7 +4,7 @@ #pragma warning disable CA1814 // Prefer jagged arrays over multidimensional -namespace Bit.TemplatePlayground.Server.Api.Migrations +namespace Bit.TemplatePlayground.Server.Api.Data.Migrations { /// public partial class InitialMigration : Migration @@ -26,6 +26,20 @@ protected override void Up(MigrationBuilder migrationBuilder) table.PrimaryKey("PK_Categories", x => x.Id); }); + migrationBuilder.CreateTable( + name: "DataProtectionKeys", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + FriendlyName = table.Column(type: "TEXT", nullable: true), + Xml = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_DataProtectionKeys", x => x.Id); + }); + migrationBuilder.CreateTable( name: "Roles", columns: table => new @@ -217,36 +231,36 @@ protected override void Up(MigrationBuilder migrationBuilder) migrationBuilder.InsertData( table: "Users", columns: new[] { "Id", "AccessFailedCount", "BirthDate", "ConcurrencyStamp", "ConfirmationEmailRequestedOn", "Email", "EmailConfirmed", "FullName", "Gender", "LockoutEnabled", "LockoutEnd", "NormalizedEmail", "NormalizedUserName", "PasswordHash", "PhoneNumber", "PhoneNumberConfirmed", "ProfileImageName", "ResetPasswordEmailRequestedOn", "SecurityStamp", "TwoFactorEnabled", "UserName" }, - values: new object[] { 1, 0, 1306790461440000060L, "2d516fa0-5d1d-4c2f-8fdf-a12901a81107", null, "test@bitplatform.dev", true, "Bit.TemplatePlayground test account", 2, false, null, "TEST@BITPLATFORM.DEV", "TEST@BITPLATFORM.DEV", "AQAAAAIAAYagAAAAEGOD8iNXQ7y87GUBkb86s9FlPq1zElgs0xidaW2/VIXzA5t+AUJ7UAYB24tHqbhzvw==", null, false, null, null, "8eecc946-5dd4-4086-93bc-bd804b4a7dc0", false, "test@bitplatform.dev" }); + values: new object[] { 1, 0, 1306790461440000060L, "ae209e4e-1401-4e10-b8c7-cdd62871f181", null, "test@bitplatform.dev", true, "Bit.TemplatePlayground test account", 2, true, null, "TEST@BITPLATFORM.DEV", "TEST@BITPLATFORM.DEV", "AQAAAAIAAYagAAAAEAfqQ2WWaMftx62DMUSGQy52CRg3R5ZjjhpwsEYW8HxWt7SfzSWbA1m/C510G828DQ==", null, false, null, null, "9cd0a3f1-7be9-42d4-bed2-6397a91b948d", false, "test@bitplatform.dev" }); migrationBuilder.InsertData( table: "Products", columns: new[] { "Id", "CategoryId", "CreatedOn", "Description", "Name", "Price" }, values: new object[,] { - { 1, 1, 1307236368384000120L, "The Ford Mustang is ranked #1 in Sports Cars", "Mustang", 27155m }, - { 2, 1, 1307227521024000120L, "The Ford GT is a mid-engine two-seater sports car manufactured and marketed by American automobile manufacturer", "GT", 500000m }, - { 3, 1, 1307209826304000120L, "Ford Ranger is a nameplate that has been used on multiple model lines of pickup trucks sold by Ford worldwide.", "Ranger", 25000m }, - { 4, 1, 1307200978944000120L, "Raptor is a SCORE off-road trophy truck living in a asphalt world", "Raptor", 53205m }, - { 5, 1, 1307192131584000120L, "The Ford Maverick is a compact pickup truck produced by Ford Motor Company.", "Maverick", 22470m }, - { 6, 2, 1307236368384000120L, "A powerful convertible sports car", "Roadster", 42800m }, - { 7, 2, 1307227521024000120L, "A perfectly adequate family sedan with sharp looks", "Altima", 24550m }, - { 8, 2, 1307209826304000120L, "Legendary supercar with AWD, 4 seats, a powerful V6 engine and the latest tech", "GT-R", 113540m }, - { 9, 2, 1307192131584000120L, "A new smart SUV", "Juke", 28100m }, - { 10, 3, 1307236368384000120L, "", "H247", 54950m }, - { 11, 3, 1307227521024000120L, "", "V297", 103360m }, - { 12, 3, 1307192131584000120L, "", "R50", 2000000m }, - { 13, 4, 1307236368384000120L, "", "M550i", 77790m }, - { 14, 4, 1307227521024000120L, "", "540i", 60945m }, - { 15, 4, 1307218673664000120L, "", "530e", 56545m }, - { 16, 4, 1307209826304000120L, "", "530i", 55195m }, - { 17, 4, 1307200978944000120L, "", "M850i", 100045m }, - { 18, 4, 1307192131584000120L, "", "X7", 77980m }, - { 19, 4, 1307183284224000120L, "", "IX", 87000m }, - { 20, 5, 1307236368384000120L, "rapid acceleration and dynamic handling", "Model 3", 61990m }, - { 21, 5, 1307227521024000120L, "finishes near the top of our luxury electric car rankings.", "Model S", 135000m }, - { 22, 5, 1307218673664000120L, "Heart-pumping acceleration, long drive range", "Model X", 138890m }, - { 23, 5, 1307192131584000120L, "extensive driving range, lots of standard safety features", "Model Y", 67790m } + { 1, 1, 1307347845120000060L, "The Ford Mustang is ranked #1 in Sports Cars", "Mustang", 27155m }, + { 2, 1, 1307338997760000060L, "The Ford GT is a mid-engine two-seater sports car manufactured and marketed by American automobile manufacturer", "GT", 500000m }, + { 3, 1, 1307321303040000120L, "Ford Ranger is a nameplate that has been used on multiple model lines of pickup trucks sold by Ford worldwide.", "Ranger", 25000m }, + { 4, 1, 1307312455680000120L, "Raptor is a SCORE off-road trophy truck living in a asphalt world", "Raptor", 53205m }, + { 5, 1, 1307303608320000120L, "The Ford Maverick is a compact pickup truck produced by Ford Motor Company.", "Maverick", 22470m }, + { 6, 2, 1307347845120000060L, "A powerful convertible sports car", "Roadster", 42800m }, + { 7, 2, 1307338997760000060L, "A perfectly adequate family sedan with sharp looks", "Altima", 24550m }, + { 8, 2, 1307321303040000120L, "Legendary supercar with AWD, 4 seats, a powerful V6 engine and the latest tech", "GT-R", 113540m }, + { 9, 2, 1307303608320000120L, "A new smart SUV", "Juke", 28100m }, + { 10, 3, 1307347845120000060L, "", "H247", 54950m }, + { 11, 3, 1307338997760000060L, "", "V297", 103360m }, + { 12, 3, 1307303608320000120L, "", "R50", 2000000m }, + { 13, 4, 1307347845120000060L, "", "M550i", 77790m }, + { 14, 4, 1307338997760000060L, "", "540i", 60945m }, + { 15, 4, 1307330150400000060L, "", "530e", 56545m }, + { 16, 4, 1307321303040000120L, "", "530i", 55195m }, + { 17, 4, 1307312455680000120L, "", "M850i", 100045m }, + { 18, 4, 1307303608320000120L, "", "X7", 77980m }, + { 19, 4, 1307294760960000120L, "", "IX", 87000m }, + { 20, 5, 1307347845120000060L, "rapid acceleration and dynamic handling", "Model 3", 61990m }, + { 21, 5, 1307338997760000060L, "finishes near the top of our luxury electric car rankings.", "Model S", 135000m }, + { 22, 5, 1307330150400000060L, "Heart-pumping acceleration, long drive range", "Model X", 138890m }, + { 23, 5, 1307303608320000120L, "extensive driving range, lots of standard safety features", "Model Y", 67790m } }); migrationBuilder.CreateIndex( @@ -295,6 +309,9 @@ protected override void Up(MigrationBuilder migrationBuilder) /// protected override void Down(MigrationBuilder migrationBuilder) { + migrationBuilder.DropTable( + name: "DataProtectionKeys"); + migrationBuilder.DropTable( name: "Products"); diff --git a/src/Server/Api/Migrations/AppDbContextModelSnapshot.cs b/src/Server/Api/Data/Migrations/AppDbContextModelSnapshot.cs similarity index 89% rename from src/Server/Api/Migrations/AppDbContextModelSnapshot.cs rename to src/Server/Api/Data/Migrations/AppDbContextModelSnapshot.cs index 62899bb..4213e3c 100644 --- a/src/Server/Api/Migrations/AppDbContextModelSnapshot.cs +++ b/src/Server/Api/Data/Migrations/AppDbContextModelSnapshot.cs @@ -6,7 +6,7 @@ #nullable disable -namespace Bit.TemplatePlayground.Server.Api.Migrations +namespace Bit.TemplatePlayground.Server.Api.Data.Migrations { [DbContext(typeof(AppDbContext))] partial class AppDbContextModelSnapshot : ModelSnapshot @@ -14,7 +14,7 @@ partial class AppDbContextModelSnapshot : ModelSnapshot protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "7.0.11"); + modelBuilder.HasAnnotation("ProductVersion", "8.0.0"); modelBuilder.Entity("Bit.TemplatePlayground.Server.Api.Models.Categories.Category", b => { @@ -182,17 +182,17 @@ protected override void BuildModel(ModelBuilder modelBuilder) Id = 1, AccessFailedCount = 0, BirthDate = 1306790461440000060L, - ConcurrencyStamp = "2d516fa0-5d1d-4c2f-8fdf-a12901a81107", + ConcurrencyStamp = "ae209e4e-1401-4e10-b8c7-cdd62871f181", Email = "test@bitplatform.dev", EmailConfirmed = true, FullName = "Bit.TemplatePlayground test account", Gender = 2, - LockoutEnabled = false, + LockoutEnabled = true, NormalizedEmail = "TEST@BITPLATFORM.DEV", NormalizedUserName = "TEST@BITPLATFORM.DEV", - PasswordHash = "AQAAAAIAAYagAAAAEGOD8iNXQ7y87GUBkb86s9FlPq1zElgs0xidaW2/VIXzA5t+AUJ7UAYB24tHqbhzvw==", + PasswordHash = "AQAAAAIAAYagAAAAEAfqQ2WWaMftx62DMUSGQy52CRg3R5ZjjhpwsEYW8HxWt7SfzSWbA1m/C510G828DQ==", PhoneNumberConfirmed = false, - SecurityStamp = "8eecc946-5dd4-4086-93bc-bd804b4a7dc0", + SecurityStamp = "9cd0a3f1-7be9-42d4-bed2-6397a91b948d", TwoFactorEnabled = false, UserName = "test@bitplatform.dev" }); @@ -233,7 +233,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { Id = 1, CategoryId = 1, - CreatedOn = 1307236368384000120L, + CreatedOn = 1307347845120000060L, Description = "The Ford Mustang is ranked #1 in Sports Cars", Name = "Mustang", Price = 27155m @@ -242,7 +242,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { Id = 2, CategoryId = 1, - CreatedOn = 1307227521024000120L, + CreatedOn = 1307338997760000060L, Description = "The Ford GT is a mid-engine two-seater sports car manufactured and marketed by American automobile manufacturer", Name = "GT", Price = 500000m @@ -251,7 +251,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { Id = 3, CategoryId = 1, - CreatedOn = 1307209826304000120L, + CreatedOn = 1307321303040000120L, Description = "Ford Ranger is a nameplate that has been used on multiple model lines of pickup trucks sold by Ford worldwide.", Name = "Ranger", Price = 25000m @@ -260,7 +260,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { Id = 4, CategoryId = 1, - CreatedOn = 1307200978944000120L, + CreatedOn = 1307312455680000120L, Description = "Raptor is a SCORE off-road trophy truck living in a asphalt world", Name = "Raptor", Price = 53205m @@ -269,7 +269,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { Id = 5, CategoryId = 1, - CreatedOn = 1307192131584000120L, + CreatedOn = 1307303608320000120L, Description = "The Ford Maverick is a compact pickup truck produced by Ford Motor Company.", Name = "Maverick", Price = 22470m @@ -278,7 +278,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { Id = 6, CategoryId = 2, - CreatedOn = 1307236368384000120L, + CreatedOn = 1307347845120000060L, Description = "A powerful convertible sports car", Name = "Roadster", Price = 42800m @@ -287,7 +287,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { Id = 7, CategoryId = 2, - CreatedOn = 1307227521024000120L, + CreatedOn = 1307338997760000060L, Description = "A perfectly adequate family sedan with sharp looks", Name = "Altima", Price = 24550m @@ -296,7 +296,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { Id = 8, CategoryId = 2, - CreatedOn = 1307209826304000120L, + CreatedOn = 1307321303040000120L, Description = "Legendary supercar with AWD, 4 seats, a powerful V6 engine and the latest tech", Name = "GT-R", Price = 113540m @@ -305,7 +305,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { Id = 9, CategoryId = 2, - CreatedOn = 1307192131584000120L, + CreatedOn = 1307303608320000120L, Description = "A new smart SUV", Name = "Juke", Price = 28100m @@ -314,7 +314,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { Id = 10, CategoryId = 3, - CreatedOn = 1307236368384000120L, + CreatedOn = 1307347845120000060L, Description = "", Name = "H247", Price = 54950m @@ -323,7 +323,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { Id = 11, CategoryId = 3, - CreatedOn = 1307227521024000120L, + CreatedOn = 1307338997760000060L, Description = "", Name = "V297", Price = 103360m @@ -332,7 +332,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { Id = 12, CategoryId = 3, - CreatedOn = 1307192131584000120L, + CreatedOn = 1307303608320000120L, Description = "", Name = "R50", Price = 2000000m @@ -341,7 +341,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { Id = 13, CategoryId = 4, - CreatedOn = 1307236368384000120L, + CreatedOn = 1307347845120000060L, Description = "", Name = "M550i", Price = 77790m @@ -350,7 +350,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { Id = 14, CategoryId = 4, - CreatedOn = 1307227521024000120L, + CreatedOn = 1307338997760000060L, Description = "", Name = "540i", Price = 60945m @@ -359,7 +359,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { Id = 15, CategoryId = 4, - CreatedOn = 1307218673664000120L, + CreatedOn = 1307330150400000060L, Description = "", Name = "530e", Price = 56545m @@ -368,7 +368,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { Id = 16, CategoryId = 4, - CreatedOn = 1307209826304000120L, + CreatedOn = 1307321303040000120L, Description = "", Name = "530i", Price = 55195m @@ -377,7 +377,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { Id = 17, CategoryId = 4, - CreatedOn = 1307200978944000120L, + CreatedOn = 1307312455680000120L, Description = "", Name = "M850i", Price = 100045m @@ -386,7 +386,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { Id = 18, CategoryId = 4, - CreatedOn = 1307192131584000120L, + CreatedOn = 1307303608320000120L, Description = "", Name = "X7", Price = 77980m @@ -395,7 +395,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { Id = 19, CategoryId = 4, - CreatedOn = 1307183284224000120L, + CreatedOn = 1307294760960000120L, Description = "", Name = "IX", Price = 87000m @@ -404,7 +404,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { Id = 20, CategoryId = 5, - CreatedOn = 1307236368384000120L, + CreatedOn = 1307347845120000060L, Description = "rapid acceleration and dynamic handling", Name = "Model 3", Price = 61990m @@ -413,7 +413,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { Id = 21, CategoryId = 5, - CreatedOn = 1307227521024000120L, + CreatedOn = 1307338997760000060L, Description = "finishes near the top of our luxury electric car rankings.", Name = "Model S", Price = 135000m @@ -422,7 +422,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { Id = 22, CategoryId = 5, - CreatedOn = 1307218673664000120L, + CreatedOn = 1307330150400000060L, Description = "Heart-pumping acceleration, long drive range", Name = "Model X", Price = 138890m @@ -431,13 +431,30 @@ protected override void BuildModel(ModelBuilder modelBuilder) { Id = 23, CategoryId = 5, - CreatedOn = 1307192131584000120L, + CreatedOn = 1307303608320000120L, Description = "extensive driving range, lots of standard safety features", Name = "Model Y", Price = 67790m }); }); + modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("FriendlyName") + .HasColumnType("TEXT"); + + b.Property("Xml") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("DataProtectionKeys"); + }); + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => { b.Property("Id") diff --git a/src/Server/Api/Extensions/IServiceCollectionExtensions.cs b/src/Server/Api/Extensions/IServiceCollectionExtensions.cs index 77688af..8879b40 100644 --- a/src/Server/Api/Extensions/IServiceCollectionExtensions.cs +++ b/src/Server/Api/Extensions/IServiceCollectionExtensions.cs @@ -1,23 +1,30 @@ using System.IdentityModel.Tokens.Jwt; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; +using Bit.TemplatePlayground.Server.Api; +using Bit.TemplatePlayground.Server.Api.Models.Identity; +using Bit.TemplatePlayground.Server.Api.Services; using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.DataProtection; using Microsoft.IdentityModel.Tokens; using Microsoft.OpenApi.Models; using Swashbuckle.AspNetCore.SwaggerGen; -using Bit.TemplatePlayground.Server.Api; -using Bit.TemplatePlayground.Server.Api.Models.Identity; -using Bit.TemplatePlayground.Server.Api.Services; namespace Microsoft.Extensions.DependencyInjection; public static class IServiceCollectionExtensions { - public static IServiceCollection AddIdentity(this IServiceCollection services, IConfiguration configuration) + public static void AddIdentity(this IServiceCollection services, IConfiguration configuration) { var appSettings = configuration.GetSection(nameof(AppSettings)).Get()!; var settings = appSettings.IdentitySettings; + var certificatePath = Path.Combine(Directory.GetCurrentDirectory(), "IdentityCertificate.pfx"); + + services.AddDataProtection() + .PersistKeysToDbContext() + .ProtectKeysWithCertificate(new X509Certificate2(certificatePath, appSettings.IdentitySettings.IdentityCertificatePassword, OperatingSystem.IsWindows() ? X509KeyStorageFlags.EphemeralKeySet : X509KeyStorageFlags.DefaultKeySet)); + services.AddIdentity(options => { options.User.RequireUniqueEmail = settings.RequireUniqueEmail; @@ -27,33 +34,26 @@ public static IServiceCollection AddIdentity(this IServiceCollection services, I options.Password.RequireUppercase = settings.PasswordRequireUppercase; options.Password.RequireNonAlphanumeric = settings.PasswordRequireNonAlphanumeric; options.Password.RequiredLength = settings.PasswordRequiredLength; - }).AddEntityFrameworkStores().AddDefaultTokenProviders(); - - return services; - } - - public static IServiceCollection AddJwt(this IServiceCollection services, IConfiguration configuration) - { - // https://github.com/dotnet/aspnetcore/issues/4660 - JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); - JwtSecurityTokenHandler.DefaultMapInboundClaims = false; - JwtSecurityTokenHandler.DefaultOutboundClaimTypeMap.Clear(); - - var appSettings = configuration.GetSection(nameof(AppSettings)).Get(); - var settings = appSettings.JwtSettings; - - services.AddScoped(); + }) + .AddEntityFrameworkStores() + .AddDefaultTokenProviders() + .AddErrorDescriber() + .AddApiEndpoints(); services.AddAuthentication(options => { - options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; - options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; - options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; - }).AddJwtBearer(async options => + options.DefaultAuthenticateScheme = IdentityConstants.BearerScheme; + options.DefaultChallengeScheme = IdentityConstants.BearerScheme; + options.DefaultScheme = IdentityConstants.BearerScheme; + }) + .AddBearerToken(IdentityConstants.BearerScheme, options => { + options.BearerTokenExpiration = settings.BearerTokenExpiration; + options.RefreshTokenExpiration = settings.RefreshTokenExpiration; + var certificatePath = Path.Combine(Directory.GetCurrentDirectory(), "IdentityCertificate.pfx"); RSA? rsaPrivateKey; - using (X509Certificate2 signingCert = new X509Certificate2(certificatePath, appSettings.JwtSettings.IdentityCertificatePassword, OperatingSystem.IsWindows() ? X509KeyStorageFlags.EphemeralKeySet : X509KeyStorageFlags.DefaultKeySet)) + using (X509Certificate2 signingCert = new X509Certificate2(certificatePath, appSettings.IdentitySettings.IdentityCertificatePassword, OperatingSystem.IsWindows() ? X509KeyStorageFlags.EphemeralKeySet : X509KeyStorageFlags.DefaultKeySet)) { rsaPrivateKey = signingCert.GetRSAPrivateKey(); } @@ -74,37 +74,26 @@ public static IServiceCollection AddJwt(this IServiceCollection services, IConfi ValidateIssuer = true, ValidIssuer = settings.Issuer, + + AuthenticationType = IdentityConstants.BearerScheme }; - options.Events = new JwtBearerEvents + options.BearerTokenProtector = new AppSecureJwtDataFormat(appSettings, validationParameters); + + options.Events = new() { - OnMessageReceived = context => + OnMessageReceived = async context => { // The server accepts the access_token from either the authorization header, the cookie, or the request URL query string - - var access_token = context.Request.Cookies["access_token"]; - - if (string.IsNullOrEmpty(access_token)) - { - access_token = context.Request.Query["access_token"]; - } - - context.Token = access_token; - - return Task.CompletedTask; + context.Token ??= context.Request.Cookies["access_token"] ?? context.Request.Query["access_token"]; } }; - - options.SaveToken = true; - options.TokenValidationParameters = validationParameters; }); services.AddAuthorization(); - - return services; } - public static IServiceCollection AddSwaggerGen(this IServiceCollection services) + public static void AddSwaggerGen(this IServiceCollection services) { services.AddSwaggerGen(options => { @@ -113,33 +102,32 @@ public static IServiceCollection AddSwaggerGen(this IServiceCollection services) options.OperationFilter(); - options.AddSecurityDefinition("bearerAuth", new OpenApiSecurityScheme + options.AddSecurityDefinition("bearerAuth", new() { Name = "Authorization", - Type = SecuritySchemeType.Http, - Scheme = "bearer", - BearerFormat = "JWT", + Description = "Enter the Bearer Authorization string as following: `Bearer Generated-Bearer-Token`", In = ParameterLocation.Header, - Description = "JWT Authorization header using the Bearer scheme." + Type = SecuritySchemeType.ApiKey, + Scheme = "Bearer" }); - options.AddSecurityRequirement(new OpenApiSecurityRequirement + options.AddSecurityRequirement(new() { { - new OpenApiSecurityScheme + new() { + Name = "Bearer", + In = ParameterLocation.Header, Reference = new OpenApiReference { - Type = ReferenceType.SecurityScheme, - Id = "bearerAuth" + Id = "Bearer", + Type = ReferenceType.SecurityScheme } }, - Array.Empty() + [] } }); }); - - return services; } public static IServiceCollection AddHealthChecks(this IServiceCollection services, IWebHostEnvironment env, IConfiguration configuration) @@ -153,7 +141,7 @@ public static IServiceCollection AddHealthChecks(this IServiceCollection service services.AddHealthChecksUI(setupSettings: setup => { - setup.AddHealthCheckEndpoint("Bit.TemplatePlaygroundHealthChecks", env.IsDevelopment() ? "https://localhost:5031/healthz" : "/healthz"); + setup.AddHealthCheckEndpoint("BPHealthChecks", env.IsDevelopment() ? "https://localhost:5031/healthz" : "/healthz"); }).AddInMemoryStorage(); var healthChecksBuilder = services.AddHealthChecks() diff --git a/src/Server/Api/Extensions/ODataOperationFilter.cs b/src/Server/Api/Extensions/ODataOperationFilter.cs index bc15442..d13ddd5 100644 --- a/src/Server/Api/Extensions/ODataOperationFilter.cs +++ b/src/Server/Api/Extensions/ODataOperationFilter.cs @@ -12,7 +12,10 @@ public void Apply(OpenApiOperation operation, OperationFilterContext context) { if (operation.Parameters == null) operation.Parameters = new List(); - var descriptor = (ControllerActionDescriptor)context.ApiDescription.ActionDescriptor; + var descriptor = context.ApiDescription.ActionDescriptor as ControllerActionDescriptor; + + if (descriptor is null) + return; var odataQueryOptionsParameter = descriptor!.Parameters.SingleOrDefault(p => typeof(ODataQueryOptions).IsAssignableFrom(p.ParameterType)); diff --git a/src/Server/Api/Program.cs b/src/Server/Api/Program.cs index 2cb10fd..241a915 100644 --- a/src/Server/Api/Program.cs +++ b/src/Server/Api/Program.cs @@ -1,5 +1,9 @@ var builder = WebApplication.CreateBuilder(args); +#if BlazorWebAssembly +builder.Configuration.AddClientConfigurations(); +#endif + #if DEBUG // The following line (using the * in the URL), allows the emulators and mobile devices to access the app using the host IP address. if (OperatingSystem.IsWindows()) diff --git a/src/Server/Api/Properties/launchSettings.json b/src/Server/Api/Properties/launchSettings.json index 512c850..0538774 100644 --- a/src/Server/Api/Properties/launchSettings.json +++ b/src/Server/Api/Properties/launchSettings.json @@ -4,7 +4,7 @@ "commandName": "Project", "dotnetRunMessages": true, "launchBrowser": true, - "applicationUrl": "https://localhost:5031;http://localhost:5030", + "applicationUrl": "http://localhost:5030;https://localhost:5031", "launchUrl": "swagger", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" @@ -16,7 +16,7 @@ "dotnetRunMessages": true, "launchBrowser": true, "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", - "applicationUrl": "https://localhost:5031;http://localhost:5030", + "applicationUrl": "http://localhost:5030;https://localhost:5031", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } diff --git a/src/Server/Api/Resources/EmailConfirmationTemplate.razor b/src/Server/Api/Resources/EmailConfirmationTemplate.razor index 2636a84..cdb03ae 100644 --- a/src/Server/Api/Resources/EmailConfirmationTemplate.razor +++ b/src/Server/Api/Resources/EmailConfirmationTemplate.razor @@ -20,7 +20,7 @@ - diff --git a/src/Server/Api/Resources/ResetPasswordTemplate.razor b/src/Server/Api/Resources/ResetPasswordTemplate.razor index cc675df..b1177d6 100644 --- a/src/Server/Api/Resources/ResetPasswordTemplate.razor +++ b/src/Server/Api/Resources/ResetPasswordTemplate.razor @@ -16,11 +16,11 @@ @EmailLocalizer[nameof(EmailStrings.ResetPasswordEmailSubject)] -
+
+ @EmailLocalizer[nameof(EmailStrings.WelcomeToApp)]
- diff --git a/src/Server/Api/Services/ApiExceptionHandler.cs b/src/Server/Api/Services/ApiExceptionHandler.cs index 08edf10..029f0d8 100644 --- a/src/Server/Api/Services/ApiExceptionHandler.cs +++ b/src/Server/Api/Services/ApiExceptionHandler.cs @@ -1,6 +1,5 @@ using System.Net; using System.Reflection; -using k8s.KubeConfigModels; using Microsoft.AspNetCore.Diagnostics; namespace Bit.TemplatePlayground.Server.Api.Services; @@ -8,6 +7,7 @@ namespace Bit.TemplatePlayground.Server.Api.Services; public partial class ApiExceptionHandler : IExceptionHandler { [AutoInject] private IWebHostEnvironment _webHostEnvironment = default!; + [AutoInject] private IStringLocalizer _localizer = default!; public async ValueTask TryHandleAsync(HttpContext httpContext, Exception e, CancellationToken cancellationToken) { @@ -15,18 +15,17 @@ public async ValueTask TryHandleAsync(HttpContext httpContext, Exception e httpContext.Response.Headers.Append("Request-ID", httpContext.TraceIdentifier); var exception = UnWrapException(e); - var localizer = httpContext.RequestServices.GetRequiredService>(); var knownException = exception as KnownException; // The details of all of the exceptions are returned only in dev mode. in any other modes like production, only the details of the known exceptions are returned. var key = knownException?.Key ?? nameof(UnknownException); - var message = knownException?.Message ?? (_webHostEnvironment.IsDevelopment() ? exception.Message : localizer[nameof(UnknownException)]); + var message = knownException?.Message ?? (_webHostEnvironment.IsDevelopment() ? exception.Message : _localizer[nameof(UnknownException)]); var statusCode = (int)(exception is RestException restExp ? restExp.StatusCode : HttpStatusCode.InternalServerError); if (exception is KnownException && message == key) { - message = localizer[message]; + message = _localizer[message]; } var restExceptionPayload = new RestErrorInfo diff --git a/src/Server/Api/Services/AppIdentityErrorDescriber.cs b/src/Server/Api/Services/AppIdentityErrorDescriber.cs new file mode 100644 index 0000000..4662c5a --- /dev/null +++ b/src/Server/Api/Services/AppIdentityErrorDescriber.cs @@ -0,0 +1,61 @@ +using System.Data; + +namespace Bit.TemplatePlayground.Server.Api.Services; + +public partial class AppIdentityErrorDescriber : IdentityErrorDescriber +{ + [AutoInject] IStringLocalizer _localizer = default!; + + IdentityError CreateIdentityError(string code, params object[] args) + { + return new() + { + Code = code, + Description = _localizer.GetString(code, args) + }; + } + + public override IdentityError ConcurrencyFailure() => CreateIdentityError(nameof(IdentityStrings.ConcurrencyFailure)); + + public override IdentityError DuplicateEmail(string email) => CreateIdentityError(nameof(IdentityStrings.DuplicateEmail), email); + + public override IdentityError DuplicateRoleName(string role) => CreateIdentityError(nameof(IdentityStrings.DuplicateRoleName), role); + + public override IdentityError DuplicateUserName(string userName) => CreateIdentityError(nameof(IdentityStrings.DuplicateUserName), userName); + + public override IdentityError InvalidEmail(string? email) => CreateIdentityError(nameof(IdentityStrings.InvalidEmail), email ?? string.Empty); + + public override IdentityError InvalidRoleName(string? role) => CreateIdentityError(nameof(IdentityStrings.InvalidRoleName), role ?? string.Empty); + + public override IdentityError InvalidToken() => CreateIdentityError(nameof(IdentityStrings.InvalidToken)); + + public override IdentityError InvalidUserName(string? userName) => CreateIdentityError(nameof(IdentityStrings.InvalidUserName), userName ?? string.Empty); + + public override IdentityError LoginAlreadyAssociated() => CreateIdentityError(nameof(IdentityStrings.LoginAlreadyAssociated)); + + public override IdentityError PasswordMismatch() => CreateIdentityError(nameof(IdentityStrings.PasswordMismatch)); + + public override IdentityError PasswordRequiresDigit() => CreateIdentityError(nameof(IdentityStrings.PasswordRequiresDigit)); + + public override IdentityError PasswordRequiresLower() => CreateIdentityError(nameof(IdentityStrings.PasswordRequiresLower)); + + public override IdentityError PasswordRequiresNonAlphanumeric() => CreateIdentityError(nameof(IdentityStrings.PasswordRequiresNonAlphanumeric)); + + public override IdentityError PasswordRequiresUniqueChars(int uniqueChars) => CreateIdentityError(nameof(IdentityStrings.PasswordRequiresUniqueChars), uniqueChars); + + public override IdentityError PasswordRequiresUpper() => CreateIdentityError(nameof(IdentityStrings.PasswordRequiresUpper)); + + public override IdentityError PasswordTooShort(int length) => CreateIdentityError(nameof(IdentityStrings.PasswordTooShort)); + + public override IdentityError RecoveryCodeRedemptionFailed() => CreateIdentityError(nameof(IdentityStrings.RecoveryCodeRedemptionFailed)); + + public override IdentityError UserAlreadyHasPassword() => CreateIdentityError(nameof(IdentityStrings.UserAlreadyHasPassword)); + + public override IdentityError UserAlreadyInRole(string role) => CreateIdentityError(nameof(IdentityStrings.UserAlreadyInRole), role); + + public override IdentityError UserLockoutNotEnabled() => CreateIdentityError(nameof(IdentityStrings.UserLockoutNotEnabled)); + + public override IdentityError UserNotInRole(string role) => CreateIdentityError(nameof(IdentityStrings.UserNotInRole), role); + + public override IdentityError DefaultError() => CreateIdentityError(nameof(IdentityStrings.DefaultError)); +} diff --git a/src/Server/Api/Services/AppSecureJwtDataFormat.cs b/src/Server/Api/Services/AppSecureJwtDataFormat.cs new file mode 100644 index 0000000..18c63cc --- /dev/null +++ b/src/Server/Api/Services/AppSecureJwtDataFormat.cs @@ -0,0 +1,56 @@ +using System.IdentityModel.Tokens.Jwt; +using Microsoft.AspNetCore.Authentication; +using Microsoft.IdentityModel.Tokens; + +namespace Bit.TemplatePlayground.Server.Api.Services; + +/// +/// Stores bearer token in jwt format +/// +public class AppSecureJwtDataFormat(AppSettings appSettings, TokenValidationParameters validationParameters) + : ISecureDataFormat +{ + public AuthenticationTicket? Unprotect(string? protectedText) + => Unprotect(protectedText, null); + + public AuthenticationTicket? Unprotect(string? protectedText, string? purpose) + { + try + { + var handler = new JwtSecurityTokenHandler(); + ClaimsPrincipal? principal = handler.ValidateToken(protectedText, validationParameters, out var validToken); + var validJwt = (JwtSecurityToken)validToken; + var data = new AuthenticationTicket(principal, properties: new AuthenticationProperties() + { + ExpiresUtc = validJwt.ValidTo + }, IdentityConstants.BearerScheme); + return data; + } + catch (Exception exp) + { + throw new UnauthorizedException(nameof(AppStrings.UnauthorizedException), exp); + } + } + + public string Protect(AuthenticationTicket data) => Protect(data, null); + + public string Protect(AuthenticationTicket data, string? purpose) + { + var jwtSecurityTokenHandler = new JwtSecurityTokenHandler(); + + var securityToken = jwtSecurityTokenHandler + .CreateJwtSecurityToken(new SecurityTokenDescriptor + { + Issuer = appSettings.IdentitySettings.Issuer, + Audience = appSettings.IdentitySettings.Audience, + IssuedAt = DateTime.UtcNow, + Expires = data.Properties.ExpiresUtc!.Value.UtcDateTime, + SigningCredentials = new SigningCredentials(validationParameters.IssuerSigningKey, SecurityAlgorithms.RsaSha512), + Subject = new ClaimsIdentity(data.Principal.Claims), + }); + + var encodedJwt = jwtSecurityTokenHandler.WriteToken(securityToken); + + return encodedJwt; + } +} diff --git a/src/Server/Api/Services/Contracts/IJwtService.cs b/src/Server/Api/Services/Contracts/IJwtService.cs deleted file mode 100644 index 6c478f8..0000000 --- a/src/Server/Api/Services/Contracts/IJwtService.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Bit.TemplatePlayground.Server.Api.Models.Identity; -using Bit.TemplatePlayground.Shared.Dtos.Identity; - -namespace Bit.TemplatePlayground.Server.Api.Services.Contracts; - -public interface IJwtService -{ - Task GenerateToken(User user); -} diff --git a/src/Server/Api/Services/Contracts/IUserInformationProvider.cs b/src/Server/Api/Services/Contracts/IUserInformationProvider.cs deleted file mode 100644 index 3b24c49..0000000 --- a/src/Server/Api/Services/Contracts/IUserInformationProvider.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Bit.TemplatePlayground.Server.Api.Services.Contracts; - -public interface IUserInformationProvider -{ - bool IsAuthenticated(); - - IEnumerable GetClaims(); - - ClaimsIdentity GetClaimsIdentity(); - - int GetUserId(); - - string GetUserName(); -} diff --git a/src/Server/Api/Services/JwtService.cs b/src/Server/Api/Services/JwtService.cs deleted file mode 100644 index 9d5a130..0000000 --- a/src/Server/Api/Services/JwtService.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System.IdentityModel.Tokens.Jwt; -using System.Security.Cryptography; -using System.Security.Cryptography.X509Certificates; -using Microsoft.IdentityModel.Tokens; -using Bit.TemplatePlayground.Server.Api.Models.Identity; -using Bit.TemplatePlayground.Shared.Dtos.Identity; - -namespace Bit.TemplatePlayground.Server.Api.Services; - -public partial class JwtService : IJwtService -{ - [AutoInject] private readonly SignInManager _signInManager = default!; - - [AutoInject] private readonly AppSettings _appSettings = default!; - - public async Task GenerateToken(User user) - { - var certificatePath = Path.Combine(Directory.GetCurrentDirectory(), "IdentityCertificate.pfx"); - RSA? rsaPrivateKey; - using (X509Certificate2 signingCert = new X509Certificate2(certificatePath, _appSettings.JwtSettings.IdentityCertificatePassword, OperatingSystem.IsWindows() ? X509KeyStorageFlags.EphemeralKeySet : X509KeyStorageFlags.DefaultKeySet)) - { - rsaPrivateKey = signingCert.GetRSAPrivateKey(); - } - - var signingCredentials = new SigningCredentials(new RsaSecurityKey(rsaPrivateKey), SecurityAlgorithms.RsaSha512); - - var claims = (await _signInManager.ClaimsFactory.CreateAsync(user)).Claims; - - var jwtSecurityTokenHandler = new JwtSecurityTokenHandler(); - - var securityToken = jwtSecurityTokenHandler - .CreateJwtSecurityToken(new SecurityTokenDescriptor - { - Issuer = _appSettings.JwtSettings.Issuer, - Audience = _appSettings.JwtSettings.Audience, - IssuedAt = DateTime.UtcNow, - NotBefore = DateTime.UtcNow.AddMinutes(_appSettings.JwtSettings.NotBeforeMinutes), - Expires = DateTime.UtcNow.AddMinutes(_appSettings.JwtSettings.ExpirationMinutes), - SigningCredentials = signingCredentials, - Subject = new ClaimsIdentity(claims) - }); - - return new SignInResponseDto - { - AccessToken = jwtSecurityTokenHandler.WriteToken(securityToken), - ExpiresIn = (long)TimeSpan.FromMinutes(_appSettings.JwtSettings.ExpirationMinutes).TotalSeconds - }; - } -} diff --git a/src/Server/Api/Services/UserInformationProvider.cs b/src/Server/Api/Services/UserInformationProvider.cs deleted file mode 100644 index 5445cdc..0000000 --- a/src/Server/Api/Services/UserInformationProvider.cs +++ /dev/null @@ -1,57 +0,0 @@ -namespace Bit.TemplatePlayground.Server.Api.Services; - -public partial class UserInformationProvider : IUserInformationProvider -{ - [AutoInject] - public IHttpContextAccessor _httpContextAccessor = default!; - - public IEnumerable GetClaims() - { - if (IsAuthenticated() is false) - { - throw new InvalidOperationException(); - } - - return GetClaimsIdentity().Claims; - } - - public ClaimsIdentity GetClaimsIdentity() - { - if (IsAuthenticated() is false) - { - throw new InvalidOperationException(); - } - - return (ClaimsIdentity)_httpContextAccessor.HttpContext!.User.Identity!; - } - - public int GetUserId() - { - if (IsAuthenticated() is false) - { - throw new InvalidOperationException(); - } - - return _httpContextAccessor.HttpContext!.User.GetUserId(); - } - - public string GetUserName() - { - if (IsAuthenticated() is false) - { - throw new InvalidOperationException(); - } - - return _httpContextAccessor.HttpContext!.User.GetUserName(); - } - - public bool IsAuthenticated() - { - if (_httpContextAccessor.HttpContext is null) - { - throw new InvalidOperationException(); - } - - return _httpContextAccessor.HttpContext.User?.Identity?.IsAuthenticated is true; - } -} diff --git a/src/Server/Api/Startup/Middlewares.cs b/src/Server/Api/Startup/Middlewares.cs index 7c35884..ba03458 100644 --- a/src/Server/Api/Startup/Middlewares.cs +++ b/src/Server/Api/Startup/Middlewares.cs @@ -15,10 +15,7 @@ public static void Use(WebApplication app, IHostEnvironment env, IConfiguration app.UseDeveloperExceptionPage(); #if BlazorWebAssembly - if (env.IsDevelopment()) - { - app.UseWebAssemblyDebugging(); - } + app.UseWebAssemblyDebugging(); #endif } @@ -45,10 +42,8 @@ public static void Use(WebApplication app, IHostEnvironment env, IConfiguration } }); - app.UseRouting(); - app.UseCors(options => options.WithOrigins("https://localhost:4031" /*BlazorServer*/, "http://localhost:8001" /*BlazorElectron*/, "https://0.0.0.0" /*BlazorHybrid*/, "app://0.0.0.0" /*BlazorHybrid*/) - .AllowAnyHeader().AllowAnyMethod().AllowCredentials()); + .AllowAnyHeader().AllowAnyMethod()); app.UseResponseCaching(); app.UseAuthentication(); diff --git a/src/Server/Api/Startup/Services.cs b/src/Server/Api/Startup/Services.cs index 23bbc37..5b9cfdc 100644 --- a/src/Server/Api/Startup/Services.cs +++ b/src/Server/Api/Startup/Services.cs @@ -7,9 +7,12 @@ using Microsoft.AspNetCore.OData; using Microsoft.AspNetCore.ResponseCompression; #if BlazorWebAssembly +using Bit.TemplatePlayground.Client.Core.Services.HttpMessageHandlers; using Bit.TemplatePlayground.Client.Web.Services; using Bit.TemplatePlayground.Client.Core.Services; using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.WebAssembly.Services; +using Microsoft.JSInterop; #endif namespace Bit.TemplatePlayground.Server.Api.Startup; @@ -24,7 +27,6 @@ public static void Add(IServiceCollection services, IWebHostEnvironment env, ICo services.AddSharedServices(); - services.AddScoped(); services.AddExceptionHandler(); #if BlazorWebAssembly @@ -32,25 +34,23 @@ public static void Add(IServiceCollection services, IWebHostEnvironment env, ICo services.AddClientSharedServices(); services.AddClientWebServices(); - // In the Pre-Rendering mode, the configured HttpClient will use the access_token provided by the cookie in the request, so the pre-rendered content would be fitting for the current user. - services.AddHttpClient("WebAssemblyPreRenderingHttpClient") - .ConfigurePrimaryHttpMessageHandler() - .ConfigureHttpClient((sp, httpClient) => + services.AddTransient(sp => + { + Uri.TryCreate(configuration.GetApiServerAddress(), UriKind.RelativeOrAbsolute, out var apiServerAddress); + + if (apiServerAddress!.IsAbsoluteUri is false) { - NavigationManager navManager = sp.GetRequiredService().HttpContext!.RequestServices.GetRequiredService(); - httpClient.BaseAddress = new Uri($"{navManager.BaseUri}api/"); - }); - services.AddScoped(); + apiServerAddress = new Uri($"{sp.GetRequiredService().HttpContext!.Request.GetBaseUrl()}{apiServerAddress}"); + } - services.AddScoped(sp => - { - IHttpClientFactory httpClientFactory = sp.GetRequiredService(); - return httpClientFactory.CreateClient("WebAssemblyPreRenderingHttpClient"); - // this is for pre rendering of blazor client/wasm - // for other usages of http client, for example calling 3rd party apis, either use services.AddHttpClient("NamedHttpClient") or services.AddHttpClient(); + return new HttpClient(sp.GetRequiredService()) + { + BaseAddress = apiServerAddress + }; }); + + services.AddTransient(); services.AddRazorPages(); - services.AddMvcCore(); #endif @@ -81,7 +81,7 @@ public static void Add(IServiceCollection services, IWebHostEnvironment env, ICo services.AddResponseCompression(opts => { opts.EnableForHttps = true; - opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(new[] { "application/octet-stream" }).ToArray(); + opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(["application/octet-stream"]).ToArray(); opts.Providers.Add(); opts.Providers.Add(); }) @@ -98,7 +98,7 @@ public static void Add(IServiceCollection services, IWebHostEnvironment env, ICo services.Configure(configuration.GetSection(nameof(AppSettings))); - services.AddScoped(sp => sp.GetRequiredService>().Value); + services.AddTransient(sp => sp.GetRequiredService>().Value); services.AddEndpointsApiExplorer(); @@ -106,11 +106,9 @@ public static void Add(IServiceCollection services, IWebHostEnvironment env, ICo services.AddIdentity(configuration); - services.AddJwt(configuration); - services.AddHealthChecks(env, configuration); - services.AddScoped(); + services.AddTransient(); var fluentEmailServiceBuilder = services.AddFluentEmail(appSettings.EmailSettings.DefaultFromEmail, appSettings.EmailSettings.DefaultFromName); diff --git a/src/Server/Api/appsettings.json b/src/Server/Api/appsettings.json index a86ceba..f92970e 100644 --- a/src/Server/Api/appsettings.json +++ b/src/Server/Api/appsettings.json @@ -10,27 +10,25 @@ } }, "AppSettings": { - "JwtSettings": { - "IdentityCertificatePassword": "P@ssw0rdP@ssw0rd", + "IdentitySettings": { "Issuer": "Bit.TemplatePlayground", "Audience": "Bit.TemplatePlayground", - "NotBeforeMinutes": "0", - "ExpirationMinutes": "1440" - }, - "IdentitySettings": { + "IdentityCertificatePassword": "P@ssw0rdP@ssw0rd", + "BearerTokenExpiration": "0.01:00:00", //Format: D.HH:mm:ss + "RefreshTokenExpiration": "14.00:00:00", //Format: D.HH:mm:ss "PasswordRequireDigit": "false", "PasswordRequiredLength": "6", "PasswordRequireNonAlphanumeric": "false", "PasswordRequireUppercase": "false", "PasswordRequireLowercase": "false", "RequireUniqueEmail": "true", - "ConfirmationEmailResendDelay": "0.00:02:00", //Format: D.HH:mm:nn - "ResetPasswordEmailResendDelay": "0.00:02:00" //Format: D.HH:mm:nn + "ConfirmationEmailResendDelay": "0.00:02:00", //Format: D.HH:mm:ss + "ResetPasswordEmailResendDelay": "0.00:02:00" //Format: D.HH:mm:ss }, "EmailSettings": { "Host": "LocalFolder", // Local folder means storing emails as .eml file in bin/Debug/net8.0/sent-emails folder (Recommended for testing purposes only) instead of sending them using smtp server. "Port": "25", - "DefaultFromEmail": "info@bit.templateplayground.com", + "DefaultFromEmail": "info@Bit.TemplatePlayground.com", "DefaultFromName": "Bit.TemplatePlayground", "UserName": null, "Password": null diff --git a/src/Server/Api/wwwroot/swagger/swagger-utils.js b/src/Server/Api/wwwroot/swagger/swagger-utils.js index 358dfc0..37cbc39 100644 --- a/src/Server/Api/wwwroot/swagger/swagger-utils.js +++ b/src/Server/Api/wwwroot/swagger/swagger-utils.js @@ -104,6 +104,7 @@ const createLoginUI = function (swagger, rootDiv) { const userNameInput = document.createElement("input"); userNameInput.type = "text"; + userNameInput.placeholder = "test@bitplatform.dev"; userNameInput.style = "margin-left: 10px; margin-right: 10px;"; userNameLabel.appendChild(userNameInput); @@ -116,6 +117,7 @@ const createLoginUI = function (swagger, rootDiv) { passwordLabel.appendChild(passwordSpan); const passwordInput = document.createElement("input"); + passwordInput.placeholder = "123456"; passwordInput.type = "password"; passwordInput.style = "margin-left: 10px; margin-right: 10px;"; passwordLabel.appendChild(passwordInput); @@ -145,24 +147,24 @@ const createLoginUI = function (swagger, rootDiv) { } const login = async (swagger, userName, password) => { - const response = await fetch('/api/Auth/SignIn', { + const response = await fetch('/api/Identity/SignIn', { headers: { "Content-Type": "application/json; charset=utf-8" }, method: 'POST', body: JSON.stringify({ "userName": userName, "password": password }) }) if (response.ok) { const result = await response.json(); - const accessToken = result.accessToken; + const access_token = result.accessToken; accessTokenExpiresIn = result.expiresIn; - const authorizationObject = getAuthorizationRequestObject(accessToken); + const authorizationObject = getAuthorizationRequestObject(access_token); swagger.authActions.authorize(authorizationObject); } else { alert(await response.text()) } } -const getAuthorizationRequestObject = (accessToken) => { +const getAuthorizationRequestObject = (access_token) => { return { "bearerAuth": { "name": "Bearer", @@ -172,7 +174,7 @@ const getAuthorizationRequestObject = (accessToken) => { "name": "Authorization", "in": "header" }, - value: accessToken + value: access_token }, }; } diff --git a/src/Shared/Attributes/DtoResourceTypeAttribute.cs b/src/Shared/Attributes/DtoResourceTypeAttribute.cs index 5765f85..8372dd0 100644 --- a/src/Shared/Attributes/DtoResourceTypeAttribute.cs +++ b/src/Shared/Attributes/DtoResourceTypeAttribute.cs @@ -4,12 +4,7 @@ /// Gets or sets the resource type to use for error message and localizations lookups. /// [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] -public class DtoResourceTypeAttribute : Attribute +public class DtoResourceTypeAttribute(Type resourceType) : Attribute { - public Type ResourceType { get; } - - public DtoResourceTypeAttribute(Type resourceType) - { - ResourceType = resourceType ?? throw new ArgumentNullException(nameof(resourceType)); - } + public Type ResourceType { get; } = resourceType ?? throw new ArgumentNullException(nameof(resourceType)); } diff --git a/src/Shared/Bit.TemplatePlayground.Shared.csproj b/src/Shared/Bit.TemplatePlayground.Shared.csproj index 2f44624..c262b73 100644 --- a/src/Shared/Bit.TemplatePlayground.Shared.csproj +++ b/src/Shared/Bit.TemplatePlayground.Shared.csproj @@ -6,21 +6,21 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - + + + + + - compile; build; native; contentfiles; analyzers; buildtransitive + compile; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Shared/Dtos/AppJsonContext.cs b/src/Shared/Dtos/AppJsonContext.cs index 5387858..d310d4a 100644 --- a/src/Shared/Dtos/AppJsonContext.cs +++ b/src/Shared/Dtos/AppJsonContext.cs @@ -1,7 +1,7 @@ -using Bit.TemplatePlayground.Shared.Dtos.Identity; -using Bit.TemplatePlayground.Shared.Dtos.Categories; +using Bit.TemplatePlayground.Shared.Dtos.Categories; using Bit.TemplatePlayground.Shared.Dtos.Dashboard; using Bit.TemplatePlayground.Shared.Dtos.Products; +using Bit.TemplatePlayground.Shared.Dtos.Identity; namespace Bit.TemplatePlayground.Shared.Dtos; @@ -23,7 +23,8 @@ namespace Bit.TemplatePlayground.Shared.Dtos; [JsonSerializable(typeof(List))] [JsonSerializable(typeof(PagedResult))] [JsonSerializable(typeof(SignInRequestDto))] -[JsonSerializable(typeof(SignInResponseDto))] +[JsonSerializable(typeof(TokenResponseDto))] +[JsonSerializable(typeof(RefreshRequestDto))] [JsonSerializable(typeof(SignUpRequestDto))] [JsonSerializable(typeof(EditUserDto))] [JsonSerializable(typeof(RestErrorInfo))] diff --git a/src/Shared/Dtos/Identity/RefreshRequestDto.cs b/src/Shared/Dtos/Identity/RefreshRequestDto.cs new file mode 100644 index 0000000..59ee326 --- /dev/null +++ b/src/Shared/Dtos/Identity/RefreshRequestDto.cs @@ -0,0 +1,8 @@ +namespace Bit.TemplatePlayground.Shared.Dtos.Identity; + +[DtoResourceType(typeof(AppStrings))] +public class RefreshRequestDto +{ + [Required(ErrorMessage = nameof(AppStrings.RequiredAttribute_ValidationError))] + public string? RefreshToken { get; set; } +} \ No newline at end of file diff --git a/src/Shared/Dtos/Identity/SignInRequestDto.cs b/src/Shared/Dtos/Identity/SignInRequestDto.cs index e746269..c88b92c 100644 --- a/src/Shared/Dtos/Identity/SignInRequestDto.cs +++ b/src/Shared/Dtos/Identity/SignInRequestDto.cs @@ -4,7 +4,7 @@ namespace Bit.TemplatePlayground.Shared.Dtos.Identity; [DtoResourceType(typeof(AppStrings))] public class SignInRequestDto { - /// me@gmail.com + /// test@bitplatform.dev [Required(ErrorMessage = nameof(AppStrings.RequiredAttribute_ValidationError))] [EmailAddress(ErrorMessage = nameof(AppStrings.EmailAddressAttribute_ValidationError))] [Display(Name = nameof(AppStrings.Email))] @@ -14,4 +14,8 @@ public class SignInRequestDto [Required(ErrorMessage = nameof(AppStrings.RequiredAttribute_ValidationError))] [Display(Name = nameof(AppStrings.Password))] public string? Password { get; set; } + + [NotMapped] + [Display(Name = nameof(AppStrings.RememberMe))] + public bool RememberMe { get; set; } = true; } diff --git a/src/Shared/Dtos/Identity/SignUpRequestDto.cs b/src/Shared/Dtos/Identity/SignUpRequestDto.cs index acd4d92..357a1f6 100644 --- a/src/Shared/Dtos/Identity/SignUpRequestDto.cs +++ b/src/Shared/Dtos/Identity/SignUpRequestDto.cs @@ -16,4 +16,10 @@ public class SignUpRequestDto [MinLength(6, ErrorMessage = nameof(AppStrings.MinLengthAttribute_ValidationError))] [Display(Name = nameof(AppStrings.Password))] public string? Password { get; set; } + + /// true + [NotMapped] + [Range(typeof(bool), "true", "true", ErrorMessage = nameof(AppStrings.YouHaveToAcceptTerms))] + [Display(Name = nameof(AppStrings.TermsAccepted))] + public bool TermsAccepted { get; set; } } diff --git a/src/Shared/Dtos/Identity/SignInResponseDto.cs b/src/Shared/Dtos/Identity/TokenDto.cs similarity index 60% rename from src/Shared/Dtos/Identity/SignInResponseDto.cs rename to src/Shared/Dtos/Identity/TokenDto.cs index 9e9fe33..3795839 100644 --- a/src/Shared/Dtos/Identity/SignInResponseDto.cs +++ b/src/Shared/Dtos/Identity/TokenDto.cs @@ -2,9 +2,13 @@ namespace Bit.TemplatePlayground.Shared.Dtos.Identity; [DtoResourceType(typeof(AppStrings))] -public class SignInResponseDto +public class TokenResponseDto { + public string? TokenType { get; set; } + public string? AccessToken { get; set; } public long ExpiresIn { get; set; } -} + + public string? RefreshToken { get; set; } +} \ No newline at end of file diff --git a/src/Shared/Dtos/PagedResultDto.cs b/src/Shared/Dtos/PagedResultDto.cs index d2d6ae8..2d486ca 100644 --- a/src/Shared/Dtos/PagedResultDto.cs +++ b/src/Shared/Dtos/PagedResultDto.cs @@ -2,11 +2,11 @@ public class PagedResult { - public IList? Items { get; set; } + public IAsyncEnumerable? Items { get; set; } public long TotalCount { get; set; } - public PagedResult(IList items, long totalCount) + public PagedResult(IAsyncEnumerable items, long totalCount) { Items = items; TotalCount = totalCount; diff --git a/src/Shared/Exceptions/ErrorResourcePayload.cs b/src/Shared/Exceptions/ErrorResourcePayload.cs index 483842e..a184689 100644 --- a/src/Shared/Exceptions/ErrorResourcePayload.cs +++ b/src/Shared/Exceptions/ErrorResourcePayload.cs @@ -4,14 +4,14 @@ public class ErrorResourcePayload { public string? ResourceTypeName { get; set; } = "*"; - public List Details { get; set; } = new(); + public List Details { get; set; } = []; } public class PropertyErrorResourceCollection { public string? Name { get; set; } = "*"; - public List Errors { get; set; } = new(); + public List Errors { get; set; } = []; } public class ErrorResource diff --git a/src/Shared/Exceptions/ResourceValidationException.cs b/src/Shared/Exceptions/ResourceValidationException.cs index 0711c8f..20ff6f5 100644 --- a/src/Shared/Exceptions/ResourceValidationException.cs +++ b/src/Shared/Exceptions/ResourceValidationException.cs @@ -5,7 +5,7 @@ namespace Bit.TemplatePlayground.Shared.Exceptions; public class ResourceValidationException : RestException { public ResourceValidationException(params LocalizedString[] errorMessages) - : this(new[] { ("*", errorMessages) }) + : this([("*", errorMessages)]) { } diff --git a/src/Shared/Exceptions/ServerConnectionException.cs b/src/Shared/Exceptions/ServerConnectionException.cs new file mode 100644 index 0000000..003e558 --- /dev/null +++ b/src/Shared/Exceptions/ServerConnectionException.cs @@ -0,0 +1,18 @@ +namespace Bit.TemplatePlayground.Shared.Exceptions; +public class ServerConnectionException : UnknownException +{ + public ServerConnectionException() + : base(nameof(AppStrings.ServerConnectionException)) + { + } + + public ServerConnectionException(string message) + : base(message) + { + } + + public ServerConnectionException(string message, Exception innerException) + : base(message, innerException) + { + } +} diff --git a/src/Shared/Extensions/ClaimsPrincipalExtensions.cs b/src/Shared/Extensions/ClaimsPrincipalExtensions.cs index 9929bc0..e388403 100644 --- a/src/Shared/Extensions/ClaimsPrincipalExtensions.cs +++ b/src/Shared/Extensions/ClaimsPrincipalExtensions.cs @@ -4,11 +4,16 @@ public static class ClaimsPrincipalExtensions { public static int GetUserId(this ClaimsPrincipal claimsPrincipal) { - return int.Parse(claimsPrincipal.FindFirst(ClaimTypes.NameIdentifier)!.Value); + return int.Parse((claimsPrincipal.FindFirst(ClaimTypes.NameIdentifier) ?? claimsPrincipal.FindFirst("nameid"))!.Value); } public static string GetUserName(this ClaimsPrincipal claimsPrincipal) { - return claimsPrincipal.FindFirst(ClaimTypes.Name)!.Value; + return (claimsPrincipal.FindFirst(ClaimTypes.Name) ?? claimsPrincipal.FindFirst("unique_name"))!.Value; + } + + public static bool IsAuthenticated(this ClaimsPrincipal? claimsPrincipal) + { + return claimsPrincipal?.Identity?.IsAuthenticated is true; } } diff --git a/src/Shared/Extensions/IServiceCollectionExtensions.cs b/src/Shared/Extensions/IServiceCollectionExtensions.cs index 9fafe06..45d880a 100644 --- a/src/Shared/Extensions/IServiceCollectionExtensions.cs +++ b/src/Shared/Extensions/IServiceCollectionExtensions.cs @@ -6,9 +6,9 @@ public static class IServiceCollectionExtensions { public static IServiceCollection AddSharedServices(this IServiceCollection services) { - // Services being registered here can get injected everywhere (Api, Web, Android, iOS, Windows, and Mac) + // Services being registered here can get injected everywhere (Api, Web, Android, iOS, Windows, macOS and Linux) - services.AddSingleton(); + services.AddTransient(); services.AddAuthorizationCore(); diff --git a/src/Shared/Infra/CultureInfoManager.cs b/src/Shared/Infra/CultureInfoManager.cs index 4a639af..b776c3e 100644 --- a/src/Shared/Infra/CultureInfoManager.cs +++ b/src/Shared/Infra/CultureInfoManager.cs @@ -1,18 +1,15 @@ -using System.Reflection; - -namespace Bit.TemplatePlayground.Shared.Infra; - +namespace Bit.TemplatePlayground.Shared.Infra; public class CultureInfoManager { public static (string name, string code) DefaultCulture { get; } = ("English", "en-US"); - public static (string name, string code)[] SupportedCultures { get; } = new (string name, string code)[] - { + public static (string name, string code)[] SupportedCultures { get; } = + [ ("English US", "en-US"), ("English UK", "en-GB"), ("Française", "fr-FR"), // ("فارسی", "fa-IR"), // To add more languages, you've to provide resx files. You might also put some efforts to change your app flow direction based on CultureInfo.CurrentUICulture.TextInfo.IsRightToLeft - }; + ]; public static CultureInfo CreateCultureInfo(string cultureInfoId) { @@ -54,51 +51,10 @@ public static string GetCurrentCulture(string? preferredCultureCookie) /// public static CultureInfo CustomizeCultureInfoForFaCulture(CultureInfo cultureInfo) { - cultureInfo.DateTimeFormat.MonthNames = new[] - { - "فروردین", "اردیبهشت", "خرداد", "تیر", "مرداد", "شهریور", "مهر", "آبان", "آذر", "دی", "بهمن", "اسفند", "" - }; - - cultureInfo.DateTimeFormat.AbbreviatedMonthNames = new[] - { - "فرور", "ارد", "خرد", "تیر", "مرد", "شهر", "مهر", "آبا", "آذر", "دی", "بهم", "اسف", "" - }; - - cultureInfo.DateTimeFormat.MonthGenitiveNames = cultureInfo.DateTimeFormat.MonthNames; - cultureInfo.DateTimeFormat.AbbreviatedMonthGenitiveNames = cultureInfo.DateTimeFormat.AbbreviatedMonthNames; - cultureInfo.DateTimeFormat.DayNames = new[] - { - "یکشنبه", "دوشنبه", "ﺳﻪشنبه", "چهارشنبه", "پنجشنبه", "جمعه", "شنبه" - }; - - cultureInfo.DateTimeFormat.AbbreviatedDayNames = new[] - { - "ی", "د", "س", "چ", "پ", "ج", "ش" - }; - - cultureInfo.DateTimeFormat.ShortestDayNames = new[] - { - "ی", "د", "س", "چ", "پ", "ج", "ش" - }; - cultureInfo.DateTimeFormat.AMDesignator = "ق.ظ"; cultureInfo.DateTimeFormat.PMDesignator = "ب.ظ"; cultureInfo.DateTimeFormat.ShortDatePattern = "yyyy/MM/dd"; - cultureInfo.DateTimeFormat.FirstDayOfWeek = DayOfWeek.Saturday; - - var cultureData = _cultureDataField.GetValue(cultureInfo.TextInfo); - - _iReadingLayoutField.SetValue(cultureData, 1 /*rtl*/); // this affects cultureInfo.TextInfo.IsRightToLeft - - if (cultureInfo.DateTimeFormat.Calendar is not PersianCalendar) - { - cultureInfo.DateTimeFormat.Calendar = new PersianCalendar(); - } return cultureInfo; } - - private static readonly FieldInfo _cultureDataField = typeof(TextInfo).GetField("_cultureData", BindingFlags.NonPublic | BindingFlags.Instance)!; - - private static readonly FieldInfo _iReadingLayoutField = Type.GetType("System.Globalization.CultureData, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e")!.GetField("_iReadingLayout", BindingFlags.NonPublic | BindingFlags.Instance)!; } diff --git a/src/Shared/Mapper.cs b/src/Shared/Mapper.cs index b03e3de..b78c5fc 100644 --- a/src/Shared/Mapper.cs +++ b/src/Shared/Mapper.cs @@ -1,6 +1,4 @@ -using Bit.TemplatePlayground.Shared.Dtos.Categories; -using Bit.TemplatePlayground.Shared.Dtos.Identity; -using Bit.TemplatePlayground.Shared.Dtos.Products; +using Bit.TemplatePlayground.Shared.Dtos.Identity; using Riok.Mapperly.Abstractions; namespace Bit.TemplatePlayground.Shared; @@ -17,7 +15,5 @@ namespace Bit.TemplatePlayground.Shared; [Mapper(UseDeepCloning = true)] public static partial class Mapper { - public static partial void Patch(this CategoryDto source, CategoryDto destination); - public static partial void Patch(this ProductDto source, ProductDto destination); public static partial void Patch(this UserDto source, UserDto destination); } diff --git a/src/Shared/Resources/AppStrings.Designer.cs b/src/Shared/Resources/AppStrings.Designer.cs index 71b9d8c..324e1f1 100644 --- a/src/Shared/Resources/AppStrings.Designer.cs +++ b/src/Shared/Resources/AppStrings.Designer.cs @@ -97,16 +97,7 @@ public static string AddCategory { } /// - /// Looks up a localized string similar to Add/Edit category. - /// - public static string AddEditCategory { - get { - return ResourceManager.GetString("AddEditCategory", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to New product. + /// Looks up a localized string similar to Add product. /// public static string AddProduct { get { @@ -123,15 +114,6 @@ public static string All { } } - /// - /// Looks up a localized string similar to The {0} field does not equal any of the values specified in AllowedValuesAttribute.. - /// - public static string AllowedValuesAttribute_Invalid { - get { - return ResourceManager.GetString("AllowedValuesAttribute_Invalid", resourceCulture); - } - } - /// /// Looks up a localized string similar to Alphabetical. /// @@ -151,11 +133,11 @@ public static string AlreadyHaveAccountMessage { } /// - /// Looks up a localized string similar to Admin​Panel. + /// Looks up a localized string similar to Are you sure you want to delete {0}. /// - public static string AppTitle { + public static string AreYouSureWannaDelete { get { - return ResourceManager.GetString("AppTitle", resourceCulture); + return ResourceManager.GetString("AreYouSureWannaDelete", resourceCulture); } } @@ -177,33 +159,6 @@ public static string AreYouSureWannaDeleteProduct { } } - /// - /// Looks up a localized string similar to Are you sure you want to Sign out?. - /// - public static string AreYouSureYouWantToSignout { - get { - return ResourceManager.GetString("AreYouSureYouWantToSignout", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The associated metadata type for type '{0}' contains the following unknown properties or fields: {1}. Please make sure that the names of these members match the names of the properties on the main type.. - /// - public static string AssociatedMetadataTypeTypeDescriptor_MetadataTypeContainsUnknownProperties { - get { - return ResourceManager.GetString("AssociatedMetadataTypeTypeDescriptor_MetadataTypeContainsUnknownProperties", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The type '{0}' does not contain a public property named '{1}'.. - /// - public static string AttributeStore_Unknown_Property { - get { - return ResourceManager.GetString("AttributeStore_Unknown_Property", resourceCulture); - } - } - /// /// Looks up a localized string similar to Back. /// @@ -222,15 +177,6 @@ public static string BadRequestException { } } - /// - /// Looks up a localized string similar to The {0} field is not a valid Base64 encoding.. - /// - public static string Base64StringAttribute_Invalid { - get { - return ResourceManager.GetString("Base64StringAttribute_Invalid", resourceCulture); - } - } - /// /// Looks up a localized string similar to Birthdate. /// @@ -303,15 +249,6 @@ public static string CheckSpamMailMessage { } } - /// - /// Looks up a localized string similar to Close. - /// - public static string Close { - get { - return ResourceManager.GetString("Close", resourceCulture); - } - } - /// /// Looks up a localized string similar to Color. /// @@ -321,24 +258,6 @@ public static string Color { } } - /// - /// Looks up a localized string similar to The property {0}.{1} could not be found.. - /// - public static string Common_PropertyNotFound { - get { - return ResourceManager.GetString("Common_PropertyNotFound", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Could not find a property named {0}.. - /// - public static string CompareAttribute_UnknownProperty { - get { - return ResourceManager.GetString("CompareAttribute_UnknownProperty", resourceCulture); - } - } - /// /// Looks up a localized string similar to '{0}' and '{1}' do not match.. /// @@ -357,15 +276,6 @@ public static string Completed { } } - /// - /// Looks up a localized string similar to Optimistic concurrency failure, object has been modified.. - /// - public static string ConcurrencyFailure { - get { - return ResourceManager.GetString("ConcurrencyFailure", resourceCulture); - } - } - /// /// Looks up a localized string similar to We have sent a confirmation link to your email address. ///Please confirm your email by clicking on the link.. @@ -403,15 +313,6 @@ public static string ConflicException { } } - /// - /// Looks up a localized string similar to The {0} field is not a valid credit card number.. - /// - public static string CreditCardAttribute_Invalid { - get { - return ResourceManager.GetString("CreditCardAttribute_Invalid", resourceCulture); - } - } - /// /// Looks up a localized string similar to Custom color. /// @@ -422,83 +323,11 @@ public static string CustomColor { } /// - /// Looks up a localized string similar to The CustomValidationAttribute method '{0}' in type '{1}' must return System.ComponentModel.DataAnnotations.ValidationResult. Use System.ComponentModel.DataAnnotations.ValidationResult.Success to represent success.. - /// - public static string CustomValidationAttribute_Method_Must_Return_ValidationResult { - get { - return ResourceManager.GetString("CustomValidationAttribute_Method_Must_Return_ValidationResult", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The CustomValidationAttribute method '{0}' does not exist in type '{1}' or is not public and static.. - /// - public static string CustomValidationAttribute_Method_Not_Found { - get { - return ResourceManager.GetString("CustomValidationAttribute_Method_Not_Found", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The CustomValidationAttribute.Method was not specified.. - /// - public static string CustomValidationAttribute_Method_Required { - get { - return ResourceManager.GetString("CustomValidationAttribute_Method_Required", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The CustomValidationAttribute method '{0}' in type '{1}' must match the expected signature: public static ValidationResult {0}(object value, ValidationContext context). The value can be strongly typed. The ValidationContext parameter is optional.. - /// - public static string CustomValidationAttribute_Method_Signature { - get { - return ResourceManager.GetString("CustomValidationAttribute_Method_Signature", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Could not convert the value of type '{0}' to '{1}' as expected by method {2}.{3}.. - /// - public static string CustomValidationAttribute_Type_Conversion_Failed { - get { - return ResourceManager.GetString("CustomValidationAttribute_Type_Conversion_Failed", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The custom validation type '{0}' must be public.. - /// - public static string CustomValidationAttribute_Type_Must_Be_Public { - get { - return ResourceManager.GetString("CustomValidationAttribute_Type_Must_Be_Public", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to {0} is not valid.. - /// - public static string CustomValidationAttribute_ValidationError { - get { - return ResourceManager.GetString("CustomValidationAttribute_ValidationError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The CustomValidationAttribute.ValidatorType was not specified.. - /// - public static string CustomValidationAttribute_ValidatorType_Required { - get { - return ResourceManager.GetString("CustomValidationAttribute_ValidatorType_Required", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The custom DataType string cannot be null or empty.. + /// Looks up a localized string similar to Dashboard. /// - public static string DataTypeAttribute_EmptyDataTypeString { + public static string Dashboard { get { - return ResourceManager.GetString("DataTypeAttribute_EmptyDataTypeString", resourceCulture); + return ResourceManager.GetString("Dashboard", resourceCulture); } } @@ -520,15 +349,6 @@ public static string DefaultColorPicker { } } - /// - /// Looks up a localized string similar to An unknown failure has occurred.. - /// - public static string DefaultError { - get { - return ResourceManager.GetString("DefaultError", resourceCulture); - } - } - /// /// Looks up a localized string similar to Delete. /// @@ -575,11 +395,11 @@ public static string DeleteProduct { } /// - /// Looks up a localized string similar to The {0} field equals one of the values specified in DeniedValuesAttribute.. + /// Looks up a localized string similar to Delete todo item. /// - public static string DeniedValuesAttribute_Invalid { + public static string DeleteTodoItem { get { - return ResourceManager.GetString("DeniedValuesAttribute_Invalid", resourceCulture); + return ResourceManager.GetString("DeleteTodoItem", resourceCulture); } } @@ -592,15 +412,6 @@ public static string Description { } } - /// - /// Looks up a localized string similar to The {0} property has not been set. Use the {1} method to get the value.. - /// - public static string DisplayAttribute_PropertyNotSet { - get { - return ResourceManager.GetString("DisplayAttribute_PropertyNotSet", resourceCulture); - } - } - /// /// Looks up a localized string similar to Don’t have an account?. /// @@ -619,24 +430,6 @@ public static string DuplicateEmail { } } - /// - /// Looks up a localized string similar to Role name '{0}' is already taken.. - /// - public static string DuplicateRoleName { - get { - return ResourceManager.GetString("DuplicateRoleName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Username '{0}' is already taken.. - /// - public static string DuplicateUserName { - get { - return ResourceManager.GetString("DuplicateUserName", resourceCulture); - } - } - /// /// Looks up a localized string similar to Edit. /// @@ -664,15 +457,6 @@ public static string EditProduct { } } - /// - /// Looks up a localized string similar to Edit profile. - /// - public static string EditProfile { - get { - return ResourceManager.GetString("EditProfile", resourceCulture); - } - } - /// /// Looks up a localized string similar to Edit profile. /// @@ -754,24 +538,6 @@ public static string EnterProductName { } } - /// - /// Looks up a localized string similar to The type provided for EnumDataTypeAttribute cannot be null.. - /// - public static string EnumDataTypeAttribute_TypeCannotBeNull { - get { - return ResourceManager.GetString("EnumDataTypeAttribute_TypeCannotBeNull", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The type '{0}' needs to represent an enumeration type.. - /// - public static string EnumDataTypeAttribute_TypeNeedsToBeAnEnum { - get { - return ResourceManager.GetString("EnumDataTypeAttribute_TypeNeedsToBeAnEnum", resourceCulture); - } - } - /// /// Looks up a localized string similar to Error. /// @@ -781,24 +547,6 @@ public static string Error { } } - /// - /// Looks up a localized string similar to The {0} field only accepts files with the following extensions: {1}. - /// - public static string FileExtensionsAttribute_Invalid { - get { - return ResourceManager.GetString("FileExtensionsAttribute_Invalid", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to An error occurred while removing file. - /// - public static string FileRemoveFailed { - get { - return ResourceManager.GetString("FileRemoveFailed", resourceCulture); - } - } - /// /// Looks up a localized string similar to An error occurred while uploading file. /// @@ -844,24 +592,6 @@ public static string ForgotPasswordLink { } } - /// - /// Looks up a localized string similar to Please enter the email address you have been signed up with so we can send a reset password link to your email address.. - /// - public static string ForgotPasswordMessage { - get { - return ResourceManager.GetString("ForgotPasswordMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Forgot password. - /// - public static string ForgotPasswordTitle { - get { - return ResourceManager.GetString("ForgotPasswordTitle", resourceCulture); - } - } - /// /// Looks up a localized string similar to FullName. /// @@ -935,7 +665,7 @@ public static string Home { } /// - /// Looks up a localized string similar to Create your multi-mode (WASM, Server, Hybrid, pre-rendering) Blazor Admin​Panel easily in the shortest time ever!. + /// Looks up a localized string similar to Create your multi-mode (WASM, Server, Hybrid, pre-rendering) Blazor app easily in the shortest time ever!. /// public static string HomeMessage { get { @@ -944,7 +674,7 @@ public static string HomeMessage { } /// - /// Looks up a localized string similar to bit Admin​Panel. + /// Looks up a localized string similar to BlazorWeb Home. /// public static string HomeTitle { get { @@ -971,529 +701,196 @@ public static string InvalidConfirmationLinkMessage { } /// - /// Looks up a localized string similar to Email '{0}' is invalid.. + /// Looks up a localized string similar to Invalid username or password. /// - public static string InvalidEmail { + public static string InvalidUsernameOrPassword { get { - return ResourceManager.GetString("InvalidEmail", resourceCulture); + return ResourceManager.GetString("InvalidUsernameOrPassword", resourceCulture); } } /// - /// Looks up a localized string similar to Type {0} must derive from {1}<{2}>.. + /// Looks up a localized string similar to Last 30 days category count. /// - public static string InvalidManagerType { + public static string Last30DaysCategoryCount { get { - return ResourceManager.GetString("InvalidManagerType", resourceCulture); + return ResourceManager.GetString("Last30DaysCategoryCount", resourceCulture); } } /// - /// Looks up a localized string similar to The provided PasswordHasherCompatibilityMode is invalid.. + /// Looks up a localized string similar to Last 30 days product count. /// - public static string InvalidPasswordHasherCompatibilityMode { + public static string Last30DaysProductCount { get { - return ResourceManager.GetString("InvalidPasswordHasherCompatibilityMode", resourceCulture); + return ResourceManager.GetString("Last30DaysProductCount", resourceCulture); } } /// - /// Looks up a localized string similar to The iteration count must be a positive integer.. + /// Looks up a localized string similar to MaxLengthAttribute must have a Length value that is greater than zero. Use MaxLength() without parameters to indicate that the string or array can have the maximum allowable length.. /// - public static string InvalidPasswordHasherIterationCount { + public static string MaxLengthAttribute_InvalidMaxLength { get { - return ResourceManager.GetString("InvalidPasswordHasherIterationCount", resourceCulture); + return ResourceManager.GetString("MaxLengthAttribute_InvalidMaxLength", resourceCulture); } } /// - /// Looks up a localized string similar to Role name '{0}' is invalid.. + /// Looks up a localized string similar to The field {0} must be a string or array type with a minimum length of '{1}'.. /// - public static string InvalidRoleName { + public static string MinLengthAttribute_ValidationError { get { - return ResourceManager.GetString("InvalidRoleName", resourceCulture); + return ResourceManager.GetString("MinLengthAttribute_ValidationError", resourceCulture); } } /// - /// Looks up a localized string similar to Invalid token.. + /// Looks up a localized string similar to Name. /// - public static string InvalidToken { + public static string Name { get { - return ResourceManager.GetString("InvalidToken", resourceCulture); + return ResourceManager.GetString("Name", resourceCulture); } } /// - /// Looks up a localized string similar to Username '{0}' is invalid, can only contain letters or digits.. + /// Looks up a localized string similar to New Password. /// - public static string InvalidUserName { + public static string NewPassword { get { - return ResourceManager.GetString("InvalidUserName", resourceCulture); + return ResourceManager.GetString("NewPassword", resourceCulture); } } /// - /// Looks up a localized string similar to Invalid username or password. + /// Looks up a localized string similar to No. /// - public static string InvalidUsernameOrPassword { + public static string No { get { - return ResourceManager.GetString("InvalidUsernameOrPassword", resourceCulture); + return ResourceManager.GetString("No", resourceCulture); } } /// - /// Looks up a localized string similar to Is accept privacy policy?. + /// Looks up a localized string similar to No todos yet. /// - public static string IsAcceptPrivacy { + public static string NoTodos { get { - return ResourceManager.GetString("IsAcceptPrivacy", resourceCulture); + return ResourceManager.GetString("NoTodos", resourceCulture); } } /// - /// Looks up a localized string similar to Known error. + /// Looks up a localized string similar to Haven’t you received the confirmation email?. /// - public static string KnownException { + public static string NotReceivedConfirmationEmailMessage { get { - return ResourceManager.GetString("KnownException", resourceCulture); + return ResourceManager.GetString("NotReceivedConfirmationEmailMessage", resourceCulture); } } /// - /// Looks up a localized string similar to Last 30 days category count. + /// Looks up a localized string similar to OR. /// - public static string Last30DaysCategoryCount { + public static string Or { get { - return ResourceManager.GetString("Last30DaysCategoryCount", resourceCulture); + return ResourceManager.GetString("Or", resourceCulture); } } /// - /// Looks up a localized string similar to Last 30 days product count. + /// Looks up a localized string similar to Password. /// - public static string Last30DaysProductCount { + public static string Password { get { - return ResourceManager.GetString("Last30DaysProductCount", resourceCulture); + return ResourceManager.GetString("Password", resourceCulture); } } /// - /// Looks up a localized string similar to LengthAttribute must have a MaximumLength value that is greater than or equal to MinimumLength.. + /// Looks up a localized string similar to Your password changed successfully.. /// - public static string LengthAttribute_InvalidMaxLength { + public static string PasswordChangedSuccessfullyMessage { get { - return ResourceManager.GetString("LengthAttribute_InvalidMaxLength", resourceCulture); + return ResourceManager.GetString("PasswordChangedSuccessfullyMessage", resourceCulture); } } /// - /// Looks up a localized string similar to LengthAttribute must have a MinimumLength value that is zero or greater.. + /// Looks up a localized string similar to Price. /// - public static string LengthAttribute_InvalidMinLength { + public static string Price { get { - return ResourceManager.GetString("LengthAttribute_InvalidMinLength", resourceCulture); + return ResourceManager.GetString("Price", resourceCulture); } } /// - /// Looks up a localized string similar to The field of type {0} must be a string, array or ICollection type.. + /// Looks up a localized string similar to Product category. /// - public static string LengthAttribute_InvalidValueType { + public static string ProductCategory { get { - return ResourceManager.GetString("LengthAttribute_InvalidValueType", resourceCulture); + return ResourceManager.GetString("ProductCategory", resourceCulture); } } /// - /// Looks up a localized string similar to The field {0} must be a string or collection type with a minimum length of '{1}' and maximum length of '{2}'.. + /// Looks up a localized string similar to Product entity could not be found. /// - public static string LengthAttribute_ValidationError { + public static string ProductCouldNotBeFound { get { - return ResourceManager.GetString("LengthAttribute_ValidationError", resourceCulture); + return ResourceManager.GetString("ProductCouldNotBeFound", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot retrieve property '{0}' because localization failed. Type '{1}' is not public or does not contain a public static string property with the name '{2}'.. + /// Looks up a localized string similar to Products. /// - public static string LocalizableString_LocalizationFailed { + public static string Products { get { - return ResourceManager.GetString("LocalizableString_LocalizationFailed", resourceCulture); + return ResourceManager.GetString("Products", resourceCulture); } } /// - /// Looks up a localized string similar to A user with this login already exists.. + /// Looks up a localized string similar to Product sales. /// - public static string LoginAlreadyAssociated { + public static string ProductSales { get { - return ResourceManager.GetString("LoginAlreadyAssociated", resourceCulture); + return ResourceManager.GetString("ProductSales", resourceCulture); } } /// - /// Looks up a localized string similar to Made with. + /// Looks up a localized string similar to This chart shows the sale number of each product.. /// - public static string MadeWith { + public static string ProductSalesText { get { - return ResourceManager.GetString("MadeWith", resourceCulture); + return ResourceManager.GetString("ProductSalesText", resourceCulture); } } /// - /// Looks up a localized string similar to MaxLengthAttribute must have a Length value that is greater than zero. Use MaxLength() without parameters to indicate that the string or array can have the maximum allowable length.. + /// Looks up a localized string similar to Products count per category chart. /// - public static string MaxLengthAttribute_InvalidMaxLength { + public static string ProductsCountPerCategoryChart { get { - return ResourceManager.GetString("MaxLengthAttribute_InvalidMaxLength", resourceCulture); + return ResourceManager.GetString("ProductsCountPerCategoryChart", resourceCulture); } } /// - /// Looks up a localized string similar to The field {0} must be a string or array type with a maximum length of '{1}'.. + /// Looks up a localized string similar to This chart shows the number of products in each category.. /// - public static string MaxLengthAttribute_ValidationError { + public static string ProductsCountPerCategoryChartText { get { - return ResourceManager.GetString("MaxLengthAttribute_ValidationError", resourceCulture); + return ResourceManager.GetString("ProductsCountPerCategoryChartText", resourceCulture); } } /// - /// Looks up a localized string similar to MetadataClassType cannot be null.. - /// - public static string MetadataTypeAttribute_TypeCannotBeNull { - get { - return ResourceManager.GetString("MetadataTypeAttribute_TypeCannotBeNull", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to MinLengthAttribute must have a Length value that is zero or greater.. - /// - public static string MinLengthAttribute_InvalidMinLength { - get { - return ResourceManager.GetString("MinLengthAttribute_InvalidMinLength", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The field {0} must be a string or array type with a minimum length of '{1}'.. - /// - public static string MinLengthAttribute_ValidationError { - get { - return ResourceManager.GetString("MinLengthAttribute_ValidationError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to AddIdentity must be called on the service collection.. - /// - public static string MustCallAddIdentity { - get { - return ResourceManager.GetString("MustCallAddIdentity", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Name. - /// - public static string Name { - get { - return ResourceManager.GetString("Name", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to New Password. - /// - public static string NewPassword { - get { - return ResourceManager.GetString("NewPassword", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to No. - /// - public static string No { - get { - return ResourceManager.GetString("No", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to No IPersonalDataProtector service was registered, this is required when ProtectPersonalData = true.. - /// - public static string NoPersonalDataProtector { - get { - return ResourceManager.GetString("NoPersonalDataProtector", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to No RoleType was specified, try AddRoles<TRole>().. - /// - public static string NoRoleType { - get { - return ResourceManager.GetString("NoRoleType", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to No IUserTwoFactorTokenProvider<{0}> named '{1}' is registered.. - /// - public static string NoTokenProvider { - get { - return ResourceManager.GetString("NoTokenProvider", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Haven’t you received the confirmation email?. - /// - public static string NotReceivedConfirmationEmailMessage { - get { - return ResourceManager.GetString("NotReceivedConfirmationEmailMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to User security stamp cannot be null.. - /// - public static string NullSecurityStamp { - get { - return ResourceManager.GetString("NullSecurityStamp", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to OR. - /// - public static string Or { - get { - return ResourceManager.GetString("Or", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Password. - /// - public static string Password { - get { - return ResourceManager.GetString("Password", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Your password changed successfully.. - /// - public static string PasswordChangedSuccessfullyMessage { - get { - return ResourceManager.GetString("PasswordChangedSuccessfullyMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Incorrect password.. - /// - public static string PasswordMismatch { - get { - return ResourceManager.GetString("PasswordMismatch", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Passwords must have at least one digit ('0'-'9').. - /// - public static string PasswordRequiresDigit { - get { - return ResourceManager.GetString("PasswordRequiresDigit", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Passwords must have at least one lowercase ('a'-'z').. - /// - public static string PasswordRequiresLower { - get { - return ResourceManager.GetString("PasswordRequiresLower", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Passwords must have at least one non alphanumeric character.. - /// - public static string PasswordRequiresNonAlphanumeric { - get { - return ResourceManager.GetString("PasswordRequiresNonAlphanumeric", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Passwords must use at least {0} different characters.. - /// - public static string PasswordRequiresUniqueChars { - get { - return ResourceManager.GetString("PasswordRequiresUniqueChars", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Passwords must have at least one uppercase ('A'-'Z').. - /// - public static string PasswordRequiresUpper { - get { - return ResourceManager.GetString("PasswordRequiresUpper", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Passwords must be at least {0} characters.. - /// - public static string PasswordTooShort { - get { - return ResourceManager.GetString("PasswordTooShort", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The {0} field is not a valid phone number.. - /// - public static string PhoneAttribute_Invalid { - get { - return ResourceManager.GetString("PhoneAttribute_Invalid", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Price. - /// - public static string Price { - get { - return ResourceManager.GetString("Price", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Privacy. - /// - public static string Privacy { - get { - return ResourceManager.GetString("Privacy", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to I agree to the . - /// - public static string PrivacyPolicyAgreementMessage { - get { - return ResourceManager.GetString("PrivacyPolicyAgreementMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Use this page to detail your site's privacy policy.. - /// - public static string PrivacyPolicyPageMessage { - get { - return ResourceManager.GetString("PrivacyPolicyPageMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Use this section to detail your site's privacy policy.. - /// - public static string PrivacyPolicySectionMessage { - get { - return ResourceManager.GetString("PrivacyPolicySectionMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Privacy Policy. - /// - public static string PrivacyPolicyTitle { - get { - return ResourceManager.GetString("PrivacyPolicyTitle", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Product category. - /// - public static string ProductCategory { - get { - return ResourceManager.GetString("ProductCategory", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Product catologue. - /// - public static string ProductCatologue { - get { - return ResourceManager.GetString("ProductCatologue", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Product entity could not be found. - /// - public static string ProductCouldNotBeFound { - get { - return ResourceManager.GetString("ProductCouldNotBeFound", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Products. - /// - public static string Products { - get { - return ResourceManager.GetString("Products", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Product sales. - /// - public static string ProductSales { - get { - return ResourceManager.GetString("ProductSales", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to This chart shows the sale number of each product.. - /// - public static string ProductSalesText { - get { - return ResourceManager.GetString("ProductSalesText", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Products count per category chart. - /// - public static string ProductsCountPerCategoryChart { - get { - return ResourceManager.GetString("ProductsCountPerCategoryChart", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to This chart shows the number of products in each category.. - /// - public static string ProductsCountPerCategoryChartText { - get { - return ResourceManager.GetString("ProductsCountPerCategoryChartText", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Products. + /// Looks up a localized string similar to Products. /// public static string ProductsPageTitle { get { @@ -1515,133 +912,43 @@ public static string ProductsPercentagePerCategory { /// public static string ProductsPercentagePerCategoryText { get { - return ResourceManager.GetString("ProductsPercentagePerCategoryText", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Image profile. - /// - public static string ProfileImage { - get { - return ResourceManager.GetString("ProfileImage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Profile updated successfully.. - /// - public static string ProfileUpdatedSuccessfullyMessage { - get { - return ResourceManager.GetString("ProfileUpdatedSuccessfullyMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The type {0} must implement {1}.. - /// - public static string RangeAttribute_ArbitraryTypeNotIComparable { - get { - return ResourceManager.GetString("RangeAttribute_ArbitraryTypeNotIComparable", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cannot use exclusive bounds when the maximum value is equal to the minimum value.. - /// - public static string RangeAttribute_CannotUseExclusiveBoundsWhenTheyAreEqual { - get { - return ResourceManager.GetString("RangeAttribute_CannotUseExclusiveBoundsWhenTheyAreEqual", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The maximum value '{0}' must be greater than or equal to the minimum value '{1}'.. - /// - public static string RangeAttribute_MinGreaterThanMax { - get { - return ResourceManager.GetString("RangeAttribute_MinGreaterThanMax", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The minimum and maximum values must be set.. - /// - public static string RangeAttribute_Must_Set_Min_And_Max { - get { - return ResourceManager.GetString("RangeAttribute_Must_Set_Min_And_Max", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The OperandType must be set when strings are used for minimum and maximum values.. - /// - public static string RangeAttribute_Must_Set_Operand_Type { - get { - return ResourceManager.GetString("RangeAttribute_Must_Set_Operand_Type", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The field {0} must be between {1} and {2}.. - /// - public static string RangeAttribute_ValidationError { - get { - return ResourceManager.GetString("RangeAttribute_ValidationError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The field {0} must be between {1} and {2} exclusive.. - /// - public static string RangeAttribute_ValidationError_MaxExclusive { - get { - return ResourceManager.GetString("RangeAttribute_ValidationError_MaxExclusive", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The field {0} must be between {1} exclusive and {2}.. - /// - public static string RangeAttribute_ValidationError_MinExclusive { - get { - return ResourceManager.GetString("RangeAttribute_ValidationError_MinExclusive", resourceCulture); + return ResourceManager.GetString("ProductsPercentagePerCategoryText", resourceCulture); } } /// - /// Looks up a localized string similar to The field {0} must be between {1} exclusive and {2} exclusive.. + /// Looks up a localized string similar to Profile Image. /// - public static string RangeAttribute_ValidationError_MinExclusive_MaxExclusive { + public static string ProfileImage { get { - return ResourceManager.GetString("RangeAttribute_ValidationError_MinExclusive_MaxExclusive", resourceCulture); + return ResourceManager.GetString("ProfileImage", resourceCulture); } } /// - /// Looks up a localized string similar to Recovery code redemption failed.. + /// Looks up a localized string similar to Profile updated successfully.. /// - public static string RecoveryCodeRedemptionFailed { + public static string ProfileUpdatedSuccessfullyMessage { get { - return ResourceManager.GetString("RecoveryCodeRedemptionFailed", resourceCulture); + return ResourceManager.GetString("ProfileUpdatedSuccessfullyMessage", resourceCulture); } } /// - /// Looks up a localized string similar to The field {0} must match the regular expression '{1}'.. + /// Looks up a localized string similar to The field {0} must be between {1} and {2}.. /// - public static string RegexAttribute_ValidationError { + public static string RangeAttribute_ValidationError { get { - return ResourceManager.GetString("RegexAttribute_ValidationError", resourceCulture); + return ResourceManager.GetString("RangeAttribute_ValidationError", resourceCulture); } } /// - /// Looks up a localized string similar to The pattern must be set to a valid regular expression.. + /// Looks up a localized string similar to Remember me?. /// - public static string RegularExpressionAttribute_Empty_Pattern { + public static string RememberMe { get { - return ResourceManager.GetString("RegularExpressionAttribute_Empty_Pattern", resourceCulture); + return ResourceManager.GetString("RememberMe", resourceCulture); } } @@ -1735,15 +1042,6 @@ public static string RestException { } } - /// - /// Looks up a localized string similar to Role {0} does not exist.. - /// - public static string RoleNotFound { - get { - return ResourceManager.GetString("RoleNotFound", resourceCulture); - } - } - /// /// Looks up a localized string similar to Save. /// @@ -1762,15 +1060,6 @@ public static string SearchOnName { } } - /// - /// Looks up a localized string similar to Select a category. - /// - public static string SelectACategory { - get { - return ResourceManager.GetString("SelectACategory", resourceCulture); - } - } - /// /// Looks up a localized string similar to Select your birth date. /// @@ -1790,11 +1079,11 @@ public static string SelectCategory { } /// - /// Looks up a localized string similar to Submit. + /// Looks up a localized string similar to Unable to connect to server.. /// - public static string SendResetLink { + public static string ServerConnectionException { get { - return ResourceManager.GetString("SendResetLink", resourceCulture); + return ResourceManager.GetString("ServerConnectionException", resourceCulture); } } @@ -1862,227 +1151,83 @@ public static string SingUpTitle { } /// - /// Looks up a localized string similar to Sort by. - /// - public static string SortBy { - get { - return ResourceManager.GetString("SortBy", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Admin​Panel is a project template that provides common features such as Sign-up & Sign-in. This template is powered by bit BlazorUI components.. - /// - public static string StartupPageDescription { - get { - return ResourceManager.GetString("StartupPageDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to bit Admin​Panel. - /// - public static string StartupPageTitle { - get { - return ResourceManager.GetString("StartupPageTitle", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Store does not implement IProtectedUserStore<TUser> which is required when ProtectPersonalData = true.. - /// - public static string StoreNotIProtectedUserStore { - get { - return ResourceManager.GetString("StoreNotIProtectedUserStore", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Store does not implement IQueryableRoleStore<TRole>.. - /// - public static string StoreNotIQueryableRoleStore { - get { - return ResourceManager.GetString("StoreNotIQueryableRoleStore", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Store does not implement IQueryableUserStore<TUser>.. - /// - public static string StoreNotIQueryableUserStore { - get { - return ResourceManager.GetString("StoreNotIQueryableUserStore", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Store does not implement IRoleClaimStore<TRole>.. - /// - public static string StoreNotIRoleClaimStore { - get { - return ResourceManager.GetString("StoreNotIRoleClaimStore", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Store does not implement IUserAuthenticationTokenStore<User>.. - /// - public static string StoreNotIUserAuthenticationTokenStore { - get { - return ResourceManager.GetString("StoreNotIUserAuthenticationTokenStore", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Store does not implement IUserAuthenticatorKeyStore<User>.. - /// - public static string StoreNotIUserAuthenticatorKeyStore { - get { - return ResourceManager.GetString("StoreNotIUserAuthenticatorKeyStore", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Store does not implement IUserClaimStore<TUser>.. - /// - public static string StoreNotIUserClaimStore { - get { - return ResourceManager.GetString("StoreNotIUserClaimStore", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Store does not implement IUserConfirmationStore<TUser>.. - /// - public static string StoreNotIUserConfirmationStore { - get { - return ResourceManager.GetString("StoreNotIUserConfirmationStore", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Store does not implement IUserEmailStore<TUser>.. - /// - public static string StoreNotIUserEmailStore { - get { - return ResourceManager.GetString("StoreNotIUserEmailStore", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Store does not implement IUserLockoutStore<TUser>.. - /// - public static string StoreNotIUserLockoutStore { - get { - return ResourceManager.GetString("StoreNotIUserLockoutStore", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Store does not implement IUserLoginStore<TUser>.. - /// - public static string StoreNotIUserLoginStore { - get { - return ResourceManager.GetString("StoreNotIUserLoginStore", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Store does not implement IUserPasswordStore<TUser>.. - /// - public static string StoreNotIUserPasswordStore { - get { - return ResourceManager.GetString("StoreNotIUserPasswordStore", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Store does not implement IUserPhoneNumberStore<TUser>.. - /// - public static string StoreNotIUserPhoneNumberStore { - get { - return ResourceManager.GetString("StoreNotIUserPhoneNumberStore", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Store does not implement IUserRoleStore<TUser>.. + /// Looks up a localized string similar to Submit. /// - public static string StoreNotIUserRoleStore { + public static string Submit { get { - return ResourceManager.GetString("StoreNotIUserRoleStore", resourceCulture); + return ResourceManager.GetString("Submit", resourceCulture); } } /// - /// Looks up a localized string similar to Store does not implement IUserSecurityStampStore<TUser>.. + /// Looks up a localized string similar to Is accept terms?. /// - public static string StoreNotIUserSecurityStampStore { + public static string TermsAccepted { get { - return ResourceManager.GetString("StoreNotIUserSecurityStampStore", resourceCulture); + return ResourceManager.GetString("TermsAccepted", resourceCulture); } } /// - /// Looks up a localized string similar to Store does not implement IUserTwoFactorRecoveryCodeStore<User>.. + /// Looks up a localized string similar to I agree to the. /// - public static string StoreNotIUserTwoFactorRecoveryCodeStore { + public static string TermsMessage { get { - return ResourceManager.GetString("StoreNotIUserTwoFactorRecoveryCodeStore", resourceCulture); + return ResourceManager.GetString("TermsMessage", resourceCulture); } } /// - /// Looks up a localized string similar to Store does not implement IUserTwoFactorStore<TUser>.. + /// Looks up a localized string similar to Terms. /// - public static string StoreNotIUserTwoFactorStore { + public static string TermsTitle { get { - return ResourceManager.GetString("StoreNotIUserTwoFactorStore", resourceCulture); + return ResourceManager.GetString("TermsTitle", resourceCulture); } } /// - /// Looks up a localized string similar to The maximum length must be a nonnegative integer.. + /// Looks up a localized string similar to Title. /// - public static string StringLengthAttribute_InvalidMaxLength { + public static string Title { get { - return ResourceManager.GetString("StringLengthAttribute_InvalidMaxLength", resourceCulture); + return ResourceManager.GetString("Title", resourceCulture); } } /// - /// Looks up a localized string similar to The field {0} must be a string with a maximum length of {1}.. + /// Looks up a localized string similar to Add a todo. /// - public static string StringLengthAttribute_ValidationError { + public static string TodoAddPlaceholder { get { - return ResourceManager.GetString("StringLengthAttribute_ValidationError", resourceCulture); + return ResourceManager.GetString("TodoAddPlaceholder", resourceCulture); } } /// - /// Looks up a localized string similar to The field {0} must be a string with a minimum length of {2} and a maximum length of {1}.. + /// Looks up a localized string similar to Todo item could not be found. /// - public static string StringLengthAttribute_ValidationErrorIncludingMinimum { + public static string ToDoItemCouldNotBeFound { get { - return ResourceManager.GetString("StringLengthAttribute_ValidationErrorIncludingMinimum", resourceCulture); + return ResourceManager.GetString("ToDoItemCouldNotBeFound", resourceCulture); } } /// - /// Looks up a localized string similar to Submit. + /// Looks up a localized string similar to Search some todo.... /// - public static string Submit { + public static string TodoSearchPlaceholder { get { - return ResourceManager.GetString("Submit", resourceCulture); + return ResourceManager.GetString("TodoSearchPlaceholder", resourceCulture); } } /// - /// Looks up a localized string similar to Terms. + /// Looks up a localized string similar to Todo. /// - public static string TermsTitle { + public static string TodoTitle { get { - return ResourceManager.GetString("TermsTitle", resourceCulture); + return ResourceManager.GetString("TodoTitle", resourceCulture); } } @@ -2113,51 +1258,6 @@ public static string TotalProducts { } } - /// - /// Looks up a localized string similar to The key parameter at position {0} with value '{1}' is not a string. Every key control parameter must be a string.. - /// - public static string UIHintImplementation_ControlParameterKeyIsNotAString { - get { - return ResourceManager.GetString("UIHintImplementation_ControlParameterKeyIsNotAString", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The key parameter at position {0} is null. Every key control parameter must be a string.. - /// - public static string UIHintImplementation_ControlParameterKeyIsNull { - get { - return ResourceManager.GetString("UIHintImplementation_ControlParameterKeyIsNull", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The key parameter at position {0} with value '{1}' occurs more than once.. - /// - public static string UIHintImplementation_ControlParameterKeyOccursMoreThanOnce { - get { - return ResourceManager.GetString("UIHintImplementation_ControlParameterKeyOccursMoreThanOnce", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The number of control parameters must be even.. - /// - public static string UIHintImplementation_NeedEvenNumberOfControlParameters { - get { - return ResourceManager.GetString("UIHintImplementation_NeedEvenNumberOfControlParameters", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Incapable de se connecter au serveur.. - /// - public static string UnableToConnectToServer { - get { - return ResourceManager.GetString("UnableToConnectToServer", resourceCulture); - } - } - /// /// Looks up a localized string similar to Your request lacks valid authentication credentials. /// @@ -2176,15 +1276,6 @@ public static string UnknownException { } } - /// - /// Looks up a localized string similar to Update. - /// - public static string Update { - get { - return ResourceManager.GetString("Update", resourceCulture); - } - } - /// /// Looks up a localized string similar to The record was modified by another user after you got the original data. the operation was canceled.. /// @@ -2203,33 +1294,6 @@ public static string UploadNewProfileImage { } } - /// - /// Looks up a localized string similar to The {0} field is not a valid fully-qualified http, https, or ftp URL.. - /// - public static string UrlAttribute_Invalid { - get { - return ResourceManager.GetString("UrlAttribute_Invalid", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to User already has a password set.. - /// - public static string UserAlreadyHasPassword { - get { - return ResourceManager.GetString("UserAlreadyHasPassword", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to User already in role '{0}'.. - /// - public static string UserAlreadyInRole { - get { - return ResourceManager.GetString("UserAlreadyInRole", resourceCulture); - } - } - /// /// Looks up a localized string similar to User image could not be found. /// @@ -2240,7 +1304,7 @@ public static string UserImageCouldNotBeFound { } /// - /// Looks up a localized string similar to User is locked out.. + /// Looks up a localized string similar to User is locked out. Try again in {0}. /// public static string UserLockedOut { get { @@ -2248,24 +1312,6 @@ public static string UserLockedOut { } } - /// - /// Looks up a localized string similar to Lockout is not enabled for this user.. - /// - public static string UserLockoutNotEnabled { - get { - return ResourceManager.GetString("UserLockoutNotEnabled", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Username. - /// - public static string UserName { - get { - return ResourceManager.GetString("UserName", resourceCulture); - } - } - /// /// Looks up a localized string similar to User {0} does not exist.. /// @@ -2275,96 +1321,6 @@ public static string UserNameNotFound { } } - /// - /// Looks up a localized string similar to User is not in role '{0}'.. - /// - public static string UserNotInRole { - get { - return ResourceManager.GetString("UserNotInRole", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Using bit platform!. - /// - public static string UsingBitPlatform { - get { - return ResourceManager.GetString("UsingBitPlatform", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Either ErrorMessageString or ErrorMessageResourceName must be set, but not both.. - /// - public static string ValidationAttribute_Cannot_Set_ErrorMessage_And_Resource { - get { - return ResourceManager.GetString("ValidationAttribute_Cannot_Set_ErrorMessage_And_Resource", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to IsValid(object value) has not been implemented by this class. The preferred entry point is GetValidationResult() and classes should override IsValid(object value, ValidationContext context).. - /// - public static string ValidationAttribute_IsValid_NotImplemented { - get { - return ResourceManager.GetString("ValidationAttribute_IsValid_NotImplemented", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Both ErrorMessageResourceType and ErrorMessageResourceName need to be set on this attribute.. - /// - public static string ValidationAttribute_NeedBothResourceTypeAndResourceName { - get { - return ResourceManager.GetString("ValidationAttribute_NeedBothResourceTypeAndResourceName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The property '{0}' on resource type '{1}' is not a string type.. - /// - public static string ValidationAttribute_ResourcePropertyNotStringType { - get { - return ResourceManager.GetString("ValidationAttribute_ResourcePropertyNotStringType", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The resource type '{0}' does not have an accessible static property named '{1}'.. - /// - public static string ValidationAttribute_ResourceTypeDoesNotHaveProperty { - get { - return ResourceManager.GetString("ValidationAttribute_ResourceTypeDoesNotHaveProperty", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The field {0} is invalid.. - /// - public static string ValidationAttribute_ValidationError { - get { - return ResourceManager.GetString("ValidationAttribute_ValidationError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The instance provided must match the ObjectInstance on the ValidationContext supplied.. - /// - public static string Validator_InstanceMustMatchValidationContextInstance { - get { - return ResourceManager.GetString("Validator_InstanceMustMatchValidationContextInstance", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The value for property '{0}' must be of type '{1}'.. - /// - public static string Validator_Property_Value_Wrong_Type { - get { - return ResourceManager.GetString("Validator_Property_Value_Wrong_Type", resourceCulture); - } - } - /// /// Looks up a localized string similar to You have already requested the confirmation email. Try again in {0}. /// @@ -2393,7 +1349,7 @@ public static string Yes { } /// - /// Looks up a localized string similar to You're signed in as. + /// Looks up a localized string similar to You're sign in as. /// public static string YouAreSignInAs { get { @@ -2402,11 +1358,11 @@ public static string YouAreSignInAs { } /// - /// Looks up a localized string similar to You must agree to our privacy policy.. + /// Looks up a localized string similar to You must agree to our terms.. /// - public static string YouHaveToAcceptPrivacyPolicy { + public static string YouHaveToAcceptTerms { get { - return ResourceManager.GetString("YouHaveToAcceptPrivacyPolicy", resourceCulture); + return ResourceManager.GetString("YouHaveToAcceptTerms", resourceCulture); } } diff --git a/src/Shared/Resources/AppStrings.fr.resx b/src/Shared/Resources/AppStrings.fr.resx index dec3ec4..2c525d6 100644 --- a/src/Shared/Resources/AppStrings.fr.resx +++ b/src/Shared/Resources/AppStrings.fr.resx @@ -117,813 +117,442 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - Active - - - Nouvelle catégorie - - - Nouveau produit - - - Créez facilement votre application Blazor multi-mode (WASM, serveur, hybride, pré-rendu) dans les plus brefs délais ! - - - Admin​Panel Accueil - - - Admin​Panel - - - Toute - - - Alphabétique + + Le champ {0} est obligatoire. - - Annuler + + « {0} » et « {1} » ne correspondent pas. - - Catégories + + Le champ {0} n'est pas une adresse e-mail valide. - - Complété + + MaxLengthAttribute doit avoir une valeur de longueur supérieure à zéro. - - Complété + + Le champ {0} doit être de type chaîne ou tableau avec une longueur minimale de « {1} ». - - Éditer + + Le champ {0} doit être compris entre {1} et {2}. - - Modifier la catégorie + + L'e-mail « {0} » est déjà pris. - - Modifier le produit + + L'utilisateur est verrouillé. Réessayez dans {0} - - Editer le profil + + L'utilisateur {0} n'existe pas. - - Nom et prénom + + Requête invalide - - Autre + + La demande n'a pas pu être traitée en raison d'un conflit dans la demande - - Femelle + + L'accès à la ressource demandée est interdit - - Mâle + + Les données de la demande ne sont pas valides - - Dépôt GitHub + + Une erreur s'est produite lors de la communication avec le serveur - - Nombre de catégories des 30 derniers jours + + Votre demande ne contient pas d'informations d'authentification valides - - Nombre de produits des 30 derniers jours + + Une erreur inconnue s'est produite - - Vous n'avez pas reçu le mail de confirmation ? + + L'enregistrement a été modifié par un autre utilisateur après avoir obtenu les données d'origine. - - je suis d'accord avec le + + Ressource introuvable - - Utilisez cette page pour détailler la politique de confidentialité de votre site. + + Trop de demandes - - Utilisez cette section pour détailler la politique de confidentialité de votre site. + + Incapable de se connecter au serveur. - - Politique de confidentialité + + Actif - - Ventes de produits + + Tous - - Ce tableau montre le nombre de vente de chaque produit. + + Alphabétique - - Tableau de nombre de produits par catégorie + + Complété - - Ce graphique montre le nombre de produits dans chaque catégorie. + + Date - - Des produits + + Editer le profil - - Pourcentage de produits par catégorie + + Nom et prénom - - Ce graphique montre le pourcentage de produits dans chaque catégorie. + + Termes Mise à jour du profil réussie. - - Retirer - - - sauvegarder - - - Sélectionnez votre date de naissance - - - Trier par - - - Le panneau d'administration est un modèle de projet qui fournit des fonctionnalités communes telles que l'inscription et la connexion. Ce modèle est alimenté par les composants bit BlazorUI. - - - bit Admin​Panel - - - Catégories totales - - - Produits totaux - - - Télécharger une nouvelle image de profil - - - Vous avez déjà un compte? - - - Vérifiez votre Spam/Junk, si vous ne le trouvez pas dans la boîte de réception. - - - Nous avons envoyé un lien de confirmation à votre adresse e-mail. -Veuillez confirmer votre email en cliquant sur le lien. - - - confirmez votre adresse email - Confirmer le nouveau mot de passe - - Vous n'avez pas de compte ? - E-mail - - Echec de la confirmation par e-mail ! - - - confirmation de l'émail - - - E-mail confirmé avec succès ! - - - mot de passe oublié? - - - Veuillez saisir l'adresse e-mail avec laquelle vous vous êtes inscrit afin que nous puissions vous envoyer un lien de réinitialisation du mot de passe à votre adresse e-mail. - - - Mot de passe oublié - - - Il semble que le lien de confirmation soit invalide ou ait expiré. - - - nouveau mot de passe - - - OU - Mot de passe - Votre mot de passe a été modifié avec succès. + Votre mot de passe a changé avec succès. Le lien de confirmation a été renvoyé à votre adresse e-mail. - - Ré-envoyer l'email - - - Réinitialiser le mot de passe - - Le lien de réinitialisation du mot de passe a été envoyé. - - - Réinitialiser le mot de passe - - - S'identifier - - - S'identifier - - - Déconnexion - - - Chantez - - - Chantez - - - Soumettre - - - Requête invalide - - - La demande n'a pas pu être traitée en raison d'un conflit dans la demande - - - Une erreur s'est produite lors de la suppression du fichier - - - Une erreur s'est produite lors du téléchargement du fichier - - - L'accès à la ressource demandée est interdit - - - Les données de la demande ne sont pas valides - - - Une erreur s'est produite lors de la communication avec le serveur - - - Votre demande ne contient pas d'identifiants d'authentification valides - - - Une erreur inconnue s'est produite + Le lien de réinitialisation du mot de passe a été envoyé à votre adresse e-mail. L'image de l'utilisateur est introuvable - - Échec de concurrence optimiste, l'objet a été modifié. - Error when optimistic concurrency fails - - - L'e-mail '{0}' est déjà pris. - Error for duplicate emails - - - Le nom de rôle '{0}' est déjà pris. - Error for duplicate roles - - - Le nom d'utilisateur '{0}' est déjà pris. - Error for duplicate user names - - - L'e-mail '{0}' n'est pas valide. - Invalid email - - - Le nom de rôle '{0}' n'est pas valide. - Error for invalid role names - - - Jeton invalide. - Error when a token is not recognized - - - Le nom d'utilisateur '{0}' n'est pas valide, ne peut contenir que des lettres ou des chiffres. - User names can only contain letters or digits - - - Un utilisateur avec ce login existe déjà. - Error when a login already linked - - - Mot de passe incorrect. - Error when a password doesn't match - - - Les mots de passe doivent comporter au moins un chiffre ('0'-'9'). - Error when passwords do not have a digit - - - Les mots de passe doivent avoir au moins une minuscule ('a'-'z'). - Error when passwords do not have a lowercase letter - - - Les mots de passe doivent comporter au moins un caractère non alphanumérique. - Error when password does not have enough non alphanumeric characters - - - Les mots de passe doivent avoir au moins une majuscule ('A'-'Z'). - Error when passwords do not have an uppercase letter - - - Le champ {0} doit être une chaîne ou un type de tableau avec une longueur minimale de '{1}'. - - - Le rôle {0} n'existe pas. - Error when a role does not exist - - - Échec de l'utilisation du code de récupération. - Error when a recovery code is not redeemed. - - - L'utilisateur a déjà défini un mot de passe. - Error when AddPasswordAsync called when a user already has a password - - - Utilisateur déjà dans le rôle '{0}'. - Error when a user is already in a role - - - L'utilisateur est verrouillé. Réessayez dans {0} - Error when a user is locked out - - - L'utilisateur {0} n'existe pas. - Error when a user does not exist - - - L'utilisateur n'est pas dans le rôle '{0}'. - Error when a user is not in the role - - - Les mots de passe doivent utiliser au moins {0} caractères différents. - Error message for passwords that are based on similar characters - - - Vous avez déjà demandé l'e-mail de confirmation. Réessayez dans {0} - - - Vous avez déjà demandé l'e-mail de réinitialisation du mot de passe. Réessayez dans {0} - - - Votre email est déjà confirmé. - - - L'enregistrement a été modifié par un autre utilisateur après que vous ayez obtenu les données d'origine. l'opération a été annulée. - - - Erreur connue + + L'entité de catégorie est introuvable - L'entité de produit est introuvable + L'entité du produit est introuvable Cette catégorie contient certains produits, vous ne pouvez donc pas la supprimer - - Ressource introuvable - - - Trop de demandes - Erreur - - Ajouter + + Vous avez déjà demandé l'e-mail de réinitialisation du mot de passe. - - Mot de passe oublié + + Est-ce que j'accepte les conditions ? - - Le type de métadonnées associé au type « {0} » contient les propriétés ou champs inconnus suivants : {1}. Veuillez vous assurer que les noms de ces membres correspondent aux noms des propriétés du type principal. + + Vous devez accepter nos conditions. - - Le type '{0}' ne contient pas de propriété publique nommée '{1}'. + + Genre - - La propriété {0}.{1} est introuvable. + + Date de naissance - - Impossible de trouver une propriété nommée {0}. + + Nom - - Le champ {0} n'est pas un numéro de carte de crédit valide. + + Catégorie - - La méthode CustomValidationAttribute '{0}' dans le type '{1}' doit renvoyer System.ComponentModel.DataAnnotations.ValidationResult. Utilisez System.ComponentModel.DataAnnotations.ValidationResult.Success pour représenter le succès. + + Description - - La méthode CustomValidationAttribute '{0}' n'existe pas dans le type '{1}' ou n'est pas publique et statique. + + Prix - - Le CustomValidationAttribute.Method n'a pas été spécifié. + + Catégories - - La méthode CustomValidationAttribute '{0}' dans le type '{1}' doit correspondre à la signature attendue : public static ValidationResult {0}(valeur d'objet, contexte ValidationContext). La valeur peut être fortement typée. Le paramètre ValidationContext est facultatif. + + Catégorie de produit - - Impossible de convertir la valeur de type '{0}' en '{1}' comme prévu par la méthode {2}.{3}. + + Des produits - - Le type de validation personnalisé '{0}' doit être public. + + Etes-vous sûr de vouloir supprimer la catégorie {0} - - {0} n'est pas valide. + + Êtes-vous sûr de vouloir supprimer le produit {0} - - Le CustomValidationAttribute.ValidatorType n'a pas été spécifié. + + Supprimer la catégorie - - La chaîne DataType personnalisée ne peut pas être nulle ou vide. + + Supprimer le produit - - La propriété {0} n'a pas été définie. Utilisez la méthode {1} pour obtenir la valeur. + + Vous devez être connecté pour continuer. - - Le type fourni pour EnumDataTypeAttribute ne peut pas être nul. + + Nom d'utilisateur ou mot de passe invalide - - Le type '{0}' doit représenter un type d'énumération. + + Vous avez déjà demandé l'e-mail de confirmation. - - Le champ {0} n'accepte que les fichiers avec les extensions suivantes : {1} + + Votre email est déjà confirmé. - - Le champ de type {0} doit être une chaîne, un tableau ou un type ICollection. + + Faire - - Impossible de récupérer la propriété '{0}' car la localisation a échoué. Le type '{1}' n'est pas public ou ne contient pas de propriété de chaîne statique publique portant le nom '{2}'. + + Titre - - MaxLengthAttribute doit avoir une valeur de longueur supérieure à zéro. Utilisez MaxLength() sans paramètres pour indiquer que la chaîne ou le tableau peut avoir la longueur maximale autorisée. + + L'élément à faire est introuvable - - Le champ {0} doit être une chaîne ou un type de tableau d'une longueur maximale de '{1}'. + + Etes-vous sûr de vouloir supprimer {0} - - MetadataClassType cannot be null. + + Supprimer l'élément à faire - - MinLengthAttribute doit avoir une valeur de longueur égale ou supérieure à zéro. + + Maison - - Le champ {0} n'est pas un numéro de téléphone valide. + + Ajouter - - Le type {0} doit implémenter {1}. + + Vous avez déjà un compte? - - La valeur maximale '{0}' doit être supérieure ou égale à la valeur minimale '{1}'. + + Nous avons envoyé un lien de confirmation à votre adresse e-mail. - - Les valeurs minimale et maximale doivent être définies. + + confirmez votre adresse email - - L'OperandType doit être défini lorsque des chaînes sont utilisées pour les valeurs minimales et maximales. + + Supprimer le compte - - Le champ {0} doit être compris entre {1} et {2}. + + Êtes-vous sûr de vouloir supprimer votre compte ? - - Le champ {0} doit correspondre à l'expression régulière '{1}'. + + Vous n'avez pas de compte ? - - Le modèle doit être défini sur une expression régulière valide. + + Modifier - - Le champ {0} est obligatoire. + + Échec de la confirmation par e-mail ! - - La longueur maximale doit être un entier non négatif. + + confirmation de l'émail - - Le champ {0} doit être une chaîne d'une longueur maximale de {1}. + + E-mail confirmé avec succès ! - - Le champ {0} doit être une chaîne d'une longueur minimale de {2} et d'une longueur maximale de {1}. + + Une erreur s'est produite lors du téléchargement du fichier - - Le paramètre clé à la position {0} avec la valeur '{1}' n'est pas une chaîne. Chaque paramètre de contrôle clé doit être une chaîne. + + Veuillez saisir l'adresse e-mail avec laquelle vous avez été inscrit afin que nous puissions envoyer un lien de réinitialisation du mot de passe à votre adresse e-mail. - - Le paramètre clé à la position {0} est nul. Chaque paramètre de contrôle clé doit être une chaîne. + + Mot de passe oublié - - Le paramètre clé à la position {0} avec la valeur '{1}' apparaît plus d'une fois. + + Mâle - - Le nombre de paramètres de contrôle doit être pair. + + Autre - - Le champ {0} n'est pas une URL http, https ou ftp complète valide. + + Dépôt GitHub - - ErrorMessageString ou ErrorMessageResourceName doivent être définis, mais pas les deux. + + Allez à aujourd'hui - - IsValid(object value) n'a pas été implémenté par cette classe. Le point d'entrée préféré est GetValidationResult() et les classes doivent remplacer IsValid(valeur d'objet, contexte ValidationContext). + + Créez facilement votre application Blazor multimode (WASM, serveur, hybride, pré-rendu) dans les plus brefs délais ! - - ErrorMessageResourceType et ErrorMessageResourceName doivent être définis sur cet attribut. + + Accueil Bit.TemplatePlayground - - La propriété '{0}' sur le type de ressource '{1}' n'est pas un type de chaîne. + + Il semble que le lien de confirmation soit invalide ou ait expiré. - - Le type de ressource '{0}' n'a pas de propriété statique accessible nommée '{1}'. + + nouveau mot de passe - - Le champ {0} est invalide. + + Non - - L'instance fournie doit correspondre à l'ObjectInstance sur le ValidationContext fourni. + + Vous n'avez pas reçu l'e-mail de confirmation ? - - La valeur de la propriété '{0}' doit être de type '{1}'. + + OU - - Accepte-t-il la politique de confidentialité ? + + Image de profil - - Nom d'utilisateur + + Retirer - - Vous devez accepter notre politique de confidentialité. + + Ré-envoyer l'email - - Aller à aujourd'hui + + Réinitialiser le mot de passe - - Veuillez saisir l'adresse e-mail avec laquelle vous vous êtes inscrit afin que nous puissions vous envoyer un lien de réinitialisation du mot de passe à votre adresse e-mail. + + Sauvegarder - - Le genre + + Se connecter - - Intimité + + se déconnecter Êtes-vous certain de vouloir vous déconnecter? - - Date de naissance - - - Maison - - - Catégorie - - - L'entité de catégorie est introuvable - - - Description - - - Nom + + S'inscrire - - Prix + + S'inscrire - - Fabriqué avec + + Soumettre - - Non + + je suis d'accord avec le - - Utiliser bit platform ! + + Téléchargez une nouvelle image de profil Oui - - Êtes-vous certain de vouloir vous déconnecter? - - - proche + + Ajouter une tâche - - Entrez le nom du produit + + Rechercher des choses à faire... - - choisissez une catégorie + + Sélectionnez votre date de naissance Choisir une catégorie - - Action - - - Couleur personnalisée + + Catégories totales - - Effacer + + Produits totaux - - Recherche par nom + + Modifier la catégorie - - Ajouter/Modifier une catégorie + + Nouvelle catégorie - + Catégories - - Editer le profil - - - Catégorie de produit - - - Catalogue de produits - - - Des produits + + Entrez le nom de la catégorie - - Id + + Entrez le nom du produit - - Couleur + + Nombre de catégories des 30 derniers jours - - Profil d'image + + Ventes de produits - - Êtes-vous sûr de vouloir supprimer la catégorie {0} + + Ce graphique montre le numéro de vente de chaque produit. - - Êtes-vous sûr de vouloir supprimer le produit {0} + + Rechercher sur le nom - - Envoyer le lien de réinitialisation + + Tableau du nombre de produits par catégorie - - Vous devez être connecté pour continuer. + + Ce graphique montre le nombre de produits dans chaque catégorie. - - Conditions + + Des produits - - Nom d'utilisateur ou mot de passe invalide + + Pourcentage de produits par catégorie - - Supprimer le compte + + Ce graphique montre le pourcentage de produits dans chaque catégorie. - - Êtes-vous sûr de vouloir supprimer votre compte? + + Se connecter - - Mise à jour + + Connectez-vous en tant qu'utilisateur différent Vous êtes connecté en tant que - - Connectez-vous en tant qu'utilisateur différent - - - Entrez le nom de la catégorie + + Réinitialiser le mot de passe - - Supprimer le produit + + Aucune tâche pour l'instant - - Supprimer la catégorie + + Ajouter un produit - - Sélecteur de couleurs par défaut + + Action Dos - - '{0}' et '{1}' ne correspondent pas. - - - Le champ {0} n’est égal à aucune des valeurs spécifiées dans AllowedValuesAttribute. - - - Le champ {0} n’est pas un codage Base64 valide. - - - Le champ {0} est égal à l’une des valeurs spécifiées dans DeniedValuesAttribute. - - - Le champ {0} n’est pas une adresse e-mail valide. - - - LengthAttribute doit avoir une valeur MinimumLength égale ou supérieure à zéro. - - - LengthAttribute doit avoir une valeur MaximumLength supérieure ou égale à MinimumLength. - - - Le champ {0} doit être un type de chaîne ou de collection dont la longueur minimale est de « {1} » et la longueur maximale de « {2} ». - - - Impossible d’utiliser des limites exclusives lorsque la valeur maximale est égale à la valeur minimale. - - - Le champ {0} doit être compris entre {1} exclusif et {2}. - - - Le champ {0} doit être compris entre {1} et {2} exclusif. - - - Le champ {0} doit être compris entre {1} exclusif et {2} exclusif. - - - Une défaillance inconnue s’est produite. - - - Le type {0} doit dériver de {1}<{2}>. - - - Le PasswordHasherCompatibilityMode fourni n’est pas valide. - - - Le nombre d’itérations doit être un entier positif. - - - AddIdentity doit être appelé sur la collection de services. - - - Aucun IUserTwoFactorTokenProvider<{0}> nommé '{1}' n’est enregistré. - - - Le tampon de sécurité de l’utilisateur ne peut pas être nul. - - - Les mots de passe doivent comporter au moins {0} caractères. - - - Store n’implémente pas IQueryableRoleStore<TRole>.</TRole> - - - Store n’implémente pas IQueryableUserStore<TUser>.</TUser> - - - Store n’implémente pas IRoleClaimStore<TRole>.</TRole> - - - Store n’implémente pas IUserAuthenticationTokenStore<User>.</User> - - - Store n’implémente pas IUserClaimStore<TUser>.</TUser> - - - Store n’implémente pas IUserConfirmationStore<TUser>.</TUser> - - - Store n’implémente pas IUserEmailStore<TUser>.</TUser> - - - Store n’implémente pas IUserLockoutStore<TUser>.</TUser> - - - Store n’implémente pas IUserLoginStore<TUser>.</TUser> + + Annuler - - Store n’implémente pas IUserPasswordStore<TUser>.</TUser> + + Vérifiez vos spams/indésirables si vous ne les trouvez pas dans la boîte de réception. - - Store n’implémente pas IUserPhoneNumberStore<TUser>.</TUser> + + Couleur - - Store n’implémente pas IUserRoleStore<TUser>.</TUser> + + Couleur personnalisée - - Store n’implémente pas IUserSecurityStampStore<TUser>.</TUser> + + Sélecteur de couleurs par défaut - - Store n’implémente pas IUserAuthenticatorKeyStore<User>.</User> + + Supprimer - - Store n’implémente pas IUserTwoFactorStore<TUser>.</TUser> + + Identifiant - - Le verrouillage n’est pas activé pour cet utilisateur. + + Mot de passe oublié? - - Store n’implémente pas IUserTwoFactorRecoveryCodeStore<User>.</User> + + Nombre de produits des 30 derniers jours - - Aucun RoleType n’a été spécifié, essayez AddRoles<TRole>().</TRole> + + Femelle - - Store n’implémente pas IProtectedUserStore,<TUser> ce qui est requis lorsque ProtectPersonalData = true.</TUser> + + Modifier le produit - - Aucun service IPersonalDataProtector n’a été enregistré, cela est requis lorsque ProtectPersonalData = true. + + Tableau de bord - - Incapable de se connecter au serveur. + + Souviens-toi de moi? \ No newline at end of file diff --git a/src/Shared/Resources/AppStrings.resx b/src/Shared/Resources/AppStrings.resx index d2605c5..a8c8b51 100644 --- a/src/Shared/Resources/AppStrings.resx +++ b/src/Shared/Resources/AppStrings.resx @@ -1,6 +1,7 @@  + - - - - + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - - The {0} field does not equal any of the values specified in AllowedValuesAttribute. - - - The associated metadata type for type '{0}' contains the following unknown properties or fields: {1}. Please make sure that the names of these members match the names of the properties on the main type. - - - The type '{0}' does not contain a public property named '{1}'. - - - The {0} field is not a valid Base64 encoding. - - - The property {0}.{1} could not be found. - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + '{0}' and '{1}' do not match. - - Could not find a property named {0}. - - - The {0} field is not a valid credit card number. - - - The CustomValidationAttribute method '{0}' in type '{1}' must return System.ComponentModel.DataAnnotations.ValidationResult. Use System.ComponentModel.DataAnnotations.ValidationResult.Success to represent success. - - - The CustomValidationAttribute method '{0}' does not exist in type '{1}' or is not public and static. - - - The CustomValidationAttribute.Method was not specified. - - - The CustomValidationAttribute method '{0}' in type '{1}' must match the expected signature: public static ValidationResult {0}(object value, ValidationContext context). The value can be strongly typed. The ValidationContext parameter is optional. - - - Could not convert the value of type '{0}' to '{1}' as expected by method {2}.{3}. - - - The custom validation type '{0}' must be public. - - - {0} is not valid. - - - The CustomValidationAttribute.ValidatorType was not specified. - - - The custom DataType string cannot be null or empty. - - - The {0} field equals one of the values specified in DeniedValuesAttribute. - - - The {0} property has not been set. Use the {1} method to get the value. - - + The {0} field is not a valid e-mail address. - - The type provided for EnumDataTypeAttribute cannot be null. - - - The type '{0}' needs to represent an enumeration type. - - - The {0} field only accepts files with the following extensions: {1} - - - Cannot retrieve property '{0}' because localization failed. Type '{1}' is not public or does not contain a public static string property with the name '{2}'. - - + MaxLengthAttribute must have a Length value that is greater than zero. Use MaxLength() without parameters to indicate that the string or array can have the maximum allowable length. - - The field {0} must be a string or array type with a maximum length of '{1}'. - - - MetadataClassType cannot be null. - - - MinLengthAttribute must have a Length value that is zero or greater. - - + The field {0} must be a string or array type with a minimum length of '{1}'. - - LengthAttribute must have a MinimumLength value that is zero or greater. - - - LengthAttribute must have a MaximumLength value that is greater than or equal to MinimumLength. - - - The field {0} must be a string or collection type with a minimum length of '{1}' and maximum length of '{2}'. - - - The field of type {0} must be a string, array or ICollection type. - - - The {0} field is not a valid phone number. - - - The type {0} must implement {1}. - - - The maximum value '{0}' must be greater than or equal to the minimum value '{1}'. - - - Cannot use exclusive bounds when the maximum value is equal to the minimum value. - - - The minimum and maximum values must be set. - - - The OperandType must be set when strings are used for minimum and maximum values. - - + The field {0} must be between {1} and {2}. - - The field {0} must be between {1} exclusive and {2}. - - - The field {0} must be between {1} and {2} exclusive. - - - The field {0} must be between {1} exclusive and {2} exclusive. - - - The field {0} must match the regular expression '{1}'. - - - The pattern must be set to a valid regular expression. - - + The {0} field is required. - - The maximum length must be a nonnegative integer. - - - The field {0} must be a string with a maximum length of {1}. - - - The field {0} must be a string with a minimum length of {2} and a maximum length of {1}. - - - The key parameter at position {0} with value '{1}' is not a string. Every key control parameter must be a string. - - - The key parameter at position {0} is null. Every key control parameter must be a string. - - - The key parameter at position {0} with value '{1}' occurs more than once. - - - The number of control parameters must be even. - - - The {0} field is not a valid fully-qualified http, https, or ftp URL. - - - Either ErrorMessageString or ErrorMessageResourceName must be set, but not both. - - - IsValid(object value) has not been implemented by this class. The preferred entry point is GetValidationResult() and classes should override IsValid(object value, ValidationContext context). - - - Both ErrorMessageResourceType and ErrorMessageResourceName need to be set on this attribute. - - - The property '{0}' on resource type '{1}' is not a string type. - - - The resource type '{0}' does not have an accessible static property named '{1}'. - - - The field {0} is invalid. - - - The instance provided must match the ObjectInstance on the ValidationContext supplied. - - - The value for property '{0}' must be of type '{1}'. - - - - Optimistic concurrency failure, object has been modified. - Error when optimistic concurrency fails - - - An unknown failure has occurred. - Default identity result error message - - - Email '{0}' is already taken. - Error for duplicate emails - - - Role name '{0}' is already taken. - Error for duplicate roles - - - Username '{0}' is already taken. - Error for duplicate user names - - - Email '{0}' is invalid. - Invalid email - - - Type {0} must derive from {1}<{2}>. - Error when the manager type is not derived correctly - - - The provided PasswordHasherCompatibilityMode is invalid. - Error when the password hasher doesn't understand the format it's being asked to produce. - - - The iteration count must be a positive integer. - Error when the iteration count is < 1. - - - Role name '{0}' is invalid. - Error for invalid role names - - - Invalid token. - Error when a token is not recognized - - - Username '{0}' is invalid, can only contain letters or digits. - User names can only contain letters or digits - - - A user with this login already exists. - Error when a login already linked - - - AddIdentity must be called on the service collection. - Error when AddIdentity is not called - - - No IUserTwoFactorTokenProvider<{0}> named '{1}' is registered. - Error when there is no IUserTwoFactorTokenProvider - - - User security stamp cannot be null. - Error when a user's security stamp is null. - - - Incorrect password. - Error when a password doesn't match - - - Passwords must have at least one digit ('0'-'9'). - Error when passwords do not have a digit - - - Passwords must have at least one lowercase ('a'-'z'). - Error when passwords do not have a lowercase letter - - - Passwords must have at least one non alphanumeric character. - Error when password does not have enough non alphanumeric characters - - - Passwords must have at least one uppercase ('A'-'Z'). - Error when passwords do not have an uppercase letter - - - Passwords must be at least {0} characters. - Error message for passwords that are too short - - - Role {0} does not exist. - Error when a role does not exist - - - Store does not implement IQueryableRoleStore<TRole>. - Error when the store does not implement this interface - - - Store does not implement IQueryableUserStore<TUser>. - Error when the store does not implement this interface - - - Store does not implement IRoleClaimStore<TRole>. - Error when the store does not implement this interface - - - Store does not implement IUserAuthenticationTokenStore<User>. - Error when the store does not implement this interface - - - Store does not implement IUserClaimStore<TUser>. - Error when the store does not implement this interface - - - Store does not implement IUserConfirmationStore<TUser>. - Error when the store does not implement this interface - - - Store does not implement IUserEmailStore<TUser>. - Error when the store does not implement this interface - - - Store does not implement IUserLockoutStore<TUser>. - Error when the store does not implement this interface - - - Store does not implement IUserLoginStore<TUser>. - Error when the store does not implement this interface - - - Store does not implement IUserPasswordStore<TUser>. - Error when the store does not implement this interface - - - Store does not implement IUserPhoneNumberStore<TUser>. - Error when the store does not implement this interface - - - Store does not implement IUserRoleStore<TUser>. - Error when the store does not implement this interface - - - Store does not implement IUserSecurityStampStore<TUser>. - Error when the store does not implement this interface - - - Store does not implement IUserAuthenticatorKeyStore<User>. - Error when the store does not implement this interface - - - Store does not implement IUserTwoFactorStore<TUser>. - Error when the store does not implement this interface - - - Recovery code redemption failed. - Error when a recovery code is not redeemed. - - - User already has a password set. - Error when AddPasswordAsync called when a user already has a password - - - User already in role '{0}'. - Error when a user is already in a role - - - User is locked out. - Error when a user is locked out - - - Lockout is not enabled for this user. - Error when lockout is not enabled - - - User {0} does not exist. - Error when a user does not exist - - - User is not in role '{0}'. - Error when a user is not in the role - - - Store does not implement IUserTwoFactorRecoveryCodeStore<User>. - Error when the store does not implement this interface - - - Passwords must use at least {0} different characters. - Error message for passwords that are based on similar characters - - - No RoleType was specified, try AddRoles<TRole>(). - Error when the IdentityBuilder.RoleType was not specified - - - Store does not implement IProtectedUserStore<TUser> which is required when ProtectPersonalData = true. - Error when the store does not implement this interface - - - No IPersonalDataProtector service was registered, this is required when ProtectPersonalData = true. - Error when there is no IPersonalDataProtector - - - + + Invalid request - + Request could not be processed because of conflict in the request - + Access to the requested resource is forbidden - + Request data is not valid - + An error occurred while communicating with server - + Your request lacks valid authentication credentials - + Unknown error has occurred - + The record was modified by another user after you got the original data. the operation was canceled. - - Known error - - + Resource not found - + Too many requests - - - Active - - - New category + + Unable to connect to server. - - New product - - - Create your multi-mode (WASM, Server, Hybrid, pre-rendering) Blazor Admin​Panel easily in the shortest time ever! - - - bit Admin​Panel - - - Admin​Panel + + + Active - + All - + Alphabetical - - Cancel - - - Categories - - + Completed - + Date - - Edit - - - Edit category - - - Edit product - - + Edit profile - + FullName - - Other - - - Female - - - Male + + Terms - - GitHub Repo + + Profile updated successfully. - - Last 30 days category count + + Confirm New Password - - Last 30 days product count + + Email - - Haven’t you received the confirmation email? + + Password - - I agree to the + + Your password changed successfully. - - Use this page to detail your site's privacy policy. + + The confirmation link has been re-sent to your email address. - - Use this section to detail your site's privacy policy. + + The reset password link has been sent to your email address. - - Privacy Policy + + User image could not be found - - Product sales + + Error - - This chart shows the sale number of each product. + + You have already requested the reset password email. Try again in {0} - - Products count per category chart + + Is accept terms? - - This chart shows the number of products in each category. + + You must agree to our terms. - - Products + + Gender - - Products percentage per category + + Birthdate - - This chart shows the percentage of products in each category. + + Name - - Profile updated successfully. + + Description - - Remove + + Price - - Save + + You must be signed in to continue. - - Select your birth date + + Invalid username or password - - Sort by + + You have already requested the confirmation email. Try again in {0} - - Admin​Panel is a project template that provides common features such as Sign-up & Sign-in. This template is powered by bit BlazorUI components. + + Your email is already confirmed. - - bit Admin​Panel + + Title - - Total categories + + Are you sure you want to delete {0} - - Total products + + Home - - Upload a new profile image + + User is locked out. Try again in {0} + + + User {0} does not exist. + + + Email '{0}' is already taken. + + + Add - + Already have an account? - - Check your Spam/Junk, if you could not find it in the Inbox. - - + We have sent a confirmation link to your email address. Please confirm your email by clicking on the link. - + Confirm Your Email Address - - Confirm New Password + + Delete Account + + + Are you sure you want to delete your account? - + Don’t have an account? - - Email + + Edit - + Email Confirmation Failed! - + Email Confirmation - + Email Confirmed Successfully! - - Forgot password? + + An error occurred while uploading file - + Please enter the email address you have been signed up with so we can send a reset password link to your email address. - - Forgot password + + Forget password + + + Male + + + Other + + + GitHub Repo + + + Go to today - + + Create your multi-mode (WASM, Server, Hybrid, pre-rendering) Blazor app easily in the shortest time ever! + + + Bit.TemplatePlayground Home + + Looks like the confirmation link either is invalid or has expired. - + New Password - - OR + + No - - Password + + Haven’t you received the confirmation email? - - Your password changed successfully. + + OR - - The confirmation link has been re-sent to your email address. + + Profile Image - - Resend email - - - Reset password + + Remove - - The reset password link has been sent to your email address. + + Resend email - + Reset password - - Sign in + + Save - + Sign in - + Sign out - + + Are you sure you want to Sign out? + + Sign up - + Sign up - + Submit - - An error occurred while removing file + + I agree to the - - An error occurred while uploading file - - - User image could not be found + + Upload a new profile image - - Category entity could not be found + + Yes - - Product entity could not be found + + Select your birth date - - This category contain some products, so you can't delete it + + Reset password - - Error + + Action - + Back - - Default color picker - - - Enter category name - - - You have already requested the reset password email. Try again in {0} - - - Add - - - Please enter the email address you have been signed up with so we can send a reset password link to your email address. - - - Forget password - - - Is accept privacy policy? - - - Username - - - You must agree to our privacy policy. - - - Gender - - - Privacy - - - Are you sure you want to Sign out? + + Cancel - - Birthdate + + Check your Spam/Junk, if you could not find it in the Inbox. - - Name + + Color - - Category + + Custom color - - Description + + Delete - - Price + + Id - - Go to today + + Sign in - + Sign in as different user - - You're signed in as - - - Made with + + You're sign in as - - No - - - Using bit platform! - - - Yes + + Forgot password? - - Are you sure you want to Sign out? + + Female - - Close + + Remember me? - - Enter product name + + Last 30 days product count - - Select a category + + Add product - + Select category - - Action - - - Custom color + + Total categories - - Delete + + Total products - - Search on name + + Edit category - - Add/Edit category + + New category - + Categories - - Edit profile - - - Home - - - Product category - - - Product catologue - - - Products - - - Color - - - Id + + Enter category name - - Image profile + + Enter product name - - Are you sure you want to delete category {0} + + Last 30 days category count - - Are you sure you want to delete product {0} + + Product sales - - Delete category + + This chart shows the sale number of each product. - - Delete product + + Search on name - - Submit + + Products count per category chart - - You must be signed in to continue. + + This chart shows the number of products in each category. - - Terms + + Products - - Invalid username or password + + Products percentage per category - - Delete Account + + This chart shows the percentage of products in each category. - - Are you sure you want to delete your account? + + Categories + + + Product category + + + Products + + + Product entity could not be found + + + This category contain some products, so you can't delete it + + + Are you sure you want to delete category {0} + + + Are you sure you want to delete product {0} + + + Delete category + + + Delete product + + + Category entity could not be found - - Update + + Category - - You have already requested the confirmation email. Try again in {0} + + Default color picker - - Your email is already confirmed. + + Edit product - - Incapable de se connecter au serveur. + + Dashboard \ No newline at end of file diff --git a/src/Shared/Resources/IdentityStrings.Designer.cs b/src/Shared/Resources/IdentityStrings.Designer.cs new file mode 100644 index 0000000..4745172 --- /dev/null +++ b/src/Shared/Resources/IdentityStrings.Designer.cs @@ -0,0 +1,513 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Bit.TemplatePlayground.Shared.Resources { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + public class IdentityStrings { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal IdentityStrings() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Bit.TemplatePlayground.Shared.Resources.IdentityStrings", typeof(IdentityStrings).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Optimistic concurrency failure, object has been modified.. + /// + public static string ConcurrencyFailure { + get { + return ResourceManager.GetString("ConcurrencyFailure", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An unknown failure has occurred.. + /// + public static string DefaultError { + get { + return ResourceManager.GetString("DefaultError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Email '{0}' is already taken.. + /// + public static string DuplicateEmail { + get { + return ResourceManager.GetString("DuplicateEmail", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Role name '{0}' is already taken.. + /// + public static string DuplicateRoleName { + get { + return ResourceManager.GetString("DuplicateRoleName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Username '{0}' is already taken.. + /// + public static string DuplicateUserName { + get { + return ResourceManager.GetString("DuplicateUserName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Email '{0}' is invalid.. + /// + public static string InvalidEmail { + get { + return ResourceManager.GetString("InvalidEmail", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Type {0} must derive from {1}<{2}>.. + /// + public static string InvalidManagerType { + get { + return ResourceManager.GetString("InvalidManagerType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The provided PasswordHasherCompatibilityMode is invalid.. + /// + public static string InvalidPasswordHasherCompatibilityMode { + get { + return ResourceManager.GetString("InvalidPasswordHasherCompatibilityMode", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The iteration count must be a positive integer.. + /// + public static string InvalidPasswordHasherIterationCount { + get { + return ResourceManager.GetString("InvalidPasswordHasherIterationCount", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Role name '{0}' is invalid.. + /// + public static string InvalidRoleName { + get { + return ResourceManager.GetString("InvalidRoleName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Invalid token.. + /// + public static string InvalidToken { + get { + return ResourceManager.GetString("InvalidToken", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Username '{0}' is invalid, can only contain letters or digits.. + /// + public static string InvalidUserName { + get { + return ResourceManager.GetString("InvalidUserName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A user with this login already exists.. + /// + public static string LoginAlreadyAssociated { + get { + return ResourceManager.GetString("LoginAlreadyAssociated", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to AddIdentity must be called on the service collection.. + /// + public static string MustCallAddIdentity { + get { + return ResourceManager.GetString("MustCallAddIdentity", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to No IPersonalDataProtector service was registered, this is required when ProtectPersonalData = true.. + /// + public static string NoPersonalDataProtector { + get { + return ResourceManager.GetString("NoPersonalDataProtector", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to No RoleType was specified, try AddRoles<TRole>().. + /// + public static string NoRoleType { + get { + return ResourceManager.GetString("NoRoleType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to No IUserTwoFactorTokenProvider<{0}> named '{1}' is registered.. + /// + public static string NoTokenProvider { + get { + return ResourceManager.GetString("NoTokenProvider", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to User security stamp cannot be null.. + /// + public static string NullSecurityStamp { + get { + return ResourceManager.GetString("NullSecurityStamp", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Incorrect password.. + /// + public static string PasswordMismatch { + get { + return ResourceManager.GetString("PasswordMismatch", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Passwords must have at least one digit ('0'-'9').. + /// + public static string PasswordRequiresDigit { + get { + return ResourceManager.GetString("PasswordRequiresDigit", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Passwords must have at least one lowercase ('a'-'z').. + /// + public static string PasswordRequiresLower { + get { + return ResourceManager.GetString("PasswordRequiresLower", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Passwords must have at least one non alphanumeric character.. + /// + public static string PasswordRequiresNonAlphanumeric { + get { + return ResourceManager.GetString("PasswordRequiresNonAlphanumeric", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Passwords must use at least {0} different characters.. + /// + public static string PasswordRequiresUniqueChars { + get { + return ResourceManager.GetString("PasswordRequiresUniqueChars", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Passwords must have at least one uppercase ('A'-'Z').. + /// + public static string PasswordRequiresUpper { + get { + return ResourceManager.GetString("PasswordRequiresUpper", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Passwords must be at least {0} characters.. + /// + public static string PasswordTooShort { + get { + return ResourceManager.GetString("PasswordTooShort", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Recovery code redemption failed.. + /// + public static string RecoveryCodeRedemptionFailed { + get { + return ResourceManager.GetString("RecoveryCodeRedemptionFailed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Role {0} does not exist.. + /// + public static string RoleNotFound { + get { + return ResourceManager.GetString("RoleNotFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Store does not implement IProtectedUserStore<TUser> which is required when ProtectPersonalData = true.. + /// + public static string StoreNotIProtectedUserStore { + get { + return ResourceManager.GetString("StoreNotIProtectedUserStore", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Store does not implement IQueryableRoleStore<TRole>.. + /// + public static string StoreNotIQueryableRoleStore { + get { + return ResourceManager.GetString("StoreNotIQueryableRoleStore", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Store does not implement IQueryableUserStore<TUser>.. + /// + public static string StoreNotIQueryableUserStore { + get { + return ResourceManager.GetString("StoreNotIQueryableUserStore", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Store does not implement IRoleClaimStore<TRole>.. + /// + public static string StoreNotIRoleClaimStore { + get { + return ResourceManager.GetString("StoreNotIRoleClaimStore", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Store does not implement IUserAuthenticationTokenStore<User>.. + /// + public static string StoreNotIUserAuthenticationTokenStore { + get { + return ResourceManager.GetString("StoreNotIUserAuthenticationTokenStore", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Store does not implement IUserAuthenticatorKeyStore<User>.. + /// + public static string StoreNotIUserAuthenticatorKeyStore { + get { + return ResourceManager.GetString("StoreNotIUserAuthenticatorKeyStore", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Store does not implement IUserClaimStore<TUser>.. + /// + public static string StoreNotIUserClaimStore { + get { + return ResourceManager.GetString("StoreNotIUserClaimStore", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Store does not implement IUserConfirmationStore<TUser>.. + /// + public static string StoreNotIUserConfirmationStore { + get { + return ResourceManager.GetString("StoreNotIUserConfirmationStore", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Store does not implement IUserEmailStore<TUser>.. + /// + public static string StoreNotIUserEmailStore { + get { + return ResourceManager.GetString("StoreNotIUserEmailStore", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Store does not implement IUserLockoutStore<TUser>.. + /// + public static string StoreNotIUserLockoutStore { + get { + return ResourceManager.GetString("StoreNotIUserLockoutStore", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Store does not implement IUserLoginStore<TUser>.. + /// + public static string StoreNotIUserLoginStore { + get { + return ResourceManager.GetString("StoreNotIUserLoginStore", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Store does not implement IUserPasswordStore<TUser>.. + /// + public static string StoreNotIUserPasswordStore { + get { + return ResourceManager.GetString("StoreNotIUserPasswordStore", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Store does not implement IUserPhoneNumberStore<TUser>.. + /// + public static string StoreNotIUserPhoneNumberStore { + get { + return ResourceManager.GetString("StoreNotIUserPhoneNumberStore", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Store does not implement IUserRoleStore<TUser>.. + /// + public static string StoreNotIUserRoleStore { + get { + return ResourceManager.GetString("StoreNotIUserRoleStore", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Store does not implement IUserSecurityStampStore<TUser>.. + /// + public static string StoreNotIUserSecurityStampStore { + get { + return ResourceManager.GetString("StoreNotIUserSecurityStampStore", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Store does not implement IUserTwoFactorRecoveryCodeStore<User>.. + /// + public static string StoreNotIUserTwoFactorRecoveryCodeStore { + get { + return ResourceManager.GetString("StoreNotIUserTwoFactorRecoveryCodeStore", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Store does not implement IUserTwoFactorStore<TUser>.. + /// + public static string StoreNotIUserTwoFactorStore { + get { + return ResourceManager.GetString("StoreNotIUserTwoFactorStore", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to User already has a password set.. + /// + public static string UserAlreadyHasPassword { + get { + return ResourceManager.GetString("UserAlreadyHasPassword", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to User already in role '{0}'.. + /// + public static string UserAlreadyInRole { + get { + return ResourceManager.GetString("UserAlreadyInRole", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to User is locked out.. + /// + public static string UserLockedOut { + get { + return ResourceManager.GetString("UserLockedOut", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Lockout is not enabled for this user.. + /// + public static string UserLockoutNotEnabled { + get { + return ResourceManager.GetString("UserLockoutNotEnabled", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to User {0} does not exist.. + /// + public static string UserNameNotFound { + get { + return ResourceManager.GetString("UserNameNotFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to User is not in role '{0}'.. + /// + public static string UserNotInRole { + get { + return ResourceManager.GetString("UserNotInRole", resourceCulture); + } + } + } +} diff --git a/src/Shared/Resources/IdentityStrings.fr.resx b/src/Shared/Resources/IdentityStrings.fr.resx new file mode 100644 index 0000000..93df6df --- /dev/null +++ b/src/Shared/Resources/IdentityStrings.fr.resx @@ -0,0 +1,186 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Le nom de rôle « {0} » est déjà pris. + + + Échec de concurrence optimiste, l'objet a été modifié. + + + Une panne inconnue s'est produite. + + + L'e-mail « {0} » est déjà pris. + + + Le nom d'utilisateur '{0}' est déjà pris. + + + L'e-mail « {0} » n'est pas valide. + + + Le nom de rôle « {0} » n'est pas valide. + + + Jeton invalide. + + + Le nom d'utilisateur '{0}' n'est pas valide et ne peut contenir que des lettres ou des chiffres. + + + Un utilisateur avec cette connexion existe déjà. + + + Mot de passe incorrect. + + + Les mots de passe doivent comporter au moins un chiffre (« 0 » - « 9 »). + + + Les mots de passe doivent comporter au moins une minuscule (« a » - « z »). + + + Les mots de passe doivent comporter au moins un caractère non alphanumérique. + + + Les mots de passe doivent comporter au moins une majuscule (« A » - « Z »). + + + Les mots de passe doivent comporter au moins {0} caractères. + + + L'utilisation du code de récupération a échoué. + + + L'utilisateur a déjà un mot de passe défini. + + + Utilisateur déjà dans le rôle « {0} ». + + + Le verrouillage n'est pas activé pour cet utilisateur. + + + L'utilisateur n'a pas le rôle « {0} ». + + + Les mots de passe doivent utiliser au moins {0} caractères différents. + + \ No newline at end of file diff --git a/src/Shared/Resources/IdentityStrings.resx b/src/Shared/Resources/IdentityStrings.resx new file mode 100644 index 0000000..c7dfef0 --- /dev/null +++ b/src/Shared/Resources/IdentityStrings.resx @@ -0,0 +1,208 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Optimistic concurrency failure, object has been modified. + Error when optimistic concurrency fails + + + An unknown failure has occurred. + Default identity result error message + + + Email '{0}' is already taken. + Error for duplicate emails + + + Role name '{0}' is already taken. + Error for duplicate roles + + + Username '{0}' is already taken. + Error for duplicate user names + + + Email '{0}' is invalid. + Invalid email + + + Role name '{0}' is invalid. + Error for invalid role names + + + Invalid token. + Error when a token is not recognized + + + Username '{0}' is invalid, can only contain letters or digits. + User names can only contain letters or digits + + + A user with this login already exists. + Error when a login already linked + + + Incorrect password. + Error when a password doesn't match + + + Passwords must have at least one digit ('0'-'9'). + Error when passwords do not have a digit + + + Passwords must have at least one lowercase ('a'-'z'). + Error when passwords do not have a lowercase letter + + + Passwords must have at least one non alphanumeric character. + Error when password does not have enough non alphanumeric characters + + + Passwords must have at least one uppercase ('A'-'Z'). + Error when passwords do not have an uppercase letter + + + Passwords must be at least {0} characters. + Error message for passwords that are too short + + + Recovery code redemption failed. + Error when a recovery code is not redeemed. + + + User already has a password set. + Error when AddPasswordAsync called when a user already has a password + + + User already in role '{0}'. + Error when a user is already in a role + + + Lockout is not enabled for this user. + Error when lockout is not enabled + + + User is not in role '{0}'. + Error when a user is not in the role + + + Passwords must use at least {0} different characters. + Error message for passwords that are based on similar characters + + \ No newline at end of file diff --git a/src/Shared/Services/Contracts/IAuthTokenProvider.cs b/src/Shared/Services/Contracts/IAuthTokenProvider.cs index 9941319..b64bfef 100644 --- a/src/Shared/Services/Contracts/IAuthTokenProvider.cs +++ b/src/Shared/Services/Contracts/IAuthTokenProvider.cs @@ -2,5 +2,6 @@ public interface IAuthTokenProvider { + bool IsInitialized { get; } Task GetAccessTokenAsync(); }
+ @EmailLocalizer.GetString(nameof(EmailStrings.ResetPasswordHello), Model.DisplayName!)