From e60612642aaa10756b5c33f7ab807d09cd3fbacd Mon Sep 17 00:00:00 2001 From: David Calavera Date: Tue, 20 Feb 2024 21:11:37 -0800 Subject: [PATCH 1/5] Improve Api Gateway events struct. - Remove the generic attribute since the authorizer values are not always of the same type, just use Value. - Match authorizer type to use the same type for all events. - Make the authorizer fields match the Java and DotNet implementation, which both use a Map for Rest and WebSocket events. -- https://github.com/aws/aws-lambda-dotnet/blob/master/Libraries/src/Amazon.Lambda.APIGatewayEvents/APIGatewayProxyRequest.cs -- https://github.com/aws/aws-lambda-java-libs/blob/main/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/APIGatewayV2WebSocketEvent.java#L225 Signed-off-by: David Calavera --- lambda-events/src/event/apigw/mod.rs | 133 ++++++++++++++++----------- 1 file changed, 80 insertions(+), 53 deletions(-) diff --git a/lambda-events/src/event/apigw/mod.rs b/lambda-events/src/event/apigw/mod.rs index 22c508b2..da84477d 100644 --- a/lambda-events/src/event/apigw/mod.rs +++ b/lambda-events/src/event/apigw/mod.rs @@ -5,19 +5,14 @@ use crate::custom_serde::{ use crate::encodings::Body; use http::{HeaderMap, Method}; use query_map::QueryMap; -use serde::de::DeserializeOwned; -use serde::{Deserialize, Serialize}; +use serde::{de::DeserializeOwned, ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer}; use serde_json::Value; use std::collections::HashMap; /// `ApiGatewayProxyRequest` contains data coming from the API Gateway proxy #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] -pub struct ApiGatewayProxyRequest -where - T1: DeserializeOwned + Default, - T1: Serialize, -{ +pub struct ApiGatewayProxyRequest { /// The resource path defined in API Gateway #[serde(default)] pub resource: Option, @@ -44,7 +39,7 @@ where #[serde(default)] pub stage_variables: HashMap, #[serde(bound = "")] - pub request_context: ApiGatewayProxyRequestContext, + pub request_context: ApiGatewayProxyRequestContext, #[serde(default)] pub body: Option, #[serde(default, deserialize_with = "deserialize_nullish_boolean")] @@ -72,11 +67,7 @@ pub struct ApiGatewayProxyResponse { /// Lambda function. It also includes Cognito identity information for the caller. #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] -pub struct ApiGatewayProxyRequestContext -where - T1: DeserializeOwned, - T1: Serialize, -{ +pub struct ApiGatewayProxyRequestContext { #[serde(default)] pub account_id: Option, #[serde(default)] @@ -99,10 +90,13 @@ where pub resource_path: Option, #[serde(default)] pub path: Option, - #[serde(deserialize_with = "deserialize_lambda_map")] - #[serde(default)] - #[serde(bound = "")] - pub authorizer: HashMap, + #[serde( + default, + deserialize_with = "deserialize_authorizer_fields", + serialize_with = "serialize_authorizer_fields", + skip_serializing_if = "ApiGatewayRequestAuthorizer::is_empty" + )] + pub authorizer: ApiGatewayRequestAuthorizer, #[serde(with = "http_method")] pub http_method: Method, #[serde(default)] @@ -168,11 +162,7 @@ pub struct ApiGatewayV2httpRequest { /// `ApiGatewayV2httpRequestContext` contains the information to identify the AWS account and resources invoking the Lambda function. #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] -pub struct ApiGatewayV2httpRequestContext -where - T1: DeserializeOwned, - T1: Serialize, -{ +pub struct ApiGatewayV2httpRequestContext { #[serde(default)] pub route_key: Option, #[serde(default)] @@ -181,9 +171,9 @@ where pub stage: Option, #[serde(default)] pub request_id: Option, - #[serde(bound = "", default)] + #[serde(default)] #[serde(skip_serializing_if = "Option::is_none")] - pub authorizer: Option>, + pub authorizer: Option, /// The API Gateway HTTP API Id #[serde(default)] #[serde(rename = "apiId")] @@ -203,19 +193,17 @@ where /// `ApiGatewayV2httpRequestContextAuthorizerDescription` contains authorizer information for the request context. #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct ApiGatewayV2httpRequestContextAuthorizerDescription -where - T1: DeserializeOwned, - T1: Serialize, -{ +pub struct ApiGatewayV2httpRequestContextAuthorizerDescription { #[serde(skip_serializing_if = "Option::is_none")] pub jwt: Option, - #[serde(deserialize_with = "deserialize_lambda_map")] - #[serde(default)] - #[serde(bound = "")] - #[serde(skip_serializing_if = "HashMap::is_empty")] - pub lambda: HashMap, + #[serde( + bound = "", + rename = "lambda", + default, + skip_serializing_if = "HashMap::is_empty", + deserialize_with = "deserialize_lambda_map" + )] + pub fields: HashMap, #[serde(skip_serializing_if = "Option::is_none")] pub iam: Option, } @@ -332,13 +320,7 @@ pub struct ApiGatewayRequestIdentity { /// `ApiGatewayWebsocketProxyRequest` contains data coming from the API Gateway proxy #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] -pub struct ApiGatewayWebsocketProxyRequest -where - T1: DeserializeOwned + Default, - T1: Serialize, - T2: DeserializeOwned + Default, - T2: Serialize, -{ +pub struct ApiGatewayWebsocketProxyRequest { /// The resource path defined in API Gateway #[serde(default)] pub resource: Option, @@ -367,7 +349,7 @@ where #[serde(default)] pub stage_variables: HashMap, #[serde(bound = "")] - pub request_context: ApiGatewayWebsocketProxyRequestContext, + pub request_context: ApiGatewayWebsocketProxyRequestContext, #[serde(default)] pub body: Option, #[serde(default, deserialize_with = "deserialize_nullish_boolean")] @@ -379,13 +361,7 @@ where /// Cognito identity information for the caller. #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] -pub struct ApiGatewayWebsocketProxyRequestContext -where - T1: DeserializeOwned, - T1: Serialize, - T2: DeserializeOwned, - T2: Serialize, -{ +pub struct ApiGatewayWebsocketProxyRequestContext { #[serde(default)] pub account_id: Option, #[serde(default)] @@ -398,8 +374,13 @@ where pub identity: ApiGatewayRequestIdentity, #[serde(default)] pub resource_path: Option, - #[serde(bound = "")] - pub authorizer: Option, + #[serde( + default, + deserialize_with = "deserialize_authorizer_fields", + serialize_with = "serialize_authorizer_fields", + skip_serializing_if = "ApiGatewayRequestAuthorizer::is_empty" + )] + pub authorizer: ApiGatewayRequestAuthorizer, #[serde(deserialize_with = "http_method::deserialize_optional")] #[serde(serialize_with = "http_method::serialize_optional")] #[serde(skip_serializing_if = "Option::is_none")] @@ -425,7 +406,7 @@ where #[serde(default)] pub message_direction: Option, #[serde(bound = "")] - pub message_id: Option, + pub message_id: Option, #[serde(default)] pub request_time: Option, pub request_time_epoch: i64, @@ -768,6 +749,40 @@ fn default_http_method() -> Method { Method::GET } +/// `ApiGatewayRequestAuthorizer` is a type alias for `ApiGatewayV2httpRequestContextAuthorizerDescription`. +/// This type is used by all events that receive request authorizer information. +pub type ApiGatewayRequestAuthorizer = ApiGatewayV2httpRequestContextAuthorizerDescription; + +impl ApiGatewayRequestAuthorizer { + fn is_empty(&self) -> bool { + self.fields.is_empty() + } +} + +fn deserialize_authorizer_fields<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + let fields: Option> = Option::deserialize(deserializer)?; + let mut authorizer = ApiGatewayRequestAuthorizer::default(); + if let Some(fields) = fields { + authorizer.fields = fields; + } + + Ok(authorizer) +} + +pub fn serialize_authorizer_fields( + authorizer: &ApiGatewayRequestAuthorizer, + ser: S, +) -> Result { + let mut map = ser.serialize_map(Some(authorizer.fields.len()))?; + for (k, v) in &authorizer.fields { + map.serialize_entry(k, v)?; + } + map.end() +} + #[cfg(test)] mod test { use super::*; @@ -991,4 +1006,16 @@ mod test { let reparsed: ApiGatewayProxyRequest = serde_json::from_slice(output.as_bytes()).unwrap(); assert_eq!(parsed, reparsed); } + + #[test] + #[cfg(feature = "apigw")] + fn example_apigw_request_authorizer_fields() { + let data = include_bytes!("../../fixtures/example-apigw-request.json"); + let parsed: ApiGatewayProxyRequest = serde_json::from_slice(data).unwrap(); + + let fields = parsed.request_context.authorizer.fields; + assert_eq!(Some("admin"), fields.get("principalId").unwrap().as_str()); + assert_eq!(Some(1), fields.get("clientId").unwrap().as_u64()); + assert_eq!(Some("Exata"), fields.get("clientName").unwrap().as_str()); + } } From 7a3ab97f555c3bebc986349a393a6e754efa1bac Mon Sep 17 00:00:00 2001 From: David Calavera Date: Tue, 20 Feb 2024 21:16:07 -0800 Subject: [PATCH 2/5] Expose RequestContext::authorizer() for Api Gateway requests. Signed-off-by :David Calavera --- lambda-http/src/request.rs | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/lambda-http/src/request.rs b/lambda-http/src/request.rs index bce2e3d3..9e789270 100644 --- a/lambda-http/src/request.rs +++ b/lambda-http/src/request.rs @@ -17,6 +17,8 @@ use crate::ext::extensions::{PathParameters, StageVariables}; use crate::ext::extensions::{QueryStringParameters, RawHttpPath}; #[cfg(feature = "alb")] use aws_lambda_events::alb::{AlbTargetGroupRequest, AlbTargetGroupRequestContext}; +#[cfg(any(feature = "apigw_rest", feature = "apigw_http", feature = "apigw_websockets"))] +use aws_lambda_events::apigw::ApiGatewayRequestAuthorizer; #[cfg(feature = "apigw_rest")] use aws_lambda_events::apigw::{ApiGatewayProxyRequest, ApiGatewayProxyRequestContext}; #[cfg(feature = "apigw_http")] @@ -425,6 +427,22 @@ impl From for http::Request { } } +impl RequestContext { + /// Returns the Api Gateway Authorizer information for a request. + #[cfg(any(feature = "apigw_rest", feature = "apigw_http", feature = "apigw_websockets"))] + pub fn authorizer(&self) -> Option<&ApiGatewayRequestAuthorizer> { + match self { + #[cfg(feature = "apigw_rest")] + Self::ApiGatewayV1(ag) => Some(&ag.authorizer), + #[cfg(feature = "apigw_http")] + Self::ApiGatewayV2(ag) => ag.authorizer.as_ref(), + #[cfg(feature = "apigw_websockets")] + Self::WebSocket(ag) => Some(&ag.authorizer), + _ => None, + } + } +} + /// Deserializes a `Request` from a `Read` impl providing JSON events. /// /// # Example @@ -920,4 +938,20 @@ mod tests { assert_eq!(vec!["val1", "val2"], query.0.my_key); assert_eq!(vec!["val3", "val4"], query.0.my_other_key); } + + #[test] + #[cfg(feature = "apigw_rest")] + fn deserializes_request_authorizer() { + let input = include_str!("../../lambda-events/src/fixtures/example-apigw-request.json"); + let result = from_str(input); + assert!( + result.is_ok(), + "event was not parsed as expected {result:?} given {input}" + ); + let req = result.expect("failed to parse request"); + + let req_context = req.request_context_ref().expect("Request is missing RequestContext"); + let authorizer = req_context.authorizer().expect("authorizer is missing"); + assert_eq!(Some("admin"), authorizer.fields.get("principalId").unwrap().as_str()); + } } From 93a0c3f7c4a6295c8e0cfc3f06283c811a9dbb74 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Tue, 20 Feb 2024 21:16:53 -0800 Subject: [PATCH 3/5] Add example on how to use Api Gateway authorizers in Axum. This also shows how to work with the RequestExt trait and the RequestContext object. Signed-off-by: David Calavera --- .../http-axum-apigw-authorizer/Cargo.toml | 14 ++++ examples/http-axum-apigw-authorizer/README.md | 13 +++ .../http-axum-apigw-authorizer/src/main.rs | 80 +++++++++++++++++++ 3 files changed, 107 insertions(+) create mode 100644 examples/http-axum-apigw-authorizer/Cargo.toml create mode 100644 examples/http-axum-apigw-authorizer/README.md create mode 100644 examples/http-axum-apigw-authorizer/src/main.rs diff --git a/examples/http-axum-apigw-authorizer/Cargo.toml b/examples/http-axum-apigw-authorizer/Cargo.toml new file mode 100644 index 00000000..5c3e806e --- /dev/null +++ b/examples/http-axum-apigw-authorizer/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "http-axum-apigw-authorizer" +version = "0.1.0" +edition = "2021" + +[dependencies] +axum = "0.7" +lambda_http = { path = "../../lambda-http" } +lambda_runtime = { path = "../../lambda-runtime" } +serde = "1.0.196" +serde_json = "1.0" +tokio = { version = "1", features = ["macros"] } +tracing = { version = "0.1", features = ["log"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/examples/http-axum-apigw-authorizer/README.md b/examples/http-axum-apigw-authorizer/README.md new file mode 100644 index 00000000..2d05df59 --- /dev/null +++ b/examples/http-axum-apigw-authorizer/README.md @@ -0,0 +1,13 @@ +# Axum example that integrates with Api Gateway authorizers + +This example shows how to extract information from the Api Gateway Request Authorizer in an Axum handler. + +## Build & Deploy + +1. Install [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation) +2. Build the function with `cargo lambda build --release` +3. Deploy the function to AWS Lambda with `cargo lambda deploy --iam-role YOUR_ROLE` + +## Build for ARM 64 + +Build the function with `cargo lambda build --release --arm64` diff --git a/examples/http-axum-apigw-authorizer/src/main.rs b/examples/http-axum-apigw-authorizer/src/main.rs new file mode 100644 index 00000000..3b8a0810 --- /dev/null +++ b/examples/http-axum-apigw-authorizer/src/main.rs @@ -0,0 +1,80 @@ +use axum::{ + async_trait, + extract::{FromRequest, Request}, + http::StatusCode, + response::Json, + routing::get, + Router, +}; +use lambda_http::{run, Error, RequestExt}; +use serde_json::{json, Value}; +use std::{collections::HashMap, env::set_var}; + +struct AuthorizerField(String); +struct AuthorizerFields(HashMap); + +#[async_trait] +impl FromRequest for AuthorizerField +where + S: Send + Sync, +{ + type Rejection = (StatusCode, &'static str); + + async fn from_request(req: Request, _state: &S) -> Result { + req.request_context_ref() + .and_then(|r| r.authorizer()) + .and_then(|a| a.fields.get("field_name")) + .and_then(|f| f.as_str()) + .map(|v| Self(v.to_string())) + .ok_or_else(|| (StatusCode::BAD_REQUEST, "`field_name` authorizer field is missing")) + } +} + +#[async_trait] +impl FromRequest for AuthorizerFields +where + S: Send + Sync, +{ + type Rejection = (StatusCode, &'static str); + + async fn from_request(req: Request, _state: &S) -> Result { + req.request_context_ref() + .and_then(|r| r.authorizer()) + .map(|a| Self(a.fields.clone())) + .ok_or_else(|| (StatusCode::BAD_REQUEST, "authorizer is missing")) + } +} + +async fn extract_field(AuthorizerField(field): AuthorizerField) -> Json { + Json(json!({ "field extracted": field })) +} + +async fn extract_all_fields(AuthorizerFields(fields): AuthorizerFields) -> Json { + Json(json!({ "authorizer fields": fields })) +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + // If you use API Gateway stages, the Rust Runtime will include the stage name + // as part of the path that your application receives. + // Setting the following environment variable, you can remove the stage from the path. + // This variable only applies to API Gateway stages, + // you can remove it if you don't use them. + // i.e with: `GET /test-stage/todo/id/123` without: `GET /todo/id/123` + set_var("AWS_LAMBDA_HTTP_IGNORE_STAGE_IN_PATH", "true"); + + // required to enable CloudWatch error logging by the runtime + tracing_subscriber::fmt() + .with_max_level(tracing::Level::INFO) + // disable printing the name of the module in every log line. + .with_target(false) + // disabling time is handy because CloudWatch will add the ingestion time. + .without_time() + .init(); + + let app = Router::new() + .route("/extract-field", get(extract_field)) + .route("/extract-all-fields", get(extract_all_fields)); + + run(app).await +} From a250d99a121c49eafacdcf132bd1c2010517ec7e Mon Sep 17 00:00:00 2001 From: David Calavera Date: Wed, 21 Feb 2024 08:36:22 -0800 Subject: [PATCH 4/5] Rename the Authorizer structs. Make the name more consistent since it's used for multiple versions of Api Gateway. Keep the old names as deprecated. Signed-off-by: David Calavera --- lambda-events/src/event/apigw/mod.rs | 33 ++++++++++++++++------------ 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/lambda-events/src/event/apigw/mod.rs b/lambda-events/src/event/apigw/mod.rs index da84477d..1a9b1f1a 100644 --- a/lambda-events/src/event/apigw/mod.rs +++ b/lambda-events/src/event/apigw/mod.rs @@ -191,11 +191,11 @@ pub struct ApiGatewayV2httpRequestContext { pub authentication: Option, } -/// `ApiGatewayV2httpRequestContextAuthorizerDescription` contains authorizer information for the request context. +/// `ApiGatewayRequestAuthorizer` contains authorizer information for the request context. #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] -pub struct ApiGatewayV2httpRequestContextAuthorizerDescription { +pub struct ApiGatewayRequestAuthorizer { #[serde(skip_serializing_if = "Option::is_none")] - pub jwt: Option, + pub jwt: Option, #[serde( bound = "", rename = "lambda", @@ -205,13 +205,13 @@ pub struct ApiGatewayV2httpRequestContextAuthorizerDescription { )] pub fields: HashMap, #[serde(skip_serializing_if = "Option::is_none")] - pub iam: Option, + pub iam: Option, } -/// `ApiGatewayV2httpRequestContextAuthorizerJwtDescription` contains JWT authorizer information for the request context. +/// `ApiGatewayRequestAuthorizerJwtDescription` contains JWT authorizer information for the request context. #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] -pub struct ApiGatewayV2httpRequestContextAuthorizerJwtDescription { +pub struct ApiGatewayRequestAuthorizerJwtDescription { #[serde(deserialize_with = "deserialize_lambda_map")] #[serde(default)] pub claims: HashMap, @@ -219,10 +219,10 @@ pub struct ApiGatewayV2httpRequestContextAuthorizerJwtDescription { pub scopes: Option>, } -/// `ApiGatewayV2httpRequestContextAuthorizerIamDescription` contains IAM information for the request context. +/// `ApiGatewayRequestAuthorizerIamDescription` contains IAM information for the request context. #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] -pub struct ApiGatewayV2httpRequestContextAuthorizerIamDescription { +pub struct ApiGatewayRequestAuthorizerIamDescription { #[serde(default)] pub access_key: Option, #[serde(default)] @@ -230,7 +230,7 @@ pub struct ApiGatewayV2httpRequestContextAuthorizerIamDescription { #[serde(default)] pub caller_id: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub cognito_identity: Option, + pub cognito_identity: Option, #[serde(default)] pub principal_org_id: Option, #[serde(default)] @@ -239,10 +239,10 @@ pub struct ApiGatewayV2httpRequestContextAuthorizerIamDescription { pub user_id: Option, } -/// `ApiGatewayV2httpRequestContextAuthorizerCognitoIdentity` contains Cognito identity information for the request context. +/// `ApiGatewayRequestAuthorizerCognitoIdentity` contains Cognito identity information for the request context. #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] -pub struct ApiGatewayV2httpRequestContextAuthorizerCognitoIdentity { +pub struct ApiGatewayRequestAuthorizerCognitoIdentity { pub amr: Vec, #[serde(default)] pub identity_id: Option, @@ -749,9 +749,14 @@ fn default_http_method() -> Method { Method::GET } -/// `ApiGatewayRequestAuthorizer` is a type alias for `ApiGatewayV2httpRequestContextAuthorizerDescription`. -/// This type is used by all events that receive request authorizer information. -pub type ApiGatewayRequestAuthorizer = ApiGatewayV2httpRequestContextAuthorizerDescription; +#[deprecated = "use `ApiGatewayRequestAuthorizer` instead"] +pub type ApiGatewayV2httpRequestContextAuthorizerDescription = ApiGatewayRequestAuthorizer; +#[deprecated = "use `ApiGatewayRequestAuthorizerJwtDescription` instead"] +pub type ApiGatewayV2httpRequestContextAuthorizerJwtDescription = ApiGatewayRequestAuthorizerJwtDescription; +#[deprecated = "use `ApiGatewayRequestAuthorizerIamDescription` instead"] +pub type ApiGatewayV2httpRequestContextAuthorizerIamDescription = ApiGatewayRequestAuthorizerIamDescription; +#[deprecated = "use `ApiGatewayRequestAuthorizerCognitoIdentity` instead"] +pub type ApiGatewayV2httpRequestContextAuthorizerCognitoIdentity = ApiGatewayRequestAuthorizerCognitoIdentity; impl ApiGatewayRequestAuthorizer { fn is_empty(&self) -> bool { From 38ed70eb96e445c5d3f18bb121cd6e186cfe5c9e Mon Sep 17 00:00:00 2001 From: David Calavera Date: Wed, 21 Feb 2024 08:44:32 -0800 Subject: [PATCH 5/5] Add example without using Axum extractor. Signed-off-by: David Calavera --- .../http-axum-apigw-authorizer/src/main.rs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/examples/http-axum-apigw-authorizer/src/main.rs b/examples/http-axum-apigw-authorizer/src/main.rs index 3b8a0810..bb03de07 100644 --- a/examples/http-axum-apigw-authorizer/src/main.rs +++ b/examples/http-axum-apigw-authorizer/src/main.rs @@ -26,7 +26,7 @@ where .and_then(|a| a.fields.get("field_name")) .and_then(|f| f.as_str()) .map(|v| Self(v.to_string())) - .ok_or_else(|| (StatusCode::BAD_REQUEST, "`field_name` authorizer field is missing")) + .ok_or((StatusCode::BAD_REQUEST, "`field_name` authorizer field is missing")) } } @@ -41,7 +41,7 @@ where req.request_context_ref() .and_then(|r| r.authorizer()) .map(|a| Self(a.fields.clone())) - .ok_or_else(|| (StatusCode::BAD_REQUEST, "authorizer is missing")) + .ok_or((StatusCode::BAD_REQUEST, "authorizer is missing")) } } @@ -53,6 +53,18 @@ async fn extract_all_fields(AuthorizerFields(fields): AuthorizerFields) -> Json< Json(json!({ "authorizer fields": fields })) } +async fn authorizer_without_extractor(req: Request) -> Result, (StatusCode, &'static str)> { + let auth = req + .request_context_ref() + .and_then(|r| r.authorizer()) + .ok_or((StatusCode::BAD_REQUEST, "authorizer is missing"))?; + + let field1 = auth.fields.get("field1").and_then(|v| v.as_str()).unwrap_or_default(); + let field2 = auth.fields.get("field2").and_then(|v| v.as_str()).unwrap_or_default(); + + Ok(Json(json!({ "field1": field1, "field2": field2 }))) +} + #[tokio::main] async fn main() -> Result<(), Error> { // If you use API Gateway stages, the Rust Runtime will include the stage name @@ -74,7 +86,8 @@ async fn main() -> Result<(), Error> { let app = Router::new() .route("/extract-field", get(extract_field)) - .route("/extract-all-fields", get(extract_all_fields)); + .route("/extract-all-fields", get(extract_all_fields)) + .route("/authorizer-without-extractor", get(authorizer_without_extractor)); run(app).await }