public async Task CanAuthenticateUsingFluentAPI() { var handlerMock = new Mock <HttpMessageHandler>(); handlerMock .Protected() .Setup <Task <HttpResponseMessage> >( "SendAsync", ItExpr.IsAny <HttpRequestMessage>(), ItExpr.IsAny <CancellationToken>() ) .ReturnsAsync(new HttpResponseMessage() { StatusCode = HttpStatusCode.OK, Content = new StringContent("{\"access_token\": \"deadbeef\",\"expires_in\": 1000,\"token_type\": \"bearer\"}"), }) .Verifiable(); var authenticated = Twitch.Authenticate() .FromAppCredentials("test", "test") .WithScope(TwitchConstants.TwitchOAuthScopes.ChatRead) .WithHttpMessageHandler(handlerMock.Object) .Build(); var message = new HttpRequestMessage(); await authenticated.AuthenticateMessageAsync(message); Assert.Equal("Bearer", message.Headers.Authorization.Scheme); Assert.Equal("deadbeef", message.Headers.Authorization.Parameter); }
async Task IChannelGrain.Activate(string userToken) { var userAuthenticated = Twitch.Authenticate() .FromOAuthToken(userToken) .Build(); var userClient = TwitchAPIClient.CreateFromBase(_appClient, userAuthenticated); var validated = await userClient.ValidateToken(); if (validated == null || validated.UserId != _channelId || validated.ExpiresIn == 0) { throw new ArgumentException("Could not validate token"); } _userClient = userClient; var channelInfoTask = _userClient.GetChannelInfoAsync(_channelId); List <HelixChannelModerator> moderators = new List <HelixChannelModerator>(); var editorsTask = _userClient.GetHelixChannelEditorsAsync(_channelId); await foreach (var moderator in _userClient.EnumerateChannelModeratorsAsync(_channelId)) { moderators.Add(moderator); } _channelState.State.BroadcasterToken = userToken; _channelState.State.Editors = (await editorsTask).ToList(); _channelState.State.Moderators = moderators.ToList(); await _channelState.WriteStateAsync(); var editorsTasks = _channelState.State.Editors.Select(editor => GrainFactory.GetGrain <IUserGrain>(editor.UserId).SetRole(new UserRole { Role = ChannelRole.Editor, ChannelId = _channelId, ChannelName = _channelInfo.BroadcasterName, }) ); var modsTasks = _channelState.State.Moderators.Select(moderator => GrainFactory.GetGrain <IUserGrain>(moderator.UserId).SetRole(new UserRole { Role = ChannelRole.Moderator, ChannelId = _channelId, ChannelName = _channelInfo.BroadcasterName, }) ); await Task.WhenAll(editorsTasks); await Task.WhenAll(modsTasks); _channelInfo = await channelInfoTask; await RegisterEventSubSubscriptions(CancellationToken.None); }
static async Task Main(string[] args) { IConfiguration configuration = null; var builder = new HostBuilder() .ConfigureLogging(configure => { configure.AddConsole(); configure.SetMinimumLevel(LogLevel.Information); }) .ConfigureAppConfiguration(configure => { configure.AddUserSecrets <Program>(); configure.AddEnvironmentVariables(); configuration = configure.Build(); }) .ConfigureServices((hostContext, services) => { // Configure services services.AddHttpClient(); services.Configure <TwitchApplicationOptions>(configuration.GetSection("twitch")); services.Configure <AzureGameLocalizationStoreOptions>(configuration.GetSection("loc:azure")); services.AddTransient <TwitchAPIClient>(); services.AddTransient <IGDBClient>(); services.AddSingleton <IMemoryCache, MemoryCache>(); services.AddSingleton <SteamStoreClient>(); services.AddSingleton <IAuthenticated>(s => Twitch.Authenticate() .FromAppCredentials( s.GetService <IOptions <TwitchApplicationOptions> >().Value.ClientId, s.GetService <IOptions <TwitchApplicationOptions> >().Value.ClientSecret) .Build() ); services.AddTransient <IGameLocalizationStore>(services => { var options = services.GetRequiredService <IOptions <AzureGameLocalizationStoreOptions> >(); if (string.IsNullOrEmpty(options.Value.StorageConnectionString) || string.IsNullOrEmpty(options.Value.TableName)) { return(null); } return(services.GetRequiredService <AzureStorageGameLocalizationStore>()); }); services.AddSingleton <AzureStorageGameLocalizationStore>(); }) .UseConsoleLifetime(); await builder.RunCommandLineApplicationAsync <Tool>(args); }
static async Task Main(string[] args) { IConfiguration configuration = null; var builder = new HostBuilder() .ConfigureLogging(configure => { configure.AddConsole(); }) .ConfigureAppConfiguration(configure => { configure.AddJsonFile("appsettings.json", true); #if DEBUG configure.AddJsonFile("appsettings.Debug.json", true); #endif configure.AddEnvironmentVariables(); configure.AddUserSecrets <Program>(); configuration = configure.Build(); }) .ConfigureServices((hostContext, services) => { // Load channels and command configuration from static json file, and inject var channelsConfig = new ConfigurationBuilder().AddJsonFile("channels.json").Build(); IEnumerable <ChannelOptions> channelOptions = new List <ChannelOptions>(); channelsConfig.GetSection("channels").Bind(channelOptions); services.AddTransient <IEnumerable <ChannelOptions> >((_) => channelOptions); // Configure services services.AddHttpClient(); services.Configure <TwitchApplicationOptions>(configuration.GetSection("twitch")); services.Configure <TwitchChatClientOptions>(configuration.GetSection("twitch").GetSection("IrcOptions")); services.AddSingleton <IMessageProcessor, TracingMessageProcessor>(); services.AddTransient <TwitchChatClient>(); services.AddTransient <TwitchAPIClient>(); services.AddTransient <IGDBClient>(); services.AddSingleton <IMemoryCache, MemoryCache>(); services.AddSingleton <SteamStoreClient>(); services.AddSingleton <IGameLocalizationStore, EmbeddedGameLocalizationDb>(); services.AddTransient <ITwitchCategoryProvider>(s => s.GetRequiredService <PollingTwitchCategoryProvider>()); services.AddScoped <PollingTwitchCategoryProvider>(); services.AddSingleton <IAuthenticated>(s => Twitch.Authenticate() .FromAppCredentials( s.GetService <IOptions <TwitchApplicationOptions> >().Value.ClientId, s.GetService <IOptions <TwitchApplicationOptions> >().Value.ClientSecret) .Build() ); services.AddSingleton <IBotAuthenticated>(s => Twitch.AuthenticateBot() .FromOAuthToken( s.GetService <IOptions <TwitchApplicationOptions> >().Value.IrcOptions.OAuthToken) .Build() ); services.AddTransient <ITwitchChatClientBuilder>(s => TwitchChatClientBuilder.Create() .WithOAuthToken(s.GetRequiredService <IOptions <TwitchApplicationOptions> >().Value.IrcOptions.OAuthToken) .WithLoggerFactory(s.GetRequiredService <ILoggerFactory>()) ); // Configure commands services.AddCommand <GameSynopsisCommand>("GameSynopsis"); services.AddCommand <TracingMessageProcessor>("MessageTracer"); // Add hosted chatbot service services.AddSingleton <TwitchChatBot>(); services.AddHostedService <TwitchChatBot>(s => s.GetRequiredService <TwitchChatBot>()); services.AddHostedService(services => services.GetRequiredService <PollingTwitchCategoryProvider>()); }) .UseConsoleLifetime(); var host = builder.Build(); var categoryProvider = host.Services.GetRequiredService <PollingTwitchCategoryProvider>(); categoryProvider.CheckAndSchedule("miekyld"); var twitchBot = host.Services.GetRequiredService <TwitchChatBot>(); twitchBot.SetChannel("158511925"); await twitchBot.RegisterMessageProcessor <GameSynopsisCommand>(new CommandOptions { Aliases = new string[] { "jeu", "game" }, Parameters = new Dictionary <string, string> { { "AsReply", bool.TrueString }, } }); categoryProvider.OnUpdate += async(sender, gameinfo) => { var context = new ProcessorContext { CategoryId = gameinfo.TwitchCategoryId, ChannelId = "158511925", ChannelName = gameinfo.Name, Language = gameinfo.Language, }; await twitchBot.UpdateContext(context); }; await host.RunAsync(); }
void BuildSiloHost() { var instrumentationKey = _configuration.GetValue <string>("ApplicationInsights:InstrumentationKey"); var hostname = _configuration.GetValue <string>("HOSTNAME"); var azureStorageConnectionString = _configuration.GetValue <string>("Storage:AzureStorageConnectionString"); var redisClusteringUrl = _configuration.GetValue <string>("REDIS_URL"); var builder = new SiloHostBuilder() .ConfigureLogging(loggingBuilder => { if (!string.IsNullOrEmpty(instrumentationKey)) { loggingBuilder.AddApplicationInsights(instrumentationKey); } loggingBuilder.AddConsole(); }) // Configure ClusterId and ServiceId .Configure <ClusterOptions>(options => { options.ClusterId = "dev"; options.ServiceId = "TwitchServices"; }) .AddStartupTask <LoadConfigurationStartupTask>() .ConfigureApplicationParts(parts => parts.AddApplicationPart(typeof(ChannelGrain).Assembly).WithReferences()) // Configure connectivity .ConfigureEndpoints(hostname: hostname, siloPort: 11111, gatewayPort: 30000); if (!string.IsNullOrEmpty(azureStorageConnectionString)) { builder.AddAzureTableGrainStorage("profileStore", (AzureTableStorageOptions options) => { options.ConnectionString = azureStorageConnectionString; options.TableName = "profiles"; options.UseJson = true; options.IndentJson = false; }); builder.AddAzureTableGrainStorage("channelStore", (AzureTableStorageOptions options) => { options.ConnectionString = azureStorageConnectionString; options.TableName = "channels"; options.UseJson = true; options.IndentJson = false; }); builder.AddAzureTableGrainStorage("botSettingsStore", (AzureTableStorageOptions options) => { options.ConnectionString = azureStorageConnectionString; options.TableName = "botsettings"; options.UseJson = true; options.IndentJson = false; }); builder.AddCustomCategoriesStorage("customCategoriesStore", (CustomCategoriesStorageOptions options) => { options.ConnectionString = azureStorageConnectionString; options.TableName = "customcategories"; }); } else { builder.AddMemoryGrainStorage("profileStore"); builder.AddMemoryGrainStorage("channelStore"); builder.AddMemoryGrainStorage("botSettingsStore"); builder.AddMemoryGrainStorage("customCategoriesStore"); } if (!string.IsNullOrEmpty(redisClusteringUrl)) { // Use redis clustering when available builder.UseRedisClustering(redisClusteringUrl); } else { // Use localhost clustering for a single local silo builder.UseLocalhostClustering(11111, 30000, null, "TwitchServices", "dev"); } // Temp builder.ConfigureServices((context, services) => { // Load channels and command configuration from static json file, and inject var channelsConfig = new ConfigurationBuilder().AddJsonFile("channels.json").Build(); IEnumerable <ChannelOptions> channelOptions = new List <ChannelOptions>(); channelsConfig.GetSection("channels").Bind(channelOptions); services.AddTransient <IEnumerable <ChannelOptions> >((_) => channelOptions); // Configure services services.AddHttpClient(); services.Configure <TwitchApplicationOptions>(_configuration.GetSection("twitch")); services.Configure <TwitchChatClientOptions>(_configuration.GetSection("twitch").GetSection("IrcOptions")); services.Configure <AzureGameLocalizationStoreOptions>(_configuration.GetSection("loc:azure")); services.AddSingleton <IMessageProcessor, TracingMessageProcessor>(); services.AddTransient <TwitchChatClient>(); services.AddTransient <TwitchAPIClient>(); services.AddTransient <IGDBClient>(); services.AddSingleton <IMemoryCache, MemoryCache>(); services.AddSingleton <SteamStoreClient>(); services.AddSingleton <IAuthenticated>(s => Twitch.Authenticate() .FromAppCredentials( s.GetService <IOptions <TwitchApplicationOptions> >().Value.ClientId, s.GetService <IOptions <TwitchApplicationOptions> >().Value.ClientSecret) .Build() ); services.AddTransient <ITwitchCategoryProvider, GrainTwitchCategoryProvider>(); services.AddSingleton <IGameLocalizationStore, AzureStorageGameLocalizationStore>(); services.PostConfigure <TwitchChatClientOptions>(options => { var oauth = Twitch.Authenticate() .FromOAuthToken(options.OAuthToken) .Build(); var loggerFactory = new LoggerFactory(); using (var httpClient = new HttpClient()) using (var apiClient = TwitchAPIClient.Create(oauth)) { options.TokenInfo = apiClient.ValidateToken().Result; } }); // Configure commands services.AddCommand <GameSynopsisCommand>("GameSynopsis"); services.AddCommand <TracingMessageProcessor>("Logger"); services.AddCommand <ResponseCommandProcessor>("Response"); }); _siloHost = builder.Build(); }
// This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddControllers(); services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo { Title = "BlipBloopWeb", Version = "v1" }); }); services.AddApplicationInsightsTelemetry(); services.AddTransient <IClientBuilder>(services => { var builder = new ClientBuilder() // Clustering information .Configure <ClusterOptions>(options => { options.ClusterId = "dev"; options.ServiceId = "TwitchServices"; }) .ConfigureApplicationParts(parts => parts.AddApplicationPart(typeof(IChannelGrain).Assembly)); var redisClusteringUrl = Configuration.GetValue <string>("REDIS_URL"); if (!string.IsNullOrEmpty(redisClusteringUrl)) { builder.UseRedisClustering(redisClusteringUrl); } else { builder.UseLocalhostClustering(); } return(builder); }); services.AddSingleton <IClientProvider, GrainClientProvider>(); // Load config, mainly the EventSub secret used for registration services.Configure <EventSubOptions>(Configuration.GetSection("twitch:EventSub")); // Add the EventSub handler instance to DI services.AddEventSub(); // Register an EventSub event Handler for channel.update events services.AddEventSubHandler <TwitchEventSubChannelUpdateEvent>(async(context, eventSub) => { context.Logger.LogInformation("Received a channel update for {channelName}, streaming {category} - {text}", eventSub.BroadcasterUserName, eventSub.CategoryName, eventSub.Title); var grainClientProvider = context.Services.GetRequiredService <IClientProvider>(); var grainClient = await grainClientProvider.GetConnectedClient(); var helixInfo = new HelixChannelInfo { BroadcasterId = eventSub.BroadcasterUserId, BroadcasterName = eventSub.BroadcasterUserName, BroadcasterLanguage = eventSub.Language, GameId = eventSub.CategoryId, GameName = eventSub.CategoryName, Title = eventSub.Title, }; var channelGrain = grainClient.GetGrain <IChannelGrain>(helixInfo.BroadcasterId); await channelGrain.OnChannelUpdate(helixInfo); }); var twitchOptions = Configuration.GetSection("twitch").Get <TwitchApplicationOptions>(); services.AddTransient <IAuthenticated>(services => Twitch.Authenticate() .FromAppCredentials(twitchOptions.ClientId, twitchOptions.ClientSecret) .Build()); services.AddSingleton <TwitchAPIClient>(); services.Configure <AzureGameLocalizationStoreOptions>(Configuration.GetSection("loc:azure")); services.AddSingleton <IGameLocalizationStore, AzureStorageGameLocalizationStore>(); services.AddHttpClient(); Action <TwitchConstants.TwitchOAuthScopes[], string, OpenIdConnectOptions> configureTwitchOpenId = (scopes, callbackPath, options) => { options.Authority = "https://id.twitch.tv/oauth2"; //options.MetadataAddress = "https://id.twitch.tv/oauth2/.well-known/openid-configuration"; options.ClientId = twitchOptions.ClientId; options.ClientSecret = twitchOptions.ClientSecret; options.ResponseType = "token id_token"; options.ResponseMode = "form_post"; options.CallbackPath = callbackPath; options.Events.OnRedirectToIdentityProvider = (context) => { context.ProtocolMessage.RedirectUri = context.ProtocolMessage.RedirectUri + "-fragment"; return(Task.CompletedTask); }; options.Events.OnTokenValidated = (context) => { ClaimsIdentity identity = context.Principal.Identity as ClaimsIdentity; identity.AddClaim(new Claim("access_token", context.ProtocolMessage.AccessToken)); identity.AddClaim(new Claim("id_token", context.ProtocolMessage.IdToken)); foreach (var scope in scopes) { identity.AddClaim(new Claim("scope", TwitchConstants.ScopesValues[scope])); } return(Task.CompletedTask); }; options.Scope.Remove("profile"); foreach (var scope in scopes) { options.Scope.Add(TwitchConstants.ScopesValues[scope]); } }; // Identity services .AddAuthentication(options => { options.DefaultScheme = "cookie"; options.DefaultChallengeScheme = "twitch"; }) .AddCookie("cookie", options => { options.Events.OnRedirectToLogin = context => { if (context.Request.Path.StartsWithSegments("/api")) { context.Response.StatusCode = StatusCodes.Status401Unauthorized; } else { context.Response.Redirect(context.RedirectUri); } return(Task.CompletedTask); }; }) .AddOpenIdConnect("twitch", "Twitch", options => configureTwitchOpenId(new TwitchConstants.TwitchOAuthScopes[] { TwitchConstants.TwitchOAuthScopes.ChannelReadEditors, TwitchConstants.TwitchOAuthScopes.ModerationRead, TwitchConstants.TwitchOAuthScopes.UserReadBlockedUsers, TwitchConstants.TwitchOAuthScopes.UserReadBroadcast, TwitchConstants.TwitchOAuthScopes.UserReadSubscriptions, TwitchConstants.TwitchOAuthScopes.ChatRead, }, "/signin-oidc", options)) .AddOpenIdConnect("twitchBot", "Twitch - Bot Account", options => configureTwitchOpenId(new TwitchConstants.TwitchOAuthScopes[] { TwitchConstants.TwitchOAuthScopes.ChannelReadEditors, TwitchConstants.TwitchOAuthScopes.ModerationRead, TwitchConstants.TwitchOAuthScopes.UserReadBlockedUsers, TwitchConstants.TwitchOAuthScopes.UserReadBroadcast, TwitchConstants.TwitchOAuthScopes.ChatRead, TwitchConstants.TwitchOAuthScopes.ChatEdit, TwitchConstants.TwitchOAuthScopes.UserEditFollows, }, "/signin-oidc-bot", options)); services.AddRazorPages(); services.AddServerSideBlazor(); services.AddMudServices(); services.AddAuthorizationCore(); }