npm package: @rivtor/residency
Description: Schrems II Data Sovereignty Lock with Cloudflare Workers integration and DNS resolution hooks
Author: Rivtor
Latest version: 1.1.0

# @rivtor/residency

**Production-ready Schrems II data sovereignty lock with Cloudflare Workers integration**

## Features

- **DNS Resolution Hook**: Intercepts fetch and axios calls for automatic enforcement
- **Region Allowlist**: EU_STRICT, GDPR_ADEQUATE, and SCCS_ONLY modes
- **Cloudflare Workers**: Edge function template included for global enforcement
- **DPIA Audit Trail**: Data Protection Impact Assessment logging
- **Client & Server Support**: Works in both browser and Node.js environments
- **IP-Based Detection**: Automatic region detection from user IP

## Installation

```bash
npm install @rivtor/residency
# or
yarn add @rivtor/residency
# or
pnpm add @rivtor/residency
```

## Quick Start

### 1. Server-Side Middleware

```typescript
import { detectRegion, parseIpFromHeaders, isDestinationAllowed } from '@rivtor/residency/server';

// API route middleware
export async function GET(request: Request) {
  // Detect user region from IP
  const headers = Object.fromEntries(request.headers.entries());
  const ip = parseIpFromHeaders(headers);
  const region = await detectRegion(ip, headers);

  // Check if external API call is allowed
  const result = await isDestinationAllowed('https://api.example.com', region);

  if (!result.allowed) {
    return new Response('Request blocked by data residency policy', {
      status: 403,
    });
  }

  // Proceed with request
}
```

### 2. Client-Side Fetch Interception

```typescript
'use client';
import { installFetchInterception } from '@rivtor/residency/client';

export default function RootLayout() {
  useEffect(() => {
    // Install fetch interception
    const cleanup = installFetchInterception({
      blockNonEuEgress: true,
      blockedDestinations: [
        'analytics.google.com',
        'tracking.example.com'
      ]
    });

    return cleanup;
  }, []);

  return <YourApp />;
}

// All fetch calls are now checked
await fetch('https://api.example.com/data'); // Allowed or blocked based on policy
```

### 3. Cloudflare Workers Edge Function

```typescript
// worker.ts
import { detectRegionFromHeaders, isDestinationAllowed } from '@rivtor/residency/server';

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const headers = Object.fromEntries(request.headers.entries());
    const region = detectRegionFromHeaders(headers) || 'global';

    // Check if request is external
    if (isExternalRequest(request.url)) {
      const result = await isDestinationAllowed(request.url, region);

      if (!result.allowed) {
        return new Response('Request blocked by data residency policy', {
          status: 403,
          headers: {
            'X-Residency-Block-Reason': result.reason
          }
        });
      }
    }

    // Proxy the request
    return fetch(request);
  }
};
```

### 4. Region Detection

```typescript
import { isEuCountry, hasGdprAdequacy, detectRegion } from '@rivtor/residency/server';

// Check if country is in EU
const inEu = isEuCountry('DE'); // true
const inEu = isEuCountry('US'); // false

// Check GDPR adequacy
const hasAdequacy = hasGdprAdequacy('JP'); // true (Japan has adequacy)

// Detect region from IP
const region = await detectRegion('192.0.2.1', {});
// Returns: 'eu', 'us', 'asia', etc.
```

### 5. DPIA Logging

```typescript
import { logDpiaEvent, getDpiaLogs } from '@rivtor/residency/server';

// Log data transfer
await logDpiaEvent({
  type: 'data_transfer',
  sourceRegion: 'eu',
  destination: 'https://api.us-service.com',
  destinationRegion: 'us',
  allowed: false,
  reason: 'Destination not in GDPR adequate country',
  userId: 'user-123',
  ipAddress: '192.0.2.1'
});

// Get DPIA logs
const logs = await getDpiaLogs({
  userId: 'user-123',
  startDate: new Date('2024-01-01'),
  endDate: new Date('2024-01-31')
});
```

## API Reference

### Server Functions

```typescript
import {
  detectRegion,
  parseIpFromHeaders,
  isDestinationAllowed,
  detectRegionFromHeaders,
  isEuCountry,
  hasGdprAdequacy
} from '@rivtor/residency/server';

// Detect region from IP and headers
await detectRegion(
  ip: string,
  headers: Record<string, string>
): Promise<Region>;

// Parse IP from headers
parseIpFromHeaders(headers: Record<string, string>): string | null;

// Check if destination is allowed
await isDestinationAllowed(
  destination: string,
  region: Region
): Promise<{
  allowed: boolean;
  reason?: string;
  region?: string;
}>;

// Detect region from headers
detectRegionFromHeaders(headers: Record<string, string>): Region | null;

// Check if country is in EU
isEuCountry(countryCode: string): boolean;

// Check if country has GDPR adequacy
hasGdprAdequacy(countryCode: string): boolean;
```

### Client Functions

```typescript
import {
  installFetchInterception,
  uninstallFetchInterception,
  checkUrlAllowed
} from '@rivtor/residency/client';

// Install fetch interception
const cleanup = installFetchInterception(config: {
  blockNonEuEgress?: boolean;
  blockedDestinations?: string[];
  allowedRegions?: Region[];
  onBlocked?: (url: string, reason: string) => void;
}): () => void;

// Uninstall fetch interception
uninstallFetchInterception(): void;

// Check if URL is allowed
await checkUrlAllowed(url: string): Promise<{
  allowed: boolean;
  reason?: string;
}>;
```

### DPIA Logging

```typescript
import {
  logDpiaEvent,
  getDpiaLogs,
  exportDpiaLogs
} from '@rivtor/residency/server';

// Log DPIA event
await logDpiaEvent({
  type: 'data_transfer' | 'data_access' | 'data_processing';
  sourceRegion: string;
  destination: string;
  destinationRegion?: string;
  allowed: boolean;
  reason?: string;
  userId?: string;
  ipAddress?: string;
  metadata?: Record<string, unknown>;
}): Promise<void>;

// Get DPIA logs
await getDpiaLogs(filters?: {
  userId?: string;
  type?: string;
  startDate?: Date;
  endDate?: Date;
  limit?: number;
}): Promise<DpiaLog[]>;

// Export DPIA logs
await exportDpiaLogs(format: 'json' | 'csv'): Promise<Buffer>;
```

## Configuration

```typescript
import type { DataResidencyConfig } from '@rivtor/residency';
import { configureResidency } from '@rivtor/residency/server';

const config: Partial<DataResidencyConfig> = {
  // Default region
  defaultRegion: 'eu',

  // Allowed regions
  allowedRegions: ['eu', 'eea', 'gdpr_adequate'],

  // Block non-EU egress
  blockNonEuEgress: true,

  // Blocked destinations
  blockedDestinations: [
    'analytics.google.com',
    'tracking.example.com',
    '*.us-api.com'
  ],

  // Destination rules
  destinationRules: [
    {
      pattern: '*.supabase.co',
      allowedRegions: ['eu'],
      description: 'Supabase EU region'
    },
    {
      pattern: '*.cloudflare.com',
      allowedRegions: ['global'],
      description: 'Cloudflare edge functions'
    }
  ],

  // DPIA logging
  logDpiaEvents: true,
  dpiaRetentionDays: 365,

  // DNS enforcement
  enforceDnsCheck: true
};

configureResidency(config);
```

## Region Modes

### EU_STRICT

Only allows data to stay within the EU/EEA:

```typescript
configureResidency({
  mode: 'EU_STRICT',
  allowedRegions: ['eu', 'eea']
});
```

### GDPR_ADEQUATE

Allows data transfer to countries with GDPR adequacy decisions:

```typescript
configureResidency({
  mode: 'GDPR_ADEQUATE',
  allowedRegions: ['eu', 'eea', 'gdpr_adequate']
  // Includes: Japan, UK, Switzerland, Canada (commercial), etc.
});
```

### SCCS_ONLY

Only allows transfers with Standard Contractual Clauses (SCCs):

```typescript
configureResidency({
  mode: 'SCCS_ONLY',
  allowWithScc: true,
  requireSccVerification: true
});
```

## GDPR Adequate Countries

The following countries are recognized as having GDPR adequacy:

- **European Union**: All EU member states
- **European Economic Area**: Norway, Iceland, Liechtenstein
- **United Kingdom**: Post-Brexit adequacy
- **Switzerland**: Federal Data Protection Act adequacy
- **Japan**: Mutual adequacy arrangement
- **Canada**: Commercial adequacy (PIPEDA)
- **Andorra**, **Argentina**, **Faroe Islands**, **Guernsey**, **Israel**, **Isle of Man**, **Jersey**, **New Zealand**, **Uruguay**

## Cloudflare Workers Template

```typescript
// workers-site/data-residency-worker.ts
import { detectRegionFromHeaders, isDestinationAllowed } from '@rivtor/residency/server';

interface Env {
  RESIDENCY_MODE: 'EU_STRICT' | 'GDPR_ADEQUATE' | 'SCCS_ONLY';
}

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const headers = Object.fromEntries(request.headers.entries());

    // Detect region from CF-IPCountry header
    const region = detectRegionFromHeaders(headers) || 'global';

    // Check external requests
    const url = new URL(request.url);
    if (isExternalRequest(url.href)) {
      const result = await isDestinationAllowed(url.href, region);

      if (!result.allowed) {
        return new Response('Request blocked by data residency policy', {
          status: 403,
          headers: {
            'Content-Type': 'application/json',
            'X-Residency-Block-Reason': result.reason || 'Unknown'
          }
        });
      }
    }

    // Proxy request
    return fetch(request);
  }
};

function isExternalRequest(url: string): boolean {
  const parsed = new URL(url);
  return !parsed.hostname.endsWith('.yourdomain.com');
}
```

## Best Practices

1. **Enable DPIA Logging**: Always log data transfers for audit purposes
2. **Use Edge Enforcement**: Deploy Cloudflare Workers for global enforcement
3. **Regular Reviews**: Review and update region allowlists quarterly
4. **Document SCCs**: Keep records of Standard Contractual Clauses
5. **Monitor Blocked Requests**: Alert on high block rates for potential issues

## Legal Compliance

This package helps comply with:

- **GDPR Article 44**: General principle for transfers
- **GDPR Article 45**: Transfers on the basis of an adequacy decision
- **GDPR Article 46**: Transfers subject to appropriate safeguards
- **Schrems II**: Data Protection Framework for EU-US transfers

## License

MIT

---

Made with ❤️ by [Rivtor](https://rivtor.com)