chore: initial project scaffold with admin web, backend, desktop client, and deployment setup
Add monorepo structure for NexaVPN WireGuard control plane including: - .gitignore for node_modules, build artifacts, and environment files - README with project overview, monorepo layout, and quick start guide - Admin web UI with React, Vite, TypeScript, and nginx reverse proxy - API client with type definitions for users, devices, policies, gateways, and audit logs - Admin pages for dashboard, users, devices, policies, g
This commit is contained in:
223
docs/api.md
Normal file
223
docs/api.md
Normal file
@@ -0,0 +1,223 @@
|
||||
# REST API Contract
|
||||
|
||||
Base path: `/api/v1`
|
||||
|
||||
All responses are JSON.
|
||||
|
||||
## Authentication
|
||||
|
||||
### `POST /auth/login`
|
||||
|
||||
Request:
|
||||
|
||||
```json
|
||||
{
|
||||
"username": "alice",
|
||||
"password": "secret-password"
|
||||
}
|
||||
```
|
||||
|
||||
Response:
|
||||
|
||||
```json
|
||||
{
|
||||
"access_token": "jwt",
|
||||
"refresh_token": "opaque-token",
|
||||
"expires_in": 900,
|
||||
"user": {
|
||||
"id": "uuid",
|
||||
"username": "alice",
|
||||
"display_name": "Alice",
|
||||
"role": "admin"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `POST /auth/refresh`
|
||||
|
||||
Request:
|
||||
|
||||
```json
|
||||
{
|
||||
"refresh_token": "opaque-token"
|
||||
}
|
||||
```
|
||||
|
||||
### `POST /auth/logout`
|
||||
|
||||
Request:
|
||||
|
||||
```json
|
||||
{
|
||||
"refresh_token": "opaque-token"
|
||||
}
|
||||
```
|
||||
|
||||
### `GET /auth/me`
|
||||
|
||||
Returns the authenticated user and role.
|
||||
|
||||
## User Self-Service
|
||||
|
||||
### `GET /me/devices`
|
||||
|
||||
Returns the current user devices.
|
||||
|
||||
### `GET /me/profile`
|
||||
|
||||
Returns profile metadata for the current active device when the client needs to resync.
|
||||
|
||||
## Device Enrollment and Provisioning
|
||||
|
||||
### `POST /devices/enroll`
|
||||
|
||||
Request:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Alice MacBook Pro",
|
||||
"platform": "macos",
|
||||
"os_version": "14.4",
|
||||
"app_version": "0.1.0",
|
||||
"device_fingerprint": "sha256:...",
|
||||
"public_key": "base64-wireguard-public-key"
|
||||
}
|
||||
```
|
||||
|
||||
Response:
|
||||
|
||||
```json
|
||||
{
|
||||
"device": {
|
||||
"id": "uuid",
|
||||
"name": "Alice MacBook Pro",
|
||||
"status": "active"
|
||||
},
|
||||
"peer": {
|
||||
"assigned_ip": "100.96.0.10/32",
|
||||
"dns_servers": ["10.20.0.53"],
|
||||
"allowed_ips": ["172.16.10.0/24"],
|
||||
"gateway": {
|
||||
"id": "uuid",
|
||||
"name": "primary-gateway",
|
||||
"endpoint": "vpn.example.com:51820",
|
||||
"public_key": "gateway-public-key"
|
||||
},
|
||||
"profile_revision": 1
|
||||
},
|
||||
"profile": {
|
||||
"format": "wireguard",
|
||||
"content": "[Interface]\n..."
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"type": "cidr",
|
||||
"value": "172.16.10.0/24",
|
||||
"label": "Private subnet"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### `GET /devices/{deviceId}/profile`
|
||||
|
||||
Admin-only debug endpoint for rendered config retrieval.
|
||||
|
||||
### `POST /devices/{deviceId}/rotate`
|
||||
|
||||
Rotates the device profile revision and forces reprovisioning.
|
||||
|
||||
### `POST /devices/{deviceId}/revoke`
|
||||
|
||||
Revokes the device and removes it from future gateway sync output.
|
||||
|
||||
### `POST /devices/{deviceId}/heartbeat`
|
||||
|
||||
Optional client status sync for last-seen and runtime metadata.
|
||||
|
||||
## Connection Metadata
|
||||
|
||||
### `GET /connection/status`
|
||||
|
||||
Returns assigned IP, latest sync time, and effective allowed resources for the current authenticated device session.
|
||||
|
||||
## Admin: Users
|
||||
|
||||
### `GET /admin/users`
|
||||
### `POST /admin/users`
|
||||
### `GET /admin/users/{id}`
|
||||
### `PATCH /admin/users/{id}`
|
||||
### `POST /admin/users/{id}/disable`
|
||||
### `POST /admin/users/{id}/enable`
|
||||
### `POST /admin/users/{id}/password`
|
||||
|
||||
## Admin: Devices
|
||||
|
||||
### `GET /admin/devices`
|
||||
### `GET /admin/devices/{id}`
|
||||
### `PATCH /admin/devices/{id}`
|
||||
### `POST /admin/devices/{id}/revoke`
|
||||
### `POST /admin/devices/{id}/rotate`
|
||||
|
||||
## Admin: Policies
|
||||
|
||||
### `GET /admin/policies`
|
||||
### `POST /admin/policies`
|
||||
### `GET /admin/policies/{id}`
|
||||
### `PATCH /admin/policies/{id}`
|
||||
### `DELETE /admin/policies/{id}`
|
||||
|
||||
Policy create request:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Finance subnet access",
|
||||
"description": "Access for finance team",
|
||||
"priority": 100,
|
||||
"effect": "allow",
|
||||
"full_tunnel": false,
|
||||
"destinations": [
|
||||
"172.16.20.0/24",
|
||||
"172.16.21.10/32"
|
||||
],
|
||||
"targets": [
|
||||
{
|
||||
"type": "user",
|
||||
"id": "uuid"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Admin: Gateways
|
||||
|
||||
### `GET /admin/gateways`
|
||||
### `POST /admin/gateways`
|
||||
### `GET /admin/gateways/{id}`
|
||||
### `PATCH /admin/gateways/{id}`
|
||||
### `GET /admin/gateways/{id}/sync`
|
||||
|
||||
The sync endpoint returns the peer and firewall bundle consumed by the gateway helper.
|
||||
|
||||
## Admin: Audit
|
||||
|
||||
### `GET /admin/audit-logs`
|
||||
|
||||
Query params:
|
||||
|
||||
- `event_type`
|
||||
- `entity_type`
|
||||
- `status`
|
||||
- `page`
|
||||
- `page_size`
|
||||
|
||||
## Error Format
|
||||
|
||||
```json
|
||||
{
|
||||
"error": {
|
||||
"code": "validation_error",
|
||||
"message": "public_key is required"
|
||||
}
|
||||
}
|
||||
```
|
||||
180
docs/architecture.md
Normal file
180
docs/architecture.md
Normal file
@@ -0,0 +1,180 @@
|
||||
# NexaVPN Architecture
|
||||
|
||||
## System Overview
|
||||
|
||||
NexaVPN is a self-hosted remote access platform that uses WireGuard for transport and a centralized control plane for identity, device enrollment, provisioning, and policy enforcement.
|
||||
|
||||
The platform is split into four major planes:
|
||||
|
||||
1. Control plane
|
||||
- Go REST API
|
||||
- PostgreSQL
|
||||
- JWT auth and refresh sessions
|
||||
- policy engine
|
||||
- audit logging
|
||||
- WireGuard profile builder
|
||||
- gateway state publisher
|
||||
2. Management plane
|
||||
- React admin console
|
||||
- user, device, policy, gateway, and audit workflows
|
||||
3. Endpoint plane
|
||||
- Tauri desktop client for Windows and macOS
|
||||
- local secure token/config storage
|
||||
- on-device WireGuard keypair generation
|
||||
- embedded tunnel lifecycle management
|
||||
4. Data plane
|
||||
- Linux WireGuard gateway
|
||||
- nftables policy enforcement
|
||||
- routed access to protected resources
|
||||
|
||||
## Logical Components
|
||||
|
||||
### Backend
|
||||
|
||||
- `auth`
|
||||
- username/password login
|
||||
- access and refresh token issuance
|
||||
- session tracking
|
||||
- `user`
|
||||
- user CRUD
|
||||
- role assignment
|
||||
- account enable/disable
|
||||
- `device`
|
||||
- device registration
|
||||
- enrollment lifecycle
|
||||
- device revocation
|
||||
- device profile rotation
|
||||
- `policy`
|
||||
- user and device policy resolution
|
||||
- group-aware target model
|
||||
- allow-list centric MVP with deny precedence reserved in schema
|
||||
- `gateway`
|
||||
- gateway inventory
|
||||
- endpoint metadata
|
||||
- peer sync bundle generation
|
||||
- firewall rule translation output
|
||||
- `profile`
|
||||
- WireGuard config assembly
|
||||
- connect metadata response
|
||||
- `audit`
|
||||
- immutable security and admin events
|
||||
- `ipam`
|
||||
- VPN address pool allocation
|
||||
- uniqueness and lifecycle tracking
|
||||
|
||||
### Admin UI
|
||||
|
||||
- Dashboard
|
||||
- counts, enrollment trend, latest audit events
|
||||
- Users
|
||||
- create, edit, disable, password reset
|
||||
- Devices
|
||||
- list, revoke, rotate, inspect assigned profile metadata
|
||||
- Policies
|
||||
- create CIDR-based allow rules and attach them to users, devices, or groups
|
||||
- Gateways
|
||||
- endpoint configuration, sync status, address pool view
|
||||
- Audit
|
||||
- searchable event history
|
||||
- Settings
|
||||
- DNS defaults, token lifetimes, bootstrap settings
|
||||
|
||||
### Desktop Client
|
||||
|
||||
- onboarding
|
||||
- server URL
|
||||
- username
|
||||
- password
|
||||
- enrollment
|
||||
- machine fingerprint generation
|
||||
- WireGuard keypair generation on-device
|
||||
- device registration
|
||||
- profile provisioning
|
||||
- runtime
|
||||
- secure local storage
|
||||
- connect/disconnect
|
||||
- status display
|
||||
- last sync time
|
||||
- allowed resources display
|
||||
- diagnostics
|
||||
- logs
|
||||
- TLS trust warning surface
|
||||
- profile refresh retry
|
||||
|
||||
### Gateway
|
||||
|
||||
- WireGuard interface
|
||||
- issued peers synced from control plane
|
||||
- nftables chain generated from effective device policies
|
||||
- route advertisement limited to assigned resources or full tunnel mode
|
||||
|
||||
## Key Design Decisions
|
||||
|
||||
### Authentication
|
||||
|
||||
- Access tokens are short-lived JWTs.
|
||||
- Refresh tokens are opaque, hashed in the database, and bound to a session.
|
||||
- Admin and standard-user authorization is role-based.
|
||||
|
||||
### Device Trust
|
||||
|
||||
- A device is represented as a durable record linked to a user.
|
||||
- Clients generate their own WireGuard keypairs.
|
||||
- Only the public key is stored server-side.
|
||||
- Device rotation invalidates the old peer and issues a fresh profile revision.
|
||||
|
||||
### Policy Model
|
||||
|
||||
- Effective access is the union of active allow policies targeted at:
|
||||
- the user
|
||||
- the device
|
||||
- any future groups
|
||||
- Device-specific policies can narrow or extend user-level access.
|
||||
- Gateway enforcement is authoritative; the client display is informational.
|
||||
|
||||
### WireGuard Provisioning
|
||||
|
||||
- The backend returns structured peer metadata and a rendered profile payload.
|
||||
- The desktop client stores the private key and profile locally.
|
||||
- The MVP uses an embedded tunnel-management abstraction rather than depending on the standalone WireGuard desktop app.
|
||||
|
||||
### Expandability
|
||||
|
||||
The schema and package layout reserve room for:
|
||||
|
||||
- MFA
|
||||
- OIDC and SSO
|
||||
- approval-based enrollment
|
||||
- group and role expansion
|
||||
- multiple gateways
|
||||
- route and posture-aware policies
|
||||
- richer sync agents at the gateway edge
|
||||
|
||||
## Request Flow Summary
|
||||
|
||||
### Login and Enrollment
|
||||
|
||||
1. User enters server URL, username, and password in the desktop app.
|
||||
2. Client authenticates against `/api/v1/auth/login`.
|
||||
3. Client generates a WireGuard keypair and device fingerprint locally.
|
||||
4. Client registers with `/api/v1/devices/enroll`.
|
||||
5. Backend resolves policy, allocates IP, selects a gateway, stores the peer, and returns profile metadata.
|
||||
6. Client stores tokens and WireGuard config securely.
|
||||
7. Client uses the embedded tunnel manager to create the local profile and expose one-click connect/disconnect.
|
||||
|
||||
### Policy Update
|
||||
|
||||
1. Admin changes a policy in the web UI.
|
||||
2. Backend stores the update and writes an audit event.
|
||||
3. Gateway sync state is recalculated.
|
||||
4. Gateway rule bundle is regenerated for affected peers.
|
||||
5. The client sees refreshed allowed resources on next sync.
|
||||
|
||||
## Security Posture
|
||||
|
||||
- Passwords use Argon2id.
|
||||
- Refresh tokens are stored hashed.
|
||||
- Device private keys stay local.
|
||||
- Audit logs capture auth, enrollment, policy, and admin actions.
|
||||
- TLS is assumed in production behind a reverse proxy.
|
||||
- Gateway firewalling enforces allowed destinations independently from the client.
|
||||
60
docs/deployment.md
Normal file
60
docs/deployment.md
Normal file
@@ -0,0 +1,60 @@
|
||||
# Deployment Layout
|
||||
|
||||
## Services
|
||||
|
||||
- `postgres`
|
||||
- primary relational database
|
||||
- `backend`
|
||||
- Go API and migration runner
|
||||
- `admin-web`
|
||||
- static React admin UI served by nginx
|
||||
- `gateway`
|
||||
- WireGuard plus nftables helper container or host-managed service
|
||||
- `reverse-proxy`
|
||||
- TLS termination and routing
|
||||
|
||||
## Docker Compose Networks
|
||||
|
||||
- `control`
|
||||
- backend, postgres, admin-web, reverse-proxy
|
||||
- `gateway`
|
||||
- backend and gateway helper communication
|
||||
|
||||
## Volume Layout
|
||||
|
||||
- postgres data volume
|
||||
- backend local state volume for dev logs if needed
|
||||
- gateway config volume for rendered peer sync
|
||||
|
||||
## Bootstrap
|
||||
|
||||
1. Start PostgreSQL.
|
||||
2. Run migrations.
|
||||
3. Start the backend.
|
||||
4. Seed roles, settings, and the initial admin user.
|
||||
5. Start the admin UI and reverse proxy.
|
||||
6. Register the first gateway.
|
||||
|
||||
## Example Commands
|
||||
|
||||
```bash
|
||||
cd deploy
|
||||
cp .env.example .env
|
||||
docker compose up -d postgres
|
||||
docker compose up -d backend admin-web reverse-proxy
|
||||
```
|
||||
|
||||
For SQL bootstrap during early MVP testing:
|
||||
|
||||
```bash
|
||||
psql "$DATABASE_URL" -f backend/migrations/000001_init.sql
|
||||
psql "$DATABASE_URL" -f backend/seed/001_seed.sql
|
||||
```
|
||||
|
||||
## Production Notes
|
||||
|
||||
- Terminate TLS at nginx or another reverse proxy.
|
||||
- Restrict backend and database exposure to private networks.
|
||||
- Run the gateway with the privileges required for WireGuard and nftables.
|
||||
- Replace example secrets before deployment.
|
||||
- Use an external secret manager when available.
|
||||
76
docs/enrollment.md
Normal file
76
docs/enrollment.md
Normal file
@@ -0,0 +1,76 @@
|
||||
# Device Enrollment And Auto-Provisioning
|
||||
|
||||
## Enrollment Flow
|
||||
|
||||
1. The user launches the desktop client.
|
||||
2. The client shows a minimal form with:
|
||||
- server URL
|
||||
- username
|
||||
- password
|
||||
3. The client validates the server URL and performs a TLS reachability check.
|
||||
4. The user submits credentials.
|
||||
5. The client calls `POST /api/v1/auth/login`.
|
||||
6. On success, the client securely stores:
|
||||
- access token
|
||||
- refresh token
|
||||
- remembered server URL
|
||||
7. The client generates a WireGuard keypair locally.
|
||||
8. The client computes a stable device fingerprint.
|
||||
9. The client calls `POST /api/v1/devices/enroll`.
|
||||
10. The backend:
|
||||
- creates or updates the device record
|
||||
- selects an active gateway
|
||||
- allocates a VPN IP
|
||||
- resolves effective policy destinations
|
||||
- stores the peer
|
||||
- renders the WireGuard profile
|
||||
- emits audit events
|
||||
11. The client stores the profile locally in secure application storage.
|
||||
12. The client registers the profile with the local tunnel manager.
|
||||
13. The post-login view shows:
|
||||
- connection status
|
||||
- assigned VPN IP
|
||||
- allowed resources
|
||||
- connect/disconnect button
|
||||
- last sync time
|
||||
|
||||
## Private Key Handling
|
||||
|
||||
- The WireGuard private key is generated on-device.
|
||||
- It is never sent to the backend.
|
||||
- The backend stores only the public key and provisioning metadata.
|
||||
- The desktop app keeps the private key inside the OS-secured storage boundary where possible.
|
||||
|
||||
## Auto-Profile Provisioning Design
|
||||
|
||||
The provisioning response returns both structured metadata and a rendered WireGuard config. The client uses the rendered config for immediate compatibility and the structured metadata for UI status.
|
||||
|
||||
### Preferred MVP Strategy
|
||||
|
||||
Use an embedded tunnel manager inside the Tauri app:
|
||||
|
||||
- Windows
|
||||
- manage the tunnel using a Rust bridge and local service abstraction
|
||||
- support future integration with WireGuardNT or the official service
|
||||
- macOS
|
||||
- manage the tunnel through a privileged helper or Network Extension bridge in a later hardening phase
|
||||
- MVP keeps the app abstraction stable while platform backends can evolve
|
||||
|
||||
This approach keeps the user flow consistent even if the platform-specific implementation differs.
|
||||
|
||||
### Fallback Strategy
|
||||
|
||||
If native embedded control is not ready on a platform:
|
||||
|
||||
- the app still auto-creates the profile locally
|
||||
- the app exposes a one-click import handoff to the system WireGuard client
|
||||
- this fallback is clearly labeled as temporary in documentation, not as the intended end state
|
||||
|
||||
## Reprovisioning
|
||||
|
||||
- Profile rotation increments `profile_revision`.
|
||||
- On next sync or forced refresh, the client fetches a new profile.
|
||||
- Revoked devices lose access because:
|
||||
- the gateway peer is removed
|
||||
- tokens can be invalidated
|
||||
- the client marks the local profile unusable
|
||||
84
docs/folder-structure.md
Normal file
84
docs/folder-structure.md
Normal file
@@ -0,0 +1,84 @@
|
||||
# Folder Structure
|
||||
|
||||
```text
|
||||
NexaVPN/
|
||||
├── README.md
|
||||
├── docs/
|
||||
│ ├── api.md
|
||||
│ ├── architecture.md
|
||||
│ ├── deployment.md
|
||||
│ ├── enrollment.md
|
||||
│ ├── folder-structure.md
|
||||
│ ├── gateway.md
|
||||
│ └── schema.md
|
||||
├── backend/
|
||||
│ ├── cmd/api/
|
||||
│ │ └── main.go
|
||||
│ ├── internal/
|
||||
│ │ ├── apiutil/
|
||||
│ │ ├── app/
|
||||
│ │ ├── audit/
|
||||
│ │ ├── auth/
|
||||
│ │ ├── config/
|
||||
│ │ ├── db/
|
||||
│ │ ├── device/
|
||||
│ │ ├── gateway/
|
||||
│ │ ├── httpserver/
|
||||
│ │ ├── ipam/
|
||||
│ │ ├── policy/
|
||||
│ │ ├── profile/
|
||||
│ │ ├── user/
|
||||
│ │ └── wireguard/
|
||||
│ ├── migrations/
|
||||
│ │ └── 000001_init.sql
|
||||
│ ├── seed/
|
||||
│ │ └── 001_seed.sql
|
||||
│ ├── Dockerfile
|
||||
│ └── go.mod
|
||||
├── admin-web/
|
||||
│ ├── src/
|
||||
│ │ ├── api/
|
||||
│ │ ├── app/
|
||||
│ │ ├── components/
|
||||
│ │ ├── features/
|
||||
│ │ │ ├── audit/
|
||||
│ │ │ ├── dashboard/
|
||||
│ │ │ ├── devices/
|
||||
│ │ │ ├── gateways/
|
||||
│ │ │ ├── policies/
|
||||
│ │ │ ├── settings/
|
||||
│ │ │ └── users/
|
||||
│ │ └── styles/
|
||||
│ ├── Dockerfile
|
||||
│ ├── index.html
|
||||
│ ├── package.json
|
||||
│ ├── tsconfig.app.json
|
||||
│ ├── tsconfig.json
|
||||
│ └── vite.config.ts
|
||||
├── desktop-client/
|
||||
│ ├── src/
|
||||
│ │ ├── App.tsx
|
||||
│ │ ├── main.tsx
|
||||
│ │ └── styles.css
|
||||
│ ├── src-tauri/
|
||||
│ │ ├── src/
|
||||
│ │ │ ├── lib.rs
|
||||
│ │ │ └── main.rs
|
||||
│ │ ├── build.rs
|
||||
│ │ ├── Cargo.toml
|
||||
│ │ └── tauri.conf.json
|
||||
│ ├── index.html
|
||||
│ ├── package.json
|
||||
│ ├── tsconfig.app.json
|
||||
│ ├── tsconfig.json
|
||||
│ └── vite.config.ts
|
||||
└── deploy/
|
||||
├── .env.example
|
||||
├── docker-compose.yml
|
||||
├── nginx/
|
||||
│ ├── admin.conf
|
||||
│ └── reverse-proxy.conf
|
||||
└── scripts/
|
||||
├── bootstrap-admin.sh
|
||||
└── gateway-entrypoint.sh
|
||||
```
|
||||
69
docs/gateway.md
Normal file
69
docs/gateway.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# Gateway Enforcement Strategy
|
||||
|
||||
## WireGuard And Firewall Roles
|
||||
|
||||
- WireGuard authenticates peers and provides encrypted transport.
|
||||
- nftables enforces which protected destinations a peer may reach.
|
||||
- NexaVPN control plane translates policy into gateway-side rules.
|
||||
|
||||
## Gateway Sync Bundle
|
||||
|
||||
Each gateway receives a generated sync bundle that contains:
|
||||
|
||||
- interface settings
|
||||
- peer list
|
||||
- peer allowed source address
|
||||
- destination policy matrix
|
||||
- DNS metadata
|
||||
- revision metadata
|
||||
|
||||
Example bundle shape:
|
||||
|
||||
```json
|
||||
{
|
||||
"gateway_id": "uuid",
|
||||
"revision": 12,
|
||||
"interface": {
|
||||
"address": "100.96.0.1/24",
|
||||
"listen_port": 51820
|
||||
},
|
||||
"peers": [
|
||||
{
|
||||
"device_id": "uuid",
|
||||
"public_key": "peer-key",
|
||||
"assigned_ip": "100.96.0.10/32",
|
||||
"allowed_destinations": [
|
||||
"172.16.10.0/24"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## nftables Model
|
||||
|
||||
Recommended model:
|
||||
|
||||
1. Accept WireGuard interface input.
|
||||
2. Map peer source VPN IP to allowed destination CIDRs.
|
||||
3. Drop traffic from VPN clients to destinations outside their effective allow list.
|
||||
4. Permit full tunnel peers through explicit default-route policy.
|
||||
|
||||
High-level chain logic:
|
||||
|
||||
- traffic enters from `wg0`
|
||||
- source address identifies the device
|
||||
- destination is matched against generated sets
|
||||
- allowed traffic is accepted
|
||||
- unmatched traffic is dropped and optionally logged
|
||||
|
||||
## Enforcement Details
|
||||
|
||||
- Each device receives a unique VPN IP, which makes firewall mapping deterministic.
|
||||
- The generated firewall rules are derived from the effective policy union.
|
||||
- Device revocation removes both the WireGuard peer and its nftables set members.
|
||||
- Full-tunnel policy expands to `0.0.0.0/0` and `::/0` when enabled in later IPv6 support.
|
||||
|
||||
## Multi-Gateway Readiness
|
||||
|
||||
The backend stores policies independently from the gateway implementation. Each gateway receives only the peers assigned to it, which keeps multi-gateway expansion straightforward later.
|
||||
204
docs/schema.md
Normal file
204
docs/schema.md
Normal file
@@ -0,0 +1,204 @@
|
||||
# PostgreSQL Schema
|
||||
|
||||
## Core Tables
|
||||
|
||||
### `roles`
|
||||
|
||||
- `id uuid primary key`
|
||||
- `name text unique not null`
|
||||
- `description text not null default ''`
|
||||
- `created_at timestamptz not null default now()`
|
||||
- `updated_at timestamptz not null default now()`
|
||||
|
||||
### `users`
|
||||
|
||||
- `id uuid primary key`
|
||||
- `role_id uuid not null references roles(id)`
|
||||
- `username citext unique not null`
|
||||
- `display_name text not null`
|
||||
- `email citext unique`
|
||||
- `password_hash text not null`
|
||||
- `is_active boolean not null default true`
|
||||
- `last_login_at timestamptz`
|
||||
- `created_at timestamptz not null default now()`
|
||||
- `updated_at timestamptz not null default now()`
|
||||
- `deleted_at timestamptz`
|
||||
|
||||
### `sessions`
|
||||
|
||||
- `id uuid primary key`
|
||||
- `user_id uuid not null references users(id)`
|
||||
- `ip_address inet`
|
||||
- `user_agent text`
|
||||
- `last_seen_at timestamptz not null default now()`
|
||||
- `expires_at timestamptz not null`
|
||||
- `created_at timestamptz not null default now()`
|
||||
- `revoked_at timestamptz`
|
||||
|
||||
### `refresh_tokens`
|
||||
|
||||
- `id uuid primary key`
|
||||
- `session_id uuid not null references sessions(id)`
|
||||
- `user_id uuid not null references users(id)`
|
||||
- `token_hash text not null`
|
||||
- `expires_at timestamptz not null`
|
||||
- `created_at timestamptz not null default now()`
|
||||
- `revoked_at timestamptz`
|
||||
|
||||
### `gateways`
|
||||
|
||||
- `id uuid primary key`
|
||||
- `name text unique not null`
|
||||
- `endpoint text not null`
|
||||
- `public_key text not null`
|
||||
- `listen_port integer not null`
|
||||
- `vpn_cidr cidr not null`
|
||||
- `dns_servers text[] not null default '{}'`
|
||||
- `is_active boolean not null default true`
|
||||
- `last_sync_at timestamptz`
|
||||
- `created_at timestamptz not null default now()`
|
||||
- `updated_at timestamptz not null default now()`
|
||||
- `deleted_at timestamptz`
|
||||
|
||||
### `devices`
|
||||
|
||||
- `id uuid primary key`
|
||||
- `user_id uuid not null references users(id)`
|
||||
- `gateway_id uuid references gateways(id)`
|
||||
- `name text not null`
|
||||
- `platform text not null`
|
||||
- `os_version text not null default ''`
|
||||
- `app_version text not null default ''`
|
||||
- `device_fingerprint text not null`
|
||||
- `public_key text not null`
|
||||
- `status text not null default 'active'`
|
||||
- `last_seen_at timestamptz`
|
||||
- `last_connected_at timestamptz`
|
||||
- `approved_at timestamptz`
|
||||
- `revoked_at timestamptz`
|
||||
- `created_at timestamptz not null default now()`
|
||||
- `updated_at timestamptz not null default now()`
|
||||
- `deleted_at timestamptz`
|
||||
|
||||
Unique index:
|
||||
|
||||
- `(user_id, device_fingerprint)` where `deleted_at is null`
|
||||
|
||||
### `wireguard_peers`
|
||||
|
||||
- `id uuid primary key`
|
||||
- `device_id uuid not null references devices(id)`
|
||||
- `gateway_id uuid not null references gateways(id)`
|
||||
- `public_key text unique not null`
|
||||
- `assigned_ip inet not null`
|
||||
- `preshared_key_ciphertext text`
|
||||
- `allowed_ips cidr[] not null default '{}'`
|
||||
- `dns_servers text[] not null default '{}'`
|
||||
- `profile_revision integer not null default 1`
|
||||
- `last_profile_issued_at timestamptz`
|
||||
- `created_at timestamptz not null default now()`
|
||||
- `updated_at timestamptz not null default now()`
|
||||
- `deleted_at timestamptz`
|
||||
|
||||
### `ip_allocations`
|
||||
|
||||
- `id uuid primary key`
|
||||
- `gateway_id uuid not null references gateways(id)`
|
||||
- `device_id uuid references devices(id)`
|
||||
- `address inet not null`
|
||||
- `status text not null default 'allocated'`
|
||||
- `allocated_at timestamptz not null default now()`
|
||||
- `released_at timestamptz`
|
||||
- `created_at timestamptz not null default now()`
|
||||
- `updated_at timestamptz not null default now()`
|
||||
|
||||
Unique indexes:
|
||||
|
||||
- `(gateway_id, address)`
|
||||
- `(device_id)` where `status = 'allocated'`
|
||||
|
||||
### `policies`
|
||||
|
||||
- `id uuid primary key`
|
||||
- `name text unique not null`
|
||||
- `description text not null default ''`
|
||||
- `priority integer not null default 100`
|
||||
- `effect text not null default 'allow'`
|
||||
- `is_active boolean not null default true`
|
||||
- `full_tunnel boolean not null default false`
|
||||
- `created_by uuid references users(id)`
|
||||
- `created_at timestamptz not null default now()`
|
||||
- `updated_at timestamptz not null default now()`
|
||||
- `deleted_at timestamptz`
|
||||
|
||||
### `policy_targets`
|
||||
|
||||
- `id uuid primary key`
|
||||
- `policy_id uuid not null references policies(id)`
|
||||
- `target_type text not null`
|
||||
- `target_id uuid not null`
|
||||
- `created_at timestamptz not null default now()`
|
||||
|
||||
Target types:
|
||||
|
||||
- `user`
|
||||
- `device`
|
||||
- `group`
|
||||
|
||||
### `policy_destinations`
|
||||
|
||||
- `id uuid primary key`
|
||||
- `policy_id uuid not null references policies(id)`
|
||||
- `destination cidr not null`
|
||||
- `description text not null default ''`
|
||||
- `created_at timestamptz not null default now()`
|
||||
|
||||
### `groups`
|
||||
|
||||
- `id uuid primary key`
|
||||
- `name text unique not null`
|
||||
- `description text not null default ''`
|
||||
- `created_at timestamptz not null default now()`
|
||||
- `updated_at timestamptz not null default now()`
|
||||
- `deleted_at timestamptz`
|
||||
|
||||
### `group_memberships`
|
||||
|
||||
- `id uuid primary key`
|
||||
- `group_id uuid not null references groups(id)`
|
||||
- `user_id uuid not null references users(id)`
|
||||
- `created_at timestamptz not null default now()`
|
||||
|
||||
### `audit_logs`
|
||||
|
||||
- `id uuid primary key`
|
||||
- `actor_user_id uuid references users(id)`
|
||||
- `actor_device_id uuid references devices(id)`
|
||||
- `event_type text not null`
|
||||
- `entity_type text not null`
|
||||
- `entity_id uuid`
|
||||
- `status text not null`
|
||||
- `ip_address inet`
|
||||
- `message text not null`
|
||||
- `metadata jsonb not null default '{}'::jsonb`
|
||||
- `created_at timestamptz not null default now()`
|
||||
|
||||
### `settings`
|
||||
|
||||
- `id uuid primary key`
|
||||
- `category text not null`
|
||||
- `key text not null`
|
||||
- `value jsonb not null`
|
||||
- `created_at timestamptz not null default now()`
|
||||
- `updated_at timestamptz not null default now()`
|
||||
|
||||
Unique index:
|
||||
|
||||
- `(category, key)`
|
||||
|
||||
## Notes
|
||||
|
||||
- UUIDs are generated with `gen_random_uuid()`.
|
||||
- `citext` is used for case-insensitive usernames and emails.
|
||||
- Soft deletes are enabled where historical traceability matters.
|
||||
- Group tables are included now so policy resolution can grow without a destructive migration later.
|
||||
Reference in New Issue
Block a user