From 8fa5de5f8dec0272b67643bc40d4a254c1889954 Mon Sep 17 00:00:00 2001 From: Jostein Lind Aanby Date: Thu, 6 Feb 2025 10:33:31 +0100 Subject: [PATCH 1/2] hm --- .../Configuration/ConfigurationSettings.cs | 16 +++++ .../Configuration/IConfigurationSettings.cs | 8 +++ exercise.wwwapi/Data/BlogData.cs | 8 +++ exercise.wwwapi/Data/DataContext.cs | 21 ++++++ exercise.wwwapi/Endpoints/AuthApi.cs | 8 +++ exercise.wwwapi/Endpoints/BlogApi.cs | 34 ++++++++++ .../Helpers/ClaimsPrincipalHelper.cs | 30 +++++++++ exercise.wwwapi/Model/Blogpost.cs | 19 ++++++ exercise.wwwapi/Model/User.cs | 17 +++++ exercise.wwwapi/Repository/IRepository.cs | 19 ++++++ exercise.wwwapi/Repository/Repository.cs | 65 +++++++++++++++++++ exercise.wwwapi/appsettings.example.json | 15 ----- exercise.wwwapi/exercise.wwwapi.csproj | 2 + 13 files changed, 247 insertions(+), 15 deletions(-) create mode 100644 exercise.wwwapi/Configuration/ConfigurationSettings.cs create mode 100644 exercise.wwwapi/Configuration/IConfigurationSettings.cs create mode 100644 exercise.wwwapi/Data/BlogData.cs create mode 100644 exercise.wwwapi/Data/DataContext.cs create mode 100644 exercise.wwwapi/Endpoints/AuthApi.cs create mode 100644 exercise.wwwapi/Endpoints/BlogApi.cs create mode 100644 exercise.wwwapi/Helpers/ClaimsPrincipalHelper.cs create mode 100644 exercise.wwwapi/Model/Blogpost.cs create mode 100644 exercise.wwwapi/Model/User.cs create mode 100644 exercise.wwwapi/Repository/IRepository.cs create mode 100644 exercise.wwwapi/Repository/Repository.cs delete mode 100644 exercise.wwwapi/appsettings.example.json diff --git a/exercise.wwwapi/Configuration/ConfigurationSettings.cs b/exercise.wwwapi/Configuration/ConfigurationSettings.cs new file mode 100644 index 0000000..7120fc7 --- /dev/null +++ b/exercise.wwwapi/Configuration/ConfigurationSettings.cs @@ -0,0 +1,16 @@ +using System; + +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(key)!; + } +} diff --git a/exercise.wwwapi/Configuration/IConfigurationSettings.cs b/exercise.wwwapi/Configuration/IConfigurationSettings.cs new file mode 100644 index 0000000..410098b --- /dev/null +++ b/exercise.wwwapi/Configuration/IConfigurationSettings.cs @@ -0,0 +1,8 @@ +using System; + +namespace exercise.wwwapi.Configuration; + +public interface IConfigurationSettings +{ + string GetValue(string key); +} diff --git a/exercise.wwwapi/Data/BlogData.cs b/exercise.wwwapi/Data/BlogData.cs new file mode 100644 index 0000000..380f5c2 --- /dev/null +++ b/exercise.wwwapi/Data/BlogData.cs @@ -0,0 +1,8 @@ +using System; + +namespace exercise.wwwapi.Data; + +public class BlogData +{ + +} diff --git a/exercise.wwwapi/Data/DataContext.cs b/exercise.wwwapi/Data/DataContext.cs new file mode 100644 index 0000000..1b366f5 --- /dev/null +++ b/exercise.wwwapi/Data/DataContext.cs @@ -0,0 +1,21 @@ +using System; +using exercise.wwwapi.Model; +using Microsoft.EntityFrameworkCore; + +namespace exercise.wwwapi.Data; + + public class DataContext : DbContext + { + public DataContext(DbContextOptions options) : base(options) + { + + } + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + //optionsBuilder.UseInMemoryDatabase(databaseName: "Database"); + } + + public DbSet Users { get; set; } + } + + diff --git a/exercise.wwwapi/Endpoints/AuthApi.cs b/exercise.wwwapi/Endpoints/AuthApi.cs new file mode 100644 index 0000000..5385acb --- /dev/null +++ b/exercise.wwwapi/Endpoints/AuthApi.cs @@ -0,0 +1,8 @@ +using System; + +namespace exercise.wwwapi.Endpoints; + +public class AuthApi +{ + +} diff --git a/exercise.wwwapi/Endpoints/BlogApi.cs b/exercise.wwwapi/Endpoints/BlogApi.cs new file mode 100644 index 0000000..e62197b --- /dev/null +++ b/exercise.wwwapi/Endpoints/BlogApi.cs @@ -0,0 +1,34 @@ +using System; +using Microsoft.AspNetCore.Authorization; + +namespace exercise.wwwapi.Endpoints; + +public static class BlogApi +{ + public static void ConfigureBlogApi(this WebApplication app) + { + var blog = app.MapGroup("/posts"); + + blog.MapGet("/", GetAllBlogPosts); + blog.MapPost("/", CreateNewPost); + blog.MapPut("/", UpdatePost); + } + + [Authorize] + private static async Task UpdatePost(HttpContext context) + { + throw new NotImplementedException(); + } + + [Authorize] + private static async Task CreateNewPost(HttpContext context) + { + throw new NotImplementedException(); + } + + [Authorize] + private static async Task GetAllBlogPosts(HttpContext context) + { + throw new NotImplementedException(); + } +} diff --git a/exercise.wwwapi/Helpers/ClaimsPrincipalHelper.cs b/exercise.wwwapi/Helpers/ClaimsPrincipalHelper.cs new file mode 100644 index 0000000..e8ac3d8 --- /dev/null +++ b/exercise.wwwapi/Helpers/ClaimsPrincipalHelper.cs @@ -0,0 +1,30 @@ +using System; +using System.Security.Claims; + +namespace exercise.wwwapi.Helpers; + +public static class ClaimsPrincipalHelper +{ + public static int? UserRealId(this ClaimsPrincipal user) + { + Claim? claim = user.FindFirst(ClaimTypes.Sid); + return int.Parse(claim?.Value); + } + public static string UserId(this ClaimsPrincipal user) + { + IEnumerable claims = user.Claims.Where(c => c.Type == ClaimTypes.NameIdentifier); + return claims.Count() >= 2 ? claims.ElementAt(1).Value : null; + + } + + public static string? Email(this ClaimsPrincipal user) + { + Claim? claim = user.FindFirst(ClaimTypes.Email); + return claim?.Value; + } + public static string? Role(this ClaimsPrincipal user) + { + Claim? claim = user.FindFirst(ClaimTypes.Role); + return claim?.Value; + } +} diff --git a/exercise.wwwapi/Model/Blogpost.cs b/exercise.wwwapi/Model/Blogpost.cs new file mode 100644 index 0000000..063778a --- /dev/null +++ b/exercise.wwwapi/Model/Blogpost.cs @@ -0,0 +1,19 @@ +using System; +using System.ComponentModel.DataAnnotations.Schema; + +namespace exercise.wwwapi.Model; + + +/* +Recommended BlogPost model: id, text, authorId (note: this should be a string, to accommodate the UUID) +*/ +[Table("blogposts")] +public class Blogpost +{ + [Column("id")] + public int Id { get; set; } + [Column("text")] + public string Text { get; set; } + [Column("authorId")] + public string AuthorId { get; set; } +} diff --git a/exercise.wwwapi/Model/User.cs b/exercise.wwwapi/Model/User.cs new file mode 100644 index 0000000..af1d579 --- /dev/null +++ b/exercise.wwwapi/Model/User.cs @@ -0,0 +1,17 @@ +using System; +using System.ComponentModel.DataAnnotations.Schema; + +namespace exercise.wwwapi.Model; + +[Table("users")] +public class User +{ + [Column("id")] + public int Id { get; set; } + [Column("username")] + public string Username { get; set; } + [Column("passwordhash")] + public string PasswordHash { get; set; } + [Column("email")] + public string Email { get; set; } +} diff --git a/exercise.wwwapi/Repository/IRepository.cs b/exercise.wwwapi/Repository/IRepository.cs new file mode 100644 index 0000000..241608f --- /dev/null +++ b/exercise.wwwapi/Repository/IRepository.cs @@ -0,0 +1,19 @@ +using System; +using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore; + +namespace exercise.wwwapi.Repository; + + public interface IRepository where T : class + { + IEnumerable GetAll(); + IEnumerable GetAll(params Expression>[] includeExpressions); + T GetById(object id); + void Insert(T obj); + void Update(T obj); + void Delete(object id); + void Save(); + DbSet Table { get; } + + } + diff --git a/exercise.wwwapi/Repository/Repository.cs b/exercise.wwwapi/Repository/Repository.cs new file mode 100644 index 0000000..f85909b --- /dev/null +++ b/exercise.wwwapi/Repository/Repository.cs @@ -0,0 +1,65 @@ +using System; +using System.Linq.Expressions; +using exercise.wwwapi.Data; +using Microsoft.EntityFrameworkCore; + +namespace exercise.wwwapi.Repository; + + public class Repository : IRepository where T : class + { + + + private DataContext _db; + private DbSet _table = null; + + public Repository(DataContext db) + { + _db = db; + _table = _db.Set(); + } + + public IEnumerable GetAll(params Expression>[] includeExpressions) + { + if (includeExpressions.Any()) + { + var set = includeExpressions + .Aggregate>, IQueryable> + (_table, (current, expression) => current.Include(expression)); + } + return _table.ToList(); + } + + public IEnumerable GetAll() + { + return _table.ToList(); + } + public T GetById(object id) + { + return _table.Find(id); + } + + public void Insert(T obj) + { + _table.Add(obj); + } + public void Update(T obj) + { + _table.Attach(obj); + _db.Entry(obj).State = EntityState.Modified; + } + + public void Delete(object id) + { + T existing = _table.Find(id); + _table.Remove(existing); + } + + + public void Save() + { + _db.SaveChanges(); + } + + public DbSet Table { get { return _table; } } + + } diff --git a/exercise.wwwapi/appsettings.example.json b/exercise.wwwapi/appsettings.example.json deleted file mode 100644 index 7a9d42d..0000000 --- a/exercise.wwwapi/appsettings.example.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - }, - "AllowedHosts": "*", - "JwtTokenSettings": { - "ValidIssuer": "YourCompanyServer", - "ValidAudience": "YourProductAudience", - "SymmetricSecurityKey": "SOME_RANDOM_SECRET", - "JwtRegisteredClaimNamesSub": "SOME_RANDOM_CODE" - } -} diff --git a/exercise.wwwapi/exercise.wwwapi.csproj b/exercise.wwwapi/exercise.wwwapi.csproj index 56929a8..6ca4a23 100644 --- a/exercise.wwwapi/exercise.wwwapi.csproj +++ b/exercise.wwwapi/exercise.wwwapi.csproj @@ -8,7 +8,9 @@ + + From 1b956063525fdffa4aa3e1ada476c96c96912018 Mon Sep 17 00:00:00 2001 From: jostein lind Date: Thu, 6 Feb 2025 14:37:35 +0100 Subject: [PATCH 2/2] CORE FINISH --- exercise.wwwapi/Data/BlogData.cs | 2 +- exercise.wwwapi/Data/DataContext.cs | 24 +++-- exercise.wwwapi/Dto/BlogPostDto.cs | 9 ++ exercise.wwwapi/Dto/BlogPostPost.cs | 8 ++ exercise.wwwapi/Dto/BlogpostPut.cs | 8 ++ exercise.wwwapi/Dto/Payload.cs | 8 ++ exercise.wwwapi/Dto/UserRequestDto.cs | 10 ++ exercise.wwwapi/Dto/UserResponseDto.cs | 8 ++ exercise.wwwapi/Endpoints/AuthApi.cs | 90 +++++++++++++++++- exercise.wwwapi/Endpoints/BlogApi.cs | 71 ++++++++++++-- exercise.wwwapi/Model/Blogpost.cs | 4 +- exercise.wwwapi/Program.cs | 116 +++++++++++++++++++++++ exercise.wwwapi/Repository/Repository.cs | 90 +++++++++--------- exercise.wwwapi/exercise.wwwapi.csproj | 8 ++ 14 files changed, 388 insertions(+), 68 deletions(-) create mode 100644 exercise.wwwapi/Dto/BlogPostDto.cs create mode 100644 exercise.wwwapi/Dto/BlogPostPost.cs create mode 100644 exercise.wwwapi/Dto/BlogpostPut.cs create mode 100644 exercise.wwwapi/Dto/Payload.cs create mode 100644 exercise.wwwapi/Dto/UserRequestDto.cs create mode 100644 exercise.wwwapi/Dto/UserResponseDto.cs diff --git a/exercise.wwwapi/Data/BlogData.cs b/exercise.wwwapi/Data/BlogData.cs index 380f5c2..61b3e61 100644 --- a/exercise.wwwapi/Data/BlogData.cs +++ b/exercise.wwwapi/Data/BlogData.cs @@ -1,4 +1,4 @@ -using System; + using System; namespace exercise.wwwapi.Data; diff --git a/exercise.wwwapi/Data/DataContext.cs b/exercise.wwwapi/Data/DataContext.cs index 1b366f5..38e22ce 100644 --- a/exercise.wwwapi/Data/DataContext.cs +++ b/exercise.wwwapi/Data/DataContext.cs @@ -1,21 +1,25 @@ using System; using exercise.wwwapi.Model; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; namespace exercise.wwwapi.Data; - public class DataContext : DbContext +public class DataContext : DbContext +{ + private readonly IConfiguration _configuration; + + public DataContext(DbContextOptions options, IConfiguration configuration) : base(options) { - public DataContext(DbContextOptions options) : base(options) - { + _configuration = configuration; + } - } - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - { - //optionsBuilder.UseInMemoryDatabase(databaseName: "Database"); - } - - public DbSet Users { get; set; } + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { } + public DbSet Users { get; set; } + public DbSet Blogposts { get; set; } // Add this line +} + diff --git a/exercise.wwwapi/Dto/BlogPostDto.cs b/exercise.wwwapi/Dto/BlogPostDto.cs new file mode 100644 index 0000000..1fb15d9 --- /dev/null +++ b/exercise.wwwapi/Dto/BlogPostDto.cs @@ -0,0 +1,9 @@ +namespace wwwapi.Dto +{ + public class BlogPostDto + { + public int PostId { get; set; } + public string Author { get; set; } + public string Text { get; set; } + } +} \ No newline at end of file diff --git a/exercise.wwwapi/Dto/BlogPostPost.cs b/exercise.wwwapi/Dto/BlogPostPost.cs new file mode 100644 index 0000000..c36f490 --- /dev/null +++ b/exercise.wwwapi/Dto/BlogPostPost.cs @@ -0,0 +1,8 @@ +namespace exercise.wwwapi.Dto +{ + public class BlogPostPost + { + public int UserId { get; set; } + public string Text { get; set; } + } +} \ No newline at end of file diff --git a/exercise.wwwapi/Dto/BlogpostPut.cs b/exercise.wwwapi/Dto/BlogpostPut.cs new file mode 100644 index 0000000..7ab651b --- /dev/null +++ b/exercise.wwwapi/Dto/BlogpostPut.cs @@ -0,0 +1,8 @@ +namespace exercise.wwwapi.Dto +{ + public class BlogPostPut + { + public int PostId { get; set; } + public string Text { get; set; } + } +} \ No newline at end of file diff --git a/exercise.wwwapi/Dto/Payload.cs b/exercise.wwwapi/Dto/Payload.cs new file mode 100644 index 0000000..525c851 --- /dev/null +++ b/exercise.wwwapi/Dto/Payload.cs @@ -0,0 +1,8 @@ +namespace exercise.wwwapi.Dto +{ + public class Payload where T : class + { + public string status { get; set; } = "success"; + public T data { get; set; } + } +} \ No newline at end of file diff --git a/exercise.wwwapi/Dto/UserRequestDto.cs b/exercise.wwwapi/Dto/UserRequestDto.cs new file mode 100644 index 0000000..c191d79 --- /dev/null +++ b/exercise.wwwapi/Dto/UserRequestDto.cs @@ -0,0 +1,10 @@ + +namespace exercise.wwwapi.Dto +{ + public class UserRequestDto + { + public required string Username { get; set; } + public required string Password { get; set; } + public required string Email { get; set; } + } +} \ No newline at end of file diff --git a/exercise.wwwapi/Dto/UserResponseDto.cs b/exercise.wwwapi/Dto/UserResponseDto.cs new file mode 100644 index 0000000..d2e1c64 --- /dev/null +++ b/exercise.wwwapi/Dto/UserResponseDto.cs @@ -0,0 +1,8 @@ +namespace exercise.wwwapi.Dto +{ + public class UserResponseDto + { + public string Username { get; set; } + public string Email { get; set; } + } +} \ No newline at end of file diff --git a/exercise.wwwapi/Endpoints/AuthApi.cs b/exercise.wwwapi/Endpoints/AuthApi.cs index 5385acb..3683246 100644 --- a/exercise.wwwapi/Endpoints/AuthApi.cs +++ b/exercise.wwwapi/Endpoints/AuthApi.cs @@ -1,8 +1,94 @@ -using System; +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; +using exercise.wwwapi.Model; +using exercise.wwwapi.Data; +using exercise.wwwapi.Helpers; +using exercise.wwwapi.Dto; +using exercise.wwwapi.Repository; +using exercise.wwwapi.Configuration; + namespace exercise.wwwapi.Endpoints; -public class AuthApi +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 GetUsers(IRepository service, ClaimsPrincipal user) + { + return TypedResults.Ok(service.GetAll()); + } + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status409Conflict)] + private static async Task Register(UserRequestDto request, IRepository service) + { + + //user exists + if (service.GetAll().Where(u => u.Username == request.Username).Any()) return Results.Conflict(new Payload() { 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() { data = "Created Account" }); + } + + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + private static async Task Login(UserRequestDto request, IRepository service, IConfigurationSettings config) + { + //user doesn't exist + if (!service.GetAll().Where(u => u.Username == request.Username).Any()) return Results.BadRequest(new Payload() { 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() { status = "Wrong Password", data = request }); + } + string token = CreateToken(user, config); + return Results.Ok(new Payload() { data = token }); + + } + private static string CreateToken(User user, IConfigurationSettings config) + { + List claims = new List + { + 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; + } } diff --git a/exercise.wwwapi/Endpoints/BlogApi.cs b/exercise.wwwapi/Endpoints/BlogApi.cs index e62197b..37b086b 100644 --- a/exercise.wwwapi/Endpoints/BlogApi.cs +++ b/exercise.wwwapi/Endpoints/BlogApi.cs @@ -1,5 +1,11 @@ -using System; +using System.Security.Claims; +using exercise.wwwapi.Dto; +using exercise.wwwapi.Helpers; +using exercise.wwwapi.Model; +using exercise.wwwapi.Repository; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using wwwapi.Dto; namespace exercise.wwwapi.Endpoints; @@ -15,20 +21,69 @@ public static void ConfigureBlogApi(this WebApplication app) } [Authorize] - private static async Task UpdatePost(HttpContext context) + private static async Task UpdatePost([FromServices] IRepository repo, ClaimsPrincipal user, [FromBody] BlogPostPut postPut) { - throw new NotImplementedException(); + try + { + var userRealId = user.UserRealId(); + var post = repo.GetById(postPut.PostId); + if (post == null) + { + return Results.NotFound("Post not found"); + } + if (userRealId.Value != post.AuthorId) + { + return Results.Unauthorized(); + } + + post.Text = postPut.Text; + repo.Update(post); + repo.Save(); // Save changes to the database + return TypedResults.Ok(post); + } + catch (Exception ex) + { + return Results.InternalServerError(ex.Message); + } } [Authorize] - private static async Task CreateNewPost(HttpContext context) + private static async Task CreateNewPost([FromServices] IRepository repo, [FromBody] BlogPostPost postPost) { - throw new NotImplementedException(); + try + { + var newPost = new Blogpost() + { + Text = postPost.Text, + AuthorId = postPost.UserId + }; + repo.Insert(newPost); + repo.Save(); // Save changes to the database + return TypedResults.Ok(newPost); + } + catch (Exception ex) + { + return Results.InternalServerError(ex.Message); + } } [Authorize] - private static async Task GetAllBlogPosts(HttpContext context) + private static async Task GetAllBlogPosts([FromServices] IRepository repo) { - throw new NotImplementedException(); + try + { + var posts = repo.GetAll(bp => bp.User); // Include the user property + var postsDto = posts.Select(p => new BlogPostDto() + { + PostId = p.Id, + Text = p.Text, + Author = p.User.Username + }); + return TypedResults.Ok(postsDto); + } + catch (Exception ex) + { + return Results.InternalServerError(ex.Message); + } } -} +} \ No newline at end of file diff --git a/exercise.wwwapi/Model/Blogpost.cs b/exercise.wwwapi/Model/Blogpost.cs index 063778a..0e7f5cb 100644 --- a/exercise.wwwapi/Model/Blogpost.cs +++ b/exercise.wwwapi/Model/Blogpost.cs @@ -15,5 +15,7 @@ public class Blogpost [Column("text")] public string Text { get; set; } [Column("authorId")] - public string AuthorId { get; set; } + public int AuthorId { get; set; } + [ForeignKey("AuthorId")] + public User User { get; set; } // Rename to follow C# naming conventions } diff --git a/exercise.wwwapi/Program.cs b/exercise.wwwapi/Program.cs index 47f22ef..f2b5c1b 100644 --- a/exercise.wwwapi/Program.cs +++ b/exercise.wwwapi/Program.cs @@ -1,6 +1,106 @@ +using exercise.wwwapi.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Options; +using Microsoft.IdentityModel.Tokens; +using Microsoft.OpenApi.Models; +using Npgsql.EntityFrameworkCore.PostgreSQL; +using System.Diagnostics; +using System.Text; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using exercise.wwwapi.Helpers; +using exercise.wwwapi.Repository; +using exercise.wwwapi.Model; +using exercise.wwwapi.Configuration; +using exercise.wwwapi.Endpoints; // Add this line + var builder = WebApplication.CreateBuilder(args); // Add services to the container. +builder.Services.AddControllers(); +builder.Services.AddDbContext(options => + options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnectionString"))); +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +var config = new ConfigurationSettings(); +// Add services to the container. +builder.Services.AddScoped(); +builder.Services.AddScoped, Repository>(); +builder.Services.AddScoped, Repository>(); + +builder.Services.AddScoped>(); +builder.Services.AddDbContext(options => +{ + options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnectionString")); + options.LogTo(message => Debug.WriteLine(message)); +}); +//authentication verifying who they say they are +//authorization verifying what they have access to +builder.Services.AddAuthentication(x => +{ + x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; + x.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; + +}).AddJwtBearer(x => +{ + x.TokenValidationParameters = new TokenValidationParameters + { + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config.GetValue("AppSettings:Token"))), + ValidateIssuer = false, + ValidateAudience = false, + ValidateLifetime = false, + ValidateIssuerSigningKey = false + }; +}); +builder.Services.AddSwaggerGen(s => +{ + s.SwaggerDoc("v1", new OpenApiInfo + { + Version = "v1", + Title = "C# API Authentication", + Description = "Demo of an API using JWT as an authentication method", + Contact = new OpenApiContact + { + Name = "Nigel", + Email = "nigel@nigel.nigel", + Url = new Uri("https://www.boolean.co.uk") + }, + License = new OpenApiLicense + { + Name = "Boolean", + Url = new Uri("https://github.com/boolean-uk/csharp-api-auth") + } + }); + + s.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme + { + Description = "Add an Authorization header with a JWT token using the Bearer scheme see the app.http file for an example.)", + Name = "Authorization", + Type = SecuritySchemeType.ApiKey, + In = ParameterLocation.Header, + Scheme = "Bearer" + }); + + s.AddSecurityRequirement(new OpenApiSecurityRequirement + { + { + new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Type = ReferenceType.SecurityScheme, + Id = "Bearer" + } + }, + Array.Empty() + } + }); +}); +builder.Services.AddAuthorization(); + +builder.Services.AddControllers(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); @@ -12,9 +112,25 @@ { app.UseSwagger(); app.UseSwaggerUI(); + app.UseSwaggerUI(options => + { + options.SwaggerEndpoint("/openapi/v1.json", "Demo API"); + }); + // app.MapScalarApiReference(); // Remove this line } +app.UseCors(x => x + .AllowAnyMethod() + .AllowAnyHeader() + .SetIsOriginAllowed(origin => true) // allow any origin + .AllowCredentials()); // allow credentials + app.UseHttpsRedirection(); +app.ConfigureBlogApi(); +app.ConfigureAuthApi(); + +app.UseAuthentication(); +app.UseAuthorization(); app.Run(); \ No newline at end of file diff --git a/exercise.wwwapi/Repository/Repository.cs b/exercise.wwwapi/Repository/Repository.cs index f85909b..0b07fb8 100644 --- a/exercise.wwwapi/Repository/Repository.cs +++ b/exercise.wwwapi/Repository/Repository.cs @@ -5,61 +5,59 @@ namespace exercise.wwwapi.Repository; - public class Repository : IRepository where T : class - { +public class Repository : IRepository where T : class +{ + private DataContext _db; + private DbSet _table = null; + public Repository(DataContext db) + { + _db = db; + _table = _db.Set(); + } - private DataContext _db; - private DbSet _table = null; - - public Repository(DataContext db) + public IEnumerable GetAll(params Expression>[] includeExpressions) + { + IQueryable query = _table; + if (includeExpressions.Any()) { - _db = db; - _table = _db.Set(); + query = includeExpressions + .Aggregate(query, (current, expression) => current.Include(expression)); } + return query.ToList(); + } - public IEnumerable GetAll(params Expression>[] includeExpressions) - { - if (includeExpressions.Any()) - { - var set = includeExpressions - .Aggregate>, IQueryable> - (_table, (current, expression) => current.Include(expression)); - } - return _table.ToList(); - } + public IEnumerable GetAll() + { + return _table.ToList(); + } - public IEnumerable GetAll() - { - return _table.ToList(); - } - public T GetById(object id) - { - return _table.Find(id); - } + public T GetById(object id) + { + return _table.Find(id); + } - public void Insert(T obj) - { - _table.Add(obj); - } - public void Update(T obj) - { - _table.Attach(obj); - _db.Entry(obj).State = EntityState.Modified; - } + public void Insert(T obj) + { + _table.Add(obj); + } - public void Delete(object id) - { - T existing = _table.Find(id); - _table.Remove(existing); - } + public void Update(T obj) + { + _table.Attach(obj); + _db.Entry(obj).State = EntityState.Modified; + } + public void Delete(object id) + { + T existing = _table.Find(id); + _table.Remove(existing); + } - public void Save() - { - _db.SaveChanges(); - } + public void Save() + { + _db.SaveChanges(); + } public DbSet Table { get { return _table; } } - - } +} diff --git a/exercise.wwwapi/exercise.wwwapi.csproj b/exercise.wwwapi/exercise.wwwapi.csproj index 6ca4a23..03106d6 100644 --- a/exercise.wwwapi/exercise.wwwapi.csproj +++ b/exercise.wwwapi/exercise.wwwapi.csproj @@ -9,9 +9,17 @@ + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + +