Auth endpoints are now functional
This commit is contained in:
@ -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) });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
{
|
||||
|
||||
@ -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
|
||||
{
|
||||
|
||||
@ -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" />
|
||||
|
||||
60
src/IO.Swagger/Migrations/20230813022042_Initial creation.Designer.cs
generated
Normal file
60
src/IO.Swagger/Migrations/20230813022042_Initial creation.Designer.cs
generated
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
39
src/IO.Swagger/Migrations/20230813022042_Initial creation.cs
Normal file
39
src/IO.Swagger/Migrations/20230813022042_Initial creation.cs
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
59
src/IO.Swagger/Migrations/20230813022424_Change user table.Designer.cs
generated
Normal file
59
src/IO.Swagger/Migrations/20230813022424_Change user table.Designer.cs
generated
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
56
src/IO.Swagger/Migrations/BankDbContextModelSnapshot.cs
Normal file
56
src/IO.Swagger/Migrations/BankDbContextModelSnapshot.cs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
17
src/IO.Swagger/Models/db/User.cs
Normal file
17
src/IO.Swagger/Models/db/User.cs
Normal 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; }
|
||||
}
|
||||
|
||||
}
|
||||
@ -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>
|
||||
///
|
||||
@ -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>
|
||||
///
|
||||
@ -32,6 +32,7 @@ namespace IO.Swagger.Models
|
||||
|
||||
[StringLength(32, MinimumLength = 3)]
|
||||
[DataMember(Name = "firstName")]
|
||||
[Required]
|
||||
public string FirstName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@ -40,6 +41,7 @@ namespace IO.Swagger.Models
|
||||
|
||||
[StringLength(32, MinimumLength = 3)]
|
||||
[DataMember(Name = "lastName")]
|
||||
[Required]
|
||||
public string LastName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@ -47,6 +49,8 @@ namespace IO.Swagger.Models
|
||||
/// </summary>
|
||||
|
||||
[DataMember(Name = "email")]
|
||||
[EmailAddress(ErrorMessage = "Invalid email format.")]
|
||||
[Required]
|
||||
public string Email { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@ -54,6 +58,8 @@ namespace IO.Swagger.Models
|
||||
/// </summary>
|
||||
|
||||
[DataMember(Name = "password")]
|
||||
[StringLength(32, MinimumLength = 8)]
|
||||
[Required]
|
||||
public string Password { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@ -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>
|
||||
///
|
||||
@ -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>
|
||||
///
|
||||
@ -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>
|
||||
///
|
||||
@ -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>
|
||||
@ -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>
|
||||
///
|
||||
@ -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>
|
||||
///
|
||||
@ -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>
|
||||
///
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
13
src/IO.Swagger/Repositories/IUserRepository.cs
Normal file
13
src/IO.Swagger/Repositories/IUserRepository.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
77
src/IO.Swagger/Repositories/UserRepository.cs
Normal file
77
src/IO.Swagger/Repositories/UserRepository.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
17
src/IO.Swagger/Services/BankDbContext.cs
Normal file
17
src/IO.Swagger/Services/BankDbContext.cs
Normal 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; }
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
46
src/IO.Swagger/Services/JwtService.cs
Normal file
46
src/IO.Swagger/Services/JwtService.cs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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>
|
||||
|
||||
Reference in New Issue
Block a user