From af1250350a49872559ad33d2a054317e20202188 Mon Sep 17 00:00:00 2001 From: Lucas Vas Date: Wed, 7 Jan 2026 23:15:22 -0500 Subject: [PATCH] feat: add post confirmation lambda hook --- signup-lambda/.gitignore | 1 + signup-lambda/Cargo.toml | 12 ++++++ signup-lambda/README.md | 52 ++++++++++++++++++++++++ signup-lambda/src/handler.rs | 76 ++++++++++++++++++++++++++++++++++++ signup-lambda/src/main.rs | 11 ++++++ 5 files changed, 152 insertions(+) create mode 100644 signup-lambda/.gitignore create mode 100644 signup-lambda/Cargo.toml create mode 100644 signup-lambda/README.md create mode 100644 signup-lambda/src/handler.rs create mode 100644 signup-lambda/src/main.rs diff --git a/signup-lambda/.gitignore b/signup-lambda/.gitignore new file mode 100644 index 0000000..1de5659 --- /dev/null +++ b/signup-lambda/.gitignore @@ -0,0 +1 @@ +target \ No newline at end of file diff --git a/signup-lambda/Cargo.toml b/signup-lambda/Cargo.toml new file mode 100644 index 0000000..3915e3d --- /dev/null +++ b/signup-lambda/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "signup-lambda" +version = "0.1.0" +edition = "2021" + +[dependencies] + +lambda_runtime = "1.0.1" +aws-sdk-cognitoidentityprovider = "1.105.0" +serde = "1" +tokio = { version = "1", features = ["macros"] } +aws-config = "1.8.12" diff --git a/signup-lambda/README.md b/signup-lambda/README.md new file mode 100644 index 0000000..fb024e8 --- /dev/null +++ b/signup-lambda/README.md @@ -0,0 +1,52 @@ +# Introduction + +signup-lambda is a Rust project that implements an AWS Lambda function in Rust. + +## Prerequisites + +- [Rust](https://www.rust-lang.org/tools/install) +- [Cargo Lambda](https://www.cargo-lambda.info/guide/installation.html) + +## Building + +To build the project for production, run `cargo lambda build --release`. Remove the `--release` flag to build for development. + +Read more about building your lambda function in [the Cargo Lambda documentation](https://www.cargo-lambda.info/commands/build.html). + +## Testing + +You can run regular Rust unit tests with `cargo test`. + +If you want to run integration tests locally, you can use the `cargo lambda watch` and `cargo lambda invoke` commands to do it. + +First, run `cargo lambda watch` to start a local server. When you make changes to the code, the server will automatically restart. + +Second, you'll need a way to pass the event data to the lambda function. + +You can use the existent [event payloads](https://github.com/awslabs/aws-lambda-rust-runtime/tree/main/lambda-events/src/fixtures) in the Rust Runtime repository if your lambda function is using one of the supported event types. + +You can use those examples directly with the `--data-example` flag, where the value is the name of the file in the [lambda-events](https://github.com/awslabs/aws-lambda-rust-runtime/tree/main/lambda-events/src/fixtures) repository without the `example_` prefix and the `.json` extension. + +```bash +cargo lambda invoke --data-example apigw-request +``` + +For generic events, where you define the event data structure, you can create a JSON file with the data you want to test with. For example: + +```json +{ + "command": "test" +} +``` + +Then, run `cargo lambda invoke --data-file ./data.json` to invoke the function with the data in `data.json`. + + +Read more about running the local server in [the Cargo Lambda documentation for the `watch` command](https://www.cargo-lambda.info/commands/watch.html). +Read more about invoking the function in [the Cargo Lambda documentation for the `invoke` command](https://www.cargo-lambda.info/commands/invoke.html). + +## Deploying + +To deploy the project, run `cargo lambda deploy`. This will create an IAM role and a Lambda function in your AWS account. + +Read more about deploying your lambda function in [the Cargo Lambda documentation](https://www.cargo-lambda.info/commands/deploy.html). diff --git a/signup-lambda/src/handler.rs b/signup-lambda/src/handler.rs new file mode 100644 index 0000000..01bb2ba --- /dev/null +++ b/signup-lambda/src/handler.rs @@ -0,0 +1,76 @@ +use lambda_runtime::LambdaEvent; +use aws_sdk_cognitoidentityprovider::Client; +use aws_config::BehaviorVersion; +use serde::{Serialize, Deserialize}; +use std::collections::HashMap; +use aws_sdk_cognitoidentityprovider::error::ProvideErrorMetadata; + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub(crate) struct PostConfirmationEvent { + version: String, + trigger_source: String, + region: String, + user_pool_id: String, + user_name: String, + caller_context: CallerContext, + request: Request, + response: Response +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub(crate) struct CallerContext { + client_id: String, +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub(crate) struct Request { + user_attributes: HashMap, +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub(crate) struct Response {} + +fn get_error_message(err: impl ProvideErrorMetadata) -> String { + err.message().unwrap_or("An unknown error occurred").to_string() +} + +async fn add_user_to_group(provider: Client, user_id: String, group_name: impl Into) -> Result<(), String> { + let group_name = group_name.into(); + println!("Adding user {} to group {}", user_id, group_name); + match provider + .admin_add_user_to_group() + .user_pool_id("us-east-1_Lfjuy5zaM") + .username(user_id) + .group_name(group_name) + .send() + .await + { + Ok(_) => Ok(()), + Err(err) => Err(get_error_message(err)), + } +} + +pub(crate) async fn handler(event: LambdaEvent) -> Result { + println!("Found event: {:?}", event.payload); + + let user_id = event.payload.request.user_attributes.get("sub").unwrap().to_string(); + let config = aws_config::load_defaults(BehaviorVersion::latest()).await; + let client = Client::new(&config); + + // Check the client ID of the request to assign the user to the correct location. + let res = match event.payload.caller_context.client_id.as_str() { + "10jr2h3vtpu9n7gj46pvg5qo2q" => add_user_to_group(client, user_id, "ScholarshipStudents").await, + "56c2bqvl021rv8d5mq36blt7jv" => add_user_to_group(client, user_id, "ScholarshipProviders").await, + _ => Err("Invalid client ID, denying all user access.".to_string()), + }; + + if res.is_ok() { + Ok(event.payload) + } else { + Err(res.unwrap_err()) + } +} diff --git a/signup-lambda/src/main.rs b/signup-lambda/src/main.rs new file mode 100644 index 0000000..8204be7 --- /dev/null +++ b/signup-lambda/src/main.rs @@ -0,0 +1,11 @@ +use lambda_runtime::{run, service_fn, tracing, Error}; +mod handler; + +use handler::handler as function_handler; + +#[tokio::main] +async fn main() -> Result<(), Error> { + tracing::init_default_subscriber(); + + run(service_fn(function_handler)).await +}