ts-otp-aws/SKILL.md
Implement OTP and passwordless authentication on AWS for TypeScript projects using Cognito CUSTOM_AUTH triggers (default) or a custom DynamoDB-backed flow, with SES (email) and SNS (SMS) delivery. Use when the user mentions OTP, one-time password, passwordless login, magic link, Cognito custom auth, DefineAuthChallenge, CreateAuthChallenge, VerifyAuthChallengeResponse, SES verification email, SNS SMS code, or MFA over email/SMS. Covers architecture decision (Cognito vs custom), Lambda trigger handlers, SES/SNS notifiers, DynamoDB schema with TTL, rate limiting, constant-time comparison, threat model (enumeration, replay, brute force), and aws-sdk-client-mock testing.
npx skillsauth add kayaman/skills ts-otp-awsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Implement OTP / passwordless authentication on AWS for TypeScript projects.
CUSTOM_AUTH triggers (DefineAuthChallenge, CreateAuthChallenge, VerifyAuthChallengeResponse, PreSignUp).Cognito User Pool with CUSTOM_AUTH flow + four Lambda triggers, SESv2 for email, SNS for SMS. The user pool stays the source of truth; the four triggers handle OTP generation, delivery, and verification. Drop-in handlers and Terraform live in assets/. The custom-DynamoDB alternate is documented in references/custom-dynamodb-otp.md — pick it only when you can't use Cognito.
| If you need… | Pick |
|---|---|
| Managed user directory, JWTs, MFA toggle | Cognito CUSTOM_AUTH |
| Standalone API, no user pool, full control over JWT | custom DynamoDB |
| Federation (Google/Apple) alongside OTP | Cognito (out of scope here) |
| Lowest latency, no cold-start triggers | custom DynamoDB |
Full matrix + threat model: references/architecture-decision.md.
┌────────┐ InitiateAuth(CUSTOM_AUTH) ┌──────────────┐ Define→Create ┌──────────────────┐
│ client │ ───────────────────────────▶ │ Cognito │ ───────────────▶ │ auth-triggers λ │
│ (Next/ │ ◀── session + masked hint ── │ User Pool │ │ ─ create-chall │
│ Astro) │ │ │ │ ─ define-chall │
│ │ Respond(answer=otp) │ │ │ ─ verify-chall │
│ │ ───────────────────────────▶ │ │ │ ─ pre-signup │
│ │ ◀── id/access/refresh ────── │ │ └────┬──────┬──────┘
└────────┘ └──────────────┘ │ │
SESv2│ │SNS
┌───────▼──┐ ┌─▼──────┐
│ email │ │ SMS │
└──────────┘ └────────┘
Custom path swaps Cognito + triggers for one Lambda + a DynamoDB OTP table (PK/SK + ttl).
scripts/audit-project.sh /path/to/repo
Reports which patterns the repo already has (Cognito CUSTOM_AUTH, custom DDB, client-only) and what's missing. Exits non-zero on missing rg. See scripts/README.md for sample output.
references/custom-dynamodb-otp.md).Copy the five trigger handlers and two notifiers verbatim:
assets/handlers/ → lambda/auth-triggers/src/handlers/
assets/notifiers/ → lambda/auth-triggers/src/notifiers/
Required env vars on the Lambda:
| Var | Purpose |
|---|---|
| SES_FROM | Verified SES sender (e.g. [email protected]) |
| SMS_SENDER_ID | 3–11 alphanumeric SMS sender (default OTP) |
| OTP_DEV_BYPASS | Set to 1 only in non-prod to accept 000000 |
| APP_NAME | Brand string used in email/SMS body |
The dispatcher (handler.ts) routes by event.triggerSource. Walkthrough of each trigger and the phantom-user (prevent_user_existence_errors) path: references/cognito-custom-auth.md.
Minimum viable set under assets/terraform/:
cognito-userpool.tf — pool + 4 trigger ARN bindings, prevent_user_existence_errors=ENABLED, no MFA (OTP is the auth).auth-triggers-lambda.tf — Node 22 ESM Lambda, log retention, IAM role.ses-identity.tf — domain identity + DKIM + custom MAIL FROM.sns-sms.tf — MonthlySpendLimit, DefaultSMSType=Transactional, opt-out preferences.dynamodb-otp-table.tf — only for the custom path (PK/SK + ttl + PITR + deletion protection).IAM least-privilege policies: assets/iam/auth-trigger-policy.json and assets/iam/custom-otp-policy.json.
Server-side adapter calling InitiateAuthCommand + RespondToAuthChallengeCommand lives at assets/frontend/cognito-server-snippet.ts. Key points:
AuthFlow: "CUSTOM_AUTH", pass ClientMetadata: { channel: "email" | "sms" }.ListUsers with phone_number = "..." filter; pass through unknown identifiers unchanged so Cognito's phantom-user path stays opaque.NotAuthorizedException → session expired; fresh challenge response → wrong code.crypto.timingSafeEqual); reject early if not 6 ASCII digits.define-challenge.ts; surface as failAuthentication=true.userNotFound=true — silently return a synthetic challenge or you'll leak existence.***1234, not +5511915551234).Full checklist: references/security-checklist.md.
aws-sdk-client-mock v4, one mock per service. Inline minimum:
import { SESv2Client, SendEmailCommand } from "@aws-sdk/client-sesv2";
import { mockClient } from "aws-sdk-client-mock";
const ses = mockClient(SESv2Client);
ses.on(SendEmailCommand).resolves({});
// run handler, then:
expect(ses.commandCalls(SendEmailCommand)).toHaveLength(1);
State-machine and phantom-user assertions: references/testing-patterns.md.
When Cognito is not an option, follow references/custom-dynamodb-otp.md. For layering TOTP authenticator apps or WebAuthn passkeys on top of OTP, see references/second-factor.md — kept brief on purpose.
Channel deliverability (SES sandbox/DKIM, SNS 10DLC/DLT/origination): references/messaging-channels.md. Source attributions: references/citations.md.
skills-ref validate /home/kayaman/Projects/ts-otp-aws-skill (frontmatter + naming).scripts/audit-project.sh ~/Projects/ai-assisted-dev should say "Cognito CUSTOM_AUTH — complete".wc -l SKILL.md < 200; each references/*.md < 500.OtpCodeInput is referenced but not bundled).tools
Guidance for designing charts, graphs, plots, dashboards, and data visualizations that communicate clearly and persuade. Use when creating or reviewing a visualization, choosing a chart type, picking a color palette, decluttering a busy graphic, fixing misleading axes or proportions, building a dashboard, annotating a figure, or turning data into a presentation, report, or data-driven story. Grounded in the standard data-visualization literature (Knaflic, Tufte, Cleveland & McGill, Cairo, Wilke, Munzner, Few, Berinato). Covers chart selection, graphical perception and encoding, color and accessibility, decluttering, graphical integrity, dashboards, and narrative. Does NOT cover building data pipelines or ETL, statistical modeling or analysis methods, BI tool/vendor selection, or general UI/UX layout (see ux-design-principles). Tool-agnostic, with optional Python recipes.
development
Architect and implement production-grade microservices systems in TypeScript (NestJS) and Python (FastAPI), including resilience, observability, testing, deployment, and migration guidance.
development
--- name: databricks-genie-spaces-best-practices description: Design, configure, curate, govern, monitor, and integrate Databricks AI/BI Genie Spaces — the natural-language-to-SQL surface over Unity Catalog. Covers space scoping, general instructions, parameterized example SQL, SQL functions, trusted assets, JOIN configuration, knowledge store, certified queries, benchmarks, monitoring tab, feedback loops, the Genie Conversation API, governance via Unity Catalog (row filters, column masks, embed
tools
O'Reilly book reference lookup for software design decisions. Coding agents MUST use this skill whenever making or reviewing any design decision — choosing an architecture pattern, selecting a data structure, structuring a module, evaluating a library, deciding on an API contract, applying a design pattern, weighing trade-offs between approaches, or any moment where a choice between two or more implementation strategies comes up. The O'Reilly MCP handles the actual book search; this skill tells you how and when to invoke it. Trigger even for seemingly small decisions (naming, layering, concurrency model, error handling strategy) — the best engineers reach for authoritative references before committing to an approach.