# Security & Roles

> ArchiTools internal architecture reference -- security layers, role model, and auth integration plan.

---

## Overview

ArchiTools is an internal-only platform deployed on-premise behind Nginx Proxy Manager. It is not exposed to the public internet. The current security posture reflects this: no authentication is required, and all features are available to anyone with network access.

This document defines the layered security model that is designed today and will be progressively enforced as the platform matures. The architecture is **future-ready** -- every entity, module, and field already carries the metadata needed for access control, even before authentication is turned on.

---

## Security Layers

Security is enforced at five distinct layers, from outermost to innermost:

```
┌─────────────────────────────────────────────┐
│  1. Network Layer                           │
│     Nginx Proxy Manager, CrowdSec, VPN     │
├─────────────────────────────────────────────┤
│  2. Application Layer                       │
│     Authentik SSO, session management       │
├─────────────────────────────────────────────┤
│  3. Module Layer                            │
│     Feature flags, role-based module gating │
├─────────────────────────────────────────────┤
│  4. Data Layer                              │
│     Visibility field on all entities        │
├─────────────────────────────────────────────┤
│  5. Field Layer                             │
│     Admin-only fields on entities           │
└─────────────────────────────────────────────┘
```

### Layer 1: Network

| Component | Purpose | Status |
|---|---|---|
| Nginx Proxy Manager | Reverse proxy, TLS termination, access control lists | Active |
| CrowdSec | Intrusion detection, IP reputation, automated banning | Planned |
| Internal DNS | ArchiTools resolves only on the office network | Active |
| VPN | Remote access for authorized employees only | Active (WireGuard) |

ArchiTools is **never** exposed on a public IP. All traffic enters through Nginx Proxy Manager on the Docker host. CrowdSec will be added as a sidecar container to provide real-time threat detection and community-sourced IP blocklists.

### Layer 2: Application (Authentik SSO)

Not yet active. When enabled, Authentik will serve as the single sign-on provider for all ArchiTools users. See [Auth Integration Plan](#auth-integration-plan) below.

### Layer 3: Module

Every module has a `featureFlag` and a `visibility` field in its `ModuleConfig`. Today, feature flags control whether a module is loaded at all. When auth is active, the visibility field will additionally gate access based on the user's role.

```typescript
// Current enforcement (no auth):
// featureFlag=true  -> module is loaded
// featureFlag=false -> module is not loaded, chunk is not fetched

// Future enforcement (with auth):
// featureFlag=true AND role meets visibility -> module is loaded
// Otherwise -> module is hidden from navigation and routes return 403
```

### Layer 4: Data

Every persistent entity in ArchiTools carries a `visibility` field:

```typescript
type Visibility = 'all' | 'internal' | 'admin' | 'guest-safe';
```

| Value | Who can see it |
|---|---|
| `all` | Any authenticated user (or everyone, in no-auth mode) |
| `internal` | Users with role `user` or above (not `guest`) |
| `admin` | Users with role `admin` only |
| `guest-safe` | Explicitly marked as safe for external/guest viewers |

This field is stored with the entity data and checked at query time. Services must filter results by visibility before returning them to the UI.

### Layer 5: Field

Individual fields on entities can be marked as admin-only. This is enforced at the component level -- admin-only fields are not rendered (not merely hidden with CSS) for non-admin users.

```typescript
interface FieldConfig {
  key: string;
  label: string;
  adminOnly?: boolean;      // field is excluded from render for non-admins
  visibility?: Visibility;  // field-level visibility override
}
```

When auth is inactive, all fields are rendered (the stub returns admin role).

---

## Role Model

Roles are designed now and embedded into interfaces and type definitions so that the codebase is ready for auth without structural changes.

```typescript
type Role = 'admin' | 'manager' | 'user' | 'viewer' | 'guest';
```

### Role Definitions

| Role | Module Access | Data Scope | Write Access | Configuration |
|---|---|---|---|---|
| `admin` | All modules | All data, all companies | Full CRUD | Full (flags, settings, users) |
| `manager` | All internal modules | Company-scoped: all data within assigned companies | Full CRUD within scope | Module-level settings |
| `user` | Standard modules | Own data + shared data | CRUD on own data | Personal preferences only |
| `viewer` | Permitted modules (read-only) | Shared data visible to their role | None | None |
| `guest` | Public-facing views only | `guest-safe` entities only | None | None |

### Role Hierarchy

Roles form a strict hierarchy. A higher role inherits all permissions of lower roles:

```
admin > manager > user > viewer > guest
```

The `hasRole` check uses this hierarchy:

```typescript
const ROLE_HIERARCHY: Record<Role, number> = {
  guest: 0,
  viewer: 1,
  user: 2,
  manager: 3,
  admin: 4,
};

function hasRole(requiredRole: Role, actualRole: Role): boolean {
  return ROLE_HIERARCHY[actualRole] >= ROLE_HIERARCHY[requiredRole];
}
```

### Company Scoping

The three companies (Beletage SRL, Urban Switch SRL, Studii de Teren SRL) are first-class entities. Managers are assigned to one or more companies and can only manage data within their scope.

```typescript
type CompanyId = 'beletage' | 'urban-switch' | 'studii-de-teren';

interface UserProfile {
  id: string;
  name: string;
  email: string;
  role: Role;
  companies: CompanyId[];  // which companies this user belongs to
  primaryCompany: CompanyId;
}
```

Admins are implicitly scoped to all companies. Users and viewers see data for the companies they belong to.

---

## Visibility Model

```typescript
type Visibility = 'all' | 'internal' | 'admin' | 'guest-safe';
```

Visibility is applied at three levels:

| Level | Where it lives | What it controls |
|---|---|---|
| Module | `ModuleConfig.visibility` | Whether the module appears in navigation and is routable |
| Entity | Entity data (e.g., `registryEntry.visibility`) | Whether the entity appears in query results |
| Field | `FieldConfig.visibility` or `FieldConfig.adminOnly` | Whether the field is rendered in the UI |

### Resolution Logic

```typescript
function canView(userRole: Role, visibility: Visibility): boolean {
  switch (visibility) {
    case 'all':
      return true;
    case 'internal':
      return hasRole('user', userRole);
    case 'admin':
      return hasRole('admin', userRole);
    case 'guest-safe':
      return true; // visible to everyone including guests
  }
}
```

---

## Auth Integration Plan

### Phase 1: No Auth (Current)

- All features are available to any user on the network.
- The `AuthContext` stub returns a synthetic admin user.
- Feature flags are the only access control mechanism.
- Visibility fields are stored on entities but not enforced at query time.

### Phase 2: Authentik SSO + Module Gating

- Authentik is deployed as a Docker container alongside ArchiTools.
- OIDC integration: ArchiTools redirects unauthenticated requests to Authentik.
- Authentik manages user accounts, passwords, and MFA.
- Role is assigned in Authentik as a custom claim and mapped to the ArchiTools `Role` type.
- Module-level gating is enforced: users only see modules permitted by their role + module visibility.
- The `AuthContext` reads from the Authentik session instead of the stub.

### Phase 3: Data-Level Permissions

- Entity visibility is enforced at query time in services.
- Company scoping is active: managers see only their companies' data.
- Field-level visibility is enforced: admin-only fields are excluded from non-admin renders.
- Audit logging: who accessed what, when.

### Phase 4: External / Guest Access

- Guest role is activated for external collaborators.
- Scoped views: guests see only `guest-safe` entities, through dedicated guest routes.
- Link-based sharing: generate time-limited URLs for specific records.
- No direct database access -- guest views are read-only projections.

---

## AuthContext Design

The `AuthContext` is the single source of truth for the current user's identity and permissions throughout the React component tree.

```typescript
// src/lib/auth/types.ts

interface AuthContext {
  user: UserProfile | null;
  role: Role;
  isAuthenticated: boolean;
  hasRole(role: Role): boolean;
  canAccess(moduleId: string): boolean;
  canView(visibility: Visibility): boolean;
}
```

### Stub Implementation (Phase 1)

```typescript
// src/lib/auth/auth-context.ts

import { createContext, useContext } from 'react';
import type { AuthContext as AuthContextType } from './types';

const STUB_USER: UserProfile = {
  id: 'dev-admin',
  name: 'Developer',
  email: 'dev@architools.local',
  role: 'admin',
  companies: ['beletage', 'urban-switch', 'studii-de-teren'],
  primaryCompany: 'beletage',
};

const stubContext: AuthContextType = {
  user: STUB_USER,
  role: 'admin',
  isAuthenticated: true,
  hasRole: () => true,
  canAccess: () => true,
  canView: () => true,
};

const AuthContext = createContext<AuthContextType>(stubContext);

export function useAuth(): AuthContextType {
  return useContext(AuthContext);
}

export { AuthContext };
```

In development mode, the stub grants full admin access. When Authentik is integrated (Phase 2), the provider will be swapped to read from the OIDC session, and the stub will only be used in test environments.

### Usage in Components

```typescript
// Gating a module route
function ModulePage({ config }: { config: ModuleConfig }) {
  const auth = useAuth();

  if (!auth.canAccess(config.id)) {
    return <AccessDenied />;
  }

  return <ModuleLoader config={config} />;
}

// Gating a field
function EntityDetail({ entity }: { entity: RegistryEntry }) {
  const auth = useAuth();

  return (
    <div>
      <p>{entity.title}</p>
      {auth.hasRole('admin') && (
        <p className="text-sm text-muted-foreground">
          Internal notes: {entity.adminNotes}
        </p>
      )}
    </div>
  );
}
```

---

## Module-Specific Security Notes

### Password Vault

The Password Vault module is a **convenience tool for non-critical credentials only**. It is explicitly **not** a production secrets manager.

**Constraints:**
- Passwords are stored in the browser's localStorage, encrypted with a user-provided passphrase using AES-256-GCM via the Web Crypto API.
- The encryption key is derived from the passphrase using PBKDF2 with a per-vault salt.
- There is **no server-side key management**, no HSM, and no key escrow.
- If the user forgets the passphrase, the vault data is unrecoverable.
- This module must display a persistent disclaimer banner:

> **Atentie:** Acest modul este destinat exclusiv pentru credentiale non-critice (conturi de servicii, parole Wi-Fi, etc.). NU stocati parole bancare, chei SSH private, sau secrete de productie. Folositi un manager de parole dedicat (Bitwarden, 1Password) pentru credentiale critice.

**What must NOT be stored here:**
- Banking credentials
- SSH private keys
- API keys for production services
- Personal identity documents

### Digital Signatures

- The module stores **file hashes** (SHA-256), not raw private keys.
- Signature verification is hash-based: the document is re-hashed and compared to the stored hash + signer metadata.
- No cryptographic signing keys are generated or stored in ArchiTools.
- The signature record structure:

```typescript
interface SignatureRecord {
  id: string;
  documentHash: string;    // SHA-256 hex digest
  fileName: string;
  signerName: string;
  signerCompany: CompanyId;
  signedAt: string;        // ISO 8601 timestamp
  metadata?: Record<string, string>;
}
```

### Data Export / Import

- All import operations must sanitize incoming data before writing to storage.
- JSON imports: validate against the expected schema using Zod. Reject payloads that do not conform.
- CSV imports: escape all string fields, reject fields exceeding length limits.
- No `eval()`, no `Function()` constructors, no dynamic code execution on imported data.
- Export operations must strip admin-only fields when the exporter's role is below `admin`.

---

## Web Security Controls

### XSS Prevention

React escapes all interpolated values by default. This is the primary XSS defense.

**Rules:**
- `dangerouslySetInnerHTML` is **prohibited** except in the Email Signature preview and Word XML preview modules. These modules render user-provided HTML in a sandboxed `<iframe>` with `sandbox="allow-same-origin"` (no `allow-scripts`).
- User input that flows into HTML attributes (e.g., `href`, `src`) must be validated against an allowlist of protocols (`https:`, `mailto:`, `tel:`).
- No inline `<script>` tags are generated anywhere in the application.

### CSRF Protection

Next.js App Router provides built-in CSRF protection for Server Actions via origin checking. Since ArchiTools currently uses client-side data storage (localStorage/IndexedDB) rather than a backend API, CSRF is not an active vector. When server-side endpoints are added in future phases, all mutations must use Server Actions or include a CSRF token.

### Content Security Policy

The following CSP headers are configured in the Nginx Proxy Manager for the ArchiTools domain:

```
Content-Security-Policy:
  default-src 'self';
  script-src 'self' 'unsafe-inline' 'unsafe-eval';
  style-src 'self' 'unsafe-inline';
  img-src 'self' data: blob:;
  font-src 'self';
  connect-src 'self';
  frame-src 'self';
  object-src 'none';
  base-uri 'self';
  form-action 'self';
```

**Notes:**
- `unsafe-inline` and `unsafe-eval` in `script-src` are required by Next.js in development. In production, these should be tightened to use nonce-based CSP once the build pipeline supports it.
- `object-src 'none'` blocks Flash and other plugin-based content.
- `frame-src 'self'` allows the sandboxed iframes used by the Email Signature and Word XML preview modules.

### Security Headers (Nginx)

In addition to CSP, the following headers are set at the Nginx level:

```nginx
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "0" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
```

`X-XSS-Protection` is set to `0` (disabled) because the modern approach is to rely on CSP rather than the browser's legacy XSS auditor, which can introduce vulnerabilities of its own.

---

## Summary

| Layer | Current State | Phase 2 | Phase 3 | Phase 4 |
|---|---|---|---|---|
| Network | Nginx + internal DNS | + CrowdSec | No change | + guest ingress rules |
| Application | No auth (stub) | Authentik SSO | No change | + guest OIDC flow |
| Module | Feature flags only | + role-based gating | No change | + guest module set |
| Data | Visibility stored, not enforced | No change | Enforced at query time | + guest-safe filter |
| Field | All fields rendered | No change | Admin-only fields gated | No change |
