📄 src/Api/YouTubeAuth/YouTubeAuthExtensions.cs
using System.Threading.Tasks;
using Google.Apis.Auth.AspNetCore3;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Slopper.Infrastructure.YouTube;

namespace Slopper.Api.YouTubeAuth;

public static class YouTubeAuthExtensions
{
    extension(AuthenticationBuilder auth)
    {
        public AuthenticationBuilder AddYouTube()
        {
            auth.AddGoogleOpenIdConnect(
                GoogleOpenIdConnectDefaults.AuthenticationScheme,
                options =>
                {
                    options.CallbackPath = "/admin/redirect/youtube";
                    options.ResponseMode = OpenIdConnectResponseMode.Query;
                    options.TokenValidationParameters.NameClaimType = "name";
                    options.Events = new()
                    {
                        OnRedirectToIdentityProvider = context =>
                        {
                            if (context.Request.Headers.GetCommaSeparatedValues("Sec-Fetch-Mode") is ["navigate"])
                            {
                                return Task.CompletedTask;
                            }

                            context.Response.OnStarting(
                                static response =>
                                {
                                    ((HttpResponse)response).StatusCode = StatusCodes.Status403Forbidden;
                                    return Task.CompletedTask;
                                },
                                context.Response
                            );
                            return Task.CompletedTask;
                        },
                    };
                }
            );

            auth.Services.AddOptions<OpenIdConnectOptions>(GoogleOpenIdConnectDefaults.AuthenticationScheme)
                .BindConfiguration("YouTube");

            auth.Services.AddTransient<IAuthorizationHandler, YouTubeScopeAuthorizationHandler>();

            auth.Services.AddSingleton<YouTubeCredentialsProvider>();
            auth.Services.AddSingleton<IYouTubeCredentialsProvider>(sp =>
                sp.GetRequiredService<YouTubeCredentialsProvider>()
            );

            return auth;
        }
    }

    private const string YouTubePolicyName = "YouTube";

    extension(AuthorizationBuilder auth)
    {
        public AuthorizationBuilder AddYouTubePolicy() =>
            auth.AddPolicy(
                YouTubePolicyName,
                policy =>
                    policy
                        .AddAuthenticationSchemes(GoogleOpenIdConnectDefaults.AuthenticationScheme)
                        .RequireAuthenticatedUser()
                        .AddRequirements(new YouTubeScopeRequirement("https://www.googleapis.com/auth/youtube.upload"))
            );
    }

    extension<TBuilder>(TBuilder builder)
        where TBuilder : IEndpointConventionBuilder
    {
        public TBuilder RequireYouTubeAuthorization() => builder.RequireAuthorization(YouTubePolicyName);
    }
}