Finishes functional design for unit 0 (back-end changes before front-end work)
This commit is contained in:
+66
@@ -0,0 +1,66 @@
|
||||
# Business Rules — Unit 0: Backend Prerequisites
|
||||
|
||||
## BR-U0-01: CORS Origin Validation
|
||||
- The CORS policy MUST only allow origins listed in `Cors:AllowedOrigins` from configuration
|
||||
- An empty `AllowedOrigins` array in production means no SPA origin is allowed (safe default)
|
||||
- `AllowCredentials()` MUST be set — required for the browser to send the httpOnly cookie
|
||||
- `AllowAnyMethod()` and `AllowAnyHeader()` are used to avoid CORS issues for future endpoints
|
||||
- The CORS middleware MUST be registered BEFORE `UseAuthentication` and `UseAuthorization` in the pipeline
|
||||
|
||||
## BR-U0-02: Refresh Token Cookie — Set Rules
|
||||
- Cookie name: `refreshToken`
|
||||
- Cookie path: `/api/v1/auth` — scoped to auth endpoints only; not sent with other API calls
|
||||
- `HttpOnly = true` — ALWAYS, no exceptions
|
||||
- `Secure = request.IsHttps` — adapts to the current request protocol (true in production, false in local HTTP)
|
||||
- `SameSite = Strict` — cookie only sent when request originates from the exact same site
|
||||
- Cookie is set on BOTH `Login` and `Refresh` responses (token rotation)
|
||||
- Cookie MUST NOT be set on failed auth attempts
|
||||
|
||||
## BR-U0-03: Refresh Token Cookie — Clear Rules
|
||||
- On `Revoke`: Set-Cookie with the same name/path but `Expires = DateTime.UnixEpoch` (epoch = effectively deleted)
|
||||
- On failed refresh (invalid/expired token): Clear the cookie in the error response
|
||||
- Cookie clearing MUST use the exact same `Path` as cookie setting (`/api/v1/auth`)
|
||||
|
||||
## BR-U0-04: HTTP Response Body — Login and Refresh
|
||||
The following fields MUST be returned in the response body:
|
||||
- `accessToken` (string) — JWT bearer token
|
||||
- `expiresAt` (ISO datetime) — access token expiry
|
||||
- `user.id` (Guid)
|
||||
- `user.email` (string)
|
||||
- `user.name` (string) — `ApplicationUser.DisplayName ?? ApplicationUser.Email`
|
||||
- `user.role` (string) — the user's primary role name (Owner / Admin / User)
|
||||
- `user.isActive` (bool)
|
||||
|
||||
The `refreshToken` MUST NOT appear in the response body.
|
||||
|
||||
## BR-U0-05: Revoke Endpoint Authorization
|
||||
- `[Authorize]` is REMOVED from the `Revoke` endpoint
|
||||
- If the refresh token cookie is present: revoke it and clear the cookie
|
||||
- If the refresh token cookie is absent: no-op, return `204 No Content` (idempotent logout)
|
||||
- This allows logout to succeed even when the access token has already expired
|
||||
|
||||
## BR-U0-06: Refresh Endpoint — Input
|
||||
- The `RefreshTokenRequest` body parameter is REMOVED
|
||||
- The refresh token is read exclusively from `Request.Cookies["refreshToken"]`
|
||||
- If the cookie is absent: return `401 Unauthorized` with a generic error message
|
||||
- The existing `accessToken` validation logic in `IAuthService.RefreshTokenAsync` can be relaxed or the signature adapted (see code generation for details)
|
||||
|
||||
## BR-U0-07: DisplayName Field
|
||||
- `ApplicationUser.DisplayName` is nullable (`string?`)
|
||||
- Maximum length: 100 characters
|
||||
- Default value: `NULL` for existing users
|
||||
- `name` in the response is computed as: `user.DisplayName ?? user.Email`
|
||||
- On new user creation via invitation (`POST /users/complete-setup`): the `DisplayName` is set from the form field
|
||||
- On owner creation (`POST /setup/owner`): `DisplayName` defaults to `NULL` (falls back to email)
|
||||
|
||||
## BR-U0-08: Migration
|
||||
- A new EF Core Code-First migration is required named e.g. `AddDisplayNameToApplicationUser`
|
||||
- The migration adds `DisplayName nvarchar(100) NULL` to the `AspNetUsers` table
|
||||
- All existing users receive `NULL` as their `DisplayName` (they see their email as display name)
|
||||
|
||||
## BR-U0-09: Security Baseline Compliance (SECURITY-12)
|
||||
- httpOnly cookie prevents XSS token theft ✅
|
||||
- `Secure = request.IsHttps` ensures the cookie is only sent over HTTPS in production ✅
|
||||
- `SameSite=Strict` prevents CSRF attacks on the refresh endpoint ✅
|
||||
- No credentials (tokens) in localStorage or response body ✅
|
||||
- Session invalidated on logout (token revoked + cookie cleared) ✅
|
||||
Reference in New Issue
Block a user