Internal

Developer Guide

Complete guide for developers: setup, architecture, testing, and deployment.

InsiderShield Developer Guide

Complete guide for developers working on the InsiderShield codebase.

Table of Contents

  1. Development Setup
  2. Project Structure
  3. Tech Stack
  4. Development Workflow
  5. Testing
  6. Database
  7. API Development
  8. Authentication
  9. Integrations
  10. Deployment
  11. Troubleshooting

Development Setup

Prerequisites

  • Node.js 20+ (LTS recommended)
  • pnpm 10+ (npm install -g pnpm)
  • Git
  • Supabase CLI (brew install supabase/tap/supabase)
  • Docker (for local Supabase)

Initial Setup

# Clone repository
git clone https://github.com/yourusername/insidershield.git
cd insidershield

# Install dependencies
pnpm install

# Copy environment variables
cp .env.production.example .env.local

# Generate secure secrets
openssl rand -base64 32  # Use for OAUTH_STATE_SECRET
openssl rand -base64 32  # Use for INTEGRATIONS_ENCRYPTION_KEY
openssl rand -hex 32     # Use for WEBHOOK_SECRET

# Start Supabase locally (optional)
supabase start

# Run database migrations
supabase migration up

# Start development server
pnpm dev

Visit http://localhost:3000

Required Environment Variables

Create .env.local with these required variables:

# Supabase
NEXT_PUBLIC_SUPABASE_URL=your_supabase_url
NEXT_PUBLIC_SUPABASE_ANON_KEY=your_anon_key
SUPABASE_SERVICE_ROLE_KEY=your_service_role_key

# Security
OAUTH_STATE_SECRET=your_oauth_secret_32_chars_min
WEBHOOK_SECRET=your_webhook_secret
INTEGRATIONS_ENCRYPTION_KEY=your_encryption_key_32_chars

# App URLs
NEXT_PUBLIC_APP_URL=http://localhost:3000
NEXT_PUBLIC_SITE_URL=http://localhost:3000

# Stripe (for billing)
STRIPE_SECRET_KEY=sk_test_...
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...

# OAuth Providers
GOOGLE_CLIENT_ID=your_google_client_id
GOOGLE_CLIENT_SECRET=your_google_client_secret
GITHUB_CLIENT_ID=your_github_client_id
GITHUB_CLIENT_SECRET=your_github_client_secret
SLACK_CLIENT_ID=your_slack_client_id
SLACK_CLIENT_SECRET=your_slack_client_secret

# Email (Resend)
RESEND_API_KEY=re_...
RESEND_FROM_EMAIL=noreply@insidershield.io

# Optional
OPENROUTER_API_KEY=your_openrouter_key  # For AI features
SENTRY_DSN=your_sentry_dsn  # For error tracking
CRON_SECRET=your_cron_secret  # For scheduled jobs

Project Structure

insidershield/
├── app/                      # Next.js App Router
│   ├── api/                  # API routes
│   │   ├── ai/              # AI chat endpoints
│   │   ├── auth/            # Authentication
│   │   ├── controls/        # Control management
│   │   ├── integrations/    # Integration OAuth & webhooks
│   │   ├── policies/        # Policy management
│   │   ├── reports/         # Report generation
│   │   └── webhooks/        # Incoming webhooks
│   ├── auth/                # Auth pages
│   ├── dashboard/           # Protected dashboard routes
│   ├── docs/                # Documentation pages
│   └── layout.tsx           # Root layout
├── components/              # React components
│   ├── ui/                  # Shadcn/UI primitives
│   ├── dashboard/           # Dashboard-specific components
│   ├── billing/             # Billing components
│   └── site/                # Marketing site components
├── lib/                     # Business logic & utilities
│   ├── data/                # Data access layer (Server Actions)
│   │   ├── actions.ts       # Generic actions
│   │   ├── *-actions.ts     # Domain-specific actions
│   │   ├── queries.ts       # Database queries
│   │   └── plan-guard.ts    # Plan-based access control
│   ├── supabase/            # Database clients
│   │   ├── client.ts        # Client-side
│   │   ├── server.ts        # Server-side
│   │   ├── admin.ts         # Admin client
│   │   └── database.types.ts # Generated types
│   ├── integrations/        # Third-party integrations
│   │   ├── github/          # GitHub integration
│   │   ├── google-workspace/ # Google integration
│   │   └── slack/           # Slack integration
│   ├── security/            # Security utilities
│   │   ├── csrf.ts          # CSRF protection
│   │   ├── oauth-state.ts   # OAuth state management
│   │   └── rate-limit.ts    # Rate limiting
│   ├── frameworks/          # Compliance frameworks
│   ├── email/               # Email service
│   └── config/              # Configuration
├── e2e/                     # End-to-end tests
├── docs/                    # Documentation (markdown)
├── public/                  # Static assets
├── supabase/               # Supabase config
│   ├── migrations/         # SQL migrations
│   └── config.toml         # Supabase configuration
├── jest.config.js          # Jest configuration
├── playwright.config.ts    # Playwright configuration
├── next.config.mjs         # Next.js configuration
├── tsconfig.json           # TypeScript configuration
└── package.json            # Dependencies

Tech Stack

Frontend

  • Next.js 16 - React framework with App Router
  • React 19 - UI library
  • TypeScript 5 - Type safety
  • Tailwind CSS 4 - Styling
  • Shadcn/UI - Component library
  • Radix UI - Headless components
  • Lucide - Icons
  • Recharts - Charts
  • React Hook Form - Form management
  • Zod - Schema validation

Backend

  • Next.js API Routes - RESTful API
  • Supabase - PostgreSQL + Auth + Storage
  • Stripe - Payment processing
  • Resend - Email delivery

Development

  • Jest - Unit testing
  • Playwright - E2E testing
  • ESLint - Linting
  • Prettier - Code formatting
  • Husky - Git hooks
  • TypeScript - Type checking

Deployment

  • Vercel - Hosting
  • Supabase - Database hosting
  • GitHub Actions - CI/CD

Development Workflow

Branch Strategy

  • main - Production branch (protected)
  • develop - Development branch
  • feature/* - Feature branches
  • fix/* - Bug fix branches
  • hotfix/* - Production hotfixes

Making Changes

# Create feature branch
git checkout -b feature/my-feature

# Make changes
# Write tests
# Run tests
pnpm test

# Lint and format
pnpm lint:fix
pnpm format

# Commit (triggers pre-commit hooks)
git add .
git commit -m "feat: add my feature"

# Push and create PR
git push origin feature/my-feature

Commit Message Format

Follow Conventional Commits:

type(scope): description

[optional body]

[optional footer]

Types:

  • feat: New feature
  • fix: Bug fix
  • docs: Documentation
  • style: Formatting
  • refactor: Code restructuring
  • test: Adding tests
  • chore: Maintenance

Examples:

feat(integrations): add Slack OAuth flow
fix(auth): resolve login redirect loop
docs(api): document webhook endpoints
test(security): add CSRF validation tests

Testing

Unit Tests (Jest)

# Run all tests
pnpm test

# Run with coverage
pnpm test:coverage

# Watch mode
pnpm test:watch

# Run specific test file
pnpm test oauth-state.test.ts

Writing Tests:

// lib/security/__tests__/example.test.ts
import { describe, it, expect } from '@jest/globals'
import { myFunction } from '../example'

describe('myFunction', () => {
  it('should return expected value', () => {
    const result = myFunction('input')
    expect(result).toBe('expected')
  })

  it('should handle edge cases', () => {
    expect(() => myFunction('')).toThrow()
  })
})

E2E Tests (Playwright)

# Run E2E tests
pnpm test:e2e

# Run with UI
pnpm test:e2e:ui

# Run specific test
pnpm test:e2e auth.spec.ts

# Debug mode
pnpm test:e2e --debug

Writing E2E Tests:

// e2e/example.spec.ts
import { test, expect } from '@playwright/test'

test('user can complete signup flow', async ({ page }) => {
  await page.goto('/auth/signup')

  await page.getByLabel('Email').fill('test@example.com')
  await page.getByLabel('Password').fill('SecurePass123!')
  await page.getByRole('button', { name: 'Sign Up' }).click()

  await expect(page).toHaveURL('/dashboard')
})

Test Coverage Goals

  • Overall: 60%+
  • Critical paths: 80%+ (auth, payments, security)
  • API routes: 70%+
  • Components: 60%+

Database

Supabase Architecture

  • PostgreSQL 14+ - Relational database
  • Row Level Security (RLS) - Data access control
  • Auth - User authentication
  • Storage - File storage (policies, reports, evidence)
  • Realtime - WebSocket subscriptions

Key Tables

-- Organizations
organizations (id, name, industry, framework, created_at)

-- Users & Membership
organization_members (organization_id, user_id, role)

-- Compliance
controls (id, organization_id, number, title, status, framework)
findings (id, organization_id, severity, status, integration_type)
policies (id, organization_id, type, file_path, status)

-- Integrations
integrations (id, organization_id, type, credentials, status)

-- Billing
subscriptions (organization_id, plan, stripe_subscription_id)

-- Activity
activities (organization_id, user_id, type, message, metadata)

Migrations

# Create migration
supabase migration new add_new_table

# Run migrations
supabase migration up

# Reset database (development only!)
supabase db reset

# Generate TypeScript types
supabase gen types typescript --local > lib/supabase/database.types.ts

Data Access Pattern

Use Server Actions for data access:

// lib/data/example-actions.ts
'use server'

import { createClient } from '@/lib/supabase/server'

export async function getMyData() {
  const supabase = await createClient()

  const {
    data: { user },
  } = await supabase.auth.getUser()
  if (!user) return { error: 'Unauthorized' }

  const { data, error } = await supabase.from('my_table').select('*').eq('user_id', user.id)

  if (error) return { error: error.message }
  return { data }
}

API Development

Creating API Routes

// app/api/example/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { createClient } from '@/lib/supabase/server'
import { validateCsrf } from '@/lib/security/csrf'

export async function POST(request: NextRequest) {
  // 1. CSRF validation for state-changing requests
  if (!validateCsrf(request)) {
    return NextResponse.json({ error: 'Invalid CSRF token' }, { status: 403 })
  }

  // 2. Authentication
  const supabase = await createClient()
  const {
    data: { user },
  } = await supabase.auth.getUser()

  if (!user) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
  }

  // 3. Parse request body
  const body = await request.json()

  // 4. Validate input
  // Use Zod schema for validation

  // 5. Business logic
  // ...

  // 6. Return response
  return NextResponse.json({ success: true })
}

API Security Checklist

  • ✅ CSRF validation on POST/PUT/DELETE
  • ✅ Authentication check
  • ✅ Authorization (RLS or manual check)
  • ✅ Input validation (Zod schemas)
  • ✅ Rate limiting
  • ✅ Error handling (don't leak sensitive info)
  • ✅ Logging (for debugging)

Rate Limiting

import { rateLimit } from '@/lib/security/rate-limit'

export async function POST(request: NextRequest) {
  // Apply rate limit
  const { success, limit, remaining } = await rateLimit(request)

  if (!success) {
    return NextResponse.json({ error: 'Too many requests' }, { status: 429 })
  }

  // Continue with request...
}

Authentication

Supabase Auth Flow

  1. Client-side: User signs up/in
  2. Cookie: Session stored in httpOnly cookie
  3. Server-side: Validate session on each request
  4. RLS: Database enforces row-level security

Checking Authentication

// Server Component
import { createClient } from '@/lib/supabase/server'

export default async function ProtectedPage() {
  const supabase = await createClient()
  const { data: { user } } = await supabase.auth.getUser()

  if (!user) redirect('/auth/login')

  return <div>Protected content</div>
}
// Client Component
'use client'

import { createClient } from '@/lib/supabase/client'
import { useEffect, useState } from 'react'

export function ProtectedComponent() {
  const [user, setUser] = useState(null)
  const supabase = createClient()

  useEffect(() => {
    supabase.auth.getUser().then(({ data: { user } }) => {
      setUser(user)
    })
  }, [])

  if (!user) return <div>Loading...</div>

  return <div>Protected content</div>
}

Integrations

Creating New Integration

  1. Define OAuth Flow
// lib/integrations/my-service/client.ts
export class MyServiceClient {
  private accessToken: string

  constructor(accessToken: string) {
    this.accessToken = accessToken
  }

  async getData() {
    const response = await fetch('https://api.myservice.com/data', {
      headers: {
        Authorization: `Bearer ${this.accessToken}`,
      },
    })
    return response.json()
  }
}
  1. Add OAuth Routes
// app/api/integrations/my-service/connect/route.ts
export async function GET(request: NextRequest) {
  // 1. Create OAuth state
  const state = createOAuthState({
    provider: 'my-service',
    organization_id: orgId,
    user_id: userId,
  })

  // 2. Redirect to OAuth provider
  const authUrl = new URL('https://myservice.com/oauth/authorize')
  authUrl.searchParams.set('client_id', process.env.MY_SERVICE_CLIENT_ID!)
  authUrl.searchParams.set('redirect_uri', callbackUrl)
  authUrl.searchParams.set('state', state)
  authUrl.searchParams.set('scope', 'read:data')

  return NextResponse.redirect(authUrl.toString())
}
  1. Add Callback Route
// app/api/integrations/my-service/callback/route.ts
export async function GET(request: NextRequest) {
  // 1. Verify OAuth state
  const state = searchParams.get('state')
  const verified = verifyOAuthState(state, 'my-service')

  if (!verified) {
    return NextResponse.redirect('/dashboard/integrations?error=invalid_state')
  }

  // 2. Exchange code for token
  const code = searchParams.get('code')
  const tokenResponse = await fetch('https://myservice.com/oauth/token', {
    method: 'POST',
    body: JSON.stringify({
      client_id: process.env.MY_SERVICE_CLIENT_ID,
      client_secret: process.env.MY_SERVICE_CLIENT_SECRET,
      code,
      grant_type: 'authorization_code',
    }),
  })

  const { access_token } = await tokenResponse.json()

  // 3. Store encrypted credentials
  const encrypted = await encryptToken(access_token)
  await supabase.from('integrations').insert({
    organization_id: verified.organization_id,
    type: 'my-service',
    credentials: encrypted,
    status: 'active',
  })

  return NextResponse.redirect('/dashboard/integrations?success=true')
}
  1. Add Scanner
// lib/integrations/my-service/scanner.ts
export async function scanMyService(integrationId: string, orgId: string) {
  // 1. Get integration credentials
  const integration = await getIntegration(integrationId)
  const accessToken = await decryptToken(integration.credentials)

  // 2. Initialize client
  const client = new MyServiceClient(accessToken)

  // 3. Run security checks
  const findings = []

  const data = await client.getData()
  if (data.securityIssue) {
    findings.push({
      organization_id: orgId,
      integration_type: 'my-service',
      severity: 'high',
      title: 'Security Issue Detected',
      description: 'Description of issue',
      remediation: 'How to fix',
    })
  }

  // 4. Save findings
  await saveFindings(findings)

  return { findingsCount: findings.length }
}

Deployment

Vercel Deployment

# Install Vercel CLI
npm i -g vercel

# Deploy to preview
vercel

# Deploy to production
vercel --prod

Environment Variables

Set in Vercel dashboard:

  • Project Settings → Environment Variables
  • Add all variables from .env.local
  • Set for Production, Preview, Development

Cron Jobs

Defined in vercel.json:

{
  "crons": [
    {
      "path": "/api/cron/sync-integrations",
      "schedule": "0 1 * * *"
    },
    {
      "path": "/api/cron/process-notifications",
      "schedule": "30 1 * * *"
    }
  ]
}

Database Migrations

# Production migrations (via Supabase dashboard)
1. Go to Supabase project → SQL Editor
2. Run migration SQL
3. Verify with test queries

# Or use CLI
supabase db push

Troubleshooting

Common Issues

Build Fails with TypeScript Errors

# Clear cache and rebuild
rm -rf .next
pnpm build

Database Connection Issues

# Check Supabase status
supabase status

# Restart local Supabase
supabase stop
supabase start

OAuth Redirect Not Working

  • Verify OAuth callback URL in provider settings
  • Check NEXT_PUBLIC_APP_URL environment variable
  • Ensure HTTPS in production

Tests Failing

# Clear Jest cache
pnpm jest --clearCache

# Run tests with verbose output
pnpm test --verbose

Debug Mode

# Next.js debug mode
NODE_OPTIONS='--inspect' pnpm dev

# Verbose logging
DEBUG=* pnpm dev

Getting Help

  • Internal Docs: Check /docs directory
  • Slack: #engineering channel
  • GitHub Issues: Create issue with bug label
  • Pair Programming: Ask in team chat

Happy coding! 🚀