refactor: relocate options classes to dedicated Options folders
Move configuration options from Core/DataAccess/DataSync/ExcelIO to dedicated Options folders within each project for better organization. Update all references and tests accordingly.
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
# Encrypted Login Design
|
||||
|
||||
## Overview
|
||||
|
||||
Consolidate login models into Core project and implement RSA-OAEP encryption for login credentials between Blazor WASM client and API.
|
||||
|
||||
## Decisions
|
||||
|
||||
| Decision | Choice | Rationale |
|
||||
|----------|--------|-----------|
|
||||
| Encryption algorithm | RSA-OAEP (SHA-256) | Simple direct encryption, suitable for small payloads |
|
||||
| Key size | 2048-bit | Industry standard, ~20 year security horizon |
|
||||
| Key distribution | Fetch at runtime | Allows key rotation without client redeployment |
|
||||
| Key storage | Auto-generate, persist to file | Self-bootstrapping, no manual key management |
|
||||
| Key expiration | None | Internal enterprise app, manual rotation if needed |
|
||||
|
||||
## New Models (Core)
|
||||
|
||||
All in `JdeScoping.Core/Models/Auth/`:
|
||||
|
||||
```csharp
|
||||
// LoginModel.cs - shared form/request model
|
||||
public class LoginModel
|
||||
{
|
||||
[Required(ErrorMessage = "Username is required")]
|
||||
public string Username { get; set; } = string.Empty;
|
||||
|
||||
[Required(ErrorMessage = "Password is required")]
|
||||
public string Password { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
// LoginResultModel.cs - API response (unencrypted)
|
||||
public record LoginResultModel(
|
||||
bool Success,
|
||||
string? ErrorMessage,
|
||||
UserInfo? User);
|
||||
|
||||
// EncryptedLoginRequest.cs - encrypted payload wrapper
|
||||
public record EncryptedLoginRequest(string EncryptedData);
|
||||
|
||||
// PublicKeyResponse.cs - public key endpoint response
|
||||
public record PublicKeyResponse(string PublicKeyPem);
|
||||
```
|
||||
|
||||
## RSA Key Service (Infrastructure)
|
||||
|
||||
**Interface:** `JdeScoping.Core/Interfaces/IRsaKeyService.cs`
|
||||
```csharp
|
||||
public interface IRsaKeyService
|
||||
{
|
||||
string GetPublicKeyPem();
|
||||
byte[] Decrypt(byte[] ciphertext);
|
||||
}
|
||||
```
|
||||
|
||||
**Implementation:** `JdeScoping.Infrastructure/Security/RsaKeyService.cs`
|
||||
- Auto-generates 2048-bit RSA key on first startup if not exists
|
||||
- Persists private key to configurable file path
|
||||
- Exports public key as PEM format
|
||||
- Decrypts using RSA-OAEP with SHA-256 padding
|
||||
|
||||
## API Changes
|
||||
|
||||
**New endpoint:**
|
||||
```
|
||||
GET /api/auth/public-key → PublicKeyResponse
|
||||
```
|
||||
|
||||
**Modified endpoint:**
|
||||
```
|
||||
POST /api/auth/login
|
||||
Body: EncryptedLoginRequest { EncryptedData: base64 }
|
||||
Response: LoginResultModel
|
||||
```
|
||||
|
||||
Flow:
|
||||
1. Receive base64-encoded encrypted data
|
||||
2. Decode and decrypt with RSA private key
|
||||
3. Deserialize JSON to LoginModel
|
||||
4. Authenticate via IAuthService
|
||||
5. Return LoginResultModel (unencrypted)
|
||||
|
||||
## Client Changes
|
||||
|
||||
**New service:** `ICryptoService` / `CryptoService`
|
||||
- Uses `Blazor.SubtleCrypto` NuGet package
|
||||
- Fetches and caches server public key
|
||||
- Encrypts LoginModel JSON with RSA-OAEP
|
||||
- Returns base64-encoded ciphertext
|
||||
|
||||
**Modified AuthService:**
|
||||
- Injects ICryptoService
|
||||
- Encrypts LoginModel before sending
|
||||
- Sends EncryptedLoginRequest to API
|
||||
|
||||
## Files to Delete
|
||||
|
||||
- `JdeScoping.Api/Models/LoginRequest.cs`
|
||||
- `JdeScoping.Client/Models/LoginModel.cs`
|
||||
|
||||
## Dependencies
|
||||
|
||||
**JdeScoping.Client.csproj:**
|
||||
```xml
|
||||
<PackageReference Include="Blazor.SubtleCrypto" Version="..." />
|
||||
```
|
||||
|
||||
## DI Registration
|
||||
|
||||
**API/Host:**
|
||||
```csharp
|
||||
services.AddSingleton<IRsaKeyService>(sp =>
|
||||
new RsaKeyService(Path.Combine(appDataPath, "rsa-key.bin")));
|
||||
```
|
||||
|
||||
**Client:**
|
||||
```csharp
|
||||
services.AddScoped<ICryptoService, CryptoService>();
|
||||
```
|
||||
|
||||
## Test Coverage
|
||||
|
||||
### RsaKeyServiceTests (Api.Tests)
|
||||
- GetPublicKeyPem returns valid PEM format
|
||||
- Decrypt with valid ciphertext returns plaintext
|
||||
- Constructor loads existing key from file
|
||||
- Constructor generates and persists new key if missing
|
||||
|
||||
### CryptoServiceTests (Client.Tests)
|
||||
- EncryptLoginAsync returns valid base64
|
||||
- EncryptLoginAsync caches public key (single HTTP call)
|
||||
|
||||
### Integration (optional)
|
||||
- Full roundtrip: encrypt on client → decrypt on API → authenticate
|
||||
Reference in New Issue
Block a user