npm package: @sunrun-security/sr-sec-ts-sns-logger
Description: TypeScript/JavaScript security logging module for sending structured security logs to AWS SNS
Author: SunRun Security Team
Latest version: 2.1.2

# sr-sec-ts-sns-logger: Developer Implementation Guide

A TypeScript/JavaScript module for sending structured security logs to AWS SNS for centralized security monitoring. This guide takes you from initial setup to a fully instrumented, production-ready implementation.

**Key Features:**
- 🔒 **Security-First**: Designed specifically for security event logging
- 🚀 **Production-Ready**: Built-in retry logic, timeouts, and error handling using AWS SDK v3
- ✅ **Compliance**: Covers all High priority security events with required data points
- 🧪 **Test Mode**: Test without AWS credentials for development and validation
- 📦 **Simple API**: One-line initialization, clean function calls with TypeScript support
- 🛡️ **Validation**: Comprehensive validation with detailed error messages showing all missing fields
- 📋 **Schema Compliant**: Implements standardized flat JSON structure with all required fields
- ⚡ **Non-Blocking**: Fire-and-forget pattern prevents application blocking
- 🎯 **Type Safe**: Full TypeScript support with comprehensive type definitions
- 📊 **Auto-Batching**: Automatically handles large record lists that exceed SNS message limits
- 🔍 **Context Extraction**: Automatically captures function/file name for enhanced debugging

---

## 📑 Table of Contents

### Getting Started
1. [Why Security Logging Matters](#-why-security-logging-matters)

### Part 1: Quick Start (5-Minute Goal)
2. [Install and Build](#step-1-install-and-build)
3. [Initialize the Logger](#step-2-initialize-the-logger)
4. [Log Your First Event](#step-3-log-your-first-event)

### Before Production Implementation
5. [The Developer's Mindset](#-the-developers-mindset-guidance-and-principles)
6. [Prerequisites: Review Required](#-prerequisites-review-required)
7. [Request SNS Permissions](#-request-sns-permissions)

### Part 2: The Implementation Playbook
8. [Identify Security-Relevant Activities](#step-1-identify-security-relevant-activities)
9. [Map Activities to Logger Functions](#step-2-map-your-activities-to-logger-events)
10. [Track Your Implementation Progress](#step-3-track-your-implementation-progress)

### Part 3: API Function Reference
11. [Authentication & Session Events](#authentication--session-events)
12. [Authorization & Access Events](#authorization--access-events)
13. [API & Data Access Events](#api--data-access-events)
14. [Key Configuration & Security Changes](#key-configuration--security-changes)

### Part 3.5: Auto-Context Extraction Helpers
15. [Available Context Helpers](#available-context-helpers)
16. [Auto-Extracted Fields](#auto-extracted-fields)
17. [Fields Requiring Manual Input](#fields-requiring-manual-input)
18. [Handling Missing Fields](#handling-missing-fields)

### Part 4: Production Readiness
15. [AWS IAM Permissions](#aws-iam-permissions)
16. [REQUIRED: Failure Monitoring](#required-failure-monitoring)
17. [Configuration Options](#configuration-options)

### Appendix
19. [Schema Structure & Fields](#appendix-a-schema-structure--fields)
20. [Sample Log Outputs](#appendix-b-sample-log-outputs)
21. [Migration from Python Version](#appendix-c-migration-from-python-version)
22. [Error Handling & Troubleshooting](#appendix-d-error-handling--troubleshooting)
23. [Module Structure](#appendix-e-module-structure)
24. [TypeScript Support](#appendix-f-typescript-support)
25. [Testing & Development](#appendix-g-testing--development)

---

## 🎯 Why Security Logging Matters

Implementing comprehensive security logging is not just a compliance requirement—it's a critical foundation for mature cybersecurity operations. This package enables organizations to:

**🔍 Mature Risk Detection Methods**
- Build baseline behavior patterns to identify anomalous activities
- Enable advanced threat hunting and security analytics
- Support machine learning-based security detection systems
- Create comprehensive audit trails for forensic investigations

**🛡️ Insider Threat Prevention**  
- Monitor privileged user activities and administrative actions
- Track data access patterns to detect unauthorized behavior
- Identify policy violations and suspicious access attempts
- Enable real-time alerting on high-risk activities

**🏢 Poaching Risk Reduction**
- Log customer data access to prevent unauthorized data harvesting
- Track bulk data exports and multi-record access patterns  
- Monitor user behavior changes that may indicate malicious intent
- Provide evidence for incident response and legal proceedings

Without proper security logging, organizations operate blind to internal threats, compliance violations, and sophisticated attacks that bypass perimeter defenses.

---

# Part 1: Quick Start (5-Minute Goal)

This section's goal is to verify your setup and send your first log. We will log a simple user login event to confirm that the pipeline is working correctly.

## Step 1: Install and Build

### Install from npm (Public Package)

The package is published publicly on npm and requires no authentication tokens or special registry configuration.

```bash
npm install @sunrun-security/sr-sec-ts-sns-logger
```

That's it -- no PAT, no `.npmrc` configuration, no GitHub Packages setup required.

### Install Dependencies in Your Application

Ensure you have the AWS SDK v3 installed:

```bash
npm install @aws-sdk/client-sns @aws-sdk/client-cloudwatch
```

---

## Step 2: Initialize the Logger

In your application's main entry point (e.g., `index.ts`, `app.ts`), initialize the logger **once**. 

**⚠️ CRITICAL**: For all development and testing, use `testMode: true`. This prints logs to the console instead of sending them to the security team's AWS SNS topic.

```typescript
import * as SecurityLogging from '@sunrun-security/sr-sec-ts-sns-logger';

// Initialize once at application startup with environment configuration
SecurityLogging.initSecurityLogging({
  testMode: process.env.NODE_ENV !== 'production', // Use testMode for all non-production
  
  // Set environment fields ONCE - automatically included in all subsequent logs
  cloudEnvType: SecurityLogging.CloudEnvType.PROD,          // Or CloudEnvType.STAGE, DEV, TEST
  cloudEnvUniqueId: process.env.AWS_ACCOUNT_ID,             // AWS Account ID
  cloudEnvName: process.env.ENVIRONMENT || 'production',    // Human-readable environment name
  serviceAccountId: process.env.AWS_EXECUTION_ROLE_ARN,     // IAM role/service account ARN
  serviceName: 'my-application',                            // Your application name
});

console.log('✅ Security logging initialized.');
```

**Production Configuration:**
```typescript
// In production with environment config (recommended)
SecurityLogging.initSecurityLogging({
  cloudEnvType: SecurityLogging.CloudEnvType.PROD,
  cloudEnvUniqueId: process.env.AWS_ACCOUNT_ID!,
  cloudEnvName: 'production',
  serviceAccountId: process.env.AWS_EXECUTION_ROLE_ARN!,
  serviceName: 'my-application',
});
```

**Benefit**: Once these fields are set during initialization, you don't need to pass them with every log call - they're automatically included!

**Multi-Region Failover Configuration (Optional but Recommended):**

For high availability, you can enable automatic failover to a secondary AWS region:

```typescript
// With failover enabled (automatically fails over to us-east-2)
SecurityLogging.initSecurityLogging({
  topicArn: "arn:aws:sns:us-west-2:123456789012:sr-sec-logging-log-topic-dev",
  failoverTopicArn: "arn:aws:sns:us-east-2:123456789012:sr-sec-logging-log-topic-failover-dev",
  enableFailover: true  // Default: true
});
```

**How Failover Works:**
- Primary region (us-west-2) is tried first with fast timeout (2 seconds)
- If primary fails with retriable error, automatically switches to failover region (us-east-2)
- Circuit breaker prevents repeated attempts to failing regions
- Automatic recovery when primary region becomes healthy
- Zero code changes required - completely transparent to your application

**Environment Variables (Alternative Configuration):**
```bash
export SECURITY_LOGS_TOPIC_ARN="arn:aws:sns:us-west-2:123456789012:my-topic"
export SECURITY_LOGS_FAILOVER_TOPIC_ARN="arn:aws:sns:us-east-2:123456789012:my-failover-topic"
export AWS_REGION="us-west-2"
export SECURITY_LOGS_FAILOVER_REGION="us-east-2"
```

**IAM User Credentials (For Applications Not Using IAM Roles):**

If your application uses IAM User credentials (static access keys) instead of IAM Roles (Lambda execution roles, ECS task roles, etc.), you can provide credentials directly:

```typescript
// With explicit IAM User credentials
SecurityLogging.initSecurityLogging({
  credentials: {
    accessKeyId: process.env.MY_AWS_ACCESS_KEY_ID!,
    secretAccessKey: process.env.MY_AWS_SECRET_ACCESS_KEY!,
    // sessionToken: process.env.AWS_SESSION_TOKEN,  // Optional, for temporary credentials
  },
  testMode: process.env.NODE_ENV !== 'production',
});
```

> **Note**: If credentials are not provided, the AWS SDK uses the default credential chain (environment variables `AWS_ACCESS_KEY_ID`/`AWS_SECRET_ACCESS_KEY`, IAM roles, credential files, etc.)

**Required IAM Policy for IAM Users:**

If using IAM User credentials to publish to a cross-account SNS topic, the IAM User needs this policy attached:

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowSNSPublishToSecurityLogging",
      "Effect": "Allow",
      "Action": "sns:Publish",
      "Resource": [
        "arn:aws:sns:us-west-2:687126124183:sr-sec-logging-log-topic-dev",
        "arn:aws:sns:us-west-2:000576341507:sr-sec-logging-log-topic-prod",
        "arn:aws:sns:us-east-2:000576341507:sr-sec-logging-log-topic-failover-prod"
      ]
    },
    {
      "Sid": "AllowKMSForSNS",
      "Effect": "Allow",
      "Action": ["kms:GenerateDataKey*", "kms:Decrypt"],
      "Resource": [
        "arn:aws:kms:us-west-2:687126124183:key/*",
        "arn:aws:kms:us-west-2:000576341507:key/*",
        "arn:aws:kms:us-east-2:000576341507:key/*"
      ],
      "Condition": {
        "StringEquals": {
          "kms:ViaService": [
            "sns.us-west-2.amazonaws.com",
            "sns.us-east-2.amazonaws.com"
          ]
        }
      }
    }
  ]
}
```

**Monitoring Failover:**
```typescript
// Get failover metrics
const metrics = SecurityLogging.getFailoverMetrics();
console.log(`Primary success: ${metrics.primary_success}`);
console.log(`Failover used: ${metrics.failover_success} times`);
console.log(`Total failures: ${metrics.total_failures}`);

// Reset metrics (useful for periodic monitoring)
SecurityLogging.resetFailoverMetrics();
```

---

## Step 3: Log Your First Event

Now that the logger is initialized, it's time to add your first security log. This step will guide you through identifying where to instrument your code and understanding where each value comes from.

### 📍 Step 3a: Locate Your Login Handler

First, identify where user authentication happens in your application. Common locations include:

- **Express/Node.js**: Login route handler (e.g., `POST /auth/login`)
- **Next.js with NextAuth**: `[...nextauth].ts` callbacks or custom signin handler
- **AWS Lambda**: Authorization/authentication Lambda functions
- **Other frameworks**: Wherever your application verifies credentials and creates a session

### 📖 Step 3b: Review the logUserLogin Function

Before implementing, review the `logUserLogin` function definition to understand all available parameters:

👉 **[View logUserLogin in security-logging-sns.ts](https://github.com/SunRun/sr-sec-js-sns-logger/blob/master/src/security-logging-sns.ts)** (Search for "Authentication & Session Events")

This shows you:
- Required vs optional parameters
- Validation rules
- Expected data types
- Available constants

### 💡 Step 3c: Understand Where Values Come From

Before writing code, map out where you'll get each required value:

| Parameter | Where to Find It | Example Source |
|-----------|-----------------|----------------|
| `actor_identifier` | User's email/username from your auth system | `user.email`, `session.user.email`, decoded JWT `sub` |
| `actor_type` | Type of user logging in | `ActorType.HUMAN_INTERNAL` for employees, `ActorType.HUMAN_CUSTOMER` for customers |
| `session_id` | Session ID from your session management | `req.session.id`, `cookies.get('session')`, JWT `jti` |
| `user_agent` | HTTP request headers | `req.headers['user-agent']` |
| `user_role` | User's role/permission level | Map your app's roles to `UserRole` constants |
| `cloud_env_unique_id` | AWS Account ID or GCP Project ID | Environment variable, hardcoded per environment |
| `service_account_id` | IAM role/service account running your code | Lambda execution role name, ECS task role, etc. |

**⚠️ Tip**: If you need to decode/extract values (e.g., from a JWT token), do this **before** calling the logging function.

### 📝 Step 3d: Implementation Example

Here's a complete example showing how to integrate security logging into a NextAuth.js signin flow:

```typescript
import * as SecurityLogging from '@sunrun-security/sr-sec-ts-sns-logger';
import { decode } from 'jsonwebtoken';

// ===== STEP 1: Initialize ONCE at app startup (e.g., in _app.tsx or instrumentation.ts) =====
// Environment fields are set here and automatically included in ALL subsequent logs
SecurityLogging.initSecurityLogging({
  testMode: process.env.NODE_ENV !== 'production',
  cloudEnvType: SecurityLogging.CloudEnvType.PROD,
  cloudEnvUniqueId: process.env.AWS_ACCOUNT_ID!,
  cloudEnvName: process.env.ENVIRONMENT || 'production',
  serviceAccountId: process.env.AWS_EXECUTION_ROLE_ARN!,
  serviceName: 'customer-portal',
});

// ===== STEP 2: Use in your auth callbacks - much simpler! =====
// Example: NextAuth signin callback
async callbacks: {
  async signIn({ user, account, profile }) {
    try {
      const userEmail = user.email;
      const sessionId = account.access_token
        ? decode(account.access_token).jti
        : `session-${Date.now()}`;
      
      const userRole = profile.role === 'admin' 
        ? SecurityLogging.UserRole.ADMIN 
        : SecurityLogging.UserRole.STANDARD_USER;
      
      const actorType = userEmail.endsWith('@sunrun.com')
        ? SecurityLogging.ActorType.HUMAN_INTERNAL
        : SecurityLogging.ActorType.HUMAN_CUSTOMER;

      // 🔥 Fire-and-forget: Only pass event-specific fields!
      // Environment fields (cloudEnvType, serviceName, etc.) are auto-included from init
      SecurityLogging.fireAndForget(
        SecurityLogging.logUserLogin({
          // --- Actor/Session Context ---
          actor_identifier: userEmail,
          actor_type: actorType,
          session_id: sessionId,

          // --- Event-Specific Fields ---
          event_type: SecurityLogging.EventType.LOGIN_ATTEMPT,
          status: SecurityLogging.Status.SUCCESS,
          user_agent: profile.user_agent || "unknown",
          user_role: userRole,
          auth_protocol: SecurityLogging.AuthProtocol.OAUTH2_JWT,
          detail: SecurityLogging.Detail.USER_INITIATED,
        }),
        'login_attempt'
      );
      
      return true;
    } catch (error) {
      console.error('Security logging error:', error);
      return true; // Never let logging errors break authentication
    }
  }
}
```

### 🎯 Alternative Example: Express.js Login Route

```typescript
import * as SecurityLogging from '@sunrun-security/sr-sec-ts-sns-logger';

// Initialize once at app startup (environment fields auto-included in all logs)
SecurityLogging.initSecurityLogging({
  cloudEnvType: SecurityLogging.CloudEnvType.PROD,
  cloudEnvUniqueId: process.env.AWS_ACCOUNT_ID!,
  cloudEnvName: 'production',
  serviceAccountId: 'ecs-task-role',
  serviceName: 'api-server',
});

app.post('/api/login', async (req, res) => {
  try {
    const { email, password } = req.body;
    const user = await authenticateUser(email, password);
    
    if (!user) {
      // Log failed login - only event-specific fields needed!
      SecurityLogging.fireAndForget(
        SecurityLogging.logUserLogin({
          actor_identifier: email,
          actor_type: SecurityLogging.ActorType.HUMAN_INTERNAL,
          session_id: req.sessionID,
          event_type: SecurityLogging.EventType.LOGIN_ATTEMPT,
          status: SecurityLogging.Status.FAILURE,
          user_agent: req.headers['user-agent'],
          user_role: SecurityLogging.UserRole.UNKNOWN,
          auth_protocol: SecurityLogging.AuthProtocol.FORM_BASED,
          detail: SecurityLogging.Detail.INVALID_CREDENTIALS,
        }),
        'login_attempt'
      );
      return res.status(401).json({ error: 'Invalid credentials' });
    }
    
    // Log successful login
    SecurityLogging.fireAndForget(
      SecurityLogging.logUserLogin({
        actor_identifier: user.email,
        actor_type: SecurityLogging.ActorType.HUMAN_INTERNAL,
        session_id: req.sessionID,
        event_type: SecurityLogging.EventType.LOGIN_ATTEMPT,
        status: SecurityLogging.Status.SUCCESS,
        user_agent: req.headers['user-agent'],
        user_role: user.role === 'admin'
          ? SecurityLogging.UserRole.ADMIN 
          : SecurityLogging.UserRole.STANDARD_USER,
        auth_protocol: SecurityLogging.AuthProtocol.FORM_BASED,
        detail: SecurityLogging.Detail.USER_INITIATED,
      }),
      'login_attempt'
    );
    
    res.json({ success: true, user });
    
  } catch (error) {
    console.error('Login error:', error);
    res.status(500).json({ error: 'Server error' });
  }
});
```

### ✅ Verify It Works

Run your application in development mode (with `testMode: true`) and trigger a login. You should see the security log printed to your console:

```json
{
  "timestamp": "2025-12-13T04:33:12.100224+00:00",
  "event_type": "login_attempt",
  "log_category": "authn_n_session",
  "status": "status.general.success",
  "event_uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "actor_identifier": "user@sunrun.com",
  "actor_type": "actor.human.internal",
  "session_id": "test-session-123",
  "cloud_env_type": "prod",
  "service_name": "test-app",
  "cloud_env_unique_id": "123456789012",
  "cloud_env_name": "production",
  "service_account_id": "lambda-execution-role",
  "user_agent": "Mozilla/5.0...",
  "user_role": "role.classification.admin",
  "auth_protocol": "auth.protocol.oauth2.jwt",
  "detail": "detail.trigger.user_initiated",
  "caller_function": "handleLogin",
  "caller_file": "auth-controller.ts"
}
```

**Note:** All fields are at the same level (flat structure) - there is no nested `base_log` or `log_specifics` object.

**🎉 Congratulations!** You've sent your first security log. The infrastructure is working. Now, let's move on to instrumenting your entire application.

---

## 🧠 The Developer's Mindset: Guidance and Principles

Implementing this framework requires more than just copying code. As an engineer, you possess the most intricate knowledge of your application. Adopting this standard effectively requires a thoughtful approach.

**Think, Don't Just Apply**: Use these templates as a guide, not a blind script. Question assumptions about your application's logic. For example, how does your service handle different user types or authentication workflows (e.g., OAuth)? Your implementation must reflect this context.

**Know Your Endpoints**: You are responsible for understanding the data sensitivity of your application's endpoints. Avoid mislabeling logs by correctly identifying the type and sensitivity of the data being accessed.

**Security is Your Responsibility**: Developers are the primary security implementers. This framework is a tool to help you build more secure applications by providing visibility into critical events.

---

## 📋 Prerequisites: Review Required

**⚠️ MANDATORY**: Before implementing security logging, you **MUST** review these resources:

### 🔗 Required Reading:
1. **[Introduction to the Sunrun Security Logging Framework](https://docs.google.com/document/d/1nSL0ImYuCG3Alr4_BeUETdHoz1nBrQrhp5jnv55f8cQ/edit?tab=t.0)** - Organizational standards
2. **[security-log-fields.ts](https://github.com/SunRun/sr-sec-js-sns-logger/blob/master/src/security-log-fields.ts)** - Technical field definitions and constants
3. **[security-logging-sns.ts](https://github.com/SunRun/sr-sec-js-sns-logger/blob/master/src/security-logging-sns.ts)** - Function implementations and validation logic

**Why This Review is Critical:**
- **🎯 Standardized Values Required**: All events MUST use predefined constants from `security-log-fields.ts`
- **📊 Schema Compliance**: Understanding required fields prevents validation errors
- **🔍 Audit & Compliance**: Consistent field values across all applications are mandatory
- **🛠️ Implementation Success**: Saves development time by preventing common mistakes
- **🔬 Validation Logic**: Review `security-logging-sns.ts` to understand exactly how each function validates parameters

### Key Areas to Review:

**From [`security-log-fields.ts`](https://github.com/SunRun/sr-sec-js-sns-logger/blob/master/src/security-log-fields.ts)**:
- **Event Types**: Supported security events (`EventType` constants)
- **Actor Types**: Actor classifications (`ActorType` constants) 
- **Status Values**: Success/failure indicators (`Status` constants)
- **Detail Values**: Contextual information (`Detail` constants)
- **User Roles**: Role classifications (`UserRole` constants)

**From [`security-logging-sns.ts`](https://github.com/SunRun/sr-sec-js-sns-logger/blob/master/src/security-logging-sns.ts)**:
- **Function Signatures**: Exact parameters for each of the 14 logging functions
- **Validation Rules**: What makes a log entry valid or invalid
- **Required vs Optional Fields**: Which fields are mandatory for each event type
- **Error Messages**: What validation errors look like and how to fix them

**❌ Common Mistakes to Avoid:**
- Using raw strings instead of predefined constants
- Skipping required fields for specific event types
- Creating custom detail values instead of using standardized constants
- Not importing constants properly from `security-log-fields.ts`

### Data Sensitivity Levels

When logging record access events, you must specify the sensitivity level of the data being accessed. Use this guide to select the appropriate level:

| Level | Constant | When to Use | Examples |
|-------|----------|-------------|----------|
| **PUBLIC** | `DataSensitivityLevel.PUBLIC` | Data that can be publicly shared | Product catalogs, marketing content, public APIs, help docs |
| **NON_PUBLIC** | `DataSensitivityLevel.NON_PUBLIC` | Internal business data, not customer-specific | Internal reports, aggregated metrics, config settings, team directories |
| **CONFIDENTIAL** | `DataSensitivityLevel.CONFIDENTIAL` | Customer/business data that shouldn't leak | Customer records, agreements, contracts, contact info, pricing |
| **RESTRICTED** | `DataSensitivityLevel.RESTRICTED` | Highly sensitive - PII, credentials, financial | SSN, bank accounts, passwords, API keys, health data |

**Decision Guide:**
```
Is this data publicly available (website, public API)?
  → YES: PUBLIC
  → NO: ↓

Does this data belong to a specific customer or contain PII?
  → YES: Is it highly sensitive (SSN, financial, credentials, health)?
         → YES: RESTRICTED
         → NO: CONFIDENTIAL
  → NO: NON_PUBLIC
```

The same levels apply to `EndpointSensitivity` for API endpoint classification.

---

## 🔐 Request SNS Permissions

**⚠️ IMPORTANT**: Before implementing security logging, you must request SNS topic permissions.

### Step 1: Review Requirements
Ensure you have:
- ✅ Read the [Introduction to the Sunrun Security Logging Framework](https://docs.google.com/document/d/1nSL0ImYuCG3Alr4_BeUETdHoz1nBrQrhp5jnv55f8cQ/edit?tab=t.0)
- ✅ Reviewed [`security-log-fields.ts`](https://github.com/SunRun/sr-sec-js-sns-logger/blob/master/src/security-log-fields.ts)
- ✅ Identified which security events your application needs to log
- ✅ Understood your application's IAM role requirements

### Step 2: Request SNS Topic Access

Before requesting, identify your **IAM Role/Service Account** based on your infrastructure:

**Common Infrastructure Examples:**

| Infrastructure Type | IAM Role/Service Account Example |
|---------------------|-----------------------------------|
| **AWS Lambda** | `arn:aws:iam::123456789012:role/my-lambda-execution-role` |
| **AWS Amplify** | `arn:aws:iam::123456789012:role/amplify-my-app-role` |
| **AWS ECS/Fargate** | `arn:aws:iam::123456789012:role/ecsTaskExecutionRole` |
| **AWS EC2** | `arn:aws:iam::123456789012:role/ec2-application-role` |
| **GKE (Google)** | `serviceAccount:my-app@project-id.iam.gserviceaccount.com` |
| **AWS EKS** | `arn:aws:iam::123456789012:role/eks-pod-role` |
| **Local/Dev** | Your IAM user: `arn:aws:iam::123456789012:user/your-username` |

**💡 How to Find Your Role:**
- **Lambda**: Check Lambda function configuration → Permissions → Execution role
- **ECS/Fargate**: Check task definition → Task execution IAM role
- **EC2**: Check instance → IAM role attached to instance
- **EKS**: Check pod service account or node IAM role
- **Amplify**: Check Amplify app → App settings → Service role

Post in **`#software-security`** Slack channel:

```
Hi Security Team! 👋

I need SNS permissions for security logging in my application.

📋 Application Details:
• Application/Service Name: [your-service-name]
• Environment(s): [prod/dev/staging]
• AWS Account ID: [123456789012] (where your application runs)
• IAM Role/Service Account: [see examples above - provide full ARN]
• SNS Topic Region: us-west-2 (fixed - this is where the security logging SNS topic is located)

📊 Event Types Needed:
• [List events: "User Login", "API Access", "Permission Changes"]

⏰ Timeline: [When you need this]

Thank you! 🙏
```

**Expected Response Time:** 2-3 business days

**📍 Note About Cross-Region Publishing:**
The security logging SNS topic is located in **us-west-2**, regardless of where your application runs. AWS SNS supports cross-region publishing, so applications running in any region (e.g., us-east-1, eu-west-1, ap-southeast-1) can publish to the us-west-2 topic without issues. Your IAM role just needs the appropriate SNS publish permissions.

---

# Part 2: The Implementation Playbook

The first log is easy. The real work is identifying and mapping all security-relevant activities in your application. This playbook provides a structured process for achieving full coverage without getting overwhelmed.

---

## Step 1: Identify Security-Relevant Activities

First, audit your application and create a simple list of all actions that should be logged. Don't worry about which logger function to use yet—just list the activities that occur.

### Think About These Categories:

**🔐 Authentication**
- Where do users log in, log out, or fail to log in?
- Where do they handle MFA?
- Password reset/change flows?

**🔑 Authorization**
- Where are permissions, roles, or group memberships changed?
- Is there an admin panel?
- Can users be enabled/disabled?
- Impersonation features?

**📊 Data Access**
- Which API endpoints read or modify data?
- Which handle sensitive or customer data?
- Which access records (single or multiple)?
- Reporting and bulk export endpoints?

**⚙️ Configuration Changes**
- Where can users change their password, API keys, or MFA settings?
- Where can admins change system-wide settings like SSO?
- API key lifecycle management?

---

## Step 2: Map Your Activities to Logger Events

Take your list of activities and map each one to one of the **14 available logger functions**. Use this decision guide:

### 🤔 Decision Guide

**Is the activity about a user logging in, out, or using MFA?**  
📖 **Review the [Authentication & Session functions →](https://github.com/SunRun/sr-sec-js-sns-logger/blob/master/src/security-logging-sns.ts#L318-L561)**
- ➡️ Use: [`logUserLogin()`](https://github.com/SunRun/sr-sec-js-sns-logger/blob/master/src/security-logging-sns.ts#L318) - User successfully/unsuccessfully logs in
- ➡️ Use: [`logMfaChallenge()`](https://github.com/SunRun/sr-sec-js-sns-logger/blob/master/src/security-logging-sns.ts#L406) - MFA verification attempt
- ➡️ Use: [`logUserLogout()`](https://github.com/SunRun/sr-sec-js-sns-logger/blob/master/src/security-logging-sns.ts#L494) - User logs out or session ends

**Is the activity about changing what a user is allowed to do?**  
📖 **Review the [Authorization & Access functions →](https://github.com/SunRun/sr-sec-js-sns-logger/blob/master/src/security-logging-sns.ts#L580-L787)**
- ➡️ Use: [`logPermissionRoleChange()`](https://github.com/SunRun/sr-sec-js-sns-logger/blob/master/src/security-logging-sns.ts#L580) - User's role or permissions are modified
- ➡️ Use: [`logUserStatusChange()`](https://github.com/SunRun/sr-sec-js-sns-logger/blob/master/src/security-logging-sns.ts#L653) - User account enabled/disabled/deleted
- ➡️ Use: [`logImpersonationEvent()`](https://github.com/SunRun/sr-sec-js-sns-logger/blob/master/src/security-logging-sns.ts#L698) - Admin impersonates another user
- ➡️ Use: [`logUserInviteEvent()`](https://github.com/SunRun/sr-sec-js-sns-logger/blob/master/src/security-logging-sns.ts#L744) - User invitation sent/accepted

**Is the activity an API endpoint being called?**  
📖 **Review the [API Endpoint & Data Access functions →](https://github.com/SunRun/sr-sec-js-sns-logger/blob/master/src/security-logging-sns.ts#L795-L938)**
- **⚠️ Critical Decision Point**: Does this endpoint access specific customer/entity data?
  - **YES** - Use: [`logRecordAccess()`](https://github.com/SunRun/sr-sec-js-sns-logger/blob/master/src/security-logging-sns.ts#L847) - Record(s) accessed (single or multiple - pass 1 or more IDs in `id_list`)
  - **NO** - It's a general API call (e.g., `GET /api/status` or internal non-data endpoint)
    - ➡️ Use: [`logApiRequest()`](https://github.com/SunRun/sr-sec-js-sns-logger/blob/master/src/security-logging-sns.ts#L795) - General API endpoint access

**Is the activity about changing critical credentials or settings?**  
📖 **Review the [Key Configuration Changes functions →](https://github.com/SunRun/sr-sec-js-sns-logger/blob/master/src/security-logging-sns.ts#L946-L1121)**
- ➡️ Use: [`logMfaStatusChange()`](https://github.com/SunRun/sr-sec-js-sns-logger/blob/master/src/security-logging-sns.ts#L946) - MFA enabled/disabled/device added
- ➡️ Use: [`logPasswordChangeReset()`](https://github.com/SunRun/sr-sec-js-sns-logger/blob/master/src/security-logging-sns.ts#L994) - Password changed or reset
- ➡️ Use: [`logApiKeyLifecycle()`](https://github.com/SunRun/sr-sec-js-sns-logger/blob/master/src/security-logging-sns.ts#L1041) - API key created/revoked/modified
- ➡️ Use: [`logAuthMechanismModification()`](https://github.com/SunRun/sr-sec-js-sns-logger/blob/master/src/security-logging-sns.ts#L1088) - SSO config or auth method changes

💡 **Pro Tip**: Click any function link above to see the exact code, parameters, and validation rules for that logging function.

---

## Step 3: Track Your Implementation Progress

For any non-trivial application, you will have dozens of activities to log. To avoid getting lost, use a tracking template.

### 📊 Implementation Tracking Template

Use this Google Sheet template to track your progress:

**[Security Logging Implementation Tracker](https://docs.google.com/spreadsheets/d/1REliTDGjTzmgjLA2W2-r-axwoA0lCdwYXYWvTRKJPiM/edit?usp=sharing)**

This structured approach ensures you don't miss any critical events and provides clear visibility into the project's progress.

---

# Part 3: API Function Reference

Now that you have a plan, use this section as a detailed reference for the parameters required by each function you identified in the playbook.

**⚠️ IMPORTANT**: Remember to pass the complete set of **base fields** with every single call. All fields are sent in a flat JSON structure (no nesting).

**💡 TIP**: For detailed implementation logic and validation rules, review the source code in [`security-logging-sns.ts`](https://github.com/SunRun/sr-sec-js-sns-logger/blob/master/src/security-logging-sns.ts). Each function includes comprehensive parameter validation and error handling.

### Base Fields (Required for ALL Functions)

```typescript
// Fields set ONCE in initSecurityLogging() - auto-included in all logs:
// cloudEnvType, cloudEnvUniqueId, cloudEnvName, serviceAccountId, serviceName

// Fields required in EACH logging call:
{
  // Who did it
  actor_identifier: string,      // e.g., "user@company.com"
  actor_type: ActorType,         // e.g., ActorType.HUMAN_INTERNAL
  
  // Session context
  session_id: string,            // e.g., "session-abc-123"
  
  // Optional but recommended
  source_ip_address?: string,        // e.g., "192.168.1.100"
  service_component_name?: string,   // e.g., "auth-handler", "payment-processor"
}
```

---

## Authentication & Session Events

### `logUserLogin()`

**When to use**: After a user attempts to sign in with their credentials.

**Event Types**: 
- `EventType.LOGIN_ATTEMPT` (consolidated event - use `status` field to indicate success/failure)

**Specific Parameters**:
- `event_type` (EventType): The type of login event (`EventType.LOGIN_ATTEMPT`)
- `status` (Status): `Status.SUCCESS` or `Status.FAILURE`
- `user_agent` (string): The user's browser user agent
- `user_role` (UserRole): The role of the user logging in
- `auth_protocol` (AuthProtocol): Authentication method used (e.g., `AuthProtocol.FORM_BASED`, `AuthProtocol.OAUTH2_JWT`, `AuthProtocol.SAML`)
- `detail` (Detail): Context for the login
- `device_id` (string, optional): Unique identifier for the user's device

**Example (Successful Login - Fire-and-Forget)**:
```typescript
// Recommended: Non-blocking, won't delay your response
// Note: Environment fields (cloudEnvType, serviceName, etc.) set in initSecurityLogging()
fireAndForget(
  logUserLogin({
    // Actor/Session context
    actor_identifier: "john.doe@company.com",
    actor_type: ActorType.HUMAN_INTERNAL,
    session_id: request.session.id,
    source_ip_address: "192.168.1.100",
    
    // Event-specific fields
    event_type: EventType.LOGIN_ATTEMPT,
    status: Status.SUCCESS,
    user_agent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
    user_role: UserRole.ADMIN,
    auth_protocol: AuthProtocol.OAUTH2_JWT,
    detail: Detail.USER_INITIATED,
  }),
  'login_attempt'
);
```

**Login Failure Example**:
```typescript
fireAndForget(
  logUserLogin({
    // ... base log fields ...
    event_type: EventType.LOGIN_ATTEMPT,
    status: Status.FAILURE,
    user_agent: request.headers['user-agent'],
    user_role: UserRole.CUSTOMER_USER,
    auth_protocol: AuthProtocol.FORM_BASED,
    detail: Detail.INVALID_CREDENTIALS, // or Detail.ACCOUNT_LOCKED
  }),
  'login_attempt'
);
```

---

### `logMfaChallenge()`

**When to use**: When a user completes (or fails) an MFA challenge.

**Event Type**:
- `EventType.MFA_CHALLENGE`

**Specific Parameters**:
- `event_type` (EventType): Must be `EventType.MFA_CHALLENGE`
- `status` (Status): `Status.SUCCESS` or `Status.FAILURE`
- `mfa_type` (MfaType): Type of MFA used (e.g., `MfaType.TOTP`, `MfaType.SMS`)
- `user_agent` (string): Browser user agent
- `user_role` (UserRole): User's role
- `detail` (Detail): For success: `Detail.USER_INITIATED`; For failure: `Detail.MFA_INVALID_CODE`, `Detail.MFA_EXPIRED_CODE`, `Detail.MFA_DEVICE_NOT_ENROLLED`, `Detail.MFA_TOO_MANY_ATTEMPTS`
- `device_id` (string, optional): Device identifier

**Example - MFA Failure**:
```typescript
// Link MFA to login via session_id
fireAndForget(
  logMfaChallenge({
    // ... base log fields ...
    session_id: 'session-mfa-flow-123',  // Same as login_attempt session_id
    event_type: EventType.MFA_CHALLENGE,
    status: Status.FAILURE,  // MFA failed
    mfa_type: MfaType.TOTP,
    user_agent: request.headers['user-agent'],
    user_role: UserRole.ADMIN,
    detail: Detail.MFA_INVALID_CODE,  // Specific failure reason
  }),
  'mfa_challenge'
);
```

---

### `logUserLogout()`

**When to use**: When a user logs out (voluntarily or forced).

**Event Types**: `EventType.USER_LOGOUT`

**Specific Parameters**:
- `event_type` (EventType): Logout event type
- `status` (Status): Success or failure
- `user_agent` (string): Browser user agent
- `user_role` (UserRole): User's role
- `detail` (Detail): Reason for logout (e.g., `Detail.USER_INITIATED`, `Detail.SESSION_TIMEOUT`, `Detail.ADMIN_INITIATED`)
- `device_id` (string, optional): Device identifier

**Example**:
```typescript
fireAndForget(
  logUserLogout({
    // ... base log fields ...
    event_type: EventType.USER_LOGOUT,
    status: Status.SUCCESS,
    user_agent: request.headers['user-agent'],
    user_role: UserRole.SALES_REP,
    detail: Detail.SESSION_TIMEOUT,
  }),
  'user_logout'
);
```

---

## Authorization & Access Events

### `logPermissionRoleChange()`

**When to use**: When an admin grants or revokes a role, permission, or group membership from a user.

**Event Types**: 
- `EventType.ROLE_ASSIGN` / `EventType.ROLE_REVOKE`
- `EventType.PERMISSION_GRANT` / `EventType.PERMISSION_REVOKE`
- `EventType.GROUP_MEMBERSHIP_ADD` / `EventType.GROUP_MEMBERSHIP_REMOVE`

**Specific Parameters**:
- `event_type` (EventType): The change event type
- `status` (Status): Success or failure
- `target_user_identifier` (string): The user whose permissions are changing
- `object_changed` (string): The type of object being modified (e.g., "role", "permission", "group")
- `previous_value` (string): The value before the change
- `new_value` (string): The value after the change
- `detail` (Detail, optional): Context for the change

**Example**:
```typescript
// Note: Environment fields set in initSecurityLogging()
fireAndForget(
  logPermissionRoleChange({
    // Actor/Session context - actor is the admin making the change
    actor_identifier: "admin@company.com",
    actor_type: ActorType.HUMAN_INTERNAL,
    session_id: request.session.id,
    
    // Event-specific fields
    event_type: EventType.ROLE_ASSIGN,
    status: Status.SUCCESS,
    target_user_identifier: "alice@company.com", // User being changed
    object_changed: "role",
    previous_value: "Sales Rep",
    new_value: "Sales Manager",
    detail: Detail.ADMIN_INITIATED,
  }),
  'role_assign'
);
```

---

### `logUserStatusChange()`

**When to use**: When a user account is enabled, disabled, locked, or unlocked.

**Event Types**: `EventType.USER_STATUS_CHANGE`

**Specific Parameters**:
- `event_type` (EventType): Status change event
- `status` (Status): Success or failure
- `target_user_identifier` (string): The user whose status is changing
- `detail` (Detail): Type of status change (e.g., `Detail.USER_ENABLED`, `Detail.USER_DISABLED`, `Detail.ACCOUNT_LOCKED`)

**Example**:
```typescript
fireAndForget(
  logUserStatusChange({
    // ... base log fields ...
    event_type: EventType.USER_STATUS_CHANGE,
    status: Status.SUCCESS,
    target_user_identifier: "departed@company.com",
    detail: Detail.USER_DISABLED,
  }),
  'user_status_change'
);
```

---

### `logImpersonationEvent()`

**When to use**: When a support admin impersonates a user (start or end).

**Event Types**: `EventType.IMPERSONATION_EVENT`

**Specific Parameters**:
- `event_type` (EventType): Impersonation event
- `status` (Status): Success or failure
- `target_user_identifier` (string): The user being impersonated
- `detail` (Detail): `Detail.IMPERSONATION_START` or `Detail.IMPERSONATION_END`

**Example**:
```typescript
fireAndForget(
  logImpersonationEvent({
    // Base log fields - actor is the support admin
    actor_identifier: "support-admin@company.com",
    // ... other base fields ...
    
    event_type: EventType.IMPERSONATION_EVENT,
    status: Status.SUCCESS,
    target_user_identifier: "customer@company.com",
    detail: Detail.IMPERSONATION_START,
  }),
  'impersonation_start'
);
```

---

### `logUserInviteEvent()`

**When to use**: When a new user is invited to the system.

**Event Types**: `EventType.USER_INVITE_EVENT`

**Specific Parameters**:
- `event_type` (EventType): Invite event
- `status` (Status): Success or failure
- `target_user_email` (string): Email of the invited user
- `assigned_role` (UserRole): Role being assigned to the new user
- `invite_status` (InviteStatus): Status of the invite (e.g., `InviteStatus.SENT`, `InviteStatus.ACCEPTED`)
- `detail` (Detail): Context for the invite

**Example**:
```typescript
fireAndForget(
  logUserInviteEvent({
    // ... base log fields ...
    event_type: EventType.USER_INVITE_EVENT,
    status: Status.SUCCESS,
    target_user_email: "newemployee@company.com",
    assigned_role: UserRole.SALES_REP,
    invite_status: InviteStatus.SENT,
    detail: Detail.USER_INITIATED,
  }),
  'user_invite'
);
```

---

## API & Data Access Events

### `logApiRequest()`

**When to use**: For general API calls that do NOT access specific customer/entity data. Use this for system endpoints, health checks, or internal APIs.

**⚠️ Important**: If the endpoint accesses customer/entity data, use `logRecordAccess()` instead.

**Event Types**: `EventType.API_REQUEST_PROCESSED`

**Specific Parameters**:
- `event_type` (EventType): API request event
- `status` (Status): Success or failure
- `auth_protocol` (AuthProtocol): How the request was authenticated (e.g., `AuthProtocol.API_KEY`, `AuthProtocol.OAUTH2_JWT`)
- `endpoint_path` (string): The API path that was called
- `http_method` (HttpMethod): The HTTP method (e.g., `HttpMethod.GET`, `HttpMethod.POST`)
- `authorization_status` (Status): Whether authorization succeeded
- `endpoint_sensitivity` (EndpointSensitivity): Sensitivity level of the endpoint
- `detail` (Detail, optional): Additional context

**Example**:
```typescript
fireAndForget(
  logApiRequest({
    // ... base log fields ...
    event_type: EventType.API_REQUEST_PROCESSED,
    status: Status.SUCCESS,
    auth_protocol: AuthProtocol.API_KEY,
    endpoint_path: "/api/v1/health",
    http_method: HttpMethod.GET,
    authorization_status: Status.SUCCESS,
    endpoint_sensitivity: EndpointSensitivity.PUBLIC,
    detail: Detail.NOT_APPLICABLE,
  }),
  'api_request'
);
```

---

### `logRecordAccess()`

**When to use**: For endpoints that access customer/entity records - whether a single record or multiple records. Use this unified function for all record access logging.

**Event Types**: 
- `EventType.RECORD_ACCESS` (unified event type for all record access)

**Specific Parameters**:
- `event_type` (EventType): `EventType.RECORD_ACCESS`
- `status` (Status): Success or failure
- `id_list` (string[]): List of record IDs being accessed (can be a single ID or multiple)
- `endpoint_path` (string): The API path being accessed
- `data_sensitivity_level` (DataSensitivityLevel): Sensitivity of the data accessed
- `detail` (Detail): Context for the action (e.g., `Detail.VIEW_RECORD`, `Detail.EXPORT_REPORT`)

**Note**: `record_count` is automatically calculated from `id_list.length` - you don't need to provide it.

**Example (Single Record Access)**:
```typescript
fireAndForget(
  logRecordAccess({
    // ... base log fields (or use auto-context extraction) ...
    event_type: EventType.RECORD_ACCESS,
    status: Status.SUCCESS,
    id_list: ["cust_12345"], // Single record - just pass one ID in the list
    endpoint_path: "/api/customers/cust_12345",
    data_sensitivity_level: DataSensitivityLevel.CONFIDENTIAL,  // Customer data
    detail: Detail.VIEW_RECORD,
  }),
  'record_access'
);
```

**Example (Multi-Record Export)**:
```typescript
fireAndForget(
  logRecordAccess({
    // ... base log fields (or use auto-context extraction) ...
    event_type: EventType.RECORD_ACCESS,
    status: Status.SUCCESS,
    id_list: ["cust_001", "cust_002", "cust_003", /* ... */], // Multiple records
    endpoint_path: "/api/customers/export",
    data_sensitivity_level: DataSensitivityLevel.CONFIDENTIAL,  // Customer data
    detail: Detail.EXPORT_REPORT,
  }),
  'record_access'
);
```

**Large Record Lists (Automatic Batching)**:
When accessing large numbers of records, the list may exceed SNS message size limits. The library automatically handles this by:
1. Generating a unique `event_uuid` for the log event
2. Splitting the `id_list` into multiple messages
3. Adding `part` and `total_parts` fields to each message for correlation

This is handled transparently - you just pass the full `id_list` and the library takes care of batching.

---

## Key Configuration & Security Changes

### `logMfaStatusChange()`

**When to use**: When MFA is enabled, disabled, or a device is added/removed.

**Event Types**: `EventType.MFA_STATUS_CHANGE`

**Specific Parameters**:
- `event_type` (EventType): MFA status change event
- `status` (Status): Success or failure
- `target_object` (string): The user whose MFA status is changing
- `mfa_status` (Status): New MFA status
- `mfa_id` (string): MFA device identifier
- `detail` (Detail): Type of change (e.g., `Detail.MFA_ENABLED`, `Detail.MFA_DISABLED`)

**Example**:
```typescript
fireAndForget(
  logMfaStatusChange({
    // ... base log fields ...
    event_type: EventType.MFA_STATUS_CHANGE,
    status: Status.SUCCESS,
    target_object: "user@company.com",
    mfa_status: Status.SUCCESS,
    mfa_id: "mfa-device-123",
    detail: Detail.MFA_ENABLED,
  }),
  'mfa_status_change'
);
```

---

### `logPasswordChangeReset()`

**When to use**: When a user changes or resets their password.

**Event Types**: `EventType.PASSWORD_CHANGE_RESET`

**Specific Parameters**:
- `event_type` (EventType): Password event
- `status` (Status): Success or failure
- `target_object` (string): The user whose password is changing
- `change_status` (Status): Status of the change
- `detail` (Detail): Type of change (e.g., `Detail.PASSWORD_CHANGE`, `Detail.PASSWORD_RESET`)

**Example**:
```typescript
fireAndForget(
  logPasswordChangeReset({
    // ... base log fields ...
    event_type: EventType.PASSWORD_CHANGE_RESET,
    status: Status.SUCCESS,
    target_object: "user@company.com",
    change_status: Status.SUCCESS,
    detail: Detail.PASSWORD_CHANGE,
  }),
  'password_change'
);
```

---

### `logApiKeyLifecycle()`

**When to use**: When API keys are created, rotated, or revoked.

**Event Types**: `EventType.API_KEY_LIFECYCLE`

**Specific Parameters**:
- `event_type` (EventType): API key event
- `status` (Status): Success or failure
- `target_object` (string): Identifier of the API key
- `key_status` (Status): Status of the key
- `detail` (Detail): Type of change (e.g., `Detail.API_KEY_CREATED`, `Detail.API_KEY_REVOKED`)

**Example**:
```typescript
fireAndForget(
  logApiKeyLifecycle({
    // ... base log fields ...
    event_type: EventType.API_KEY_LIFECYCLE,
    status: Status.SUCCESS,
    target_object: "api-key-prod-2024",
    key_status: Status.SUCCESS,
    detail: Detail.API_KEY_CREATED,
  }),
  'api_key_created'
);
```

---

### `logAuthMechanismModification()`

**When to use**: When system-wide authentication settings change (e.g., SSO configuration, local auth settings).

**Event Types**: `EventType.AUTH_MECHANISM_MODIFICATION`

**Specific Parameters**:
- `event_type` (EventType): Auth mechanism event
- `status` (Status): Success or failure
- `target_object` (string): The auth mechanism being modified
- `auth_status` (Status): Status of the auth mechanism
- `detail` (Detail): Type of change (e.g., `Detail.SSO_CONFIG_CREATED`, `Detail.SSO_CONFIG_MODIFIED`)

**Example**:
```typescript
fireAndForget(
  logAuthMechanismModification({
    // ... base log fields ...
    event_type: EventType.AUTH_MECHANISM_MODIFICATION,
    status: Status.SUCCESS,
    target_object: "okta-integration",
    auth_status: Status.SUCCESS,
    detail: Detail.SSO_CONFIG_CREATED,
  }),
  'sso_config_created'
);
```

---

## 📖 Complete Working Example

For a comprehensive end-to-end example showing all 14 security event types with production-ready code, see [`example-publish.ts`](example-publish.ts).

Run the example: `npx ts-node example-publish.ts`

---

# Part 3.5: Auto-Context Extraction Helpers

The package includes helper functions that automatically extract common security context fields from your Next.js application, reducing boilerplate and ensuring consistency.

## Available Context Helpers

```typescript
import {
  createSecurityContext,       // Main helper: extracts all available fields
  getMissingContextFields,     // Check which fields couldn't be auto-extracted
  diagnoseSecurityContext,     // Get detailed info on missing fields + how to fix
  warnMissingContextFields,    // Log warnings for missing fields (dev helper)
} from '@sunrun-security/sr-sec-ts-sns-logger';
```

## Auto-Extracted Fields

| Field | Source (Priority Order) | Notes |
|-------|-------------------------|-------|
| `actor_identifier` | `session.user.email` | From next-auth session |
| `user_agent` | `request.headers['user-agent']` | Browser/client user agent |
| `source_ip_address` | `x-forwarded-for` → `x-real-ip` → `cf-connecting-ip` | Client IP from proxy headers |
| `endpoint_path` | `request.nextUrl.pathname` → `request.url` (parsed) | Request path |
| `http_method` | `request.method` | GET, POST, etc. |
| `session_id` | `session.id` → `sessionToken` (hashed) → `jti` (hashed) → `email+expires` (hashed) | Stable throughout user session |
| `cloud_env_type` | `CLOUD_ENV_TYPE` → `NEXT_PUBLIC_ENVIRONMENT_NAME` → `NODE_ENV` → `VERCEL_ENV` → Lambda function name pattern | Auto-mapped to `prod`/`stage`/`dev`/`test` |
| `cloud_env_name` | `CLOUD_ENV_NAME` → `NEXT_PUBLIC_ENVIRONMENT_NAME` → `VERCEL_ENV` → `NODE_ENV` | Human-readable name (formatted) |
| `cloud_env_unique_id` | `CLOUD_ENV_UNIQUE_ID` → `AWS_ACCOUNT_ID` → ARN parsing from `AWS_EXECUTION_ROLE_ARN`, `AWS_LAMBDA_FUNCTION_ARN` | AWS Account ID (12-digit) |
| `service_name` | `SERVICE_NAME` → `NEXT_PUBLIC_APP_NAME` → `AWS_LAMBDA_FUNCTION_NAME` (cleaned) → `npm_package_name` | Application/service name |
| `service_account_id` | `SERVICE_ACCOUNT_ID` → `AWS_EXECUTION_ROLE_ARN` → `AWS_ROLE_ARN` → `ROLE_ARN` → IAM user pattern from `AWS_ACCESS_KEY_ID` | IAM role/user ARN |

**Note:** If you set `cloudEnvType`, `cloudEnvUniqueId`, `cloudEnvName`, `serviceAccountId`, or `serviceName` during `initSecurityLogging()`, those values take precedence and are automatically included in all logs.

## Fields Requiring Manual Input

These fields **must** be set manually and cannot be auto-extracted:

| Field | Why Manual? |
|-------|-------------|
| `actor_type` | Business logic decision (HUMAN_INTERNAL, SYSTEM, etc.) |
| `user_role` | Application-specific role from your database/auth system |
| `event_type` | Specific to the action being performed |
| `status` | Outcome of the operation |
| Other event-specific fields | Depends on the event type |

## Usage Example

### Basic Usage

```typescript
import { getServerSession } from 'next-auth';
import { NextRequest, NextResponse } from 'next/server';
import {
  createSecurityContext,
  warnMissingContextFields,
  logRecordAccess,
  fireAndForget,
  EventType,
  Status,
  ActorType,
  UserRole,
  Category,
} from '@sunrun-security/sr-sec-ts-sns-logger';

export async function GET(request: NextRequest) {
  const session = await getServerSession();
  
  // Auto-extract what we can from request/session/environment
  const context = createSecurityContext(request, session);
  
  // Optional: warn in development if fields couldn't be extracted
  if (process.env.NODE_ENV === 'development') {
    warnMissingContextFields(context);
  }
  
  // Use the context - logging function will return error if required fields missing
  const result = await logRecordAccess({
    ...context,                    // All auto-extracted fields
    event_type: EventType.RECORD_ACCESS,
    actor_type: ActorType.HUMAN_INTERNAL,  // Manual: business logic
    user_role: UserRole.ADMIN,              // Manual: from your auth system
    status: Status.SUCCESS,
    category: Category.CUSTOMER_DATA_ACTIONS,
    id_list: ['customer-123'],
  });
  
  // Check result - no exceptions thrown
  if (result.status === 'failure') {
    console.error('Security logging failed:', result.message);
    // Message will list any missing required fields
  }
  
  fireAndForget(Promise.resolve(result), 'record_access');
  
  return NextResponse.json({ success: true });
}
```

### Required Environment Variables

For full auto-extraction to work, configure these environment variables:

```bash
# Required for cloud_env_type and cloud_env_name
NEXT_PUBLIC_ENVIRONMENT_NAME=production  # or staging, development, etc.

# Required for service_name (or uses npm_package_name)
SERVICE_NAME=my-nextjs-app

# Required for cloud_env_unique_id (if not in AWS Lambda)
AWS_ACCOUNT_ID=123456789012

# Required for service_account_id (if not in AWS Lambda)
SERVICE_ACCOUNT_ID=arn:aws:iam::123456789012:role/my-role
# OR
AWS_EXECUTION_ROLE_ARN=arn:aws:iam::123456789012:role/my-role
```

### Handling Missing Fields

Auto-extraction is **best-effort** - if environment variables aren't set, the fields will be `undefined`. The logging functions handle this gracefully by returning a failure response (not throwing):

```typescript
const result = await logRecordAccess({ ...context, /* other fields */ });

if (result.status === 'failure') {
  // result.message will say "Missing required fields: cloud_env_type, service_name"
  console.error(result.message);
}
```

### Diagnosing Missing Fields in Development

Use `diagnoseSecurityContext()` to get detailed information about what's missing and how to fix it:

```typescript
import { createSecurityContext, diagnoseSecurityContext } from '@sunrun-security/sr-sec-ts-sns-logger';

const context = createSecurityContext(request, session);
const diagnostics = diagnoseSecurityContext(context);

if (diagnostics.hasMissing) {
  console.log('Missing fields - set these environment variables:');
  diagnostics.suggestions.forEach(({ field, envVar, description }) => {
    console.log(`  ${field}: ${envVar} (${description})`);
  });
}

// Example output:
// Missing fields - set these environment variables:
//   cloud_env_type: CLOUD_ENV_TYPE or NEXT_PUBLIC_ENVIRONMENT_NAME (Environment type)
//   service_name: SERVICE_NAME (Application/service name)
```

---

# Part 4: Production Readiness & Configuration

Once you have implemented and tested your logging using the playbook, follow these steps to prepare for production deployment.

---

## AWS IAM Permissions

### SNS Publishing Permissions

Add to your application's IAM role:

```json
{
  "Effect": "Allow",
  "Action": ["sns:Publish"],
  "Resource": "arn:aws:sns:${var.aws_region}:YOUR_SECURITY_ACCOUNT_ID:sr-sec-logging-log-topic-${local.environment}"
}
```

### KMS Permissions (if SNS topic is encrypted)

```json
{
  "Effect": "Allow",
  "Action": [
    "kms:GenerateDataKey",
    "kms:Decrypt"
  ],
  "Resource": "arn:aws:kms:${var.aws_region}:YOUR_SECURITY_ACCOUNT_ID:key/*"
}
```

### CloudWatch Metrics Permissions

```json
{
  "Effect": "Allow",
  "Action": ["cloudwatch:PutMetricData"],
  "Resource": "*",
  "Condition": {
    "StringEquals": {
      "cloudwatch:namespace": "SecurityLogging"
    }
  }
}
```

---

## REQUIRED: Failure Monitoring

**⚠️ CRITICAL REQUIREMENT**: All teams using this library **MUST** implement CloudWatch alarms to monitor for security logging failures.

### Quick Implementation

**Step 1: Install AWS CloudWatch SDK**
```bash
npm install @aws-sdk/client-cloudwatch
```

**Step 2: Set Up CloudWatch Metrics Helper (One-Time Setup)**

Create a reusable CloudWatch metrics helper in your application:

```typescript
// File: lib/security-metrics.ts
import { CloudWatchClient, PutMetricDataCommand } from '@aws-sdk/client-cloudwatch';

export class SecurityLoggingMetrics {
  private cloudwatch: CloudWatchClient;
  private serviceName: string;
  private namespace: string;
  private environment: string;

  constructor() {
    this.cloudwatch = new CloudWatchClient({ region: process.env.AWS_REGION || 'us-west-2' });
    this.serviceName = process.env.SERVICE_NAME || 'your-service-name';
    this.environment = process.env.ENV_TYPE || 'unknown';
    this.namespace = 'SecurityLogging'; // Standard namespace for all services
  }

  /**
   * Report a security logging failure to CloudWatch
   * This is fire-and-forget to avoid blocking on metrics too
   */
  reportFailure(eventType?: string): void {
    const dimensions = [
      { Name: 'ServiceName', Value: this.serviceName },
      { Name: 'Environment', Value: this.environment }
    ];
    
    // Add event type dimension if provided
    if (eventType) {
      dimensions.push({ Name: 'EventType', Value: eventType });
    }

    const command = new PutMetricDataCommand({
      Namespace: this.namespace,
      MetricData: [{
        MetricName: 'log_failure',
        Value: 1.0,
        Unit: 'Count',
        Timestamp: new Date(),
        Dimensions: dimensions
      }]
    });
    
    // Fire-and-forget: Don't await to avoid blocking
    this.cloudwatch.send(command).catch(error => {
      // Log but don't throw - metrics failure shouldn't break the app
      console.error('Failed to report security logging failure metric:', error);
    });
  }
}

// Export a singleton instance
export const securityMetrics = new SecurityLoggingMetrics();
```

**Step 3: Wire Up Error Handler at Application Startup**

In your application's initialization code (e.g., `app.ts`, `index.ts`, `server.ts`):

```typescript
// File: app.ts or index.ts
import * as SecurityLogging from '@sunrun-security/sr-sec-ts-sns-logger';
import { securityMetrics } from './lib/security-metrics';

// Initialize security logging
SecurityLogging.initSecurityLogging({
  testMode: process.env.NODE_ENV !== 'production'
});

// Set up error handler to automatically emit CloudWatch metrics on failures
SecurityLogging.setSecurityLoggingErrorHandler((error, eventType) => {
  console.error(`Security logging failed for ${eventType}:`, error);
  securityMetrics.reportFailure(eventType);
});
```

**Step 4: Use Fire-and-Forget Everywhere**

Now in your application code, use `fireAndForget` and failures automatically get logged + reported:

```typescript
// File: controllers/auth.ts
import * as SecurityLogging from '@sunrun-security/sr-sec-ts-sns-logger';

async function handleUserLogin(user, request) {
  // Fire-and-forget: Failures automatically emit CloudWatch metrics via the error handler
  // Note: Environment fields (cloudEnvType, serviceName, etc.) already set in initSecurityLogging()
  SecurityLogging.fireAndForget(
    SecurityLogging.logUserLogin({
      actor_identifier: user.email,
      actor_type: SecurityLogging.ActorType.HUMAN_INTERNAL,
      session_id: request.session.id,
      event_type: SecurityLogging.EventType.LOGIN_ATTEMPT,
      status: SecurityLogging.Status.SUCCESS,
      user_agent: request.headers['user-agent'],
      user_role: SecurityLogging.UserRole.ADMIN,
      auth_protocol: SecurityLogging.AuthProtocol.OAUTH2_JWT,
      detail: SecurityLogging.Detail.USER_INITIATED,
    }),
    SecurityLogging.EventType.LOGIN_ATTEMPT // For better CloudWatch dimensions
  );

  return { success: true };
}
```

✅ **That's it!** Your application now:
- Doesn't block on security logging (fire-and-forget)
- Automatically logs errors to console
- Automatically emits CloudWatch metrics for monitoring
- Stays fast even if SNS is slow or down

**Step 5: Create CloudWatch Alarms**

Production (strict monitoring):
```json
{
  "AlarmName": "security-logging-failures-prod",
  "ComparisonOperator": "GreaterThanOrEqualToThreshold",
  "EvaluationPeriods": 1,
  "MetricName": "log_failure",
  "Namespace": "SecurityLogging",
  "Period": 300,
  "Statistic": "Sum",
  "Threshold": 1,
  "AlarmDescription": "SEV_1: Critical - Security logging failures detected"
}
```

### Reusable Terraform Module

Use our pre-built Terraform module: [security-logging-alerts](https://github.com/SunRun/softsec-aws-user-access-review/tree/develop/templates/shared/security-logging-alerts)

```hcl
module "security_logging_alerts" {
  source = "../../../templates/shared/security-logging-alerts"
  
  service_name            = "your-service-name"
  environment             = local.environment
  cloudwatch_namespace    = "YourTeam/SecurityLogging"
  
  failure_threshold_immediate = 1
  severity_immediate          = "sev_1"
  
  common_tags = local.common_tags
}
```

---

## Configuration Options

### `initSecurityLogging()` Parameters

```typescript
initSecurityLogging({
  // --- SNS Configuration ---
  topicArn?: string,           // Override SNS topic ARN (auto-detected by default)
  regionName?: string,         // Override AWS region (defaults to us-west-2)
  testMode?: boolean,          // Enable test mode (logs to console, no SNS)
  
  // --- Environment Configuration (set once, auto-included in all logs) ---
  cloudEnvType?: string,       // e.g., CloudEnvType.PROD, CloudEnvType.DEV
  cloudEnvUniqueId?: string,   // AWS Account ID, e.g., "123456789012"
  cloudEnvName?: string,       // Environment name, e.g., "production"
  serviceAccountId?: string,   // IAM role ARN or service account ID
  serviceName?: string,        // Your application name
});
```

**Production Example** (recommended):
```typescript
SecurityLogging.initSecurityLogging({
  cloudEnvType: SecurityLogging.CloudEnvType.PROD,
  cloudEnvUniqueId: process.env.AWS_ACCOUNT_ID!,
  cloudEnvName: 'production',
  serviceAccountId: process.env.AWS_EXECUTION_ROLE_ARN!,
  serviceName: 'my-application',
});
// Now all log calls automatically include these environment fields!
```

**Development Example**:
```typescript
SecurityLogging.initSecurityLogging({
  testMode: true,  // Logs to console instead of SNS
  cloudEnvType: SecurityLogging.CloudEnvType.DEV,
  cloudEnvUniqueId: '687126124183',
  cloudEnvName: 'development',
  serviceAccountId: 'local-dev',
  serviceName: 'my-application',
});
```

### Environment Variables (Alternative to Init Parameters)

| Variable | Description | Example |
|----------|-------------|---------|
| `SECURITY_LOGS_TOPIC_ARN` | Override SNS topic ARN | `arn:aws:sns:us-west-2:123:topic` |
| `AWS_REGION` | Override AWS region | `us-west-2` |
| `CLOUD_ENV_TYPE` | Environment type | `prod`, `dev`, `stage` |
| `CLOUD_ENV_UNIQUE_ID` | AWS Account ID | `123456789012` |
| `CLOUD_ENV_NAME` | Environment name | `production` |
| `SERVICE_ACCOUNT_ID` | IAM role ARN | `arn:aws:iam::123:role/my-role` |
| `SERVICE_NAME` | Your service name | `my-app` |

> **Note**: Init parameters take precedence over environment variables.

---

# Appendix

## Appendix A: Schema Structure & Fields

This module implements a **flat JSON schema** where all fields are at the same level (no nested objects).

### Base Fields (In Every Log Entry)

**These fields appear in every log entry. Most are set once during initialization:**

| Field Name | Description | Example | Set Via |
|------------|-------------|---------|---------|
| `timestamp` | Event timestamp in UTC | `"2025-12-13T04:33:12.000Z"` | Auto-generated |
| `event_uuid` | Unique event identifier | `"1ade00c0-3dde-4d1f-..."` | Auto-generated |
| `log_category` | High-level event category | `"authn_n_session"` | Auto-derived from event_type |
| `caller_function` | Function that called the logger | `"signIn"` | Auto-extracted |
| `caller_file` | File that called the logger | `"auth.ts"` | Auto-extracted |
| `cloud_env_type` | Environment type | `"prod"`, `"dev"` | **Init** or per-call |
| `service_name` | Application/service name | `"my-app"` | **Init** or per-call |
| `cloud_env_unique_id` | AWS Account ID | `"123456789012"` | **Init** or per-call |
| `cloud_env_name` | Environment name | `"production"` | **Init** or per-call |
| `service_account_id` | IAM role ARN | `"arn:aws:iam::..."` | **Init** or per-call |
| `event_type` | Event type identifier | `"login_attempt"` | Per-call (required) |
| `status` | Event outcome | `"status.general.success"` | Per-call (required) |
| `actor_identifier` | User email/ID | `"user@company.com"` | Per-call (required) |
| `actor_type` | Actor classification | `"actor.human.internal"` | Per-call (required) |
| `session_id` | Session identifier | `"session-abc-123"` | Per-call (required) |
| `source_ip_address` | Source IP address | `"203.0.113.54"` | Per-call (optional) |
| `service_component_name` | Specific component within service | `"auth-handler"` | Per-call (optional) |

**Key Point**: Set `cloud_env_*`, `service_*` fields once in `initSecurityLogging()` - they're automatically included in all subsequent logs!

### Event-Specific Fields (Additional Required Fields)

Each logging function requires additional fields beyond the base fields. These event-specific fields are included in the same flat JSON structure (not nested). See the [API Function Reference](#part-3-api-function-reference) for details on each function's required fields.

**Example Combined Structure:**
```json
{
  "timestamp": "2025-12-13T04:33:35.331000+00:00",
  "event_type": "login_attempt",
  "log_category": "authn_n_session",
  "status": "status.general.success",
  "actor_identifier": "user@company.com",
  "actor_type": "actor.human.internal",
  "session_id": "session-abc-123",
  "cloud_env_type": "prod",
  "service_name": "my-application",
  "cloud_env_unique_id": "123456789012",
  "cloud_env_name": "production",
  "service_account_id": "arn:aws:iam::123456789012:role/my-role",
  "source_ip_address": "10.0.0.1",
  "user_agent": "Mozilla/5.0 (Test Browser)",
  "user_role": "role.classification.admin",
  "auth_protocol": "auth.protocol.oauth2_j