auth.md 6.0 KB

Authentication & Authorization

This document describes the authentication and authorization model for the internal delivery note browser.

The system uses:

  • MongoDB to store users (via Mongoose models).
  • Cookie-based sessions with a signed JWT payload.
  • Role-aware access control (branch, admin, dev).
  • Branch-level RBAC enforcement for filesystem-related APIs.

NOTE: This document is a living document. As we extend the auth system (sessions, routes, policies, password flows), we will update this file.


1. Goals & Scope

The main goals of the authentication and authorization system are:

  • 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.
  • Sessions are stored in signed JWTs in HTTP-only cookies.

This document covers:

  • Environment variables related to auth.
  • Roles and RBAC rules.
  • Session payload and cookie configuration.
  • Login and logout endpoints.

2. Environment & Configuration

2.1 Required variables

Auth depends on the following environment variables:

  • SESSION_SECRET (required)

    • Strong, random string used to sign and verify JWT session tokens.
    • Minimum length: 32 characters.
    • Must be kept secret.
    • Should differ between environments (dev/staging/prod).

Auth endpoints also require DB connectivity:

  • MONGODB_URI (required)

2.2 Optional variables

  • SESSION_COOKIE_SECURE (optional)

    • Overrides the Secure cookie flag.
    • Allowed values: true or false.

Default behavior:

  • Secure cookie is enabled when NODE_ENV === "production".

Local HTTP testing (e.g. http://localhost:3000 with Docker + next start):

  • Set SESSION_COOKIE_SECURE=false in your local .env.docker.

Staging/Production:

  • Prefer HTTPS.
  • Keep SESSION_COOKIE_SECURE unset (or true) and run the app behind TLS.

If the application is served over plain HTTP (no TLS), many clients will not send Secure cookies back. In that case, logins will appear to “work” (Set-Cookie is present), but subsequent requests will still be unauthenticated.

2.3 Fail-fast environment validation

The repo provides centralized env validation:

  • lib/config/validateEnv.js validates required env vars and basic sanity checks.
  • scripts/validate-env.mjs runs validation against process.env.

In Docker, run validation before starting the server:

node scripts/validate-env.mjs && npm run start

3. Roles

3.1 branch

  • Represents a user who belongs to a specific branch/location.
  • Must have a valid branchId (e.g. "NL01").
  • Intended access pattern:

    • Can only access delivery notes for their own branch.
    • Cannot access other branches.

3.2 admin

  • Administrator account.
  • Typically not bound to any single branch (branchId = null).
  • Intended access pattern:

    • Can access delivery notes across all branches.

3.3 dev

  • Development/engineering account.
  • Typically not bound to any single branch (branchId = null).
  • Intended access pattern:

    • Full or near-full access.

4. Authorization: Branch-Level RBAC

RBAC is enforced on branch-related filesystem APIs.

4.1 Response semantics

Error responses use the standardized API error payload:

{
	"error": {
		"message": "Human readable message",
		"code": "SOME_MACHINE_CODE",
		"details": {}
	}
}
  • 401 Unauthorized: no valid session (getSession() returns null).

    { "error": { "message": "Unauthorized", "code": "AUTH_UNAUTHENTICATED" } }
    
  • 403 Forbidden: session exists but the user is not allowed to access the requested branch.

    { "error": { "message": "Forbidden", "code": "AUTH_FORBIDDEN_BRANCH" } }
    

4.2 Permission helpers

RBAC rules live in lib/auth/permissions.js:

  • canAccessBranch(session, branchId)
  • filterBranchesForSession(session, branchIds)

4.3 Protected endpoints

These endpoints require a valid session:

  • GET /api/branches
  • GET /api/branches/[branch]/years
  • GET /api/branches/[branch]/[year]/months
  • GET /api/branches/[branch]/[year]/[month]/days
  • GET /api/files?branch=&year=&month=&day=

5. Sessions & Cookies

Sessions are implemented as signed JWTs stored in HTTP-only cookies.

5.1 Session payload

{
	"userId": "<MongoDB ObjectId as string>",
	"role": "branch | admin | dev",
	"branchId": "NL01 | null",
	"iat": 1700000000,
	"exp": 1700003600
}

5.2 JWT signing

  • Algorithm: HS256.
  • Secret: SESSION_SECRET.
  • Token lifetime: SESSION_MAX_AGE_SECONDS = 8 hours.

5.3 Cookie settings

Cookie name: auth_session

Attributes:

  • httpOnly: true
  • secure: resolved via NODE_ENV + optional SESSION_COOKIE_SECURE override
  • sameSite: "lax"
  • path: "/"
  • maxAge: 8 hours

Implementation lives in lib/auth/session.js:

  • createSession({ userId, role, branchId })
  • getSession()
  • destroySession()

6. Core Auth Endpoints

6.1 POST /api/auth/login

Authenticate a user and set the session cookie.

Responses:

  • 200 { "ok": true }

  • 400 (invalid JSON/body)

    {
    	"error": {
    		"message": "Invalid request body",
    		"code": "VALIDATION_INVALID_JSON"
    	}
    }
    
  • 400 (missing username/password)

    {
    	"error": {
    		"message": "Missing username or password",
    		"code": "VALIDATION_MISSING_FIELD",
    		"details": { "fields": ["username", "password"] }
    	}
    }
    
  • 401 (invalid credentials)

    {
    	"error": {
    		"message": "Invalid credentials",
    		"code": "AUTH_INVALID_CREDENTIALS"
    	}
    }
    
  • 500

    {
    	"error": {
    		"message": "Internal server error",
    		"code": "INTERNAL_SERVER_ERROR"
    	}
    }
    

6.2 GET /api/auth/logout

Clears the session cookie.

  • Returns 200 { "ok": true } on success.
  • Logout is idempotent.

7. Security Notes

  • Use HTTPS for real users (staging/prod).
  • Keep SESSION_SECRET secret and rotate when needed.
  • Local HTTP testing is supported via SESSION_COOKIE_SECURE=false.