diff --git a/.gitignore b/.gitignore index 0290c0055..75589ba35 100644 --- a/.gitignore +++ b/.gitignore @@ -443,5 +443,6 @@ $RECYCLE.BIN/ # App specific appsettings.json -/API/datingapp.db-shm -/API/datingapp.db-wal \ No newline at end of file +/API/kavita.db +/API/kavita.db-shm +/API/kavita.db-wal \ No newline at end of file diff --git a/API/API.csproj b/API/API.csproj index fb104f05e..784cff6b4 100644 --- a/API/API.csproj +++ b/API/API.csproj @@ -5,6 +5,7 @@ + diff --git a/API/Controllers/AccountController.cs b/API/Controllers/AccountController.cs new file mode 100644 index 000000000..a621c1086 --- /dev/null +++ b/API/Controllers/AccountController.cs @@ -0,0 +1,94 @@ +using System; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; +using API.Data; +using API.DTOs; +using API.Entities; +using API.Interfaces; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +namespace API.Controllers +{ + public class AccountController : BaseApiController + { + private readonly DataContext _context; + private readonly ITokenService _tokenService; + private readonly IUserRepository _userRepository; + private readonly ILogger _logger; + + public AccountController(DataContext context, ITokenService tokenService, IUserRepository userRepository, ILogger logger) + { + _context = context; + _tokenService = tokenService; + _userRepository = userRepository; + _logger = logger; + } + + [HttpPost("register")] + public async Task> Register(RegisterDto registerDto) + { + _logger.LogInformation("Username: " + registerDto.Password); + if (await UserExists(registerDto.Username)) + { + return BadRequest("Username is taken."); + } + + using var hmac = new HMACSHA512(); + var user = new AppUser + { + UserName = registerDto.Username.ToLower(), + PasswordHash = hmac.ComputeHash(Encoding.UTF8.GetBytes(registerDto.Password)), + PasswordSalt = hmac.Key, + IsAdmin = registerDto.IsAdmin, + LastActive = DateTime.Now + }; + + _context.Users.Add(user); + await _context.SaveChangesAsync(); + + return new UserDto() + { + Username = user.UserName, + Token = _tokenService.CreateToken(user), + IsAdmin = user.IsAdmin + }; + } + + [HttpPost("login")] + public async Task> Login(LoginDto loginDto) + { + var user = await _userRepository.GetUserByUsernameAsync(loginDto.Username); + + if (user == null) return Unauthorized("Invalid username"); + + using var hmac = new HMACSHA512(user.PasswordSalt); + + var computedHash = hmac.ComputeHash(Encoding.UTF8.GetBytes(loginDto.Password)); + + for (int i = 0; i < computedHash.Length; i++) + { + if (computedHash[i] != user.PasswordHash[i]) return Unauthorized("Invalid password"); + } + + // Update LastActive on account + user.LastActive = DateTime.Now; + _userRepository.Update(user); + await _userRepository.SaveAllAsync(); + + return new UserDto() + { + Username = user.UserName, + Token = _tokenService.CreateToken(user), + IsAdmin = user.IsAdmin + }; + } + + private async Task UserExists(string username) + { + return await _context.Users.AnyAsync(user => user.UserName == username.ToLower()); + } + } +} \ No newline at end of file diff --git a/API/Controllers/BaseApiController.cs b/API/Controllers/BaseApiController.cs new file mode 100644 index 000000000..bb3886ab8 --- /dev/null +++ b/API/Controllers/BaseApiController.cs @@ -0,0 +1,10 @@ +using Microsoft.AspNetCore.Mvc; + +namespace API.Controllers +{ + [ApiController] + [Route("api/[controller]")] + public class BaseApiController : ControllerBase + { + } +} \ No newline at end of file diff --git a/API/Controllers/UsersController.cs b/API/Controllers/UsersController.cs new file mode 100644 index 000000000..dd27d3f15 --- /dev/null +++ b/API/Controllers/UsersController.cs @@ -0,0 +1,28 @@ +using System.Collections; +using System.Collections.Generic; +using System.Threading.Tasks; +using API.Data; +using API.DTOs; +using API.Interfaces; +using Microsoft.AspNetCore.Mvc; + +namespace API.Controllers +{ + public class UsersController : BaseApiController + { + private readonly DataContext _context; + private readonly IUserRepository _userRepository; + + public UsersController(DataContext context, IUserRepository userRepository) + { + _context = context; + _userRepository = userRepository; + } + + [HttpGet] + public async Task>> GetUsers() + { + return Ok(await _userRepository.GetMembersAsync()); + } + } +} \ No newline at end of file diff --git a/API/DTOs/LoginDto.cs b/API/DTOs/LoginDto.cs new file mode 100644 index 000000000..9983415e0 --- /dev/null +++ b/API/DTOs/LoginDto.cs @@ -0,0 +1,8 @@ +namespace API.DTOs +{ + public class LoginDto + { + public string Username { get; set; } + public string Password { get; set; } + } +} \ No newline at end of file diff --git a/API/DTOs/MemberDto.cs b/API/DTOs/MemberDto.cs new file mode 100644 index 000000000..a1f8b377b --- /dev/null +++ b/API/DTOs/MemberDto.cs @@ -0,0 +1,16 @@ +using System; + +namespace API.DTOs +{ + /// + /// Represents a member of a Kavita server. + /// + public class MemberDto + { + public int Id { get; set; } + public string Username { get; set; } + public DateTime Created { get; set; } + public DateTime LastActive { get; set; } + public bool IsAdmin { get; set; } + } +} \ No newline at end of file diff --git a/API/DTOs/RegisterDto.cs b/API/DTOs/RegisterDto.cs new file mode 100644 index 000000000..61fd26f96 --- /dev/null +++ b/API/DTOs/RegisterDto.cs @@ -0,0 +1,14 @@ +using System.ComponentModel.DataAnnotations; + +namespace API.DTOs +{ + public class RegisterDto + { + [Required] + public string Username { get; set; } + [Required] + [StringLength(8, MinimumLength = 4)] + public string Password { get; set; } + public bool IsAdmin { get; set; } + } +} \ No newline at end of file diff --git a/API/DTOs/UserDto.cs b/API/DTOs/UserDto.cs new file mode 100644 index 000000000..c8b97abb6 --- /dev/null +++ b/API/DTOs/UserDto.cs @@ -0,0 +1,9 @@ +namespace API.DTOs +{ + public class UserDto + { + public string Username { get; set; } + public string Token { get; set; } + public bool IsAdmin { get; set; } + } +} \ No newline at end of file diff --git a/API/Data/DataContext.cs b/API/Data/DataContext.cs new file mode 100644 index 000000000..bca4e24be --- /dev/null +++ b/API/Data/DataContext.cs @@ -0,0 +1,15 @@ +using API.Entities; +using Microsoft.EntityFrameworkCore; + +namespace API.Data +{ + public class DataContext : DbContext + { + public DataContext(DbContextOptions options) : base(options) + { + + } + + public DbSet Users { get; set; } + } +} \ No newline at end of file diff --git a/API/Data/Migrations/20201213205325_AddUser.Designer.cs b/API/Data/Migrations/20201213205325_AddUser.Designer.cs new file mode 100644 index 000000000..565d03517 --- /dev/null +++ b/API/Data/Migrations/20201213205325_AddUser.Designer.cs @@ -0,0 +1,56 @@ +// +using System; +using API.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace API.Data.Migrations +{ + [DbContext(typeof(DataContext))] + [Migration("20201213205325_AddUser")] + partial class AddUser + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "5.0.1"); + + modelBuilder.Entity("API.Entities.AppUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("IsAdmin") + .HasColumnType("INTEGER"); + + b.Property("LastActive") + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("BLOB"); + + b.Property("PasswordSalt") + .HasColumnType("BLOB"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/API/Data/Migrations/20201213205325_AddUser.cs b/API/Data/Migrations/20201213205325_AddUser.cs new file mode 100644 index 000000000..4429111b1 --- /dev/null +++ b/API/Data/Migrations/20201213205325_AddUser.cs @@ -0,0 +1,36 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace API.Data.Migrations +{ + public partial class AddUser : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Users", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + UserName = table.Column(type: "TEXT", nullable: true), + PasswordHash = table.Column(type: "BLOB", nullable: true), + PasswordSalt = table.Column(type: "BLOB", nullable: true), + Created = table.Column(type: "TEXT", nullable: false), + LastActive = table.Column(type: "TEXT", nullable: false), + IsAdmin = table.Column(type: "INTEGER", nullable: false), + RowVersion = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Users", x => x.Id); + }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Users"); + } + } +} diff --git a/API/Data/Migrations/DataContextModelSnapshot.cs b/API/Data/Migrations/DataContextModelSnapshot.cs new file mode 100644 index 000000000..9ed038826 --- /dev/null +++ b/API/Data/Migrations/DataContextModelSnapshot.cs @@ -0,0 +1,54 @@ +// +using System; +using API.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace API.Data.Migrations +{ + [DbContext(typeof(DataContext))] + partial class DataContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "5.0.1"); + + modelBuilder.Entity("API.Entities.AppUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("IsAdmin") + .HasColumnType("INTEGER"); + + b.Property("LastActive") + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("BLOB"); + + b.Property("PasswordSalt") + .HasColumnType("BLOB"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/API/Data/UserRepository.cs b/API/Data/UserRepository.cs new file mode 100644 index 000000000..2308525a4 --- /dev/null +++ b/API/Data/UserRepository.cs @@ -0,0 +1,61 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using API.DTOs; +using API.Entities; +using API.Interfaces; +using AutoMapper; +using AutoMapper.QueryableExtensions; +using Microsoft.EntityFrameworkCore; + +namespace API.Data +{ + public class UserRepository : IUserRepository + { + private readonly DataContext _context; + private readonly IMapper _mapper; + + public UserRepository(DataContext context, IMapper mapper) + { + _context = context; + _mapper = mapper; + } + + public void Update(AppUser user) + { + _context.Entry(user).State = EntityState.Modified; + } + + public async Task SaveAllAsync() + { + return await _context.SaveChangesAsync() > 0; + } + + public async Task> GetUsersAsync() + { + return await _context.Users.ToListAsync(); + } + + public async Task GetUserByIdAsync(int id) + { + return await _context.Users.FindAsync(id); + } + + public async Task GetUserByUsernameAsync(string username) + { + return await _context.Users.SingleOrDefaultAsync(x => x.UserName == username); + } + + public async Task> GetMembersAsync() + { + return await _context.Users.ProjectTo(_mapper.ConfigurationProvider).ToListAsync(); + } + + public async Task GetMemberAsync(string username) + { + return await _context.Users.Where(x => x.UserName == username) + .ProjectTo(_mapper.ConfigurationProvider) + .SingleOrDefaultAsync(); + } + } +} \ No newline at end of file diff --git a/API/Entities/AppUser.cs b/API/Entities/AppUser.cs new file mode 100644 index 000000000..a15e894c4 --- /dev/null +++ b/API/Entities/AppUser.cs @@ -0,0 +1,27 @@ +using System; +using System.ComponentModel.DataAnnotations; +using API.Entities.Interfaces; + + +namespace API.Entities +{ + public class AppUser : IHasConcurrencyToken + { + public int Id { get; set; } + public string UserName { get; set; } + public byte[] PasswordHash { get; set; } + public byte[] PasswordSalt { get; set; } + public DateTime Created { get; set; } = DateTime.Now; + public DateTime LastActive { get; set; } + public bool IsAdmin { get; set; } + + [ConcurrencyCheck] + public uint RowVersion { get; set; } + + public void OnSavingChanges() + { + RowVersion++; + } + + } +} \ No newline at end of file diff --git a/API/Entities/Interfaces/IHasConcurrencyToken.cs b/API/Entities/Interfaces/IHasConcurrencyToken.cs new file mode 100644 index 000000000..9372f1eb7 --- /dev/null +++ b/API/Entities/Interfaces/IHasConcurrencyToken.cs @@ -0,0 +1,19 @@ +namespace API.Entities.Interfaces +{ + /// + /// An interface abstracting an entity that has a concurrency token. + /// + public interface IHasConcurrencyToken + { + /// + /// Gets the version of this row. Acts as a concurrency token. + /// + uint RowVersion { get; } + + /// + /// Called when saving changes to this entity. + /// + void OnSavingChanges(); + + } +} \ No newline at end of file diff --git a/API/Errors/ApiException.cs b/API/Errors/ApiException.cs new file mode 100644 index 000000000..3f026bb64 --- /dev/null +++ b/API/Errors/ApiException.cs @@ -0,0 +1,16 @@ +namespace API.Errors +{ + public class ApiException + { + public int Status { get; set; } + public string Message { get; set; } + public string Details { get; set; } + + public ApiException(int status, string message = null, string details = null) + { + Status = status; + Message = message; + Details = details; + } + } +} \ No newline at end of file diff --git a/API/Extensions/ApplicationServiceExtensions.cs b/API/Extensions/ApplicationServiceExtensions.cs new file mode 100644 index 000000000..07b096c95 --- /dev/null +++ b/API/Extensions/ApplicationServiceExtensions.cs @@ -0,0 +1,27 @@ +using API.Data; +using API.Helpers; +using API.Interfaces; +using API.Services; +using AutoMapper; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace API.Extensions +{ + public static class ApplicationServiceExtensions + { + public static IServiceCollection AddApplicationServices(this IServiceCollection services, IConfiguration config) + { + services.AddAutoMapper(typeof(AutoMapperProfiles).Assembly); + services.AddScoped(); + services.AddScoped(); + services.AddDbContext(options => + { + options.UseSqlite(config.GetConnectionString("DefaultConnection")); + }); + + return services; + } + } +} \ No newline at end of file diff --git a/API/Extensions/ClaimsPrincipalExtensions.cs b/API/Extensions/ClaimsPrincipalExtensions.cs new file mode 100644 index 000000000..3dacfc854 --- /dev/null +++ b/API/Extensions/ClaimsPrincipalExtensions.cs @@ -0,0 +1,12 @@ +using System.Security.Claims; + +namespace API.Extensions +{ + public static class ClaimsPrincipalExtensions + { + public static string GetUsername(this ClaimsPrincipal user) + { + return user.FindFirst(ClaimTypes.NameIdentifier)?.Value; + } + } +} \ No newline at end of file diff --git a/API/Extensions/IdentityServiceExtensions.cs b/API/Extensions/IdentityServiceExtensions.cs new file mode 100644 index 000000000..386efc0f2 --- /dev/null +++ b/API/Extensions/IdentityServiceExtensions.cs @@ -0,0 +1,28 @@ +using System.Collections; +using System.Text; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.IdentityModel.Tokens; + +namespace API.Extensions +{ + public static class IdentityServiceExtensions + { + public static IServiceCollection AddIdentityServices(this IServiceCollection services, IConfiguration config) + { + services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) + .AddJwtBearer(options => + { + options.TokenValidationParameters = new TokenValidationParameters() + { + ValidateIssuerSigningKey = true, + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config["TokenKey"])), + ValidateIssuer = false, + ValidateAudience = false + }; + }); + return services; + } + } +} \ No newline at end of file diff --git a/API/Helpers/AutoMapperProfiles.cs b/API/Helpers/AutoMapperProfiles.cs new file mode 100644 index 000000000..49da92f21 --- /dev/null +++ b/API/Helpers/AutoMapperProfiles.cs @@ -0,0 +1,14 @@ +using API.DTOs; +using API.Entities; +using AutoMapper; + +namespace API.Helpers +{ + public class AutoMapperProfiles : Profile + { + public AutoMapperProfiles() + { + CreateMap(); + } + } +} \ No newline at end of file diff --git a/API/Interfaces/ITokenService.cs b/API/Interfaces/ITokenService.cs new file mode 100644 index 000000000..e721d9ade --- /dev/null +++ b/API/Interfaces/ITokenService.cs @@ -0,0 +1,9 @@ +using API.Entities; + +namespace API.Interfaces +{ + public interface ITokenService + { + string CreateToken(AppUser user); + } +} \ No newline at end of file diff --git a/API/Interfaces/IUserRepository.cs b/API/Interfaces/IUserRepository.cs new file mode 100644 index 000000000..69b872821 --- /dev/null +++ b/API/Interfaces/IUserRepository.cs @@ -0,0 +1,19 @@ +using System.Collections; +using System.Collections.Generic; +using System.Threading.Tasks; +using API.DTOs; +using API.Entities; + +namespace API.Interfaces +{ + public interface IUserRepository + { + void Update(AppUser user); + Task SaveAllAsync(); + Task> GetUsersAsync(); + Task GetUserByIdAsync(int id); + Task GetUserByUsernameAsync(string username); + Task> GetMembersAsync(); + Task GetMemberAsync(string username); + } +} \ No newline at end of file diff --git a/API/Middleware/ExceptionMiddleware.cs b/API/Middleware/ExceptionMiddleware.cs new file mode 100644 index 000000000..413fbdb16 --- /dev/null +++ b/API/Middleware/ExceptionMiddleware.cs @@ -0,0 +1,56 @@ +using System; +using System.Net; +using System.Text.Json; +using System.Threading.Tasks; +using API.Errors; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace API.Middleware +{ + public class ExceptionMiddleware + { + private readonly RequestDelegate _next; + private readonly ILogger _logger; + private readonly IHostEnvironment _env; + + + public ExceptionMiddleware(RequestDelegate next, ILogger logger, IHostEnvironment env) + { + _next = next; + _logger = logger; + _env = env; + } + + public async Task InvokeAsync(HttpContext context) + { + _logger.LogError("The middleware called"); + try + { + await _next(context); // downstream middlewares or http call + } + catch (Exception ex) + { + _logger.LogError(ex, ex.Message); + context.Response.ContentType = "application/json"; + context.Response.StatusCode = (int) HttpStatusCode.InternalServerError; + + var response = _env.IsDevelopment() + ? new ApiException(context.Response.StatusCode, ex.Message, ex.StackTrace) + : new ApiException(context.Response.StatusCode, "Internal Server Error"); + + var options = new JsonSerializerOptions + { + PropertyNamingPolicy = + JsonNamingPolicy.CamelCase + }; + + var json = JsonSerializer.Serialize(response, options); + + await context.Response.WriteAsync(json); + + } + } + } +} \ No newline at end of file diff --git a/API/Program.cs b/API/Program.cs index 0f618b9ff..f3a23a6c1 100644 --- a/API/Program.cs +++ b/API/Program.cs @@ -1,9 +1,9 @@ using System; -using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; +using API.Data; using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; @@ -11,9 +11,26 @@ namespace API { public class Program { - public static void Main(string[] args) + public static async Task Main(string[] args) { - CreateHostBuilder(args).Build().Run(); + var host = CreateHostBuilder(args).Build(); + + using var scope = host.Services.CreateScope(); + var services = scope.ServiceProvider; + + try + { + var context = services.GetRequiredService(); + // Apply all migrations on startup + await context.Database.MigrateAsync(); + } + catch (Exception ex) + { + var logger = services.GetRequiredService < ILogger>(); + logger.LogError(ex, "An error occurred during migration"); + } + + await host.RunAsync(); } public static IHostBuilder CreateHostBuilder(string[] args) => diff --git a/API/Properties/launchSettings.json b/API/Properties/launchSettings.json index 85ac7db6f..677d81685 100644 --- a/API/Properties/launchSettings.json +++ b/API/Properties/launchSettings.json @@ -20,7 +20,7 @@ "API": { "commandName": "Project", "dotnetRunMessages": "true", - "launchBrowser": true, + "launchBrowser": false, "launchUrl": "swagger", "applicationUrl": "https://localhost:5001;http://localhost:5000", "environmentVariables": { diff --git a/API/Services/TokenService.cs b/API/Services/TokenService.cs new file mode 100644 index 000000000..195af02d2 --- /dev/null +++ b/API/Services/TokenService.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; +using API.Entities; +using API.Interfaces; +using Microsoft.Extensions.Configuration; +using Microsoft.IdentityModel.Tokens; +using JwtRegisteredClaimNames = Microsoft.IdentityModel.JsonWebTokens.JwtRegisteredClaimNames; + + +namespace API.Services +{ + public class TokenService : ITokenService + { + private readonly SymmetricSecurityKey _key; + + public TokenService(IConfiguration config) + { + _key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config["TokenKey"])); + } + + public string CreateToken(AppUser user) + { + var claims = new List + { + new Claim(JwtRegisteredClaimNames.NameId, user.UserName) + }; + + var creds = new SigningCredentials(_key, SecurityAlgorithms.HmacSha512Signature); + + var tokenDescriptor = new SecurityTokenDescriptor() + { + Subject = new ClaimsIdentity(claims), + Expires = DateTime.Now.AddDays(7), + SigningCredentials = creds + }; + + var tokenHandler = new JwtSecurityTokenHandler(); + var token = tokenHandler.CreateToken(tokenDescriptor); + + return tokenHandler.WriteToken(token); + } + } +} \ No newline at end of file diff --git a/API/Startup.cs b/API/Startup.cs index 622d31cb2..b691a71a4 100644 --- a/API/Startup.cs +++ b/API/Startup.cs @@ -1,33 +1,31 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +using API.Extensions; +using API.Middleware; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.HttpsPolicy; -using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; using Microsoft.OpenApi.Models; namespace API { public class Startup { - public Startup(IConfiguration configuration) - { - Configuration = configuration; - } + private readonly IConfiguration _config; - public IConfiguration Configuration { get; } + public Startup(IConfiguration config) + { + _config = config; + } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { + services.AddApplicationServices(_config); services.AddControllers(); + services.AddCors(); + services.AddIdentityServices(_config); services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo { Title = "API", Version = "v1" }); @@ -37,9 +35,11 @@ namespace API // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { + app.UseMiddleware(); + if (env.IsDevelopment()) { - app.UseDeveloperExceptionPage(); + //app.UseDeveloperExceptionPage(); app.UseSwagger(); app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "API v1")); } @@ -47,6 +47,11 @@ namespace API app.UseHttpsRedirection(); app.UseRouting(); + + // Ordering is important. Cors, authentication, authorization + app.UseCors(policy => policy.AllowAnyHeader().AllowAnyMethod().WithOrigins("https://localhost:4200")); + + app.UseAuthentication(); app.UseAuthorization();