using BCrypt.Net; using Microsoft.AspNetCore.Identity.Data; using Microsoft.IdentityModel.Tokens; using MySqlConnector; using System.Collections.Generic; using System.Data; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; var builder = WebApplication.CreateBuilder(args); var connectionString = builder.Configuration.GetConnectionString("Default"); var jwtSecret = builder.Configuration["Jwt:Secret"] ?? "CHANGE_ME_TO_A_LONG_RANDOM_SECRET"; var jwtIssuer = builder.Configuration["Jwt:Issuer"] ?? "Ticket-System"; var jwtAudience = builder.Configuration["Jwt:Audience"] ?? "Ticket-System"; builder.Services.AddTransient(_ => new MySqlConnection(connectionString)); var app = builder.Build(); app.Use(async (context, next) => { var path = context.Request.Path.Value ?? ""; if (path.Equals("/signin", StringComparison.OrdinalIgnoreCase)) //|| path.Equals("/user/create", StringComparison.OrdinalIgnoreCase)) { await next(); return; } if (!context.Request.Headers.TryGetValue("Authorization", out var authHeaderValues)) { context.Response.StatusCode = StatusCodes.Status403Forbidden; await context.Response.WriteAsJsonAsync(new { error = "Please add the JWT token to the header" }); return; } var authHeader = authHeaderValues.ToString(); var parts = authHeader.Split(' ', StringSplitOptions.RemoveEmptyEntries); if (parts.Length != 2 || !parts[0].Equals("Bearer", StringComparison.OrdinalIgnoreCase)) { context.Response.StatusCode = StatusCodes.Status409Conflict; await context.Response.WriteAsJsonAsync(new { error = "Invalid token specified" }); return; } var token = parts[1]; var principal = ValidateJwt(token, jwtSecret, jwtIssuer, jwtAudience); if (principal == null) { context.Response.StatusCode = StatusCodes.Status403Forbidden; await context.Response.WriteAsJsonAsync(new { error = "Invalid token specified" }); return; } context.Items["user"] = principal.Identity?.Name ?? principal.FindFirstValue(ClaimTypes.Name) ?? ""; await next(); }); app.MapPost("/signin", async (SignInRequest req, MySqlConnection conn) => { const string sql = "SELECT userID, username, password FROM User WHERE username = @username LIMIT 1;"; await conn.OpenAsync(); await using var cmd = new MySqlCommand(sql, conn); cmd.Parameters.AddWithValue("@username", req.name); await using var reader = await cmd.ExecuteReaderAsync(); if (!await reader.ReadAsync()) return Results.Unauthorized(); var userId = reader.GetInt32("userID").ToString(); var username = reader.GetString("username"); var pwHash = reader.GetString("password"); var ok = BCrypt.Net.BCrypt.Verify(req.password, pwHash); if (!ok) return Results.Unauthorized(); var token = CreateJwt(userId, username, jwtSecret, jwtIssuer, jwtAudience, minutesValid: 120); return Results.Ok(new ResponseToken(token)); }); app.MapGet("/token/validate", (HttpContext ctx) => { return Results.Ok(new TokenIsValid(true)); }); app.MapGet("/me", (HttpContext ctx) => { var user = ctx.Items["user"]?.ToString() ?? ""; return Results.Ok(new { user }); }); app.MapPost("/user/create", async (RegisterRequest req, MySqlConnection conn) => { await conn.OpenAsync(); var pwHash = BCrypt.Net.BCrypt.HashPassword(req.password); var userId = Guid.NewGuid().ToString("N"); const string insertSql = """ INSERT INTO User (userID, username, password) VALUES (@userID, @username, @password); """; await using var cmd = new MySqlCommand(insertSql, conn); cmd.Parameters.AddWithValue("@userID", userId); cmd.Parameters.AddWithValue("@username", req.name); cmd.Parameters.AddWithValue("@password", pwHash); var rows = await cmd.ExecuteNonQueryAsync(); if (rows != 1) return Results.Problem("User konnte nicht angelegt werden."); var token = CreateJwt(userId, req.name, jwtSecret, jwtIssuer, jwtAudience, minutesValid: 120); return Results.Created($"/user/{userId}", new ResponseToken(token)); }); app.MapPost("/ticket/create", async (HttpContext ctx, CreateTicketRequest req, MySqlConnection conn) => { var userId = ctx.Items["user"]?.ToString(); if (string.IsNullOrEmpty(userId)) return Results.Unauthorized(); await conn.OpenAsync(); const string insertSql = """ INSERT INTO ticket (userID, ticketID, ticketname, status, priority, OpenAt, property) VALUES (@userID,(select max(ticketID) from ticket) + 1, @ticketname, @beschreibung, @status, @priority, @openAt, @property); """; await using var cmd = new MySqlCommand(insertSql, conn); cmd.Parameters.AddWithValue("@userID", userId); cmd.Parameters.AddWithValue("@status", req.status); cmd.Parameters.AddWithValue("@ticketname", req.ticketname); cmd.Parameters.AddWithValue("@beschreibung", req.beschreibung); cmd.Parameters.AddWithValue("@priority", req.priority); cmd.Parameters.AddWithValue("@openAt", DateTime.UtcNow); cmd.Parameters.AddWithValue("@property", req.property); var rows = await cmd.ExecuteNonQueryAsync(); if (rows != 1) return Results.Problem("Ticket konnte nicht angelegt werden"); var ticketId = cmd.LastInsertedId; return Results.Created($"/ticket/{ticketId}", new { ticketID = ticketId, userID = userId }); }); app.MapGet("/ticket/show/all", async (HttpContext ctx, MySqlConnection conn) => { var userId = ctx.Items["user"]?.ToString(); if (string.IsNullOrEmpty(userId)) return Results.Unauthorized(); await conn.OpenAsync(); const string sql = """ SELECT t.ticketID, t.ticketname, u.username, t.status, t.priority, t.category FROM ticket t JOIN User u ON t.userID = u.userID WHERE t.userID = @userID ORDER BY t.priority; """; await using var cmd = new MySqlCommand(sql, conn); cmd.Parameters.AddWithValue("@userID", userId); await using var reader = await cmd.ExecuteReaderAsync(); var tickets = new List(); while (await reader.ReadAsync()) { tickets.Add(new { ticketID = reader.GetInt64("ticketID"), ticketname = reader.GetString("ticketname"), username = reader.GetString("username"), status = reader.GetInt32("status"), priority = reader.GetInt32("priority"), category = reader.GetString("category") }); } return Results.Ok(tickets); }); app.MapGet("/ticket/show/{ticketId:int}", async ( HttpContext ctx, long ticketId, MySqlConnection conn) => { var userId = ctx.Items["user"]?.ToString(); if (string.IsNullOrEmpty(userId)) return Results.Unauthorized(); await conn.OpenAsync(); const string ticketSql = """ SELECT t.TicketID, t.Ticketname, t.UserID, u.Username AS OwnerUsername, t.Status, t.Priority, t.OpenAt, t.ClosedAt, t.category FROM Ticket t JOIN User u ON u.UserID = t.UserID WHERE t.TicketID = @ticketId AND t.UserID = @userId; """; object? ticket = null; await using (var cmd = new MySqlCommand(ticketSql, conn)) { cmd.Parameters.AddWithValue("@ticketId", ticketId); cmd.Parameters.AddWithValue("@userId", userId); await using var reader = await cmd.ExecuteReaderAsync(); if (!await reader.ReadAsync()) return Results.NotFound("Ticket nicht gefunden"); ticket = new { ticketID = reader.GetInt64("TicketID"), ticketname = reader.GetString("Ticketname"), userID = reader.GetInt64("UserID"), ownerUsername = reader.GetString("OwnerUsername"), status = reader.GetInt32("Status"), priority = reader.GetInt32("Priority"), openAt = reader.GetString("OpenAt"), closedAt = reader.IsDBNull("ClosedAt") ? null : reader.GetString("ClosedAt"), category = reader.GetString("category") }; } const string messagesSql = """ SELECT m.MessageID, m.Sequence, m.SendAt, m.Content, m.Sender, su.Username AS SenderUsername, m.Reciver, ru.Username AS ReciverUsername FROM Messages m LEFT JOIN User su ON su.UserID = m.Sender LEFT JOIN User ru ON ru.UserID = m.Reciver WHERE m.TicketID = @ticketId ORDER BY m.Sequence ASC; """; var messages = new List(); await using (var cmd = new MySqlCommand(messagesSql, conn)) { cmd.Parameters.AddWithValue("@ticketId", ticketId); await using var reader = await cmd.ExecuteReaderAsync(); while (await reader.ReadAsync()) { messages.Add(new { messageID = reader.GetInt64("MessageID"), sequence = reader.GetInt32("Sequence"), sendAt = reader.GetString("SendAt"), content = reader.GetString("Content"), sender = reader.GetInt64("Sender"), senderUsername = reader.IsDBNull("SenderUsername") ? null : reader.GetString("SenderUsername"), reciver = reader.GetInt64("Reciver"), reciverUsername = reader.IsDBNull("ReciverUsername") ? null : reader.GetString("ReciverUsername") }); } } return Results.Ok(new { ticket, messages }); }); static string CreateJwt(string userId, string username, string secret, string issuer, string audience, int minutesValid) { var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secret)); var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var claims = new List { new Claim(ClaimTypes.NameIdentifier, userId), new Claim(ClaimTypes.Name, username), }; var token = new JwtSecurityToken( issuer: issuer, audience: audience, claims: claims, notBefore: DateTime.UtcNow, expires: DateTime.UtcNow.AddMinutes(minutesValid), signingCredentials: creds ); return new JwtSecurityTokenHandler().WriteToken(token); } static ClaimsPrincipal? ValidateJwt(string token, string secret, string issuer, string audience) { var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secret)); var handler = new JwtSecurityTokenHandler(); try { var principal = handler.ValidateToken(token, new TokenValidationParameters { ValidateIssuer = true, ValidIssuer = issuer, ValidateAudience = true, ValidAudience = audience, ValidateIssuerSigningKey = true, IssuerSigningKey = key, ValidateLifetime = true, ClockSkew = TimeSpan.FromSeconds(30) }, out _); return principal; } catch { return null; } ; } app.Run(); public record RegisterRequest(string name, string password); public record SignInRequest(string name, string password); public record ResponseToken(string token); public record TokenIsValid(bool is_valid); public record CreateTicketRequest( int status, int priority, string property, string ticketname, string beschreibung );