Skip to content
Snippets Groups Projects
Commit c86c643f authored by Adiv's avatar Adiv
Browse files

added image storing

parent 7c034634
No related branches found
No related tags found
No related merge requests found
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>73bbe79f-c176-4d01-8185-46859357e82a</UserSecretsId>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<DockerfileContext>.</DockerfileContext>
</PropertyGroup>
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>73bbe79f-c176-4d01-8185-46859357e82a</UserSecretsId>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<DockerfileContext>.</DockerfileContext>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AutoMapper" Version="12.0.1" />
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.0.0" />
<PackageReference Include="BenchmarkDotNet" Version="0.13.5" />
<PackageReference Include="LazyCache" Version="2.4.0" />
<PackageReference Include="LazyCache.AspNetCore" Version="2.4.0" />
<PackageReference Include="MailKit" Version="3.6.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.0-preview.2.23153.2" />
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="8.0.0-preview.2.23153.2" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.0-preview.1.23112.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.0-preview.2.23128.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.0-preview.2.23128.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="8.0.0-preview.2.23128.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.0-preview.2.23128.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.0-preview.2.23128.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.18.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Polly" Version="7.2.3" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="6.5.0" />
<PackageReference Include="Swashbuckle.AspNetCore.Newtonsoft" Version="6.5.0" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="AutoMapper" Version="12.0.1"/>
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.0.0"/>
<PackageReference Include="Azure.Storage.Blobs" Version="12.16.0-beta.1"/>
<PackageReference Include="Azure.Storage.Common" Version="12.15.0-beta.1"/>
<PackageReference Include="BenchmarkDotNet" Version="0.13.5"/>
<PackageReference Include="LazyCache" Version="2.4.0"/>
<PackageReference Include="LazyCache.AspNetCore" Version="2.4.0"/>
<PackageReference Include="MailKit" Version="3.6.0"/>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.0-preview.2.23153.2"/>
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="8.0.0-preview.2.23153.2"/>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.0-preview.1.23112.2"/>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.0-preview.2.23128.3"/>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.0-preview.2.23128.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="8.0.0-preview.2.23128.3"/>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.0-preview.2.23128.3"/>
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.0-preview.2.23128.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.18.1"/>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3"/>
<PackageReference Include="Polly" Version="7.2.3"/>
<PackageReference Include="SixLabors.ImageSharp" Version="3.0.1"/>
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0"/>
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="6.5.0"/>
<PackageReference Include="Swashbuckle.AspNetCore.Newtonsoft" Version="6.5.0"/>
</ItemGroup>
</Project>
......@@ -19,6 +19,11 @@ public abstract class Constants
public const string ProfileSasToken = "profileSasToken";
}
public static class AzureBlobContainers
{
public const string ProfilePictures = "profile-pictures";
}
public static class AuthorizationPolicies
{
public const string HasUserId = "HasUserId";
......
......@@ -18,11 +18,11 @@ using Swashbuckle.AspNetCore.Annotations;
[ApiController]
public class AuthController : BaseAuthController
{
private readonly IAppCache _memoryCache;
private readonly IAuthService _authService;
private readonly IUserService _userService;
private readonly IEmailService _emailService;
private readonly IAppCache _memoryCache;
private readonly IRefreshTokenService _refreshTokenService;
private readonly IUserService _userService;
public AuthController(IAppCache memoryCache, IAuthService authService, IRefreshTokenService refreshTokenService,
IUserService userService, IEmailService emailService)
......@@ -100,6 +100,8 @@ public class AuthController : BaseAuthController
{
try
{
if (string.IsNullOrWhiteSpace(request.Password) || string.IsNullOrWhiteSpace(request.ConfirmPassword))
return BadRequest("Please enter a password.");
SecurityHelper.EnsurePasswordComplexity(request.Password, request.ConfirmPassword);
await _authService.SetPassword(request);
}
......@@ -252,4 +254,13 @@ public class AuthController : BaseAuthController
return Ok(null);
}
[HttpPost("UploadProfilePicture")]
[Consumes("multipart/form-data")]
public async Task<ActionResult<ResponseEnvelope<string>>> UploadProfilePicture(IFormFile? profilePicture)
{
if (profilePicture == null)
return BadRequest("Please upload a proper image.");
return Ok(await _userService.UploadProfilePicture(UserId, profilePicture));
}
}
\ No newline at end of file
......@@ -28,8 +28,7 @@ public class ResponseOperationFilter : IOperationFilter
operation.Responses.Add("403", new OpenApiResponse {Description = "Forbidden"});
if (authAttributes != null && authorizeAttributes.Any() && anonAttributes != null && !anonAttributes.Any())
{
operation.Security.Add(new OpenApiSecurityRequirement()
operation.Security.Add(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
......@@ -38,12 +37,11 @@ public class ResponseOperationFilter : IOperationFilter
{
Id = "Bearer",
Type = ReferenceType.SecurityScheme
},
}
},
new List<string>()
}
});
}
var apiTokenAttributes = context.MethodInfo.DeclaringType?.GetCustomAttributes(true)
.Union(context.MethodInfo.GetCustomAttributes(true))
......@@ -59,12 +57,11 @@ public class ResponseOperationFilter : IOperationFilter
return;
#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.
Constants.Roles[] roles = (Constants.Roles[]) roleField.GetValue(apiTokenAttributes.First());
var roles = (Constants.Roles[]) roleField.GetValue(apiTokenAttributes.First());
#pragma warning restore CS8600 // Converting null literal or possible null value to non-nullable type.
if (roles != null && roles.Contains(Constants.Roles.Api))
{
operation.Security.Add(new OpenApiSecurityRequirement()
operation.Security.Add(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
......@@ -73,17 +70,13 @@ public class ResponseOperationFilter : IOperationFilter
{
Id = "Authentication-API-Key",
Type = ReferenceType.SecurityScheme
},
}
},
new List<string>()
}
});
}
if (roles != null)
{
operation.Description += "Required Roles: " + string.Join(", ", roles);
}
if (roles != null) operation.Description += "Required Roles: " + string.Join(", ", roles);
}
}
}
\ No newline at end of file
......@@ -2,6 +2,7 @@ namespace AuthenticationMicroservice.Middleware;
using System.Net;
using Exceptions;
using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json;
public class ExceptionMiddleware
......@@ -48,7 +49,7 @@ public class ExceptionMiddleware
logger.LogWarning(ex.Message);
break;
case Microsoft.EntityFrameworkCore.DbUpdateException dbex:
case DbUpdateException dbex:
envelope = new ResponseEnvelope<object>(dbex,
userMessage: "Database Error has occurred. Please try again or report the issue.");
context.Response.StatusCode = (int) HttpStatusCode.InternalServerError;
......@@ -85,8 +86,6 @@ public class ResponseEnvelope
public class ResponseEnvelope<T> : ResponseEnvelope
{
public new T? Data { get; set; }
public ResponseEnvelope()
{
}
......@@ -94,7 +93,7 @@ public class ResponseEnvelope<T> : ResponseEnvelope
public ResponseEnvelope(Exception ex, int statusCode = 400, string? userMessage = null) : this(
new List<ErrorDetails>
{
new ErrorDetails
new()
{
Message = ex.Message,
InnerMessage = ex.InnerException?.Message,
......@@ -125,6 +124,8 @@ public class ResponseEnvelope<T> : ResponseEnvelope
StatusCode = statusCode;
}
public new T? Data { get; set; }
public override string ToString()
{
return JsonConvert.SerializeObject(this);
......
......@@ -9,7 +9,7 @@ public class SwaggerAuthMiddleware
public SwaggerAuthMiddleware(RequestDelegate next)
{
this._next = next;
_next = next;
}
public async Task InvokeAsync(HttpContext context)
......
......@@ -9,11 +9,13 @@ public class AuthenticationDbContext : DbContext
{
public static string ConnectionStringName = "DbConnectionString";
public AuthenticationDbContext(DbContextOptions<AuthenticationDbContext> options) : base(options) { }
public AuthenticationDbContext(DbContextOptions<AuthenticationDbContext> options) : base(options)
{
}
public DbSet<User> User { get; set; } = null!;
public DbSet<RefreshToken> RefreshToken { get; set; } = null!;
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
......
......@@ -24,8 +24,9 @@ builder.Configuration.AddJsonFile("appsettings.Development.json", true, true);
builder.Services.Configure<EmailConfig>(builder.Configuration.GetSection("EmailConfig"));
builder.Services.Configure<TokenSettings>(builder.Configuration.GetSection("TokenSettings"));
builder.Services.AddDbContext<AuthenticationDbContext>(opt =>
opt.UseSqlServer(builder.Configuration.GetConnectionString("AuthenticationDb") ??
opt.UseSqlServer(builder.Configuration.GetConnectionString("DbConnectionString") ??
throw new Exception("Could not connect to database.")));
builder.Services.Configure<ConnectionStrings>(builder.Configuration.GetSection("ConnectionStrings"));
builder.Services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo {Title = "AuthenticationMicroserviceAPI", Version = "v1"});
......@@ -104,8 +105,11 @@ var mapperConfig = new MapperConfiguration(mc => { mc.AddProfile(new AutoMapperP
var mapper = mapperConfig.CreateMapper();
builder.Services.AddSingleton(mapper);
// Add repositories from /Repositories
builder.Services.AddTransient(typeof(IBaseRepository<>), typeof(BaseRepository<>));
// Add services from /Services
builder.Services.AddTransient<IStorageService, StorageService>();
builder.Services.AddTransient<IUserService, UserService>();
builder.Services.AddTransient<IRefreshTokenService, RefreshTokenService>();
builder.Services.AddTransient<IEmailService, EmailService>();
......@@ -143,9 +147,6 @@ app.UseMiddleware<ExceptionMiddleware>();
app.UseHttpsRedirection();
app.UseOutputCache();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
app.Run();
\ No newline at end of file
......@@ -26,8 +26,8 @@ public interface IBaseRepository<TEntity> where TEntity : class
public sealed class BaseRepository<TEntity> : IBaseRepository<TEntity> where TEntity : class
{
private readonly AuthenticationDbContext _context;
private readonly DbSet<TEntity> _dbSet;
private readonly AuthenticationDbContext _context;
public BaseRepository(AuthenticationDbContext context)
{
......@@ -47,12 +47,14 @@ public sealed class BaseRepository<TEntity> : IBaseRepository<TEntity> where TEn
public TEntity GetByIdThrowIfNull(int id)
{
return GetById(id) ?? throw new AuthenticationException($"Item with ID {id} does not exist.", HttpStatusCode.NotFound);
return GetById(id) ??
throw new AuthenticationException($"Item with ID {id} does not exist.", HttpStatusCode.NotFound);
}
public TEntity GetByIdThrowIfNull(Guid id)
{
return GetById(id) ?? throw new AuthenticationException($"Item with ID {id} does not exist.", HttpStatusCode.NotFound);
return GetById(id) ??
throw new AuthenticationException($"Item with ID {id} does not exist.", HttpStatusCode.NotFound);
}
public IQueryable<TEntity> GetAll()
......
......@@ -30,7 +30,8 @@ public class AuthService : IAuthService
private readonly AuthenticationDbContext _context;
private readonly IRefreshTokenService _refreshTokenService;
public AuthService(AuthenticationDbContext context, IEmailService emailService, IRefreshTokenService refreshTokenService)
public AuthService(AuthenticationDbContext context, IEmailService emailService,
IRefreshTokenService refreshTokenService)
{
_context = context;
_emailService = emailService;
......@@ -62,7 +63,7 @@ public class AuthService : IAuthService
throw new AuthenticationException("User not found.", HttpStatusCode.Unauthorized);
return await GenerateAuthenticatedUser(user, oldRefreshToken);
}
public async Task<AuthenticatedUserDTO> GenerateAuthenticatedUser(User user, string? oldToken = null)
{
var newAccess = _refreshTokenService.CreateNewTokenForUser(user);
......
......@@ -41,7 +41,8 @@ public class EmailService : IEmailService
}
catch (Exception ex)
{
throw new AuthenticationException($"Failed to send email to: {to}. " + ex.Message, HttpStatusCode.InternalServerError);
throw new AuthenticationException($"Failed to send email to: {to}. " + ex.Message,
HttpStatusCode.InternalServerError);
}
}
}
\ No newline at end of file
namespace AuthenticationMicroservice.Services;
using System.Web;
using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Models;
using Azure.Storage.Sas;
using Microsoft.Extensions.Options;
using Settings;
public interface IStorageService
{
Task<Uri> SaveImageAsJpgBlob(string containerName, string fileName, Stream input,
Dictionary<string, string>? metadata = null);
string GetSasForFile(string containerName, string url, DateTimeOffset? expires = null);
}
public class StorageService : IStorageService
{
private readonly ConnectionStrings _connectionStrings;
public StorageService(IOptions<ConnectionStrings> connectionStrings)
{
_connectionStrings = connectionStrings.Value;
}
public string GetSasForFile(string containerName, string url, DateTimeOffset? expires = null)
{
url = GetFileFromUrl(url, containerName) ?? string.Empty;
if (string.IsNullOrWhiteSpace(url))
return string.Empty;
var container = new BlobContainerClient(_connectionStrings.AzureBlobStorage, containerName);
var blob = container.GetBlobClient(url);
var urlWithSas = blob.GenerateSasUri(BlobSasPermissions.Read, DateTimeOffset.Now.AddHours(3)).ToString();
return urlWithSas;
}
public async Task<Uri> SaveImageAsJpgBlob(string containerName, string fileName, Stream input,
Dictionary<string, string>? metadata = null)
{
using var image = await Image.LoadAsync(input);
using var croppedImage = new MemoryStream();
var clone = image.Clone(context =>
context.Resize(new ResizeOptions {Mode = ResizeMode.Max, Size = new Size(1366, 768)}));
await clone.SaveAsJpegAsync(croppedImage);
croppedImage.Position = 0;
var blob = await GetBlobReference(containerName, fileName);
await blob.SetHttpHeadersAsync(new BlobHttpHeaders {ContentType = "image/jpeg"});
await blob.UploadAsync(croppedImage);
return blob.Uri;
}
private async Task<BlobClient> GetBlobReference(string? containerName, string blobName)
{
var container = new BlobContainerClient(_connectionStrings.AzureBlobStorage, containerName);
await container.CreateIfNotExistsAsync();
var blob = container.GetBlobClient(blobName);
return blob;
}
private static string? GetFileFromUrl(string url, string container)
{
if (string.IsNullOrEmpty(url))
return null;
var userDataContainerString = container + "/";
var fileUrl =
url[(url.IndexOf(userDataContainerString, StringComparison.Ordinal) + userDataContainerString.Length)..];
return HttpUtility.UrlDecode(fileUrl);
}
}
\ No newline at end of file
......@@ -14,19 +14,20 @@ public interface IUserService
{
Task<User> CreateUser(UserRegisterRequestDTO request);
Task<string> UploadProfilePicture(int userId, IFormFile profilePicture);
Task<string> EditProfilePicture(int userId, IFormFile? profilePicture = null);
List<UserDTO> GetListOfAllUsers();
}
public class UserService : IUserService
{
private readonly IMapper _mapper;
private readonly IStorageService _storageService;
private readonly IBaseRepository<User> _userRepo;
public UserService(IMapper mapper, IBaseRepository<User> userRepo)
public UserService(IMapper mapper, IBaseRepository<User> userRepo, IStorageService storageService)
{
_mapper = mapper;
_userRepo = userRepo;
_storageService = storageService;
}
public async Task<User> CreateUser(UserRegisterRequestDTO request)
......@@ -50,18 +51,22 @@ public class UserService : IUserService
return await _userRepo.CreateAndSaveAsync(newUser);
}
public Task<string> UploadProfilePicture(int userId, IFormFile profilePicture)
public async Task<string> UploadProfilePicture(int userId, IFormFile profilePicture)
{
throw new NotImplementedException();
}
public Task<string> EditProfilePicture(int userId, IFormFile? profilePicture = null)
{
throw new NotImplementedException();
var user = _userRepo.GetByIdThrowIfNull(userId);
using var ms = new MemoryStream();
await profilePicture.CopyToAsync(ms);
ms.Position = 0;
var fileName = $"{user.Id}/{user.FirstName}";
var image = await _storageService.SaveImageAsJpgBlob(Constants.AzureBlobContainers.ProfilePictures, fileName,
ms);
user.ProfilePictureUrl = image.ToString();
await _userRepo.UpdateAndSaveAsync(user);
return _storageService.GetSasForFile(Constants.AzureBlobContainers.ProfilePictures, image.ToString());
}
public List<UserDTO> GetListOfAllUsers()
{
return _mapper.ProjectTo<UserDTO>(_userRepo.GetAll()).ToList();
return _mapper.ProjectTo<UserDTO>(_userRepo.GetAll().AsNoTracking()).ToList();
}
}
\ No newline at end of file
namespace AuthenticationMicroservice.Settings;
public class ConnectionStrings
{
public string? DbConnectionString { get; set; }
public string? AzureBlobStorage { get; set; }
}
\ No newline at end of file
......@@ -5,5 +5,22 @@
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
"AllowedHosts": "*",
"ConnectionStrings": {
"DbConnectionString": "",
"AzureBlobStorage": ""
},
"TokenSettings": {
"RefreshTokenValidityMinutes": 43200,
"AccessTokenValidityMinutes": 3600,
"RefreshTokenRandomNumbers": 32,
"Secret": ""
},
"EmailConfig": {
"Email": "",
"Password": ""
},
"Group17Website": {
"BaseUrl": ""
}
}
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment