152 lines
5.5 KiB
C#
152 lines
5.5 KiB
C#
using System.Net.Http.Headers;
|
|
using System.Security.Claims;
|
|
using System.Text.Json;
|
|
using Bifrost.Web.Configuration;
|
|
using Bifrost.Web.Data;
|
|
using Bifrost.Web.Features.Membership;
|
|
using Bifrost.Web.Features.Shared;
|
|
using Microsoft.AspNetCore.Authentication;
|
|
using Microsoft.AspNetCore.Authentication.Cookies;
|
|
using Microsoft.AspNetCore.Authentication.OAuth;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.Options;
|
|
|
|
var builder = WebApplication.CreateBuilder(args);
|
|
|
|
builder.Services
|
|
.AddOptions<GitHubOAuthOptions>()
|
|
.BindConfiguration(GitHubOAuthOptions.SectionName);
|
|
|
|
builder.Services
|
|
.AddOptions<BootstrapOptions>()
|
|
.BindConfiguration(BootstrapOptions.SectionName);
|
|
|
|
builder.Services.AddRazorPages(options =>
|
|
{
|
|
options.Conventions.AuthorizePage("/Membership/Status");
|
|
options.Conventions.AuthorizeFolder("/App", ActiveMemberRequirement.PolicyName);
|
|
});
|
|
|
|
builder.Services.AddDbContext<BifrostDbContext>(options =>
|
|
{
|
|
var connectionString = builder.Configuration.GetConnectionString("Bifrost")
|
|
?? "Host=127.0.0.1;Database=bifrost;Username=postgres;Password=postgres";
|
|
|
|
options.UseNpgsql(connectionString);
|
|
});
|
|
|
|
builder.Services.AddHttpContextAccessor();
|
|
builder.Services.AddSingleton(TimeProvider.System);
|
|
builder.Services.AddScoped<ICurrentBifrostActorAccessor, CurrentBifrostActorAccessor>();
|
|
builder.Services.AddScoped<AuditTrailService>();
|
|
builder.Services.AddScoped<DashboardSnapshotService>();
|
|
builder.Services.AddScoped<MembershipSynchronizationService>();
|
|
builder.Services.AddScoped<IAuthorizationHandler, ActiveMemberAuthorizationHandler>();
|
|
|
|
builder.Services
|
|
.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
|
|
.AddCookie(options =>
|
|
{
|
|
options.LoginPath = "/auth/sign-in";
|
|
options.AccessDeniedPath = "/Membership/Status";
|
|
options.SlidingExpiration = true;
|
|
})
|
|
.AddOAuth(GitHubAuthenticationDefaults.AuthenticationScheme, options =>
|
|
{
|
|
var gitHubOptions = builder.Configuration
|
|
.GetSection(GitHubOAuthOptions.SectionName)
|
|
.Get<GitHubOAuthOptions>() ?? new GitHubOAuthOptions();
|
|
|
|
options.ClientId = gitHubOptions.ClientId;
|
|
options.ClientSecret = gitHubOptions.ClientSecret;
|
|
options.CallbackPath = gitHubOptions.CallbackPath;
|
|
options.AuthorizationEndpoint = "https://github.com/login/oauth/authorize";
|
|
options.TokenEndpoint = "https://github.com/login/oauth/access_token";
|
|
options.UserInformationEndpoint = "https://api.github.com/user";
|
|
options.SaveTokens = true;
|
|
options.Scope.Add("read:user");
|
|
options.Scope.Add("user:email");
|
|
options.ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "id");
|
|
options.ClaimActions.MapJsonKey(ClaimTypes.Name, "login");
|
|
options.ClaimActions.MapJsonKey(BifrostClaimTypes.GitHubLogin, "login");
|
|
options.ClaimActions.MapJsonKey(BifrostClaimTypes.DisplayName, "name");
|
|
options.ClaimActions.MapJsonKey(BifrostClaimTypes.AvatarUrl, "avatar_url");
|
|
|
|
options.Events = new OAuthEvents
|
|
{
|
|
OnCreatingTicket = async context =>
|
|
{
|
|
using var request = new HttpRequestMessage(HttpMethod.Get, context.Options.UserInformationEndpoint);
|
|
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/vnd.github+json"));
|
|
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", context.AccessToken);
|
|
request.Headers.UserAgent.ParseAdd("Bifrost");
|
|
|
|
using var response = await context.Backchannel.SendAsync(
|
|
request,
|
|
HttpCompletionOption.ResponseHeadersRead,
|
|
context.HttpContext.RequestAborted);
|
|
|
|
response.EnsureSuccessStatusCode();
|
|
|
|
await using var payloadStream = await response.Content.ReadAsStreamAsync(context.HttpContext.RequestAborted);
|
|
using var payload = await JsonDocument.ParseAsync(payloadStream, cancellationToken: context.HttpContext.RequestAborted);
|
|
context.RunClaimActions(payload.RootElement);
|
|
}
|
|
};
|
|
});
|
|
|
|
builder.Services.AddAuthorization(options =>
|
|
{
|
|
options.AddPolicy(ActiveMemberRequirement.PolicyName, policy =>
|
|
{
|
|
policy.RequireAuthenticatedUser();
|
|
policy.AddRequirements(new ActiveMemberRequirement());
|
|
});
|
|
});
|
|
|
|
var app = builder.Build();
|
|
|
|
if (!app.Environment.IsDevelopment())
|
|
{
|
|
app.UseExceptionHandler("/Error");
|
|
app.UseHsts();
|
|
}
|
|
|
|
app.UseHttpsRedirection();
|
|
app.UseStaticFiles();
|
|
app.UseRouting();
|
|
app.UseAuthentication();
|
|
app.UseMiddleware<GitHubMembershipSynchronizationMiddleware>();
|
|
app.UseAuthorization();
|
|
|
|
app.MapGet("/auth/sign-in", async (HttpContext httpContext, IOptions<GitHubOAuthOptions> gitHubOptions) =>
|
|
{
|
|
if (!gitHubOptions.Value.IsConfigured)
|
|
{
|
|
httpContext.Response.Redirect("/?auth=github-not-configured");
|
|
return;
|
|
}
|
|
|
|
var redirectUri = httpContext.Request.Query["returnUrl"].ToString();
|
|
if (string.IsNullOrWhiteSpace(redirectUri))
|
|
{
|
|
redirectUri = "/App";
|
|
}
|
|
|
|
await httpContext.ChallengeAsync(
|
|
GitHubAuthenticationDefaults.AuthenticationScheme,
|
|
new() { RedirectUri = redirectUri });
|
|
}).AllowAnonymous();
|
|
|
|
app.MapPost("/auth/sign-out", async (HttpContext httpContext) =>
|
|
{
|
|
await httpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
|
httpContext.Response.Redirect("/");
|
|
}).AllowAnonymous();
|
|
|
|
app.MapRazorPages();
|
|
|
|
app.Run();
|
|
|
|
public partial class Program;
|