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);
}
}
48 changes: 48 additions & 0 deletions exercise.wwwapi/Data/DataContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using System.Numerics;
using exercise.wwwapi.Configuration;
using exercise.wwwapi.Models;
using Microsoft.EntityFrameworkCore;

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"
)!;
this.Database.EnsureCreated();
this.Database.SetConnectionString(connectionString);
}

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

protected override void OnModelCreating(ModelBuilder m)
{
m.Entity<User>().HasKey(c => c.Id);
m.Entity<User>().HasMany(c => c.Posts).WithOne().HasForeignKey(t => t.AuthorId);
m.Entity<User>()
.HasMany(u => u.Following)
.WithOne(f => f.Follower)
.HasForeignKey(f => f.FollowerId);

m.Entity<User>()
.HasMany(u => u.FollowedBy)
.WithOne(f => f.Followee)
.HasForeignKey(f => f.FolloweeId);

m.Entity<BlogPost>().HasKey(c => c.Id);
}

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

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).RequireAuthorization();
}

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

[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status409Conflict)]
private static async Task<IResult> Register(UserRequestDto request, IRepository<User> repo)
{
//user exists
if (await UserExists(repo, request.Username))
return Results.Conflict(
new Payload<UserRequestDto>()
{
status = "Username already exists!",
data = request,
}
);

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

var user = new User()
{
Username = request.Username,
PasswordHash = passwordHash,
Email = request.Email,
};

await repo.Insert(user);

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> repo,
IConfigurationSettings config
)
{
User? user = await GetByUsername(repo, request.Username);
if (user == null)
return Results.BadRequest(
new Payload<UserRequestDto>() { status = "User does not exist", data = request }
);

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;
}

private static async Task<bool> UserExists(IRepository<User> repo, string username)
{
return (await repo.GetBy(user => user.Username == username)) != null;
}

private static async Task<User?> GetByUsername(IRepository<User> repo, string username)
{
return await repo.GetBy(user => user.Username == username);
}
}
180 changes: 180 additions & 0 deletions exercise.wwwapi/EndPoints/SecureApi.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
using System.Security.Claims;
using AutoMapper;
using exercise.wwwapi.Helpers;
using exercise.wwwapi.Models;
using exercise.wwwapi.Repository;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;

namespace exercise.wwwapi.EndPoints;

public static class SecureApi
{
public static void ConfigureSecureApi(this WebApplication app)
{
app.MapPost("/posts", CreatePost);
app.MapPut("/posts", UpdatePost);
app.MapGet("/posts", GetPosts);
app.MapPost("/users/follow/{userId}", FollowUser);
app.MapPost("/users/unfollow/{userId}", UnFollowUser);
app.MapGet("/posts/following", GetFollowingPosts);
}

[Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
private static async Task<Results<Ok<IEnumerable<BlogPost>>, UnauthorizedHttpResult>> GetPosts(
IRepository<User> userRepo,
IRepository<BlogPost> blogRepo,
ClaimsPrincipal user
)
{
return TypedResults.Ok(await blogRepo.GetAll());
}

[Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
private static async Task<
Results<Ok<BlogPost>, UnauthorizedHttpResult, BadRequest<string>>
> CreatePost(
IRepository<User> userRepo,
IRepository<BlogPost> blogRepo,
ClaimsPrincipal user,
BlogPostPost blogpost
)
{
var id = user.UserRealId();
if (id == null)
{
return TypedResults.BadRequest("Error with user id");
}

var post = BlogPost.Create(id ?? 0, blogpost.Text);
var result = await blogRepo.Insert(post);
if (result == null)
{
return TypedResults.BadRequest("Something went wrong :)");
}
return TypedResults.Ok(result);
}

[Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
private static async Task<
Results<Ok<BlogPost>, UnauthorizedHttpResult, BadRequest<string>>
> UpdatePost(
IRepository<User> userRepo,
IRepository<BlogPost> blogRepo,
IMapper mapper,
ClaimsPrincipal user,
int blogId,
BlogPostPost updated
)
{
var source = await blogRepo.GetById(blogId);
if (source == null)
return TypedResults.BadRequest("No blog with given id");
if (source.AuthorId != user.UserRealId())
{
return TypedResults.Unauthorized();
}
var result = await blogRepo.Update(mapper, blogId, updated);
if (result == null)
{
return TypedResults.BadRequest("Try again with a better payload please");
}
return TypedResults.Ok(result);
}

[Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
private static async Task<
Results<Ok<UserFollow>, UnauthorizedHttpResult, BadRequest<string>>
> FollowUser(
IRepository<UserFollow> followRepo,
IRepository<User> userRepo,
ClaimsPrincipal user,
int followUserId
)
{
var id = user.UserRealId();
if (id == null)
{
return TypedResults.BadRequest("You don't exist?");
}
if (!await userRepo.Exists(followUserId))
{
return TypedResults.BadRequest("User to follow does not exist");
}
var followExists = await followRepo.GetBy(f =>
f.FolloweeId == followUserId && f.FollowerId == id
);
if (followExists != null)
{
return TypedResults.BadRequest("Already following");
}
var follow = new UserFollow
{
FollowerId = user.UserRealId() ?? 0,
FolloweeId = followUserId,
};
var result = await followRepo.Insert(follow);
return TypedResults.Ok(result);
}

[Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
private static async Task<
Results<Ok<UserFollow>, UnauthorizedHttpResult, BadRequest<string>>
> UnFollowUser(
IRepository<UserFollow> followRepo,
IRepository<User> userRepo,
ClaimsPrincipal user,
int followUserId
)
{
var id = user.UserRealId();
if (id == null)
{
return TypedResults.BadRequest("You don't exist?");
}
var follow = await followRepo.GetBy(f =>
f.FolloweeId == followUserId && f.FollowerId == id
);
if (follow == null)
{
return TypedResults.BadRequest("You don't follow this user");
}
var result = await followRepo.Delete(follow.id);
return TypedResults.Ok(result);
}

[Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
private static async Task<
Results<Ok<IEnumerable<BlogPost>>, UnauthorizedHttpResult, BadRequest<string>>
> GetFollowingPosts(
IRepository<UserFollow> followRepo,
IRepository<User> userRepo,
ClaimsPrincipal user
)
{
var id = user.UserRealId();
if (id == null)
{
return TypedResults.BadRequest("You don't exist?");
}
var following = (await followRepo.GetAll()).Where(f => f.FollowerId == id);
var a = following.Select(f => f.Followee).SelectMany(p => p!.Posts!);
return TypedResults.Ok(a);
}
}
Loading