Skip to content

Commit

Permalink
Merge pull request #19 from Alper-Soy/develop
Browse files Browse the repository at this point in the history
Prod - 08/20/2024
  • Loading branch information
Alper-Soy authored Aug 20, 2024
2 parents 17a3a0d + 5949cd0 commit c168627
Show file tree
Hide file tree
Showing 40 changed files with 1,418 additions and 143 deletions.
8 changes: 7 additions & 1 deletion API/API.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,16 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.7">
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.8"/>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.8">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.0.1"/>
</ItemGroup>

<ItemGroup>
<Folder Include="Contracts\User\"/>
</ItemGroup>

</Project>
9 changes: 9 additions & 0 deletions API/Contracts/Auth/AuthUserDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace API.Contracts.User;

public class AuthUserDto
{
public string DisplayName { get; set; }
public string Token { get; set; }
public string Image { get; set; }
public string Username { get; set; }
}
7 changes: 7 additions & 0 deletions API/Contracts/Auth/LoginDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace API.Contracts.Auth;

public class LoginDto
{
public string Email { get; set; }
public string Password { get; set; }
}
16 changes: 16 additions & 0 deletions API/Contracts/Auth/RegisterDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System.ComponentModel.DataAnnotations;

namespace API.Contracts.Auth;

public class RegisterDto
{
[Required] [EmailAddress] public string Email { get; set; }

[Required]
[RegularExpression("(?=.*\\d)(?=.*[a-z])(?=.*[A-Z]).{4,8}$", ErrorMessage = "Password must be complex")]
public string Password { get; set; }

[Required] public string DisplayName { get; set; }

[Required] public string Username { get; set; }
}
83 changes: 83 additions & 0 deletions API/Controllers/AccountController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
using System.Security.Claims;
using API.Contracts.Auth;
using API.Contracts.User;
using API.Services.Auth;
using Domain.Entities;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;

namespace API.Controllers;

[ApiController]
[Route("api/[controller]")]
public class AccountController(UserManager<User> userManager, TokenService tokenService) : ControllerBase
{
[AllowAnonymous]
[HttpPost("login")]
public async Task<ActionResult<AuthUserDto>> Login(LoginDto loginDto)
{
var user = await userManager.FindByEmailAsync(loginDto.Email);

if (user == null) return Unauthorized();

var result = await userManager.CheckPasswordAsync(user, loginDto.Password);

if (result) return CreateUserObject(user);

return Unauthorized();
}

[AllowAnonymous]
[HttpPost("register")]
public async Task<ActionResult<AuthUserDto>> Register(RegisterDto registerDto)
{
if (await userManager.Users.AnyAsync(x => x.UserName == registerDto.Username))
{
ModelState.AddModelError("username", "Username taken");
return ValidationProblem();
}


if (await userManager.Users.AnyAsync(x => x.Email == registerDto.Email))
{
ModelState.AddModelError("email", "Email taken");
return ValidationProblem();
}


var user = new User
{
DisplayName = registerDto.DisplayName,
Email = registerDto.Email,
UserName = registerDto.Username
};

var result = await userManager.CreateAsync(user, registerDto.Password);

if (result.Succeeded) return CreateUserObject(user);

return BadRequest(result.Errors);
}

[Authorize]
[HttpGet]
public async Task<ActionResult<AuthUserDto>> GetCurrentUser()
{
var user = await userManager.FindByEmailAsync(User.FindFirstValue(ClaimTypes.Email));

return CreateUserObject(user);
}

private AuthUserDto CreateUserObject(User user)
{
return new AuthUserDto
{
DisplayName = user.DisplayName,
Image = null,
Token = tokenService.CreateToken(user),
Username = user.UserName
};
}
}
44 changes: 21 additions & 23 deletions API/Controllers/BuggyController.cs
Original file line number Diff line number Diff line change
@@ -1,32 +1,30 @@
using System;
using Microsoft.AspNetCore.Mvc;

namespace API.Controllers
namespace API.Controllers;

public class BuggyController : BaseApiController
{
public class BuggyController : BaseApiController
[HttpGet("not-found")]
public ActionResult GetNotFound()
{
[HttpGet("not-found")]
public ActionResult GetNotFound()
{
return NotFound();
}
return NotFound();
}

[HttpGet("bad-request")]
public ActionResult GetBadRequest()
{
return BadRequest("This is a bad request");
}
[HttpGet("bad-request")]
public ActionResult GetBadRequest()
{
return BadRequest("This is a bad request");
}

[HttpGet("server-error")]
public ActionResult GetServerError()
{
throw new Exception("This is a server error");
}
[HttpGet("server-error")]
public ActionResult GetServerError()
{
throw new Exception("This is a server error");
}

[HttpGet("unauthorised")]
public ActionResult GetUnauthorised()
{
return Unauthorized();
}
[HttpGet("unauthorised")]
public ActionResult GetUnauthorised()
{
return Unauthorized();
}
}
38 changes: 38 additions & 0 deletions API/Extensions/IdentityServiceExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System.Text;
using API.Services.Auth;
using Domain.Entities;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using Persistence;

namespace API.Extensions;

public static class IdentityServiceExtensions
{
public static IServiceCollection AddIdentityServices(this IServiceCollection services, IConfiguration config)
{
services.AddIdentityCore<User>(opt =>
{
opt.Password.RequireNonAlphanumeric = false;
opt.User.RequireUniqueEmail = true;
})
.AddEntityFrameworkStores<DataContext>();


var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config["TokenKey"]));
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = key,
ValidateIssuer = false,
ValidateAudience = false
};
});

services.AddScoped<TokenService>();

return services;
}
}
2 changes: 1 addition & 1 deletion API/Middleware/ExceptionMiddleware.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public async Task InvokeAsync(HttpContext context)
var response = env.IsDevelopment()
? new AppException(context.Response.StatusCode, ex.Message, ex.StackTrace)
: new AppException(context.Response.StatusCode, "Internal Server Error");


var json = JsonSerializer.Serialize(response, JsonOptions);

Expand Down
15 changes: 13 additions & 2 deletions API/Program.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
using API.Extensions;
using API.Middleware;
using Domain.Entities;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc.Authorization;
using Microsoft.EntityFrameworkCore;
using Persistence;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllers();
builder.Services.AddControllers(opt =>
{
var policy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();
opt.Filters.Add(new AuthorizeFilter(policy));
});
builder.Services.AddApplicationServices(builder.Configuration);
builder.Services.AddIdentityServices(builder.Configuration);

var app = builder.Build();

Expand All @@ -17,6 +26,7 @@

app.UseCors("CorsPolicy");

app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();
Expand All @@ -27,8 +37,9 @@
try
{
var context = services.GetRequiredService<DataContext>();
var userManager = services.GetRequiredService<UserManager<User>>();
await context.Database.MigrateAsync();
await Seed.SeedData(context);
await Seed.SeedData(context, userManager);
}
catch (Exception e)
{
Expand Down
36 changes: 36 additions & 0 deletions API/Services/Auth/TokenService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using Domain.Entities;
using Microsoft.IdentityModel.Tokens;

namespace API.Services.Auth;

public class TokenService(IConfiguration config)
{
public string CreateToken(User user)
{
var claims = new List<Claim>
{
new(ClaimTypes.Name, user.UserName),
new(ClaimTypes.NameIdentifier, user.Id),
new(ClaimTypes.Email, user.Email)
};

var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config["TokenKey"]));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha512Signature);

var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(claims),
Expires = DateTime.UtcNow.AddDays(7),
SigningCredentials = creds
};

var tokenHandler = new JwtSecurityTokenHandler();

var token = tokenHandler.CreateToken(tokenDescriptor);

return tokenHandler.WriteToken(token);
}
}
Binary file modified API/activity-hub.db
Binary file not shown.
Binary file modified API/activity-hub.db-shm
Binary file not shown.
Binary file modified API/activity-hub.db-wal
Binary file not shown.
3 changes: 2 additions & 1 deletion API/appsettings.Development.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@
},
"ConnectionStrings": {
"DefaultConnection": "Data Source=activity-hub.db"
}
},
"TokenKey": "887aa8d55236124da3ae8da02e0809d2140470eb9aa6583686fafed91b9586eb"
}
4 changes: 2 additions & 2 deletions Application/Core/Result.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ public class Result<T>

public static Result<T> Success(T value)
{
return new Result<T>() { IsSuccess = true, Value = value };
return new Result<T> { IsSuccess = true, Value = value };
}

public static Result<T> Failure(string error)
{
return new Result<T>() { IsSuccess = false, Error = error };
return new Result<T> { IsSuccess = false, Error = error };
}
}
4 changes: 4 additions & 0 deletions Domain/Domain.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,8 @@
<Nullable>disable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.8"/>
</ItemGroup>

</Project>
9 changes: 9 additions & 0 deletions Domain/Entities/User.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Microsoft.AspNetCore.Identity;

namespace Domain.Entities;

public class User : IdentityUser
{
public string DisplayName { get; set; }
public string Bio { get; set; }
}
3 changes: 2 additions & 1 deletion Persistence/DataContext.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
using Domain.Entities;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;

namespace Persistence;

public class DataContext(DbContextOptions options) : DbContext(options)
public class DataContext(DbContextOptions options) : IdentityDbContext<User>(options)
{
public DbSet<Activity> Activities { get; set; }
}
Loading

0 comments on commit c168627

Please sign in to comment.