npm package: @goobits/security
Description: Comprehensive security utilities for Goobits projects - CSRF, reCAPTCHA, validation, rate limiting, admin auth, and audit logging
Author: Goobits
Latest version: 1.2.0

# @goobits/security

**Version 1.2.0** - Stable

Comprehensive security utilities for Goobits projects including CSRF protection, reCAPTCHA verification, request validation, rate limiting, admin authentication, and audit logging. Built for SvelteKit applications with integrated logging via @goobits/logger.

## 🔒 Features

- 🛡️ **CSRF Protection**: Double-submit cookie pattern with timing-safe comparison and token expiration
- 🤖 **reCAPTCHA Verification**: Support for both v2 and v3 with score thresholds and flexible options API
- ✅ **Request Validation**: Zod-based middleware for body, query, and params
- ⏱️ **Rate Limiting**: In-memory and Redis-based rate limiting with factory pattern ⭐ NEW in v1.2.0
- 🔐 **Admin Authentication**: JWT-based admin auth with API key fallback ⭐ NEW in v1.2.0
- 📝 **Audit Logging**: Security-sensitive operation tracking for GDPR compliance ⭐ NEW in v1.2.0
- ⏰ **Token Expiration**: Automatic CSRF token expiration and cleanup (1-hour default)
- 📊 **Integrated Logging**: Built-in logging via @goobits/logger
- 🔧 **SvelteKit Native**: Designed for SvelteKit's request/response model
- 🧪 **Test-Friendly**: Debug flags for development and testing
- 📦 **Minimal Dependencies**: Only depends on @goobits/logger, jsonwebtoken, and peer deps

## 📦 Installation

```bash
# In workspace monorepo
pnpm add @goobits/security

# Or in individual package
npm install @goobits/security zod
```

## 🛡️ CSRF Protection

Implements the double-submit cookie pattern for CSRF protection with timing-safe token comparison.

### Quick Start

```javascript
// src/routes/api/contact/+server.js
import { validateCsrfToken, createCsrfProtectedResponse } from '@goobits/security'
import { json } from '@sveltejs/kit'

export async function POST({ request }) {
  // Verify CSRF token
  if (!validateCsrfToken(request)) {
    return json({ error: 'Invalid CSRF token' }, { status: 403 })
  }

  // Process the request...
  const result = await processContactForm(data)

  // Return response with new CSRF token
  return createCsrfProtectedResponse(
    json({ success: true, data: result })
  )
}
```

### API Reference

#### `generateCsrfToken()`

Generate a new random CSRF token.

```javascript
import { generateCsrfToken } from '@goobits/security'

const token = generateCsrfToken()
// Returns: 64-character hex string
```

#### `setCsrfCookie(response, token)`

Set CSRF token as HTTP-only cookie in response.

```javascript
import { setCsrfCookie } from '@goobits/security'

export async function GET() {
  const response = json({ message: 'Hello' })
  const token = generateCsrfToken()
  setCsrfCookie(response, token)
  return response
}
```

#### `getCsrfToken(request)`

Extract CSRF token from request cookies.

```javascript
import { getCsrfToken } from '@goobits/security'

const token = getCsrfToken(request)
// Returns: token string or null
```

#### `validateCsrfToken(request)`

Validate CSRF token using double-submit cookie pattern with timing-safe comparison.

```javascript
import { validateCsrfToken } from '@goobits/security'

if (!validateCsrfToken(request)) {
  return json({ error: 'Invalid CSRF token' }, { status: 403 })
}
```

#### `createCsrfProtectedResponse(response)`

Convenience function to generate token and set cookie in one call.

```javascript
import { createCsrfProtectedResponse } from '@goobits/security'

return createCsrfProtectedResponse(
  json({ success: true })
)
```

#### `generateCsrfTokenWithExpiry(options)` ⭐ NEW in v1.1.0

Generate a CSRF token with expiration tracking.

```javascript
import { generateCsrfTokenWithExpiry } from '@goobits/security'

// Default: 1 hour expiry with tracking
const token = generateCsrfTokenWithExpiry()

// Custom expiry time
const token = generateCsrfTokenWithExpiry({ expiryMs: 30 * 60 * 1000 }) // 30 minutes

// Without expiry tracking
const token = generateCsrfTokenWithExpiry({ trackExpiry: false })
```

#### `validateCsrfToken(request, options)` - Enhanced in v1.1.0

Now supports expiry checking with options parameter:

```javascript
import { validateCsrfToken } from '@goobits/security'

// Basic validation (backward compatible)
if (!validateCsrfToken(request)) {
  return json({ error: 'Invalid CSRF token' }, { status: 403 })
}

// With expiry checking
if (!validateCsrfToken(request, { checkExpiry: true })) {
  return json({ error: 'Invalid or expired CSRF token' }, { status: 403 })
}
```

#### Token Store Management ⭐ NEW in v1.1.0

Utilities for managing the in-memory token store:

```javascript
import {
  getCsrfTokenStoreSize,
  cleanupCsrfTokens,
  clearCsrfTokenStore
} from '@goobits/security'

// Get number of tracked tokens
console.log(`Active tokens: ${ getCsrfTokenStoreSize() }`)

// Manually cleanup expired tokens
const cleaned = cleanupCsrfTokens()
console.log(`Cleaned up ${ cleaned } expired tokens`)

// Clear all tokens (useful for testing)
clearCsrfTokenStore()
```

### Configuration

```javascript
// Disable CSRF validation for testing (DO NOT USE IN PRODUCTION)
// Set environment variable: DISABLE_CSRF=true
```

**Cookie Settings**:
- Name: `csrf-token`
- HttpOnly: `true`
- Secure: `true` (production), `false` (development)
- SameSite: `lax`
- Path: `/`
- MaxAge: 24 hours

**Header**: `X-CSRF-Token`

### Client-Side Integration

```javascript
// Fetch the CSRF token from cookie and send in header
async function submitForm(data) {
  const csrfToken = getCookie('csrf-token')

  const response = await fetch('/api/contact', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-CSRF-Token': csrfToken
    },
    body: JSON.stringify(data)
  })

  return response.json()
}

function getCookie(name) {
  const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'))
  return match ? match[2] : null
}
```

## 🤖 reCAPTCHA Verification

Server-side reCAPTCHA token verification supporting both v2 (checkbox) and v3 (score-based).

### Setup

```bash
# Set environment variable
RECAPTCHA_SECRET_KEY=your_secret_key_here
```

### Basic Usage

```javascript
// src/routes/api/contact/+server.js
import { verifyRecaptchaToken } from '@goobits/security'
import { json } from '@sveltejs/kit'

export async function POST({ request }) {
  const data = await request.json()
  const { recaptchaToken } = data

  // Verify token (v2 or v3)
  const isValid = await verifyRecaptchaToken(recaptchaToken)

  if (!isValid) {
    return json({ error: 'reCAPTCHA verification failed' }, { status: 400 })
  }

  // Process the request...
  return json({ success: true })
}
```

### reCAPTCHA v3 with Score and Action

```javascript
import { verifyRecaptchaToken } from '@goobits/security'

// Positional arguments (backward compatible)
const isValid = await verifyRecaptchaToken(
  token,
  'contact_form', // Expected action
  0.7             // Minimum score (0.0 - 1.0)
)

// Options object pattern ⭐ NEW in v1.1.0
const isValid = await verifyRecaptchaToken(token, {
  action: 'contact_form',
  minScore: 0.7,
  allowInDevelopment: false
})

if (!isValid) {
  return json({ error: 'reCAPTCHA failed' }, { status: 400 })
}
```

### Detailed Verification

For debugging and monitoring, use the detailed verification function:

```javascript
import { verifyRecaptchaTokenWithDetails } from '@goobits/security'

const result = await verifyRecaptchaTokenWithDetails(token, 'submit', 0.5)

if (!result.success) {
  console.error('Verification failed:', result.error, result.details)
  return json({ error: 'Verification failed' }, { status: 400 })
}

// Access detailed information
console.log('Score:', result.score)
console.log('Action:', result.action)
console.log('Timestamp:', result.challenge_ts)
```

### API Reference

#### `verifyRecaptchaToken(token, actionOrOptions, minScore)` - Enhanced in v1.1.0

Now supports both positional arguments (backward compatible) and options object pattern.

**Positional Arguments** (backward compatible):
- `token` (string, required): The reCAPTCHA response token
- `action` (string, optional): Expected action name (v3 only)
- `minScore` (number, optional, default: 0.5): Minimum score threshold (v3 only, 0.0 to 1.0)

**Options Object** ⭐ NEW:
- `token` (string, required): The reCAPTCHA response token
- `options` (object, optional):
  - `action` (string): Expected action name (v3 only)
  - `minScore` (number, default: 0.5): Minimum score threshold (0.0 to 1.0)
  - `secretKey` (string): Override RECAPTCHA_SECRET_KEY env var
  - `allowInDevelopment` (boolean, default: true): Bypass verification in dev when secret key is missing

**Returns**: `Promise<boolean>`

**Examples**:
```javascript
// Positional args (backward compatible)
await verifyRecaptchaToken(token, 'submit_form', 0.7)

// Options object
await verifyRecaptchaToken(token, {
  action: 'submit_form',
  minScore: 0.7,
  allowInDevelopment: false
})
```

#### `verifyRecaptchaTokenWithDetails(token, action, minScore)`

Same parameters as above, but returns detailed result object:

```typescript
{
  success: boolean
  score?: number          // v3 only
  action?: string         // v3 only
  challenge_ts?: string   // ISO timestamp
  hostname?: string       // Request hostname
  error?: string          // Error message if failed
  details?: object        // Additional error details
  devBypass?: boolean     // True if bypassed in development
}
```

### Development Mode

In development (NODE_ENV !== 'production'), if RECAPTCHA_SECRET_KEY is not set:
- `verifyRecaptchaToken()` returns `true` (bypasses verification)
- `verifyRecaptchaTokenWithDetails()` returns `{ success: false, devBypass: true }`

In production, missing secret key always fails verification.

## ✅ Request Validation

Zod-based middleware for validating request body, query parameters, and URL parameters.

### Basic Usage

```javascript
// src/routes/api/users/+server.js
import { withValidation } from '@goobits/security'
import { z } from 'zod'
import { json } from '@sveltejs/kit'

// Define schemas
const createUserSchema = z.object({
  name: z.string().min(2).max(100),
  email: z.string().email(),
  age: z.number().int().positive().optional()
})

// Wrap handler with validation
export const POST = withValidation({
  body: createUserSchema
})(async (event) => {
  const { body } = event.locals.validated

  // body is now typed and validated
  const user = await createUser(body)

  return json({ success: true, user })
})
```

### Validating Query Parameters

```javascript
import { withValidation } from '@goobits/security'
import { z } from 'zod'

const searchSchema = z.object({
  q: z.string().min(1),
  page: z.string().regex(/^\d+$/).transform(Number).optional(),
  limit: z.string().regex(/^\d+$/).transform(Number).optional()
})

export const GET = withValidation({
  query: searchSchema
})(async (event) => {
  const { query } = event.locals.validated

  // query.q is string, query.page and query.limit are numbers
  const results = await search(query.q, query.page, query.limit)

  return json({ results })
})
```

### Validating URL Parameters

```javascript
// src/routes/api/users/[id]/+server.js
import { withValidation } from '@goobits/security'
import { z } from 'zod'

const paramsSchema = z.object({
  id: z.string().uuid()
})

export const GET = withValidation({
  params: paramsSchema
})(async (event) => {
  const { params } = event.locals.validated

  const user = await getUserById(params.id)

  return json({ user })
})
```

### Combined Validation

```javascript
const bodySchema = z.object({
  name: z.string().min(2),
  status: z.enum(['active', 'inactive'])
})

const querySchema = z.object({
  notify: z.string().transform(val => val === 'true')
})

const paramsSchema = z.object({
  id: z.string().uuid()
})

export const PUT = withValidation({
  body: bodySchema,
  query: querySchema,
  params: paramsSchema
})(async (event) => {
  const { body, query, params } = event.locals.validated

  const user = await updateUser(params.id, body, query.notify)

  return json({ user })
})
```

### Alternative Pattern: `getInputValidator`

For more control, use the validator directly:

```javascript
import { getInputValidator } from '@goobits/security'
import { z } from 'zod'

const schema = z.object({
  email: z.string().email()
})

export async function POST({ request }) {
  const validator = getInputValidator(schema)
  const body = await request.json()

  const result = validator(body)

  if (!result.success) {
    return json({
      error: 'Validation failed',
      details: result.error
    }, { status: 400 })
  }

  // Use result.data (validated and typed)
  return json({ success: true, email: result.data.email })
}
```

### Error Responses

Validation failures return:

```json
{
  "success": false,
  "error": "Invalid request data",
  "details": [
    {
      "code": "too_small",
      "minimum": 2,
      "path": ["name"],
      "message": "String must contain at least 2 character(s)"
    }
  ]
}
```

## ⏱️ Rate Limiting

⭐ **NEW in v1.2.0** - Protect your API endpoints from abuse with in-memory or Redis-based rate limiting.

### Quick Start

```javascript
// src/routes/api/contact/+server.js
import { withRateLimit } from '@goobits/security'
import { json } from '@sveltejs/kit'

export const POST = withRateLimit({
  action: 'contact-form',
  windowMs: 60000,      // 1 minute
  maxRequests: 5,       // 5 requests per minute
  message: 'Too many requests. Please try again later.'
})(async ({ request, locals }) => {
  // Your handler code here
  return json({ success: true })
})
```

### Factory Pattern (In-Memory vs Redis)

The rate limiter automatically selects the appropriate backend:

```javascript
import { getRateLimiter } from '@goobits/security'

// Get the configured rate limiter (in-memory or Redis)
const rateLimiter = await getRateLimiter()

// Use it directly
const result = await rateLimiter.rateLimitRequest('user-123', 'login')
if (!result.allowed) {
  return json({ error: 'Rate limit exceeded' }, { status: 429 })
}
```

**Configuration**:
- **Development**: Uses in-memory rate limiting by default
- **Production**: Uses Redis if `REDIS_HOST` or `REDIS_URL` is set
- **Force Redis in dev**: Set `FORCE_REDIS_RATELIMIT=true`

### Middleware Patterns

#### Form Submission Rate Limiting

```javascript
import { rateLimitFormSubmission } from '@goobits/security'

export const POST = async ({ request, locals }) => {
  const data = await request.json()

  // Rate limit by IP + email
  const rateLimitResult = await rateLimitFormSubmission(
    locals.clientIP,
    data.email,
    'contact',
    { maxAttempts: 3, windowMinutes: 10 }
  )

  if (!rateLimitResult.allowed) {
    return json({
      error: 'Too many attempts. Please try again later.',
      retryAfter: rateLimitResult.retryAfter
    }, { status: 429 })
  }

  // Process form...
  return json({ success: true })
}
```

#### Custom Rate Limit Handler

```javascript
import { createRateLimitHandler } from '@goobits/security'

const apiRateLimit = createRateLimitHandler({
  action: 'api-call',
  windowMs: 60000,
  maxRequests: 100,
  keyGenerator: (event) => event.locals.userId || event.locals.clientIP
})

export const GET = async (event) => {
  const rateLimitResponse = await apiRateLimit(event)
  if (rateLimitResponse) {
    return rateLimitResponse // 429 response
  }

  // Handle request...
  return json({ data: [] })
}
```

### API Reference

#### `withRateLimit(options)`

Middleware wrapper for rate limiting.

**Options**:
- `action` (string): Action identifier
- `windowMs` (number): Time window in milliseconds
- `maxRequests` (number): Max requests per window
- `message` (string): Error message
- `keyGenerator` (function): Custom key function (optional)
- `skipSuccessfulRequests` (boolean): Don't count successful requests (default: false)

#### `rateLimitRequest(identifier, action)`

Check rate limit for an identifier.

**Returns**: `{ allowed: boolean, remaining: number, retryAfter?: number }`

#### `cleanupRateLimits()`

Manually cleanup expired rate limit entries (automatic in background).

## 🔐 Admin Authentication

⭐ **NEW in v1.2.0** - JWT-based admin authentication with audit logging integration.

### Setup

```bash
# Required environment variables
JWT_SECRET=your_jwt_secret_here

# Optional: Admin API key for service-to-service auth
ADMIN_API_KEY=your_api_key_here
```

### Basic Usage

```javascript
// src/routes/api/admin/users/+server.js
import { requireAdmin } from '@goobits/security'
import { json } from '@sveltejs/kit'

export const GET = requireAdmin(async ({ locals }) => {
  // locals.adminUser contains authenticated admin info
  const { adminUser } = locals

  console.log('Admin user:', adminUser.id, adminUser.role)

  // Admin-only logic here
  const users = await getAllUsers()

  return json({ users })
})
```

### Authentication Methods

#### 1. JWT Bearer Token

```javascript
// Client request with JWT
fetch('/api/admin/users', {
  headers: {
    'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'
  }
})
```

#### 2. API Key

```javascript
// Client request with API key (header)
fetch('/api/admin/users', {
  headers: {
    'Authorization': 'ApiKey your_api_key_here'
  }
})

// Or using custom header
fetch('/api/admin/users', {
  headers: {
    'X-Admin-API-Key': 'your_api_key_here'
  }
})
```

### API Reference

#### `requireAdmin(handler)`

Middleware that requires admin authentication. Returns 401 if not authenticated.

**Features**:
- Verifies JWT tokens with admin role
- Supports API key authentication
- Automatic audit logging
- Sets `locals.adminUser` for handlers

#### `getAdminUser(request)`

Extract and verify admin credentials from request.

**Returns**: Admin user object or `null`

```javascript
{
  id: string,
  email?: string,
  role: 'admin',
  authMethod: 'jwt' | 'apikey'
}
```

#### `createAdminToken(payload)`

Create a JWT token with admin role (for testing/development).

```javascript
import { createAdminToken } from '@goobits/security'

const token = createAdminToken({
  id: 'user-123',
  email: 'admin@example.com'
})
// Expires in 24 hours
```

#### `generateAdminApiKey()`

Generate a secure random API key.

```javascript
import { generateAdminApiKey } from '@goobits/security'

const apiKey = await generateAdminApiKey()
// Returns: 64-character hex string
```

### JWT Token Requirements

Your JWT must include one of:
- `role: 'admin'`
- `isAdmin: true`
- `admin: true`

```javascript
// Example JWT payload
{
  "id": "user-123",
  "email": "admin@example.com",
  "role": "admin",
  "iat": 1234567890,
  "exp": 1234654290
}
```

## 📝 Audit Logging

⭐ **NEW in v1.2.0** - Track security-sensitive operations for compliance and monitoring.

### Basic Usage

```javascript
// src/routes/api/admin/delete-user/+server.js
import { withAuditLogging, auditLog } from '@goobits/security'
import { json } from '@sveltejs/kit'

export const DELETE = withAuditLogging({
  action: 'delete_user',
  includeRequestBody: true
})(async ({ request, locals, params }) => {
  const userId = params.id

  // Delete the user
  await deleteUser(userId)

  // Log specific audit event
  await auditLog({
    eventType: 'user.deleted',
    severity: 'high',
    actor: locals.adminUser?.email || 'system',
    action: 'delete_user',
    outcome: 'success',
    metadata: {
      userId,
      deletedBy: locals.adminUser?.id
    },
    ipAddress: locals.clientIP,
    userAgent: request.headers.get('user-agent')
  })

  return json({ success: true })
})
```

### Middleware Usage

```javascript
import { withAuditLogging } from '@goobits/security'

// Automatically log request start/end with timing
export const POST = withAuditLogging({
  action: 'payment_processed',
  includeRequestBody: false,  // Don't log sensitive payment data
  includeResponse: false
})(async (event) => {
  // Handler code...
  return json({ success: true })
})
```

### Direct Logging

```javascript
import { auditLog } from '@goobits/security'

await auditLog({
  eventType: 'login.failed',
  severity: 'medium',
  actor: email,
  action: 'user_login',
  outcome: 'failure',
  metadata: {
    reason: 'invalid_password',
    attempts: 3
  },
  ipAddress: request.headers.get('x-forwarded-for'),
  userAgent: request.headers.get('user-agent')
})
```

### Audit Event Structure

```typescript
{
  eventType: string       // Event classification (e.g., 'user.deleted')
  severity: string        // 'info' | 'low' | 'medium' | 'high' | 'critical'
  actor: string          // Who performed the action
  action: string         // What action was performed
  outcome: string        // 'success' | 'failure' | 'blocked'
  metadata?: object      // Additional context
  ipAddress?: string     // Client IP
  userAgent?: string     // User agent
  timestamp: string      // ISO 8601 timestamp (auto-added)
}
```

### Integration with Admin Auth

Admin authentication automatically logs:
- Successful admin logins
- Failed authentication attempts
- Admin access to protected endpoints

```javascript
// Combines admin auth + audit logging
import { requireAdmin } from '@goobits/security'

export const DELETE = requireAdmin(async ({ locals }) => {
  // Admin auth automatically logs:
  // - admin.authentication_success on successful auth
  // - admin.authentication_failed on failed auth

  // Your admin logic here...
  return json({ success: true })
})
```

### GDPR Compliance

Audit logs via @goobits/logger include:
- Timestamp for all events
- Actor identification
- Action performed
- Outcome tracking
- IP address logging (for security)

Configure retention policies in your logging infrastructure.

## 🔐 Security Best Practices

### 1. CSRF Protection

**DO**:
- ✅ Use CSRF tokens for all state-changing operations (POST, PUT, DELETE)
- ✅ Validate tokens on the server side
- ✅ Rotate tokens after sensitive operations
- ✅ Use secure, HTTP-only cookies

**DON'T**:
- ❌ Disable CSRF in production
- ❌ Store CSRF tokens in localStorage (XSS vulnerability)
- ❌ Skip validation for "internal" APIs
- ❌ Use predictable token generation

### 2. reCAPTCHA

**DO**:
- ✅ Set appropriate score thresholds for your use case (0.5 is a good default)
- ✅ Validate action names to prevent token reuse
- ✅ Keep your secret key secure (environment variables)
- ✅ Monitor scores and adjust thresholds over time

**DON'T**:
- ❌ Trust client-side validation alone
- ❌ Use the same reCAPTCHA for different actions
- ❌ Set score threshold too high (may block legitimate users)
- ❌ Commit secret keys to version control

### 3. Request Validation

**DO**:
- ✅ Validate all user input (body, query, params)
- ✅ Use strict schemas with appropriate constraints
- ✅ Sanitize output when rendering user data
- ✅ Log validation failures for monitoring

**DON'T**:
- ❌ Trust any client-side validation
- ❌ Skip validation for "internal" endpoints
- ❌ Expose detailed error messages in production
- ❌ Use overly permissive schemas

### 4. General

**DO**:
- ✅ Use HTTPS in production
- ✅ Set appropriate CORS policies
- ✅ Implement rate limiting
- ✅ Keep dependencies updated
- ✅ Use environment-based configuration

**DON'T**:
- ❌ Disable security features in production
- ❌ Log sensitive data (passwords, tokens)
- ❌ Trust X-Forwarded-For without validation
- ❌ Use default or weak secrets

## 🏗️ SvelteKit Integration

### Complete Example: Contact Form

```javascript
// src/routes/api/contact/+server.js
import {
  validateCsrfToken,
  verifyRecaptchaToken,
  withValidation,
  createCsrfProtectedResponse
} from '@goobits/security'
import { z } from 'zod'
import { json } from '@sveltejs/kit'

const contactSchema = z.object({
  name: z.string().min(2).max(100),
  email: z.string().email(),
  message: z.string().min(10).max(5000),
  recaptchaToken: z.string()
})

export const POST = withValidation({
  body: contactSchema
})(async (event) => {
  const { request, locals } = event
  const { body } = locals.validated

  // 1. Validate CSRF token
  if (!validateCsrfToken(request)) {
    return json({ error: 'Invalid CSRF token' }, { status: 403 })
  }

  // 2. Verify reCAPTCHA
  const isHuman = await verifyRecaptchaToken(
    body.recaptchaToken,
    'contact_form',
    0.5
  )

  if (!isHuman) {
    return json({ error: 'reCAPTCHA verification failed' }, { status: 400 })
  }

  // 3. Process the contact form
  await sendEmail({
    to: 'contact@example.com',
    from: body.email,
    subject: `Contact from ${body.name}`,
    text: body.message
  })

  // 4. Return success with new CSRF token
  return createCsrfProtectedResponse(
    json({ success: true, message: 'Message sent successfully' })
  )
})
```

### Hooks Integration

```javascript
// src/hooks.server.js
import { generateCsrfToken, setCsrfCookie } from '@goobits/security'

export async function handle({ event, resolve }) {
  // Generate CSRF token for GET requests if not present
  if (event.request.method === 'GET') {
    const response = await resolve(event)

    // Add CSRF token to response if it's an HTML page
    if (response.headers.get('content-type')?.includes('text/html')) {
      const token = generateCsrfToken()
      setCsrfCookie(response, token)
    }

    return response
  }

  return resolve(event)
}
```

## 📊 Logging

All security operations are logged via @goobits/logger:

```javascript
import { LoggerConfig, LogLevel } from '@goobits/logger'

// Configure log level for security operations
LoggerConfig.setLogLevel(LogLevel.INFO)

// Security logs will show with [CSRF], [reCAPTCHA], or [Validation] prefixes
```

**Logged Events**:
- CSRF token generation and validation
- reCAPTCHA verification attempts and failures
- Request validation errors
- Security configuration warnings

## 🧪 Testing

### Disable CSRF for Tests

```bash
# In test environment
DISABLE_CSRF=true
```

Or programmatically:

```javascript
import { DISABLE_CSRF } from '@goobits/security'

if (DISABLE_CSRF) {
  console.log('CSRF validation is disabled')
}
```

### Mock reCAPTCHA

```javascript
// In development, set invalid secret to bypass
// (verifyRecaptchaToken returns true when secret is missing in dev mode)
delete process.env.RECAPTCHA_SECRET_KEY
```

## 📄 License

MIT

## 🤝 Contributing

Part of the Goobits monorepo. See main repository for contribution guidelines.

## 🔗 Related Packages

- **@goobits/logger** - Unified logging (used internally)
- **@goobits/store** - E-commerce components using security utilities
- **@goobits/ui** - UI components

## 📚 Further Reading

- [OWASP CSRF Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html)
- [Google reCAPTCHA Documentation](https://developers.google.com/recaptcha/docs/verify)
- [Zod Documentation](https://zod.dev/)
- [SvelteKit Security Best Practices](https://kit.svelte.dev/docs/configuration)