Compare commits

..

6 Commits

6 changed files with 68 additions and 11 deletions
@@ -3,6 +3,9 @@ using Microsoft.EntityFrameworkCore;
using URL_Shortener.DTO; using URL_Shortener.DTO;
using URL_Shortener.Models; using URL_Shortener.Models;
using HashidsNet; using HashidsNet;
using Microsoft.AspNetCore.Authorization;
using System.Security.Claims;
using URL_Shortener.Extensions;
namespace URL_Shortener.Controllers namespace URL_Shortener.Controllers
{ {
@@ -38,13 +41,15 @@ namespace URL_Shortener.Controllers
} }
[HttpPost] [HttpPost]
[Authorize]
public async Task<IActionResult> CreateShortUrlAsync(CreateShortUrlDto dto) public async Task<IActionResult> CreateShortUrlAsync(CreateShortUrlDto dto)
{ {
var shortUrl = new ShortUrl var shortUrl = new ShortUrl
{ {
OriginalUrl = dto.OriginalUrl, OriginalUrl = dto.OriginalUrl,
ExpirationDateTime = dto.ExpirationDateTime, ExpirationDateTime = dto.ExpirationDateTime,
ClickLimit = dto.ClickLimit ClickLimit = dto.ClickLimit,
UserId = int.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)!)
}; };
await _db.ShortUrls.AddAsync(shortUrl); await _db.ShortUrls.AddAsync(shortUrl);
await _db.SaveChangesAsync(); await _db.SaveChangesAsync();
@@ -55,12 +60,16 @@ namespace URL_Shortener.Controllers
} }
[HttpPatch("{id:int}")] [HttpPatch("{id:int}")]
[Authorize]
public async Task<IActionResult> UpdateShortUrlAsync(int id, UpdateShortUrlDto dto) public async Task<IActionResult> UpdateShortUrlAsync(int id, UpdateShortUrlDto dto)
{ {
var shortUrl = await _db.ShortUrls.FindAsync(id); var shortUrl = await _db.ShortUrls.FindAsync(id);
if (shortUrl == null) if (shortUrl == null)
return NotFound("Short URL not found"); return NotFound("Short URL not found");
if (!User.CompareId(shortUrl.UserId))
throw new Exception("Not authorized");
if (!string.IsNullOrEmpty(dto.OriginalUrl)) if (!string.IsNullOrEmpty(dto.OriginalUrl))
shortUrl.OriginalUrl = dto.OriginalUrl; shortUrl.OriginalUrl = dto.OriginalUrl;
if (dto.ExpirationDateTime.HasValue) if (dto.ExpirationDateTime.HasValue)
@@ -78,12 +87,16 @@ namespace URL_Shortener.Controllers
} }
[HttpDelete("{id:int}")] [HttpDelete("{id:int}")]
[Authorize]
public async Task<IActionResult> DeleteShortUrlByIdAsync(int id) public async Task<IActionResult> DeleteShortUrlByIdAsync(int id)
{ {
var shortUrl = await _db.ShortUrls.FindAsync(id); var shortUrl = await _db.ShortUrls.FindAsync(id);
if (shortUrl == null) if (shortUrl == null)
return NotFound(); return NotFound();
if (!User.CompareId(shortUrl.UserId))
throw new Exception("Not authorized");
_db.ShortUrls.Remove(shortUrl); _db.ShortUrls.Remove(shortUrl);
var clickInfos = _db.ClickInfos.Where(c => c.ShortUrlId == id); var clickInfos = _db.ClickInfos.Where(c => c.ShortUrlId == id);
+2 -2
View File
@@ -13,8 +13,8 @@ namespace URL_Shortener.Controllers
[HttpPost("register")] [HttpPost("register")]
public async Task<IActionResult> RegisterAsync(RegisterUserDTO dto) public async Task<IActionResult> RegisterAsync(RegisterUserDTO dto)
{ {
await _userService.RegisterAsync(dto.Name, dto.Email, dto.Password); var jwt = await _userService.RegisterAsync(dto.Name, dto.Email, dto.Password);
return Ok(); return Ok(jwt);
} }
[HttpPost("login")] [HttpPost("login")]
@@ -0,0 +1,12 @@
using System.Security.Claims;
namespace URL_Shortener.Extensions;
public static class ClaimsPrincipalExtensions
{
public static bool CompareId(this ClaimsPrincipal claimsPrincipal, int userId)
{
return int.TryParse(claimsPrincipal.FindFirstValue(ClaimTypes.NameIdentifier), out int id) &&
id == userId;
}
}
+34 -4
View File
@@ -1,7 +1,11 @@
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using URL_Shortener; using URL_Shortener;
using URL_Shortener.Services; using URL_Shortener.Services;
using URL_Shortener.Settings; using URL_Shortener.Settings;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi;
using System.Text;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
@@ -12,13 +16,40 @@ builder.Services.AddControllers();
builder.Services.AddOpenApi(); builder.Services.AddOpenApi();
builder.Services.AddEndpointsApiExplorer(); builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(); builder.Services.AddSwaggerGen(options =>
{
options.AddSecurityDefinition("bearer", new OpenApiSecurityScheme
{
Type = SecuritySchemeType.Http,
Scheme = "bearer",
BearerFormat = "JWT",
Description = "JWT Authorization header using the Bearer scheme."
});
options.AddSecurityRequirement(document => new OpenApiSecurityRequirement
{
[new OpenApiSecuritySchemeReference("bearer", document)] = []
});
});
builder.Services.AddSingleton<HashidsNet.Hashids>(); builder.Services.AddSingleton<HashidsNet.Hashids>();
builder.Services.AddScoped<UserService>(); builder.Services.AddScoped<UserService>();
builder.Services.AddScoped<JwtService>(); builder.Services.AddScoped<JwtService>();
builder.Services.Configure<AuthSettings>(builder.Configuration.GetSection("AuthSettings"));
var authSettings = builder.Configuration.GetSection("AuthSettings");
builder.Services.Configure<AuthSettings>(authSettings);
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(o => o.TokenValidationParameters = new()
{
ValidateIssuerSigningKey = true,
ValidateAudience = false,
ValidateIssuer = false,
ValidateLifetime = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(
authSettings.Get<AuthSettings>()!.SecretKey))
});
builder.Services.AddDbContext<AppDbContext>(options => builder.Services.AddDbContext<AppDbContext>(options =>
{ {
@@ -45,8 +76,7 @@ using (var scope = app.Services.CreateScope())
retry.Execute(db.Database.Migrate); retry.Execute(db.Database.Migrate);
} }
//app.UseHttpsRedirection(); app.UseAuthentication();
app.UseAuthorization(); app.UseAuthorization();
app.MapControllers(); app.MapControllers();
+3 -3
View File
@@ -14,9 +14,9 @@ namespace URL_Shortener.Services
public string GenerateToken(User user) public string GenerateToken(User user)
{ {
var claims = new List<Claim>() { var claims = new List<Claim>() {
new("userName", user.Name), new(ClaimTypes.Name, user.Name),
new("userEmail", user.Email), new(ClaimTypes.Email, user.Email),
new("id", user.Id.ToString()) new(ClaimTypes.NameIdentifier, user.Id.ToString())
}; };
var jwt = new JwtSecurityToken( var jwt = new JwtSecurityToken(
expires: DateTime.UtcNow.Add(options.Value.Expires), expires: DateTime.UtcNow.Add(options.Value.Expires),
+3 -1
View File
@@ -9,7 +9,7 @@ namespace URL_Shortener.Services
private readonly AppDbContext _db = db; private readonly AppDbContext _db = db;
private readonly JwtService _jwtService = jwtService; private readonly JwtService _jwtService = jwtService;
public async Task RegisterAsync(string userName, string email, string password, CancellationToken cancellationToken = default) public async Task<string> RegisterAsync(string userName, string email, string password, CancellationToken cancellationToken = default)
{ {
if (await _db.Users.FirstOrDefaultAsync(u => u.Email == email, cancellationToken) != null) if (await _db.Users.FirstOrDefaultAsync(u => u.Email == email, cancellationToken) != null)
throw new Exception("This user already exists."); throw new Exception("This user already exists.");
@@ -23,6 +23,8 @@ namespace URL_Shortener.Services
await _db.Users.AddAsync(user, cancellationToken); await _db.Users.AddAsync(user, cancellationToken);
await _db.SaveChangesAsync(cancellationToken); await _db.SaveChangesAsync(cancellationToken);
return _jwtService.GenerateToken(user);
} }
public async Task<string> LoginAsync(string email, string password, CancellationToken cancellationToken = default) public async Task<string> LoginAsync(string email, string password, CancellationToken cancellationToken = default)