This commit is contained in:
2026-03-29 23:47:31 +02:00
commit 216d5d2280
75 changed files with 5702 additions and 0 deletions

View File

@@ -0,0 +1,147 @@
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using LehrerApp.Sync.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
namespace LehrerApp.Api;
public static class Endpoints
{
// ── Auth ──────────────────────────────────────────────────────────────────
public static void MapAuthEndpoints(this WebApplication app, string jwtSecret)
{
app.MapPost("/api/auth/login", (LoginRequest req) =>
{
// TODO: Passwort-Hash gegen DB prüfen
if (string.IsNullOrWhiteSpace(req.Username) ||
string.IsNullOrWhiteSpace(req.Password))
return Results.Unauthorized();
var token = GenerateJwt(req.Username, jwtSecret);
return Results.Ok(new { token, userId = req.Username });
});
app.MapPost("/api/auth/register", (RegisterRequest req) =>
{
// TODO: User anlegen, Passwort hashen (BCrypt)
if (string.IsNullOrWhiteSpace(req.Username) ||
req.Password.Length < 12)
return Results.BadRequest(
"Passwort muss mindestens 12 Zeichen haben.");
var token = GenerateJwt(req.Username, jwtSecret);
return Results.Ok(new { token, userId = req.Username });
});
}
// ── Sync ──────────────────────────────────────────────────────────────────
public static void MapSyncEndpoints(this WebApplication app)
{
var sync = app.MapGroup("/api/sync").RequireAuthorization();
sync.MapPost("/push", (
[FromBody] List<SyncEvent> events,
ClaimsPrincipal user,
EventStore store) =>
{
var userId = user.FindFirst(ClaimTypes.NameIdentifier)?.Value;
if (userId is null) return Results.Unauthorized();
var result = store.Push(userId, events);
return Results.Ok(result);
});
sync.MapGet("/pull", (
[FromQuery] long since,
[FromQuery] string deviceId,
ClaimsPrincipal user,
EventStore store) =>
{
var userId = user.FindFirst(ClaimTypes.NameIdentifier)?.Value;
if (userId is null) return Results.Unauthorized();
var result = store.Pull(userId, since, deviceId);
return Results.Ok(result);
});
sync.MapGet("/status", (ClaimsPrincipal user) =>
{
var userId = user.FindFirst(ClaimTypes.NameIdentifier)?.Value;
if (userId is null) return Results.Unauthorized();
return Results.Ok(new { userId, timestamp = DateTime.UtcNow });
});
}
// ── Snapshot ──────────────────────────────────────────────────────────────
public static void MapSnapshotEndpoints(this WebApplication app)
{
var snap = app.MapGroup("/api/snapshot").RequireAuthorization();
// Sender: Snapshot hochladen → Einmal-Code erhalten
snap.MapPost("/upload", (
[FromBody] SnapshotUploadRequest request,
ClaimsPrincipal user,
SnapshotStore store) =>
{
var userId = user.FindFirst(ClaimTypes.NameIdentifier)?.Value;
if (userId is null) return Results.Unauthorized();
if (string.IsNullOrWhiteSpace(request.EncryptedPayload))
return Results.BadRequest("Kein Payload.");
var result = store.Store(userId, request);
return Results.Ok(result);
});
// Empfänger: Snapshot per Code abrufen (Einmal-Verwendung)
snap.MapGet("/{code}", (
string code,
ClaimsPrincipal user,
SnapshotStore store) =>
{
var userId = user.FindFirst(ClaimTypes.NameIdentifier)?.Value;
if (userId is null) return Results.Unauthorized();
var result = store.Retrieve(userId, code);
if (result is null)
return Results.NotFound(
"Snapshot nicht gefunden, abgelaufen oder bereits verwendet.");
return Results.Ok(result);
});
}
// ── JWT ───────────────────────────────────────────────────────────────────
private static string GenerateJwt(string userId, string secret)
{
var key = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(secret));
var creds = new SigningCredentials(
key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
claims:
[
new Claim(ClaimTypes.NameIdentifier, userId),
new Claim(JwtRegisteredClaimNames.Jti,
Guid.NewGuid().ToString()),
],
expires: DateTime.UtcNow.AddDays(30),
signingCredentials: creds);
return new JwtSecurityTokenHandler().WriteToken(token);
}
}
public record LoginRequest(string Username, string Password);
public record RegisterRequest(string Username, string Password, string DisplayName);
// Ergänzung wird unten in der bestehenden Datei angefügt
// In Program.cs: app.MapReadableSnapshotEndpoints(); und app.MapPlainSyncEndpoints();