# Acceptance Criteria Derived from validation rules, test assertions, configuration limits, and health check patterns found in the codebase. ## Authentication | # | Criterion | Threshold | Source | |---|-----------|-----------|--------| | AC-1 | Login with valid credentials returns a JWT token | Token is non-empty string | `Program.cs` `/login` endpoint | | AC-2 | Login with unknown email returns error code 10 | HTTP 409, `ErrorCode: 10` | `UserService.ValidateUser` throws `NoEmailFound` | | AC-3 | Login with wrong password returns error code 30 | HTTP 409, `ErrorCode: 30` | `UserService.ValidateUser` throws `WrongPassword` | | AC-4 | JWT token expires after configured hours | Token `exp` claim = now + `TokenLifetimeHours` | `AuthService.CreateToken`, default 4 hours | | AC-5 | JWT token contains user ID, email, and role claims | Claims: `NameIdentifier`, `Name`, `Role` | `AuthService.CreateToken` | ## User Management | # | Criterion | Threshold | Source | |---|-----------|-----------|--------| | AC-6 | Registration rejects email < 8 characters | Validation error, code `EmailLengthIncorrect` | `RegisterUserValidator` | | AC-7 | Registration rejects invalid email format | Validation error, code `WrongEmail` | `RegisterUserValidator` | | AC-8 | Registration rejects password < 8 characters | Validation error, code `PasswordLengthIncorrect` | `RegisterUserValidator` | | AC-9 | Registration rejects duplicate email | HTTP 409, `ErrorCode: 20` (EmailExists) | `UserService.RegisterUser` | | AC-10 | Password is stored as SHA-384 hash, never plaintext | `PasswordHash` column contains Base64 of SHA-384 | `Security.ToHash()` | | AC-11 | User listing supports optional email and role filters | Filters applied via `WhereIf` | `UserService.GetUsers` | | AC-12 | Only ApiAdmin role can create, list, modify, or delete users | Endpoints require `apiAdminPolicy` | `Program.cs` authorization | ## Hardware Binding | # | Criterion | Threshold | Source | |---|-----------|-----------|--------| | AC-13 | First hardware check stores the hardware fingerprint | `hardware` column updated from null to provided string | `UserService.CheckHardwareHash` | | AC-14 | Subsequent hardware checks compare hash of provided hardware against stored | Hash comparison via `Security.GetHWHash` | `UserService.CheckHardwareHash` | | AC-15 | Hardware mismatch returns error code 40 | HTTP 409, `ErrorCode: 40` (HardwareIdMismatch) | `UserService.CheckHardwareHash` | | AC-16 | Admin can reset hardware by setting it to null | `PUT /users/hardware/set` with null hardware | `UserService.UpdateHardware` | ## Resource Management | # | Criterion | Threshold | Source | |---|-----------|-----------|--------| | AC-17 | File upload supports up to 200 MB | Kestrel `MaxRequestBodySize = 209715200` | `Program.cs` | | AC-18 | Uploaded file is saved to configured resource folder | File written to `ResourcesConfig.ResourcesFolder` | `ResourcesService.SaveResource` | | AC-19 | Resource download returns AES-256-CBC encrypted stream | Encryption via `Security.EncryptTo` | `ResourcesService.GetEncryptedResource` | | AC-20 | Encrypted resource can be decrypted with same key | Round-trip encrypt/decrypt preserves data | `SecurityTest.EncryptDecryptTest` ✓ | | AC-21 | Large files (hundreds of MB) can be encrypted/decrypted | Round-trip works for ~400 MB files | `SecurityTest.EncryptDecryptLargeFileTest` ✓ | | AC-22 | Missing file upload returns error code 60 | HTTP 409, `ErrorCode: 60` (NoFileProvided) | `ResourcesService.SaveResource` | | AC-23 | Installer download returns latest `AzaionSuite.Iterative*` file | Scans installer folder, returns first match | `ResourcesService.GetInstaller` | | AC-24 | Only ApiAdmin can clear resource folders | `POST /resources/clear` requires `apiAdminPolicy` | `Program.cs` | ## API Behavior | # | Criterion | Threshold | Source | |---|-----------|-----------|--------| | AC-25 | Business errors return HTTP 409 with JSON `{ErrorCode, Message}` | Handled by `BusinessExceptionHandler` | `BusinessExceptionHandler.TryHandleAsync` | | AC-26 | Swagger UI available in Development environment | `app.UseSwagger()` conditional on `IsDevelopment` | `Program.cs` | | AC-27 | Root URL redirects to /swagger | URL rewrite rule | `Program.cs` | | AC-28 | CORS allows requests from admin.azaion.com | Origins: `https://admin.azaion.com`, `http://admin.azaion.com` | `Program.cs` |