1import type * as lambda from "aws-lambda";
2import type * as Effect from "effect/Effect";
3import * as Binding from "../../Binding.ts";
4import type { UserPool, UserPoolTriggerName } from "./UserPool.ts";
5
6/**
7 * The typed event each trigger slot receives (the Cognito
8 * `version`/`triggerSource`/`request`/`response` envelope from
9 * `aws-lambda`).
10 */
11export interface UserPoolTriggerEventMap {
12 PreSignUp: lambda.PreSignUpTriggerEvent;
13 PostConfirmation: lambda.PostConfirmationTriggerEvent;
14 PreAuthentication: lambda.PreAuthenticationTriggerEvent;
15 PostAuthentication: lambda.PostAuthenticationTriggerEvent;
16 CustomMessage: lambda.CustomMessageTriggerEvent;
17 DefineAuthChallenge: lambda.DefineAuthChallengeTriggerEvent;
18 CreateAuthChallenge: lambda.CreateAuthChallengeTriggerEvent;
19 VerifyAuthChallengeResponse: lambda.VerifyAuthChallengeResponseTriggerEvent;
20 PreTokenGeneration: lambda.PreTokenGenerationTriggerEvent;
21 UserMigration: lambda.UserMigrationTriggerEvent;
22}
23
24export type UserPoolTriggerEvent<T extends UserPoolTriggerName> =
25 UserPoolTriggerEventMap[T];
26
27/**
28 * The `triggerSource` prefix Cognito stamps on invocations of each trigger
29 * slot (e.g. `PreSignUp` fires with `PreSignUp_SignUp`,
30 * `PreSignUp_AdminCreateUser`, …; `PreTokenGeneration` fires with
31 * `TokenGeneration_*`). Used by the runtime dispatcher to route events to
32 * the registered handler.
33 */
34export const userPoolTriggerSourcePrefixes: Record<
35 UserPoolTriggerName,
36 string
37> = {
38 PreSignUp: "PreSignUp_",
39 PostConfirmation: "PostConfirmation_",
40 PreAuthentication: "PreAuthentication_",
41 PostAuthentication: "PostAuthentication_",
42 CustomMessage: "CustomMessage_",
43 DefineAuthChallenge: "DefineAuthChallenge_",
44 CreateAuthChallenge: "CreateAuthChallenge_",
45 VerifyAuthChallengeResponse: "VerifyAuthChallengeResponse_",
46 PreTokenGeneration: "TokenGeneration_",
47 UserMigration: "UserMigration_",
48};
49
50export interface UserPoolTriggerProps<
51 T extends UserPoolTriggerName = UserPoolTriggerName,
52> {
53 /** The trigger slot to handle, e.g. `PreSignUp` or `CustomMessage`. */
54 trigger: T;
55}
56
57/**
58 * A user pool trigger handler receives the typed trigger event and returns
59 * the (usually mutated) event, which becomes the Lambda's response back to
60 * Cognito. Returning `void` responds with the event as-is.
61 */
62export type UserPoolTriggerHandler<
63 T extends UserPoolTriggerName,
64 Req = never,
65> = (
66 event: UserPoolTriggerEvent<T>,
67) => Effect.Effect<UserPoolTriggerEvent<T> | void, never, Req>;
68
69export type UserPoolTriggerEventSourceService = <
70 T extends UserPoolTriggerName,
71 Req = never,
72>(
73 userPool: UserPool,
74 props: UserPoolTriggerProps<T>,
75 handler: UserPoolTriggerHandler<T, Req>,
76) => Effect.Effect<void, never, never>;
77
78/**
79 * Event source connecting a Cognito {@link UserPool} Lambda trigger to the
80 * hosting Lambda function.
81 *
82 * At deploy time the Lambda implementation
83 * (`Lambda.UserPoolTriggerEventSource`) injects the function ARN into the
84 * pool's `LambdaConfig` (via the pool's binding contract) and creates the
85 * `lambda:InvokeFunction` Permission for `cognito-idp.amazonaws.com`; at
86 * runtime it dispatches matching trigger events to the handler and returns
87 * the handler's (mutated) event to Cognito.
88 *
89 * Use the {@link onUserPoolTrigger} helper (or the per-slot shorthands
90 * {@link onPreSignUp}, {@link onPostConfirmation},
91 * {@link onPreTokenGeneration}, {@link onCustomMessage}) rather than the
92 * service directly, and provide `Lambda.UserPoolTriggerEventSource` on the
93 * hosting function.
94 * ### Handling User Pool Triggers
95 * **Example:** Auto-confirm Sign-ups from a Lambda Function
96 * ```typescript
97 * export default AuthFunction.make(
98 * { main: import.meta.url },
99 * Effect.gen(function* () {
100 * const pool = yield* Cognito.UserPool("Users", {});
101 *
102 * // deploy: wires PreSignUp in the pool's LambdaConfig + invoke Permission
103 * // runtime: dispatches PreSignUp_* events to this handler
104 * yield* Cognito.onPreSignUp(pool, (event) =>
105 * Effect.sync(() => Cognito.autoConfirmUser(event, { verifyEmail: true })),
106 * );
107 *
108 * return {};
109 * }).pipe(Effect.provide(Lambda.UserPoolTriggerEventSource)),
110 * );
111 * ```
112 *
113 * @binding
114 */
115export interface UserPoolTriggerEventSource extends Binding.Service<
116 UserPoolTriggerEventSource,
117 "AWS.Cognito.UserPoolTriggerEventSource",
118 UserPoolTriggerEventSourceService
119> {}
120
121export const UserPoolTriggerEventSource =
122 Binding.Service<UserPoolTriggerEventSource>(
123 "AWS.Cognito.UserPoolTriggerEventSource",
124 );
125
126/**
127 * Handle a Cognito user pool Lambda trigger with the current Lambda
128 * function.
129 *
130 * Provide `Lambda.UserPoolTriggerEventSource` on the hosting function to
131 * satisfy the requirement.
132 *
133 * @param userPool The user pool whose trigger to handle.
134 * @param props The trigger slot to handle.
135 * @param handler Invoked once per trigger event; the returned event is the
136 * response Cognito receives.
137 *
138 * @example Auto-confirm every sign-up
139 * ```typescript
140 * yield* Cognito.onUserPoolTrigger(pool, { trigger: "PreSignUp" }, (event) =>
141 * Effect.sync(() => Cognito.autoConfirmUser(event, { verifyEmail: true })),
142 * );
143 * ```
144 */
145export function onUserPoolTrigger<T extends UserPoolTriggerName, Req = never>(
146 userPool: UserPool,
147 props: UserPoolTriggerProps<T>,
148 handler: UserPoolTriggerHandler<T, Req>,
149): Effect.Effect<void, never, UserPoolTriggerEventSource> {
150 return UserPoolTriggerEventSource.use((source) =>
151 source(userPool, props, handler),
152 );
153}
154
155/**
156 * Handle the `PreSignUp` trigger — runs before Cognito registers a new
157 * user; the handler can auto-confirm the user (see
158 * {@link autoConfirmUser}) or reject the sign-up.
159 */
160export const onPreSignUp = <Req = never>(
161 userPool: UserPool,
162 handler: UserPoolTriggerHandler<"PreSignUp", Req>,
163) => onUserPoolTrigger(userPool, { trigger: "PreSignUp" }, handler);
164
165/**
166 * Handle the `PostConfirmation` trigger — runs after a user confirms their
167 * account (welcome emails, provisioning rows, analytics).
168 */
169export const onPostConfirmation = <Req = never>(
170 userPool: UserPool,
171 handler: UserPoolTriggerHandler<"PostConfirmation", Req>,
172) => onUserPoolTrigger(userPool, { trigger: "PostConfirmation" }, handler);
173
174/**
175 * Handle the `PreTokenGeneration` trigger — runs before Cognito issues
176 * tokens; the handler can add, override, or suppress claims via
177 * `event.response.claimsOverrideDetails`.
178 */
179export const onPreTokenGeneration = <Req = never>(
180 userPool: UserPool,
181 handler: UserPoolTriggerHandler<"PreTokenGeneration", Req>,
182) => onUserPoolTrigger(userPool, { trigger: "PreTokenGeneration" }, handler);
183
184/**
185 * Handle the `CustomMessage` trigger — customizes the verification /
186 * invitation messages Cognito sends by mutating
187 * `event.response.emailSubject` / `emailMessage` / `smsMessage`.
188 */
189export const onCustomMessage = <Req = never>(
190 userPool: UserPool,
191 handler: UserPoolTriggerHandler<"CustomMessage", Req>,
192) => onUserPoolTrigger(userPool, { trigger: "CustomMessage" }, handler);
193
194/**
195 * Mutate a `PreSignUp` trigger event's response to auto-confirm the user
196 * (and optionally auto-verify their email / phone number), then return the
197 * event so it can be handed straight back to Cognito.
198 *
199 * @example Sign-ups arrive CONFIRMED, no confirmation code required
200 * ```typescript
201 * yield* Cognito.onPreSignUp(pool, (event) =>
202 * Effect.sync(() => Cognito.autoConfirmUser(event, { verifyEmail: true })),
203 * );
204 * ```
205 */
206export const autoConfirmUser = <E extends lambda.PreSignUpTriggerEvent>(
207 event: E,
208 options?: {
209 /** Also mark the user's email as verified. @default false */
210 verifyEmail?: boolean;
211 /** Also mark the user's phone number as verified. @default false */
212 verifyPhone?: boolean;
213 },
214): E => {
215 event.response.autoConfirmUser = true;
216 if (options?.verifyEmail === true) {
217 event.response.autoVerifyEmail = true;
218 }
219 if (options?.verifyPhone === true) {
220 event.response.autoVerifyPhone = true;
221 }
222 return event;
223};