Browse Source

RHL-004-docs(auth): enhance authentication documentation with branch-level RBAC details and response semantics

Code_Uwe 2 ngày trước cách đây
mục cha
commit
c17a32de3a
1 tập tin đã thay đổi với 115 bổ sung35 xóa
  1. 115 35
      Docs/auth.md

+ 115 - 35
Docs/auth.md

@@ -1,3 +1,13 @@
+<!-- --------------------------------------------------------------------------- -->
+
+<!-- Folder: Docs -->
+
+<!-- File: auth.md -->
+
+<!-- Relative path: Docs/auth.md -->
+
+<!-- --------------------------------------------------------------------------- -->
+
 # Authentication & Authorization
 
 This document describes the authentication and authorization model for the internal delivery note browser.
@@ -7,6 +17,7 @@ The system uses:
 - MongoDB to store users.
 - Cookie-based sessions with a signed JWT payload.
 - Role-aware access control (`branch`, `admin`, `dev`).
+- Branch-level RBAC enforcement for filesystem APIs.
 - Extensible password management and recovery flows.
 
 > NOTE: This document is a living document. As we extend the auth system (sessions, routes, policies, password flows), we will update this file.
@@ -15,9 +26,9 @@ The system uses:
 
 ## 1. Goals & Scope
 
-The main goals of the authentication system are:
+The main goals of the authentication and authorization system are:
 
-- Only authenticated users can access the application.
+- Only authenticated users can access protected backend APIs.
 - Branch users can only see delivery notes for **their own branch**.
 - Admin and dev users can access data across branches.
 - Passwords are never stored in plaintext.
@@ -28,6 +39,7 @@ This document covers:
 
 - User model and roles.
 - Environment variables related to auth.
+- RBAC rules and protected filesystem endpoints.
 - Session payload and cookie configuration.
 - Login and logout endpoints.
 - Planned endpoints for password management and recovery.
@@ -143,6 +155,7 @@ This ensures that sensitive information is not exposed via API responses or logs
 ### 3.4 Role Assignment & User Provisioning
 
 - Users are **created by an admin** (no public self-registration).
+
 - When a user is created:
 
   - `role` is set by the admin.
@@ -192,11 +205,80 @@ This ensures that sensitive information is not exposed via API responses or logs
 
 ---
 
-## 5. Sessions & Cookies
+## 5. Authorization: Branch-Level RBAC
+
+The backend enforces **Role-Based Access Control (RBAC)** on branch-related filesystem APIs.
+
+### 5.1 Response Semantics
+
+- **401 Unauthorized**: no valid session (`getSession()` returns `null`).
+
+  ```json
+  { "error": "Unauthorized" }
+  ```
+
+- **403 Forbidden**: session exists but the user is not allowed to access the requested branch.
+
+  ```json
+  { "error": "Forbidden" }
+  ```
+
+> Note: Some legacy `400`/`500` messages are still returned in German (e.g. missing params, filesystem errors). We may normalize these later.
+
+### 5.2 Permission Helpers
+
+RBAC rules are implemented in `lib/auth/permissions.js`:
+
+- `canAccessBranch(session, branchId)`
+
+  - No session → `false`
+  - `role = "branch"` → `true` only if `session.branchId === branchId`
+  - `role = "admin" | "dev"` → `true` for any branch
+
+- `filterBranchesForSession(session, branchIds)`
+
+  - `role = "branch"` → returns only the user’s own branch (if present)
+  - `role = "admin" | "dev"` → returns all
+
+### 5.3 Protected Filesystem APIs
+
+The following endpoints are protected and must be called only with a valid session:
+
+- `GET /api/branches`
+
+  - Requires session (401 otherwise)
+  - `branch` role: returns only `[session.branchId]`
+  - `admin`/`dev`: returns all branches
+
+- `GET /api/branches/[branch]/years`
+
+- `GET /api/branches/[branch]/[year]/months`
+
+- `GET /api/branches/[branch]/[year]/[month]/days`
+
+  - Requires session (401 otherwise)
+  - Requires branch access (403 if not allowed)
+
+- `GET /api/files?branch=&year=&month=&day=`
+
+  - Requires session (401 otherwise)
+  - Requires branch access (403 if not allowed)
+
+Implementation pattern (high-level):
+
+1. `const session = await getSession()`
+2. If `!session` → return 401
+3. Extract requested branch (`params.branch` or `query.branch`)
+4. If `!canAccessBranch(session, requestedBranch)` → return 403
+5. Proceed with storage access and return data
+
+---
+
+## 6. Sessions & Cookies
 
 Sessions are implemented as signed JWTs stored in HTTP-only cookies.
 
-### 5.1 Session Payload Format
+### 6.1 Session Payload Format
 
 A session payload has the following structure:
 
@@ -218,7 +300,7 @@ A session payload has the following structure:
 
 The `iat` and `exp` fields are managed by the JWT library.
 
-### 5.2 JWT Signing
+### 6.2 JWT Signing
 
 - JWTs are signed using a symmetric secret (`SESSION_SECRET`).
 - Algorithm: `HS256` (HMAC using SHA-256).
@@ -228,7 +310,7 @@ The `iat` and `exp` fields are managed by the JWT library.
   - `SESSION_MAX_AGE_SECONDS = 60 * 60 * 8` (8 hours).
   - Configured in `lib/auth/session.js`.
 
-### 5.3 Cookie Settings
+### 6.3 Cookie Settings
 
 The session token is stored in an HTTP-only cookie with the following properties:
 
@@ -260,9 +342,9 @@ Cookies are written and cleared using Next.js `cookies()` from `next/headers` in
 
 ---
 
-## 6. Core Auth Endpoints
+## 7. Core Auth Endpoints
 
-### 6.1 `POST /api/auth/login`
+### 7.1 `POST /api/auth/login`
 
 **Purpose**
 Authenticate a user using `username` and `password`, create a session, and set the session cookie.
@@ -295,6 +377,7 @@ Authenticate a user using `username` and `password`, create a session, and set t
    - If `username` or `password` is missing or empty → `400 { "error": "Missing username or password" }`.
 
 3. Connect to MongoDB.
+
 4. Look up the user in MongoDB by normalized `username`.
 
    - If no user is found → `401 { "error": "Invalid credentials" }`.
@@ -307,6 +390,7 @@ Authenticate a user using `username` and `password`, create a session, and set t
 6. On success:
 
    - Create a session payload `{ userId, role, branchId }`.
+
    - Call `createSession({ userId, role, branchId })`:
 
      - Signs a JWT with the session payload.
@@ -324,8 +408,6 @@ Authenticate a user using `username` and `password`, create a session, and set t
   }
   ```
 
-  (Session cookie is set in the response headers.)
-
 - `400 Bad Request`:
 
   ```json
@@ -358,7 +440,7 @@ Authenticate a user using `username` and `password`, create a session, and set t
   }
   ```
 
-### 6.2 `GET /api/auth/logout`
+### 7.2 `GET /api/auth/logout`
 
 **Purpose**
 Destroy the current session by clearing the session cookie.
@@ -404,11 +486,11 @@ Logout is **idempotent**:
 
 ---
 
-## 7. Password Management & Recovery (Planned)
+## 8. Password Management & Recovery (Planned)
 
 The database model is already prepared for password management and password recovery flows, but the respective endpoints may be implemented in a separate epic.
 
-### 7.1 Change Password
+### 8.1 Change Password
 
 **Endpoint**
 `POST /api/auth/change-password` (planned)
@@ -445,7 +527,7 @@ Allow logged-in users to change their password by providing the current password
 8. Optionally update a `passwordChangedAt` field if introduced later.
 9. Return `{ "ok": true }`.
 
-### 7.2 Request Password Reset
+### 8.2 Request Password Reset
 
 **Endpoint**
 `POST /api/auth/request-password-reset` (planned)
@@ -465,8 +547,6 @@ Start the "forgot password" flow by sending a reset link to the user's email add
 }
 ```
 
-- The frontend may allow either username or email. The backend resolves it accordingly.
-
 **Planned Behavior**
 
 1. Normalize the identifier (trim + lowercase).
@@ -481,22 +561,17 @@ Start the "forgot password" flow by sending a reset link to the user's email add
 4. If a user is found:
 
    - Generate a secure random token (or a signed token).
-
    - Store it in `passwordResetToken`.
-
    - Set `passwordResetExpiresAt` to a timestamp in the near future (e.g. now + 30 minutes).
-
    - Send an email to `user.email` containing a link like:
 
      ```
      https://<app-domain>/reset-password?token=<passwordResetToken>
      ```
 
-   - The email is sent using a mailer (e.g. `nodemailer`).
+5. Always return `{ "ok": true }`.
 
-5. Always return `{ "ok": true }` to the client, regardless of whether a user was found.
-
-### 7.3 Reset Password
+### 8.3 Reset Password
 
 **Endpoint**
 `POST /api/auth/reset-password` (planned)
@@ -520,8 +595,11 @@ Complete the password reset process using a valid reset token.
 **Planned Behavior**
 
 1. Find user by `passwordResetToken`.
+
 2. If no user is found → return a generic error (e.g. `{ "error": "Invalid or expired token" }`).
+
 3. Check that `passwordResetExpiresAt` is in the future.
+
 4. If the token has expired:
 
    - Return a generic error.
@@ -535,15 +613,17 @@ Complete the password reset process using a valid reset token.
    - Optionally set `mustChangePassword = false`.
 
 6. Optionally invalidate other active sessions if a "global logout on password change" is implemented.
+
 7. Return `{ "ok": true }`.
 
-### 7.4 Email Sending
+### 8.4 Email Sending
 
 Password reset emails will be sent using a mailer library (e.g. `nodemailer`), configured for the environment.
 
 Key points:
 
 - Emails are sent to `user.email`.
+
 - The content includes:
 
   - A short explanation of the password reset process.
@@ -554,12 +634,12 @@ Key points:
 
 ---
 
-## 8. Security Considerations
+## 9. Security Considerations
 
-1. **Never trust client-provided `branchId`.**
+1. **Never trust client-provided branch information.**
 
-   - The effective `branchId` for authorization must always come from the **session payload** (derived from the user record), not from query parameters or request bodies.
-   - Even if routes use `branch` parameters for URL structure, the backend must enforce access based on the `branchId` in the session.
+   - The effective branch authorization is enforced using the **session payload** (`session.branchId`) and RBAC rules.
+   - Even if routes use `branch` parameters for URL structure, the backend enforces branch access based on the session.
 
 2. **Password handling.**
 
@@ -589,13 +669,9 @@ Key points:
 
 ---
 
-## 9. Future Work & Integration
+## 10. Future Work & Integration
 
-- Protect existing filesystem APIs (`/api/branches/*`, `/api/files`, etc.) by:
-
-  - Calling `getSession()` at the start of each route.
-  - Returning `401` if no valid session exists.
-  - Resolving the effective `branchId` from the session and enforcing that branch users only see their own branch.
+- **(Optional)** Add a `middleware.js` for frontend route protection (redirect unauthenticated users to login for certain pages).
 
 - Implement password management endpoints:
 
@@ -604,6 +680,7 @@ Key points:
   - `POST /api/auth/reset-password`
 
 - Integrate an email provider using `nodemailer` or similar for password reset.
+
 - Build frontend UI for:
 
   - Login
@@ -611,4 +688,7 @@ Key points:
   - Change password
   - “Forgot password” / reset password flows.
 
-- Optionally extend auditing and logging for security-relevant events.
+- Optional improvements:
+
+  - Normalize API error messages (language and structure) across all endpoints.
+  - Add auditing for admin actions and branch access.