Compare commits
4 Commits
9eb0c4b69a
...
184fbb3728
Author | SHA1 | Date | |
---|---|---|---|
|
184fbb3728 | ||
|
4d2948f3f3 | ||
|
73bfd8d01c | ||
|
3423c9df34 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -5,3 +5,4 @@
|
|||||||
/backend/API/obj
|
/backend/API/obj
|
||||||
/backend/API/appsettings.Development.json
|
/backend/API/appsettings.Development.json
|
||||||
/backend/API/appsettings.json
|
/backend/API/appsettings.json
|
||||||
|
*bin
|
||||||
|
@ -7,6 +7,17 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.15" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.10" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.10">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.10">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="MySql.EntityFrameworkCore" Version="8.0.8" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
236
backend/API/BusinessLogic/UserLogic.cs
Normal file
236
backend/API/BusinessLogic/UserLogic.cs
Normal file
@ -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<IActionResult> 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<IActionResult> 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<IActionResult> 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<IActionResult> 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<IActionResult> 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<IActionResult> 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<IActionResult> 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<User> UpdateRefreshToken(User user)
|
||||||
|
{
|
||||||
|
user.RefreshToken = Guid.NewGuid().ToString();
|
||||||
|
user.RefreshTokenExpireAt = DateTime.Now.AddDays(30);
|
||||||
|
await _dbAccess.UpdateUser(user);
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
78
backend/API/Controllers/UserController.cs
Normal file
78
backend/API/Controllers/UserController.cs
Normal file
@ -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<IActionResult> 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<IActionResult> Login([FromBody] LoginDTO loginDTO)
|
||||||
|
{
|
||||||
|
return await _userLogic.Login(loginDTO);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("create")]
|
||||||
|
public async Task<IActionResult> CreateUser([FromBody] CreateUserDTO userDTO)
|
||||||
|
{
|
||||||
|
return await _userLogic.RegisterUser(userDTO);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Authorize]
|
||||||
|
[HttpPut("change-password")]
|
||||||
|
public async Task<IActionResult> 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<IActionResult> 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<IActionResult> 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<IActionResult> RefreashToken(string refreshToken)
|
||||||
|
{
|
||||||
|
return await _userLogic.RefreshToken(refreshToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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<WeatherForecastController> _logger;
|
|
||||||
|
|
||||||
public WeatherForecastController(ILogger<WeatherForecastController> logger)
|
|
||||||
{
|
|
||||||
_logger = logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet(Name = "GetWeatherForecast")]
|
|
||||||
public IEnumerable<WeatherForecast> 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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
12
backend/API/DBAccess/DBContext.cs
Normal file
12
backend/API/DBAccess/DBContext.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
using API.Models.UserModels;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace API.DBAccess
|
||||||
|
{
|
||||||
|
public class DBContext : DbContext
|
||||||
|
{
|
||||||
|
public DbSet<User> Users { get; set; }
|
||||||
|
|
||||||
|
public DBContext(DbContextOptions<DBContext> options) : base(options) { }
|
||||||
|
}
|
||||||
|
}
|
93
backend/API/DBAccess/DbAccess.cs
Normal file
93
backend/API/DBAccess/DbAccess.cs
Normal file
@ -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<User> ReadUser(int userId)
|
||||||
|
{
|
||||||
|
return await _context.Users.FirstOrDefaultAsync(u => u.Id == userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<User>> ReadAllUsers()
|
||||||
|
{
|
||||||
|
return await _context.Users.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<User> ReadUserByRefreshToken(string refreshToken)
|
||||||
|
{
|
||||||
|
return await _context.Users.FirstOrDefaultAsync(u => u.RefreshToken == refreshToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<User> ReadUserForDelete(int userId)
|
||||||
|
{
|
||||||
|
return await _context.Users.FirstOrDefaultAsync(u => u.Id == userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<User> 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<IActionResult> 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<IActionResult> 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<IActionResult> 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<IActionResult> 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" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
59
backend/API/Migrations/20250423074254_init.Designer.cs
generated
Normal file
59
backend/API/Migrations/20250423074254_init.Designer.cs
generated
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
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
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
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<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("Email")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("Password")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("RefreshToken")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<DateTime>("RefreshTokenExpireAt")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<string>("Salt")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("UserName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Users");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
45
backend/API/Migrations/20250423074254_init.cs
Normal file
45
backend/API/Migrations/20250423074254_init.cs
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using MySql.EntityFrameworkCore.Metadata;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace API.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class init : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AlterDatabase()
|
||||||
|
.Annotation("MySQL:Charset", "utf8mb4");
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Users",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "int", nullable: false)
|
||||||
|
.Annotation("MySQL:ValueGenerationStrategy", MySQLValueGenerationStrategy.IdentityColumn),
|
||||||
|
UserName = table.Column<string>(type: "longtext", nullable: false),
|
||||||
|
Password = table.Column<string>(type: "longtext", nullable: false),
|
||||||
|
Email = table.Column<string>(type: "longtext", nullable: false),
|
||||||
|
Salt = table.Column<string>(type: "longtext", nullable: true),
|
||||||
|
RefreshToken = table.Column<string>(type: "longtext", nullable: true),
|
||||||
|
RefreshTokenExpireAt = table.Column<DateTime>(type: "datetime(6)", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Users", x => x.Id);
|
||||||
|
})
|
||||||
|
.Annotation("MySQL:Charset", "utf8mb4");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Users");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
56
backend/API/Migrations/DBContextModelSnapshot.cs
Normal file
56
backend/API/Migrations/DBContextModelSnapshot.cs
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
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<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("Email")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("Password")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("RefreshToken")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<DateTime>("RefreshTokenExpireAt")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<string>("Salt")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("UserName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Users");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
9
backend/API/Models/UserModels/ChangePasswordDTO.cs
Normal file
9
backend/API/Models/UserModels/ChangePasswordDTO.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
namespace API.Models.UserModels
|
||||||
|
{
|
||||||
|
public class ChangePasswordDTO
|
||||||
|
{
|
||||||
|
public string OldPassword { get; set; }
|
||||||
|
|
||||||
|
public string NewPassword { get; set; }
|
||||||
|
}
|
||||||
|
}
|
11
backend/API/Models/UserModels/CreateUserDTO.cs
Normal file
11
backend/API/Models/UserModels/CreateUserDTO.cs
Normal file
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
9
backend/API/Models/UserModels/LoginDTO.cs
Normal file
9
backend/API/Models/UserModels/LoginDTO.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
namespace API.Models.UserModels
|
||||||
|
{
|
||||||
|
public class LoginDTO
|
||||||
|
{
|
||||||
|
public string EmailUsr { get; set; }
|
||||||
|
|
||||||
|
public string Password { get; set; }
|
||||||
|
}
|
||||||
|
}
|
9
backend/API/Models/UserModels/UpdateUserDTO.cs
Normal file
9
backend/API/Models/UserModels/UpdateUserDTO.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
namespace API.Models.UserModels
|
||||||
|
{
|
||||||
|
public class UpdateUserDTO
|
||||||
|
{
|
||||||
|
public string UserName { get; set; }
|
||||||
|
|
||||||
|
public string Email { get; set; }
|
||||||
|
}
|
||||||
|
}
|
19
backend/API/Models/UserModels/User.cs
Normal file
19
backend/API/Models/UserModels/User.cs
Normal file
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
internal class Program
|
||||||
// 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())
|
|
||||||
{
|
{
|
||||||
app.UseSwagger();
|
private static void Main(string[] args)
|
||||||
app.UseSwaggerUI();
|
{
|
||||||
}
|
var app = CreateWebHostBuilder(args).Build();
|
||||||
|
|
||||||
app.UseHttpsRedirection();
|
RunMigrations(app);
|
||||||
|
|
||||||
app.UseAuthorization();
|
|
||||||
|
|
||||||
app.MapControllers();
|
|
||||||
|
|
||||||
app.Run();
|
app.Run();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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<Startup>();
|
||||||
|
|
||||||
|
public static async void RunMigrations(IWebHost app)
|
||||||
|
{
|
||||||
|
await using var scope = app.Services.CreateAsyncScope();
|
||||||
|
await using var db = scope.ServiceProvider.GetService<DBContext>();
|
||||||
|
|
||||||
|
if (db != null)
|
||||||
|
{
|
||||||
|
await db.Database.MigrateAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
122
backend/API/Startup.cs
Normal file
122
backend/API/Startup.cs
Normal file
@ -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<DBContext>(options =>
|
||||||
|
options.UseMySQL(_configuration.GetConnectionString("Database")));
|
||||||
|
|
||||||
|
services.AddScoped<DbAccess>();
|
||||||
|
services.AddScoped<UserLogic>();
|
||||||
|
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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; }
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user