Skip to content
Open
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
15 changes: 15 additions & 0 deletions exercise.wwwapi/Configuration/ConfigurationSettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace exercise.wwwapi.Configuration
{
public class ConfigurationSettings : IConfigurationSettings
{
IConfiguration _configuration;
public ConfigurationSettings()
{
_configuration = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build();
}
public string GetValue(string key)
{
return _configuration.GetValue<string>(key)!;
}
}
}
7 changes: 7 additions & 0 deletions exercise.wwwapi/Configuration/IConfigurationSettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace exercise.wwwapi.Configuration
{
public interface IConfigurationSettings
{
string GetValue(string key);
}
}
15 changes: 15 additions & 0 deletions exercise.wwwapi/DTO/AddBlogDTO.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace exercise.wwwapi.DTO
{
public class AddBlogDTO
{
public string Title { get; set; }

public string Content { get; set; }

public string Description { get; set; }

public string Author { get; set; }

public int UserId { get; set; }
}
}
19 changes: 19 additions & 0 deletions exercise.wwwapi/DTO/BlogDTO.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using exercise.wwwapi.Models;

namespace exercise.wwwapi.DTO
{
public class BlogDTO
{
public int Id { get; set; }

public string Title { get; set; }

public string Content { get; set; }

public string Description { get; set; }

public string Author { get; set; }

public UserDTO User { get; set; }
}
}
11 changes: 11 additions & 0 deletions exercise.wwwapi/DTO/UpdateDTO.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace exercise.wwwapi.DTO
{
public class UpdateDTO
{
public string Title { get; set; }
public string Content { get; set; }
public string Description { get; set; }
public string Author { get; set; }
}

}
9 changes: 9 additions & 0 deletions exercise.wwwapi/DTO/UserDTO.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace exercise.wwwapi.DTO
{
public class UserDTO
{
public int Id { get; set; }
public string Username { get; set; }
public string Email { get; set; }
}
}
12 changes: 12 additions & 0 deletions exercise.wwwapi/DTO/UserRequestDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System.ComponentModel.DataAnnotations.Schema;

namespace exercise.wwwapi.DTO
{
[NotMapped]
public class UserRequestDto
{
public required string Username { get; set; }
public required string Password { get; set; }
public required string Email { get; set; }
}
}
12 changes: 12 additions & 0 deletions exercise.wwwapi/DTO/UserResponseDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System.ComponentModel.DataAnnotations.Schema;

namespace exercise.wwwapi.DTO
{
[NotMapped]
public class UserResponseDto
{
public string Username { get; set; }
public string PasswordHash { get; set; }
public string Email { get; set; }
}
}
25 changes: 25 additions & 0 deletions exercise.wwwapi/Data/DataContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using exercise.wwwapi.Configuration;
using exercise.wwwapi.Models;
using Microsoft.EntityFrameworkCore;
using System.Numerics;

namespace exercise.wwwapi.Data
{
public class DataContext : DbContext
{
private string connectionString;
public DataContext(DbContextOptions<DataContext> options) : base(options) {

var configuration = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build();
connectionString = configuration.GetValue<string>("ConnectionStrings:DefaultConnectionString");

}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseNpgsql(connectionString);
}

public DbSet<User> Users { get; set; }
public DbSet<Blog> Blogs { get; set; }
}
}
94 changes: 94 additions & 0 deletions exercise.wwwapi/EndPoints/AuthApi.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
using exercise.wwwapi.Configuration;
using exercise.wwwapi.DTO;
using exercise.wwwapi.Helpers;
using exercise.wwwapi.Models;
using exercise.wwwapi.Repository;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ActionConstraints;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;

namespace exercise.wwwapi.EndPoints
{
public static class AuthApi
{
public static void ConfigureAuthApi(this WebApplication app)
{
app.MapPost("register", Register);
app.MapPost("login", Login);
app.MapGet("users", GetUsers);

}
[Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
private static async Task<IResult> GetUsers(IRepository<User> service, ClaimsPrincipal user)
{
return TypedResults.Ok(service.GetAll());
}
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status409Conflict)]
private static async Task<IResult> Register(UserRequestDto request, IRepository<User> service)
{

//user exists
if (service.GetAll().Where(u => u.Username == request.Username).Any()) return Results.Conflict(new Payload<UserRequestDto>() { status = "Username already exists!", data = request });

string passwordHash = BCrypt.Net.BCrypt.HashPassword(request.Password);

var user = new User();

user.Username = request.Username;
user.PasswordHash = passwordHash;
user.Email = request.Email;

service.Insert(user);
service.Save();

return Results.Ok(new Payload<string>() { data = "Created Account" });
}

[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
private static async Task<IResult> Login(UserRequestDto request, IRepository<User> service, IConfigurationSettings config)
{
//user doesn't exist
if (!service.GetAll().Where(u => u.Username == request.Username).Any()) return Results.BadRequest(new Payload<UserRequestDto>() { status = "User does not exist", data = request });

User user = service.GetAll().FirstOrDefault(u => u.Username == request.Username)!;


if (!BCrypt.Net.BCrypt.Verify(request.Password, user.PasswordHash))
{
return Results.BadRequest(new Payload<UserRequestDto>() { status = "Wrong Password", data = request });
}
string token = CreateToken(user, config);
return Results.Ok(new Payload<string>() { data = token }) ;

}
private static string CreateToken(User user, IConfigurationSettings config)
{
List<Claim> claims = new List<Claim>
{
new Claim(ClaimTypes.Sid, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.Username),
new Claim(ClaimTypes.Email, user.Email),

};

var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config.GetValue("AppSettings:Token")));
var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha512Signature);
var token = new JwtSecurityToken(
claims: claims,
expires: DateTime.Now.AddDays(1),
signingCredentials: credentials
);
var jwt = new JwtSecurityTokenHandler().WriteToken(token);
return jwt;
}
}
}

109 changes: 109 additions & 0 deletions exercise.wwwapi/EndPoints/BlogEndpoint.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
using System.Security.Claims;
using AutoMapper;
using exercise.wwwapi.DTO;
using exercise.wwwapi.Helpers;
using exercise.wwwapi.Models;
using exercise.wwwapi.Repository;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;

namespace exercise.wwwapi.EndPoints
{
public static class BlogEndpoint
{
public static void ConfigureBlogApi(this WebApplication app)
{
app.MapGet("getBlogs", GetBlogs);
app.MapGet("getBlogs/{id}", GetBlog);
app.MapPost("AddBlogs", AddBlog);
app.MapPut("updateBlog/{id}", UpdateBlog);
}

[Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public static async Task<IResult> GetBlogs(IRepository<Blog> repository, IMapper mapper)
{
var blogs = await repository.GetWithIncludes(p => p.User);

if (blogs == null || !blogs.Any())
{
return TypedResults.NotFound(new Payload<string> { status = "error", data = "No blogs found." });
}

var response = mapper.Map<List<BlogDTO>>(blogs);
return TypedResults.Ok(new Payload<List<BlogDTO>> { data = response });
}

[Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public static async Task<IResult> GetBlog(IRepository<Blog> repository, IMapper mapper, int id)
{
var blog = await repository.GetByIdWithIncludes(id, p => p.User);
if (blog == null)
{
return TypedResults.NotFound(new Payload<string> { status = "error", data = $"Blog with ID {id} not found." });
}

var response = mapper.Map<BlogDTO>(blog);
return TypedResults.Ok(new Payload<BlogDTO> { data = response });
}

[Authorize]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public static async Task<IResult> AddBlog(IRepository<Blog> repository, IMapper mapper, AddBlogDTO blogDto, ClaimsPrincipal user)
{
int? userId = user.UserRealId();
if (userId == null)
{
return TypedResults.BadRequest(new Payload<string> { status = "error", data = "User ID could not be determined." });
}

Blog create = new Blog()
{
Title = blogDto.Title,
Content = blogDto.Content,
Description = blogDto.Description,
Author = blogDto.Author,
UserId = userId.Value
};

repository.Insert(create);
repository.Save();

var response = mapper.Map<AddBlogDTO>(create);
return TypedResults.Created($"/blogs/{create.Id}", new Payload<AddBlogDTO> { data = response });
}

[Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public static async Task<IResult> UpdateBlog(IRepository<Blog> repository, IMapper mapper, int id, UpdateDTO blogDto, ClaimsPrincipal user)
{
var blog = repository.GetById(id);
if (blog == null)
{
return TypedResults.NotFound(new Payload<string> { status = "error", data = "Blog not found." });
}
if (blog.UserId != user.UserRealId())
{
return TypedResults.BadRequest(new Payload<string> { status = "error", data = "Unauthorized to update this blog." });
}

blog.Title = blogDto.Title;
blog.Content = blogDto.Content;
blog.Description = blogDto.Description;
blog.Author = blogDto.Author;

repository.Update(blog);
repository.Save();

var response = mapper.Map<BlogDTO>(blog);
return TypedResults.Ok(new Payload<BlogDTO> { data = response });
}
}
}
32 changes: 32 additions & 0 deletions exercise.wwwapi/EndPoints/SecureApi.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using exercise.wwwapi.Helpers;
using exercise.wwwapi.Models;
using exercise.wwwapi.Repository;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Win32;
using System.Security.Claims;

namespace exercise.wwwapi.EndPoints
{
public static class SecureApi
{
public static void ConfigureSecureApi(this WebApplication app)
{
app.MapGet("message", GetMessage);
//app.MapGet("message2", GetMessage2);


}

[Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
private static async Task<IResult> GetMessage(IRepository<User> service, ClaimsPrincipal user, ILogger logger)
{
logger.LogDebug(new string('*', 1000));
return TypedResults.Ok(new { LoggedIn = true, UserId=user.UserRealId().ToString(), Email = $"{user.Email()}", Message = "Pulled the userid and email out of the claims" });
}


}
}
Loading