npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@sunrun-security/sr-sec-ts-sns-logger

v2.1.2

Published

TypeScript/JavaScript security logging module for sending structured security logs to AWS SNS

Readme

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

Part 1: Quick Start (5-Minute Goal)

  1. Install and Build
  2. Initialize the Logger
  3. Log Your First Event

Before Production Implementation

  1. The Developer's Mindset
  2. Prerequisites: Review Required
  3. Request SNS Permissions

Part 2: The Implementation Playbook

  1. Identify Security-Relevant Activities
  2. Map Activities to Logger Functions
  3. Track Your Implementation Progress

Part 3: API Function Reference

  1. Authentication & Session Events
  2. Authorization & Access Events
  3. API & Data Access Events
  4. Key Configuration & Security Changes

Part 3.5: Auto-Context Extraction Helpers

  1. Available Context Helpers
  2. Auto-Extracted Fields
  3. Fields Requiring Manual Input
  4. Handling Missing Fields

Part 4: Production Readiness

  1. AWS IAM Permissions
  2. REQUIRED: Failure Monitoring
  3. Configuration Options

Appendix

  1. Schema Structure & Fields
  2. Sample Log Outputs
  3. Migration from Python Version
  4. Error Handling & Troubleshooting
  5. Module Structure
  6. TypeScript Support
  7. 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.

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:

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.

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:

// 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:

// 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):

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:

// 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:

{
  "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:

// 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 (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:

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

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:

{
  "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": "[email protected]",
  "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 - Organizational standards
  2. security-log-fields.ts - Technical field definitions and constants
  3. 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:

  • 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:

  • 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:

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:[email protected] | | 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 →

Is the activity about changing what a user is allowed to do?
📖 Review the Authorization & Access functions →

Is the activity an API endpoint being called?
📖 Review the API Endpoint & Data Access functions →

  • ⚠️ Critical Decision Point: Does this endpoint access specific customer/entity data?
    • YES - Use: logRecordAccess() - 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)

Is the activity about changing critical credentials or settings?
📖 Review the Key Configuration Changes functions →

💡 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

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. Each function includes comprehensive parameter validation and error handling.

Base Fields (Required for ALL Functions)

// 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., "[email protected]"
  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):

// Recommended: Non-blocking, won't delay your response
// Note: Environment fields (cloudEnvType, serviceName, etc.) set in initSecurityLogging()
fireAndForget(
  logUserLogin({
    // Actor/Session context
    actor_identifier: "[email protected]",
    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:

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:

// 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:

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:

// Note: Environment fields set in initSecurityLogging()
fireAndForget(
  logPermissionRoleChange({
    // Actor/Session context - actor is the admin making the change
    actor_identifier: "[email protected]",
    actor_type: ActorType.HUMAN_INTERNAL,
    session_id: request.session.id,
    
    // Event-specific fields
    event_type: EventType.ROLE_ASSIGN,
    status: Status.SUCCESS,
    target_user_identifier: "[email protected]", // 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:

fireAndForget(
  logUserStatusChange({
    // ... base log fields ...
    event_type: EventType.USER_STATUS_CHANGE,
    status: Status.SUCCESS,
    target_user_identifier: "[email protected]",
    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:

fireAndForget(
  logImpersonationEvent({
    // Base log fields - actor is the support admin
    actor_identifier: "[email protected]",
    // ... other base fields ...
    
    event_type: EventType.IMPERSONATION_EVENT,
    status: Status.SUCCESS,
    target_user_identifier: "[email protected]",
    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:

fireAndForget(
  logUserInviteEvent({
    // ... base log fields ...
    event_type: EventType.USER_INVITE_EVENT,
    status: Status.SUCCESS,
    target_user_email: "[email protected]",
    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:

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):

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):

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:

fireAndForget(
  logMfaStatusChange({
    // ... base log fields ...
    event_type: EventType.MFA_STATUS_CHANGE,
    status: Status.SUCCESS,
    target_object: "[email protected]",
    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:

fireAndForget(
  logPasswordChangeReset({
    // ... base log fields ...
    event_type: EventType.PASSWORD_CHANGE_RESET,
    status: Status.SUCCESS,
    target_object: "[email protected]",
    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:

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:

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.

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

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-forx-real-ipcf-connecting-ip | Client IP from proxy headers | | endpoint_path | request.nextUrl.pathnamerequest.url (parsed) | Request path | | http_method | request.method | GET, POST, etc. | | session_id | session.idsessionToken (hashed) → jti (hashed) → email+expires (hashed) | Stable throughout user session | | cloud_env_type | CLOUD_ENV_TYPENEXT_PUBLIC_ENVIRONMENT_NAMENODE_ENVVERCEL_ENV → Lambda function name pattern | Auto-mapped to prod/stage/dev/test | | cloud_env_name | CLOUD_ENV_NAMENEXT_PUBLIC_ENVIRONMENT_NAMEVERCEL_ENVNODE_ENV | Human-readable name (formatted) | | cloud_env_unique_id | CLOUD_ENV_UNIQUE_IDAWS_ACCOUNT_ID → ARN parsing from AWS_EXECUTION_ROLE_ARN, AWS_LAMBDA_FUNCTION_ARN | AWS Account ID (12-digit) | | service_name | SERVICE_NAMENEXT_PUBLIC_APP_NAMEAWS_LAMBDA_FUNCTION_NAME (cleaned) → npm_package_name | Application/service name | | service_account_id | SERVICE_ACCOUNT_IDAWS_EXECUTION_ROLE_ARNAWS_ROLE_ARNROLE_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

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:

# 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):

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:

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:

{
  "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)

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

CloudWatch Metrics Permissions

{
  "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

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:

// 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):

// 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:

// 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):

{
  "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

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

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):

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:

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 | "[email protected]" | 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 for details on each function's required fields.

Example Combined Structure:

{
  "timestamp": "2025-12-13T04:33:35.331000+00:00",
  "event_type": "login_attempt",
  "log_category": "authn_n_session",
  "status": "status.general.success",
  "actor_identifier": "[email protected]",
  "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