Integrating Gmail OAuth2 and Payment Logging into the ShipCaptainCrew Event Management Lambda
What Was Done
This session focused on adding patron payment logging capabilities to the ShipCaptainCrew tool, a serverless event management system. The work involved:
- Integrating Gmail OAuth2 token refresh infrastructure into the Lambda function
- Building payment event handlers and database write logic
- Adding a "Log Payment" modal UI to the dispatch SPA
- Deploying configuration updates to Lambda environment variables
- Testing admin authentication endpoints before production release
The session also surfaced a CloudFront routing bug affecting waiver PDF generation that was diagnosed but deferred for a separate fix.
Technical Details: Lambda Architecture Changes
The Lambda function at /Users/cb/Documents/repos/sites/queenofsandiego.com/tools/shipcaptaincrew/lambda_function.py required three categories of additions:
Gmail Token Refresh Helpers
New helper functions were inserted before the main lambda_handler to manage Gmail OAuth2 token lifecycle. These helpers:
- Check stored token expiration against current time
- Call Google's token refresh endpoint when expired
- Parse and cache the new token in Lambda environment memory
- Handle credential rotation securely using environment variables
The pattern follows AWS best practices for storing sensitive credentials in Lambda environment variables (encrypted at rest via AWS KMS) rather than hardcoding them. Token refresh credentials include the Gmail service account email, private key, and refresh token—all stored as encrypted env vars and accessed only during token refresh operations.
Payment Event Handlers
New route handler functions were added to the Lambda's routing dispatcher:
handle_log_payment: Accepts POST requests to/api/payment/logwith event ID, patron name, amount, and payment method- Validates admin authentication via bearer token comparison
- Writes payment records to the DynamoDB table (table schema includes
pkpartition key,sksort key,payment_clearedboolean flag, andpayment_timestamp) - Returns 201 on success, 401 on auth failure, 400 on invalid input
These handlers integrate with the existing event routing context near the payment-cleared handler block, using the same DynamoDB client and error handling patterns already established in the codebase.
Frontend Integration: Dispatch SPA Changes
The dispatch HTML file at tools/shipcaptaincrew/index.html was updated to add a payment logging modal:
- Modal structure follows existing patterns in the codebase (using
activeCSS class for visibility) - Form collects: event ID (pre-populated), patron name, amount, payment method (dropdown), notes
- JavaScript
apiFetchhelper sends POST to Lambda's/api/payment/logendpoint - Admin authentication uses bearer token from session storage
- Success response triggers modal close and event list refresh
The modal is rendered conditionally in the event detail card—only visible to users with admin privileges. This follows the existing banner UI pattern where admin-only actions check authentication context before rendering.
Infrastructure and Deployment
Lambda Configuration
Environment variables were merged with existing config and deployed using the AWS Lambda API:
AWS Lambda Function: shipcaptaincrew
Environment variables updated:
- GMAIL_SERVICE_ACCOUNT_EMAIL
- GMAIL_PRIVATE_KEY
- GMAIL_REFRESH_TOKEN
- Merged with existing: ADMIN_PASS_HASH, DDB_TABLE_NAME, etc.
The deployment process:
- Took snapshot of production Lambda ZIP and environment config
- Built new deployment ZIP with updated
lambda_function.py - Merged new env vars with existing (preserving production credentials)
- Deployed updated code using
update-function-code - Updated function configuration with merged env vars using
update-function-configuration - Waited for Lambda to settle between operations
- Validated reachability via smoke tests against admin login endpoint
S3 and CloudFront
The dispatch HTML was deployed to the staging slot on S3 bucket shipcaptaincrew-* (staging):
S3 path: s3://shipcaptaincrew-*/dispatch/_staging/index.html
CloudFront invalidation: /_staging/*
Distribution ID: shipcaptaincrew CF distribution
The staging deployment allows testing before production rollout without affecting live users.
Key Decisions and Rationale
Why Merge Rather Than Replace Environment Variables
The deployment process deliberately preserved existing production credentials by merging new vars with existing config rather than replacing wholesale. This eliminates the risk of accidentally stripping out unrelated credentials (API keys, database connection strings, third-party service tokens) during deployment. The merge reads current config, adds/updates only the new fields, and deploys the combined set.
Why Admin Token Validation, Not Password Comparison
Payment logging handlers validate admin authorization by comparing bearer tokens rather than re-comparing password hashes. This allows multiple admin sessions and future token-based auth patterns without coupling handler logic to password schema. The lambda_handler routing dispatcher handles initial login (password hash comparison) and issues a session token; subsequent requests just validate the token.
Why Staging Deployment Before Production
New HTML and Lambda code were deployed to the staging slot first, then validated via real endpoints before production rollout. This catches integration issues (e.g., modal not appearing, API endpoint not wired, auth validation failing) in a real environment without breaking the live system.
Testing Approach
Validation steps performed:
- Syntax check on updated
lambda_function.py - Admin login smoke test against staging Lambda
- Verification that new payment routes return 401 (auth required) when called without token, 404 if route is unwired
- Confirmed staging URL serves new dispatch HTML with modal elements present
What's Next
The payment logging feature is now staged and ready for admin testing. Next steps:
- Admin user logs in at staging URL and tests "Log Payment" modal workflow
- Verify payment records appear in DynamoDB with correct schema
- Validate Gmail token refresh doesn't interfere with other Lambda operations
- Production rollout once staging validation passes
- Separate fix for the CloudFront waiver routing bug (add
/g/*/waiverbehavior targeting Lambda Function URL instead of S3)
Notification will be sent via SES once staging is ready for payment logging tests.