Convert to Blazor (#6)

Convert existing progress to Blazor on client and server
This commit was merged in pull request #6.
This commit is contained in:
2020-12-18 21:46:28 -05:00
committed by GitHub
parent 2f84821d11
commit e0b3fa8759
97 changed files with 2592 additions and 31063 deletions

View File

@@ -0,0 +1,65 @@
using JobsJobsJobs.Server.Data;
using JobsJobsJobs.Shared;
using JobsJobsJobs.Shared.Api;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using NodaTime;
using Npgsql;
using System.Threading.Tasks;
namespace JobsJobsJobs.Server.Areas.Api.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class CitizenController : ControllerBase
{
private readonly IConfigurationSection _config;
private readonly IClock _clock;
private readonly NpgsqlConnection _db;
public CitizenController(IConfiguration config, IClock clock, NpgsqlConnection db)
{
_config = config.GetSection("Auth");
_clock = clock;
_db = db;
}
[HttpGet("log-on/{authCode}")]
public async Task<IActionResult> LogOn([FromRoute] string authCode)
{
// Step 1 - Verify with Mastodon
var accountResult = await Auth.VerifyWithMastodon(authCode, _config);
if (accountResult.IsError) return BadRequest(accountResult.Error);
// Step 2 - Find / establish Jobs, Jobs, Jobs account
var account = accountResult.Ok;
var now = _clock.GetCurrentInstant();
await _db.OpenAsync();
var citizen = await _db.FindCitizenByNAUser(account.Username);
if (citizen == null)
{
citizen = new Citizen(await CitizenId.Create(), account.Username, account.DisplayName, account.Url,
now, now);
await _db.AddCitizen(citizen);
}
else
{
citizen = citizen with
{
DisplayName = account.DisplayName,
LastSeenOn = now
};
await _db.UpdateCitizenOnLogOn(citizen);
}
// Step 3 - Generate JWT
var jwt = Auth.CreateJwt(citizen, _config);
return new JsonResult(new LogOnSuccess(jwt, citizen.Id.ToString(), citizen.DisplayName));
}
}
}

View File

@@ -0,0 +1,40 @@
using JobsJobsJobs.Server.Data;
using JobsJobsJobs.Shared;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Npgsql;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
namespace JobsJobsJobs.Server.Areas.Api.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class ProfileController : ControllerBase
{
/// <summary>
/// The database connection
/// </summary>
private readonly NpgsqlConnection db;
public ProfileController(NpgsqlConnection dbConn)
{
db = dbConn;
}
[Authorize]
[HttpGet("")]
public async Task<IActionResult> Get()
{
await db.OpenAsync();
var profile = await db.FindProfileByCitizen(
CitizenId.Parse(User.FindFirst(ClaimTypes.NameIdentifier)!.Value));
return profile == null ? NoContent() : Ok(profile);
}
}
}

View File

@@ -0,0 +1,112 @@
using JobsJobsJobs.Server.Models;
using JobsJobsJobs.Shared;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Security.Claims;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
namespace JobsJobsJobs.Server
{
/// <summary>
/// Authentication / authorization utility methods
/// </summary>
public static class Auth
{
/// <summary>
/// Verify the authorization code with Mastodon and get the user's profile
/// </summary>
/// <param name="authCode">The code from the authorization flow</param>
/// <param name="config">The authorization configuration section</param>
/// <returns>The Mastodon account (or an error if one is encountered)</returns>
public static async Task<Result<MastodonAccount>> VerifyWithMastodon(string authCode,
IConfigurationSection config)
{
using var http = new HttpClient();
// Use authorization code to get an access token from NAS
using var codeResult = await http.PostAsJsonAsync("https://noagendasocial.com/oauth/token", new
{
client_id = config["ClientId"],
client_secret = config["Secret"],
redirect_uri = "https://localhost:3005/citizen/authorized",
grant_type = "authorization_code",
code = authCode,
scope = "read"
});
if (!codeResult.IsSuccessStatusCode)
{
Console.WriteLine($"ERR: {await codeResult.Content.ReadAsStringAsync()}");
return Result<MastodonAccount>.AsError(
$"Could not get token ({codeResult.StatusCode:D}: {codeResult.ReasonPhrase})");
}
using var tokenResponse = JsonSerializer.Deserialize<JsonDocument>(
new ReadOnlySpan<byte>(await codeResult.Content.ReadAsByteArrayAsync()));
if (tokenResponse == null)
{
return Result<MastodonAccount>.AsError("Could not parse authorization code result");
}
var accessToken = tokenResponse.RootElement.GetProperty("access_token").GetString();
// Use access token to get profile from NAS
using var req = new HttpRequestMessage(HttpMethod.Get, $"{config["ApiUrl"]}accounts/verify_credentials");
req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
using var profileResult = await http.SendAsync(req);
if (!profileResult.IsSuccessStatusCode)
{
return Result<MastodonAccount>.AsError(
$"Could not get profile ({profileResult.StatusCode:D}: {profileResult.ReasonPhrase})");
}
var profileResponse = JsonSerializer.Deserialize<MastodonAccount>(
new ReadOnlySpan<byte>(await profileResult.Content.ReadAsByteArrayAsync()));
if (profileResponse == null)
{
return Result<MastodonAccount>.AsError("Could not parse profile result");
}
if (profileResponse.Username != profileResponse.AccountName)
{
return Result<MastodonAccount>.AsError(
$"Profiles must be from noagendasocial.com; yours is {profileResponse.AccountName}");
}
return Result<MastodonAccount>.AsOk(profileResponse);
}
/// <summary>
/// Create a JSON Web Token for this citizen to use for further requests to this API
/// </summary>
/// <param name="citizen">The citizen for which the token should be generated</param>
/// <param name="config">The authorization configuration section</param>
/// <returns>The JWT</returns>
public static string CreateJwt(Citizen citizen, IConfigurationSection config)
{
var tokenHandler = new JwtSecurityTokenHandler();
var token = tokenHandler.CreateToken(new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.NameIdentifier, citizen.Id.ToString()),
new Claim(ClaimTypes.Name, citizen.DisplayName),
}),
Expires = DateTime.UtcNow.AddHours(2),
Issuer = "https://jobsjobs.jobs",
Audience = "https://jobsjobs.jobs",
SigningCredentials = new SigningCredentials(
new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config["ServerSecret"])),
SecurityAlgorithms.HmacSha256Signature)
});
return tokenHandler.WriteToken(token);
}
}
}

View File

@@ -0,0 +1,86 @@
using JobsJobsJobs.Shared;
using Npgsql;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
namespace JobsJobsJobs.Server.Data
{
/// <summary>
/// Extensions to the NpgslConnection type supporting the manipulation of citizens
/// </summary>
public static class CitizenExtensions
{
/// <summary>
/// Populate a citizen object from the given data reader
/// </summary>
/// <param name="rdr">The data reader from which the values should be obtained</param>
/// <returns>A populated citizen</returns>
private static Citizen ToCitizen(NpgsqlDataReader rdr) =>
new Citizen(CitizenId.Parse(rdr.GetString("id")), rdr.GetString("na_user"), rdr.GetString("display_name"),
rdr.GetString("profile_url"), rdr.GetInstant("joined_on"), rdr.GetInstant("last_seen_on"));
/// <summary>
/// Retrieve a citizen by their No Agenda Social user name
/// </summary>
/// <param name="naUser">The NAS user name</param>
/// <returns>The citizen, or null if not found</returns>
public static async Task<Citizen?> FindCitizenByNAUser(this NpgsqlConnection conn, string naUser)
{
using var cmd = conn.CreateCommand();
cmd.CommandText = "SELECT * FROM citizen WHERE na_user = @na_user";
cmd.Parameters.Add(new NpgsqlParameter("@na_user", naUser));
using NpgsqlDataReader rdr = await cmd.ExecuteReaderAsync().ConfigureAwait(false);
if (await rdr.ReadAsync().ConfigureAwait(false))
{
return ToCitizen(rdr);
}
return null;
}
/// <summary>
/// Add a citizen
/// </summary>
/// <param name="citizen">The citizen to be added</param>
public static async Task AddCitizen(this NpgsqlConnection conn, Citizen citizen)
{
using var cmd = conn.CreateCommand();
cmd.CommandText =
@"INSERT INTO citizen (
na_user, display_name, profile_url, joined_on, last_seen_on, id
) VALUES(
@na_user, @display_name, @profile_url, @joined_on, @last_seen_on, @id
)";
cmd.Parameters.Add(new NpgsqlParameter("@id", citizen.Id.ToString()));
cmd.Parameters.Add(new NpgsqlParameter("@na_user", citizen.NaUser));
cmd.Parameters.Add(new NpgsqlParameter("@display_name", citizen.DisplayName));
cmd.Parameters.Add(new NpgsqlParameter("@profile_url", citizen.ProfileUrl));
cmd.Parameters.Add(new NpgsqlParameter("@joined_on", citizen.JoinedOn));
cmd.Parameters.Add(new NpgsqlParameter("@last_seen_on", citizen.LastSeenOn));
await cmd.ExecuteNonQueryAsync().ConfigureAwait(false);
}
/// <summary>
/// Update a citizen after they have logged on (update last seen, sync display name)
/// </summary>
/// <param name="citizen">The updated citizen</param>
public static async Task UpdateCitizenOnLogOn(this NpgsqlConnection conn, Citizen citizen)
{
using var cmd = conn.CreateCommand();
cmd.CommandText =
@"UPDATE citizen
SET display_name = @display_name,
last_seen_on = @last_seen_on
WHERE id = @id";
cmd.Parameters.Add(new NpgsqlParameter("@id", citizen.Id.ToString()));
cmd.Parameters.Add(new NpgsqlParameter("@display_name", citizen.DisplayName));
cmd.Parameters.Add(new NpgsqlParameter("@last_seen_on", citizen.LastSeenOn));
await cmd.ExecuteNonQueryAsync().ConfigureAwait(false);
}
}
}

View File

@@ -0,0 +1,52 @@
using JobsJobsJobs.Shared;
using NodaTime;
using Npgsql;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace JobsJobsJobs.Server.Data
{
/// <summary>
/// Extensions to the Npgsql data reader
/// </summary>
public static class NpgsqlDataReaderExtensions
{
/// <summary>
/// Get a boolean by its name
/// </summary>
/// <param name="name">The name of the field to be retrieved as a boolean</param>
/// <returns>The specified field as a boolean</returns>
public static bool GetBoolean(this NpgsqlDataReader rdr, string name) => rdr.GetBoolean(rdr.GetOrdinal(name));
/// <summary>
/// Get an Instant by its name
/// </summary>
/// <param name="name">The name of the field to be retrieved as an Instant</param>
/// <returns>The specified field as an Instant</returns>
public static Instant GetInstant(this NpgsqlDataReader rdr, string name) =>
rdr.GetFieldValue<Instant>(rdr.GetOrdinal(name));
/// <summary>
/// Get a 64-bit integer by its name
/// </summary>
/// <param name="name">The name of the field to be retrieved as a 64-bit integer</param>
/// <returns>The specified field as a 64-bit integer</returns>
public static long GetInt64(this NpgsqlDataReader rdr, string name) => rdr.GetInt64(rdr.GetOrdinal(name));
/// <summary>
/// Get a string by its name
/// </summary>
/// <param name="name">The name of the field to be retrieved as a string</param>
/// <returns>The specified field as a string</returns>
public static string GetString(this NpgsqlDataReader rdr, string name) => rdr.GetString(rdr.GetOrdinal(name));
/// <summary>
/// Determine if a column is null
/// </summary>
/// <param name="name">The name of the column to check</param>
/// <returns>True if the column is null, false if not</returns>
public static bool IsDBNull(this NpgsqlDataReader rdr, string name) => rdr.IsDBNull(rdr.GetOrdinal(name));
}
}

View File

@@ -0,0 +1,52 @@
using JobsJobsJobs.Shared;
using Npgsql;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace JobsJobsJobs.Server.Data
{
/// <summary>
/// Extensions to the NpgsqlConnection type to support manipulation of profiles
/// </summary>
public static class ProfileExtensions
{
/// <summary>
/// Populate a profile object from the given data reader
/// </summary>
/// <param name="rdr">The data reader from which values should be obtained</param>
/// <returns>The populated profile</returns>
private static Profile ToProfile(NpgsqlDataReader rdr)
{
var continentId = ContinentId.Parse(rdr.GetString("continent_id"));
return new Profile(CitizenId.Parse(rdr.GetString("id")), rdr.GetBoolean("seeking_employment"),
rdr.GetBoolean("is_public"), continentId, rdr.GetString("region"), rdr.GetBoolean("remote_work"),
rdr.GetBoolean("full_time"), new MarkdownString(rdr.GetString("biography")),
rdr.GetInstant("last_updated_on"),
rdr.IsDBNull("experience") ? null : new MarkdownString(rdr.GetString("experience")))
{
Continent = new Continent(continentId, rdr.GetString("continent_name"))
};
}
/// <summary>
/// Retrieve an employment profile by a citizen ID
/// </summary>
/// <param name="citizen">The ID of the citizen whose profile should be retrieved</param>
/// <returns>The profile, or null if it does not exist</returns>
public static async Task<Profile?> FindProfileByCitizen(this NpgsqlConnection conn, CitizenId citizen)
{
using var cmd = conn.CreateCommand();
cmd.CommandText =
@"SELECT p.*, c.name AS continent_name
FROM profile p
INNER JOIN continent c ON p.continent_id = c.id
WHERE citizen_id = @id";
cmd.Parameters.Add(new NpgsqlParameter("@id", citizen.Id.ToString()));
using var rdr = await cmd.ExecuteReaderAsync().ConfigureAwait(false);
return await rdr.ReadAsync().ConfigureAwait(false) ? ToProfile(rdr) : null;
}
}
}

View File

@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="5.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="5.0.1" />
<PackageReference Include="NodaTime.Serialization.SystemTextJson" Version="1.0.0" />
<PackageReference Include="Npgsql" Version="5.0.1.1" />
<PackageReference Include="Npgsql.NodaTime" Version="5.0.1.1" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.8.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Client\JobsJobsJobs.Client.csproj" />
<ProjectReference Include="..\Shared\JobsJobsJobs.Shared.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Controllers\" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<RazorPage_SelectedScaffolderID>RazorPageScaffolder</RazorPage_SelectedScaffolderID>
<RazorPage_SelectedScaffolderCategoryPath>root/Common/RazorPage</RazorPage_SelectedScaffolderCategoryPath>
<ActiveDebugProfile>JobsJobsJobs.Server</ActiveDebugProfile>
<Controller_SelectedScaffolderID>ApiControllerEmptyScaffolder</Controller_SelectedScaffolderID>
<Controller_SelectedScaffolderCategoryPath>root/Common/Api</Controller_SelectedScaffolderCategoryPath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DebuggerFlavor>ProjectDebugger</DebuggerFlavor>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,34 @@
using System.Text.Json.Serialization;
namespace JobsJobsJobs.Server.Models
{
/// <summary>
/// The variables we need from the account information we get from No Agenda Social
/// </summary>
public class MastodonAccount
{
/// <summary>
/// The user name (what we store as naUser)
/// </summary>
[JsonPropertyName("username")]
public string Username { get; set; } = "";
/// <summary>
/// The account name; will be the same as username for local (non-federated) accounts
/// </summary>
[JsonPropertyName("acct")]
public string AccountName { get; set; } = "";
/// <summary>
/// The user's display name as it currently shows on No Agenda Social
/// </summary>
[JsonPropertyName("display_name")]
public string DisplayName { get; set; } = "";
/// <summary>
/// The user's profile URL
/// </summary>
[JsonPropertyName("url")]
public string Url { get; set; } = "";
}
}

View File

@@ -0,0 +1,42 @@
@page
@model JobsJobsJobs.Server.Pages.ErrorModel
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>Error</title>
<link href="~/css/bootstrap/bootstrap.min.css" rel="stylesheet" />
<link href="~/css/app.css" rel="stylesheet" />
</head>
<body>
<div class="main">
<div class="content px-4">
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
@if (Model.ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@Model.RequestId</code>
</p>
}
<h3>Development Mode</h3>
<p>
Swapping to the <strong>Development</strong> environment displays detailed information about the error that occurred.
</p>
<p>
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
It can result in displaying sensitive information from exceptions to end users.
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
and restarting the app.
</p>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,32 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
namespace JobsJobsJobs.Server.Pages
{
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
[IgnoreAntiforgeryToken]
public class ErrorModel : PageModel
{
public string RequestId { get; set; } = default!;
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
private readonly ILogger<ErrorModel> _logger;
public ErrorModel(ILogger<ErrorModel> logger)
{
_logger = logger;
}
public void OnGet()
{
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
}
}
}

View File

@@ -0,0 +1,29 @@
@page
@model JobsJobsJobs.Server.Pages._HostModel
@{
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>Jobs, Jobs, Jobs</title>
<base href="/" />
<link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />
<link href="css/app.css" rel="stylesheet" />
<link href="JobsJobsJobs.Client.styles.css" rel="stylesheet" />
</head>
<body>
<component type="typeof(JobsJobsJobs.Client.App)" render-mode="WebAssemblyPrerendered" />
<div id="blazor-error-ui">
An unhandled error has occurred.
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>
<script src="_framework/blazor.webassembly.js"></script>
</body>
</html>

View File

@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace JobsJobsJobs.Server.Pages
{
public class _HostModel : PageModel
{
public void OnGet()
{
}
}
}

View File

@@ -0,0 +1,26 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace JobsJobsJobs.Server
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}

View File

@@ -0,0 +1,30 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:49363",
"sslPort": 44308
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"JobsJobsJobs.Server": {
"commandName": "Project",
"dotnetRunMessages": "true",
"launchBrowser": true,
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
"applicationUrl": "https://localhost:3005;http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@@ -0,0 +1,90 @@
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.ResponseCompression;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.IdentityModel.Tokens;
using NodaTime;
using NodaTime.Serialization.SystemTextJson;
using Npgsql;
using System.Text;
namespace JobsJobsJobs.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)
{
// TODO: configure JSON serialization for NodaTime
services.AddScoped(_ => new NpgsqlConnection(Configuration.GetConnectionString("JobsDb")));
services.AddSingleton<IClock>(SystemClock.Instance);
services.AddLogging();
services.AddControllersWithViews();
services.AddRazorPages()
.AddJsonOptions(options =>
options.JsonSerializerOptions.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb));
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.RequireHttpsMetadata = false;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidAudience = "https://jobsjobs.jobs",
ValidIssuer = "https://jobsjobs.jobs",
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(
Configuration.GetSection("Auth")["ServerSecret"]))
};
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
NpgsqlConnection.GlobalTypeMapper.UseNodaTime();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseWebAssemblyDebugging();
}
else
{
app.UseExceptionHandler("/Error");
// 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();
app.UseBlazorFrameworkFiles();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
endpoints.MapControllers();
endpoints.MapFallbackToFile("index.html");
});
}
}
}

View File

@@ -0,0 +1,10 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}