aboutsummaryrefslogtreecommitdiff
path: root/API
diff options
context:
space:
mode:
Diffstat (limited to 'API')
-rw-r--r--API/Controllers/UserController.cs24
-rw-r--r--API/Database/DevHiveContext.cs2
-rw-r--r--API/Database/UserDbRepository.cs6
-rw-r--r--API/Migrations/20201211193121_UserRole.Designer.cs314
-rw-r--r--API/Migrations/20201211193121_UserRole.cs17
-rw-r--r--API/Migrations/DevHiveContextModelSnapshot.cs66
-rw-r--r--API/Service/UserService.cs64
-rw-r--r--API/Startup.cs60
-rw-r--r--API/appsettings.json3
9 files changed, 514 insertions, 42 deletions
diff --git a/API/Controllers/UserController.cs b/API/Controllers/UserController.cs
index fdb1c44..ceeee33 100644
--- a/API/Controllers/UserController.cs
+++ b/API/Controllers/UserController.cs
@@ -4,6 +4,9 @@ using API.Service;
using AutoMapper;
using Microsoft.AspNetCore.Mvc;
using Data.Models.DTOs;
+using Microsoft.AspNetCore.Authorization;
+using Data.Models.Options;
+using Microsoft.Extensions.Configuration;
namespace API.Controllers
{
@@ -13,16 +16,23 @@ namespace API.Controllers
{
private readonly UserService _service;
- public UserController(DevHiveContext context, IMapper mapper)
+ public UserController(DevHiveContext context, IMapper mapper, JWTOptions jwtOptions)
{
- this._service = new UserService(context, mapper);
+ this._service = new UserService(context, mapper, jwtOptions);
}
- //Create
[HttpPost]
- public async Task<IActionResult> Create([FromBody] UserDTO userDTO)
+ [Route("login")]
+ public async Task<IActionResult> Login([FromBody] LoginDTO loginDTO)
{
- return await this._service.CreateUser(userDTO);
+ return await this._service.LoginUser(loginDTO);
+ }
+
+ [HttpPost]
+ [Route("register")]
+ public async Task<IActionResult> Register([FromBody] RegisterDTO registerDto)
+ {
+ return await this._service.RegisterUser(registerDto);
}
//Read
@@ -34,13 +44,15 @@ namespace API.Controllers
//Update
[HttpPut]
+ [Authorize]
public async Task<IActionResult> Update(int id, [FromBody] UserDTO userDTO)
{
return await this._service.UpdateUser(id, userDTO);
}
//Delete
- [HttpDelete]
+ [HttpDelete]
+ [Authorize]
public async Task<IActionResult> Delete(int id)
{
return await this._service.DeleteUser(id);
diff --git a/API/Database/DevHiveContext.cs b/API/Database/DevHiveContext.cs
index f8ddf83..7cb8f16 100644
--- a/API/Database/DevHiveContext.cs
+++ b/API/Database/DevHiveContext.cs
@@ -6,7 +6,7 @@ using Microsoft.AspNetCore.Identity;
namespace API.Database
{
- public class DevHiveContext : IdentityDbContext<User, Roles, int>
+ public class DevHiveContext : IdentityDbContext<User, UserRoles, int>
{
public DevHiveContext(DbContextOptions options)
: base(options) { }
diff --git a/API/Database/UserDbRepository.cs b/API/Database/UserDbRepository.cs
index b8bf8e4..2e7b0bb 100644
--- a/API/Database/UserDbRepository.cs
+++ b/API/Database/UserDbRepository.cs
@@ -17,6 +17,12 @@ namespace API.Database
this._dbRepository = new DbRepository<User>(context);
}
+ public User FindByUsername(string username)
+ {
+ return this._dbRepository.DbSet
+ .FirstOrDefault(usr => usr.UserName == username);
+ }
+
public bool DoesUsernameExist(string username)
{
return this._dbRepository.DbSet
diff --git a/API/Migrations/20201211193121_UserRole.Designer.cs b/API/Migrations/20201211193121_UserRole.Designer.cs
new file mode 100644
index 0000000..eb5dead
--- /dev/null
+++ b/API/Migrations/20201211193121_UserRole.Designer.cs
@@ -0,0 +1,314 @@
+// <auto-generated />
+using System;
+using API.Database;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+namespace API.Migrations
+{
+ [DbContext(typeof(DevHiveContext))]
+ [Migration("20201211193121_UserRole")]
+ partial class UserRole
+ {
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .UseIdentityByDefaultColumns()
+ .HasAnnotation("Relational:MaxIdentifierLength", 63)
+ .HasAnnotation("ProductVersion", "5.0.1");
+
+ modelBuilder.Entity("Data.Models.Classes.Language", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .UseIdentityByDefaultColumn();
+
+ b.HasKey("Id");
+
+ b.ToTable("Languages");
+ });
+
+ modelBuilder.Entity("Data.Models.Classes.Technology", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .UseIdentityByDefaultColumn();
+
+ b.HasKey("Id");
+
+ b.ToTable("Technologies");
+ });
+
+ modelBuilder.Entity("Data.Models.Classes.User", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .UseIdentityByDefaultColumn();
+
+ b.Property<int>("AccessFailedCount")
+ .HasColumnType("integer");
+
+ b.Property<string>("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("text");
+
+ b.Property<string>("Email")
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.Property<bool>("EmailConfirmed")
+ .HasColumnType("boolean");
+
+ b.Property<string>("FirstName")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property<string>("LastName")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property<bool>("LockoutEnabled")
+ .HasColumnType("boolean");
+
+ b.Property<DateTimeOffset?>("LockoutEnd")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property<string>("NormalizedEmail")
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.Property<string>("NormalizedUserName")
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.Property<string>("PasswordHash")
+ .HasColumnType("text");
+
+ b.Property<string>("PhoneNumber")
+ .HasColumnType("text");
+
+ b.Property<bool>("PhoneNumberConfirmed")
+ .HasColumnType("boolean");
+
+ b.Property<string>("ProfilePicture")
+ .HasColumnType("text");
+
+ b.Property<string>("Role")
+ .HasColumnType("text");
+
+ b.Property<string>("SecurityStamp")
+ .HasColumnType("text");
+
+ b.Property<bool>("TwoFactorEnabled")
+ .HasColumnType("boolean");
+
+ b.Property<string>("UserName")
+ .IsRequired()
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedEmail")
+ .HasDatabaseName("EmailIndex");
+
+ b.HasIndex("NormalizedUserName")
+ .IsUnique()
+ .HasDatabaseName("UserNameIndex");
+
+ b.HasIndex("UserName")
+ .IsUnique();
+
+ b.ToTable("AspNetUsers");
+ });
+
+ modelBuilder.Entity("Data.Models.Classes.UserRoles", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .UseIdentityByDefaultColumn();
+
+ b.Property<string>("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("text");
+
+ b.Property<string>("Name")
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.Property<string>("NormalizedName")
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedName")
+ .IsUnique()
+ .HasDatabaseName("RoleNameIndex");
+
+ b.ToTable("AspNetRoles");
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<int>", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .UseIdentityByDefaultColumn();
+
+ b.Property<string>("ClaimType")
+ .HasColumnType("text");
+
+ b.Property<string>("ClaimValue")
+ .HasColumnType("text");
+
+ b.Property<int>("RoleId")
+ .HasColumnType("integer");
+
+ b.HasKey("Id");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("AspNetRoleClaims");
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<int>", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .UseIdentityByDefaultColumn();
+
+ b.Property<string>("ClaimType")
+ .HasColumnType("text");
+
+ b.Property<string>("ClaimValue")
+ .HasColumnType("text");
+
+ b.Property<int>("UserId")
+ .HasColumnType("integer");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AspNetUserClaims");
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<int>", b =>
+ {
+ b.Property<string>("LoginProvider")
+ .HasColumnType("text");
+
+ b.Property<string>("ProviderKey")
+ .HasColumnType("text");
+
+ b.Property<string>("ProviderDisplayName")
+ .HasColumnType("text");
+
+ b.Property<int>("UserId")
+ .HasColumnType("integer");
+
+ b.HasKey("LoginProvider", "ProviderKey");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AspNetUserLogins");
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<int>", b =>
+ {
+ b.Property<int>("UserId")
+ .HasColumnType("integer");
+
+ b.Property<int>("RoleId")
+ .HasColumnType("integer");
+
+ b.HasKey("UserId", "RoleId");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("AspNetUserRoles");
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<int>", b =>
+ {
+ b.Property<int>("UserId")
+ .HasColumnType("integer");
+
+ b.Property<string>("LoginProvider")
+ .HasColumnType("text");
+
+ b.Property<string>("Name")
+ .HasColumnType("text");
+
+ b.Property<string>("Value")
+ .HasColumnType("text");
+
+ b.HasKey("UserId", "LoginProvider", "Name");
+
+ b.ToTable("AspNetUserTokens");
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<int>", b =>
+ {
+ b.HasOne("Data.Models.Classes.UserRoles", null)
+ .WithMany()
+ .HasForeignKey("RoleId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<int>", b =>
+ {
+ b.HasOne("Data.Models.Classes.User", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<int>", b =>
+ {
+ b.HasOne("Data.Models.Classes.User", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<int>", b =>
+ {
+ b.HasOne("Data.Models.Classes.UserRoles", null)
+ .WithMany()
+ .HasForeignKey("RoleId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("Data.Models.Classes.User", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<int>", b =>
+ {
+ b.HasOne("Data.Models.Classes.User", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/API/Migrations/20201211193121_UserRole.cs b/API/Migrations/20201211193121_UserRole.cs
new file mode 100644
index 0000000..d934cc1
--- /dev/null
+++ b/API/Migrations/20201211193121_UserRole.cs
@@ -0,0 +1,17 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+namespace API.Migrations
+{
+ public partial class UserRole : Migration
+ {
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+
+ }
+
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+
+ }
+ }
+}
diff --git a/API/Migrations/DevHiveContextModelSnapshot.cs b/API/Migrations/DevHiveContextModelSnapshot.cs
index eb9d6a4..2c517ca 100644
--- a/API/Migrations/DevHiveContextModelSnapshot.cs
+++ b/API/Migrations/DevHiveContextModelSnapshot.cs
@@ -31,34 +31,6 @@ namespace API.Migrations
b.ToTable("Languages");
});
- modelBuilder.Entity("Data.Models.Classes.Roles", b =>
- {
- b.Property<int>("Id")
- .ValueGeneratedOnAdd()
- .HasColumnType("integer")
- .UseIdentityByDefaultColumn();
-
- b.Property<string>("ConcurrencyStamp")
- .IsConcurrencyToken()
- .HasColumnType("text");
-
- b.Property<string>("Name")
- .HasMaxLength(256)
- .HasColumnType("character varying(256)");
-
- b.Property<string>("NormalizedName")
- .HasMaxLength(256)
- .HasColumnType("character varying(256)");
-
- b.HasKey("Id");
-
- b.HasIndex("NormalizedName")
- .IsUnique()
- .HasDatabaseName("RoleNameIndex");
-
- b.ToTable("AspNetRoles");
- });
-
modelBuilder.Entity("Data.Models.Classes.Technology", b =>
{
b.Property<int>("Id")
@@ -126,6 +98,9 @@ namespace API.Migrations
b.Property<string>("ProfilePicture")
.HasColumnType("text");
+ b.Property<string>("Role")
+ .HasColumnType("text");
+
b.Property<string>("SecurityStamp")
.HasColumnType("text");
@@ -146,9 +121,40 @@ namespace API.Migrations
.IsUnique()
.HasDatabaseName("UserNameIndex");
+ b.HasIndex("UserName")
+ .IsUnique();
+
b.ToTable("AspNetUsers");
});
+ modelBuilder.Entity("Data.Models.Classes.UserRoles", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .UseIdentityByDefaultColumn();
+
+ b.Property<string>("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("text");
+
+ b.Property<string>("Name")
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.Property<string>("NormalizedName")
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedName")
+ .IsUnique()
+ .HasDatabaseName("RoleNameIndex");
+
+ b.ToTable("AspNetRoles");
+ });
+
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<int>", b =>
{
b.Property<int>("Id")
@@ -252,7 +258,7 @@ namespace API.Migrations
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<int>", b =>
{
- b.HasOne("Data.Models.Classes.Roles", null)
+ b.HasOne("Data.Models.Classes.UserRoles", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
@@ -279,7 +285,7 @@ namespace API.Migrations
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<int>", b =>
{
- b.HasOne("Data.Models.Classes.Roles", null)
+ b.HasOne("Data.Models.Classes.UserRoles", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
diff --git a/API/Service/UserService.cs b/API/Service/UserService.cs
index 3c3b390..797a924 100644
--- a/API/Service/UserService.cs
+++ b/API/Service/UserService.cs
@@ -4,6 +4,13 @@ using AutoMapper;
using Data.Models.Classes;
using Data.Models.DTOs;
using Microsoft.AspNetCore.Mvc;
+using Data.Models.Options;
+using System.IdentityModel.Tokens.Jwt;
+using Microsoft.IdentityModel.Tokens;
+using System.Security.Claims;
+using System;
+using System.Text;
+using Microsoft.Extensions.Configuration;
namespace API.Service
{
@@ -11,24 +18,71 @@ namespace API.Service
{
private readonly UserDbRepository _userDbRepository;
private readonly IMapper _userMapper;
+ private readonly JWTOptions _jwtOptions;
- public UserService(DevHiveContext context, IMapper mapper)
+ public UserService(DevHiveContext context, IMapper mapper, JWTOptions jwtOptions)
{
this._userDbRepository = new UserDbRepository(context);
this._userMapper = mapper;
+ this._jwtOptions = jwtOptions;
}
-
- public async Task<IActionResult> CreateUser(UserDTO userDTO)
+
+ public async Task<IActionResult> LoginUser(LoginDTO loginDTO)
{
- if (this._userDbRepository.DoesUsernameExist(userDTO.UserName))
+ User user = this._userDbRepository.FindByUsername(loginDTO.UserName);
+
+ if (user == null)
+ return new NotFoundObjectResult("User does not exist!");
+
+ // Get key from appsettings.json
+ var key = Encoding.ASCII.GetBytes(_jwtOptions.Secret);
+
+ if (user.PasswordHash != GeneratePasswordHash(loginDTO.Password))
+ return new BadRequestObjectResult("Incorrect password!");
+
+ // Create Jwt Token configuration
+ var tokenHandler = new JwtSecurityTokenHandler();
+ var tokenDescriptor = new SecurityTokenDescriptor
+ {
+ Subject = new ClaimsIdentity(new Claim[]
+ {
+ new Claim(ClaimTypes.Role, user.Role) // Authorize user by role
+ }),
+ Expires = DateTime.UtcNow.AddDays(7),
+ SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
+ };
+
+ // Create Jwt Token
+ var token = tokenHandler.CreateToken(tokenDescriptor);
+ var tokenString = tokenHandler.WriteToken(token);
+
+ return new OkObjectResult(new
+ {
+ Token = tokenString
+ });
+ }
+
+ public async Task<IActionResult> RegisterUser(RegisterDTO registerDTO)
+ {
+
+ if (this._userDbRepository.DoesUsernameExist(registerDTO.UserName))
return new BadRequestObjectResult("Username already exists!");
- User user = this._userMapper.Map<User>(userDTO);
+ User user = this._userMapper.Map<User>(registerDTO);
+
+ user.Role = UserRoles.User;
+ user.PasswordHash = GeneratePasswordHash(registerDTO.Password);
+
await this._userDbRepository.AddAsync(user);
return new CreatedResult("CreateUser", user);
}
+ private string GeneratePasswordHash(string password)
+ {
+ return password; // TEMPORARY!
+ }
+
public async Task<IActionResult> GetUserById(int id)
{
User user = await this._userDbRepository.FindByIdAsync(id);
diff --git a/API/Startup.cs b/API/Startup.cs
index b3d0769..49dd794 100644
--- a/API/Startup.cs
+++ b/API/Startup.cs
@@ -5,7 +5,17 @@ using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
+<<<<<<< HEAD
+using Microsoft.OpenApi.Models;
+using Data.Models.Classes;
+using Data.Models.Options;
+using Microsoft.IdentityModel.Tokens;
+using Microsoft.AspNetCore.Authentication.JwtBearer;
+using System.Text;
+using System.Threading.Tasks;
+=======
using API.Extensions;
+>>>>>>> 8bd7295dc4694c1c0ed6fbc05d390223bfc4ef05
namespace API
{
@@ -23,6 +33,56 @@ namespace API
{
services.AddControllers();
+ services.AddDbContext<DevHiveContext>(options =>
+ options.UseNpgsql(Configuration.GetConnectionString("DEV")));
+
+ services.AddIdentity<User, UserRoles>()
+ .AddEntityFrameworkStores<DevHiveContext>();
+
+ services.Configure<IdentityOptions>(options =>
+ {
+ options.User.RequireUniqueEmail = true;
+
+ options.Password.RequiredLength = 5;
+ });
+
+ services.AddSingleton<JWTOptions>(
+ new JWTOptions(Configuration.GetSection("AppSettings").GetSection("Secret").Value));
+
+ // Get key from appsettings.json
+ var key = Encoding.ASCII.GetBytes(Configuration.GetSection("AppSettings").GetSection("Secret").Value);
+ // Setup Jwt Authentication
+ services.AddAuthentication(x =>
+ {
+ x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
+ x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
+ })
+ .AddJwtBearer(x =>
+ {
+ x.Events = new JwtBearerEvents
+ {
+ OnTokenValidated = context =>
+ {
+ // TODO: add more authentication
+ return Task.CompletedTask;
+ }
+ };
+ x.RequireHttpsMetadata = false;
+ x.SaveToken = true;
+ x.TokenValidationParameters = new TokenValidationParameters
+ {
+ ValidateIssuerSigningKey = true,
+ IssuerSigningKey = new SymmetricSecurityKey(key),
+ ValidateIssuer = false,
+ ValidateAudience = false
+ };
+ });
+
+ services.AddSwaggerGen(c =>
+ {
+ c.SwaggerDoc("v1", new OpenApiInfo { Title = "API", Version = "v1" });
+ });
+
services.DatabaseConfiguration(Configuration);
services.SwaggerConfiguration();
services.JWTConfiguration();
diff --git a/API/appsettings.json b/API/appsettings.json
index 31c8109..1784183 100644
--- a/API/appsettings.json
+++ b/API/appsettings.json
@@ -1,4 +1,7 @@
{
+ "AppSettings": {
+ "Secret": "ADD_ANY_STRING_WITH_32_OR_MORE_CHARACTERS"
+ },
"ConnectionStrings" : {
"DEV": "Server=localhost;Port=5432;Database=API;User Id=postgres;Password=;"
},