Complete Next.js 16 application with 13 fully implemented modules: Email Signature, Word XML Generator, Registratura, Dashboard, Tag Manager, IT Inventory, Address Book, Password Vault, Mini Utilities, Prompt Generator, Digital Signatures, Word Templates, and AI Chat. Includes core platform systems (module registry, feature flags, storage abstraction, i18n, theming, auth stub, tagging), 16 technical documentation files, Docker deployment config, and legacy HTML tool reference. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
16 KiB
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 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.
// 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:
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.
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.
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:
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.
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
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
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
AuthContextstub 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
Roletype. - Module-level gating is enforced: users only see modules permitted by their role + module visibility.
- The
AuthContextreads 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-safeentities, 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.
// 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)
// 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
// 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:
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(), noFunction()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:
dangerouslySetInnerHTMLis prohibited except in the Email Signature preview and Word XML preview modules. These modules render user-provided HTML in a sandboxed<iframe>withsandbox="allow-same-origin"(noallow-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-inlineandunsafe-evalinscript-srcare 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:
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 |