Просмотр исходного кода

RHL-003-docs(auth): update authentication documentation for clarity and completeness

Code_Uwe 1 месяц назад
Родитель
Сommit
b19950f085
1 измененных файлов с 214 добавлено и 151 удалено
  1. 214 151
      Docs/auth.md

+ 214 - 151
Docs/auth.md

@@ -1,9 +1,3 @@
-Perfekt, dann machen wir jetzt einmal eine **saubere, komplette `docs/auth.md`** mit allem, was wir bisher besprochen haben – inklusive des geplanten Epics **“Password management & recovery”**.
-
-Alles in Englisch, strukturiert, so dass du es 1:1 als Datei ablegen kannst.
-
----
-
 # Authentication & Authorization
 # Authentication & Authorization
 
 
 This document describes the authentication and authorization model for the internal delivery note browser.
 This document describes the authentication and authorization model for the internal delivery note browser.
@@ -13,7 +7,7 @@ The system uses:
 - MongoDB to store users.
 - MongoDB to store users.
 - Cookie-based sessions with a signed JWT payload.
 - Cookie-based sessions with a signed JWT payload.
 - Role-aware access control (`branch`, `admin`, `dev`).
 - Role-aware access control (`branch`, `admin`, `dev`).
-- Future extensions for password management and recovery.
+- 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.
 > NOTE: This document is a living document. As we extend the auth system (sessions, routes, policies, password flows), we will update this file.
 
 
@@ -33,6 +27,7 @@ The main goals of the authentication system are:
 This document covers:
 This document covers:
 
 
 - User model and roles.
 - User model and roles.
+- Environment variables related to auth.
 - Session payload and cookie configuration.
 - Session payload and cookie configuration.
 - Login and logout endpoints.
 - Login and logout endpoints.
 - Planned endpoints for password management and recovery.
 - Planned endpoints for password management and recovery.
@@ -40,11 +35,31 @@ This document covers:
 
 
 ---
 ---
 
 
-## 2. User Model
+## 2. Environment Variables
+
+The authentication system depends on the following environment variables:
+
+- `SESSION_SECRET` (required)
+
+  - Strong, random string used to sign and verify JWT session tokens.
+  - Must be kept secret and should differ between environments (dev, staging, prod).
+
+Example for `.env.local.example`:
+
+```env
+# Session / JWT
+SESSION_SECRET=change-me-to-a-long-random-string
+```
+
+If `SESSION_SECRET` is not set, session utilities will throw an error.
+
+---
+
+## 3. User Model
 
 
 Users are stored in MongoDB using the `User` collection.
 Users are stored in MongoDB using the `User` collection.
 
 
-### 2.1 Fields
+### 3.1 Fields
 
 
 - **username** (`String`, required, unique, lowercased)
 - **username** (`String`, required, unique, lowercased)
 
 
@@ -56,7 +71,8 @@ Users are stored in MongoDB using the `User` collection.
 - **email** (`String`, required, unique, lowercased)
 - **email** (`String`, required, unique, lowercased)
 
 
   - Contact address used for password recovery and notifications.
   - Contact address used for password recovery and notifications.
-  - Typically the branch email address for branch accounts, or a personal email address for individual users.
+  - For branch accounts, this is typically the branch email address.
+  - For individual accounts, this can be the personal work email.
   - Stored in lowercase.
   - Stored in lowercase.
   - Unique per user.
   - Unique per user.
 
 
@@ -100,20 +116,22 @@ Users are stored in MongoDB using the `User` collection.
   - Timestamp when the user record was created.
   - Timestamp when the user record was created.
 
 
 - **updatedAt** (`Date`, auto-generated)
 - **updatedAt** (`Date`, auto-generated)
+
   - Timestamp when the user record was last updated.
   - Timestamp when the user record was last updated.
 
 
-### 2.2 Validation Rules & Invariants
+### 3.2 Validation Rules & Invariants
 
 
 - `username` must be unique and is stored in lowercase.
 - `username` must be unique and is stored in lowercase.
 - `email` must be unique and is stored in lowercase.
 - `email` must be unique and is stored in lowercase.
 - `passwordHash` must be present for all users.
 - `passwordHash` must be present for all users.
 - When `role = "branch"`, `branchId` must be a non-empty string.
 - When `role = "branch"`, `branchId` must be a non-empty string.
 - For `role = "admin"` and `role = "dev"`, `branchId` is optional and usually `null`.
 - For `role = "admin"` and `role = "dev"`, `branchId` is optional and usually `null`.
-- `passwordResetToken` and `passwordResetExpiresAt` must be consistent:
+- `passwordResetToken` and `passwordResetExpiresAt` should be consistent:
+
   - If one is set, the other should also be set.
   - If one is set, the other should also be set.
   - Once a reset is completed or expired, both should be cleared.
   - Once a reset is completed or expired, both should be cleared.
 
 
-### 2.3 Serialization Rules
+### 3.3 Serialization Rules
 
 
 When converting `User` documents to JSON or plain objects (e.g. in API responses), the following fields must be hidden:
 When converting `User` documents to JSON or plain objects (e.g. in API responses), the following fields must be hidden:
 
 
@@ -122,55 +140,63 @@ When converting `User` documents to JSON or plain objects (e.g. in API responses
 
 
 This ensures that sensitive information is not exposed via API responses or logs.
 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.
+  - `branchId` is set by the admin and cannot be chosen or changed by the user.
+
+- For branch accounts, we typically create one or more users per branch with:
+
+  - `role = "branch"`
+  - `branchId` set to the respective branch identifier (e.g. `"NL01"`).
+
+- The user is provided with an initial password and is encouraged (or forced via `mustChangePassword`) to change it after the first login.
+
 ---
 ---
 
 
-## 3. Roles
+## 4. Roles
 
 
-### 3.1 `branch`
+### 4.1 `branch`
 
 
 - Represents a user who belongs to a specific branch/location.
 - Represents a user who belongs to a specific branch/location.
 - Must have a valid `branchId` (e.g. `"NL01"`).
 - Must have a valid `branchId` (e.g. `"NL01"`).
 - Intended access pattern (high-level):
 - Intended access pattern (high-level):
+
   - Can only access delivery notes for their own branch.
   - Can only access delivery notes for their own branch.
   - Cannot access other branches.
   - Cannot access other branches.
   - No global configuration or system-wide administration.
   - No global configuration or system-wide administration.
 
 
-### 3.2 `admin`
+### 4.2 `admin`
 
 
 - System administrator.
 - System administrator.
 - Typically not bound to any single branch (`branchId = null`).
 - Typically not bound to any single branch (`branchId = null`).
 - Intended access pattern (high-level):
 - Intended access pattern (high-level):
+
   - Can access delivery notes across all branches.
   - Can access delivery notes across all branches.
   - Can perform user administration (create/update users).
   - Can perform user administration (create/update users).
   - Can perform configuration-level changes.
   - Can perform configuration-level changes.
 
 
-### 3.3 `dev`
+### 4.3 `dev`
 
 
 - Development/engineering account.
 - Development/engineering account.
 - Used for debugging, maintenance, and operational tooling.
 - Used for debugging, maintenance, and operational tooling.
 - Typically not bound to any single branch (`branchId = null`).
 - Typically not bound to any single branch (`branchId = null`).
 - Intended access pattern (high-level):
 - Intended access pattern (high-level):
+
   - Full or near-full access to the system.
   - Full or near-full access to the system.
   - Can be used in development/staging environments.
   - Can be used in development/staging environments.
   - Production use should be limited and auditable.
   - Production use should be limited and auditable.
 
 
-### 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.
-  - `branchId` is set by the admin and cannot be chosen by the user.
-- For branch accounts, we typically create one or more users per branch with:
-  - `role = "branch"`
-  - `branchId` set to the respective branch identifier.
-
 ---
 ---
 
 
-## 4. Sessions & Cookies
+## 5. Sessions & Cookies
 
 
 Sessions are implemented as signed JWTs stored in HTTP-only cookies.
 Sessions are implemented as signed JWTs stored in HTTP-only cookies.
 
 
-### 4.1 Session Payload Format
+### 5.1 Session Payload Format
 
 
 A session payload has the following structure:
 A session payload has the following structure:
 
 
@@ -192,51 +218,60 @@ A session payload has the following structure:
 
 
 The `iat` and `exp` fields are managed by the JWT library.
 The `iat` and `exp` fields are managed by the JWT library.
 
 
-### 4.2 JWT Signing
-
-- The JWT is signed using a symmetric secret (`SESSION_SECRET`).
-
-- Recommended algorithm: `HS256` (HMAC using SHA-256).
-
-- The secret is defined via environment variable:
-
-  - `SESSION_SECRET` (required, strong random string)
+### 5.2 JWT Signing
 
 
-- Token lifetime (example):
+- JWTs are signed using a symmetric secret (`SESSION_SECRET`).
+- Algorithm: `HS256` (HMAC using SHA-256).
+- Secret is defined via environment variable `SESSION_SECRET`.
+- Token lifetime:
 
 
-  - Access token / session lifetime: e.g. 8 hours (configurable).
+  - `SESSION_MAX_AGE_SECONDS = 60 * 60 * 8` (8 hours).
+  - Configured in `lib/auth/session.js`.
 
 
-### 4.3 Cookie Settings
+### 5.3 Cookie Settings
 
 
-The session token is stored in an HTTP-only cookie, for example:
+The session token is stored in an HTTP-only cookie with the following properties:
 
 
-- **Cookie name**: `auth_session` (TBD, but must be consistent across backend/frontend)
+- **Cookie name**: `auth_session`
 - **Attributes**:
 - **Attributes**:
 
 
   - `httpOnly: true`
   - `httpOnly: true`
   - `secure: process.env.NODE_ENV === "production"`
   - `secure: process.env.NODE_ENV === "production"`
-  - `sameSite: "lax"` (or stricter, e.g. `"strict"` if acceptable)
+  - `sameSite: "lax"`
   - `path: "/"` (cookie is sent for all paths)
   - `path: "/"` (cookie is sent for all paths)
-  - `maxAge`: matches or slightly exceeds the JWT `exp` lifetime.
+  - `maxAge: 8 hours` (matching `SESSION_MAX_AGE_SECONDS`)
 
 
-Cookies are written and cleared using Next.js `NextResponse` helpers in API routes.
+Cookies are written and cleared using Next.js `cookies()` from `next/headers` inside `lib/auth/session.js`:
 
 
----
+- `createSession({ userId, role, branchId })`:
+
+  - Creates and signs a JWT.
+  - Sets the `auth_session` cookie.
+
+- `getSession()`:
 
 
-## 5. Auth Endpoints (Core)
+  - Reads the `auth_session` cookie.
+  - Verifies the JWT and returns `{ userId, role, branchId }` or `null`.
+  - If the token is invalid or expired, clears the cookie and returns `null`.
 
 
-The core auth endpoints handle login and logout using the session cookie.
+- `destroySession()`:
+
+  - Clears the `auth_session` cookie by setting an empty value with `maxAge: 0`.
+
+---
 
 
-### 5.1 `POST /api/auth/login`
+## 6. Core Auth Endpoints
 
 
-**Purpose:**
+### 6.1 `POST /api/auth/login`
+
+**Purpose**
 Authenticate a user using `username` and `password`, create a session, and set the session cookie.
 Authenticate a user using `username` and `password`, create a session, and set the session cookie.
 
 
-**Method & URL:**
+**Method & URL**
 
 
 - `POST /api/auth/login`
 - `POST /api/auth/login`
 
 
-**Request Body (JSON):**
+**Request Body (JSON)**
 
 
 ```json
 ```json
 {
 {
@@ -248,101 +283,148 @@ Authenticate a user using `username` and `password`, create a session, and set t
 - `username` (string): Login name (case-insensitive).
 - `username` (string): Login name (case-insensitive).
 - `password` (string): Plaintext password entered by the user.
 - `password` (string): Plaintext password entered by the user.
 
 
-**Behavior:**
+**Behavior**
+
+1. Normalize `username`:
+
+   - Trim whitespace and convert to lowercase.
+
+2. Parse and validate request body:
+
+   - If body is missing or invalid JSON → `400 { "error": "Invalid request body" }`.
+   - 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" }`.
+
+5. Verify the password using bcrypt:
+
+   - Compare provided `password` with `user.passwordHash`.
+   - If password does not match → `401 { "error": "Invalid credentials" }`.
 
 
-1. Normalize `username` (trim + lowercase).
-2. Look up the user in MongoDB by `username`.
-3. If user not found → return `401` with `{ "error": "Invalid credentials" }`.
-4. Verify the password using bcrypt (compare with `passwordHash`).
-5. If password does not match → return `401` with `{ "error": "Invalid credentials" }`.
 6. On success:
 6. On success:
 
 
    - Create a session payload `{ userId, role, branchId }`.
    - Create a session payload `{ userId, role, branchId }`.
-   - Sign a JWT using `SESSION_SECRET`.
-   - Set the JWT in the `auth_session` HTTP-only cookie.
-   - Return `200` with `{ "ok": true }`.
+   - Call `createSession({ userId, role, branchId })`:
 
 
-**Successful Response (200):**
+     - Signs a JWT with the session payload.
+     - Sets the `auth_session` HTTP-only cookie.
 
 
-```json
-{
-	"ok": true
-}
-```
+   - Return `200 { "ok": true }`.
+
+**Possible Responses**
+
+- `200 OK`:
 
 
-(Session cookie is set in the response headers.)
+  ```json
+  {
+  	"ok": true
+  }
+  ```
 
 
-**Error Responses:**
+  (Session cookie is set in the response headers.)
 
 
 - `400 Bad Request`:
 - `400 Bad Request`:
 
 
-  - Missing `username` or `password`.
-  - Invalid body format.
+  ```json
+  {
+  	"error": "Invalid request body"
+  }
+  ```
+
+  or
+
+  ```json
+  {
+  	"error": "Missing username or password"
+  }
+  ```
 
 
 - `401 Unauthorized`:
 - `401 Unauthorized`:
 
 
-  - User not found.
-  - Password does not match.
+  ```json
+  {
+  	"error": "Invalid credentials"
+  }
+  ```
 
 
 - `500 Internal Server Error`:
 - `500 Internal Server Error`:
 
 
-  - Unexpected server-side error.
+  ```json
+  {
+  	"error": "Internal server error"
+  }
+  ```
 
 
-### 5.2 `GET /api/auth/logout`
+### 6.2 `GET /api/auth/logout`
 
 
-**Purpose:**
+**Purpose**
 Destroy the current session by clearing the session cookie.
 Destroy the current session by clearing the session cookie.
 
 
-**Method & URL:**
+**Method & URL**
 
 
 - `GET /api/auth/logout`
 - `GET /api/auth/logout`
 
 
-**Request:**
+**Request**
 
 
 - No request body.
 - No request body.
 - Uses the current session cookie (if present).
 - Uses the current session cookie (if present).
 
 
-**Behavior:**
+**Behavior**
+
+1. Call `destroySession()`:
 
 
-1. Clear the `auth_session` cookie (e.g. by setting an expired cookie).
-2. Return `200` with `{ "ok": true }`.
+   - Clears the `auth_session` cookie by setting an empty value with `maxAge: 0`.
+
+2. Return `200 { "ok": true }`.
 
 
 Logout is **idempotent**:
 Logout is **idempotent**:
 
 
 - If the cookie does not exist, the endpoint still returns `{ "ok": true }`.
 - If the cookie does not exist, the endpoint still returns `{ "ok": true }`.
 
 
-**Response (200):**
+**Responses**
 
 
-```json
-{
-	"ok": true
-}
-```
+- `200 OK`:
+
+  ```json
+  {
+  	"ok": true
+  }
+  ```
+
+- `500 Internal Server Error` (if `destroySession` throws):
+
+  ```json
+  {
+  	"error": "Internal server error"
+  }
+  ```
 
 
 ---
 ---
 
 
-## 6. Password Management & Recovery (Planned)
+## 7. Password Management & Recovery (Planned)
 
 
-This section describes the **planned** password management and recovery flows.
-The database model is already prepared for these scenarios, even if the endpoints are not yet implemented.
+The database model is already prepared for password management and password recovery flows, but the respective endpoints may be implemented in a separate epic.
 
 
-### 6.1 Change Password
+### 7.1 Change Password
 
 
-**Endpoint:** `POST /api/auth/change-password`
-**Status:** Planned.
+**Endpoint**
+`POST /api/auth/change-password` (planned)
 
 
-**Purpose:**
+**Purpose**
 Allow logged-in users to change their password by providing the current password and a new password.
 Allow logged-in users to change their password by providing the current password and a new password.
 
 
-**Method & URL:**
+**Method & URL**
 
 
 - `POST /api/auth/change-password`
 - `POST /api/auth/change-password`
 
 
-**Authentication:**
+**Authentication**
 
 
 - Requires a valid session (user must be logged in).
 - Requires a valid session (user must be logged in).
 
 
-**Request Body (JSON):**
+**Request Body (JSON)**
 
 
 ```json
 ```json
 {
 {
@@ -351,39 +433,31 @@ Allow logged-in users to change their password by providing the current password
 }
 }
 ```
 ```
 
 
-**Behavior (planned):**
+**Planned Behavior**
 
 
-1. Extract `userId` from the current session.
+1. Extract `userId` from the current session (`getSession()`).
 2. Load user from MongoDB.
 2. Load user from MongoDB.
 3. Verify `currentPassword` against `passwordHash` using bcrypt.
 3. Verify `currentPassword` against `passwordHash` using bcrypt.
-4. If verification fails → return `400` or `401` with a generic error (e.g. `{ "error": "Invalid password" }`).
+4. If verification fails → return a generic error (e.g. `400` or `401` with `{ "error": "Invalid password" }`).
 5. Hash `newPassword` with bcrypt.
 5. Hash `newPassword` with bcrypt.
 6. Update `passwordHash` in the database.
 6. Update `passwordHash` in the database.
 7. Optionally set `mustChangePassword = false`.
 7. Optionally set `mustChangePassword = false`.
 8. Optionally update a `passwordChangedAt` field if introduced later.
 8. Optionally update a `passwordChangedAt` field if introduced later.
 9. Return `{ "ok": true }`.
 9. Return `{ "ok": true }`.
 
 
-**Response (200):**
-
-```json
-{
-	"ok": true
-}
-```
-
-### 6.2 Request Password Reset
+### 7.2 Request Password Reset
 
 
-**Endpoint:** `POST /api/auth/request-password-reset`
-**Status:** Planned.
+**Endpoint**
+`POST /api/auth/request-password-reset` (planned)
 
 
-**Purpose:**
+**Purpose**
 Start the "forgot password" flow by sending a reset link to the user's email address.
 Start the "forgot password" flow by sending a reset link to the user's email address.
 
 
-**Method & URL:**
+**Method & URL**
 
 
 - `POST /api/auth/request-password-reset`
 - `POST /api/auth/request-password-reset`
 
 
-**Request Body (JSON):**
+**Request Body (JSON)**
 
 
 ```json
 ```json
 {
 {
@@ -393,11 +467,11 @@ 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.
 - The frontend may allow either username or email. The backend resolves it accordingly.
 
 
-**Behavior (planned):**
+**Planned Behavior**
 
 
 1. Normalize the identifier (trim + lowercase).
 1. Normalize the identifier (trim + lowercase).
 
 
-2. Try to find a user by `email` (and optionally by `username` if needed).
+2. Try to find a user by `email` (and optionally by `username`).
 
 
 3. If no user is found:
 3. If no user is found:
 
 
@@ -422,27 +496,19 @@ Start the "forgot password" flow by sending a reset link to the user's email add
 
 
 5. Always return `{ "ok": true }` to the client, regardless of whether a user was found.
 5. Always return `{ "ok": true }` to the client, regardless of whether a user was found.
 
 
-**Response (200):**
-
-```json
-{
-	"ok": true
-}
-```
-
-### 6.3 Reset Password
+### 7.3 Reset Password
 
 
-**Endpoint:** `POST /api/auth/reset-password`
-**Status:** Planned.
+**Endpoint**
+`POST /api/auth/reset-password` (planned)
 
 
-**Purpose:**
+**Purpose**
 Complete the password reset process using a valid reset token.
 Complete the password reset process using a valid reset token.
 
 
-**Method & URL:**
+**Method & URL**
 
 
 - `POST /api/auth/reset-password`
 - `POST /api/auth/reset-password`
 
 
-**Request Body (JSON):**
+**Request Body (JSON)**
 
 
 ```json
 ```json
 {
 {
@@ -451,12 +517,16 @@ Complete the password reset process using a valid reset token.
 }
 }
 ```
 ```
 
 
-**Behavior (planned):**
+**Planned Behavior**
 
 
 1. Find user by `passwordResetToken`.
 1. Find user by `passwordResetToken`.
 2. If no user is found → return a generic error (e.g. `{ "error": "Invalid or expired token" }`).
 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.
 3. Check that `passwordResetExpiresAt` is in the future.
-4. If the token has expired → return a generic error and clear token/expiry fields.
+4. If the token has expired:
+
+   - Return a generic error.
+   - Clear `passwordResetToken` and `passwordResetExpiresAt`.
+
 5. If the token is valid:
 5. If the token is valid:
 
 
    - Hash `newPassword` with bcrypt.
    - Hash `newPassword` with bcrypt.
@@ -467,15 +537,7 @@ Complete the password reset process using a valid reset token.
 6. Optionally invalidate other active sessions if a "global logout on password change" is implemented.
 6. Optionally invalidate other active sessions if a "global logout on password change" is implemented.
 7. Return `{ "ok": true }`.
 7. Return `{ "ok": true }`.
 
 
-**Response (200):**
-
-```json
-{
-	"ok": true
-}
-```
-
-### 6.4 Email Sending
+### 7.4 Email Sending
 
 
 Password reset emails will be sent using a mailer library (e.g. `nodemailer`), configured for the environment.
 Password reset emails will be sent using a mailer library (e.g. `nodemailer`), configured for the environment.
 
 
@@ -492,7 +554,7 @@ Key points:
 
 
 ---
 ---
 
 
-## 7. Security Considerations
+## 8. Security Considerations
 
 
 1. **Never trust client-provided `branchId`.**
 1. **Never trust client-provided `branchId`.**
 
 
@@ -527,25 +589,26 @@ Key points:
 
 
 ---
 ---
 
 
-## 8. Open Points & Future Work
+## 9. Future Work & Integration
 
 
-- Implement the session utility (`lib/auth/session.js`) with:
+- Protect existing filesystem APIs (`/api/branches/*`, `/api/files`, etc.) by:
 
 
-  - `createSession({ userId, role, branchId })`
-  - `getSession()`
-  - `destroySession()`
+  - 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.
 
 
-- Implement the login and logout endpoints as described above.
 - Implement password management endpoints:
 - Implement password management endpoints:
 
 
   - `POST /api/auth/change-password`
   - `POST /api/auth/change-password`
   - `POST /api/auth/request-password-reset`
   - `POST /api/auth/request-password-reset`
   - `POST /api/auth/reset-password`
   - `POST /api/auth/reset-password`
 
 
-- Implement email sending for password reset using `nodemailer` or similar.
-- Implement a UI for:
+- Integrate an email provider using `nodemailer` or similar for password reset.
+- Build frontend UI for:
 
 
   - Login
   - Login
   - Logout
   - Logout
   - Change password
   - Change password
   - “Forgot password” / reset password flows.
   - “Forgot password” / reset password flows.
+
+- Optionally extend auditing and logging for security-relevant events.