Creating a AWS Cognito PreSignup Lambda in DotNet - c#

Using a .Net Core 1.0 Lambda I want to be able to create a Lambda function which handles the PreSignUp trigger from an AWS Cognito User pool.
using Amazon.Lambda.Core;
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.Json.JsonSerializer))]
public class PreSignUp_SignUp
{
public string userPoolId { get; set; }
public const string EmailKey = "email";
public const string PhoneNumber = "phone_number";
public Dictionary<string,string> userAttributes { get; set; }
public Dictionary<string, string> validationData { get; set; }
}
public class PreSignup_SignUpResponse
{
public bool autoConfirmUser { get; set; }
}
public class Function
{
public PreSignup_SignUpResponse FunctionHandler(PreSignUp_SignUp input, ILambdaContext context)
{
return new PreSignup_SignUpResponse { autoConfirmUser = true };
}
}
Though the request succeeds and returns a response when invoking the Lambda with an example request of:
{
"datasetName": "datasetName",
"eventType": "SyncTrigger",
"region": "us-east-1",
"identityId": "identityId",
"datasetRecords": {
"SampleKey2": {
"newValue": "newValue2",
"oldValue": "oldValue2",
"op": "replace"
},
"SampleKey1": {
"newValue": "newValue1",
"oldValue": "oldValue1",
"op": "replace"
}
},
"identityPoolId": "identityPoolId",
"version": 2
}
When performing an actual SignUp via the .Net AmazonCognitoIdentityProviderClient I get back an error:
Amazon.CognitoIdentityProvider.Model.InvalidLambdaResponseException :
Unrecognizable lambda output
Which I'm guessing means I have not got the shape of the response (and possibly even request) correct.
Does anyone have an example of a .Net Lambda function that works for the PreSignUp trigger in AWS Cognito?

The cognito trigger requests/responses must contain the entire payload as specified in the Cognito trigger documentation:
http://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-identity-pools-working-with-aws-lambda-triggers.html
I have found while diagnosing this issue the best place to start is by creating a function handler that takes a JObject and then logs and return's that same object e.g.
public JObject FunctionHandler(JObject input, ILambdaContext context)
{
context.Logger.LogLine("Input was: " + input);
return input;
}
This captures the payload in cloudwatch logs and then helps steer you towards the strongly typed structured required.
In my case for PreSignUp I ended up creating the following types to make a simple function which auto-verifies all supplied credentials.
public abstract class AbstractTriggerRequest
{
[JsonProperty("userAttributes")]
public Dictionary<string, string> UserAttributes { get; set; }
}
public abstract class AbstractTriggerResponse
{
}
public class TriggerCallerContext
{
[JsonProperty("awsSdkVersion")]
public string AwsSdkVersion { get; set; }
[JsonProperty("clientId")]
public string ClientId { get; set; }
}
public abstract class AbstractTriggerBase<TRequest, TResponse>
where TRequest: AbstractTriggerRequest
where TResponse: AbstractTriggerResponse
{
[JsonProperty("version")]
public int Version { get; set; }
[JsonProperty("triggerSource")]
public string TriggerSource { get; set; }
[JsonProperty("region")]
public string Region { get; set; }
[JsonProperty("userPoolId")]
public string UserPoolId { get; set; }
[JsonProperty("callerContext")]
public TriggerCallerContext CallerContext { get; set; }
[JsonProperty("request")]
public TRequest Request { get; set; }
[JsonProperty("response")]
public TResponse Response { get; set; }
[JsonProperty("userName", NullValueHandling = NullValueHandling.Ignore)]
public string UserName { get; set; }
}
public class PreSignUpSignUpRequest : AbstractTriggerRequest
{
[JsonProperty("validationData")]
public Dictionary<string,string> ValidationData { get; set; }
}
The Lambda function then ends up with the following signature:
public class Function
{
public PreSignUp_SignUp FunctionHandler(PreSignUp_SignUp input, ILambdaContext context)
{
context.Logger.LogLine("Auto-confirming everything!");
input.Response = new PreSignUpSignUpResponse {
AutoConfirmUser = true,
// you can only auto-verify email or phone if it's present in the user attributes
AutoVerifyEmail = input.Request.UserAttributes.ContainsKey("email"),
AutoVerifyPhone = input.Request.UserAttributes.ContainsKey("phone_number")
};
return input;
}
}
Hopefully this helps anyone else running into issues writing Lambda triggers for Cognito.

The previous two responses are now inaccurate unless you still use the old, less performant Amazon.Lambda.Serialization.Json.JsonSerializer. This old serializer uses Newtonsoft.Json while the new Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer implements the recent System.Text.Json.
As a result, a JObject parameter is no longer appropriate and should instead be replaced with JsonElement. If you try to use JObject with this new serializer, you will get an error since the serializer doesn't know how to deal with this object.
You should read this to gain a better understanding of how it all works, but you access properties of the JsonElement using GetProperty("[insert property name here]").
For example:
public async Task<JsonElement> FunctionHandler(JsonElement input, ILambdaContext context)
{
var request = input.GetProperty("request");
var userAttributes = request.GetProperty("userAttributes");
string email = userAttributes.GetProperty("email").GetString();
return input;
}
This way, you don't need to construct entire classes to accommodate the required request and response parameters, just get and set the properties you need.

There is already another great answer in here. However I'm not a expert .NET developer so this solution makes more sense to me.
class AutoVerifyEmail
{
public AutoVerifyEmail() { }
public JObject AutoVerifyEmailPreSignup(JObject input, ILambdaContext context)
{
//Console.Write(input); //Print Input
input["response"]["autoVerifyEmail"] = true;
input["response"]["autoConfirmUser"] = true;
return input;
}
}

Related

How to return IEnumerable json array in a Http Post controller using .Net core

I have the following dotnet core code and I'm trying to return a TestResponse JSON object that has a few nodes under it. However, using the return Enumerable.Range(1, 4).Select(index => new Entities.TestResponse call in the post return for some reason all the attributes of response are not found in the enclosure when clearly Entities.TestResponse has the response definition. I'm probably not configuring the Enumerable enclosure correctly. Does anyone know how to resolve this, so I can set the response.result & response.exception and return response JSON from my REST POST method?
namespace TestApi.Entities
{
public class TestResponse
{
public TestResponseNodes response { get; set; }
}
public class TestResponseNodes
{
public string result { get; set; }
public string exception { get; set; }
}
}
[HttpPost]
public Task<IEnumerable<Entities.TestResponse>> Post([FromBody] String input)
{
return Enumerable.Range(1, 4).Select(index => new Entities.TestResponse
{
response.result = "No Error",
response.exception = "None"
}).ToArray();
}
Your syntax is wrong, you need to also new up the inner object, for example:
new Entities.TestResponse
{
response = new Entities.TestResponseNodes
{
result = "No Error",
exception = "None"
}
}
As an aside, you should follow common C# conventions and capitalise your property names, for example:
public class TestResponse
{
public TestResponseNodes Response;
}
public class TestResponseNodes
{
public string Result { get; set; }
public string Exception { get; set; }
}

Custom error objects for .Net Core 3 web api

I am currently developing a web api in .NET Core 3. I currently have the following model for my error response object:
public class ErrorRo
{
public string Message { get; set; }
public int StatusCode { get; set; }
public string Endpoint { get; set; }
public string Parameters { get; set; }
public string IpAddress { get; set; }
}
This is a mandated response I need to implement, management has pushed this. It allows more verbose error messages for people hitting our API so that they know what went wrong.
At the moment I am currently populating this object manually in the methods themselves. Is there a way where I can overwrite the response methods. I.e. can I override the BadRequest of IActionResult to automatically populate these fields?
Thanks!
You can use result filters for this purpose. Add a filter which repalces result before sending it back
Model
public class CustomErroModel
{
public string Message { get; set; }
public int StatusCode { get; set; }
public string Endpoint { get; set; }
public string Parameters { get; set; }
public string IpAddress { get; set; }
}
Filter
public class BadRequestCustomErrorFilterAttribute : ResultFilterAttribute
{
public override void OnResultExecuting(ResultExecutingContext context)
{
//todo: check for BadRequestObjectResult if anything is returned for bad request
if (context.Result is BadRequestResult)
{
var result = new CustomErroModel
{
StatusCode = 200, //you status code
Endpoint = context.HttpContext.Request.GetDisplayUrl(),
Message = "some message",
IpAddress = context.HttpContext.Connection.RemoteIpAddress.ToString(), //find better implementation in case of proxy
//this returns only parameters that controller expects but not those are not defined in model
Parameters = string.Join(", ", context.ModelState.Select(v => $"{v.Key}={v.Value.AttemptedValue}"))
};
context.Result = new OkObjectResult(result); // or any other ObjectResult
}
}
}
Then apply filter per action or globally
[BadRequestCustomErrorFilter]
public IActionResult SomeAction(SomeModel model)
or
services
.AddMvc(options =>
{
options.Filters.Add<BadRequestCustomErrorFilterAttribute>();
//...
}
Well it depends on the scenario, but one possible approach could be to use a middleware using a similar strategy like the one described in this question, so that you complete the response with extra information.

Deserialize RESTSharp JSON Response

I'm working on a project to gather data from NOAA. I'm having trouble figuring out how to make the response usable.
This is what NOAA's API response looks like to my call:
{
"metadata": {
"resultset": {
"offset": 1,
"count": 38859,
"limit": 2
}
},
"results": [
{
"mindate": "1983-01-01",
"maxdate": "2019-12-24",
"name": "Abu Dhabi, AE",
"datacoverage": 1,
"id": "CITY:AE000001"
},
{
"mindate": "1944-03-01",
"maxdate": "2019-12-24",
"name": "Ajman, AE",
"datacoverage": 0.9991,
"id": "CITY:AE000002"
}
]
}
I used JSON2CSharp.com to convert the result set into my needed classes. Below is the relevant code:
public class NOAA
{
public class Resultset
{
public int offset { get; set; }
public int count { get; set; }
public int limit { get; set; }
}
public class Metadata
{
public Resultset resultset { get; set; }
}
public class Location
{
public string mindate { get; set; }
public string maxdate { get; set; }
public string name { get; set; }
public double datacoverage { get; set; }
public string id { get; set; }
}
public class RootObject
{
public Metadata metadata { get; set; }
public List<Location> results { get; set; }
}
public class Response
{
IList<Metadata> metadata;
IList<Location> results;
}
public void RestFactory(string Token, string Endpoint, Dictionary<string, string> Params)
{
// Initiate the REST request
var client = new RestClient("https://www.ncdc.noaa.gov/cdo-web/api/v2/" + Endpoint);
var request = new RestRequest(Method.GET);
// Add the token
request.AddHeader("token", Token);
// Add the parameters
foreach (KeyValuePair<string, string> entry in Params)
{
request.AddParameter(entry.Key, entry.Value);
}
// Execute the REST request
var response = client.Execute(request);
// Deserialize the response
Response noaa = new JsonDeserializer().Deserialize<Response>(response);
// Print to console
foreach (Location loc in noaa)
{
Console.WriteLine(loc.name);
}
}
}
At this point, I'm just trying to print the location name to reach my next learning milestone. I'm getting the error:
Severity Code Description Project File Line Suppression State
Error CS1579 foreach statement cannot operate on variables of type 'NOAA.Response' because 'NOAA.Response' does not contain a public instance definition for 'GetEnumerator'
Other than the error, I think I don't quite understand the proper approach since the response has more than one "layer". Guidance?
Your foreach loop is trying to call an iterator on the object itself, not the list inside it.
Try this instead
foreach (Location loc in noaa.results)
{
Console.WriteLine(loc.name);
}
I can tell you the cause of error. That is because noaa is not iterable. If you want to iterate over any object then it needs to implement IEnumerable interface. This is the reason because of which noaa is not iterable. noaa does not inherit this interface or implement it. Do you get the same error if you use noaa.results?

Operation is not valid due to the current state of the object (System.Text.Json)

We've got an API, which simply posts incoming JSON documents to a message bus, having assigned a GUID to each. We're upgrading from .Net Core 2.2 to 3.1 and were aiming to replace NewtonSoft with the new System.Text.Json library.
We deserialise the incoming document, assign the GUID to one of the fields and then reserialise before sending to the message bus. Unfortunately, the reserialisation is failing with the exception Operation is not valid due to the current state of the object.
Here's a controller that shows the problem:-
using System;
using System.Net;
using Project.Models;
using Microsoft.AspNetCore.Mvc;
using System.IO;
using System.Text;
using System.Text.Json;
namespace Project.Controllers
{
[Route("api/test")]
public class TestController : Controller
{
private const string JSONAPIMIMETYPE = "application/vnd.api+json";
public TestController()
{
}
[HttpPost("{eventType}")]
public async System.Threading.Tasks.Task<IActionResult> ProcessEventAsync([FromRoute] string eventType)
{
try
{
JsonApiMessage payload;
using (StreamReader reader = new StreamReader(Request.Body, Encoding.UTF8)) {
string payloadString = await reader.ReadToEndAsync();
try {
payload = JsonSerializer.Deserialize<JsonApiMessage>(payloadString);
}
catch (Exception ex) {
return StatusCode((int)HttpStatusCode.BadRequest);
}
}
if ( ! Request.ContentType.Contains(JSONAPIMIMETYPE) )
{
return StatusCode((int)HttpStatusCode.UnsupportedMediaType);
}
Guid messageID = Guid.NewGuid();
payload.Data.Id = messageID.ToString();
// we would send the message here but for this test, just reserialise it
string reserialisedPayload = JsonSerializer.Serialize(payload);
Request.HttpContext.Response.ContentType = JSONAPIMIMETYPE;
return Accepted(payload);
}
catch (Exception ex)
{
return StatusCode((int)HttpStatusCode.InternalServerError);
}
}
}
}
The JsonApiMessage object is defined like this:-
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Project.Models
{
public class JsonApiMessage
{
[JsonPropertyName("data")]
public JsonApiData Data { get; set; }
[JsonPropertyName("included")]
public JsonApiData[] Included { get; set; }
}
public class JsonApiData
{
[JsonPropertyName("type")]
public string Type { get; set; }
[JsonPropertyName("id")]
public string Id { get; set; }
[JsonPropertyName("attributes")]
public JsonElement Attributes { get; set; }
[JsonPropertyName("meta")]
public JsonElement Meta { get; set; }
[JsonPropertyName("relationships")]
public JsonElement Relationships { get; set; }
}
}
An example call looks like this:-
POST http://localhost:5000/api/test/event
Content-Type: application/vnd.api+json; charset=UTF-8
{
"data": {
"type": "test",
"attributes": {
"source": "postman",
"instance": "jg",
"level": "INFO",
"message": "If this comes back with an ID, the API is probably working"
}
}
}
When I examine the contents of payload at a breakpoint in Visual Studio, it looks OK at the top level but the JsonElement bits look opaque, so I don't know if they've been parsed properly. Their structure can vary, so we only care that they are valid JSON. In the old NewtonSoft version, they were JObjects.
After the GUID has been added, it appears in the payload object when examined at a breakpoint but I'm suspicious that the problem is related to other elements in the object being read-only or something similar.
Your problem can be reproduced with the following more minimal example. Define the following model:
public class JsonApiMessage
{
public JsonElement data { get; set; }
}
Then attempt to deserialize and re-serialize an empty JSON object like so:
var payload = JsonSerializer.Deserialize<JsonApiMessage>("{}");
var newJson = JsonSerializer.Serialize(payload, new JsonSerializerOptions { WriteIndented = true });
And you will get an exception (demo fiddle #1 here):
System.InvalidOperationException: Operation is not valid due to the current state of the object.
at System.Text.Json.JsonElement.WriteTo(Utf8JsonWriter writer)
at System.Text.Json.Serialization.Converters.JsonConverterJsonElement.Write(Utf8JsonWriter writer, JsonElement value, JsonSerializerOptions options)
The problem seems to be that JsonElement is a struct, and the default value for this struct can't be serialized. In fact, simply doing JsonSerializer.Serialize(new JsonElement()); throws the same exception (demo fiddle #2 here). (This contrasts with JObject which is a reference type whose default value is, of course, null.)
So, what are your options? You could make all your JsonElement properties be nullable, and set IgnoreNullValues = true while re-serializing:
public class JsonApiData
{
[JsonPropertyName("type")]
public string Type { get; set; }
[JsonPropertyName("id")]
public string Id { get; set; }
[JsonPropertyName("attributes")]
public JsonElement? Attributes { get; set; }
[JsonPropertyName("meta")]
public JsonElement? Meta { get; set; }
[JsonPropertyName("relationships")]
public JsonElement? Relationships { get; set; }
}
And then:
var reserialisedPayload = JsonSerializer.Serialize(payload, new JsonSerializerOptions { IgnoreNullValues = true });
Demo fiddle #3 here.
Or, in .NET 5 or later, you could mark all of your JsonElement properties with [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]:
public class JsonApiData
{
// Remainder unchanged
[JsonPropertyName("attributes")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public JsonElement Attributes { get; set; }
[JsonPropertyName("meta")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public JsonElement Meta { get; set; }
[JsonPropertyName("relationships")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public JsonElement Relationships { get; set; }
}
Doing so will cause uninitialized elements to be skipped during serialization without needing to modify serialization options.
Demo fiddle #4 here.
Or, you could simplify your data model by binding all the JSON properties other than Id to a JsonExtensionData property like so:
public class JsonApiData
{
[JsonPropertyName("id")]
public string Id { get; set; }
[JsonExtensionData]
public Dictionary<string, JsonElement> ExtensionData { get; set; }
}
This approach avoids the need to manually set IgnoreNullValues when re-serializing, and thus ASP.NET Core will re-serialize the model correctly automatically.
Demo fiddle #5 here.
The exception is right - the state of the object is invalid. The Meta and Relasionships elements are non-nullable but the JSON string doesn't contain them. The deserialized object ends up with Undefined values in those properties that can't be serialized.
[JsonPropertyName("meta")]
public JsonElement? Meta { get; set; }
[JsonPropertyName("relationships")]
public JsonElement? Relationships { get; set; }
The quick fix would be to change those properties to JsonElement?. This will allow correct deserialization and serialization. By default, the missing elements will be emitted as nulls:
"meta": null,
"relationships": null
To ignore them, add the IgnoreNullValues =true option :
var newJson = JsonSerializer.Serialize(payload, new JsonSerializerOptions
{ WriteIndented = true,IgnoreNullValues =true });
The real solution though would be to get rid of all that code. It hampers the use of System.Text.Json. Left by itself, ASP.NET Core uses Pipelines to read the input stream without allocating, deserializes the payload and calls the method with the deserialized object as a parameter, using minimal allocations. Any return values are serialized in the same way.
The question's code though allocates a lot - it caches the input in the StreamReader, then the entire payload is cached in the payloadString and then again, as the payload object. The reverse process also uses temporary strings. This code takes at least twice as much RAM as ASP.NET Core would use.
The action code should be just :
[HttpPost("{eventType}")]
public async Task<IActionResult> ProcessEventAsync([FromRoute] string eventType,
MyApiData payload)
{
Guid messageID = Guid.NewGuid();
payload.Data.Id = messageID.ToString();
return Accepted(payload);
}
Where MyApiData is a strongly-typed object. The shape of the Json example corresponds to :
public class Attributes
{
public string source { get; set; }
public string instance { get; set; }
public string level { get; set; }
public string message { get; set; }
}
public class Data
{
public string type { get; set; }
public Attributes attributes { get; set; }
}
public class MyApiData
{
public Data data { get; set; }
public Data[] included {get;set;}
}
All other checks are performed by ASP.NET Core itself - ASP.NET Core will reject any POST that doesn't have the correct MIME type. It will return a 400 if the request is badly formatted. It will return a 500 if the code throws

ASP.NET Core FromBody Model Binding: Bind a Class with Interafece Field

I've been struggling a lot with that, I found some questions but none could answer my needs. I will try to post a better question and some of the things I tried.
Here is the situation:
I have an APIGateway and a WebApp. The WebApp sends POST requests to the APIGateway, so far so good. I use the FromBody attribute to send larger objects, and that was fine too until I introduced interfaces :))
Here's some code:
WebApp:
public interface ICommand
{
Guid CorrelationId { get; set; }
Guid SocketId { get; set; }
}
public class Command : ICommand
{
public Command(Guid CorrelationId, Guid SocketId)
{
this.CorrelationId = CorrelationId;
this.SocketId = SocketId;
}
public Guid CorrelationId { get; set; } = new Guid();
public Guid SocketId { get; set; } = new Guid();
}
public interface IDocument
{
Guid Id { get; set; }
ulong Number { get; set; }
}
public class Document : IDocument
{
public Guid Id { get; set; } = new Guid();
public ulong Number { get; set; } = 0;
}
public interface ICreateDocumentCommand : ICommand
{
IDocument Document { get; set; }
}
public class CreateDocumentCommand : Command, ICreateDocumentCommand
{
public CreateDocumentCommand(IDocument Document, ICommand Command) : base(Command.CorrelationId, Command.SocketId)
{
this.Document = Document;
}
public IDocument Document { get; set; }
}
APIGateway:
[HttpPost]
public async Task<IActionResult> Create([FromBody]CreateDocumentCommand documentCommand)
{
if (documentCommand == null)
{
return StatusCode(403);
}
return Json(documentCommand.Document.Id);
}
Use case:
public class InventoryList : Document
{
public Guid WarehouseId { get; set; } = new Guid();
}
// Example document class
////////////////////////////////////////
// Example POST Request
ICommand command = new Command(messageId, socketId);
switch (item.GetType().Name)
{
case "InventoryList":
command = new CreateDocumentCommand((InventoryList)item, command);
break;
}
string result = await PostAsync($"{apiGatewayAddress}{item.GetType().BaseType.Name}/Create", command, accessToken);
My POST sending function:
public async Task<string> PostAsync<T>(string uri, T item, string authorizationToken = null, string authorizationMethod = "Bearer")
{
JsonSerializerSettings jsonSerializerSettings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All };
HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Post, uri);
requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
requestMessage.Content = new StringContent(JsonConvert.SerializeObject(item, typeof(T), jsonSerializerSettings), System.Text.Encoding.UTF8, "application/json");
return await _client.SendAsync(requestMessage).Result.Content.ReadAsStringAsync();
}
As you can see I have included TypeNameHandling.All in the JSON serialization settings, the request is sent and the Create in the APIGateway gets called. However the parameter documentCommand is NULL.
I've read this: Asp.Net Core Post FromBody Always Null
This: ASP.NET Core MVC - Model Binding : Bind an interface model using the attribute [FromBody] (BodyModelBinder)
This: Casting interfaces for deserialization in JSON.NET
Tried all kind of magic tricks, created new constructors, marked them with [JSONConstructor], still no success. Also I tried changing the APIGateway Cerate method parameter type to ICreateDocumentCommand and again I got a null. I've been searching some model binding tricks online however I couldn't find anything for binding with FromBody. I also found some solution including DI but I am looking for a simple solution. I hope that we will be able to find one :)
Turns out, passing interfaces or classes with interfaces inside as JSON is not that easy. I added a custom JSONConverter and it works now!

Categories