Files
Project-CBackend/TicketSystem/Program.cs
Tweeen 690987eb8f Änderung aller Features auf API
Ticket_System wurde zu TicketSystem in TicketSystem ist nun eine API.WEB Core anwendung. Implementierung von /signin, /user/create, /ticket/create, /ticket/show/all, /ticket/show/{ticketID}
2026-01-30 11:51:31 +01:00

371 lines
11 KiB
C#

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<object>();
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<object>();
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<Claim>
{
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
);