remove separate identity api
This commit is contained in:
parent
50e3f220e1
commit
628fab2146
27 changed files with 0 additions and 903 deletions
|
@ -1,135 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using AutoMapper;
|
|
||||||
using System.IdentityModel.Tokens.Jwt;
|
|
||||||
using WebApi.Helpers;
|
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
using System.Text;
|
|
||||||
using Microsoft.IdentityModel.Tokens;
|
|
||||||
using System.Security.Claims;
|
|
||||||
using Microsoft.AspNetCore.Authorization;
|
|
||||||
using WebApi.Services;
|
|
||||||
using WebApi.Entities;
|
|
||||||
using WebApi.Models.Users;
|
|
||||||
|
|
||||||
namespace WebApi.Controllers
|
|
||||||
{
|
|
||||||
[Authorize]
|
|
||||||
[ApiController]
|
|
||||||
[Route("[controller]")]
|
|
||||||
public class UsersController : ControllerBase
|
|
||||||
{
|
|
||||||
private IUserService _userService;
|
|
||||||
private IMapper _mapper;
|
|
||||||
private readonly AppSettings _appSettings;
|
|
||||||
|
|
||||||
public UsersController(
|
|
||||||
IUserService userService,
|
|
||||||
IMapper mapper,
|
|
||||||
IOptions<AppSettings> appSettings)
|
|
||||||
{
|
|
||||||
_userService = userService;
|
|
||||||
_mapper = mapper;
|
|
||||||
_appSettings = appSettings.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
[AllowAnonymous]
|
|
||||||
[HttpPost("authenticate")]
|
|
||||||
public IActionResult Authenticate([FromBody]AuthenticateModel model)
|
|
||||||
{
|
|
||||||
var user = _userService.Authenticate(model.Username, model.Password);
|
|
||||||
|
|
||||||
if (user == null)
|
|
||||||
return BadRequest(new { message = "Username or password is incorrect" });
|
|
||||||
|
|
||||||
var tokenHandler = new JwtSecurityTokenHandler();
|
|
||||||
var key = Encoding.ASCII.GetBytes(_appSettings.Secret);
|
|
||||||
var tokenDescriptor = new SecurityTokenDescriptor
|
|
||||||
{
|
|
||||||
Subject = new ClaimsIdentity(new Claim[]
|
|
||||||
{
|
|
||||||
new Claim(ClaimTypes.Name, user.Id.ToString())
|
|
||||||
}),
|
|
||||||
Expires = DateTime.UtcNow.AddDays(7),
|
|
||||||
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
|
|
||||||
};
|
|
||||||
var token = tokenHandler.CreateToken(tokenDescriptor);
|
|
||||||
var tokenString = tokenHandler.WriteToken(token);
|
|
||||||
|
|
||||||
// return basic user info and authentication token
|
|
||||||
return Ok(new
|
|
||||||
{
|
|
||||||
Id = user.Id,
|
|
||||||
Username = user.Username,
|
|
||||||
FirstName = user.FirstName,
|
|
||||||
LastName = user.LastName,
|
|
||||||
Initials = user.Initials,
|
|
||||||
Token = tokenString
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
[AllowAnonymous]
|
|
||||||
[HttpPost("register")]
|
|
||||||
public IActionResult Register([FromBody]RegisterModel model)
|
|
||||||
{
|
|
||||||
// map model to entity
|
|
||||||
var user = _mapper.Map<User>(model);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// create user
|
|
||||||
_userService.Create(user, model.Password);
|
|
||||||
return Ok();
|
|
||||||
}
|
|
||||||
catch (AppException ex)
|
|
||||||
{
|
|
||||||
// return error message if there was an exception
|
|
||||||
return BadRequest(new { message = ex.Message });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet]
|
|
||||||
public IActionResult GetAll()
|
|
||||||
{
|
|
||||||
var users = _userService.GetAll();
|
|
||||||
var model = _mapper.Map<IList<UserModel>>(users);
|
|
||||||
return Ok(model);
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{id}")]
|
|
||||||
public IActionResult GetById(int id)
|
|
||||||
{
|
|
||||||
var user = _userService.GetById(id);
|
|
||||||
var model = _mapper.Map<UserModel>(user);
|
|
||||||
return Ok(model);
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPut("{id}")]
|
|
||||||
public IActionResult Update(int id, [FromBody]UpdateModel model)
|
|
||||||
{
|
|
||||||
// map model to entity and set id
|
|
||||||
var user = _mapper.Map<User>(model);
|
|
||||||
user.Id = id;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// update user
|
|
||||||
_userService.Update(user, model.Password);
|
|
||||||
return Ok();
|
|
||||||
}
|
|
||||||
catch (AppException ex)
|
|
||||||
{
|
|
||||||
// return error message if there was an exception
|
|
||||||
return BadRequest(new { message = ex.Message });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpDelete("{id}")]
|
|
||||||
public IActionResult Delete(int id)
|
|
||||||
{
|
|
||||||
_userService.Delete(id);
|
|
||||||
return Ok();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
namespace WebApi.Entities
|
|
||||||
{
|
|
||||||
public class User
|
|
||||||
{
|
|
||||||
public int Id { get; set; }
|
|
||||||
public string FirstName { get; set; }
|
|
||||||
public string LastName { get; set; }
|
|
||||||
public string Username { get; set; }
|
|
||||||
public string Initials { get; set; }
|
|
||||||
public byte[] PasswordHash { get; set; }
|
|
||||||
public byte[] PasswordSalt { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Globalization;
|
|
||||||
|
|
||||||
namespace WebApi.Helpers
|
|
||||||
{
|
|
||||||
// Custom exception class for throwing application specific exceptions (e.g. for validation)
|
|
||||||
// that can be caught and handled within the application
|
|
||||||
public class AppException : Exception
|
|
||||||
{
|
|
||||||
public AppException() : base() {}
|
|
||||||
|
|
||||||
public AppException(string message) : base(message) { }
|
|
||||||
|
|
||||||
public AppException(string message, params object[] args)
|
|
||||||
: base(String.Format(CultureInfo.CurrentCulture, message, args))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
namespace WebApi.Helpers
|
|
||||||
{
|
|
||||||
public class AppSettings
|
|
||||||
{
|
|
||||||
public string Secret { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
using AutoMapper;
|
|
||||||
using WebApi.Entities;
|
|
||||||
using WebApi.Models.Users;
|
|
||||||
|
|
||||||
namespace WebApi.Helpers
|
|
||||||
{
|
|
||||||
public class AutoMapperProfile : Profile
|
|
||||||
{
|
|
||||||
public AutoMapperProfile()
|
|
||||||
{
|
|
||||||
CreateMap<User, UserModel>();
|
|
||||||
CreateMap<RegisterModel, User>();
|
|
||||||
CreateMap<UpdateModel, User>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using WebApi.Entities;
|
|
||||||
|
|
||||||
namespace WebApi.Helpers
|
|
||||||
{
|
|
||||||
public class DataContext : DbContext
|
|
||||||
{
|
|
||||||
protected readonly IConfiguration Configuration;
|
|
||||||
|
|
||||||
public DataContext(IConfiguration configuration)
|
|
||||||
{
|
|
||||||
Configuration = configuration;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnConfiguring(DbContextOptionsBuilder options)
|
|
||||||
{
|
|
||||||
// connect to sql server database
|
|
||||||
options.UseSqlServer(Configuration.GetConnectionString("WebApiDatabase"));
|
|
||||||
}
|
|
||||||
|
|
||||||
public DbSet<User> Users { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
|
|
||||||
namespace WebApi.Helpers
|
|
||||||
{
|
|
||||||
public class SqliteDataContext : DataContext
|
|
||||||
{
|
|
||||||
public SqliteDataContext(IConfiguration configuration) : base(configuration) { }
|
|
||||||
|
|
||||||
protected override void OnConfiguring(DbContextOptionsBuilder options)
|
|
||||||
{
|
|
||||||
// connect to sqlite database
|
|
||||||
options.UseSqlite(Configuration.GetConnectionString("WebApiDatabase"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Binary file not shown.
|
@ -1,53 +0,0 @@
|
||||||
// <auto-generated />
|
|
||||||
using System;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
|
||||||
using Microsoft.EntityFrameworkCore.Metadata;
|
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
|
||||||
using WebApi.Helpers;
|
|
||||||
|
|
||||||
namespace WebApi.Migrations.SqlServerMigrations
|
|
||||||
{
|
|
||||||
[DbContext(typeof(DataContext))]
|
|
||||||
[Migration("20200102103423_InitialCreate")]
|
|
||||||
partial class InitialCreate
|
|
||||||
{
|
|
||||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
|
||||||
{
|
|
||||||
#pragma warning disable 612, 618
|
|
||||||
modelBuilder
|
|
||||||
.HasAnnotation("ProductVersion", "3.1.0")
|
|
||||||
.HasAnnotation("Relational:MaxIdentifierLength", 128)
|
|
||||||
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
|
|
||||||
|
|
||||||
modelBuilder.Entity("WebApi.Entities.User", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("int")
|
|
||||||
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
|
|
||||||
|
|
||||||
b.Property<string>("FirstName")
|
|
||||||
.HasColumnType("nvarchar(max)");
|
|
||||||
|
|
||||||
b.Property<string>("LastName")
|
|
||||||
.HasColumnType("nvarchar(max)");
|
|
||||||
|
|
||||||
b.Property<byte[]>("PasswordHash")
|
|
||||||
.HasColumnType("varbinary(max)");
|
|
||||||
|
|
||||||
b.Property<byte[]>("PasswordSalt")
|
|
||||||
.HasColumnType("varbinary(max)");
|
|
||||||
|
|
||||||
b.Property<string>("Username")
|
|
||||||
.HasColumnType("nvarchar(max)");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.ToTable("Users");
|
|
||||||
});
|
|
||||||
#pragma warning restore 612, 618
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
using System;
|
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
|
|
||||||
namespace WebApi.Migrations.SqlServerMigrations
|
|
||||||
{
|
|
||||||
public partial class InitialCreate : Migration
|
|
||||||
{
|
|
||||||
protected override void Up(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.CreateTable(
|
|
||||||
name: "Users",
|
|
||||||
columns: table => new
|
|
||||||
{
|
|
||||||
Id = table.Column<int>(nullable: false)
|
|
||||||
.Annotation("SqlServer:Identity", "1, 1"),
|
|
||||||
FirstName = table.Column<string>(nullable: true),
|
|
||||||
LastName = table.Column<string>(nullable: true),
|
|
||||||
Username = table.Column<string>(nullable: true),
|
|
||||||
PasswordHash = table.Column<byte[]>(nullable: true),
|
|
||||||
PasswordSalt = table.Column<byte[]>(nullable: true)
|
|
||||||
},
|
|
||||||
constraints: table =>
|
|
||||||
{
|
|
||||||
table.PrimaryKey("PK_Users", x => x.Id);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Down(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.DropTable(
|
|
||||||
name: "Users");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,51 +0,0 @@
|
||||||
// <auto-generated />
|
|
||||||
using System;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
|
||||||
using Microsoft.EntityFrameworkCore.Metadata;
|
|
||||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
|
||||||
using WebApi.Helpers;
|
|
||||||
|
|
||||||
namespace WebApi.Migrations.SqlServerMigrations
|
|
||||||
{
|
|
||||||
[DbContext(typeof(DataContext))]
|
|
||||||
partial class DataContextModelSnapshot : ModelSnapshot
|
|
||||||
{
|
|
||||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
|
||||||
{
|
|
||||||
#pragma warning disable 612, 618
|
|
||||||
modelBuilder
|
|
||||||
.HasAnnotation("ProductVersion", "3.1.0")
|
|
||||||
.HasAnnotation("Relational:MaxIdentifierLength", 128)
|
|
||||||
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
|
|
||||||
|
|
||||||
modelBuilder.Entity("WebApi.Entities.User", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("int")
|
|
||||||
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
|
|
||||||
|
|
||||||
b.Property<string>("FirstName")
|
|
||||||
.HasColumnType("nvarchar(max)");
|
|
||||||
|
|
||||||
b.Property<string>("LastName")
|
|
||||||
.HasColumnType("nvarchar(max)");
|
|
||||||
|
|
||||||
b.Property<byte[]>("PasswordHash")
|
|
||||||
.HasColumnType("varbinary(max)");
|
|
||||||
|
|
||||||
b.Property<byte[]>("PasswordSalt")
|
|
||||||
.HasColumnType("varbinary(max)");
|
|
||||||
|
|
||||||
b.Property<string>("Username")
|
|
||||||
.HasColumnType("nvarchar(max)");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.ToTable("Users");
|
|
||||||
});
|
|
||||||
#pragma warning restore 612, 618
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,52 +0,0 @@
|
||||||
// <auto-generated />
|
|
||||||
using System;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
|
||||||
using WebApi.Helpers;
|
|
||||||
|
|
||||||
namespace WebApi.Migrations.SqliteMigrations
|
|
||||||
{
|
|
||||||
[DbContext(typeof(SqliteDataContext))]
|
|
||||||
[Migration("20200102102942_InitialCreate")]
|
|
||||||
partial class InitialCreate
|
|
||||||
{
|
|
||||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
|
||||||
{
|
|
||||||
#pragma warning disable 612, 618
|
|
||||||
modelBuilder
|
|
||||||
.HasAnnotation("ProductVersion", "3.1.0");
|
|
||||||
|
|
||||||
modelBuilder.Entity("WebApi.Entities.User", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<string>("FirstName")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("LastName")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("Initials")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<byte[]>("PasswordHash")
|
|
||||||
.HasColumnType("BLOB");
|
|
||||||
|
|
||||||
b.Property<byte[]>("PasswordSalt")
|
|
||||||
.HasColumnType("BLOB");
|
|
||||||
|
|
||||||
b.Property<string>("Username")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.ToTable("Users");
|
|
||||||
});
|
|
||||||
#pragma warning restore 612, 618
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
using System;
|
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
|
|
||||||
namespace WebApi.Migrations.SqliteMigrations
|
|
||||||
{
|
|
||||||
public partial class InitialCreate : Migration
|
|
||||||
{
|
|
||||||
protected override void Up(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.CreateTable(
|
|
||||||
name: "Users",
|
|
||||||
columns: table => new
|
|
||||||
{
|
|
||||||
Id = table.Column<int>(nullable: false)
|
|
||||||
.Annotation("Sqlite:Autoincrement", true),
|
|
||||||
FirstName = table.Column<string>(nullable: true),
|
|
||||||
LastName = table.Column<string>(nullable: true),
|
|
||||||
Username = table.Column<string>(nullable: true),
|
|
||||||
Initials = table.Column<string>(nullable: true),
|
|
||||||
PasswordHash = table.Column<byte[]>(nullable: true),
|
|
||||||
PasswordSalt = table.Column<byte[]>(nullable: true)
|
|
||||||
},
|
|
||||||
constraints: table =>
|
|
||||||
{
|
|
||||||
table.PrimaryKey("PK_Users", x => x.Id);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Down(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.DropTable(
|
|
||||||
name: "Users");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,50 +0,0 @@
|
||||||
// <auto-generated />
|
|
||||||
using System;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
|
||||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
|
||||||
using WebApi.Helpers;
|
|
||||||
|
|
||||||
namespace WebApi.Migrations.SqliteMigrations
|
|
||||||
{
|
|
||||||
[DbContext(typeof(SqliteDataContext))]
|
|
||||||
partial class SqliteDataContextModelSnapshot : ModelSnapshot
|
|
||||||
{
|
|
||||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
|
||||||
{
|
|
||||||
#pragma warning disable 612, 618
|
|
||||||
modelBuilder
|
|
||||||
.HasAnnotation("ProductVersion", "3.1.0");
|
|
||||||
|
|
||||||
modelBuilder.Entity("WebApi.Entities.User", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<string>("FirstName")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("LastName")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("Initials")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<byte[]>("PasswordHash")
|
|
||||||
.HasColumnType("BLOB");
|
|
||||||
|
|
||||||
b.Property<byte[]>("PasswordSalt")
|
|
||||||
.HasColumnType("BLOB");
|
|
||||||
|
|
||||||
b.Property<string>("Username")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.ToTable("Users");
|
|
||||||
});
|
|
||||||
#pragma warning restore 612, 618
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
|
|
||||||
namespace WebApi.Models.Users
|
|
||||||
{
|
|
||||||
public class AuthenticateModel
|
|
||||||
{
|
|
||||||
[Required]
|
|
||||||
public string Username { get; set; }
|
|
||||||
|
|
||||||
[Required]
|
|
||||||
public string Password { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
|
|
||||||
namespace WebApi.Models.Users
|
|
||||||
{
|
|
||||||
public class RegisterModel
|
|
||||||
{
|
|
||||||
[Required]
|
|
||||||
public string FirstName { get; set; }
|
|
||||||
|
|
||||||
[Required]
|
|
||||||
public string LastName { get; set; }
|
|
||||||
|
|
||||||
[Required]
|
|
||||||
public string Username { get; set; }
|
|
||||||
|
|
||||||
[Required]
|
|
||||||
public string Password { get; set; }
|
|
||||||
|
|
||||||
[Required]
|
|
||||||
public string Initials { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
namespace WebApi.Models.Users
|
|
||||||
{
|
|
||||||
public class UpdateModel
|
|
||||||
{
|
|
||||||
public string FirstName { get; set; }
|
|
||||||
public string LastName { get; set; }
|
|
||||||
public string Username { get; set; }
|
|
||||||
public string Password { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
namespace WebApi.Models.Users
|
|
||||||
{
|
|
||||||
public class UserModel
|
|
||||||
{
|
|
||||||
public int Id { get; set; }
|
|
||||||
public string FirstName { get; set; }
|
|
||||||
public string LastName { get; set; }
|
|
||||||
public string Username { get; set; }
|
|
||||||
public string Initials { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
using Microsoft.AspNetCore.Hosting;
|
|
||||||
using Microsoft.Extensions.Hosting;
|
|
||||||
|
|
||||||
namespace WebApi
|
|
||||||
{
|
|
||||||
public class Program
|
|
||||||
{
|
|
||||||
public static void Main(string[] args)
|
|
||||||
{
|
|
||||||
CreateHostBuilder(args).Build().Run();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IHostBuilder CreateHostBuilder(string[] args) =>
|
|
||||||
Host.CreateDefaultBuilder(args)
|
|
||||||
.ConfigureWebHostDefaults(webBuilder =>
|
|
||||||
{
|
|
||||||
webBuilder.UseStartup<Startup>()
|
|
||||||
.UseUrls("http://localhost:4000");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
{
|
|
||||||
"profiles": {
|
|
||||||
"Development": {
|
|
||||||
"commandName": "Project",
|
|
||||||
"environmentVariables": {
|
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,159 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using WebApi.Entities;
|
|
||||||
using WebApi.Helpers;
|
|
||||||
|
|
||||||
namespace WebApi.Services
|
|
||||||
{
|
|
||||||
public interface IUserService
|
|
||||||
{
|
|
||||||
User Authenticate(string username, string password);
|
|
||||||
IEnumerable<User> GetAll();
|
|
||||||
User GetById(int id);
|
|
||||||
User Create(User user, string password);
|
|
||||||
void Update(User user, string password = null);
|
|
||||||
void Delete(int id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class UserService : IUserService
|
|
||||||
{
|
|
||||||
private DataContext _context;
|
|
||||||
|
|
||||||
public UserService(DataContext context)
|
|
||||||
{
|
|
||||||
_context = context;
|
|
||||||
}
|
|
||||||
|
|
||||||
public User Authenticate(string username, string password)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password))
|
|
||||||
return null;
|
|
||||||
|
|
||||||
var user = _context.Users.SingleOrDefault(x => x.Username == username);
|
|
||||||
|
|
||||||
// check if username exists
|
|
||||||
if (user == null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
// check if password is correct
|
|
||||||
if (!VerifyPasswordHash(password, user.PasswordHash, user.PasswordSalt))
|
|
||||||
return null;
|
|
||||||
|
|
||||||
// authentication successful
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerable<User> GetAll()
|
|
||||||
{
|
|
||||||
return _context.Users;
|
|
||||||
}
|
|
||||||
|
|
||||||
public User GetById(int id)
|
|
||||||
{
|
|
||||||
return _context.Users.Find(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public User Create(User user, string password)
|
|
||||||
{
|
|
||||||
// validation
|
|
||||||
if (string.IsNullOrWhiteSpace(password))
|
|
||||||
throw new AppException("Password is required");
|
|
||||||
|
|
||||||
if (_context.Users.Any(x => x.Username == user.Username))
|
|
||||||
throw new AppException("Username \"" + user.Username + "\" is already taken");
|
|
||||||
|
|
||||||
byte[] passwordHash, passwordSalt;
|
|
||||||
CreatePasswordHash(password, out passwordHash, out passwordSalt);
|
|
||||||
|
|
||||||
user.PasswordHash = passwordHash;
|
|
||||||
user.PasswordSalt = passwordSalt;
|
|
||||||
|
|
||||||
_context.Users.Add(user);
|
|
||||||
_context.SaveChanges();
|
|
||||||
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Update(User userParam, string password = null)
|
|
||||||
{
|
|
||||||
var user = _context.Users.Find(userParam.Id);
|
|
||||||
|
|
||||||
if (user == null)
|
|
||||||
throw new AppException("User not found");
|
|
||||||
|
|
||||||
// update username if it has changed
|
|
||||||
if (!string.IsNullOrWhiteSpace(userParam.Username) && userParam.Username != user.Username)
|
|
||||||
{
|
|
||||||
// throw error if the new username is already taken
|
|
||||||
if (_context.Users.Any(x => x.Username == userParam.Username))
|
|
||||||
throw new AppException("Username " + userParam.Username + " is already taken");
|
|
||||||
|
|
||||||
user.Username = userParam.Username;
|
|
||||||
}
|
|
||||||
|
|
||||||
// update user properties if provided
|
|
||||||
if (!string.IsNullOrWhiteSpace(userParam.FirstName))
|
|
||||||
user.FirstName = userParam.FirstName;
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(userParam.LastName))
|
|
||||||
user.LastName = userParam.LastName;
|
|
||||||
|
|
||||||
// update password if provided
|
|
||||||
if (!string.IsNullOrWhiteSpace(password))
|
|
||||||
{
|
|
||||||
byte[] passwordHash, passwordSalt;
|
|
||||||
CreatePasswordHash(password, out passwordHash, out passwordSalt);
|
|
||||||
|
|
||||||
user.PasswordHash = passwordHash;
|
|
||||||
user.PasswordSalt = passwordSalt;
|
|
||||||
}
|
|
||||||
|
|
||||||
_context.Users.Update(user);
|
|
||||||
_context.SaveChanges();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Delete(int id)
|
|
||||||
{
|
|
||||||
var user = _context.Users.Find(id);
|
|
||||||
if (user != null)
|
|
||||||
{
|
|
||||||
_context.Users.Remove(user);
|
|
||||||
_context.SaveChanges();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// private helper methods
|
|
||||||
|
|
||||||
private static void CreatePasswordHash(string password, out byte[] passwordHash, out byte[] passwordSalt)
|
|
||||||
{
|
|
||||||
if (password == null) throw new ArgumentNullException("password");
|
|
||||||
if (string.IsNullOrWhiteSpace(password)) throw new ArgumentException("Value cannot be empty or whitespace only string.", "password");
|
|
||||||
|
|
||||||
using (var hmac = new System.Security.Cryptography.HMACSHA512())
|
|
||||||
{
|
|
||||||
passwordSalt = hmac.Key;
|
|
||||||
passwordHash = hmac.ComputeHash(System.Text.Encoding.UTF8.GetBytes(password));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool VerifyPasswordHash(string password, byte[] storedHash, byte[] storedSalt)
|
|
||||||
{
|
|
||||||
if (password == null) throw new ArgumentNullException("password");
|
|
||||||
if (string.IsNullOrWhiteSpace(password)) throw new ArgumentException("Value cannot be empty or whitespace only string.", "password");
|
|
||||||
if (storedHash.Length != 64) throw new ArgumentException("Invalid length of password hash (64 bytes expected).", "passwordHash");
|
|
||||||
if (storedSalt.Length != 128) throw new ArgumentException("Invalid length of password salt (128 bytes expected).", "passwordHash");
|
|
||||||
|
|
||||||
using (var hmac = new System.Security.Cryptography.HMACSHA512(storedSalt))
|
|
||||||
{
|
|
||||||
var computedHash = hmac.ComputeHash(System.Text.Encoding.UTF8.GetBytes(password));
|
|
||||||
for (int i = 0; i < computedHash.Length; i++)
|
|
||||||
{
|
|
||||||
if (computedHash[i] != storedHash[i]) return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,106 +0,0 @@
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Builder;
|
|
||||||
using Microsoft.AspNetCore.Hosting;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Microsoft.Extensions.Hosting;
|
|
||||||
using WebApi.Helpers;
|
|
||||||
using WebApi.Services;
|
|
||||||
using AutoMapper;
|
|
||||||
using Microsoft.IdentityModel.Tokens;
|
|
||||||
using System.Text;
|
|
||||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace WebApi
|
|
||||||
{
|
|
||||||
public class Startup
|
|
||||||
{
|
|
||||||
private readonly IWebHostEnvironment _env;
|
|
||||||
private readonly IConfiguration _configuration;
|
|
||||||
|
|
||||||
public Startup(IWebHostEnvironment env, IConfiguration configuration)
|
|
||||||
{
|
|
||||||
_env = env;
|
|
||||||
_configuration = configuration;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This method gets called by the runtime. Use this method to add services to the container.
|
|
||||||
public void ConfigureServices(IServiceCollection services)
|
|
||||||
{
|
|
||||||
// use sql server db in production and sqlite db in development
|
|
||||||
if (_env.IsProduction())
|
|
||||||
services.AddDbContext<DataContext>();
|
|
||||||
else
|
|
||||||
services.AddDbContext<DataContext, SqliteDataContext>();
|
|
||||||
|
|
||||||
services.AddCors();
|
|
||||||
services.AddControllers();
|
|
||||||
services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
|
|
||||||
|
|
||||||
// configure strongly typed settings objects
|
|
||||||
var appSettingsSection = _configuration.GetSection("AppSettings");
|
|
||||||
services.Configure<AppSettings>(appSettingsSection);
|
|
||||||
|
|
||||||
// configure jwt authentication
|
|
||||||
var appSettings = appSettingsSection.Get<AppSettings>();
|
|
||||||
var key = Encoding.ASCII.GetBytes(appSettings.Secret);
|
|
||||||
services.AddAuthentication(x =>
|
|
||||||
{
|
|
||||||
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
|
|
||||||
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
|
|
||||||
})
|
|
||||||
.AddJwtBearer(x =>
|
|
||||||
{
|
|
||||||
x.Events = new JwtBearerEvents
|
|
||||||
{
|
|
||||||
OnTokenValidated = context =>
|
|
||||||
{
|
|
||||||
var userService = context.HttpContext.RequestServices.GetRequiredService<IUserService>();
|
|
||||||
var userId = int.Parse(context.Principal.Identity.Name);
|
|
||||||
var user = userService.GetById(userId);
|
|
||||||
if (user == null)
|
|
||||||
{
|
|
||||||
// return unauthorized if user no longer exists
|
|
||||||
context.Fail("Unauthorized");
|
|
||||||
}
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
x.RequireHttpsMetadata = false;
|
|
||||||
x.SaveToken = true;
|
|
||||||
x.TokenValidationParameters = new TokenValidationParameters
|
|
||||||
{
|
|
||||||
ValidateIssuerSigningKey = true,
|
|
||||||
IssuerSigningKey = new SymmetricSecurityKey(key),
|
|
||||||
ValidateIssuer = false,
|
|
||||||
ValidateAudience = false
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// configure DI for application services
|
|
||||||
services.AddScoped<IUserService, UserService>();
|
|
||||||
}
|
|
||||||
|
|
||||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
|
||||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, DataContext dataContext)
|
|
||||||
{
|
|
||||||
// migrate any database changes on startup (includes initial db creation)
|
|
||||||
dataContext.Database.Migrate();
|
|
||||||
|
|
||||||
app.UseRouting();
|
|
||||||
|
|
||||||
// global cors policy
|
|
||||||
app.UseCors(x => x
|
|
||||||
.AllowAnyOrigin()
|
|
||||||
.AllowAnyMethod()
|
|
||||||
.AllowAnyHeader());
|
|
||||||
|
|
||||||
app.UseAuthentication();
|
|
||||||
app.UseAuthorization();
|
|
||||||
|
|
||||||
app.UseEndpoints(endpoints => endpoints.MapControllers());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
|
||||||
</PropertyGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="AutoMapper" Version="9.0.0" />
|
|
||||||
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="7.0.0" />
|
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="3.1.0" />
|
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.0" />
|
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.0">
|
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
|
||||||
<PrivateAssets>all</PrivateAssets>
|
|
||||||
</PackageReference>
|
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.0" />
|
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.0" />
|
|
||||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="5.6.0" />
|
|
||||||
</ItemGroup>
|
|
||||||
</Project>
|
|
|
@ -1,12 +0,0 @@
|
||||||
{
|
|
||||||
"ConnectionStrings": {
|
|
||||||
"WebApiDatabase": "Data Source=LocalDatabase.db"
|
|
||||||
},
|
|
||||||
"Logging": {
|
|
||||||
"LogLevel": {
|
|
||||||
"Default": "Debug",
|
|
||||||
"System": "Information",
|
|
||||||
"Microsoft": "Information"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
{
|
|
||||||
"AppSettings": {
|
|
||||||
"Secret": "THIS IS USED TO SIGN AND VERIFY JWT TOKENS, REPLACE IT WITH YOUR OWN SECRET, IT CAN BE ANY STRING"
|
|
||||||
},
|
|
||||||
"ConnectionStrings": {
|
|
||||||
"WebApiDatabase": "ENTER PRODUCTION SQL SERVER CONNECTION STRING HERE"
|
|
||||||
},
|
|
||||||
"Logging": {
|
|
||||||
"LogLevel": {
|
|
||||||
"Default": "Information",
|
|
||||||
"Microsoft": "Warning",
|
|
||||||
"Microsoft.Hosting.Lifetime": "Information"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"AllowedHosts": "*"
|
|
||||||
}
|
|
Loading…
Add table
Add a link
Reference in a new issue