From e64b8a91ad1a5acea64f7d2a51ac00618ad96876 Mon Sep 17 00:00:00 2001 From: Javier Calvarro Nelson Date: Fri, 17 Jan 2020 12:44:30 -0800 Subject: [PATCH] [Fixes #17014] Add an auth-enabled template for Blazor WASM projects (#18354) * [Fixes #17014] Add an auth-enabled template for Blazor WASM projects * The template enables auth using cookies. * Includes a UserController to return the user claims/data to the user * Includes an IdentityAuthenticationStateProvider that fetches the claims data from the application. * It requires a hosted project. --- eng/Versions.props | 4 + .../BlazorWasm-CSharp.Client.csproj.in | 1 + .../BlazorWasm-CSharp.Server.csproj.in | 17 ++ ...crosoft.AspNetCore.Blazor.Templates.csproj | 10 +- .../.template.config/dotnetcli.host.json | 28 +- .../.template.config/template.json | 181 ++++++++++++ .../.template.config/vs-2017.3.host.json | 24 ++ .../BlazorWasm-CSharp/Client/App.razor | 17 +- .../Client/Pages/FetchData.razor | 6 + .../HostAuthenticationStateProvider.cs | 56 ++++ .../LoginDisplay.IndividualLocalAuth.razor | 12 + .../Client/Shared/MainLayout.Auth.razor | 16 + ...inLayout.razor => MainLayout.NoAuth.razor} | 0 .../BlazorWasm-CSharp/Client/Startup.cs | 7 + .../BlazorWasm-CSharp/Client/_Imports.razor | 3 + .../Client/wwwroot/favicon.ico | Bin 0 -> 32038 bytes .../Identity/Pages/Account/LogOut.cshtml | 15 + .../Pages/Shared/_LoginPartial.cshtml | 27 ++ .../Server/Controllers/UserController.cs | 61 ++++ .../Controllers/WeatherForecastController.cs | 6 + .../Server/Data/ApplicationDbContext.cs | 16 + ...000000000_CreateIdentitySchema.Designer.cs | 270 +++++++++++++++++ .../00000000000000_CreateIdentitySchema.cs | 217 ++++++++++++++ .../ApplicationDbContextModelSnapshot.cs | 268 +++++++++++++++++ ...000000000_CreateIdentitySchema.Designer.cs | 277 ++++++++++++++++++ .../00000000000000_CreateIdentitySchema.cs | 220 ++++++++++++++ .../ApplicationDbContextModelSnapshot.cs | 275 +++++++++++++++++ .../BlazorWasm-CSharp/Server/Startup.cs | 61 +++- .../content/BlazorWasm-CSharp/Server/app.db | Bin 0 -> 106496 bytes .../Server/appsettings.Development.json | 9 + .../BlazorWasm-CSharp/Server/appsettings.json | 19 ++ .../Shared/Authorization/ClaimValue.cs | 18 ++ .../Shared/Authorization/UserInfo.cs | 19 ++ .../test/BlazorWasmTemplateTest.cs | 107 ++++++- 34 files changed, 2256 insertions(+), 11 deletions(-) create mode 100644 src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/Services/HostAuthenticationStateProvider.cs create mode 100644 src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/Shared/LoginDisplay.IndividualLocalAuth.razor create mode 100644 src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/Shared/MainLayout.Auth.razor rename src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/Shared/{MainLayout.razor => MainLayout.NoAuth.razor} (100%) create mode 100644 src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/wwwroot/favicon.ico create mode 100644 src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/Areas/Identity/Pages/Account/LogOut.cshtml create mode 100644 src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/Areas/Identity/Pages/Shared/_LoginPartial.cshtml create mode 100644 src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/Controllers/UserController.cs create mode 100644 src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/Data/ApplicationDbContext.cs create mode 100644 src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/Data/SqlLite/00000000000000_CreateIdentitySchema.Designer.cs create mode 100644 src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/Data/SqlLite/00000000000000_CreateIdentitySchema.cs create mode 100644 src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/Data/SqlLite/ApplicationDbContextModelSnapshot.cs create mode 100644 src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/Data/SqlServer/00000000000000_CreateIdentitySchema.Designer.cs create mode 100644 src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/Data/SqlServer/00000000000000_CreateIdentitySchema.cs create mode 100644 src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/Data/SqlServer/ApplicationDbContextModelSnapshot.cs create mode 100644 src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/app.db create mode 100644 src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/appsettings.Development.json create mode 100644 src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/appsettings.json create mode 100644 src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Shared/Authorization/ClaimValue.cs create mode 100644 src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Shared/Authorization/UserInfo.cs diff --git a/eng/Versions.props b/eng/Versions.props index d2e01e6ef5..e1dab327ee 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -204,6 +204,10 @@ 4.5.2 1.10.0 + 3.1.0 + 3.1.0 + 3.1.0 + 3.1.0 5.2.6 2.3.2 diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/BlazorWasm-CSharp.Client.csproj.in b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/BlazorWasm-CSharp.Client.csproj.in index 70927b08e3..3ef13b2f33 100644 --- a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/BlazorWasm-CSharp.Client.csproj.in +++ b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/BlazorWasm-CSharp.Client.csproj.in @@ -10,6 +10,7 @@ + diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/BlazorWasm-CSharp.Server.csproj.in b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/BlazorWasm-CSharp.Server.csproj.in index 5fe7473679..dd9965626c 100644 --- a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/BlazorWasm-CSharp.Server.csproj.in +++ b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/BlazorWasm-CSharp.Server.csproj.in @@ -13,4 +13,21 @@ + + + + + + + + + + + + + + + + + diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/Microsoft.AspNetCore.Blazor.Templates.csproj b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/Microsoft.AspNetCore.Blazor.Templates.csproj index 65457a000d..5ae2bfe6ca 100644 --- a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/Microsoft.AspNetCore.Blazor.Templates.csproj +++ b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/Microsoft.AspNetCore.Blazor.Templates.csproj @@ -20,9 +20,15 @@ MicrosoftAspNetCoreBlazorBuildPackageVersion=$(MicrosoftAspNetCoreBlazorBuildPackageVersion); MicrosoftAspNetCoreBlazorDevServerPackageVersion=$(MicrosoftAspNetCoreBlazorDevServerPackageVersion); MicrosoftAspNetCoreBlazorHttpClientPackageVersion=$(MicrosoftAspNetCoreBlazorHttpClientPackageVersion); - MonoWebAssemblyInteropPackageVersion=$(MonoWebAssemblyInteropPackageVersion); - MicrosoftEntityFrameworkCoreSqlServerPackageVersion=$(MicrosoftEntityFrameworkCoreSqlServerPackageVersion); MicrosoftAspNetCoreBlazorServerPackageVersion=$(MicrosoftAspNetCoreBlazorServerPackageVersion); + MicrosoftAspNetCoreComponentsAuthorizationPackageVersion=$(MicrosoftAspNetCoreComponentsAuthorizationPackageVersion); + MicrosoftAspNetCoreDiagnosticsEntityFrameworkCorePackageVersion=$(MicrosoftAspNetCoreDiagnosticsEntityFrameworkCorePackageVersion); + MicrosoftAspNetCoreIdentityEntityFrameworkCorePackageVersion=$(MicrosoftAspNetCoreIdentityEntityFrameworkCorePackageVersion); + MicrosoftAspNetCoreIdentityUIPackageVersion=$(MicrosoftAspNetCoreIdentityUIPackageVersion); + MicrosoftEntityFrameworkCoreSqlServerPackageVersion=$(MicrosoftEntityFrameworkCoreSqlServerPackageVersion); + MicrosoftEntityFrameworkCoreSqlitePackageVersion=$(MicrosoftEntityFrameworkCoreSqlitePackageVersion); + MicrosoftEntityFrameworkCoreToolsPackageVersion=$(MicrosoftEntityFrameworkCoreToolsPackageVersion); + MonoWebAssemblyInteropPackageVersion=$(MonoWebAssemblyInteropPackageVersion); diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/.template.config/dotnetcli.host.json b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/.template.config/dotnetcli.host.json index 4e89e1d2dc..38651820f2 100644 --- a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/.template.config/dotnetcli.host.json +++ b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/.template.config/dotnetcli.host.json @@ -5,11 +5,31 @@ "longName": "no-restore", "shortName": "" }, - "Hosted": { - "longName": "hosted" - }, + "Hosted": { + "longName": "hosted" + }, "Framework": { "longName": "framework" + }, + "UseLocalDB": { + "longName": "use-local-db" + }, + "HttpPort": { + "isHidden": true + }, + "HttpsPort": { + "isHidden": true + }, + "ExcludeLaunchSettings": { + "longName": "exclude-launch-settings", + "shortName": "" + }, + "UserSecretsId": { + "isHidden": true + }, + "NoHttps": { + "longName": "no-https", + "shortName": "" } } -} \ No newline at end of file +} diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/.template.config/template.json b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/.template.config/template.json index b6cb64fcec..d2e1735f7e 100644 --- a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/.template.config/template.json +++ b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/.template.config/template.json @@ -74,6 +74,82 @@ "exclude": [ "*.sln" ] + }, + { + "condition": "(!IndividualLocalAuth || UseLocalDB)", + "exclude": [ + "Server/app.db" + ] + }, + { + "condition": "(!IndividualLocalAuth)", + "exclude": [ + "Server/Data/SqlLite/**", + "Server/Data/SqlServer/**", + "Server/Data/ApplicationDbContext.cs", + "Server/Areas/**" + ] + }, + { + "condition": "(IndividualLocalAuth && UseLocalDB)", + "rename": { + "Server/Data/SqlServer/": "Server/Data/Migrations/" + }, + "exclude": [ + "Server/Data/SqlLite/**" + ] + }, + { + "condition": "(IndividualLocalAuth && !UseLocalDB)", + "rename": { + "Server/Data/SqlLite/": "Server/Data/Migrations/" + }, + "exclude": [ + "Server/Data/SqlServer/**" + ] + }, + { + "condition": "(NoAuth && Hosted)", + "rename": { + "Client/Shared/MainLayout.NoAuth.razor": "Client/Shared/MainLayout.razor" + }, + "exclude": [ + "Client/Shared/LoginDisplay.*.razor", + "Client/Shared/MainLayout.Auth.razor" + ] + }, + { + "condition": "(!Hosted)", + "rename": { + "Client/Shared/MainLayout.NoAuth.razor": "Shared/MainLayout.razor" + }, + "exclude": [ + "Client/Shared/LoginDisplay.*.razor", + "Client/Shared/MainLayout.Auth.razor" + ] + }, + { + "condition": "(!NoAuth && Hosted)", + "rename": { + "Client/Shared/MainLayout.Auth.razor": "Client/Shared/MainLayout.razor" + }, + "exclude": [ + "Client/Shared/MainLayout.NoAuth.razor" + ] + }, + { + "condition": "(IndividualLocalAuth)", + "rename": { + "Client/Shared/LoginDisplay.IndividualLocalAuth.razor": "Client/Shared/LoginDisplay.razor" + } + }, + { + "condition": "(NoAuth)", + "exclude": [ + "Client/Services/HostAuthenticationStateProvider.cs", + "Server/Controllers/UserController.cs", + "Shared/Authorization/*" + ] } ] } @@ -107,6 +183,111 @@ "datatype": "bool", "defaultValue": "false", "description": "If specified, includes an ASP.NET Core host for the Blazor app." + }, + "auth": { + "type": "parameter", + "datatype": "choice", + "choices": [ + { + "choice": "None", + "description": "No authentication" + }, + { + "choice": "Individual", + "description": "Individual authentication" + } + ], + "defaultValue": "None", + "description": "The type of authentication to use" + }, + "UserSecretsId": { + "type": "parameter", + "datatype": "string", + "replaces": "aspnet-BlazorServerWeb-CSharp-53bc9b9d-9d6a-45d4-8429-2a2761773502", + "defaultValue": "aspnet-BlazorServerWeb-CSharp-53bc9b9d-9d6a-45d4-8429-2a2761773502", + "description": "The ID to use for secrets (use with OrgReadAccess or Individual auth)." + }, + "ExcludeLaunchSettings": { + "type": "parameter", + "datatype": "bool", + "defaultValue": "false", + "description": "Whether to exclude launchSettings.json from the generated template." + }, + "HttpPort": { + "type": "parameter", + "datatype": "integer", + "description": "Port number to use for the HTTP endpoint in launchSettings.json." + }, + "HttpPortGenerated": { + "type": "generated", + "generator": "port" + }, + "HttpPortReplacer": { + "type": "generated", + "generator": "coalesce", + "parameters": { + "sourceVariableName": "HttpPort", + "fallbackVariableName": "HttpPortGenerated" + }, + "replaces": "8080" + }, + "HttpsPort": { + "type": "parameter", + "datatype": "integer", + "description": "Port number to use for the HTTPS endpoint in launchSettings.json. This option is only applicable when the parameter no-https is not used (no-https will be ignored if either IndividualAuth or OrganizationalAuth is used)." + }, + "HttpsPortGenerated": { + "type": "generated", + "generator": "port", + "parameters": { + "low": 44300, + "high": 44399 + } + }, + "HttpsPortReplacer": { + "type": "generated", + "generator": "coalesce", + "parameters": { + "sourceVariableName": "HttpsPort", + "fallbackVariableName": "HttpsPortGenerated" + }, + "replaces": "44300" + }, + "IndividualLocalAuth": { + "type": "computed", + "value": "(auth == \"Individual\")" + }, + "NoAuth": { + "type": "computed", + "value": "(!(IndividualAuth))" + }, + "IndividualAuth": { + "type": "computed", + "value": "(IndividualLocalAuth)" + }, + "RequiresHttps": { + "type": "computed", + "value": "(IndividualAuth || !NoHttps)" + }, + "NoHttps": { + "type": "parameter", + "datatype": "bool", + "defaultValue": "false", + "description": "Whether to turn off HTTPS. This option only applies if Individual, IndividualB2C, SingleOrg, or MultiOrg aren't used for --auth." + }, + "UseLocalDB": { + "type": "parameter", + "datatype": "bool", + "defaultValue": "false", + "description": "Whether to use LocalDB instead of SQLite. This option only applies if --auth Individual or --auth IndividualB2C is specified." + }, + "copyrightYear": { + "type": "generated", + "generator": "now", + "replaces": "copyrightYear", + "parameters": { + "format": "yyyy" + } } }, "tags": { diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/.template.config/vs-2017.3.host.json b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/.template.config/vs-2017.3.host.json index 5cb50d10a5..cbed84838b 100644 --- a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/.template.config/vs-2017.3.host.json +++ b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/.template.config/vs-2017.3.host.json @@ -18,6 +18,30 @@ "additionalWizardParameters": { "$isMultiProjectTemplate$": "true" }, + "supportedAuthentications": [ + { + "auth": "None", + "authenticationType": "NoAuth", + "allowUnsecured": true + }, + { + "auth": "Individual", + "authenticationType": "IndividualAuth", + "b2cAuthenticationOptions": "Local" + } + ], + "ports": [ + { + "name": "HttpPort", + "useHttps": false + }, + { + "name": "HttpsPort", + "useHttps": true + } + ], + "excludeLaunchSettings": false, + "disableHttpsSymbol": "NoHttps", "symbolInfo": [ { "id": "Hosted", diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/App.razor b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/App.razor index 1c360b7121..7b58ea096f 100644 --- a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/App.razor +++ b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/App.razor @@ -1,4 +1,5 @@ - +@*#if (NoAuth) + @@ -8,3 +9,17 @@ +#else + + + + + + + +

Sorry, there's nothing at this address.

+
+
+
+
+#endif*@ diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/Pages/FetchData.razor b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/Pages/FetchData.razor index 0faf18fefd..99cda40a53 100644 --- a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/Pages/FetchData.razor +++ b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/Pages/FetchData.razor @@ -1,7 +1,13 @@ @page "/fetchdata" +@*#if (!NoAuth) +@using Microsoft.AspNetCore.Authorization +#endif*@ @*#if (Hosted) @using BlazorWasm_CSharp.Shared #endif*@ +@*#if (!NoAuth) +@attribute [Authorize] +#endif*@ @inject HttpClient Http

Weather forecast

diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/Services/HostAuthenticationStateProvider.cs b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/Services/HostAuthenticationStateProvider.cs new file mode 100644 index 0000000000..efb2cd636d --- /dev/null +++ b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/Services/HostAuthenticationStateProvider.cs @@ -0,0 +1,56 @@ +using BlazorWasm_CSharp.Shared.Authorization; +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Authorization; +using System.Net.Http; +using System.Security.Claims; +using System.Threading.Tasks; + +namespace BlazorWasm_CSharp.Client +{ + public class HostAuthenticationStateProvider : AuthenticationStateProvider + { + private readonly HttpClient _client; + private ClaimsPrincipal _currentUser; + + public HostAuthenticationStateProvider(HttpClient client) + { + _client = client; + } + + public override async Task GetAuthenticationStateAsync() + { + if (_currentUser == null) + { + await FetchUser(); + } + + return new AuthenticationState(_currentUser); + } + + private async Task FetchUser() + { + var user = await _client.GetJsonAsync("User"); + + if (!user.IsAuthenticated) + { + _currentUser = new ClaimsPrincipal(new ClaimsIdentity()); + return; + } + + var identity = new ClaimsIdentity( + nameof(HostAuthenticationStateProvider), + user.NameClaimType, + user.RoleClaimType); + + if (user.Claims != null) + { + foreach (var claim in user.Claims) + { + identity.AddClaim(new Claim(claim.Type, claim.Value)); + } + } + + _currentUser = new ClaimsPrincipal(identity); + } + } +} diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/Shared/LoginDisplay.IndividualLocalAuth.razor b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/Shared/LoginDisplay.IndividualLocalAuth.razor new file mode 100644 index 0000000000..25f9db264b --- /dev/null +++ b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/Shared/LoginDisplay.IndividualLocalAuth.razor @@ -0,0 +1,12 @@ + + + Hello, @context.User.Identity.Name! +
+ +
+
+ + Register + Log in + +
diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/Shared/MainLayout.Auth.razor b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/Shared/MainLayout.Auth.razor new file mode 100644 index 0000000000..fafa2f55f1 --- /dev/null +++ b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/Shared/MainLayout.Auth.razor @@ -0,0 +1,16 @@ +@inherits LayoutComponentBase + + + +
+
+ + About +
+ +
+ @Body +
+
diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/Shared/MainLayout.razor b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/Shared/MainLayout.NoAuth.razor similarity index 100% rename from src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/Shared/MainLayout.razor rename to src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/Shared/MainLayout.NoAuth.razor diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/Startup.cs b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/Startup.cs index b30f14ae06..38fd10a30e 100644 --- a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/Startup.cs +++ b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/Startup.cs @@ -1,3 +1,6 @@ +#if (!NoAuth) +using Microsoft.AspNetCore.Components.Authorization; +#endif using Microsoft.AspNetCore.Components.Builder; using Microsoft.Extensions.DependencyInjection; @@ -11,6 +14,10 @@ namespace BlazorWasm_CSharp { public void ConfigureServices(IServiceCollection services) { +#if (IndividualLocalAuth) + services.AddAuthorizationCore(); + services.AddSingleton(); +#endif } public void Configure(IComponentsApplicationBuilder app) diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/_Imports.razor b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/_Imports.razor index 912a5070c2..fa501010b9 100644 --- a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/_Imports.razor +++ b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/_Imports.razor @@ -1,4 +1,7 @@ @using System.Net.Http +@*#if (!NoAuth) +@using Microsoft.AspNetCore.Components.Authorization +#endif*@ @using Microsoft.AspNetCore.Components.Forms @using Microsoft.AspNetCore.Components.Routing @using Microsoft.AspNetCore.Components.Web diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/wwwroot/favicon.ico b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/wwwroot/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..a3a799985c43bc7309d701b2cad129023377dc71 GIT binary patch literal 32038 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 SignInManager +@functions { + public async Task OnPost() + { + if (SignInManager.IsSignedIn(User)) + { + await SignInManager.SignOutAsync(); + } + + return Redirect("~/"); + } +} diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/Areas/Identity/Pages/Shared/_LoginPartial.cshtml b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/Areas/Identity/Pages/Shared/_LoginPartial.cshtml new file mode 100644 index 0000000000..a4f854aac3 --- /dev/null +++ b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/Areas/Identity/Pages/Shared/_LoginPartial.cshtml @@ -0,0 +1,27 @@ +@using Microsoft.AspNetCore.Identity +@inject SignInManager SignInManager +@inject UserManager UserManager +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers + + diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/Controllers/UserController.cs b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/Controllers/UserController.cs new file mode 100644 index 0000000000..6c27a4ad7c --- /dev/null +++ b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/Controllers/UserController.cs @@ -0,0 +1,61 @@ +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using BlazorWasm_CSharp.Shared.Authorization; + +namespace BlazorWasm_CSharp.Server.Controllers +{ + [ApiController] + public class UserController : ControllerBase + { + [HttpGet("/User")] + [Authorize] + [AllowAnonymous] + public IActionResult GetCurrentUser() => + Ok(User.Identity.IsAuthenticated ? CreateUserInfo(User) : UserInfo.Anonymous); + + private UserInfo CreateUserInfo(ClaimsPrincipal claimsPrincipal) + { + if (!claimsPrincipal.Identity.IsAuthenticated) + { + return UserInfo.Anonymous; + } + + var userInfo = new UserInfo(); + userInfo.IsAuthenticated = true; + + if (claimsPrincipal.Identity is ClaimsIdentity claimsIdentity) + { + userInfo.NameClaimType = claimsIdentity.NameClaimType; + userInfo.RoleClaimType = claimsIdentity.RoleClaimType; + } + else + { + userInfo.NameClaimType = "name"; + userInfo.RoleClaimType = "roles"; + } + + if (claimsPrincipal.Claims.Any()) + { + var claims = new List(); + var nameClaims = claimsPrincipal.FindAll(userInfo.NameClaimType); + foreach (var claim in nameClaims) + { + claims.Add(new ClaimValue(userInfo.NameClaimType, claim.Value)); + } + + // Uncomment this code if you want to send additional claims to the client. + //foreach (var claim in claimsPrincipal.Claims.Except(nameClaims)) + //{ + // claims.Add(new ClaimValue(claim.Type, claim.Value)); + //} + + userInfo.Claims = claims; + } + + return userInfo; + } + } +} diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/Controllers/WeatherForecastController.cs b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/Controllers/WeatherForecastController.cs index 6862c16a71..c6f2eb0ece 100644 --- a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/Controllers/WeatherForecastController.cs +++ b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/Controllers/WeatherForecastController.cs @@ -3,11 +3,17 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +#if (!NoAuth) +using Microsoft.AspNetCore.Authorization; +#endif using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; namespace BlazorWasm_CSharp.Server.Controllers { +#if (!NoAuth) + [Authorize] +#endif [ApiController] [Route("[controller]")] public class WeatherForecastController : ControllerBase diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/Data/ApplicationDbContext.cs b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/Data/ApplicationDbContext.cs new file mode 100644 index 0000000000..81390814c5 --- /dev/null +++ b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/Data/ApplicationDbContext.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + +namespace BlazorWasm_CSharp.Server.Data +{ + public class ApplicationDbContext : IdentityDbContext + { + public ApplicationDbContext(DbContextOptions options) + : base(options) + { + } + } +} diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/Data/SqlLite/00000000000000_CreateIdentitySchema.Designer.cs b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/Data/SqlLite/00000000000000_CreateIdentitySchema.Designer.cs new file mode 100644 index 0000000000..7bea3b5b34 --- /dev/null +++ b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/Data/SqlLite/00000000000000_CreateIdentitySchema.Designer.cs @@ -0,0 +1,270 @@ +// +using System; +using BlazorWasm_CSharp.Server.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace BlazorWasm_CSharp.Server.Data.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("00000000000000_CreateIdentitySchema")] + partial class CreateIdentitySchema + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "3.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT") + .HasMaxLength(256); + + b.Property("NormalizedName") + .HasColumnType("TEXT") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasName("RoleNameIndex"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Email") + .HasColumnType("TEXT") + .HasMaxLength(256); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasColumnType("TEXT") + .HasMaxLength(256); + + b.Property("NormalizedUserName") + .HasColumnType("TEXT") + .HasMaxLength(256); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasColumnType("TEXT") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasName("UserNameIndex"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT") + .HasMaxLength(128); + + b.Property("ProviderKey") + .HasColumnType("TEXT") + .HasMaxLength(128); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("LoginProvider") + .HasColumnType("TEXT") + .HasMaxLength(128); + + b.Property("Name") + .HasColumnType("TEXT") + .HasMaxLength(128); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/Data/SqlLite/00000000000000_CreateIdentitySchema.cs b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/Data/SqlLite/00000000000000_CreateIdentitySchema.cs new file mode 100644 index 0000000000..1df0c12752 --- /dev/null +++ b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/Data/SqlLite/00000000000000_CreateIdentitySchema.cs @@ -0,0 +1,217 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace BlazorWasm_CSharp.Server.Data.Migrations +{ + public partial class CreateIdentitySchema : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AspNetRoles", + columns: table => new + { + Id = table.Column(nullable: false), + Name = table.Column(maxLength: 256, nullable: true), + NormalizedName = table.Column(maxLength: 256, nullable: true), + ConcurrencyStamp = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetUsers", + columns: table => new + { + Id = table.Column(nullable: false), + UserName = table.Column(maxLength: 256, nullable: true), + NormalizedUserName = table.Column(maxLength: 256, nullable: true), + Email = table.Column(maxLength: 256, nullable: true), + NormalizedEmail = table.Column(maxLength: 256, nullable: true), + EmailConfirmed = table.Column(nullable: false), + PasswordHash = table.Column(nullable: true), + SecurityStamp = table.Column(nullable: true), + ConcurrencyStamp = table.Column(nullable: true), + PhoneNumber = table.Column(nullable: true), + PhoneNumberConfirmed = table.Column(nullable: false), + TwoFactorEnabled = table.Column(nullable: false), + LockoutEnd = table.Column(nullable: true), + LockoutEnabled = table.Column(nullable: false), + AccessFailedCount = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetRoleClaims", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + RoleId = table.Column(nullable: false), + ClaimType = table.Column(nullable: true), + ClaimValue = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserClaims", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + UserId = table.Column(nullable: false), + ClaimType = table.Column(nullable: true), + ClaimValue = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetUserClaims_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserLogins", + columns: table => new + { + LoginProvider = table.Column(maxLength: 128, nullable: false), + ProviderKey = table.Column(maxLength: 128, nullable: false), + ProviderDisplayName = table.Column(nullable: true), + UserId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); + table.ForeignKey( + name: "FK_AspNetUserLogins_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserRoles", + columns: table => new + { + UserId = table.Column(nullable: false), + RoleId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserTokens", + columns: table => new + { + UserId = table.Column(nullable: false), + LoginProvider = table.Column(maxLength: 128, nullable: false), + Name = table.Column(maxLength: 128, nullable: false), + Value = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + table.ForeignKey( + name: "FK_AspNetUserTokens_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AspNetRoleClaims_RoleId", + table: "AspNetRoleClaims", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "RoleNameIndex", + table: "AspNetRoles", + column: "NormalizedName", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserClaims_UserId", + table: "AspNetUserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserLogins_UserId", + table: "AspNetUserLogins", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserRoles_RoleId", + table: "AspNetUserRoles", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "EmailIndex", + table: "AspNetUsers", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "AspNetUsers", + column: "NormalizedUserName", + unique: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AspNetRoleClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserLogins"); + + migrationBuilder.DropTable( + name: "AspNetUserRoles"); + + migrationBuilder.DropTable( + name: "AspNetUserTokens"); + + migrationBuilder.DropTable( + name: "AspNetRoles"); + + migrationBuilder.DropTable( + name: "AspNetUsers"); + } + } +} diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/Data/SqlLite/ApplicationDbContextModelSnapshot.cs b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/Data/SqlLite/ApplicationDbContextModelSnapshot.cs new file mode 100644 index 0000000000..01098a9b9f --- /dev/null +++ b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/Data/SqlLite/ApplicationDbContextModelSnapshot.cs @@ -0,0 +1,268 @@ +// +using System; +using BlazorWasm_CSharp.Server.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace BlazorWasm_CSharp.Server.Data.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + partial class ApplicationDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "3.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT") + .HasMaxLength(256); + + b.Property("NormalizedName") + .HasColumnType("TEXT") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasName("RoleNameIndex"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Email") + .HasColumnType("TEXT") + .HasMaxLength(256); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasColumnType("TEXT") + .HasMaxLength(256); + + b.Property("NormalizedUserName") + .HasColumnType("TEXT") + .HasMaxLength(256); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasColumnType("TEXT") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasName("UserNameIndex"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT") + .HasMaxLength(128); + + b.Property("ProviderKey") + .HasColumnType("TEXT") + .HasMaxLength(128); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("LoginProvider") + .HasColumnType("TEXT") + .HasMaxLength(128); + + b.Property("Name") + .HasColumnType("TEXT") + .HasMaxLength(128); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/Data/SqlServer/00000000000000_CreateIdentitySchema.Designer.cs b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/Data/SqlServer/00000000000000_CreateIdentitySchema.Designer.cs new file mode 100644 index 0000000000..f1d1fc3f8c --- /dev/null +++ b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/Data/SqlServer/00000000000000_CreateIdentitySchema.Designer.cs @@ -0,0 +1,277 @@ +// +using System; +using BlazorWasm_CSharp.Server.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace BlazorWasm_CSharp.Server.Data.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("00000000000000_CreateIdentitySchema")] + partial class CreateIdentitySchema + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "3.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("NormalizedName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("NormalizedUserName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("Name") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/Data/SqlServer/00000000000000_CreateIdentitySchema.cs b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/Data/SqlServer/00000000000000_CreateIdentitySchema.cs new file mode 100644 index 0000000000..eb6f88d7cb --- /dev/null +++ b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/Data/SqlServer/00000000000000_CreateIdentitySchema.cs @@ -0,0 +1,220 @@ +using System; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace BlazorWasm_CSharp.Server.Data.Migrations +{ + public partial class CreateIdentitySchema : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AspNetRoles", + columns: table => new + { + Id = table.Column(nullable: false), + Name = table.Column(maxLength: 256, nullable: true), + NormalizedName = table.Column(maxLength: 256, nullable: true), + ConcurrencyStamp = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetUsers", + columns: table => new + { + Id = table.Column(nullable: false), + UserName = table.Column(maxLength: 256, nullable: true), + NormalizedUserName = table.Column(maxLength: 256, nullable: true), + Email = table.Column(maxLength: 256, nullable: true), + NormalizedEmail = table.Column(maxLength: 256, nullable: true), + EmailConfirmed = table.Column(nullable: false), + PasswordHash = table.Column(nullable: true), + SecurityStamp = table.Column(nullable: true), + ConcurrencyStamp = table.Column(nullable: true), + PhoneNumber = table.Column(nullable: true), + PhoneNumberConfirmed = table.Column(nullable: false), + TwoFactorEnabled = table.Column(nullable: false), + LockoutEnd = table.Column(nullable: true), + LockoutEnabled = table.Column(nullable: false), + AccessFailedCount = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetRoleClaims", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + RoleId = table.Column(nullable: false), + ClaimType = table.Column(nullable: true), + ClaimValue = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserClaims", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + UserId = table.Column(nullable: false), + ClaimType = table.Column(nullable: true), + ClaimValue = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetUserClaims_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserLogins", + columns: table => new + { + LoginProvider = table.Column(maxLength: 128, nullable: false), + ProviderKey = table.Column(maxLength: 128, nullable: false), + ProviderDisplayName = table.Column(nullable: true), + UserId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); + table.ForeignKey( + name: "FK_AspNetUserLogins_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserRoles", + columns: table => new + { + UserId = table.Column(nullable: false), + RoleId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserTokens", + columns: table => new + { + UserId = table.Column(nullable: false), + LoginProvider = table.Column(maxLength: 128, nullable: false), + Name = table.Column(maxLength: 128, nullable: false), + Value = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + table.ForeignKey( + name: "FK_AspNetUserTokens_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AspNetRoleClaims_RoleId", + table: "AspNetRoleClaims", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "RoleNameIndex", + table: "AspNetRoles", + column: "NormalizedName", + unique: true, + filter: "[NormalizedName] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserClaims_UserId", + table: "AspNetUserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserLogins_UserId", + table: "AspNetUserLogins", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserRoles_RoleId", + table: "AspNetUserRoles", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "EmailIndex", + table: "AspNetUsers", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "AspNetUsers", + column: "NormalizedUserName", + unique: true, + filter: "[NormalizedUserName] IS NOT NULL"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AspNetRoleClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserLogins"); + + migrationBuilder.DropTable( + name: "AspNetUserRoles"); + + migrationBuilder.DropTable( + name: "AspNetUserTokens"); + + migrationBuilder.DropTable( + name: "AspNetRoles"); + + migrationBuilder.DropTable( + name: "AspNetUsers"); + } + } +} diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/Data/SqlServer/ApplicationDbContextModelSnapshot.cs b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/Data/SqlServer/ApplicationDbContextModelSnapshot.cs new file mode 100644 index 0000000000..07026c1264 --- /dev/null +++ b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/Data/SqlServer/ApplicationDbContextModelSnapshot.cs @@ -0,0 +1,275 @@ +// +using System; +using BlazorWasm_CSharp.Server.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace BlazorWasm_CSharp.Server.Data.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + partial class ApplicationDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "3.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("NormalizedName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("NormalizedUserName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("Name") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/Startup.cs b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/Startup.cs index fc9fe04212..fd1767bd47 100644 --- a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/Startup.cs +++ b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/Startup.cs @@ -1,18 +1,53 @@ using Microsoft.AspNetCore.Builder; +#if (IndividualLocalAuth) +using Microsoft.AspNetCore.Components.Authorization; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Identity.UI; +#endif +#if (RequiresHttps) +using Microsoft.AspNetCore.HttpsPolicy; +#endif using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.ResponseCompression; +#if (IndividualLocalAuth) +using Microsoft.EntityFrameworkCore; +#endif +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using System.Linq; +#if (IndividualLocalAuth) +using BlazorWasm_CSharp.Server.Data; +#endif namespace BlazorWasm_CSharp.Server { public class Startup { + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + public IConfiguration Configuration { get; } + // This method gets called by the runtime. Use this method to add services to the container. // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 public void ConfigureServices(IServiceCollection services) { +#if (IndividualLocalAuth) + services.AddDbContext(options => +#if (UseLocalDB) + options.UseSqlServer( + Configuration.GetConnectionString("DefaultConnection"))); +#else + options.UseSqlite( + Configuration.GetConnectionString("DefaultConnection"))); +#endif + services.AddDefaultIdentity(options => options.SignIn.RequireConfirmedAccount = true) + .AddEntityFrameworkStores(); + +#endif services.AddMvc(); services.AddResponseCompression(opts => { @@ -29,17 +64,41 @@ namespace BlazorWasm_CSharp.Server if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); +#if (IndividualLocalAuth) + app.UseDatabaseErrorPage(); +#endif app.UseBlazorDebugging(); } + else + { + app.UseExceptionHandler("/Error"); +#if (RequiresHttps) + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + app.UseHsts(); + } + app.UseHttpsRedirection(); +#else + } + +#endif app.UseStaticFiles(); app.UseClientSideBlazorFiles(); app.UseRouting(); +#if (IndividualLocalAuth) + app.UseAuthentication(); + app.UseAuthorization(); + +#endif app.UseEndpoints(endpoints => { - endpoints.MapDefaultControllerRoute(); +#if (IndividualLocalAuth) + endpoints.MapRazorPages(); +#endif + endpoints.MapControllers(); + endpoints.MapFallbackToClientSideBlazor("index.html"); }); } diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/app.db b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/app.db new file mode 100644 index 0000000000000000000000000000000000000000..ec163057f1f5433ac77fa2fea05aa4a544e6ded0 GIT binary patch literal 106496 zcmeI)UvJyi83$m|b|g!-Eay+dt-w&02G$6+i)2IL6- zL`S5Yx5y532 zGl}(oKf6FT&iz2Q`C0CVrLPtbvtOpaUwnW5^ZB*p^SN)6y_v7)p3JDicS0fY&A6^& z)(?csDY39F4Bj@oE#nh)zfrIcHN)Anjb7QhZ+7hleXF*jtFIKRN};Yus4(^0Ba74@d(_3Xu%m#RuhsVZ7gsrjetMRH9h=a8q>yrq;CQu@V0tytJn zUM2!s@_seT5+8v-9AYAHccZzCk06`Le~?Rxw^xOPTRQfmHkpwcdf&0!<)~`oMkLG2 zr>l!8aeZBQnvT>2n*)N)*>ZMVRj}ruEZpVYo^@olj9z>J$bR#-aXev*Ez>@1>&I>x zyT#-em`C?=1?I=fR~R2LDYS5S`ANBEeTKYWNwPPyNwKy*IoVz`p=`4?OD9JQZn~em z6-hUn>VkB$<;8L79?qlQK&OM8jAZ4eQXNhLQg~=`Bi!FRoPfe!y`pO5csq(#_tWcc zSU)~ASbcD-@96D*Wc_)oftx2-Ft0juyO|`NdJkN7Z^{=OJN#92AUW*d zLFjK-YiRw>UH|IiA1~B!j!HyNRUcbUjw?ad9E|w0 zrcIf@EXIyUIY`WK^7pw%XX)_h<*o{TSfN7aKKSvGkPEbX*gWyvOrGzu$c_+3WouFc zZPp#qC(V#7ol!hg_w8Jqr`r-xj!aqcL)zObbf&)7+2b^T_O}m4NYF+rY;JOexk30C z?!tw^zK{|(HiVOk>(FQ@r5*ErPj^hKYu_?$QtijFD`$9{*lhx(VV@JZ#&i?|M?S5! z`c3B!`It?Dja)d0h&q!LKi(KO=330u|DWOn@&oRyr0uX=z1Rwwb2tWV=5P$##Ab{`xr~wE-00Izz00bZa0SG_<0uX?} z^b6qo|Mce=X9xiZKmY;|fB*y_009U<00I!e_kYv?1Rwwb2tWV=5P$##AOHafKw$a> z@cn=KbBr^D00bZa0SG_<0uX=z1Rwwb2xN0F5(~-aiKV~i{+j*U(pQUz*)P-IFTOwj z`TSb)`P{e3-pto?Pi9o%JE4&HChT&V?2G&Xxy_=-uxa<2 z)$8bO^HZZmyOZ;|7jv`X>Zx#BYF^J?jCrZ5l$5HX6_uKQy6$dNlAJ@H4yY}qtdPN1EYymHE#+k* zpe65Dvn=ru_`@M40(ZCZ5o9y@4{}NI_NtI@OUHiHHXWm(_Z`b!j;c0pM6$e`dm;Qw zTwfQSrXw}MMr*J+Th5NFis9h)GVd<$_N*hbW%S|;K=zxrjpGShY?=09TR(Qo*exc% zz&yH_D=oes2N|L>qO^UVk$;tMj31yqDSvomdNYQjZc`K4` zHq`~`X3LA?(mk9!z-PDZkFQ>hLo0VzB*xe@N~9Zo=DuU=6#a=aZytNZD7H>@8Y z8mvCJ)pzuEKeGP3)xgaYESOiFx!p{XPQ3>%yEja2<5Az}HjT*Q0*4-Hyw)XC`5M_X ze{z%_O(mVN*a@*z2Fn?)SMQTS&`jo-mriAP@1HuAwD0p=IfkhWf_bHLKACa9*i5Cw zl@;Nc5*b5wFnsL!IBtJ74@q;E&G93KT+c(o{x~Q!N%7bv-E14JqSfy@ql3`jv}kpk z{a%lh+i}g&JBR#$QaYrMm{nx2duFd=#Efpgs%e&?=#_3@7vYB(FQ@r5*ErPj^hKYu_?$QtijFD`$9{*lhx(VV@JZ#&i?|M?S5!`c3B!`It?D zWs||M8i|NHlN3MR7&qo(kJGpjyLy&OzIRBf(Qg!L#;%^uJv+(DIOPN1qGU-327w7*%8t{Su1Rwwb2tWV=p1`5V z9`{;tpV9H}4u&50qO0sTy;~}KntFS0_`n~xb@-_-cOP<#CG^ynzpb4A!_}1dHhI7- z?H3?oO*qDLjiAeG-z6YjRKcb$u4|M?ao-y1M8-;en?yM;`Y0 zKYSV!_y6&c4gwH>00bZa0SG_<0uX=z1RyZ|0{H$v{W-=NLI45~fB*y_009U<00Izz H00jOE{cSD9 literal 0 HcmV?d00001 diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/appsettings.Development.json b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/appsettings.Development.json new file mode 100644 index 0000000000..8983e0fc1c --- /dev/null +++ b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/appsettings.json b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/appsettings.json new file mode 100644 index 0000000000..fbfae3faee --- /dev/null +++ b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/appsettings.json @@ -0,0 +1,19 @@ +{ +////#if (IndividualLocalAuth) +// "ConnectionStrings": { +////#if (UseLocalDB) +// "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=aspnet-BlazorServerWeb-CSharp-53bc9b9d-9d6a-45d4-8429-2a2761773502;Trusted_Connection=True;MultipleActiveResultSets=true" +////#else +// "DefaultConnection": "DataSource=app.db" +////#endif +// }, +////#endif + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "AllowedHosts": "*" +} diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Shared/Authorization/ClaimValue.cs b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Shared/Authorization/ClaimValue.cs new file mode 100644 index 0000000000..de28f359b4 --- /dev/null +++ b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Shared/Authorization/ClaimValue.cs @@ -0,0 +1,18 @@ +namespace BlazorWasm_CSharp.Shared.Authorization +{ + public class ClaimValue + { + public ClaimValue() + { + } + + public ClaimValue(string type, string value) + { + Type = type; + Value = value; + } + + public string Type { get; set; } + public string Value { get; set; } + } +} diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Shared/Authorization/UserInfo.cs b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Shared/Authorization/UserInfo.cs new file mode 100644 index 0000000000..c21d8b50d6 --- /dev/null +++ b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Shared/Authorization/UserInfo.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; + +namespace BlazorWasm_CSharp.Shared.Authorization +{ + public class UserInfo + { + public static readonly UserInfo Anonymous = new UserInfo(); + + public bool IsAuthenticated { get; set; } + + public string NameClaimType { get; set; } + + public string RoleClaimType { get; set; } + + public ICollection Claims { get; set; } + } +} diff --git a/src/ProjectTemplates/test/BlazorWasmTemplateTest.cs b/src/ProjectTemplates/test/BlazorWasmTemplateTest.cs index 41cb82ac21..238385bf17 100644 --- a/src/ProjectTemplates/test/BlazorWasmTemplateTest.cs +++ b/src/ProjectTemplates/test/BlazorWasmTemplateTest.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.IO; using System.Net; using System.Threading; @@ -90,7 +91,64 @@ namespace Templates.Test } } - protected async Task BuildAndRunTest(string appName, Project project) + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task BlazorWasmHostedTemplate_IndividualAuth_Works(bool useLocalDb) + { + var project = await ProjectFactory.GetOrCreateProject("blazorhostedindividual" + (useLocalDb ? "uld" : ""), Output); + + var createResult = await project.RunDotNetNewAsync("blazorwasm", args: new[] { "--hosted", "-au", "Individual", useLocalDb ? "-uld" : "" }); + Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", project, createResult)); + + var serverProject = GetSubProject(project, "Server", $"{project.ProjectName}.Server"); + + var serverProjectFileContents = ReadFile(serverProject.TemplateOutputDir, $"{serverProject.ProjectName}.csproj"); + if (!useLocalDb) + { + Assert.Contains(".db", serverProjectFileContents); + } + var publishResult = await serverProject.RunDotNetPublishAsync(); + Assert.True(0 == publishResult.ExitCode, ErrorMessages.GetFailedProcessMessage("publish", serverProject, publishResult)); + + // Run dotnet build after publish. The reason is that one uses Config = Debug and the other uses Config = Release + // The output from publish will go into bin/Release/netcoreappX.Y/publish and won't be affected by calling build + // later, while the opposite is not true. + + var buildResult = await serverProject.RunDotNetBuildAsync(); + Assert.True(0 == buildResult.ExitCode, ErrorMessages.GetFailedProcessMessage("build", serverProject, buildResult)); + + var migrationsResult = await serverProject.RunDotNetEfCreateMigrationAsync("blazorwasm"); + Assert.True(0 == migrationsResult.ExitCode, ErrorMessages.GetFailedProcessMessage("run EF migrations", serverProject, migrationsResult)); + serverProject.AssertEmptyMigration("blazorwasm"); + + if (useLocalDb) + { + using var dbUpdateResult = await serverProject.RunDotNetEfUpdateDatabaseAsync(); + Assert.True(0 == dbUpdateResult.ExitCode, ErrorMessages.GetFailedProcessMessage("update database", serverProject, dbUpdateResult)); + } + + await BuildAndRunTest(project.ProjectName, serverProject, usesAuth: true); + + using var aspNetProcess = serverProject.StartPublishedProjectAsync(); + + Assert.False( + aspNetProcess.Process.HasExited, + ErrorMessages.GetFailedProcessMessageOrEmpty("Run published project", serverProject, aspNetProcess.Process)); + + await aspNetProcess.AssertStatusCode("/", HttpStatusCode.OK, "text/html"); + if (BrowserFixture.IsHostAutomationSupported()) + { + aspNetProcess.VisitInBrowser(Browser); + TestBasicNavigation(project.ProjectName, usesAuth: true); + } + else + { + BrowserFixture.EnforceSupportedConfigurations(); + } + } + + protected async Task BuildAndRunTest(string appName, Project project, bool usesAuth = false) { using var aspNetProcess = project.StartBuiltProjectAsync(); @@ -102,7 +160,7 @@ namespace Templates.Test if (BrowserFixture.IsHostAutomationSupported()) { aspNetProcess.VisitInBrowser(Browser); - TestBasicNavigation(appName); + TestBasicNavigation(appName, usesAuth); } else { @@ -110,7 +168,7 @@ namespace Templates.Test } } - private void TestBasicNavigation(string appName) + private void TestBasicNavigation(string appName, bool usesAuth = false) { // Give components.server enough time to load so that it can replace // the prerendered content before we start making assertions. @@ -133,6 +191,40 @@ namespace Templates.Test Browser.FindElement(By.CssSelector("p+button")).Click(); Browser.Equal("Current count: 1", () => Browser.FindElement(By.CssSelector("h1 + p")).Text); + if (usesAuth) + { + Browser.FindElement(By.PartialLinkText("Log in")).Click(); + Browser.Contains("/Identity/Account/Login", () => Browser.Url); + + Browser.FindElement(By.PartialLinkText("Register as a new user")).Click(); + + var userName = $"{Guid.NewGuid()}@example.com"; + var password = $"!Test.Password1$"; + Browser.Exists(By.Name("Input.Email")); + Browser.FindElement(By.Name("Input.Email")).SendKeys(userName); + Browser.FindElement(By.Name("Input.Password")).SendKeys(password); + Browser.FindElement(By.Name("Input.ConfirmPassword")).SendKeys(password); + Browser.FindElement(By.Id("registerSubmit")).Click(); + + // We will be redirected to the RegisterConfirmation + Browser.Contains("/Identity/Account/RegisterConfirmation", () => Browser.Url); + Browser.FindElement(By.PartialLinkText("Click here to confirm your account")).Click(); + + // We will be redirected to the ConfirmEmail + Browser.Contains("/Identity/Account/ConfirmEmail", () => Browser.Url); + + // Now we can login + Browser.FindElement(By.PartialLinkText("Login")).Click(); + Browser.Exists(By.Name("Input.Email")); + Browser.FindElement(By.Name("Input.Email")).SendKeys(userName); + Browser.FindElement(By.Name("Input.Password")).SendKeys(password); + Browser.FindElement(By.Id("login-submit")).Click(); + + // Need to navigate to fetch page + Browser.Navigate().GoToUrl(new Uri(Browser.Url).GetLeftPart(UriPartial.Authority)); + Browser.Equal(appName.Trim(), () => Browser.Title.Trim()); + } + // Can navigate to the 'fetch data' page Browser.FindElement(By.PartialLinkText("Fetch data")).Click(); Browser.Contains("fetchdata", () => Browser.Url); @@ -143,6 +235,15 @@ namespace Templates.Test Browser.Equal(5, () => Browser.FindElements(By.CssSelector("p+table>tbody>tr")).Count); } + private string ReadFile(string basePath, string path) + { + var fullPath = Path.Combine(basePath, path); + var doesExist = File.Exists(fullPath); + + Assert.True(doesExist, $"Expected file to exist, but it doesn't: {path}"); + return File.ReadAllText(Path.Combine(basePath, path)); + } + private Project GetSubProject(Project project, string projectDirectory, string projectName) { var subProjectDirectory = Path.Combine(project.TemplateOutputDir, projectDirectory);