Login done.

This commit is contained in:
Fergal Moran
2023-12-01 18:27:54 +00:00
parent 117f83ea8e
commit b2e98ea716
10 changed files with 310 additions and 89 deletions

155
.editorconfig Normal file
View File

@@ -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

2
.gitignore vendored
View File

@@ -361,3 +361,5 @@ MigrationBackup/
# Fody - auto-generated XML schema # Fody - auto-generated XML schema
FodyWeavers.xsd FodyWeavers.xsd
.idea/

View File

@@ -5,39 +5,67 @@
@inject SignInManager<ApplicationUser> SignInManager @inject SignInManager<ApplicationUser> SignInManager
@inject IdentityRedirectManager RedirectManager @inject IdentityRedirectManager RedirectManager
@if (externalLogins.Length == 0) @if (externalLogins.Length == 0) {
{ <div>
<p>
There are no external authentication services configured. See this
<a href="https://go.microsoft.com/fwlink/?LinkID=532715">
article
about setting up this ASP.NET application to support logging in via external services
</a>.
</p>
</div>
} else {
<form class="form-horizontal" action="account/performexternallogin" method="post">
<div> <div>
<p> <AntiforgeryToken/>
There are no external authentication services configured. See this <a href="https://go.microsoft.com/fwlink/?LinkID=532715">article <input type="hidden" name="ReturnUrl" value="@ReturnUrl"/>
about setting up this ASP.NET application to support logging in via external services</a>. <p>
</p> @foreach (var provider in externalLogins) {
<button
class="flex items-center bg-white border border-gray-300 rounded-lg shadow-md max-w-xs px-6 py-2 text-sm font-medium text-gray-800 hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500"
type="submit" name="provider" value="@provider.Name" title="Log in using your @provider.DisplayName account">
<svg class="h-6 w-6 mr-2" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="-0.5 0 48 48" version="1.1">
<g id="Icons" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Color-" transform="translate(-401.000000, -860.000000)">
<g id="Google" transform="translate(401.000000, 860.000000)">
<path
d="M9.82727273,24 C9.82727273,22.4757333 10.0804318,21.0144 10.5322727,19.6437333 L2.62345455,13.6042667 C1.08206818,16.7338667 0.213636364,20.2602667 0.213636364,24 C0.213636364,27.7365333 1.081,31.2608 2.62025,34.3882667 L10.5247955,28.3370667 C10.0772273,26.9728 9.82727273,25.5168 9.82727273,24"
id="Fill-1" fill="#FBBC05">
</path>
<path
d="M23.7136364,10.1333333 C27.025,10.1333333 30.0159091,11.3066667 32.3659091,13.2266667 L39.2022727,6.4 C35.0363636,2.77333333 29.6954545,0.533333333 23.7136364,0.533333333 C14.4268636,0.533333333 6.44540909,5.84426667 2.62345455,13.6042667 L10.5322727,19.6437333 C12.3545909,14.112 17.5491591,10.1333333 23.7136364,10.1333333"
id="Fill-2" fill="#EB4335">
</path>
<path
d="M23.7136364,37.8666667 C17.5491591,37.8666667 12.3545909,33.888 10.5322727,28.3562667 L2.62345455,34.3946667 C6.44540909,42.1557333 14.4268636,47.4666667 23.7136364,47.4666667 C29.4455,47.4666667 34.9177955,45.4314667 39.0249545,41.6181333 L31.5177727,35.8144 C29.3995682,37.1488 26.7323182,37.8666667 23.7136364,37.8666667"
id="Fill-3" fill="#34A853">
</path>
<path
d="M46.1454545,24 C46.1454545,22.6133333 45.9318182,21.12 45.6113636,19.7333333 L23.7136364,19.7333333 L23.7136364,28.8 L36.3181818,28.8 C35.6879545,31.8912 33.9724545,34.2677333 31.5177727,35.8144 L39.0249545,41.6181333 C43.3393409,37.6138667 46.1454545,31.6490667 46.1454545,24"
id="Fill-4" fill="#4285F4">
</path>
</g>
</g>
</g>
</svg>
<span>Continue with Google</span>
</button>
}
</p>
</div> </div>
} </form>
else
{
<form class="form-horizontal" action="Account/PerformExternalLogin" method="post">
<div>
<AntiforgeryToken />
<input type="hidden" name="ReturnUrl" value="@ReturnUrl" />
<p>
@foreach (var provider in externalLogins)
{
<button type="submit" class="btn btn-primary" name="provider" value="@provider.Name" title="Log in using your @provider.DisplayName account">@provider.DisplayName</button>
}
</p>
</div>
</form>
} }
@code { @code {
private AuthenticationScheme[] externalLogins = []; private AuthenticationScheme[] externalLogins = [];
[SupplyParameterFromQuery] [SupplyParameterFromQuery]
private string? ReturnUrl { get; set; } 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();
}
} }

View File

@@ -1,9 +1,7 @@
using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace Ferglie.Api.Data namespace Ferglie.Api.Data {
{ public class ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
public class ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : IdentityDbContext<ApplicationUser>(options) : IdentityDbContext<ApplicationUser>(options) { }
{
}
} }

View File

@@ -1,3 +1,5 @@
using System.Net;
using System.Security.Cryptography.X509Certificates;
using Ferglie.Api.Components; using Ferglie.Api.Components;
using Ferglie.Api.Components.Account; using Ferglie.Api.Components.Account;
using Ferglie.Api.Data; using Ferglie.Api.Data;
@@ -6,45 +8,69 @@ using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args); 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. // Add services to the container.
builder.Services.AddRazorComponents() builder.Services.AddRazorComponents()
.AddInteractiveServerComponents(); .AddInteractiveServerComponents();
builder.Services.AddCascadingAuthenticationState(); builder.Services.AddCascadingAuthenticationState();
builder.Services.AddScoped<IdentityUserAccessor>(); builder.Services.AddScoped<IdentityUserAccessor>();
builder.Services.AddScoped<IdentityRedirectManager>(); builder.Services.AddScoped<IdentityRedirectManager>();
builder.Services.AddScoped<AuthenticationStateProvider, IdentityRevalidatingAuthenticationStateProvider>(); builder.Services.AddScoped<AuthenticationStateProvider, IdentityRevalidatingAuthenticationStateProvider>();
builder.Services.AddAuthentication(options => builder.Services.AddAuthentication(options => {
{ options.DefaultScheme = IdentityConstants.ApplicationScheme;
options.DefaultScheme = IdentityConstants.ApplicationScheme; options.DefaultSignInScheme = IdentityConstants.ExternalScheme;
options.DefaultSignInScheme = IdentityConstants.ExternalScheme; options.RequireAuthenticatedSignIn = true;
}) })
.AddIdentityCookies(); .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<ApplicationDbContext>(options => builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString)); options.UseNpgsql(connectionString)
.UseSnakeCaseNamingConvention());
builder.Services.AddDatabaseDeveloperPageExceptionFilter(); builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddIdentityCore<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true) builder.Services.AddIdentityCore<ApplicationUser>(options => {
.AddEntityFrameworkStores<ApplicationDbContext>() options.SignIn.RequireConfirmedAccount = false;
.AddSignInManager() options.SignIn.RequireConfirmedEmail = false;
.AddDefaultTokenProviders(); })
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddSignInManager()
.AddDefaultTokenProviders();
builder.Services.AddSingleton<IEmailSender<ApplicationUser>, IdentityNoOpEmailSender>(); builder.Services.AddSingleton<IEmailSender<ApplicationUser>, IdentityNoOpEmailSender>();
builder.Services.Configure<RouteOptions>(options => options.LowercaseUrls = true);
builder.Services.AddControllers();
var app = builder.Build(); var app = builder.Build();
// Configure the HTTP request pipeline. // Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment()) if (app.Environment.IsDevelopment()) {
{
app.UseMigrationsEndPoint(); app.UseMigrationsEndPoint();
} } else {
else app.UseExceptionHandler("/Error", true);
{
app.UseExceptionHandler("/Error", createScopeForErrors: true);
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. // 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.UseHsts();
} }
@@ -55,7 +81,9 @@ app.UseStaticFiles();
app.UseAntiforgery(); app.UseAntiforgery();
app.MapRazorComponents<App>() app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode(); .AddInteractiveServerRenderMode();
app.MapControllers();
// Add additional endpoints required by the Identity /Account Razor components. // Add additional endpoints required by the Identity /Account Razor components.
app.MapAdditionalIdentityEndpoints(); app.MapAdditionalIdentityEndpoints();

View File

@@ -1,38 +1,15 @@
{ {
"$schema": "http://json.schemastore.org/launchsettings.json", "$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": { "profiles": {
"windowsAuthentication": false, "https": {
"anonymousAuthentication": true, "commandName": "Project",
"iisExpress": { "dotnetRunMessages": true,
"applicationUrl": "http://localhost:22017", "launchBrowser": false,
"sslPort": 44398 "applicationUrl": "https://10.1.1.1:5001",
} "environmentVariables": {
}, "ASPNETCORE_ENVIRONMENT": "Development",
"profiles": { "DOTNET_ROOT_X64": "/home/fergalm/.dotnet"
"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"
}
} }
} }
} }
}

View File

@@ -10,10 +10,13 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="EFCore.NamingConventions" Version="8.0.0-rc.2" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.Google" Version="8.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="8.0.0" /> <PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="8.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.0" /> <PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.0" /> <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.0" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.0" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -4,5 +4,9 @@
"Default": "Information", "Default": "Information",
"Microsoft.AspNetCore": "Warning" "Microsoft.AspNetCore": "Warning"
} }
},
"SSL": {
"PemFile": "/etc/letsencrypt/live/dev.fergl.ie/fullchain.pem",
"KeyFile": "/etc/letsencrypt/live/dev.fergl.ie/privkey.pem"
} }
} }

View File

@@ -1,6 +1,6 @@
{ {
"ConnectionStrings": { "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": { "Logging": {
"LogLevel": { "LogLevel": {

26
scripts/reset.sh Executable file
View File

@@ -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