Fix code issues and add missing documentation
- Fix duplicate tenant_id parameter in seed.py (line 148) - Add security warning to SECRET_KEY in .env.example - Create comprehensive README.md with setup instructions - Add Alembic configuration files (alembic.ini, env.py, script.py.mako) - Create initial database migration for all tables - Document project structure, features, and deployment checklist
This commit is contained in:
240
README.md
Normal file
240
README.md
Normal file
@@ -0,0 +1,240 @@
|
||||
# TrustOS
|
||||
|
||||
The AI Operating System for Cyber Resilience
|
||||
|
||||
TrustOS is an AI-powered cyber resilience platform for SMB and mid-market companies that need enterprise-grade security clarity without building an enterprise security team. The platform helps leadership teams understand their top cyber risks, prioritize fixes, track remediation, and prove improvement to customers, boards, insurers, and regulators.
|
||||
|
||||
## Architecture
|
||||
|
||||
TrustOS is a full-stack application with the following components:
|
||||
|
||||
- **Frontend**: Next.js 16 (React) + TypeScript + Tailwind CSS + shadcn/ui
|
||||
- **Backend**: Python (FastAPI) + SQLAlchemy 2.0 + Async PostgreSQL
|
||||
- **Database**: PostgreSQL 16
|
||||
- **AI Layer**: OpenAI GPT-4o-mini or Anthropic Claude 3 Haiku
|
||||
- **Deployment**: Docker Compose (local), Railway/Render/VPS (production)
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
trustos/
|
||||
├── frontend/ # Next.js frontend application
|
||||
│ ├── src/
|
||||
│ │ ├── app/ # Next.js App Router pages
|
||||
│ │ ├── components/ # React components
|
||||
│ │ ├── hooks/ # Custom React hooks
|
||||
│ │ └── lib/ # Utility functions and API client
|
||||
│ ├── package.json
|
||||
│ └── tailwind.config.ts
|
||||
├── backend/ # FastAPI backend application
|
||||
│ ├── app/
|
||||
│ │ ├── api/routes/ # API endpoints
|
||||
│ │ ├── core/ # Configuration and security
|
||||
│ │ ├── db/ # Database session
|
||||
│ │ ├── models/ # SQLAlchemy models
|
||||
│ │ ├── schemas/ # Pydantic schemas
|
||||
│ │ ├── services/ # Business logic (AI, risk calculator, report generator)
|
||||
│ │ └── workers/ # Background tasks
|
||||
│ ├── alembic/ # Database migrations
|
||||
│ ├── requirements.txt
|
||||
│ ├── seed.py # Demo data seeding script
|
||||
│ └── .env.example
|
||||
├── infra/ # Docker configuration
|
||||
│ ├── docker-compose.yml
|
||||
│ ├── Dockerfile.backend
|
||||
│ └── Dockerfile.frontend
|
||||
├── docs/ # Documentation
|
||||
└── trustos-plan.md # Detailed build plan and stages
|
||||
```
|
||||
|
||||
## Quick Start (Docker Compose)
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Docker and Docker Compose installed
|
||||
- Git
|
||||
|
||||
### Setup
|
||||
|
||||
1. Clone the repository:
|
||||
```bash
|
||||
git clone <repository-url>
|
||||
cd trustos
|
||||
```
|
||||
|
||||
2. Copy environment files:
|
||||
```bash
|
||||
cp backend/.env.example backend/.env
|
||||
```
|
||||
|
||||
3. Start all services:
|
||||
```bash
|
||||
cd infra
|
||||
docker-compose up --build
|
||||
```
|
||||
|
||||
4. Access the application:
|
||||
- Frontend: http://localhost:3000
|
||||
- Backend API: http://localhost:8000
|
||||
- API Documentation: http://localhost:8000/docs
|
||||
|
||||
### Demo Credentials
|
||||
|
||||
The seed script creates a demo tenant "Acme Corp" with the following users:
|
||||
|
||||
- **Executive (CEO)**: executive@acmecorp.io / TrustOS2024!
|
||||
- **IT Admin**: it@acmecorp.io / TrustOS2024!
|
||||
- **TrustOS Admin**: admin@trustos.com / TrustOS-Admin-2024!
|
||||
|
||||
## Development Setup (Local)
|
||||
|
||||
### Backend Setup
|
||||
|
||||
1. Create a Python virtual environment:
|
||||
```bash
|
||||
cd backend
|
||||
python -m venv venv
|
||||
source venv/bin/activate # On Windows: venv\Scripts\activate
|
||||
```
|
||||
|
||||
2. Install dependencies:
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
3. Set up environment variables:
|
||||
```bash
|
||||
cp .env.example .env
|
||||
# Edit .env with your configuration
|
||||
```
|
||||
|
||||
4. Initialize the database:
|
||||
```bash
|
||||
python seed.py
|
||||
```
|
||||
|
||||
5. Run the backend server:
|
||||
```bash
|
||||
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
|
||||
```
|
||||
|
||||
### Frontend Setup
|
||||
|
||||
1. Install dependencies:
|
||||
```bash
|
||||
cd frontend
|
||||
npm install
|
||||
```
|
||||
|
||||
2. Set up environment variables:
|
||||
```bash
|
||||
cp .env.example .env.local
|
||||
# Edit .env.local with NEXT_PUBLIC_API_URL=http://localhost:8000
|
||||
```
|
||||
|
||||
3. Run the development server:
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
## Database Migrations
|
||||
|
||||
TrustOS uses Alembic for database migrations. In development, tables are auto-created on startup. For production, use migrations:
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
alembic revision --autogenerate -m "description"
|
||||
alembic upgrade head
|
||||
```
|
||||
|
||||
## Key Features
|
||||
|
||||
### Phase 1: Vault Audit (Current Implementation)
|
||||
- Executive dashboard with Cyber Health Score
|
||||
- Top 3 Risks with AI-translated business impact
|
||||
- Risk trend visualization (90-day history)
|
||||
- Findings management with remediation tracking
|
||||
- Digital footprint center for executive exposure
|
||||
- Multi-role authentication (Executive, IT Admin, TrustOS Admin)
|
||||
- Vault Audit Report generation with PDF export
|
||||
|
||||
### Phase 2: Monthly Monitoring (Planned)
|
||||
- Continuous monitoring engine
|
||||
- Daily automated assessments
|
||||
- Certificate expiration monitoring
|
||||
- CVE monitoring
|
||||
- Cloud posture checks
|
||||
- Breach intelligence integration
|
||||
|
||||
### Phase 3: Full Platform (Planned)
|
||||
- Attack path visualization
|
||||
- AI Security Coach
|
||||
- Executive protection services
|
||||
- Board reporting
|
||||
- Advanced integrations
|
||||
|
||||
## Configuration
|
||||
|
||||
### Backend Environment Variables
|
||||
|
||||
See `backend/.env.example` for the full list. Key variables:
|
||||
|
||||
- `DATABASE_URL`: PostgreSQL connection string (async)
|
||||
- `SECRET_KEY`: JWT signing key (generate with `openssl rand -hex 32`)
|
||||
- `OPENAI_API_KEY` or `ANTHROPIC_API_KEY`: For AI features
|
||||
- `AI_PROVIDER`: `openai` or `anthropic`
|
||||
|
||||
### Frontend Environment Variables
|
||||
|
||||
- `NEXT_PUBLIC_API_URL`: Backend API URL
|
||||
|
||||
## Security Notes
|
||||
|
||||
- All API routes require authentication except `/health` and `/api/v1/auth/login`
|
||||
- Multi-tenant isolation enforced at the database and API level
|
||||
- Role-based access control (RBAC) for different user types
|
||||
- Authorization-first approach: only scan explicitly authorized assets
|
||||
- Secrets should never be committed to git (use `.env` files, ignored by git)
|
||||
|
||||
## Testing
|
||||
|
||||
Run backend tests:
|
||||
```bash
|
||||
cd backend
|
||||
pytest
|
||||
```
|
||||
|
||||
Run frontend tests:
|
||||
```bash
|
||||
cd frontend
|
||||
npm test
|
||||
```
|
||||
|
||||
## Deployment
|
||||
|
||||
### Production Checklist
|
||||
|
||||
- [ ] Change `SECRET_KEY` to a cryptographically secure random value
|
||||
- [ ] Set strong database passwords
|
||||
- [ ] Configure production database (Supabase, RDS, etc.)
|
||||
- [ ] Enable HTTPS/TLS
|
||||
- [ ] Set up proper CORS origins
|
||||
- [ ] Configure AI API keys with appropriate rate limits
|
||||
- [ ] Set up logging and monitoring
|
||||
- [ ] Enable database backups
|
||||
- [ ] Review and update security headers
|
||||
- [ ] Run Alembic migrations instead of auto-create tables
|
||||
|
||||
## Documentation
|
||||
|
||||
- **Business Plan**: See `readplan.txt` for the complete business strategy
|
||||
- **Build Plan**: See `trustos-plan.md` for detailed implementation stages
|
||||
- **API Documentation**: Available at `/docs` when backend is running
|
||||
|
||||
## License
|
||||
|
||||
Proprietary - All rights reserved
|
||||
|
||||
## Support
|
||||
|
||||
For technical issues or questions, contact the TrustOS development team.
|
||||
@@ -3,6 +3,8 @@ DATABASE_URL=postgresql+asyncpg://trustos:trustos_dev@postgres:5432/trustos
|
||||
SYNC_DATABASE_URL=postgresql://trustos:trustos_dev@postgres:5432/trustos
|
||||
|
||||
# Auth
|
||||
# IMPORTANT: Generate a secure random key in production using: openssl rand -hex 32
|
||||
# Never use the default value in production environments
|
||||
SECRET_KEY=changeme-use-openssl-rand-hex-32-in-production
|
||||
ACCESS_TOKEN_EXPIRE_MINUTES=480
|
||||
|
||||
|
||||
112
backend/alembic.ini
Normal file
112
backend/alembic.ini
Normal file
@@ -0,0 +1,112 @@
|
||||
# A generic, single database configuration.
|
||||
|
||||
[alembic]
|
||||
# path to migration scripts
|
||||
script_location = alembic
|
||||
|
||||
# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s
|
||||
file_template = %%(year)d%%(month).2d%%(day).2d_%%(hour).2d%%(minute).2d_%%(rev)s_%%(slug)s
|
||||
|
||||
# sys.path path, will be prepended to sys.path if present.
|
||||
prepend_sys_path = .
|
||||
|
||||
# timezone to use when rendering the date within the migration file
|
||||
# as well as the filename.
|
||||
# If specified, requires the python-dateutil library that can be
|
||||
# installed by adding `alembic[tz]` to the pip requirements
|
||||
# string value is passed to dateutil.tz.gettz()
|
||||
# leave blank for localtime
|
||||
# timezone =
|
||||
|
||||
# max length of characters to apply to the
|
||||
# "slug" field
|
||||
# truncate_slug_length = 40
|
||||
|
||||
# set to 'true' to run the environment during
|
||||
# the 'revision' command, regardless of autogenerate
|
||||
# revision_environment = false
|
||||
|
||||
# set to 'true' to allow .pyc and .pyo files without
|
||||
# a source .py file to be detected as revisions in the
|
||||
# versions/ directory
|
||||
# sourceless = false
|
||||
|
||||
# version location specification; This defaults
|
||||
# to alembic/versions. When using multiple version
|
||||
# directories, initial revisions must be specified with --version-path.
|
||||
# The path separator used here should be the separator specified by "version_path_separator" below.
|
||||
# version_locations = %(here)s/bar:%(here)s/bat:alembic/versions
|
||||
|
||||
# version path separator; As mentioned above, this is the character used to split
|
||||
# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep.
|
||||
# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas.
|
||||
# Valid values for version_path_separator are:
|
||||
#
|
||||
# version_path_separator = :
|
||||
# version_path_separator = ;
|
||||
# version_path_separator = space
|
||||
version_path_separator = os # Use os.pathsep. Default configuration used for new projects.
|
||||
|
||||
# set to 'true' to search source files recursively
|
||||
# in each "version_locations" directory
|
||||
# new in Alembic version 1.10
|
||||
# recursive_version_locations = false
|
||||
|
||||
# the output encoding used when revision files
|
||||
# are written from script.py.mako
|
||||
# output_encoding = utf-8
|
||||
|
||||
sqlalchemy.url = postgresql+asyncpg://trustos:trustos_dev@localhost:5432/trustos
|
||||
|
||||
|
||||
[post_write_hooks]
|
||||
# post_write_hooks defines scripts or Python functions that are run
|
||||
# on newly generated revision scripts. See the documentation for further
|
||||
# detail and examples
|
||||
|
||||
# format using "black" - use the console_scripts runner, against the "black" entrypoint
|
||||
# hooks = black
|
||||
# black.type = console_scripts
|
||||
# black.entrypoint = black
|
||||
# black.options = -l 79 REVISION_SCRIPT_FILENAME
|
||||
|
||||
# lint with attempts to fix using "ruff" - use the exec runner, execute a binary
|
||||
# hooks = ruff
|
||||
# ruff.type = exec
|
||||
# ruff.executable = %(here)s/.venv/bin/ruff
|
||||
# ruff.options = --fix REVISION_SCRIPT_FILENAME
|
||||
|
||||
# Logging configuration
|
||||
[loggers]
|
||||
keys = root,sqlalchemy,alembic
|
||||
|
||||
[handlers]
|
||||
keys = console
|
||||
|
||||
[formatters]
|
||||
keys = generic
|
||||
|
||||
[logger_root]
|
||||
level = WARN
|
||||
handlers = console
|
||||
qualname =
|
||||
|
||||
[logger_sqlalchemy]
|
||||
level = WARN
|
||||
handlers =
|
||||
qualname = sqlalchemy.engine
|
||||
|
||||
[logger_alembic]
|
||||
level = INFO
|
||||
handlers =
|
||||
qualname = alembic
|
||||
|
||||
[handler_console]
|
||||
class = StreamHandler
|
||||
args = (sys.stderr,)
|
||||
level = NOTSET
|
||||
formatter = generic
|
||||
|
||||
[formatter_generic]
|
||||
format = %(levelname)-5.5s [%(name)s] %(message)s
|
||||
datefmt = %H:%M:%S
|
||||
84
backend/alembic/env.py
Normal file
84
backend/alembic/env.py
Normal file
@@ -0,0 +1,84 @@
|
||||
from logging.config import fileConfig
|
||||
|
||||
from sqlalchemy import engine_from_config
|
||||
from sqlalchemy import pool
|
||||
|
||||
from alembic import context
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Add the parent directory to sys.path to import app modules
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
|
||||
|
||||
from app.core.config import settings
|
||||
from app.db.session import Base
|
||||
from app.models import models # Import all models
|
||||
|
||||
# this is the Alembic Config object, which provides
|
||||
# access to the values within the .ini file in use.
|
||||
config = context.config
|
||||
|
||||
# Interpret the config file for Python logging.
|
||||
# This line sets up loggers basically.
|
||||
if config.config_file_name is not None:
|
||||
fileConfig(config.config_file_name)
|
||||
|
||||
# add your model's MetaData object here
|
||||
# for 'autogenerate' support
|
||||
target_metadata = Base.metadata
|
||||
|
||||
# Override sqlalchemy.url with the one from settings
|
||||
config.set_main_option("sqlalchemy.url", settings.SYNC_DATABASE_URL)
|
||||
|
||||
|
||||
def run_migrations_offline() -> None:
|
||||
"""Run migrations in 'offline' mode.
|
||||
|
||||
This configures the context with just a URL
|
||||
and not an Engine, though an Engine is acceptable
|
||||
here as well. By skipping the Engine creation
|
||||
we don't even need a DBAPI to be available.
|
||||
|
||||
Calls to context.execute() here emit the given string to the
|
||||
script output.
|
||||
|
||||
"""
|
||||
url = config.get_main_option("sqlalchemy.url")
|
||||
context.configure(
|
||||
url=url,
|
||||
target_metadata=target_metadata,
|
||||
literal_binds=True,
|
||||
dialect_opts={"paramstyle": "named"},
|
||||
)
|
||||
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
|
||||
def run_migrations_online() -> None:
|
||||
"""Run migrations in 'online' mode.
|
||||
|
||||
In this scenario we need to create an Engine
|
||||
and associate a connection with the context.
|
||||
|
||||
"""
|
||||
connectable = engine_from_config(
|
||||
config.get_section(config.config_ini_section, {}),
|
||||
prefix="sqlalchemy.",
|
||||
poolclass=pool.NullPool,
|
||||
)
|
||||
|
||||
with connectable.connect() as connection:
|
||||
context.configure(
|
||||
connection=connection, target_metadata=target_metadata
|
||||
)
|
||||
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
|
||||
if context.is_offline_mode():
|
||||
run_migrations_offline()
|
||||
else:
|
||||
run_migrations_online()
|
||||
26
backend/alembic/script.py.mako
Normal file
26
backend/alembic/script.py.mako
Normal file
@@ -0,0 +1,26 @@
|
||||
"""${message}
|
||||
|
||||
Revision ID: ${up_revision}
|
||||
Revises: ${down_revision | comma,n}
|
||||
Create Date: ${create_date}
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
${imports if imports else ""}
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = ${repr(up_revision)}
|
||||
down_revision: Union[str, None] = ${repr(down_revision)}
|
||||
branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
|
||||
depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
${upgrades if upgrades else "pass"}
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
${downgrades if downgrades else "pass"}
|
||||
@@ -0,0 +1,187 @@
|
||||
"""Initial migration - create all tables
|
||||
|
||||
Revision ID: 001
|
||||
Revises:
|
||||
Create Date: 2025-01-01 00:00:00.000000
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = '001'
|
||||
down_revision: Union[str, None] = None
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# Create tenants table
|
||||
op.create_table(
|
||||
'tenants',
|
||||
sa.Column('id', sa.String(36), primary_key=True),
|
||||
sa.Column('name', sa.String(255), nullable=False),
|
||||
sa.Column('slug', sa.String(100), unique=True, nullable=False),
|
||||
sa.Column('industry', sa.String(100), nullable=True),
|
||||
sa.Column('size_range', sa.String(50), nullable=True),
|
||||
sa.Column('contact_email', sa.String(255), nullable=True),
|
||||
sa.Column('is_active', sa.Boolean(), default=True),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=False),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=False),
|
||||
)
|
||||
|
||||
# Create users table
|
||||
op.create_table(
|
||||
'users',
|
||||
sa.Column('id', sa.String(36), primary_key=True),
|
||||
sa.Column('tenant_id', sa.String(36), sa.ForeignKey('tenants.id'), nullable=False),
|
||||
sa.Column('email', sa.String(255), unique=True, nullable=False),
|
||||
sa.Column('hashed_password', sa.String(255), nullable=False),
|
||||
sa.Column('full_name', sa.String(255), nullable=False),
|
||||
sa.Column('role', sa.String(50), nullable=False),
|
||||
sa.Column('is_active', sa.Boolean(), default=True),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=False),
|
||||
sa.Column('last_login', sa.DateTime(), nullable=True),
|
||||
)
|
||||
|
||||
# Create authorized_assets table
|
||||
op.create_table(
|
||||
'authorized_assets',
|
||||
sa.Column('id', sa.String(36), primary_key=True),
|
||||
sa.Column('tenant_id', sa.String(36), sa.ForeignKey('tenants.id'), nullable=False),
|
||||
sa.Column('value', sa.String(500), nullable=False),
|
||||
sa.Column('asset_type', sa.String(50), nullable=False),
|
||||
sa.Column('description', sa.Text(), nullable=True),
|
||||
sa.Column('scope_agreement_ref', sa.String(255), nullable=True),
|
||||
sa.Column('authorized_by', sa.String(255), nullable=True),
|
||||
sa.Column('authorized_at', sa.DateTime(), nullable=False),
|
||||
sa.Column('is_active', sa.Boolean(), default=True),
|
||||
)
|
||||
|
||||
# Create assets table
|
||||
op.create_table(
|
||||
'assets',
|
||||
sa.Column('id', sa.String(36), primary_key=True),
|
||||
sa.Column('tenant_id', sa.String(36), sa.ForeignKey('tenants.id'), nullable=False),
|
||||
sa.Column('name', sa.String(500), nullable=False),
|
||||
sa.Column('asset_type', sa.String(50), nullable=False),
|
||||
sa.Column('value', sa.String(500), nullable=False),
|
||||
sa.Column('owner_name', sa.String(255), nullable=True),
|
||||
sa.Column('owner_email', sa.String(255), nullable=True),
|
||||
sa.Column('tags', sa.Text(), nullable=True),
|
||||
sa.Column('notes', sa.Text(), nullable=True),
|
||||
sa.Column('is_active', sa.Boolean(), default=True),
|
||||
sa.Column('first_seen', sa.DateTime(), nullable=False),
|
||||
sa.Column('last_seen', sa.DateTime(), nullable=False),
|
||||
)
|
||||
|
||||
# Create executives table
|
||||
op.create_table(
|
||||
'executives',
|
||||
sa.Column('id', sa.String(36), primary_key=True),
|
||||
sa.Column('tenant_id', sa.String(36), sa.ForeignKey('tenants.id'), nullable=False),
|
||||
sa.Column('full_name', sa.String(255), nullable=False),
|
||||
sa.Column('title', sa.String(255), nullable=True),
|
||||
sa.Column('corporate_email', sa.String(255), nullable=True),
|
||||
sa.Column('is_enrolled', sa.Boolean(), default=True),
|
||||
sa.Column('notes', sa.Text(), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=False),
|
||||
)
|
||||
|
||||
# Create findings table
|
||||
op.create_table(
|
||||
'findings',
|
||||
sa.Column('id', sa.String(36), primary_key=True),
|
||||
sa.Column('tenant_id', sa.String(36), sa.ForeignKey('tenants.id'), nullable=False),
|
||||
sa.Column('asset_id', sa.String(36), sa.ForeignKey('assets.id'), nullable=True),
|
||||
sa.Column('executive_id', sa.String(36), sa.ForeignKey('executives.id'), nullable=True),
|
||||
sa.Column('title', sa.String(500), nullable=False),
|
||||
sa.Column('severity', sa.String(50), nullable=False),
|
||||
sa.Column('status', sa.String(50), nullable=False),
|
||||
sa.Column('category', sa.String(50), nullable=False),
|
||||
sa.Column('technical_description', sa.Text(), nullable=True),
|
||||
sa.Column('cve_id', sa.String(30), nullable=True),
|
||||
sa.Column('cvss_score', sa.Float(), nullable=True),
|
||||
sa.Column('affected_component', sa.String(500), nullable=True),
|
||||
sa.Column('evidence', sa.Text(), nullable=True),
|
||||
sa.Column('ai_summary', sa.Text(), nullable=True),
|
||||
sa.Column('ai_business_impact', sa.Text(), nullable=True),
|
||||
sa.Column('ai_impact_level', sa.String(20), nullable=True),
|
||||
sa.Column('ai_remediation_steps', sa.Text(), nullable=True),
|
||||
sa.Column('ai_fix_priority', sa.String(20), nullable=True),
|
||||
sa.Column('ai_generated_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('assignee_email', sa.String(255), nullable=True),
|
||||
sa.Column('due_date', sa.DateTime(), nullable=True),
|
||||
sa.Column('resolution_note', sa.Text(), nullable=True),
|
||||
sa.Column('resolved_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('verified_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('source', sa.String(100), nullable=True),
|
||||
sa.Column('is_top_risk', sa.Boolean(), default=False),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=False),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=False),
|
||||
)
|
||||
|
||||
# Create risk_scores table
|
||||
op.create_table(
|
||||
'risk_scores',
|
||||
sa.Column('id', sa.String(36), primary_key=True),
|
||||
sa.Column('tenant_id', sa.String(36), sa.ForeignKey('tenants.id'), nullable=False),
|
||||
sa.Column('score_date', sa.DateTime(), nullable=False),
|
||||
sa.Column('overall_score', sa.Float(), nullable=False),
|
||||
sa.Column('score_identity', sa.Float(), nullable=True),
|
||||
sa.Column('score_cloud', sa.Float(), nullable=True),
|
||||
sa.Column('score_network', sa.Float(), nullable=True),
|
||||
sa.Column('score_web', sa.Float(), nullable=True),
|
||||
sa.Column('score_credential', sa.Float(), nullable=True),
|
||||
sa.Column('score_digital_footprint', sa.Float(), nullable=True),
|
||||
sa.Column('score_third_party', sa.Float(), nullable=True),
|
||||
sa.Column('critical_count', sa.Integer(), default=0),
|
||||
sa.Column('high_count', sa.Integer(), default=0),
|
||||
sa.Column('medium_count', sa.Integer(), default=0),
|
||||
sa.Column('low_count', sa.Integer(), default=0),
|
||||
sa.Column('notes', sa.Text(), nullable=True),
|
||||
)
|
||||
|
||||
# Create attack_paths table
|
||||
op.create_table(
|
||||
'attack_paths',
|
||||
sa.Column('id', sa.String(36), primary_key=True),
|
||||
sa.Column('finding_id', sa.String(36), sa.ForeignKey('findings.id'), nullable=False),
|
||||
sa.Column('title', sa.String(500), nullable=False),
|
||||
sa.Column('ai_narrative', sa.Text(), nullable=True),
|
||||
sa.Column('nodes_json', sa.Text(), nullable=True),
|
||||
sa.Column('edges_json', sa.Text(), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=False),
|
||||
)
|
||||
|
||||
# Create audit_reports table
|
||||
op.create_table(
|
||||
'audit_reports',
|
||||
sa.Column('id', sa.String(36), primary_key=True),
|
||||
sa.Column('tenant_id', sa.String(36), sa.ForeignKey('tenants.id'), nullable=False),
|
||||
sa.Column('title', sa.String(500), nullable=False),
|
||||
sa.Column('report_date', sa.DateTime(), nullable=False),
|
||||
sa.Column('baseline_score', sa.Float(), nullable=True),
|
||||
sa.Column('executive_summary', sa.Text(), nullable=True),
|
||||
sa.Column('scope_description', sa.Text(), nullable=True),
|
||||
sa.Column('key_findings_json', sa.Text(), nullable=True),
|
||||
sa.Column('pdf_path', sa.String(500), nullable=True),
|
||||
sa.Column('is_baseline', sa.Boolean(), default=True),
|
||||
sa.Column('generated_by', sa.String(255), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=False),
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_table('audit_reports')
|
||||
op.drop_table('attack_paths')
|
||||
op.drop_table('risk_scores')
|
||||
op.drop_table('findings')
|
||||
op.drop_table('executives')
|
||||
op.drop_table('assets')
|
||||
op.drop_table('authorized_assets')
|
||||
op.drop_table('users')
|
||||
op.drop_table('tenants')
|
||||
@@ -145,7 +145,6 @@ async def seed():
|
||||
hashed_password=hash_password("TrustOS-Admin-2024!"),
|
||||
full_name="TrustOS Admin",
|
||||
role=UserRole.trustos_admin,
|
||||
tenant_id="acme-corp-demo-001",
|
||||
),
|
||||
]
|
||||
for u in users:
|
||||
|
||||
Reference in New Issue
Block a user