Add authentication for secure document access

Introduced a new login page (`Login.razor`) to handle user authentication via access codes. Implemented `AuthService` to validate access codes through an API. Updated `ReportViewer.razor` to check user access and redirect unauthorized users to the login page. Modified `Program.cs` to register `AuthService` for dependency injection. Enhanced document fetching in `ReportViewer.razor` to ensure secure access control.
This commit is contained in:
2026-05-31 09:01:02 +02:00
parent 390f2d2cae
commit de8d363c27
4 changed files with 102 additions and 0 deletions

View File

@@ -0,0 +1,71 @@
@page "/login/{EnvelopeKey}"
@using EnvelopeGenerator.ReceiverUI.Options
@using Microsoft.Extensions.Options
<div class="login-page-wrapper d-flex align-items-center justify-content-center min-vh-100 bg-light">
<div class="card shadow-sm" style="max-width: 420px; width: 100%;">
<div class="card-body p-4">
<h4 class="card-title mb-1">Zugang zum Dokument</h4>
<p class="text-muted mb-4" style="font-size: 0.9rem;">
Bitte geben Sie den Zugangscode ein, den Sie per E-Mail erhalten haben, um das Dokument zu öffnen.
</p>
@if (!string.IsNullOrWhiteSpace(ErrorMessage)) {
<div class="alert alert-danger py-2">@ErrorMessage</div>
}
<div class="mb-3">
<label class="form-label" for="login-access-code">Zugangscode</label>
<input id="login-access-code"
type="password"
class="form-control"
placeholder="Zugangscode eingeben"
@bind="AccessCode"
@bind:event="oninput"
@onkeydown="OnKeyDownAsync"
disabled="@IsLoading" />
</div>
<button class="btn btn-primary w-100"
@onclick="SubmitAsync"
disabled="@(IsLoading || string.IsNullOrWhiteSpace(AccessCode))">
@if (IsLoading) {
<span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>
<span>Überprüfen …</span>
} else {
<span>Weiter</span>
}
</button>
</div>
</div>
</div>
@code {
[Parameter] public string EnvelopeKey { get; set; } = string.Empty;
string AccessCode = string.Empty;
string? ErrorMessage;
bool IsLoading;
async Task OnKeyDownAsync(Microsoft.AspNetCore.Components.Web.KeyboardEventArgs e) {
if (e.Key == "Enter")
await SubmitAsync();
}
async Task SubmitAsync() {
if (string.IsNullOrWhiteSpace(AccessCode)) return;
IsLoading = true;
ErrorMessage = null;
await InvokeAsync(StateHasChanged);
// TODO: API entegrasyonu buraya eklenecek
await Task.Delay(500); // placeholder
IsLoading = false;
ErrorMessage = "Der eingegebene Zugangscode ist ungültig. Bitte versuchen Sie es erneut.";
await InvokeAsync(StateHasChanged);
}
}

View File

@@ -19,8 +19,10 @@
@using EnvelopeGenerator.ReceiverUI.Options
@inject IJSRuntime JSRuntime
@inject IOptions<ApiOptions> AppOptions
@inject NavigationManager Navigation
@inject InMemoryReportStorageWebExtension ReportStorage
@inject EnvelopeGenerator.ReceiverUI.Services.DocumentService DocumentService
@inject EnvelopeGenerator.ReceiverUI.Services.AuthService AuthService
<link href="_content/DevExpress.Blazor.Themes/blazing-berry.bs5.min.css" rel="stylesheet" />
<link href="_content/DevExpress.Blazor.Reporting.Viewer/css/dx-blazor-reporting-components.bs5.css" rel="stylesheet" />
@@ -170,6 +172,14 @@
protected override async Task OnInitializedAsync() {
if (!string.IsNullOrWhiteSpace(EnvelopeKey)) {
var hasAccess = await AuthService.CheckEnvelopeAccessAsync(EnvelopeKey);
if (!hasAccess) {
Navigation.NavigateTo($"/login/{Uri.EscapeDataString(EnvelopeKey)}");
return;
}
}
if (!AppOptions.Value.ForceToUseFakeDocument && !string.IsNullOrWhiteSpace(EnvelopeKey)) {
var (pdfBytes, _) = await DocumentService.GetDocumentAsync(EnvelopeKey);
if (pdfBytes is { Length: > 0 }) {

View File

@@ -15,6 +15,7 @@ builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.
builder.Services.Configure<ApiOptions>(opts =>
builder.Configuration.GetSection(ApiOptions.SectionName).Bind(opts));
builder.Services.AddScoped<EnvelopeGenerator.ReceiverUI.Services.DocumentService>();
builder.Services.AddScoped<EnvelopeGenerator.ReceiverUI.Services.AuthService>();
builder.Services.AddDevExpressWebAssemblyBlazorReportViewer();
builder.Services.AddDevExpressWebAssemblyBlazorPdfViewer();

View File

@@ -0,0 +1,20 @@
using System.Net;
using EnvelopeGenerator.ReceiverUI.Options;
using Microsoft.Extensions.Options;
namespace EnvelopeGenerator.ReceiverUI.Services;
public class AuthService(HttpClient http, IOptions<ApiOptions> apiOptions)
{
private readonly ApiOptions _api = apiOptions.Value;
/// <summary>
/// Checks whether the current user holds a valid receiver token for the given envelope key.
/// Calls GET /api/auth/check/envelope/{envelopeKey}.
/// </summary>
public async Task<bool> CheckEnvelopeAccessAsync(string envelopeKey, CancellationToken cancel = default)
{
var response = await http.GetAsync($"{_api.BaseUrl}/api/auth/check/envelope/{Uri.EscapeDataString(envelopeKey)}", cancel);
return response.StatusCode == HttpStatusCode.OK;
}
}