From 73bfd8d01c986eeb81761bcb8cbf44caa60133bb Mon Sep 17 00:00:00 2001 From: Jeas0001 Date: Thu, 24 Apr 2025 12:27:58 +0200 Subject: [PATCH] UserController done --- .gitignore | 2 +- backend/API/API.csproj | 11 + backend/API/BusinessLogic/UserLogic.cs | 236 ++++++++++++++++++ backend/API/Controllers/UserController.cs | 78 ++++++ .../Controllers/WeatherForecastController.cs | 33 --- backend/API/DBAccess/DBContext.cs | 12 + backend/API/DBAccess/DbAccess.cs | 93 +++++++ .../20250423074254_init.Designer.cs | 59 +++++ backend/API/Migrations/20250423074254_init.cs | 45 ++++ .../API/Migrations/DBContextModelSnapshot.cs | 56 +++++ .../Models/UserModels/ChangePasswordDTO.cs | 9 + .../API/Models/UserModels/CreateUserDTO.cs | 11 + backend/API/Models/UserModels/LoginDTO.cs | 9 + .../API/Models/UserModels/UpdateUserDTO.cs | 9 + backend/API/Models/UserModels/User.cs | 19 ++ backend/API/Program.cs | 45 ++-- backend/API/Startup.cs | 122 +++++++++ backend/API/WeatherForecast.cs | 13 - 18 files changed, 797 insertions(+), 65 deletions(-) create mode 100644 backend/API/BusinessLogic/UserLogic.cs create mode 100644 backend/API/Controllers/UserController.cs delete mode 100644 backend/API/Controllers/WeatherForecastController.cs create mode 100644 backend/API/DBAccess/DBContext.cs create mode 100644 backend/API/DBAccess/DbAccess.cs create mode 100644 backend/API/Migrations/20250423074254_init.Designer.cs create mode 100644 backend/API/Migrations/20250423074254_init.cs create mode 100644 backend/API/Migrations/DBContextModelSnapshot.cs create mode 100644 backend/API/Models/UserModels/ChangePasswordDTO.cs create mode 100644 backend/API/Models/UserModels/CreateUserDTO.cs create mode 100644 backend/API/Models/UserModels/LoginDTO.cs create mode 100644 backend/API/Models/UserModels/UpdateUserDTO.cs create mode 100644 backend/API/Models/UserModels/User.cs create mode 100644 backend/API/Startup.cs delete mode 100644 backend/API/WeatherForecast.cs diff --git a/.gitignore b/.gitignore index 223a8e2..da7a856 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,4 @@ /backend/API/obj /backend/API/appsettings.Development.json /backend/API/appsettings.json -/backend/API/bin/Debug/net8.0 +*bin diff --git a/backend/API/API.csproj b/backend/API/API.csproj index 9daa180..1e9549b 100644 --- a/backend/API/API.csproj +++ b/backend/API/API.csproj @@ -7,6 +7,17 @@ + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + diff --git a/backend/API/BusinessLogic/UserLogic.cs b/backend/API/BusinessLogic/UserLogic.cs new file mode 100644 index 0000000..07fb887 --- /dev/null +++ b/backend/API/BusinessLogic/UserLogic.cs @@ -0,0 +1,236 @@ +using API.DBAccess; +using API.Models.UserModels; +using Microsoft.AspNetCore.Mvc; +using Microsoft.IdentityModel.Tokens; +using Org.BouncyCastle.Asn1.Ocsp; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Security.Cryptography; +using System.Text; +using System.Text.RegularExpressions; + +namespace API.BusinessLogic +{ + public class UserLogic + { + private readonly DbAccess _dbAccess; + private readonly IConfiguration _configuration; + + public UserLogic(IConfiguration configuration, DbAccess dbAccess) + { + _dbAccess = dbAccess; + _configuration = configuration; + } + + public async Task GetUser(int userId) + { + User user = await _dbAccess.ReadUser(userId); + + if (user == null || user.Id == 0) { return new ConflictObjectResult(new { message = "Could not find user" }); } + return new OkObjectResult(new { user.Id, user.UserName, user.Email }); + } + + public async Task RegisterUser(CreateUserDTO userDTO) + { + if (!EmailCheck(userDTO.Email)) + { + return new ConflictObjectResult(new { message = "Invalid email address" }); + } + + if (!PasswordSecurity(userDTO.Password)) + { + return new ConflictObjectResult(new { message = "Password is not up to the security standard" }); + } + + var users = await _dbAccess.ReadAllUsers(); + + foreach (var item in users) + { + if (item.UserName == userDTO.UserName) + { + return new ConflictObjectResult(new { message = "Username is already in use." }); + } + + if (item.Email == userDTO.Email) + { + return new ConflictObjectResult(new { message = "Email is already in use." }); + } + } + + string salt = Guid.NewGuid().ToString(); + string hashedPassword = ComputeHash(userDTO.Password, SHA256.Create(), salt); + + User user = new User + { + UserName = userDTO.UserName, + Email = userDTO.Email, + Password = hashedPassword, + Salt = salt, + }; + + return await _dbAccess.CreateUser(user); + } + + public async Task Login(LoginDTO loginDTO) + { + var user = await _dbAccess.ReadUserForLogin(loginDTO.EmailUsr); + + if (user == null || user.Id == 0) { return new ConflictObjectResult(new { message = "Could not find user" }); } + + string hashedPassword = ComputeHash(loginDTO.Password, SHA256.Create(), user.Salt); + + if (user.Password == hashedPassword) + { + var token = GenerateJwtToken(user); + + user = await UpdateRefreshToken(user); + + return new OkObjectResult(new { token, user.UserName, user.Id, refreshToken = user.RefreshToken }); + } + + return new ConflictObjectResult(new { message = "Invalid password" }); + } + + public async Task EditProfile(UpdateUserDTO userDTO, int userId) + { + var profile = await _dbAccess.ReadUser(userId); + var users = await _dbAccess.ReadAllUsers(); + + if (profile == null) { return new ConflictObjectResult(new { message = "User does not exist" }); } + + if (!EmailCheck(userDTO.Email)) + { + return new ConflictObjectResult(new { message = "Invalid email address" }); + } + + foreach (var item in users) + { + if (item.UserName == userDTO.UserName) + { + return new ConflictObjectResult(new { message = "Username is already in use." }); + } + + if (item.Email == userDTO.Email) + { + return new ConflictObjectResult(new { message = "Email is already in use." }); + } + } + + if (userDTO.Email == "" || userDTO.Email == null) + { + return new ConflictObjectResult(new { message = "Please enter an email" }); + } + + if (userDTO.UserName == "" || userDTO.UserName == null) + { + return new ConflictObjectResult(new { message = "Please enter an userName" }); + } + + profile.Email = userDTO.Email; + profile.UserName = userDTO.UserName; + + return await _dbAccess.UpdateUser(profile); + } + + public async Task ChangePassword(ChangePasswordDTO passwordDTO, int userId) + { + var user = await _dbAccess.ReadUser(userId); + + if (user == null) { return new ConflictObjectResult(new { message = "User does not exist" }); } + + string hashedPassword = ComputeHash(passwordDTO.OldPassword, SHA256.Create(), user.Salt); + + if (user.Password != hashedPassword) + { + return new ConflictObjectResult(new { message = "Old password is incorrect" }); + + } + + if (!PasswordSecurity(passwordDTO.NewPassword)) + { + return new ConflictObjectResult(new { message = "New password is not up to the security standard" }); + } + + string hashedNewPassword = ComputeHash(passwordDTO.NewPassword, SHA256.Create(), user.Salt); + user.Password = hashedNewPassword; + + return await _dbAccess.UpdatePassword(user); + } + + public async Task DeleteUser(int userId) + { + var user = await _dbAccess.ReadUserForDelete(userId); + + if (user != null) { return await _dbAccess.DeleteUser(user); } + + return new ConflictObjectResult(new { message = "Invalid user" }); + } + + public async Task RefreshToken(string refreshToken) + { + User user = await _dbAccess.ReadUserByRefreshToken(refreshToken); + if (user == null) { return new ConflictObjectResult(new { message = "Could not match refreshtoken" }); } + user = await UpdateRefreshToken(user); + string jwtToken = GenerateJwtToken(user); + return new OkObjectResult(new { token = jwtToken, refreshToken = user.RefreshToken }); + } + + private bool PasswordSecurity(string password) + { + var hasMinimum8Chars = new Regex(@".{8,}"); + + return hasMinimum8Chars.IsMatch(password); + } + + private bool EmailCheck(string email) + { + return new Regex(@".+@.+\..+").IsMatch(email); + } + + private static string ComputeHash(string input, HashAlgorithm algorithm, string salt) + { + Byte[] inputBytes = Encoding.UTF8.GetBytes(input); + Byte[] saltBytes = Encoding.UTF8.GetBytes(salt); + + // Combine salt and input bytes + Byte[] saltedInput = new Byte[saltBytes.Length + inputBytes.Length]; + saltBytes.CopyTo(saltedInput, 0); + inputBytes.CopyTo(saltedInput, saltBytes.Length); + + Byte[] hashedBytes = algorithm.ComputeHash(saltedInput); + + return BitConverter.ToString(hashedBytes); + } + + private string GenerateJwtToken(User user) + { + var claims = new[] + { + new Claim(JwtRegisteredClaimNames.Sub, user.Id.ToString()), + new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), + new Claim(ClaimTypes.Name, user.UserName) + }; + + var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes + (_configuration["JwtSettings:Key"])); + var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); + + var token = new JwtSecurityToken( + _configuration["JwtSettings:Issuer"], + _configuration["JwtSettings:Audience"], + claims, + expires: DateTime.Now.AddHours(1), + signingCredentials: creds); + + return new JwtSecurityTokenHandler().WriteToken(token); + } + + private async Task UpdateRefreshToken(User user) + { + user.RefreshToken = Guid.NewGuid().ToString(); + user.RefreshTokenExpireAt = DateTime.Now.AddDays(30); + await _dbAccess.UpdateUser(user); + return user; + } + } +} diff --git a/backend/API/Controllers/UserController.cs b/backend/API/Controllers/UserController.cs new file mode 100644 index 0000000..30ecf89 --- /dev/null +++ b/backend/API/Controllers/UserController.cs @@ -0,0 +1,78 @@ +using API.BusinessLogic; +using API.Models.UserModels; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using System.Security.Claims; + +namespace API.Controllers +{ + [ApiController] + [Route("api/[controller]")] + public class UserController : Controller + { + private readonly UserLogic _userLogic; + + public UserController(UserLogic userLogic) + { + _userLogic = userLogic; + } + + [Authorize] + [HttpGet("get")] + public async Task ReadUser() + { + var claims = HttpContext.User.Claims; + string userIdString = claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier).Value; + int userId = Convert.ToInt32(userIdString); + return await _userLogic.GetUser(userId); + } + + [HttpPost("login")] + public async Task Login([FromBody] LoginDTO loginDTO) + { + return await _userLogic.Login(loginDTO); + } + + [HttpPost("create")] + public async Task CreateUser([FromBody] CreateUserDTO userDTO) + { + return await _userLogic.RegisterUser(userDTO); + } + + [Authorize] + [HttpPut("change-password")] + public async Task ChangePassword([FromBody] ChangePasswordDTO passwordDTO) + { + var claims = HttpContext.User.Claims; + string userIdString = claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier).Value; + int userId = Convert.ToInt32(userIdString); + return await _userLogic.ChangePassword(passwordDTO, userId); + } + + [Authorize] + [HttpPut("update")] + public async Task UpdateUser([FromBody] UpdateUserDTO userDTO) + { + var claims = HttpContext.User.Claims; + string userIdString = claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier).Value; + int userId = Convert.ToInt32(userIdString); + return await _userLogic.EditProfile(userDTO, userId); + } + + [Authorize] + [HttpDelete("delete")] + public async Task DeleteUser() + { + var claims = HttpContext.User.Claims; + string userIdString = claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier).Value; + int userId = Convert.ToInt32(userIdString); + return await _userLogic.DeleteUser(userId); + } + + [HttpPost("refreshtoken/{refreshToken}")] + public async Task RefreashToken(string refreshToken) + { + return await _userLogic.RefreshToken(refreshToken); + } + } +} diff --git a/backend/API/Controllers/WeatherForecastController.cs b/backend/API/Controllers/WeatherForecastController.cs deleted file mode 100644 index aaf3a93..0000000 --- a/backend/API/Controllers/WeatherForecastController.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Microsoft.AspNetCore.Mvc; - -namespace API.Controllers -{ - [ApiController] - [Route("[controller]")] - public class WeatherForecastController : ControllerBase - { - private static readonly string[] Summaries = new[] - { - "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" - }; - - private readonly ILogger _logger; - - public WeatherForecastController(ILogger logger) - { - _logger = logger; - } - - [HttpGet(Name = "GetWeatherForecast")] - public IEnumerable Get() - { - return Enumerable.Range(1, 5).Select(index => new WeatherForecast - { - Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), - TemperatureC = Random.Shared.Next(-20, 55), - Summary = Summaries[Random.Shared.Next(Summaries.Length)] - }) - .ToArray(); - } - } -} diff --git a/backend/API/DBAccess/DBContext.cs b/backend/API/DBAccess/DBContext.cs new file mode 100644 index 0000000..e7fffff --- /dev/null +++ b/backend/API/DBAccess/DBContext.cs @@ -0,0 +1,12 @@ +using API.Models.UserModels; +using Microsoft.EntityFrameworkCore; + +namespace API.DBAccess +{ + public class DBContext : DbContext + { + public DbSet Users { get; set; } + + public DBContext(DbContextOptions options) : base(options) { } + } +} diff --git a/backend/API/DBAccess/DbAccess.cs b/backend/API/DBAccess/DbAccess.cs new file mode 100644 index 0000000..f994c5a --- /dev/null +++ b/backend/API/DBAccess/DbAccess.cs @@ -0,0 +1,93 @@ +using API.Models.UserModels; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace API.DBAccess +{ + public class DbAccess + { + private readonly DBContext _context; + + public DbAccess(DBContext context) + { + _context = context; + } + + public async Task ReadUser(int userId) + { + return await _context.Users.FirstOrDefaultAsync(u => u.Id == userId); + } + + public async Task> ReadAllUsers() + { + return await _context.Users.ToListAsync(); + } + + public async Task ReadUserByRefreshToken(string refreshToken) + { + return await _context.Users.FirstOrDefaultAsync(u => u.RefreshToken == refreshToken); + } + + public async Task ReadUserForDelete(int userId) + { + return await _context.Users.FirstOrDefaultAsync(u => u.Id == userId); + } + + public async Task ReadUserForLogin(string emailOrUsername) + { + if (emailOrUsername.Contains("@")) + { + return await _context.Users.FirstOrDefaultAsync(u => u.Email == emailOrUsername); + } + else + { + return await _context.Users.FirstOrDefaultAsync(u => u.UserName == emailOrUsername); + } + } + + public async Task CreateUser(User user) + { + _context.Users.Add(user); + + bool saved = await _context.SaveChangesAsync() == 1; + + if (saved) { return new OkObjectResult(true); } + + return new ConflictObjectResult(new { message = "Could not save to database" }); + + } + + public async Task UpdateUser(User user) + { + _context.Entry(user).State = EntityState.Modified; + + bool saved = await _context.SaveChangesAsync() == 1; + + if (saved) { return new OkObjectResult(user); } + + return new ConflictObjectResult(new { message = "Could not save to database" }); + + } + + public async Task UpdatePassword(User user) + { + _context.Entry(user).State = EntityState.Modified; + + bool saved = await _context.SaveChangesAsync() == 1; + + if (saved) { return new OkObjectResult(user); } + + return new ConflictObjectResult(new { message = "Could not save to database" }); + } + + public async Task DeleteUser(User user) + { + _context.Users.Remove(user); + bool saved = await _context.SaveChangesAsync() >= 0; + + if (saved) { return new OkObjectResult(saved); } + + return new ConflictObjectResult(new { message = "Could not save to database" }); + } + } +} diff --git a/backend/API/Migrations/20250423074254_init.Designer.cs b/backend/API/Migrations/20250423074254_init.Designer.cs new file mode 100644 index 0000000..9c85172 --- /dev/null +++ b/backend/API/Migrations/20250423074254_init.Designer.cs @@ -0,0 +1,59 @@ +// +using System; +using API.DBAccess; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace API.Migrations +{ + [DbContext(typeof(DBContext))] + [Migration("20250423074254_init")] + partial class init + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.10") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("API.Models.UserModels.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Email") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Password") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("RefreshToken") + .HasColumnType("longtext"); + + b.Property("RefreshTokenExpireAt") + .HasColumnType("datetime(6)"); + + b.Property("Salt") + .HasColumnType("longtext"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/backend/API/Migrations/20250423074254_init.cs b/backend/API/Migrations/20250423074254_init.cs new file mode 100644 index 0000000..0580713 --- /dev/null +++ b/backend/API/Migrations/20250423074254_init.cs @@ -0,0 +1,45 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using MySql.EntityFrameworkCore.Metadata; + +#nullable disable + +namespace API.Migrations +{ + /// + public partial class init : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterDatabase() + .Annotation("MySQL:Charset", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "Users", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("MySQL:ValueGenerationStrategy", MySQLValueGenerationStrategy.IdentityColumn), + UserName = table.Column(type: "longtext", nullable: false), + Password = table.Column(type: "longtext", nullable: false), + Email = table.Column(type: "longtext", nullable: false), + Salt = table.Column(type: "longtext", nullable: true), + RefreshToken = table.Column(type: "longtext", nullable: true), + RefreshTokenExpireAt = table.Column(type: "datetime(6)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Users", x => x.Id); + }) + .Annotation("MySQL:Charset", "utf8mb4"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Users"); + } + } +} diff --git a/backend/API/Migrations/DBContextModelSnapshot.cs b/backend/API/Migrations/DBContextModelSnapshot.cs new file mode 100644 index 0000000..71ff77e --- /dev/null +++ b/backend/API/Migrations/DBContextModelSnapshot.cs @@ -0,0 +1,56 @@ +// +using System; +using API.DBAccess; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace API.Migrations +{ + [DbContext(typeof(DBContext))] + partial class DBContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.10") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("API.Models.UserModels.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Email") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Password") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("RefreshToken") + .HasColumnType("longtext"); + + b.Property("RefreshTokenExpireAt") + .HasColumnType("datetime(6)"); + + b.Property("Salt") + .HasColumnType("longtext"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/backend/API/Models/UserModels/ChangePasswordDTO.cs b/backend/API/Models/UserModels/ChangePasswordDTO.cs new file mode 100644 index 0000000..59063ed --- /dev/null +++ b/backend/API/Models/UserModels/ChangePasswordDTO.cs @@ -0,0 +1,9 @@ +namespace API.Models.UserModels +{ + public class ChangePasswordDTO + { + public string OldPassword { get; set; } + + public string NewPassword { get; set; } + } +} diff --git a/backend/API/Models/UserModels/CreateUserDTO.cs b/backend/API/Models/UserModels/CreateUserDTO.cs new file mode 100644 index 0000000..c674c21 --- /dev/null +++ b/backend/API/Models/UserModels/CreateUserDTO.cs @@ -0,0 +1,11 @@ +namespace API.Models.UserModels +{ + public class CreateUserDTO + { + public string UserName { get; set; } + + public string Password { get; set; } + + public string Email { get; set; } + } +} diff --git a/backend/API/Models/UserModels/LoginDTO.cs b/backend/API/Models/UserModels/LoginDTO.cs new file mode 100644 index 0000000..721cda3 --- /dev/null +++ b/backend/API/Models/UserModels/LoginDTO.cs @@ -0,0 +1,9 @@ +namespace API.Models.UserModels +{ + public class LoginDTO + { + public string EmailUsr { get; set; } + + public string Password { get; set; } + } +} diff --git a/backend/API/Models/UserModels/UpdateUserDTO.cs b/backend/API/Models/UserModels/UpdateUserDTO.cs new file mode 100644 index 0000000..6fdae36 --- /dev/null +++ b/backend/API/Models/UserModels/UpdateUserDTO.cs @@ -0,0 +1,9 @@ +namespace API.Models.UserModels +{ + public class UpdateUserDTO + { + public string UserName { get; set; } + + public string Email { get; set; } + } +} diff --git a/backend/API/Models/UserModels/User.cs b/backend/API/Models/UserModels/User.cs new file mode 100644 index 0000000..107646a --- /dev/null +++ b/backend/API/Models/UserModels/User.cs @@ -0,0 +1,19 @@ +namespace API.Models.UserModels +{ + public class User + { + public int Id { get; set; } + + public string UserName { get; set; } + + public string Password { get; set; } + + public string Email { get; set; } + + public string? Salt { get; set; } + + public string? RefreshToken { get; set; } + + public DateTime RefreshTokenExpireAt { get; set; } + } +} diff --git a/backend/API/Program.cs b/backend/API/Program.cs index 48863a6..58ea8e3 100644 --- a/backend/API/Program.cs +++ b/backend/API/Program.cs @@ -1,25 +1,34 @@ -var builder = WebApplication.CreateBuilder(args); +using API; +using Microsoft.AspNetCore; +using API.DBAccess; +using Microsoft.EntityFrameworkCore; -// Add services to the container. -builder.Services.AddControllers(); -// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle -builder.Services.AddEndpointsApiExplorer(); -builder.Services.AddSwaggerGen(); - -var app = builder.Build(); - -// Configure the HTTP request pipeline. -if (app.Environment.IsDevelopment()) +internal class Program { - app.UseSwagger(); - app.UseSwaggerUI(); -} + private static void Main(string[] args) + { + var app = CreateWebHostBuilder(args).Build(); -app.UseHttpsRedirection(); + RunMigrations(app); -app.UseAuthorization(); + app.Run(); + } -app.MapControllers(); + // Calls the startup class and creates the webinterface + public static IWebHostBuilder CreateWebHostBuilder(string[] args) => + WebHost.CreateDefaultBuilder(args) + .UseUrls("http://0.0.0.0:5000") + .UseStartup(); -app.Run(); + public static async void RunMigrations(IWebHost app) + { + await using var scope = app.Services.CreateAsyncScope(); + await using var db = scope.ServiceProvider.GetService(); + + if (db != null) + { + await db.Database.MigrateAsync(); + } + } +} \ No newline at end of file diff --git a/backend/API/Startup.cs b/backend/API/Startup.cs new file mode 100644 index 0000000..c0d4f97 --- /dev/null +++ b/backend/API/Startup.cs @@ -0,0 +1,122 @@ +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.EntityFrameworkCore; +using Microsoft.IdentityModel.Tokens; +using Microsoft.OpenApi.Models; +using System.Text; +using API.DBAccess; +using API.BusinessLogic; + +namespace API +{ + public class Startup + { + public IConfiguration _configuration { get; } + + public Startup(IConfiguration configuration) + { + _configuration = configuration; + } + + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + // Sets the connectionstring to the database so dbcontext can find it + services.AddDbContext(options => + options.UseMySQL(_configuration.GetConnectionString("Database"))); + + services.AddScoped(); + services.AddScoped(); + + services.AddControllers(); + + services.AddAuthentication(x => + { + x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; + x.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; + }).AddJwtBearer(x => + { + x.TokenValidationParameters = new TokenValidationParameters + { + ValidIssuer = _configuration["JwtSettings:Issuer"], + ValidAudience = _configuration["JwtSettings:Audience"], + IssuerSigningKey = new SymmetricSecurityKey + ( + Encoding.UTF8.GetBytes(_configuration["JwtSettings:Key"]) + ), + ValidateIssuer = true, + ValidateAudience = true, + ValidateLifetime = true, + ValidateIssuerSigningKey = true + }; + }); + + + services.AddCors(options => + { + options.AddPolicy("AllowAll", builder => + { + builder.AllowAnyOrigin() + .AllowAnyMethod().AllowAnyHeader(); + }); + }); + + services.AddSwaggerGen(c => + { + c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" }); + + // Configure Swagger to use Bearer token authentication + c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme + { + Description = "JWT Authorization header using the Bearer scheme", + Type = SecuritySchemeType.Http, + Scheme = "bearer" + }); + + c.AddSecurityRequirement(new OpenApiSecurityRequirement + { + { + new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Type = ReferenceType.SecurityScheme, + Id = "Bearer" + } + }, + new string[] { } + } + }); + }); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + app.UseSwagger(); + app.UseSwaggerUI(c => + { + c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1"); + }); + + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseCors("AllowAll"); + + app.UseRouting(); + + app.UseAuthentication(); + + app.UseAuthorization(); + + app.UseEndpoints(endpoints => + { + endpoints.MapControllers(); + }); + } + } +} \ No newline at end of file diff --git a/backend/API/WeatherForecast.cs b/backend/API/WeatherForecast.cs deleted file mode 100644 index 10c41a8..0000000 --- a/backend/API/WeatherForecast.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace API -{ - public class WeatherForecast - { - public DateOnly Date { get; set; } - - public int TemperatureC { get; set; } - - public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); - - public string? Summary { get; set; } - } -}