Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prod - 08/20/2024 #19

Merged
merged 2 commits into from
Aug 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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