diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..354e5f2 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,155 @@ +############################### +# Core EditorConfig Options # +############################### + +root = true + +# All files +[*] +indent_style = space +indent_size = 2 + +# Code files +[*.{cs,csx,vb,vbx}] +indent_size = 2 +insert_final_newline = true +charset = utf-8-bom + +############################### +# .NET Coding Conventions # +############################### + +[*.{cs,vb}] +# Organize usings +dotnet_sort_system_directives_first = true +dotnet_separate_import_directive_groups = false + +# this. preferences +dotnet_style_qualification_for_field = false:silent +dotnet_style_qualification_for_property = false:silent +dotnet_style_qualification_for_method = false:silent +dotnet_style_qualification_for_event = false:silent + +# Language keywords vs BCL types preferences +dotnet_style_predefined_type_for_locals_parameters_members = true:silent +dotnet_style_predefined_type_for_member_access = true:silent + +# Parentheses preferences +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent + +# Modifier preferences +dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent +dotnet_style_readonly_field = true:suggestion + +# Expression-level preferences +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_auto_properties = true:silent +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent + +############################### +# Naming Conventions # +############################### + +# Style Definitions +dotnet_naming_style.pascal_case_style.capitalization = pascal_case + +# Use PascalCase for constant fields +dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields +dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style +dotnet_naming_symbols.constant_fields.applicable_kinds = field +dotnet_naming_symbols.constant_fields.applicable_accessibilities = * +dotnet_naming_symbols.constant_fields.required_modifiers = const + +dotnet_naming_rule.private_members_with_underscore.symbols = private_fields +dotnet_naming_rule.private_members_with_underscore.style = prefix_underscore +dotnet_naming_rule.private_members_with_underscore.severity = suggestion +dotnet_naming_symbols.private_fields.applicable_kinds = field +dotnet_naming_symbols.private_fields.applicable_accessibilities = private +dotnet_naming_style.prefix_underscore.capitalization = camel_case +dotnet_naming_style.prefix_underscore.required_prefix = _ + + +############################### +# C# Code Style Rules # +############################### + +[*.cs] +# var preferences +csharp_style_var_for_built_in_types = true:silent +csharp_style_var_when_type_is_apparent = true:silent +csharp_style_var_elsewhere = true:silent + +# Expression-bodied members +csharp_style_expression_bodied_methods = false:silent +csharp_style_expression_bodied_constructors = false:silent +csharp_style_expression_bodied_operators = false:silent +csharp_style_expression_bodied_properties = true:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_accessors = true:silent + +# Pattern-matching preferences +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion + +# Null-checking preferences +csharp_style_throw_expression = true:suggestion +csharp_style_conditional_delegate_call = true:suggestion + +# Modifier preferences +csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion + +# Expression-level preferences +csharp_prefer_braces = true:silent +csharp_style_deconstructed_variable_declaration = true:suggestion +csharp_prefer_simple_default_expression = true:suggestion +csharp_style_pattern_local_over_anonymous_function = true:suggestion +csharp_style_inlined_variable_declaration = true:suggestion + +############################### +# C# Formatting Rules # +############################### + +# New line preferences +csharp_new_line_before_open_brace = false +csharp_new_line_before_else = false +csharp_new_line_before_catch = false +csharp_new_line_before_finally = false +csharp_new_line_before_members_in_object_initializers = false +csharp_new_line_before_members_in_anonymous_types = false +csharp_new_line_between_query_expression_clauses = false + +# Indentation preferences +csharp_indent_case_contents = true +csharp_indent_switch_labels = true +csharp_indent_labels = flush_left + +# Space preferences +csharp_space_after_cast = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_around_binary_operators = before_and_after +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false + +# Wrapping preferences +csharp_preserve_single_line_statements = true +csharp_preserve_single_line_blocks = true + +csharp_nested_ternary_style = compact diff --git a/.gitignore b/.gitignore index 9491a2f..95c5523 100644 --- a/.gitignore +++ b/.gitignore @@ -360,4 +360,6 @@ MigrationBackup/ .ionide/ # Fody - auto-generated XML schema -FodyWeavers.xsd \ No newline at end of file +FodyWeavers.xsd + +.idea/ \ No newline at end of file diff --git a/Components/Account/Shared/ExternalLoginPicker.razor b/Components/Account/Shared/ExternalLoginPicker.razor index 713957d..7e26c7d 100644 --- a/Components/Account/Shared/ExternalLoginPicker.razor +++ b/Components/Account/Shared/ExternalLoginPicker.razor @@ -5,39 +5,67 @@ @inject SignInManager SignInManager @inject IdentityRedirectManager RedirectManager -@if (externalLogins.Length == 0) -{ +@if (externalLogins.Length == 0) { +
+

+ There are no external authentication services configured. See this + + article + about setting up this ASP.NET application to support logging in via external services + . +

+
+} else { +
-

- There are no external authentication services configured. See this article - about setting up this ASP.NET application to support logging in via external services. -

+ + +

+ @foreach (var provider in externalLogins) { + + } +

-} -else -{ - -
- - -

- @foreach (var provider in externalLogins) - { - - } -

-
-
+ } @code { - private AuthenticationScheme[] externalLogins = []; + private AuthenticationScheme[] externalLogins = []; - [SupplyParameterFromQuery] - private string? ReturnUrl { get; set; } + [SupplyParameterFromQuery] + private string? ReturnUrl { get; set; } - protected override async Task OnInitializedAsync() - { - externalLogins = (await SignInManager.GetExternalAuthenticationSchemesAsync()).ToArray(); - } -} + protected override async Task OnInitializedAsync() { + externalLogins = (await SignInManager.GetExternalAuthenticationSchemesAsync()).ToArray(); + } + +} \ No newline at end of file diff --git a/Data/ApplicationDbContext.cs b/Data/ApplicationDbContext.cs index 0751efc..04580d3 100644 --- a/Data/ApplicationDbContext.cs +++ b/Data/ApplicationDbContext.cs @@ -1,9 +1,7 @@ using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; -namespace Ferglie.Api.Data -{ - public class ApplicationDbContext(DbContextOptions options) : IdentityDbContext(options) - { - } +namespace Ferglie.Api.Data { + public class ApplicationDbContext(DbContextOptions options) + : IdentityDbContext(options) { } } diff --git a/Program.cs b/Program.cs index 847d6f8..688a94f 100644 --- a/Program.cs +++ b/Program.cs @@ -1,3 +1,5 @@ +using System.Net; +using System.Security.Cryptography.X509Certificates; using Ferglie.Api.Components; using Ferglie.Api.Components.Account; using Ferglie.Api.Data; @@ -6,45 +8,69 @@ using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; var builder = WebApplication.CreateBuilder(args); +builder.WebHost.ConfigureKestrel(options => { + var pemFile = builder.Configuration["SSL:PemFile"]; + var keyFile = builder.Configuration["SSL:KeyFile"]; + if (string.IsNullOrEmpty(pemFile) || string.IsNullOrEmpty(keyFile)) { + return; + } + + options.Listen(IPAddress.Any, 5001, listenOptions => { + var certPem = File.ReadAllText(pemFile); + var keyPem = File.ReadAllText(keyFile); + var x509 = X509Certificate2.CreateFromPem(certPem, keyPem); + listenOptions.UseHttps(x509); + }); +}); // Add services to the container. builder.Services.AddRazorComponents() - .AddInteractiveServerComponents(); + .AddInteractiveServerComponents(); builder.Services.AddCascadingAuthenticationState(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); -builder.Services.AddAuthentication(options => - { - options.DefaultScheme = IdentityConstants.ApplicationScheme; - options.DefaultSignInScheme = IdentityConstants.ExternalScheme; - }) - .AddIdentityCookies(); +builder.Services.AddAuthentication(options => { + options.DefaultScheme = IdentityConstants.ApplicationScheme; + options.DefaultSignInScheme = IdentityConstants.ExternalScheme; + options.RequireAuthenticatedSignIn = true; + }) + .AddGoogle(googleOptions => { + googleOptions.ClientId = builder.Configuration["Authentication:Google:ClientId"]; + googleOptions.ClientSecret = builder.Configuration["Authentication:Google:ClientSecret"]; + }) + .AddIdentityCookies(); -var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found."); +var connectionString = + builder.Configuration.GetConnectionString("DefaultConnection") ?? + throw new InvalidOperationException("Connection string 'DefaultConnection' not found."); builder.Services.AddDbContext(options => - options.UseSqlServer(connectionString)); + options.UseNpgsql(connectionString) + .UseSnakeCaseNamingConvention()); + builder.Services.AddDatabaseDeveloperPageExceptionFilter(); -builder.Services.AddIdentityCore(options => options.SignIn.RequireConfirmedAccount = true) - .AddEntityFrameworkStores() - .AddSignInManager() - .AddDefaultTokenProviders(); +builder.Services.AddIdentityCore(options => { + options.SignIn.RequireConfirmedAccount = false; + options.SignIn.RequireConfirmedEmail = false; + }) + .AddEntityFrameworkStores() + .AddSignInManager() + .AddDefaultTokenProviders(); builder.Services.AddSingleton, IdentityNoOpEmailSender>(); +builder.Services.Configure(options => options.LowercaseUrls = true); +builder.Services.AddControllers(); var app = builder.Build(); // Configure the HTTP request pipeline. -if (app.Environment.IsDevelopment()) -{ +if (app.Environment.IsDevelopment()) { app.UseMigrationsEndPoint(); -} -else -{ - app.UseExceptionHandler("/Error", createScopeForErrors: true); +} else { + app.UseExceptionHandler("/Error", true); // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts(); } @@ -55,7 +81,9 @@ app.UseStaticFiles(); app.UseAntiforgery(); app.MapRazorComponents() - .AddInteractiveServerRenderMode(); + .AddInteractiveServerRenderMode(); + +app.MapControllers(); // Add additional endpoints required by the Identity /Account Razor components. app.MapAdditionalIdentityEndpoints(); diff --git a/Properties/launchSettings.json b/Properties/launchSettings.json index 7448e62..86ff2d4 100644 --- a/Properties/launchSettings.json +++ b/Properties/launchSettings.json @@ -1,38 +1,15 @@ { "$schema": "http://json.schemastore.org/launchsettings.json", - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:22017", - "sslPort": 44398 - } - }, - "profiles": { - "http": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": true, - "applicationUrl": "http://localhost:5207", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "https": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": true, - "applicationUrl": "https://localhost:7178;http://localhost:5207", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } + "profiles": { + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "https://10.1.1.1:5001", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ROOT_X64": "/home/fergalm/.dotnet" } } } +} diff --git a/api.fergl.ie.csproj b/api.fergl.ie.csproj index f3b816b..5bfd5b0 100644 --- a/api.fergl.ie.csproj +++ b/api.fergl.ie.csproj @@ -10,10 +10,13 @@ + + + diff --git a/appsettings.Development.json b/appsettings.Development.json index 0c208ae..23ab61d 100644 --- a/appsettings.Development.json +++ b/appsettings.Development.json @@ -4,5 +4,9 @@ "Default": "Information", "Microsoft.AspNetCore": "Warning" } + }, + "SSL": { + "PemFile": "/etc/letsencrypt/live/dev.fergl.ie/fullchain.pem", + "KeyFile": "/etc/letsencrypt/live/dev.fergl.ie/privkey.pem" } } diff --git a/appsettings.json b/appsettings.json index 077b1c4..cf0c82f 100644 --- a/appsettings.json +++ b/appsettings.json @@ -1,6 +1,6 @@ { "ConnectionStrings": { - "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=aspnet-api.fergl.ie-b899f8de-d911-4922-9247-97db7392e48b;Trusted_Connection=True;MultipleActiveResultSets=true" + "DefaultConnection": "Host=localhost;Database=api.fergl.ie;Username=postgres;Password=hackme" }, "Logging": { "LogLevel": { diff --git a/scripts/reset.sh b/scripts/reset.sh new file mode 100755 index 0000000..507faf5 --- /dev/null +++ b/scripts/reset.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + +source $HOME/.prv/env + +reset_pg() { + export PGUSER=$PGUSER + export PGPASSWORD=$PGPASSWORD + export PGHOST=$PGHOST + export DBNAME="api.fergl.ie" + export ASPNETCORE_Environment=Development + + echo "Dropping db" + dropdb -f --if-exists ${DBNAME} + echo "Creating db" + createdb ${DBNAME} +} +echo Nuking existing +reset_pg + +rm -rf /srv/dev/sites/api.fergl.ie/api.fergl.ie/Data/Migrations/* +#read -p "Press enter to continue" + +cd /srv/dev/sites/api.fergl.ie/api.fergl.ie/ || exit + +dotnet ef migrations add "Initial" -o Data/Migrations/ +dotnet ef database update