|
|
@@ -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.
|