features/identity-roles #14
@ -1,27 +1,13 @@
|
|||||||
using System.Text;
|
|
||||||
using Hangfire;
|
using Hangfire;
|
||||||
using Hangfire.MemoryStorage;
|
using Hangfire.MemoryStorage;
|
||||||
using HealthChecks.UI.Client;
|
using HealthChecks.UI.Client;
|
||||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
|
||||||
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
|
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Newsbot.Collector.Api.Startup;
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Microsoft.IdentityModel.Tokens;
|
|
||||||
using Microsoft.OpenApi.Models;
|
|
||||||
using Newsbot.Collector.Api;
|
|
||||||
using Newsbot.Collector.Api.Authentication;
|
|
||||||
using Newsbot.Collector.Api.Domain;
|
|
||||||
using Newsbot.Collector.Api.Services;
|
|
||||||
using Newsbot.Collector.Database;
|
|
||||||
using Newsbot.Collector.Database.Repositories;
|
|
||||||
using Newsbot.Collector.Domain.Consts;
|
using Newsbot.Collector.Domain.Consts;
|
||||||
using Newsbot.Collector.Domain.Entities;
|
|
||||||
using Newsbot.Collector.Domain.Interfaces;
|
|
||||||
using Newsbot.Collector.Domain.Models;
|
using Newsbot.Collector.Domain.Models;
|
||||||
using Newsbot.Collector.Domain.Models.Config;
|
using Newsbot.Collector.Domain.Models.Config;
|
||||||
using Newsbot.Collector.Domain.Models.Config.Sources;
|
using Newsbot.Collector.Domain.Models.Config.Sources;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using Serilog.Events;
|
|
||||||
using ILogger = Serilog.ILogger;
|
using ILogger = Serilog.ILogger;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
@ -37,26 +23,10 @@ Log.Logger = GetLogger(config);
|
|||||||
Log.Information("Starting up");
|
Log.Information("Starting up");
|
||||||
|
|
||||||
// configure Entity Framework
|
// configure Entity Framework
|
||||||
var dbconn = config.GetConnectionString("Database");
|
|
||||||
builder.Services.AddDbContext<DatabaseContext>(o => o.UseNpgsql(dbconn ?? ""));
|
|
||||||
|
|
||||||
// Configure how Identity will be managed
|
|
||||||
builder.Services.AddIdentity<IdentityUser, IdentityRole>()
|
|
||||||
.AddRoles<IdentityRole>()
|
|
||||||
.AddEntityFrameworkStores<DatabaseContext>();
|
|
||||||
|
|
||||||
// Allow the controllers to access all the table repositories based on the interface
|
// Allow the controllers to access all the table repositories based on the interface
|
||||||
builder.Services.AddScoped<IArticlesRepository, ArticlesTable>();
|
DatabaseStartup.BuildDatabase(builder.Services, config);
|
||||||
builder.Services.AddScoped<IDiscordQueueRepository, DiscordQueueTable>();
|
DatabaseStartup.InjectTableClasses(builder.Services);
|
||||||
builder.Services.AddScoped<IDiscordWebHooksRepository, DiscordWebhooksTable>();
|
DatabaseStartup.InjectIdentityService(builder.Services);
|
||||||
builder.Services.AddScoped<IIconsRepository, IconsTable>();
|
|
||||||
builder.Services.AddScoped<ISourcesRepository, SourcesTable>();
|
|
||||||
builder.Services.AddScoped<IDiscordNotificationRepository, DiscordNotificationTable>();
|
|
||||||
builder.Services.AddScoped<IUserSourceSubscription, UserSourceSubscriptionTable>();
|
|
||||||
builder.Services.AddScoped<IRefreshTokenRepository, RefreshTokenTable>();
|
|
||||||
|
|
||||||
// Configure Identity
|
|
||||||
builder.Services.AddScoped<IIdentityService, IdentityService>();
|
|
||||||
|
|
||||||
// Configure Hangfire
|
// Configure Hangfire
|
||||||
builder.Services.AddHangfire(f => f.UseMemoryStorage());
|
builder.Services.AddHangfire(f => f.UseMemoryStorage());
|
||||||
@ -70,99 +40,14 @@ builder.Services.AddHealthChecks()
|
|||||||
|
|
||||||
builder.Services.AddControllers();
|
builder.Services.AddControllers();
|
||||||
|
|
||||||
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
|
SwaggerStartup.ConfigureSwagger(builder.Services);
|
||||||
builder.Services.AddEndpointsApiExplorer();
|
|
||||||
builder.Services.AddSwaggerGen();
|
|
||||||
|
|
||||||
builder.Services.Configure<ConnectionStrings>(config.GetSection("ConnectionStrings"));
|
builder.Services.Configure<ConnectionStrings>(config.GetSection("ConnectionStrings"));
|
||||||
builder.Services.Configure<ConfigSectionConnectionStrings>(config.GetSection(ConfigSectionsConst.ConnectionStrings));
|
builder.Services.Configure<ConfigSectionConnectionStrings>(config.GetSection(ConfigSectionsConst.ConnectionStrings));
|
||||||
builder.Services.Configure<ConfigSectionRssModel>(config.GetSection(ConfigSectionsConst.Rss));
|
builder.Services.Configure<ConfigSectionRssModel>(config.GetSection(ConfigSectionsConst.Rss));
|
||||||
builder.Services.Configure<ConfigSectionYoutubeModel>(config.GetSection(ConfigSectionsConst.Youtube));
|
builder.Services.Configure<ConfigSectionYoutubeModel>(config.GetSection(ConfigSectionsConst.Youtube));
|
||||||
|
|
||||||
// Configure JWT for auth and load it into DI so we can use it in the controllers
|
IdentityStartup.DefineJwtRequirements(builder.Services, config);
|
||||||
var jwtSettings = new JwtSettings();
|
|
||||||
config.Bind(nameof(jwtSettings), jwtSettings);
|
|
||||||
builder.Services.AddSingleton(jwtSettings);
|
|
||||||
|
|
||||||
// Configure how the Token Validation will be handled
|
|
||||||
var tokenValidationParameters = new TokenValidationParameters
|
|
||||||
{
|
|
||||||
ValidateIssuerSigningKey = true,
|
|
||||||
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(jwtSettings.Secret ?? "")),
|
|
||||||
ValidateIssuer = false,
|
|
||||||
ValidateAudience = false,
|
|
||||||
RequireExpirationTime = false,
|
|
||||||
ValidateLifetime = true
|
|
||||||
};
|
|
||||||
builder.Services.AddSingleton(tokenValidationParameters);
|
|
||||||
|
|
||||||
// Build the Authentication that will be used
|
|
||||||
builder.Services.AddAuthentication(x =>
|
|
||||||
{
|
|
||||||
x.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
|
|
||||||
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
|
|
||||||
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
|
|
||||||
}).AddJwtBearer(x =>
|
|
||||||
{
|
|
||||||
x.SaveToken = true;
|
|
||||||
x.TokenValidationParameters = tokenValidationParameters;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Build the Authorization Policy that the users will conform to.
|
|
||||||
builder.Services.AddAuthorization(options =>
|
|
||||||
options.AddPolicy(Authorization.AdministratorPolicy, b => b.RequireClaim( Authorization.AdministratorClaim, "true") ));
|
|
||||||
|
|
||||||
// Configure swagger authentication
|
|
||||||
builder.Services.AddSwaggerGen(cfg =>
|
|
||||||
{
|
|
||||||
cfg.AddSecurityDefinition("ApiKey", new OpenApiSecurityScheme
|
|
||||||
{
|
|
||||||
Description = "The API key to access the API",
|
|
||||||
Type = SecuritySchemeType.ApiKey,
|
|
||||||
Name = "x-api-key",
|
|
||||||
In = ParameterLocation.Header,
|
|
||||||
Scheme = "ApiKeyScheme"
|
|
||||||
});
|
|
||||||
|
|
||||||
cfg.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
|
|
||||||
{
|
|
||||||
Description = "JWT Authorization Header using the bearer scheme",
|
|
||||||
Name = "Authorization",
|
|
||||||
In = ParameterLocation.Header,
|
|
||||||
Type = SecuritySchemeType.ApiKey
|
|
||||||
});
|
|
||||||
|
|
||||||
cfg.AddSecurityRequirement(new OpenApiSecurityRequirement
|
|
||||||
{
|
|
||||||
//{
|
|
||||||
// new OpenApiSecurityScheme
|
|
||||||
// {
|
|
||||||
// Reference = new OpenApiReference
|
|
||||||
// {
|
|
||||||
// Type = ReferenceType.SecurityScheme,
|
|
||||||
// Id = "ApiKey"
|
|
||||||
// },
|
|
||||||
// In = ParameterLocation.Header
|
|
||||||
// },
|
|
||||||
// new List<string>()
|
|
||||||
//},
|
|
||||||
{
|
|
||||||
new OpenApiSecurityScheme
|
|
||||||
{
|
|
||||||
Reference = new OpenApiReference
|
|
||||||
{
|
|
||||||
Type = ReferenceType.SecurityScheme,
|
|
||||||
Id = "Bearer"
|
|
||||||
},
|
|
||||||
Scheme = "oauth2",
|
|
||||||
Name = "Bearer",
|
|
||||||
In = ParameterLocation.Header
|
|
||||||
},
|
|
||||||
new List<string>()
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
@ -199,9 +84,7 @@ app.MapControllers();
|
|||||||
using var serviceScope = app.Services.CreateScope();
|
using var serviceScope = app.Services.CreateScope();
|
||||||
if (config.GetValue<bool>(ConfigConst.RunDatabaseMigrationsOnStartup))
|
if (config.GetValue<bool>(ConfigConst.RunDatabaseMigrationsOnStartup))
|
||||||
{
|
{
|
||||||
var dbContext = serviceScope.ServiceProvider.GetRequiredService<DatabaseContext>();
|
await DatabaseStartup.RunDatabaseMigrationsAsync(serviceScope);
|
||||||
dbContext.Database.Migrate();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -209,18 +92,7 @@ else
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Inject the roles
|
// Inject the roles
|
||||||
var roleManager = serviceScope.ServiceProvider.GetRequiredService<RoleManager<IdentityRole>>();
|
await DatabaseStartup.InjectIdentityRolesAsync(serviceScope);
|
||||||
if (!await roleManager.RoleExistsAsync("Administrators"))
|
|
||||||
{
|
|
||||||
var adminRole = new IdentityRole("Administrators");
|
|
||||||
await roleManager.CreateAsync(adminRole);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!await roleManager.RoleExistsAsync("Users"))
|
|
||||||
{
|
|
||||||
var userRole = new IdentityRole("Users");
|
|
||||||
await roleManager.CreateAsync(userRole);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start the application
|
// Start the application
|
||||||
app.Run();
|
app.Run();
|
||||||
|
@ -3,7 +3,7 @@ using Newsbot.Collector.Domain.Consts;
|
|||||||
using Newsbot.Collector.Domain.Models.Config;
|
using Newsbot.Collector.Domain.Models.Config;
|
||||||
using Newsbot.Collector.Services.Jobs;
|
using Newsbot.Collector.Services.Jobs;
|
||||||
|
|
||||||
namespace Newsbot.Collector.Api;
|
namespace Newsbot.Collector.Api.Startup;
|
||||||
|
|
||||||
public static class BackgroundJobs
|
public static class BackgroundJobs
|
||||||
{
|
{
|
61
Newsbot.Collector.Api/Startup/DatabaseStartup.cs
Normal file
61
Newsbot.Collector.Api/Startup/DatabaseStartup.cs
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Newsbot.Collector.Api.Domain;
|
||||||
|
using Newsbot.Collector.Api.Services;
|
||||||
|
using Newsbot.Collector.Database;
|
||||||
|
using Newsbot.Collector.Database.Repositories;
|
||||||
|
using Newsbot.Collector.Domain.Interfaces;
|
||||||
|
|
||||||
|
namespace Newsbot.Collector.Api.Startup;
|
||||||
|
|
||||||
|
public class DatabaseStartup
|
||||||
|
{
|
||||||
|
public static void BuildDatabase(IServiceCollection services, IConfiguration config)
|
||||||
|
{
|
||||||
|
var dbconn = config.GetConnectionString("Database");
|
||||||
|
services.AddDbContext<DatabaseContext>(o => o.UseNpgsql(dbconn ?? ""));
|
||||||
|
|
||||||
|
// Add identity to our ef connection
|
||||||
|
services.AddIdentity<IdentityUser, IdentityRole>()
|
||||||
|
.AddRoles<IdentityRole>()
|
||||||
|
.AddEntityFrameworkStores<DatabaseContext>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void InjectTableClasses(IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddScoped<IArticlesRepository, ArticlesTable>();
|
||||||
|
services.AddScoped<IDiscordQueueRepository, DiscordQueueTable>();
|
||||||
|
services.AddScoped<IDiscordWebHooksRepository, DiscordWebhooksTable>();
|
||||||
|
services.AddScoped<IIconsRepository, IconsTable>();
|
||||||
|
services.AddScoped<ISourcesRepository, SourcesTable>();
|
||||||
|
services.AddScoped<IDiscordNotificationRepository, DiscordNotificationTable>();
|
||||||
|
services.AddScoped<IUserSourceSubscription, UserSourceSubscriptionTable>();
|
||||||
|
services.AddScoped<IRefreshTokenRepository, RefreshTokenTable>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void InjectIdentityService(IServiceCollection services)
|
||||||
|
{
|
||||||
|
// Configure Identity
|
||||||
|
services.AddScoped<IIdentityService, IdentityService>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task RunDatabaseMigrationsAsync(IServiceScope serviceScope)
|
||||||
|
{
|
||||||
|
var dbContext = serviceScope.ServiceProvider.GetRequiredService<DatabaseContext>();
|
||||||
|
await dbContext.Database.MigrateAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task InjectIdentityRolesAsync(IServiceScope serviceScope)
|
||||||
|
{
|
||||||
|
var roleManager = serviceScope.ServiceProvider.GetRequiredService<RoleManager<IdentityRole>>();
|
||||||
|
if (!await roleManager.RoleExistsAsync(Authorization.AdministratorsRole))
|
||||||
|
{
|
||||||
|
await roleManager.CreateAsync(new IdentityRole(Authorization.AdministratorsRole));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!await roleManager.RoleExistsAsync(Authorization.UsersRole))
|
||||||
|
{
|
||||||
|
await roleManager.CreateAsync(new IdentityRole(Authorization.UsersRole));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
50
Newsbot.Collector.Api/Startup/IdentityStartup.cs
Normal file
50
Newsbot.Collector.Api/Startup/IdentityStartup.cs
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
using System.Text;
|
||||||
|
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||||
|
using Microsoft.IdentityModel.Tokens;
|
||||||
|
using Newsbot.Collector.Api.Domain;
|
||||||
|
using Newsbot.Collector.Domain.Models.Config;
|
||||||
|
|
||||||
|
namespace Newsbot.Collector.Api.Startup;
|
||||||
|
|
||||||
|
public static class IdentityStartup
|
||||||
|
{
|
||||||
|
public static void DefineJwtRequirements(IServiceCollection services, IConfiguration config)
|
||||||
|
{
|
||||||
|
// Configure JWT for auth and load it into DI so we can use it in the controllers
|
||||||
|
|
||||||
|
var jwtSettings = new JwtSettings();
|
||||||
|
config.Bind(nameof(jwtSettings), jwtSettings);
|
||||||
|
services.AddSingleton(jwtSettings);
|
||||||
|
|
||||||
|
// Configure how the Token Validation will be handled
|
||||||
|
var tokenValidationParameters = new TokenValidationParameters
|
||||||
|
{
|
||||||
|
ValidateIssuerSigningKey = true,
|
||||||
|
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(jwtSettings.Secret ?? "")),
|
||||||
|
ValidateIssuer = false,
|
||||||
|
ValidateAudience = false,
|
||||||
|
RequireExpirationTime = false,
|
||||||
|
ValidateLifetime = true
|
||||||
|
};
|
||||||
|
services.AddSingleton(tokenValidationParameters);
|
||||||
|
|
||||||
|
// Build the Authentication that will be used
|
||||||
|
services.AddAuthentication(x =>
|
||||||
|
{
|
||||||
|
x.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||||
|
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||||
|
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||||
|
}).AddJwtBearer(x =>
|
||||||
|
{
|
||||||
|
x.SaveToken = true;
|
||||||
|
x.TokenValidationParameters = tokenValidationParameters;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Build the Authorization Policy that the users will conform to.
|
||||||
|
services.AddAuthorization(options =>
|
||||||
|
{
|
||||||
|
options.AddPolicy(Authorization.AdministratorPolicy,
|
||||||
|
b => b.RequireClaim(Authorization.AdministratorClaim, "true"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
67
Newsbot.Collector.Api/Startup/SwaggerStartup.cs
Normal file
67
Newsbot.Collector.Api/Startup/SwaggerStartup.cs
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
using Microsoft.OpenApi.Models;
|
||||||
|
|
||||||
|
namespace Newsbot.Collector.Api.Startup;
|
||||||
|
|
||||||
|
public static class SwaggerStartup
|
||||||
|
{
|
||||||
|
public static void ConfigureSwagger(IServiceCollection services)
|
||||||
|
{
|
||||||
|
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
|
||||||
|
services.AddEndpointsApiExplorer();
|
||||||
|
services.AddSwaggerGen();
|
||||||
|
|
||||||
|
services.AddEndpointsApiExplorer();
|
||||||
|
services.AddSwaggerGen();
|
||||||
|
|
||||||
|
// Configure swagger authentication
|
||||||
|
services.AddSwaggerGen(cfg =>
|
||||||
|
{
|
||||||
|
cfg.AddSecurityDefinition("ApiKey", new OpenApiSecurityScheme
|
||||||
|
{
|
||||||
|
Description = "The API key to access the API",
|
||||||
|
Type = SecuritySchemeType.ApiKey,
|
||||||
|
Name = "x-api-key",
|
||||||
|
In = ParameterLocation.Header,
|
||||||
|
Scheme = "ApiKeyScheme"
|
||||||
|
});
|
||||||
|
|
||||||
|
cfg.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
|
||||||
|
{
|
||||||
|
Description = "JWT Authorization Header using the bearer scheme",
|
||||||
|
Name = "Authorization",
|
||||||
|
In = ParameterLocation.Header,
|
||||||
|
Type = SecuritySchemeType.ApiKey
|
||||||
|
});
|
||||||
|
|
||||||
|
cfg.AddSecurityRequirement(new OpenApiSecurityRequirement
|
||||||
|
{
|
||||||
|
//{
|
||||||
|
// new OpenApiSecurityScheme
|
||||||
|
// {
|
||||||
|
// Reference = new OpenApiReference
|
||||||
|
// {
|
||||||
|
// Type = ReferenceType.SecurityScheme,
|
||||||
|
// Id = "ApiKey"
|
||||||
|
// },
|
||||||
|
// In = ParameterLocation.Header
|
||||||
|
// },
|
||||||
|
// new List<string>()
|
||||||
|
//},
|
||||||
|
{
|
||||||
|
new OpenApiSecurityScheme
|
||||||
|
{
|
||||||
|
Reference = new OpenApiReference
|
||||||
|
{
|
||||||
|
Type = ReferenceType.SecurityScheme,
|
||||||
|
Id = "Bearer"
|
||||||
|
},
|
||||||
|
Scheme = "oauth2",
|
||||||
|
Name = "Bearer",
|
||||||
|
In = ParameterLocation.Header
|
||||||
|
},
|
||||||
|
new List<string>()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user