Auth endpoints are now functional

This commit is contained in:
2023-08-13 00:54:23 -04:00
parent a636309b5a
commit fdf286e22f
26 changed files with 705 additions and 159 deletions

View File

@ -17,7 +17,12 @@ using System.ComponentModel.DataAnnotations;
using IO.Swagger.Attributes;
using IO.Swagger.Security;
using Microsoft.AspNetCore.Authorization;
using IO.Swagger.Models;
using IO.Swagger.Models.dto;
using IO.Swagger.Repositories;
using System.Threading.Tasks;
using System.Linq;
using IO.Swagger.Services;
using System.Security.Claims;
namespace IO.Swagger.Controllers
{
@ -27,6 +32,15 @@ namespace IO.Swagger.Controllers
[ApiController]
public class AuthApiController : ControllerBase
{
private readonly IUserRepository repository;
private readonly JwtService jwt;
public AuthApiController(IUserRepository repository, JwtService jwt)
{
this.repository = repository ?? throw new ArgumentNullException(nameof(repository));
this.jwt = jwt ?? throw new ArgumentNullException(nameof(jwt));
}
/// <summary>
/// Get user details
/// </summary>
@ -37,15 +51,13 @@ namespace IO.Swagger.Controllers
[Authorize(AuthenticationSchemes = BearerAuthenticationHandler.SchemeName)]
[ValidateModelState]
[SwaggerOperation("GetUserDetails")]
public virtual IActionResult GetUserDetails()
public virtual async Task<IActionResult> GetUserDetails()
{
//TODO: Uncomment the next line to return response 200 or use other options such as return this.NotFound(), return this.BadRequest(..), ...
// return StatusCode(200);
//TODO: Uncomment the next line to return response 401 or use other options such as return this.NotFound(), return this.BadRequest(..), ...
// return StatusCode(401);
throw new NotImplementedException();
var userIdString = HttpContext.User.Claims.First(c => c.Type == ClaimTypes.NameIdentifier).Value;
if (!int.TryParse(userIdString, out int userId))
return Unauthorized();
var user = await repository.RetrieveUser(userId);
return user == null ? NoContent() : Ok(user);
}
/// <summary>
@ -58,40 +70,39 @@ namespace IO.Swagger.Controllers
[Route("/v1/api/auth/login")]
[ValidateModelState]
[SwaggerOperation("LoginUser")]
public virtual IActionResult LoginUser([FromBody]AuthLoginBody body)
public virtual async Task<IActionResult> LoginUser([FromBody]AuthLoginBody body)
{
//TODO: Uncomment the next line to return response 200 or use other options such as return this.NotFound(), return this.BadRequest(..), ...
// return StatusCode(200);
//TODO: Uncomment the next line to return response 401 or use other options such as return this.NotFound(), return this.BadRequest(..), ...
// return StatusCode(401);
throw new NotImplementedException();
if (!ModelState.IsValid)
{
var errors = ModelState.Values.SelectMany(v => v.Errors.Select(e => e.ErrorMessage));
return BadRequest(errors);
}
var user = await repository.LoginUser(body);
return user == null ? Unauthorized() : Ok(new { token = jwt.GenerateJwt(user.Id) });
}
/// <summary>
/// Register a new user
/// </summary>
/// <param name="body"></param>
/// <response code="201">User registered successfully</response>
/// <response code="200">User registered successfully</response>
/// <response code="400">Bad Request</response>
/// <response code="409">Conflict (user with provided email already exists)</response>
[HttpPost]
[Route("/v1/api/auth/register")]
[ValidateModelState]
[SwaggerOperation("RegisterUser")]
public virtual IActionResult RegisterUser([FromBody]AuthRegisterBody body)
public async Task<IActionResult> RegisterUser([FromBody]AuthRegisterBody body)
{
//TODO: Uncomment the next line to return response 201 or use other options such as return this.NotFound(), return this.BadRequest(..), ...
// return StatusCode(201);
if (!ModelState.IsValid)
{
var errors = ModelState.Values.SelectMany(v => v.Errors.Select(e => e.ErrorMessage));
return BadRequest(errors);
}
//TODO: Uncomment the next line to return response 400 or use other options such as return this.NotFound(), return this.BadRequest(..), ...
// return StatusCode(400);
//TODO: Uncomment the next line to return response 409 or use other options such as return this.NotFound(), return this.BadRequest(..), ...
// return StatusCode(409);
throw new NotImplementedException();
var user = await repository.RegisterUser(body);
return user == null ? StatusCode(409) : Ok(new { token = jwt.GenerateJwt(user.Id) });
}
}
}

View File

@ -17,7 +17,7 @@ using System.ComponentModel.DataAnnotations;
using IO.Swagger.Attributes;
using IO.Swagger.Security;
using Microsoft.AspNetCore.Authorization;
using IO.Swagger.Models;
using IO.Swagger.Models.dto;
namespace IO.Swagger.Controllers
{

View File

@ -17,7 +17,7 @@ using System.ComponentModel.DataAnnotations;
using IO.Swagger.Attributes;
using IO.Swagger.Security;
using Microsoft.AspNetCore.Authorization;
using IO.Swagger.Models;
using IO.Swagger.Models.dto;
namespace IO.Swagger.Controllers
{

View File

@ -9,6 +9,12 @@
<PackageId>IO.Swagger</PackageId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.10" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="7.0.10" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.10">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.5.1" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="5.5.1" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="5.5.1" />

View File

@ -0,0 +1,60 @@
// <auto-generated />
using IO.Swagger.Services;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace IO.Swagger.Migrations
{
[DbContext(typeof(BankDbContext))]
[Migration("20230813022042_Initial creation")]
partial class Initialcreation
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "7.0.10")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("IO.Swagger.Models.db.User", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("Email")
.HasColumnType("nvarchar(max)");
b.Property<string>("FirstName")
.HasColumnType("nvarchar(max)");
b.Property<string>("LastName")
.HasColumnType("nvarchar(max)");
b.Property<string>("PasswordHash")
.HasColumnType("nvarchar(max)");
b.Property<string>("Salt")
.HasColumnType("nvarchar(max)");
b.Property<string>("Username")
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.ToTable("Users");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,39 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace IO.Swagger.Migrations
{
/// <inheritdoc />
public partial class Initialcreation : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Users",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Email = table.Column<string>(type: "nvarchar(max)", nullable: true),
FirstName = table.Column<string>(type: "nvarchar(max)", nullable: true),
LastName = table.Column<string>(type: "nvarchar(max)", nullable: true),
Username = table.Column<string>(type: "nvarchar(max)", nullable: true),
PasswordHash = table.Column<string>(type: "nvarchar(max)", nullable: true),
Salt = table.Column<string>(type: "nvarchar(max)", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Users", x => x.Id);
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Users");
}
}
}

View File

@ -0,0 +1,59 @@
// <auto-generated />
using IO.Swagger.Services;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace IO.Swagger.Migrations
{
[DbContext(typeof(BankDbContext))]
[Migration("20230813022424_Change user table")]
partial class Changeusertable
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "7.0.10")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("IO.Swagger.Models.db.User", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("Email")
.HasColumnType("nvarchar(max)");
b.Property<string>("FirstName")
.HasMaxLength(32)
.HasColumnType("nvarchar(32)");
b.Property<string>("LastName")
.HasMaxLength(32)
.HasColumnType("nvarchar(32)");
b.Property<string>("PasswordHash")
.HasColumnType("nvarchar(max)");
b.Property<string>("Salt")
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.ToTable("Users");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,68 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace IO.Swagger.Migrations
{
/// <inheritdoc />
public partial class Changeusertable : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Username",
table: "Users");
migrationBuilder.AlterColumn<string>(
name: "LastName",
table: "Users",
type: "nvarchar(32)",
maxLength: 32,
nullable: true,
oldClrType: typeof(string),
oldType: "nvarchar(max)",
oldNullable: true);
migrationBuilder.AlterColumn<string>(
name: "FirstName",
table: "Users",
type: "nvarchar(32)",
maxLength: 32,
nullable: true,
oldClrType: typeof(string),
oldType: "nvarchar(max)",
oldNullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "LastName",
table: "Users",
type: "nvarchar(max)",
nullable: true,
oldClrType: typeof(string),
oldType: "nvarchar(32)",
oldMaxLength: 32,
oldNullable: true);
migrationBuilder.AlterColumn<string>(
name: "FirstName",
table: "Users",
type: "nvarchar(max)",
nullable: true,
oldClrType: typeof(string),
oldType: "nvarchar(32)",
oldMaxLength: 32,
oldNullable: true);
migrationBuilder.AddColumn<string>(
name: "Username",
table: "Users",
type: "nvarchar(max)",
nullable: true);
}
}
}

View File

@ -0,0 +1,56 @@
// <auto-generated />
using IO.Swagger.Services;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace IO.Swagger.Migrations
{
[DbContext(typeof(BankDbContext))]
partial class BankDbContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "7.0.10")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("IO.Swagger.Models.db.User", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("Email")
.HasColumnType("nvarchar(max)");
b.Property<string>("FirstName")
.HasMaxLength(32)
.HasColumnType("nvarchar(32)");
b.Property<string>("LastName")
.HasMaxLength(32)
.HasColumnType("nvarchar(32)");
b.Property<string>("PasswordHash")
.HasColumnType("nvarchar(max)");
b.Property<string>("Salt")
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.ToTable("Users");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,17 @@
using System.ComponentModel.DataAnnotations;
namespace IO.Swagger.Models.db
{
public class User
{
public int Id { get; set; }
public string Email { get; set; }
[StringLength(32, MinimumLength = 3)]
public string FirstName { get; set; }
[StringLength(32, MinimumLength = 3)]
public string LastName { get; set; }
public string PasswordHash { get; set; }
public string Salt { get; set; }
}
}

View File

@ -18,7 +18,7 @@ using System.ComponentModel.DataAnnotations;
using System.Runtime.Serialization;
using Newtonsoft.Json;
namespace IO.Swagger.Models
namespace IO.Swagger.Models.dto
{
/// <summary>
///
@ -30,14 +30,14 @@ namespace IO.Swagger.Models
/// Gets or Sets Email
/// </summary>
[DataMember(Name="email")]
[DataMember(Name = "email")]
public string Email { get; set; }
/// <summary>
/// Gets or Sets Password
/// </summary>
[DataMember(Name="password")]
[DataMember(Name = "password")]
public string Password { get; set; }
/// <summary>
@ -117,7 +117,7 @@ namespace IO.Swagger.Models
}
#region Operators
#pragma warning disable 1591
#pragma warning disable 1591
public static bool operator ==(AuthLoginBody left, AuthLoginBody right)
{
@ -129,7 +129,7 @@ namespace IO.Swagger.Models
return !Equals(left, right);
}
#pragma warning restore 1591
#pragma warning restore 1591
#endregion Operators
}
}

View File

@ -18,7 +18,7 @@ using System.ComponentModel.DataAnnotations;
using System.Runtime.Serialization;
using Newtonsoft.Json;
namespace IO.Swagger.Models
namespace IO.Swagger.Models.dto
{
/// <summary>
///
@ -30,30 +30,36 @@ namespace IO.Swagger.Models
/// Gets or Sets FirstName
/// </summary>
[StringLength(32, MinimumLength=3)]
[DataMember(Name="firstName")]
[StringLength(32, MinimumLength = 3)]
[DataMember(Name = "firstName")]
[Required]
public string FirstName { get; set; }
/// <summary>
/// Gets or Sets LastName
/// </summary>
[StringLength(32, MinimumLength=3)]
[DataMember(Name="lastName")]
[StringLength(32, MinimumLength = 3)]
[DataMember(Name = "lastName")]
[Required]
public string LastName { get; set; }
/// <summary>
/// Gets or Sets Email
/// </summary>
[DataMember(Name="email")]
[DataMember(Name = "email")]
[EmailAddress(ErrorMessage = "Invalid email format.")]
[Required]
public string Email { get; set; }
/// <summary>
/// Gets or Sets Password
/// </summary>
[DataMember(Name="password")]
[DataMember(Name = "password")]
[StringLength(32, MinimumLength = 8)]
[Required]
public string Password { get; set; }
/// <summary>
@ -149,7 +155,7 @@ namespace IO.Swagger.Models
}
#region Operators
#pragma warning disable 1591
#pragma warning disable 1591
public static bool operator ==(AuthRegisterBody left, AuthRegisterBody right)
{
@ -161,7 +167,7 @@ namespace IO.Swagger.Models
return !Equals(left, right);
}
#pragma warning restore 1591
#pragma warning restore 1591
#endregion Operators
}
}

View File

@ -18,7 +18,7 @@ using System.ComponentModel.DataAnnotations;
using System.Runtime.Serialization;
using Newtonsoft.Json;
namespace IO.Swagger.Models
namespace IO.Swagger.Models.dto
{
/// <summary>
///
@ -30,22 +30,22 @@ namespace IO.Swagger.Models
/// Gets or Sets CollectionId
/// </summary>
[DataMember(Name="collectionId")]
[DataMember(Name = "collectionId")]
public int? CollectionId { get; set; }
/// <summary>
/// Gets or Sets AssetName
/// </summary>
[StringLength(32, MinimumLength=1)]
[DataMember(Name="assetName")]
[StringLength(32, MinimumLength = 1)]
[DataMember(Name = "assetName")]
public string AssetName { get; set; }
/// <summary>
/// Gets or Sets AssetLink
/// </summary>
[RegularExpression("/^(https?|ftp)://[^\\s/$.?#].[^\\s]*$/")]
[DataMember(Name="assetLink")]
[DataMember(Name = "assetLink")]
public string AssetLink { get; set; }
/// <summary>
@ -133,7 +133,7 @@ namespace IO.Swagger.Models
}
#region Operators
#pragma warning disable 1591
#pragma warning disable 1591
public static bool operator ==(CurrencyAddAssetBody left, CurrencyAddAssetBody right)
{
@ -145,7 +145,7 @@ namespace IO.Swagger.Models
return !Equals(left, right);
}
#pragma warning restore 1591
#pragma warning restore 1591
#endregion Operators
}
}

View File

@ -18,7 +18,7 @@ using System.ComponentModel.DataAnnotations;
using System.Runtime.Serialization;
using Newtonsoft.Json;
namespace IO.Swagger.Models
namespace IO.Swagger.Models.dto
{
/// <summary>
///
@ -30,16 +30,16 @@ namespace IO.Swagger.Models
/// Gets or Sets Name
/// </summary>
[StringLength(32, MinimumLength=1)]
[DataMember(Name="name")]
[StringLength(32, MinimumLength = 1)]
[DataMember(Name = "name")]
public string Name { get; set; }
/// <summary>
/// Gets or Sets Symbol
/// </summary>
[StringLength(4, MinimumLength=1)]
[DataMember(Name="symbol")]
[StringLength(4, MinimumLength = 1)]
[DataMember(Name = "symbol")]
public string Symbol { get; set; }
/// <summary>
@ -119,7 +119,7 @@ namespace IO.Swagger.Models
}
#region Operators
#pragma warning disable 1591
#pragma warning disable 1591
public static bool operator ==(CurrencyCreateBody left, CurrencyCreateBody right)
{
@ -131,7 +131,7 @@ namespace IO.Swagger.Models
return !Equals(left, right);
}
#pragma warning restore 1591
#pragma warning restore 1591
#endregion Operators
}
}

View File

@ -18,7 +18,7 @@ using System.ComponentModel.DataAnnotations;
using System.Runtime.Serialization;
using Newtonsoft.Json;
namespace IO.Swagger.Models
namespace IO.Swagger.Models.dto
{
/// <summary>
///
@ -30,7 +30,7 @@ namespace IO.Swagger.Models
/// Gets or Sets CollectionName
/// </summary>
[DataMember(Name="collectionName")]
[DataMember(Name = "collectionName")]
public string CollectionName { get; set; }
/// <summary>
@ -78,11 +78,11 @@ namespace IO.Swagger.Models
if (ReferenceEquals(this, other)) return true;
return
(
CollectionName == other.CollectionName ||
CollectionName != null &&
CollectionName.Equals(other.CollectionName)
);
;
}
/// <summary>
@ -102,7 +102,7 @@ namespace IO.Swagger.Models
}
#region Operators
#pragma warning disable 1591
#pragma warning disable 1591
public static bool operator ==(CurrencyCreateCollectionBody left, CurrencyCreateCollectionBody right)
{
@ -114,7 +114,7 @@ namespace IO.Swagger.Models
return !Equals(left, right);
}
#pragma warning restore 1591
#pragma warning restore 1591
#endregion Operators
}
}

View File

@ -18,7 +18,7 @@ using System.ComponentModel.DataAnnotations;
using System.Runtime.Serialization;
using Newtonsoft.Json;
namespace IO.Swagger.Models
namespace IO.Swagger.Models.dto
{
/// <summary>
///
@ -30,14 +30,14 @@ namespace IO.Swagger.Models
/// Gets or Sets CurrencyId
/// </summary>
[DataMember(Name="currencyId")]
[DataMember(Name = "currencyId")]
public int? CurrencyId { get; set; }
/// <summary>
/// Gets or Sets Amount
/// </summary>
[DataMember(Name="amount")]
[DataMember(Name = "amount")]
public decimal? Amount { get; set; }
/// <summary>
@ -117,7 +117,7 @@ namespace IO.Swagger.Models
}
#region Operators
#pragma warning disable 1591
#pragma warning disable 1591
public static bool operator ==(CurrencyMintBody left, CurrencyMintBody right)
{
@ -129,7 +129,7 @@ namespace IO.Swagger.Models
return !Equals(left, right);
}
#pragma warning restore 1591
#pragma warning restore 1591
#endregion Operators
}
}

View File

@ -18,7 +18,7 @@ using System.ComponentModel.DataAnnotations;
using System.Runtime.Serialization;
using Newtonsoft.Json;
namespace IO.Swagger.Models
namespace IO.Swagger.Models.dto
{
/// <summary>
///
@ -30,14 +30,14 @@ namespace IO.Swagger.Models
/// Gets or Sets Email
/// </summary>
[DataMember(Name="email")]
[DataMember(Name = "email")]
public string Email { get; set; }
/// <summary>
/// Gets or Sets AssetId
/// </summary>
[DataMember(Name="assetId")]
[DataMember(Name = "assetId")]
public int? AssetId { get; set; }
/// <summary>
@ -117,7 +117,7 @@ namespace IO.Swagger.Models
}
#region Operators
#pragma warning disable 1591
#pragma warning disable 1591
public static bool operator ==(WalletTransferDigitalBody left, WalletTransferDigitalBody right)
{
@ -129,7 +129,7 @@ namespace IO.Swagger.Models
return !Equals(left, right);
}
#pragma warning restore 1591
#pragma warning restore 1591
#endregion Operators
}
}

View File

@ -18,7 +18,7 @@ using System.ComponentModel.DataAnnotations;
using System.Runtime.Serialization;
using Newtonsoft.Json;
namespace IO.Swagger.Models
namespace IO.Swagger.Models.dto
{
/// <summary>
///
@ -30,21 +30,21 @@ namespace IO.Swagger.Models
/// Gets or Sets Email
/// </summary>
[DataMember(Name="email")]
[DataMember(Name = "email")]
public string Email { get; set; }
/// <summary>
/// Gets or Sets Amount
/// </summary>
[DataMember(Name="amount")]
[DataMember(Name = "amount")]
public decimal? Amount { get; set; }
/// <summary>
/// Gets or Sets CurrencyId
/// </summary>
[DataMember(Name="currencyId")]
[DataMember(Name = "currencyId")]
public int? CurrencyId { get; set; }
/// <summary>
@ -132,7 +132,7 @@ namespace IO.Swagger.Models
}
#region Operators
#pragma warning disable 1591
#pragma warning disable 1591
public static bool operator ==(WalletTransferPhysicalBody left, WalletTransferPhysicalBody right)
{
@ -144,7 +144,7 @@ namespace IO.Swagger.Models
return !Equals(left, right);
}
#pragma warning restore 1591
#pragma warning restore 1591
#endregion Operators
}
}

View File

@ -1,12 +1,4 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:50352/",
"sslPort": 0
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
@ -20,10 +12,12 @@
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "https://localhost:5001;http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
"ASPNETCORE_ENVIRONMENT": "Development",
"DATABASE_CONNECTION_STRING": "Server=localhost\\SQLEXPRESS;Database=BankDb;Trusted_Connection=True;Integrated Security=True;TrustServerCertificate=True",
"JWT_SECRET_KEY": "mysuperduperubereepykeepysecretkey"
},
"applicationUrl": "https://localhost:5001;http://localhost:5000"
},
"Docker": {
"commandName": "Docker",
@ -32,5 +26,13 @@
"publishAllPorts": true,
"useSSL": true
}
},
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:50352/",
"sslPort": 0
}
}
}

View File

@ -0,0 +1,13 @@
using IO.Swagger.Models.db;
using IO.Swagger.Models.dto;
using System.Threading.Tasks;
namespace IO.Swagger.Repositories
{
public interface IUserRepository
{
Task<User> LoginUser(AuthLoginBody request);
Task<User> RegisterUser(AuthRegisterBody request);
Task<User> RetrieveUser(int userId);
}
}

View File

@ -0,0 +1,77 @@
using IO.Swagger.Models.db;
using System.Security.Cryptography;
using System;
using IO.Swagger.Services;
using IO.Swagger.Models.dto;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using System.Linq;
namespace IO.Swagger.Repositories
{
public class UserRepository : IUserRepository
{
private readonly BankDbContext bankDbContext;
public UserRepository(BankDbContext bankDbContext)
{
this.bankDbContext = bankDbContext;
}
public async Task<User> RegisterUser(AuthRegisterBody request)
{
request.Email = request.Email.ToLower();
if (await bankDbContext.Users.CountAsync((User u) => u.Email == request.Email) > 0)
return null;
// Generate a random salt
byte[] saltBytes = new byte[16];
new RNGCryptoServiceProvider().GetBytes(saltBytes);
string salt = Convert.ToBase64String(saltBytes);
// Hash the password along with the salt
string password = request.Password;
string saltedPassword = password + salt;
byte[] passwordBytes = System.Text.Encoding.UTF8.GetBytes(saltedPassword);
byte[] hashedBytes = new SHA256Managed().ComputeHash(passwordBytes);
string hashedPassword = Convert.ToBase64String(hashedBytes);
// Create and insert the user
var newUser = new User
{
PasswordHash = hashedPassword,
Salt = salt,
Email = request.Email,
FirstName = request.FirstName,
LastName = request.LastName
};
await bankDbContext.Users.AddAsync(newUser);
await bankDbContext.SaveChangesAsync();
return newUser;
}
public async Task<User> LoginUser(AuthLoginBody request)
{
request.Email = request.Email.ToLower();
var user = await bankDbContext.Users.FirstOrDefaultAsync(u => u.Email.Equals(request.Email));
if (user == null)
return null;
// Hash the supplied password with the retrieved salt
string password = request.Password;
string saltedPassword = password + user.Salt;
byte[] passwordBytes = System.Text.Encoding.UTF8.GetBytes(saltedPassword);
byte[] hashedBytes = new SHA256Managed().ComputeHash(passwordBytes);
string hashedPassword = Convert.ToBase64String(hashedBytes);
if (hashedPassword != user.PasswordHash)
return null;
return user;
}
public async Task<User> RetrieveUser(int userId)
{
return await bankDbContext.Users.FirstOrDefaultAsync(u => u.Id == userId);
}
}
}

View File

@ -1,4 +1,6 @@
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Text;
@ -7,6 +9,8 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json.Linq;
namespace IO.Swagger.Security
{
@ -15,6 +19,9 @@ namespace IO.Swagger.Security
/// </summary>
public class BearerAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
private readonly string secretKey;
private readonly byte[] secretBytes;
/// <summary>
/// scheme name for authentication handler.
/// </summary>
@ -22,6 +29,8 @@ namespace IO.Swagger.Security
public BearerAuthenticationHandler(IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock)
{
secretKey = Environment.GetEnvironmentVariable("JWT_SECRET_KEY");
secretBytes = Encoding.UTF8.GetBytes(secretKey);
}
/// <summary>
@ -37,22 +46,44 @@ namespace IO.Swagger.Security
{
var authHeader = AuthenticationHeaderValue.Parse(Request.Headers["Authorization"]);
/// TODO handle token.
}
catch
var tokenHandler = new JwtSecurityTokenHandler();
var validationParameters = new TokenValidationParameters
{
return AuthenticateResult.Fail("Invalid Authorization Header");
}
var claims = new[] {
new Claim(ClaimTypes.NameIdentifier, "changeme"),
new Claim(ClaimTypes.Name, "changeme"),
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(secretBytes),
ValidateIssuer = false,
ValidateAudience = false
};
var identity = new ClaimsIdentity(claims, Scheme.Name);
try
{
var claimsPrincipal = tokenHandler.ValidateToken(authHeader.Parameter, validationParameters, out _);
var userIdClaim = claimsPrincipal.FindFirst(ClaimTypes.NameIdentifier);
if (userIdClaim != null && int.TryParse(userIdClaim.Value, out int userId))
{
var claims = new[]{ new Claim(ClaimTypes.NameIdentifier, userId.ToString()) };
var identity = new ClaimsIdentity(claims, SchemeName);
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, Scheme.Name);
return AuthenticateResult.Success(ticket);
}
}
catch (Exception)
{
return AuthenticateResult.Fail("Invalid Auth Token");
}
}
catch
{
return AuthenticateResult.Fail("Invalid Authorization Header");
}
return AuthenticateResult.Fail("Missing Authorization Header");
}
}
}

View File

@ -0,0 +1,17 @@
using IO.Swagger.Models.db;
using Microsoft.EntityFrameworkCore;
namespace IO.Swagger.Services
{
public class BankDbContext : DbContext
{
public BankDbContext(DbContextOptions options) : base(options)
{
}
public DbSet<User> Users { get; set; }
}
}

View File

@ -0,0 +1,46 @@
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using System;
namespace IO.Swagger.Services
{
public class JwtService
{
private readonly string secretKey;
private readonly byte[] secretBytes;
public JwtService()
{
secretKey = Environment.GetEnvironmentVariable("JWT_SECRET_KEY");
secretBytes = Encoding.UTF8.GetBytes(secretKey);
}
public string GenerateJwt(int userId)
{
var claims = new[]
{
new Claim(ClaimTypes.NameIdentifier, userId.ToString())
// You can add more claims as needed
};
var tokenHandler = new JwtSecurityTokenHandler();
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(claims),
Expires = DateTime.UtcNow.AddHours(1), // Token expiration time
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(secretBytes), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
public int GetUserIdFromJwt(string jwtToken)
{
return -1; // Return -1 if user ID extraction fails
}
}
}

View File

@ -23,6 +23,9 @@ using Swashbuckle.AspNetCore.Swagger;
using Swashbuckle.AspNetCore.SwaggerGen;
using IO.Swagger.Filters;
using IO.Swagger.Security;
using IO.Swagger.Repositories;
using IO.Swagger.Services;
using Microsoft.EntityFrameworkCore;
namespace IO.Swagger
{
@ -94,7 +97,40 @@ namespace IO.Swagger
// Include DataAnnotation attributes on Controller Action parameters as Swagger validation rules (e.g required, pattern, ..)
// Use [ValidateModelState] on Actions to actually validate it in C# as well!
c.OperationFilter<GeneratePathParamsValidationFilter>();
c.AddSecurityDefinition("bearerAuth", new OpenApiSecurityScheme()
{
In = ParameterLocation.Header,
Description = "Please enter a valid token",
Name = "Authorization",
Type = SecuritySchemeType.Http,
BearerFormat = "JWT",
Scheme = "bearer"
});
c.AddSecurityRequirement(new OpenApiSecurityRequirement()
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type=ReferenceType.SecurityScheme,
Id="bearerAuth"
}
},
new string[]{}
}
});
});
string connectionString = Environment.GetEnvironmentVariable("DATABASE_CONNECTION_STRING");
if (string.IsNullOrEmpty(connectionString))
{
throw new Exception("Database connection string not found in environment variable.");
}
services.AddScoped<IUserRepository, UserRepository>();
services.AddDbContext<BankDbContext>(x => x.UseSqlServer(connectionString: connectionString));
services.AddSingleton<JwtService>();
}
/// <summary>
@ -103,8 +139,10 @@ namespace IO.Swagger
/// <param name="app"></param>
/// <param name="env"></param>
/// <param name="loggerFactory"></param>
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory)
/// <param name="context"></param>
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory, BankDbContext context)
{
context.Database.Migrate();
app.UseRouting();
//TODO: Uncomment this if you need wwwroot folder

View File

@ -17,7 +17,7 @@
<environmentVariable name="DOTNET_MODIFIABLE_ASSEMBLIES" value="debug" />
<environmentVariable name="ASPNETCORE_AUTO_RELOAD_WS_KEY" value="MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuvpSuAatIokMxzmPdM4egZimjWBPCukyvXO2w3GLeYU+aRSYH/KiZnBvy6785t/Fr2/Y4gF665nvaEMM1uveLOQvwRuICcL/R5mbYwKtKd/VJ4NYamzS5Zd4Fms/9eRf4IHGFu+SjX2Q/MTqVJa7a7w1hLAKoBDGbqJ2V0i7w87qj08XTcBdLRs5gL0je9EJ1QeggFzcY6SVCp+a9MFpybM57WI82h9uysW9rC9H9n0Nelc2D9+2HUbBExHQVTVXKlQ2KWHbxhBKsHzf4k4bBjy6V31e9G/dnWCciXNBQiIPidCp4Nt3AF+bpq080+aRFzIIh5wK1tupyG1BsffFHQIDAQAB" />
<environmentVariable name="ASPNETCORE_AUTO_RELOAD_VDIR" value="/" />
<environmentVariable name="DOTNET_HOTRELOAD_NAMEDPIPE_NAME" value="89813085-9fe3-4177-915d-b2a8e67e66aa" />
<environmentVariable name="DOTNET_HOTRELOAD_NAMEDPIPE_NAME" value="43e4d2da-7f6d-4426-a3ad-e156f95d80c9" />
</environmentVariables>
</aspNetCore>
</system.webServer>