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}
371 lines
11 KiB
C#
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
|
|
); |